From c9ca2d4afa6a81203da68bc8ca4cbbf0487eaa29 Mon Sep 17 00:00:00 2001 From: Adrien Piquerez Date: Mon, 12 Jul 2021 14:20:17 +0200 Subject: [PATCH 1/5] Use `-Dsbt.script` to start sbt server In order to start the sbt server we must use the sbt script because it is the only way to load the .sbtopts and the .jvmopts file properly. To do so the sbt script can pass a -Dsbt.script prop to the java server. It is used in the NetworkClient to start the server, and it is replicated in the BuildServerConnection file (.bsp/sbt.json). --- .../sbt/internal/client/NetworkClient.scala | 15 +++-- .../internal/bsp/BuildServerConnection.scala | 60 ++++++++++++------- sbt | 11 ++++ 3 files changed, 61 insertions(+), 25 deletions(-) diff --git a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala index d6b376dac..d9514a323 100644 --- a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala +++ b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala @@ -1045,7 +1045,8 @@ object NetworkClient { private[client] val noStdErr = "--no-stderr" private[client] val sbtBase = "--sbt-base-directory" private[client] def parseArgs(args: Array[String]): Arguments = { - var sbtScript = if (Properties.isWin) "sbt.bat" else "sbt" + val defaultSbtScript = if (Properties.isWin) "sbt.bat" else "sbt" + var sbtScript = Properties.propOrNone("sbt.script") var launchJar: Option[String] = None var bsp = false val commandArgs = new mutable.ArrayBuffer[String] @@ -1067,11 +1068,10 @@ object NetworkClient { sbtScript = a .split("--sbt-script=") .lastOption - .map(_.replaceAllLiterally("%20", " ")) - .getOrElse(sbtScript) + .orElse(sbtScript) case "--sbt-script" if i + 1 < sanitized.length => i += 1 - sbtScript = sanitized(i).replaceAllLiterally("%20", " ") + sbtScript = Some(sanitized(i)) case a if a.startsWith("--sbt-launch-jar=") => launchJar = a .split("--sbt-launch-jar=") @@ -1091,12 +1091,17 @@ object NetworkClient { } val base = new File("").getCanonicalFile if (!sbtArguments.contains("-Dsbt.io.virtual=true")) sbtArguments += "-Dsbt.io.virtual=true" + if (!sbtArguments.exists(_.startsWith("-Dsbt.script"))) { + sbtScript.foreach { sbtScript => + sbtArguments += s"-Dsbt.script=$sbtScript" + } + } new Arguments( base, sbtArguments.toSeq, commandArgs.toSeq, completionArguments.toSeq, - sbtScript, + sbtScript.getOrElse(defaultSbtScript).replaceAllLiterally("%20", " "), bsp, launchJar ) diff --git a/protocol/src/main/scala/sbt/internal/bsp/BuildServerConnection.scala b/protocol/src/main/scala/sbt/internal/bsp/BuildServerConnection.scala index 9b21dd31e..9ee2da80c 100644 --- a/protocol/src/main/scala/sbt/internal/bsp/BuildServerConnection.scala +++ b/protocol/src/main/scala/sbt/internal/bsp/BuildServerConnection.scala @@ -7,12 +7,13 @@ package sbt.internal.bsp -import java.io.File - -import sbt.internal.bsp +import sbt.internal.bsp.codec.JsonProtocol.BspConnectionDetailsFormat import sbt.io.IO import sjsonnew.support.scalajson.unsafe.{ CompactPrinter, Converter } +import java.io.File +import java.nio.file.{ Files, Paths } + object BuildServerConnection { final val name = "sbt" final val bspVersion = "2.0.0-M5" @@ -21,26 +22,45 @@ object BuildServerConnection { private final val SbtLaunchJar = "sbt-launch(-.*)?\\.jar".r private[sbt] def writeConnectionFile(sbtVersion: String, baseDir: File): Unit = { - import bsp.codec.JsonProtocol._ val bspConnectionFile = new File(baseDir, ".bsp/sbt.json") - val javaHome = System.getProperty("java.home") - val classPath = System.getProperty("java.class.path") - val sbtLaunchJar = classPath - .split(File.pathSeparator) - .find(jar => SbtLaunchJar.findFirstIn(jar).nonEmpty) - .map(_.replaceAllLiterally(" ", "%20")) - val argv = - Vector( - s"$javaHome/bin/java", - "-Xms100m", - "-Xmx100m", - "-classpath", - classPath, - "xsbt.boot.Boot", - "-bsp" - ) ++ sbtLaunchJar.map(jar => s"--sbt-launch-jar=$jar") + val argv = Option(System.getProperty("sbt.script")) + .map(_.replaceAllLiterally("%20", " ")) + .orElse(sbtScriptInPath) match { + case Some(sbtScript) => + Vector(sbtScript, "-bsp", s"-Dsbt.script=${sbtScript.replaceAllLiterally(" ", "%20")}") + case None => + // IntelliJ can start sbt even if the sbt script is not accessible from $PATH. + // To do so it uses its own bundled sbt-launch.jar. + // In that case, we must pass the path of the sbt-launch.jar to the BSP connection + // so that the server can be started. + // A known problem in that situation is that the .sbtopts and .jvmopts are not loaded. + val javaHome = System.getProperty("java.home") + val classPath = System.getProperty("java.class.path") + val sbtLaunchJar = classPath + .split(File.pathSeparator) + .find(jar => SbtLaunchJar.findFirstIn(jar).nonEmpty) + .map(_.replaceAllLiterally(" ", "%20")) + + Vector( + s"$javaHome/bin/java", + "-Xms100m", + "-Xmx100m", + "-classpath", + classPath, + "xsbt.boot.Boot", + "-bsp" + ) ++ sbtLaunchJar.map(jar => s"--sbt-launch-jar=$jar") + } val details = BspConnectionDetails(name, sbtVersion, bspVersion, languages, argv) val json = Converter.toJson(details).get IO.write(bspConnectionFile, CompactPrinter(json), append = false) } + + private def sbtScriptInPath: Option[String] = { + // For those who use an old sbt script, the -Dsbt.script is not set + // As a fallback we try to find the sbt script in $PATH + val envPath = Option(System.getenv("PATH")).getOrElse("") + val allPaths = envPath.split(File.pathSeparator).map(Paths.get(_)) + allPaths.map(_.resolve("sbt")).find(Files.exists(_)).map(_.toString) + } } diff --git a/sbt b/sbt index 8adff2117..990515d6c 100755 --- a/sbt +++ b/sbt @@ -299,6 +299,16 @@ addDefaultMemory() { fi } +addSbtScriptProperty () { + if [[ "${java_args[@]}" == -Dsbt.script=* ]]; then + : + else + sbt_script=$0 + sbt_script=${sbt_script/ /%20} + addJava "-Dsbt.script=$sbt_script" + fi +} + require_arg () { local type="$1" local opt="$2" @@ -769,6 +779,7 @@ else java_version="$(jdk_version)" vlog "[process_args] java_version = '$java_version'" addDefaultMemory + addSbtScriptProperty set -- "${residual_args[@]}" argumentCount=$# run From c31503cca291c5c07211e85f0cf4d89b65814ffc Mon Sep 17 00:00:00 2001 From: Adrien Piquerez Date: Wed, 14 Jul 2021 10:23:44 +0200 Subject: [PATCH 2/5] Warn when using -sbt-launch-jar --- .../scala/sbt/internal/client/NetworkClient.scala | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala index d9514a323..3ae676893 100644 --- a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala +++ b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala @@ -330,6 +330,18 @@ class NetworkClient( val cmd = arguments.sbtLaunchJar match { case Some(lj) => + if (log) { + val sbtScript = if (Properties.isWin) "sbt.bat" else "sbt" + console.appendLog(Level.Warn, s"server is started using sbt-launch jar directly") + console.appendLog( + Level.Warn, + "this is not the recommended way: .sbtopts and .jvmopts files are not loaded and SBT_OPTS is ignored" + ) + console.appendLog( + Level.Warn, + s"either upgrade $sbtScript to its latest version or make sure it is accessible from $$PATH, and run 'sbt bspConfig'" + ) + } List("java") ++ arguments.sbtArguments ++ List("-jar", lj, DashDashDetachStdio, DashDashServer) case _ => From c4e6cf54d29857d9dcf40c085ebfdaf11887976c Mon Sep 17 00:00:00 2001 From: Adrien Piquerez Date: Wed, 14 Jul 2021 10:24:52 +0200 Subject: [PATCH 3/5] Use java to start BSP client --- .../internal/bsp/BuildServerConnection.scala | 60 ++++++++++--------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/protocol/src/main/scala/sbt/internal/bsp/BuildServerConnection.scala b/protocol/src/main/scala/sbt/internal/bsp/BuildServerConnection.scala index 9ee2da80c..593d925e3 100644 --- a/protocol/src/main/scala/sbt/internal/bsp/BuildServerConnection.scala +++ b/protocol/src/main/scala/sbt/internal/bsp/BuildServerConnection.scala @@ -13,6 +13,7 @@ import sjsonnew.support.scalajson.unsafe.{ CompactPrinter, Converter } import java.io.File import java.nio.file.{ Files, Paths } +import scala.util.Properties object BuildServerConnection { final val name = "sbt" @@ -23,34 +24,34 @@ object BuildServerConnection { private[sbt] def writeConnectionFile(sbtVersion: String, baseDir: File): Unit = { val bspConnectionFile = new File(baseDir, ".bsp/sbt.json") - val argv = Option(System.getProperty("sbt.script")) - .map(_.replaceAllLiterally("%20", " ")) - .orElse(sbtScriptInPath) match { - case Some(sbtScript) => - Vector(sbtScript, "-bsp", s"-Dsbt.script=${sbtScript.replaceAllLiterally(" ", "%20")}") - case None => - // IntelliJ can start sbt even if the sbt script is not accessible from $PATH. - // To do so it uses its own bundled sbt-launch.jar. - // In that case, we must pass the path of the sbt-launch.jar to the BSP connection - // so that the server can be started. - // A known problem in that situation is that the .sbtopts and .jvmopts are not loaded. - val javaHome = System.getProperty("java.home") - val classPath = System.getProperty("java.class.path") - val sbtLaunchJar = classPath - .split(File.pathSeparator) - .find(jar => SbtLaunchJar.findFirstIn(jar).nonEmpty) - .map(_.replaceAllLiterally(" ", "%20")) + val javaHome = System.getProperty("java.home") + val classPath = System.getProperty("java.class.path") - Vector( - s"$javaHome/bin/java", - "-Xms100m", - "-Xmx100m", - "-classpath", - classPath, - "xsbt.boot.Boot", - "-bsp" - ) ++ sbtLaunchJar.map(jar => s"--sbt-launch-jar=$jar") - } + val sbtScript = Option(System.getProperty("sbt.script")) + .orElse(sbtScriptInPath) + .map(script => s"-Dsbt.script=$script") + + // IntelliJ can start sbt even if the sbt script is not accessible from $PATH. + // To do so it uses its own bundled sbt-launch.jar. + // In that case, we must pass the path of the sbt-launch.jar to the BSP connection + // so that the server can be started. + // A known problem in that situation is that the .sbtopts and .jvmopts are not loaded. + val sbtLaunchJar = classPath + .split(File.pathSeparator) + .find(jar => SbtLaunchJar.findFirstIn(jar).nonEmpty) + .map(_.replaceAllLiterally(" ", "%20")) + .map(jar => s"--sbt-launch-jar=$jar") + + val argv = + Vector( + s"$javaHome/bin/java", + "-Xms100m", + "-Xmx100m", + "-classpath", + classPath, + "xsbt.boot.Boot", + "-bsp" + ) ++ sbtScript.orElse(sbtLaunchJar) val details = BspConnectionDetails(name, sbtVersion, bspVersion, languages, argv) val json = Converter.toJson(details).get IO.write(bspConnectionFile, CompactPrinter(json), append = false) @@ -61,6 +62,9 @@ object BuildServerConnection { // As a fallback we try to find the sbt script in $PATH val envPath = Option(System.getenv("PATH")).getOrElse("") val allPaths = envPath.split(File.pathSeparator).map(Paths.get(_)) - allPaths.map(_.resolve("sbt")).find(Files.exists(_)).map(_.toString) + allPaths + .map(_.resolve("sbt")) + .find(file => Files.exists(file)) + .map(_.toString.replaceAllLiterally(" ", "%20")) } } From 2287f3e3844271d8391075198b1dff5452df6aa3 Mon Sep 17 00:00:00 2001 From: Adrien Piquerez Date: Wed, 14 Jul 2021 10:25:20 +0200 Subject: [PATCH 4/5] Find sbt.bat in windows $PATH --- .../main/scala/sbt/internal/bsp/BuildServerConnection.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/protocol/src/main/scala/sbt/internal/bsp/BuildServerConnection.scala b/protocol/src/main/scala/sbt/internal/bsp/BuildServerConnection.scala index 593d925e3..85c2bd3cb 100644 --- a/protocol/src/main/scala/sbt/internal/bsp/BuildServerConnection.scala +++ b/protocol/src/main/scala/sbt/internal/bsp/BuildServerConnection.scala @@ -60,11 +60,12 @@ object BuildServerConnection { private def sbtScriptInPath: Option[String] = { // For those who use an old sbt script, the -Dsbt.script is not set // As a fallback we try to find the sbt script in $PATH + val fileName = if (Properties.isWin) "sbt.bat" else "sbt" val envPath = Option(System.getenv("PATH")).getOrElse("") val allPaths = envPath.split(File.pathSeparator).map(Paths.get(_)) allPaths - .map(_.resolve("sbt")) - .find(file => Files.exists(file)) + .map(_.resolve(fileName)) + .find(file => Files.exists(file) && Files.isExecutable(file)) .map(_.toString.replaceAllLiterally(" ", "%20")) } } From 966633aa8192ef285fe3dac05b318a174867b08e Mon Sep 17 00:00:00 2001 From: Adrien Piquerez Date: Wed, 14 Jul 2021 10:32:02 +0200 Subject: [PATCH 5/5] Fix duplicated -Dsbt.script --- sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sbt b/sbt index 990515d6c..7fb865514 100755 --- a/sbt +++ b/sbt @@ -300,7 +300,7 @@ addDefaultMemory() { } addSbtScriptProperty () { - if [[ "${java_args[@]}" == -Dsbt.script=* ]]; then + if [[ "${java_args[@]}" == *-Dsbt.script=* ]]; then : else sbt_script=$0