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).
This commit is contained in:
Adrien Piquerez 2021-07-12 14:20:17 +02:00
parent 5f4798fa76
commit c9ca2d4afa
3 changed files with 61 additions and 25 deletions

View File

@ -1045,7 +1045,8 @@ object NetworkClient {
private[client] val noStdErr = "--no-stderr" private[client] val noStdErr = "--no-stderr"
private[client] val sbtBase = "--sbt-base-directory" private[client] val sbtBase = "--sbt-base-directory"
private[client] def parseArgs(args: Array[String]): Arguments = { 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 launchJar: Option[String] = None
var bsp = false var bsp = false
val commandArgs = new mutable.ArrayBuffer[String] val commandArgs = new mutable.ArrayBuffer[String]
@ -1067,11 +1068,10 @@ object NetworkClient {
sbtScript = a sbtScript = a
.split("--sbt-script=") .split("--sbt-script=")
.lastOption .lastOption
.map(_.replaceAllLiterally("%20", " ")) .orElse(sbtScript)
.getOrElse(sbtScript)
case "--sbt-script" if i + 1 < sanitized.length => case "--sbt-script" if i + 1 < sanitized.length =>
i += 1 i += 1
sbtScript = sanitized(i).replaceAllLiterally("%20", " ") sbtScript = Some(sanitized(i))
case a if a.startsWith("--sbt-launch-jar=") => case a if a.startsWith("--sbt-launch-jar=") =>
launchJar = a launchJar = a
.split("--sbt-launch-jar=") .split("--sbt-launch-jar=")
@ -1091,12 +1091,17 @@ object NetworkClient {
} }
val base = new File("").getCanonicalFile val base = new File("").getCanonicalFile
if (!sbtArguments.contains("-Dsbt.io.virtual=true")) sbtArguments += "-Dsbt.io.virtual=true" 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( new Arguments(
base, base,
sbtArguments.toSeq, sbtArguments.toSeq,
commandArgs.toSeq, commandArgs.toSeq,
completionArguments.toSeq, completionArguments.toSeq,
sbtScript, sbtScript.getOrElse(defaultSbtScript).replaceAllLiterally("%20", " "),
bsp, bsp,
launchJar launchJar
) )

View File

@ -7,12 +7,13 @@
package sbt.internal.bsp package sbt.internal.bsp
import java.io.File import sbt.internal.bsp.codec.JsonProtocol.BspConnectionDetailsFormat
import sbt.internal.bsp
import sbt.io.IO import sbt.io.IO
import sjsonnew.support.scalajson.unsafe.{ CompactPrinter, Converter } import sjsonnew.support.scalajson.unsafe.{ CompactPrinter, Converter }
import java.io.File
import java.nio.file.{ Files, Paths }
object BuildServerConnection { object BuildServerConnection {
final val name = "sbt" final val name = "sbt"
final val bspVersion = "2.0.0-M5" final val bspVersion = "2.0.0-M5"
@ -21,26 +22,45 @@ object BuildServerConnection {
private final val SbtLaunchJar = "sbt-launch(-.*)?\\.jar".r private final val SbtLaunchJar = "sbt-launch(-.*)?\\.jar".r
private[sbt] def writeConnectionFile(sbtVersion: String, baseDir: File): Unit = { private[sbt] def writeConnectionFile(sbtVersion: String, baseDir: File): Unit = {
import bsp.codec.JsonProtocol._
val bspConnectionFile = new File(baseDir, ".bsp/sbt.json") val bspConnectionFile = new File(baseDir, ".bsp/sbt.json")
val javaHome = System.getProperty("java.home") val argv = Option(System.getProperty("sbt.script"))
val classPath = System.getProperty("java.class.path") .map(_.replaceAllLiterally("%20", " "))
val sbtLaunchJar = classPath .orElse(sbtScriptInPath) match {
.split(File.pathSeparator) case Some(sbtScript) =>
.find(jar => SbtLaunchJar.findFirstIn(jar).nonEmpty) Vector(sbtScript, "-bsp", s"-Dsbt.script=${sbtScript.replaceAllLiterally(" ", "%20")}")
.map(_.replaceAllLiterally(" ", "%20")) case None =>
val argv = // IntelliJ can start sbt even if the sbt script is not accessible from $PATH.
Vector( // To do so it uses its own bundled sbt-launch.jar.
s"$javaHome/bin/java", // In that case, we must pass the path of the sbt-launch.jar to the BSP connection
"-Xms100m", // so that the server can be started.
"-Xmx100m", // A known problem in that situation is that the .sbtopts and .jvmopts are not loaded.
"-classpath", val javaHome = System.getProperty("java.home")
classPath, val classPath = System.getProperty("java.class.path")
"xsbt.boot.Boot", val sbtLaunchJar = classPath
"-bsp" .split(File.pathSeparator)
) ++ sbtLaunchJar.map(jar => s"--sbt-launch-jar=$jar") .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 details = BspConnectionDetails(name, sbtVersion, bspVersion, languages, argv)
val json = Converter.toJson(details).get val json = Converter.toJson(details).get
IO.write(bspConnectionFile, CompactPrinter(json), append = false) 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)
}
} }

11
sbt
View File

@ -299,6 +299,16 @@ addDefaultMemory() {
fi 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 () { require_arg () {
local type="$1" local type="$1"
local opt="$2" local opt="$2"
@ -769,6 +779,7 @@ else
java_version="$(jdk_version)" java_version="$(jdk_version)"
vlog "[process_args] java_version = '$java_version'" vlog "[process_args] java_version = '$java_version'"
addDefaultMemory addDefaultMemory
addSbtScriptProperty
set -- "${residual_args[@]}" set -- "${residual_args[@]}"
argumentCount=$# argumentCount=$#
run run