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