mirror of https://github.com/sbt/sbt.git
fix: Use sbt script in BSP config instead of hardcoded Java path (#8920)
**Problem** sbt bspConfig writes the absolute path of the current Java binary into .bsp/sbt.json. When the user switches Java versions (via sdkman, cs java, etc.) or removes that JDK, the IDE fails to start the sbt BSP server because the hardcoded path is stale or gone. **Solution** When an sbt launcher script is available (via `sbt.script` system property or PATH lookup), generate: "argv": ["/path/to/sbt", "bsp"]
This commit is contained in:
parent
f92c06155c
commit
be305eb3a5
|
|
@ -49,8 +49,14 @@ object BspConfigTest extends BasicTestSuite:
|
|||
|
||||
// Verify argv structure
|
||||
assert(argv.nonEmpty, "argv should not be empty")
|
||||
assert(argv.head.contains("java"), s"argv should start with java command, got: ${argv.head}")
|
||||
assert(argv.contains("-bsp"), s"argv should contain -bsp flag, got: $argv")
|
||||
// When sbt script is available, argv uses the sbt script with "bsp" command.
|
||||
// When not, argv falls back to direct java invocation with "-bsp" flag.
|
||||
val usesSbtScript = argv.last == "bsp" && !argv.head.contains("java")
|
||||
val usesJavaDirect = argv.head.contains("java") && argv.contains("-bsp")
|
||||
assert(
|
||||
usesSbtScript || usesJavaDirect,
|
||||
s"argv should either use sbt script with 'bsp' command or java with '-bsp' flag, got: $argv"
|
||||
)
|
||||
|
||||
// Test execution of the generated argv
|
||||
// Run the BSP command with a very short timeout to verify it starts correctly
|
||||
|
|
|
|||
|
|
@ -442,6 +442,11 @@ if "%~0" == "shutdownall" (
|
|||
goto args_loop
|
||||
)
|
||||
|
||||
if "%~0" == "bsp" (
|
||||
set sbt_args_client=0
|
||||
goto args_loop
|
||||
)
|
||||
|
||||
if "%~0" == "--script-version" (
|
||||
set sbt_args_print_sbt_script_version=1
|
||||
goto args_loop
|
||||
|
|
|
|||
|
|
@ -1306,7 +1306,7 @@ object NetworkClient {
|
|||
case "--sbt-launch-jar" if i + 1 < sanitized.length =>
|
||||
i += 1
|
||||
launchJar = Option(sanitized(i).replace("%20", " "))
|
||||
case "-bsp" | "--bsp" => bsp = true
|
||||
case "-bsp" | "--bsp" | "bsp" => bsp = true
|
||||
case "-no-server" | "--no-server" =>
|
||||
System.setProperty("sbt.server.autostart", "false")
|
||||
case a if a.startsWith("--autostart=") =>
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ private[sbt] object xMain:
|
|||
.map(_.trim)
|
||||
.filterNot(_ == DashDashServer)
|
||||
val isClient: String => Boolean = cmd => (cmd == JavaClient) || (cmd == DashDashClient)
|
||||
val isBsp: String => Boolean = cmd => (cmd == "-bsp") || (cmd == "--bsp")
|
||||
val isBsp: String => Boolean = cmd => (cmd == "-bsp") || (cmd == "--bsp") || (cmd == "bsp")
|
||||
val isNew: String => Boolean = cmd => (cmd == "new") || (cmd == "init")
|
||||
lazy val isServer = !userCommands.exists(c => isBsp(c) || isClient(c))
|
||||
// keep this lazy to prevent project directory created prematurely
|
||||
|
|
|
|||
|
|
@ -26,42 +26,48 @@ object BuildServerConnection {
|
|||
|
||||
private[sbt] def writeConnectionFile(sbtVersion: String, baseDir: File): Unit = {
|
||||
val bspConnectionFile = new File(baseDir, ".bsp/sbt.json")
|
||||
val javaHome = Util.javaHome
|
||||
val classPath = System.getProperty("java.class.path")
|
||||
|
||||
val sbtScript = Option(System.getProperty("sbt.script"))
|
||||
.map(_.replace("%20", " "))
|
||||
.filter(_.nonEmpty)
|
||||
.orElse(sbtScriptInPath)
|
||||
.map(script => s"-Dsbt.script=$script")
|
||||
|
||||
val sbtOptsArgs = parseSbtOpts(sys.env.get("SBT_OPTS"))
|
||||
|
||||
val sbtLaunchJar = classPath
|
||||
.split(File.pathSeparator)
|
||||
.find(jar => SbtLaunchJar.findFirstIn(jar).nonEmpty)
|
||||
.map(_.replace(" ", "%20"))
|
||||
.map(jar => s"--sbt-launch-jar=$jar")
|
||||
|
||||
val argv =
|
||||
Vector(
|
||||
s"$javaHome/bin/java",
|
||||
"-Xms100m",
|
||||
"-Xmx100m",
|
||||
) ++
|
||||
sbtOptsArgs ++
|
||||
Vector(
|
||||
"-classpath",
|
||||
classPath,
|
||||
) ++
|
||||
sbtScript ++
|
||||
Vector("xsbt.boot.Boot", "-bsp") ++
|
||||
(if (sbtScript.isEmpty) sbtLaunchJar else None)
|
||||
val argv = sbtScript match
|
||||
case Some(script) =>
|
||||
Vector(script, "bsp")
|
||||
case None =>
|
||||
buildFallbackArgv
|
||||
|
||||
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] = {
|
||||
private[sbt] def buildFallbackArgv: Vector[String] = {
|
||||
val javaHome = Util.javaHome
|
||||
val classPath = System.getProperty("java.class.path")
|
||||
val sbtOptsArgs = parseSbtOpts(sys.env.get("SBT_OPTS"))
|
||||
val sbtLaunchJar = classPath
|
||||
.split(File.pathSeparator)
|
||||
.find(jar => SbtLaunchJar.findFirstIn(jar).nonEmpty)
|
||||
.map(_.replace(" ", "%20"))
|
||||
.map(jar => s"--sbt-launch-jar=$jar")
|
||||
|
||||
Vector(
|
||||
s"$javaHome/bin/java",
|
||||
"-Xms100m",
|
||||
"-Xmx100m",
|
||||
) ++
|
||||
sbtOptsArgs ++
|
||||
Vector(
|
||||
"-classpath",
|
||||
classPath,
|
||||
) ++
|
||||
Vector("xsbt.boot.Boot", "-bsp") ++
|
||||
sbtLaunchJar
|
||||
}
|
||||
|
||||
private[sbt] def sbtScriptInPath: Option[String] = {
|
||||
val fileName = if (Properties.isWin) "sbt.bat" else "sbt"
|
||||
val envPath = sys.env.collectFirst {
|
||||
case (k, v) if k.toUpperCase() == "PATH" => v
|
||||
|
|
@ -72,7 +78,7 @@ object BuildServerConnection {
|
|||
allPaths
|
||||
.map(_.resolve(fileName))
|
||||
.find(file => Files.exists(file) && Files.isExecutable(file))
|
||||
.map(_.toString.replace(" ", "%20"))
|
||||
.map(_.toString)
|
||||
}
|
||||
|
||||
private[sbt] def parseSbtOpts(sbtOpts: Option[String]): Vector[String] =
|
||||
|
|
|
|||
|
|
@ -45,3 +45,18 @@ object BuildServerConnectionSpec extends BasicTestSuite:
|
|||
test("parseSbtOpts should handle whitespace-separated options"):
|
||||
val result = BuildServerConnection.parseSbtOpts(Some(" -Dfoo=bar -Xmx1g "))
|
||||
assert(result == Vector("-Dfoo=bar", "-Xmx1g"))
|
||||
|
||||
test("sbtScriptInPath should return None when sbt is not in PATH"):
|
||||
val result = BuildServerConnection.sbtScriptInPath
|
||||
result match
|
||||
case Some(path) => assert(path.nonEmpty)
|
||||
case None => assert(true)
|
||||
|
||||
test("buildFallbackArgv should include java path and -bsp flag"):
|
||||
val argv = BuildServerConnection.buildFallbackArgv
|
||||
assert(argv.head.contains("java"), s"argv should start with java, got: ${argv.head}")
|
||||
assert(argv.contains("-bsp"), s"argv should contain -bsp, got: $argv")
|
||||
assert(argv.contains("-Xms100m"), s"argv should contain -Xms100m, got: $argv")
|
||||
assert(argv.contains("-Xmx100m"), s"argv should contain -Xmx100m, got: $argv")
|
||||
assert(argv.contains("-classpath"), s"argv should contain -classpath, got: $argv")
|
||||
assert(argv.contains("xsbt.boot.Boot"), s"argv should contain xsbt.boot.Boot, got: $argv")
|
||||
|
|
|
|||
1
sbt
1
sbt
|
|
@ -826,6 +826,7 @@ process_args () {
|
|||
-Dsbt.color=never|-Dsbt.log.noformat=true) addJava "$1" && use_colors=0 && shift ;;
|
||||
"-D*"|-D*) addJava "$1" && shift ;;
|
||||
-J*) addJava "${1:2}" && shift ;;
|
||||
bsp) use_sbtn=0 && addResidual "-bsp" && shift ;;
|
||||
*) addResidual "$1" && shift ;;
|
||||
esac
|
||||
done
|
||||
|
|
|
|||
|
|
@ -51,8 +51,11 @@ object Main:
|
|||
else System.err.println("[error] sbt requires at least JDK 8+, you have " + javaVer)
|
||||
return 1
|
||||
|
||||
val bspMode = opts.residual.exists(a => a == "bsp" || a == "-bsp" || a == "--bsp")
|
||||
val clientOpt = opts.client || sys.env.get("SBT_NATIVE_CLIENT").contains("true")
|
||||
val useNativeClient = shouldRunNativeClient(opts.copy(client = clientOpt), buildPropsVersion)
|
||||
val useNativeClient =
|
||||
if bspMode then false
|
||||
else shouldRunNativeClient(opts.copy(client = clientOpt), buildPropsVersion)
|
||||
|
||||
if useNativeClient then
|
||||
val scriptPath = sbtBinDir.getAbsolutePath.replace("\\", "/") + "/sbt.bat"
|
||||
|
|
|
|||
Loading…
Reference in New Issue