[2.x] fix: Propagate SBT_OPTS to BSP config (#8531)

Fixes #7469

When running 'sbt bspConfig', the generated .bsp/sbt.json now includes
JVM options from the SBT_OPTS environment variable. This ensures that
options like -Dsbt.boot.directory are propagated to the BSP server.

The parseSbtOpts method extracts -D, -X, and -J prefixed options from
SBT_OPTS and includes them in the BSP connection argv.
This commit is contained in:
MkDev11 2026-01-14 14:20:30 -05:00 committed by GitHub
parent 8433dd8db6
commit 1ed08f0034
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 64 additions and 9 deletions

View File

@ -33,11 +33,8 @@ object BuildServerConnection {
.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 sbtOptsArgs = parseSbtOpts(sys.env.get("SBT_OPTS"))
val sbtLaunchJar = classPath
.split(File.pathSeparator)
.find(jar => SbtLaunchJar.findFirstIn(jar).nonEmpty)
@ -49,9 +46,12 @@ object BuildServerConnection {
s"$javaHome/bin/java",
"-Xms100m",
"-Xmx100m",
"-classpath",
classPath,
) ++
sbtOptsArgs ++
Vector(
"-classpath",
classPath,
) ++
sbtScript ++
Vector("xsbt.boot.Boot", "-bsp") ++
(if (sbtScript.isEmpty) sbtLaunchJar else None)
@ -62,8 +62,6 @@ 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 = sys.env.collectFirst {
case (k, v) if k.toUpperCase() == "PATH" => v
@ -76,4 +74,14 @@ object BuildServerConnection {
.find(file => Files.exists(file) && Files.isExecutable(file))
.map(_.toString.replace(" ", "%20"))
}
private[sbt] def parseSbtOpts(sbtOpts: Option[String]): Vector[String] =
sbtOpts match
case Some(opts) if opts.nonEmpty =>
opts
.split("\\s+")
.filter(arg => arg.startsWith("-D") || arg.startsWith("-X") || arg.startsWith("-J"))
.map(arg => if (arg.startsWith("-J")) arg.stripPrefix("-J") else arg)
.toVector
case _ => Vector.empty
}

View File

@ -0,0 +1,47 @@
/*
* sbt
* Copyright 2023, Scala center
* Copyright 2011 - 2022, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.bsp
import verify.BasicTestSuite
object BuildServerConnectionSpec extends BasicTestSuite:
test("parseSbtOpts should return empty vector for None"):
val result = BuildServerConnection.parseSbtOpts(None)
assert(result.isEmpty)
test("parseSbtOpts should return empty vector for empty string"):
val result = BuildServerConnection.parseSbtOpts(Some(""))
assert(result.isEmpty)
test("parseSbtOpts should parse -D system properties"):
val result = BuildServerConnection.parseSbtOpts(Some("-Dsbt.boot.directory=/custom/path"))
assert(result == Vector("-Dsbt.boot.directory=/custom/path"))
test("parseSbtOpts should parse -X JVM options"):
val result = BuildServerConnection.parseSbtOpts(Some("-Xmx2g -Xms512m"))
assert(result == Vector("-Xmx2g", "-Xms512m"))
test("parseSbtOpts should parse -J prefixed options and strip the prefix"):
val result = BuildServerConnection.parseSbtOpts(Some("-J-Xmx4g"))
assert(result == Vector("-Xmx4g"))
test("parseSbtOpts should parse multiple mixed options"):
val result = BuildServerConnection.parseSbtOpts(
Some("-Dsbt.boot.directory=/path -Xmx2g -J-XX:+UseG1GC")
)
assert(result == Vector("-Dsbt.boot.directory=/path", "-Xmx2g", "-XX:+UseG1GC"))
test("parseSbtOpts should filter out non-JVM options"):
val result = BuildServerConnection.parseSbtOpts(Some("-Dfoo=bar --some-flag -Xmx1g"))
assert(result == Vector("-Dfoo=bar", "-Xmx1g"))
test("parseSbtOpts should handle whitespace-separated options"):
val result = BuildServerConnection.parseSbtOpts(Some(" -Dfoo=bar -Xmx1g "))
assert(result == Vector("-Dfoo=bar", "-Xmx1g"))