mirror of https://github.com/sbt/sbt.git
Merge 13cf82fb27 into 2d2d5136ef
This commit is contained in:
commit
9125887856
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2023, Scala center
|
||||
* Copyright 2011 - 2022, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package example.test
|
||||
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* First-hop propagation guard: an explicit `-java-home` must be the JDK the Windows launcher runs
|
||||
* (echoed on the verbose command line). Not the full sbt/sbt#963 regression — the
|
||||
* `.java-version`-override case needs two JDK paths + the native client, so it is verified manually
|
||||
* on Windows.
|
||||
*/
|
||||
object JavaHomeScriptTest extends verify.BasicTestSuite with ShellScriptUtil:
|
||||
private val jdkHome =
|
||||
new File(sys.env.getOrElse("JAVA_HOME", System.getProperty("java.home"))).getAbsolutePath
|
||||
|
||||
testOutput("sbt -java-home selects the JDK for the launcher")(
|
||||
"-java-home",
|
||||
jdkHome,
|
||||
"compile",
|
||||
"-v"
|
||||
): (out: List[String]) =>
|
||||
if !isWindows then cancel("`-java-home` bin/java.exe selection is Windows-specific")
|
||||
else
|
||||
val javaExe = new File(new File(jdkHome, "bin"), "java.exe").getAbsolutePath
|
||||
assert(
|
||||
out.exists(_.contains(javaExe)),
|
||||
s"launcher should run $javaExe; command echo was: ${out.mkString(" | ")}"
|
||||
)
|
||||
end JavaHomeScriptTest
|
||||
|
|
@ -87,7 +87,8 @@ if exist "!SBT_CONFIG!" (
|
|||
)
|
||||
|
||||
rem poor man's jenv (which is not available on Windows)
|
||||
if defined JAVA_HOMES (
|
||||
rem explicit -java-home wins over the project .java-version
|
||||
if not defined SBT_EXPLICIT_JAVA_HOME if defined JAVA_HOMES (
|
||||
if exist .java-version for /F %%A in (.java-version) do (
|
||||
set JAVA_HOME=%JAVA_HOMES%\%%A
|
||||
set JDK_HOME=%JAVA_HOMES%\%%A
|
||||
|
|
@ -503,8 +504,11 @@ if defined _java_home_arg (
|
|||
if not "%~1" == "" (
|
||||
if exist "%~1\bin\java.exe" (
|
||||
set "_JAVACMD=%~1\bin\java.exe"
|
||||
set "JAVACMD=%~1\bin\java.exe"
|
||||
set "JAVA_HOME=%~1"
|
||||
set "JDK_HOME=%~1"
|
||||
set "SBT_EXPLICIT_JAVA_HOME=1"
|
||||
set "PATH=%~1\bin;!PATH!"
|
||||
shift
|
||||
goto args_loop
|
||||
) else (
|
||||
|
|
@ -786,6 +790,9 @@ if defined sbt_args_verbose (
|
|||
echo.
|
||||
)
|
||||
|
||||
rem one-hop marker: drop it before the server JVM
|
||||
set "SBT_EXPLICIT_JAVA_HOME="
|
||||
|
||||
"!_JAVACMD!" !_JAVA_OPTS! !_SBT_OPTS! %JAVA_TOOL_OPTIONS% %JDK_JAVA_OPTIONS% -cp "!sbt_jar!" xsbt.boot.Boot %*
|
||||
|
||||
goto :eof
|
||||
|
|
|
|||
|
|
@ -4,56 +4,89 @@ import scopt.OParser
|
|||
|
||||
object ArgParser:
|
||||
|
||||
def parse(args: Array[String]): Option[LauncherOptions] =
|
||||
private val parser =
|
||||
val b = OParser.builder[LauncherOptions]
|
||||
val parser =
|
||||
import b.*
|
||||
OParser.sequence(
|
||||
programName("sbtw"),
|
||||
head("sbtw", "Windows launcher for sbt"),
|
||||
opt[Unit]('h', "help").action((_, c) => c.copy(help = true)),
|
||||
opt[Unit]('v', "verbose").action((_, c) => c.copy(verbose = true)),
|
||||
opt[Unit]('d', "debug").action((_, c) => c.copy(debug = true)),
|
||||
opt[Unit]('V', "version").action((_, c) => c.copy(version = true)),
|
||||
opt[Unit]("numeric-version").action((_, c) => c.copy(numericVersion = true)),
|
||||
opt[Unit]("script-version").action((_, c) => c.copy(scriptVersion = true)),
|
||||
opt[Unit]("shutdownall").action((_, c) => c.copy(shutdownAll = true)),
|
||||
opt[Unit]("allow-empty").action((_, c) => c.copy(allowEmpty = true)),
|
||||
opt[Unit]("sbt-create").action((_, c) => c.copy(allowEmpty = true)),
|
||||
opt[Unit]("client").action((_, c) => c.copy(client = true)),
|
||||
opt[Unit]("server").action((_, c) => c.copy(server = true)),
|
||||
opt[Unit]("jvm-client").action((_, c) => c.copy(jvmClient = true)),
|
||||
opt[Unit]("no-server").action((_, c) => c.copy(noServer = true)),
|
||||
opt[Unit]("no-colors").action((_, c) => c.copy(noColors = true)),
|
||||
opt[Unit]("no-global").action((_, c) => c.copy(noGlobal = true)),
|
||||
opt[Unit]("no-share").action((_, c) => c.copy(noShare = true)),
|
||||
opt[Unit]("no-hide-jdk-warnings").action((_, c) => c.copy(noHideJdkWarnings = true)),
|
||||
opt[Unit]("debug-inc").action((_, c) => c.copy(debugInc = true)),
|
||||
opt[Unit]("timings").action((_, c) => c.copy(timings = true)),
|
||||
opt[Unit]("traces").action((_, c) => c.copy(traces = true)),
|
||||
opt[Unit]("batch").action((_, c) => c.copy(batch = true)),
|
||||
opt[String]("sbt-dir").action((x, c) => c.copy(sbtDir = Some(x))),
|
||||
opt[String]("sbt-boot").action((x, c) => c.copy(sbtBoot = Some(x))),
|
||||
opt[String]("sbt-cache").action((x, c) => c.copy(sbtCache = Some(x))),
|
||||
opt[String]("sbt-jar").action((x, c) => c.copy(sbtJar = Some(x))),
|
||||
opt[String]("sbt-version").action((x, c) => c.copy(sbtVersion = Some(x))),
|
||||
opt[String]("ivy").action((x, c) => c.copy(ivy = Some(x))),
|
||||
opt[Int]("mem").action((x, c) => c.copy(mem = Some(x))),
|
||||
opt[String]("supershell").action((x, c) => c.copy(supershell = Some(x))),
|
||||
opt[String]("color").action((x, c) => c.copy(color = Some(x))),
|
||||
opt[String]("autostart").action((x, c) => c.copy(autostart = Some(x))),
|
||||
opt[Int]("jvm-debug").action((x, c) => c.copy(jvmDebug = Some(x))),
|
||||
opt[String]("java-home").action((x, c) => c.copy(javaHome = Some(x))),
|
||||
opt[String]("experimental_execution_log").action((x, c) =>
|
||||
c.copy(experimentalExecutionLog = Some(x))
|
||||
),
|
||||
arg[String]("<arg>")
|
||||
.unbounded()
|
||||
.optional()
|
||||
.action((x, c) => c.copy(residual = c.residual :+ x)),
|
||||
)
|
||||
import b.*
|
||||
OParser.sequence(
|
||||
programName("sbtw"),
|
||||
head("sbtw", "Windows launcher for sbt"),
|
||||
opt[Unit]('h', "help").action((_, c) => c.copy(help = true)),
|
||||
opt[Unit]('v', "verbose").action((_, c) => c.copy(verbose = true)),
|
||||
opt[Unit]('d', "debug").action((_, c) => c.copy(debug = true)),
|
||||
opt[Unit]('V', "version").action((_, c) => c.copy(version = true)),
|
||||
opt[Unit]("numeric-version").action((_, c) => c.copy(numericVersion = true)),
|
||||
opt[Unit]("script-version").action((_, c) => c.copy(scriptVersion = true)),
|
||||
opt[Unit]("shutdownall").action((_, c) => c.copy(shutdownAll = true)),
|
||||
opt[Unit]("allow-empty").action((_, c) => c.copy(allowEmpty = true)),
|
||||
opt[Unit]("sbt-create").action((_, c) => c.copy(allowEmpty = true)),
|
||||
opt[Unit]("client").action((_, c) => c.copy(client = true)),
|
||||
opt[Unit]("server").action((_, c) => c.copy(server = true)),
|
||||
opt[Unit]("jvm-client").action((_, c) => c.copy(jvmClient = true)),
|
||||
opt[Unit]("no-server").action((_, c) => c.copy(noServer = true)),
|
||||
opt[Unit]("no-colors").action((_, c) => c.copy(noColors = true)),
|
||||
opt[Unit]("no-global").action((_, c) => c.copy(noGlobal = true)),
|
||||
opt[Unit]("no-share").action((_, c) => c.copy(noShare = true)),
|
||||
opt[Unit]("no-hide-jdk-warnings").action((_, c) => c.copy(noHideJdkWarnings = true)),
|
||||
opt[Unit]("debug-inc").action((_, c) => c.copy(debugInc = true)),
|
||||
opt[Unit]("timings").action((_, c) => c.copy(timings = true)),
|
||||
opt[Unit]("traces").action((_, c) => c.copy(traces = true)),
|
||||
opt[Unit]("batch").action((_, c) => c.copy(batch = true)),
|
||||
opt[String]("sbt-dir").action((x, c) => c.copy(sbtDir = Some(x))),
|
||||
opt[String]("sbt-boot").action((x, c) => c.copy(sbtBoot = Some(x))),
|
||||
opt[String]("sbt-cache").action((x, c) => c.copy(sbtCache = Some(x))),
|
||||
opt[String]("sbt-jar").action((x, c) => c.copy(sbtJar = Some(x))),
|
||||
opt[String]("sbt-version").action((x, c) => c.copy(sbtVersion = Some(x))),
|
||||
opt[String]("ivy").action((x, c) => c.copy(ivy = Some(x))),
|
||||
opt[Int]("mem").action((x, c) => c.copy(mem = Some(x))),
|
||||
opt[String]("supershell").action((x, c) => c.copy(supershell = Some(x))),
|
||||
opt[String]("color").action((x, c) => c.copy(color = Some(x))),
|
||||
opt[String]("autostart").action((x, c) => c.copy(autostart = Some(x))),
|
||||
opt[Int]("jvm-debug").action((x, c) => c.copy(jvmDebug = Some(x))),
|
||||
opt[String]("java-home").action((x, c) => c.copy(javaHome = Some(x))),
|
||||
opt[String]("experimental_execution_log").action((x, c) =>
|
||||
c.copy(experimentalExecutionLog = Some(x))
|
||||
),
|
||||
arg[String]("<arg>")
|
||||
.unbounded()
|
||||
.optional()
|
||||
.action((x, c) => c.copy(residual = c.residual :+ x)),
|
||||
)
|
||||
|
||||
/** Long-option names, derived from the parser (`fullName` is `--name` for options) — one source of truth. */
|
||||
private[sbtw] val longNames: Set[String] =
|
||||
parser.toList
|
||||
.map(_.fullName)
|
||||
.collect {
|
||||
case n if n.startsWith("--") => n.drop(2)
|
||||
}
|
||||
.toSet
|
||||
|
||||
/** Long options that take a value, probed from the parser so it stays derived. */
|
||||
private val valueTakingNames: Set[String] =
|
||||
longNames.filter(n => OParser.runParser(parser, Seq(s"--$n"), LauncherOptions())._1.isEmpty)
|
||||
|
||||
/** Rewrites single-dash `-name` to `--name`, stopping at `--` and skipping option value slots. */
|
||||
private[sbtw] def normalizeArgs(args: Seq[String]): Seq[String] =
|
||||
val (options, passthrough) = args.span(_ != "--")
|
||||
val out = List.newBuilder[String]
|
||||
var expectValue = false
|
||||
options.foreach: arg =>
|
||||
if expectValue then
|
||||
out += arg
|
||||
expectValue = false
|
||||
else
|
||||
val normalized = alias(arg)
|
||||
out += normalized
|
||||
expectValue = normalized.startsWith("--") && valueTakingNames(normalized.drop(2))
|
||||
out.result() ++ passthrough
|
||||
|
||||
private def alias(arg: String): String =
|
||||
if arg.startsWith("-") && !arg.startsWith("--") && longNames(arg.drop(1)) then "-" + arg
|
||||
else arg
|
||||
|
||||
def parse(args: Array[String]): Option[LauncherOptions] =
|
||||
OParser
|
||||
.parse(parser, args, LauncherOptions())
|
||||
.parse(parser, normalizeArgs(args.toSeq), LauncherOptions())
|
||||
.map: opts =>
|
||||
val sbtNew = opts.residual.contains("new") || opts.residual.contains("init")
|
||||
val isScript = opts.residual.exists(_.startsWith("-Dsbt.main.class=sbt.ScriptMain"))
|
||||
|
|
|
|||
|
|
@ -21,13 +21,31 @@ object ConfigLoader:
|
|||
val fromConfig = new File(sbtHome, "conf/sbtopts")
|
||||
val fromEtc = new File("/etc/sbt/sbtopts")
|
||||
val fromSbtConfig = new File(sbtHome, "conf/sbtconfig.txt")
|
||||
val fromEnv = sys.env.get("SBT_OPTS").toSeq.flatMap(_.split("\\s+").filter(_.nonEmpty))
|
||||
val fromProjectLines = loadLines(fromProject).map(stripJ)
|
||||
val fromConfigLines = loadLines(fromConfig)
|
||||
val fromEtcLines = loadLines(fromEtc)
|
||||
val fromSbtConfigLines = loadLines(fromSbtConfig)
|
||||
val fromEnv = sys.env.get("SBT_OPTS").toSeq.flatMap(tokenize)
|
||||
val fromProjectLines = loadLines(fromProject).flatMap(tokenize).map(stripJ)
|
||||
val fromConfigLines = loadLines(fromConfig).flatMap(tokenize)
|
||||
val fromEtcLines = loadLines(fromEtc).flatMap(tokenize)
|
||||
val fromSbtConfigLines = loadLines(fromSbtConfig).flatMap(tokenize)
|
||||
(fromEtcLines ++ fromConfigLines ++ fromSbtConfigLines ++ fromEnv ++ fromProjectLines)
|
||||
|
||||
/** Splits an sbtopts line on unquoted whitespace, honoring `'`/`"` (like the bash `parseLineIntoWords`). */
|
||||
private[sbtw] def tokenize(line: String): Seq[String] =
|
||||
val out = scala.collection.mutable.ListBuffer.empty[String]
|
||||
val word = new StringBuilder
|
||||
var inDouble = false
|
||||
var inSingle = false
|
||||
line.foreach: c =>
|
||||
if inDouble then if c == '"' then inDouble = false else word += c
|
||||
else if inSingle then if c == '\'' then inSingle = false else word += c
|
||||
else
|
||||
c match
|
||||
case '"' => inDouble = true
|
||||
case '\'' => inSingle = true
|
||||
case w if w.isWhitespace => if word.nonEmpty then { out += word.toString; word.clear() }
|
||||
case other => word += other
|
||||
if word.nonEmpty then out += word.toString
|
||||
out.toList
|
||||
|
||||
def loadJvmOpts(cwd: File): Seq[String] =
|
||||
val fromProject = new File(cwd, ".jvmopts")
|
||||
val fromEnv = sys.env.get("JAVA_OPTS").toSeq.flatMap(_.split("\\s+").filter(_.nonEmpty))
|
||||
|
|
|
|||
|
|
@ -16,8 +16,7 @@ object Main:
|
|||
)
|
||||
val sbtBinDir = new File(sbtHome, "bin")
|
||||
|
||||
val fileSbtOpts = ConfigLoader.loadSbtOpts(cwd, sbtHome)
|
||||
val fileArgs = fileSbtOpts.flatMap(_.split("\\s+").filter(_.nonEmpty))
|
||||
val fileArgs = ConfigLoader.loadSbtOpts(cwd, sbtHome)
|
||||
val allArgs = fileArgs ++ args
|
||||
|
||||
ArgParser.parse(allArgs.toArray) match
|
||||
|
|
@ -31,8 +30,9 @@ object Main:
|
|||
else if opts.version || opts.numericVersion || opts.scriptVersion then
|
||||
handleVersionCommands(cwd, sbtHome, sbtBinDir, opts)
|
||||
else if opts.shutdownAll then
|
||||
val javaCmd = Runner.findJavaCmd(opts.javaHome)
|
||||
Runner.shutdownAll(javaCmd)
|
||||
SelectedJava.resolve(opts.javaHome) match
|
||||
case Left(error) => reportJavaHomeError(error)
|
||||
case Right(selected) => Runner.shutdownAll(selected)
|
||||
else if !opts.allowEmpty && !opts.sbtNew && !ConfigLoader.isSbtProjectDir(cwd) then
|
||||
System.err.println(
|
||||
"[error] Neither build.sbt nor a 'project' directory in the current directory: " + cwd
|
||||
|
|
@ -40,61 +40,80 @@ object Main:
|
|||
System.err.println("[error] run 'sbt new', touch build.sbt, or run 'sbt --allow-empty'.")
|
||||
1
|
||||
else
|
||||
val buildPropsVersion = ConfigLoader.sbtVersionFromBuildProperties(cwd)
|
||||
SelectedJava.resolve(opts.javaHome) match
|
||||
case Left(error) => reportJavaHomeError(error)
|
||||
case Right(selected) => launch(cwd, sbtBinDir, opts, selected)
|
||||
|
||||
val javaCmd = Runner.findJavaCmd(opts.javaHome)
|
||||
val javaVer = Runner.javaVersion(javaCmd)
|
||||
val minJdk = Runner.minimumJdkVersion(buildPropsVersion)
|
||||
if javaVer > 0 && javaVer < minJdk then
|
||||
if minJdk >= 17 then
|
||||
System.err.println(
|
||||
"[error] sbt 2.x requires JDK 17 or above, but you have JDK " + javaVer
|
||||
)
|
||||
else System.err.println("[error] sbt requires at least JDK 8+, you have " + javaVer)
|
||||
1
|
||||
private def reportJavaHomeError(error: String): Int =
|
||||
System.err.println(error)
|
||||
1
|
||||
|
||||
private def launch(
|
||||
cwd: File,
|
||||
sbtBinDir: File,
|
||||
opts: LauncherOptions,
|
||||
selected: SelectedJava
|
||||
): Int =
|
||||
val buildPropsVersion = ConfigLoader.sbtVersionFromBuildProperties(cwd)
|
||||
val javaVer = Runner.javaVersion(selected.javaCmd)
|
||||
val minJdk = Runner.minimumJdkVersion(buildPropsVersion)
|
||||
if javaVer > 0 && javaVer < minJdk then
|
||||
if minJdk >= 17 then
|
||||
System.err.println(
|
||||
"[error] sbt 2.x requires JDK 17 or above, but you have JDK " + javaVer
|
||||
)
|
||||
else System.err.println("[error] sbt requires at least JDK 8+, you have " + javaVer)
|
||||
1
|
||||
else
|
||||
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 =
|
||||
if bspMode then false
|
||||
else shouldRunNativeClient(opts.copy(client = clientOpt), buildPropsVersion)
|
||||
|
||||
if useNativeClient then
|
||||
val scriptPath = sbtBinDir.getAbsolutePath.replace("\\", "/") + "/sbt.bat"
|
||||
Runner.runNativeClient(sbtBinDir, scriptPath, opts, selected)
|
||||
else
|
||||
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 =
|
||||
if bspMode then false
|
||||
else shouldRunNativeClient(opts.copy(client = clientOpt), buildPropsVersion)
|
||||
|
||||
if useNativeClient then
|
||||
val scriptPath = sbtBinDir.getAbsolutePath.replace("\\", "/") + "/sbt.bat"
|
||||
Runner.runNativeClient(sbtBinDir, scriptPath, opts)
|
||||
val sbtJar = opts.sbtJar
|
||||
.filter(p => new File(p).isFile)
|
||||
.getOrElse(new File(sbtBinDir, "sbt-launch.jar").getAbsolutePath)
|
||||
if !new File(sbtJar).isFile then
|
||||
System.err.println("[error] Launcher jar not found: " + sbtJar)
|
||||
1
|
||||
else
|
||||
val sbtJar = opts.sbtJar
|
||||
.filter(p => new File(p).isFile)
|
||||
.getOrElse(new File(sbtBinDir, "sbt-launch.jar").getAbsolutePath)
|
||||
if !new File(sbtJar).isFile then
|
||||
System.err.println("[error] Launcher jar not found: " + sbtJar)
|
||||
1
|
||||
else
|
||||
var javaOpts = ConfigLoader.loadJvmOpts(cwd)
|
||||
if javaOpts.isEmpty then javaOpts = ConfigLoader.defaultJavaOpts
|
||||
var sbtOpts = Runner.buildSbtOpts(opts)
|
||||
var javaOpts = ConfigLoader.loadJvmOpts(cwd)
|
||||
if javaOpts.isEmpty then javaOpts = ConfigLoader.defaultJavaOpts
|
||||
var sbtOpts = Runner.buildSbtOpts(opts)
|
||||
|
||||
val (residualJava, bootArgs) = Runner.splitResidual(opts.residual)
|
||||
javaOpts = javaOpts ++ residualJava
|
||||
val (residualJava, bootArgs) = Runner.splitResidual(opts.residual)
|
||||
javaOpts = javaOpts ++ residualJava
|
||||
|
||||
val (finalJava, finalSbt) = if opts.mem.isDefined then
|
||||
val evictedJava = Memory.evictMemoryOpts(javaOpts)
|
||||
val evictedSbt = Memory.evictMemoryOpts(sbtOpts)
|
||||
val memOpts = Memory.addMemory(opts.mem.get, javaVer)
|
||||
(evictedJava ++ memOpts, evictedSbt)
|
||||
else Memory.addDefaultMemory(javaOpts, sbtOpts, javaVer, LauncherOptions.defaultMemMb)
|
||||
sbtOpts = finalSbt
|
||||
val (finalJava, finalSbt) = if opts.mem.isDefined then
|
||||
val evictedJava = Memory.evictMemoryOpts(javaOpts)
|
||||
val evictedSbt = Memory.evictMemoryOpts(sbtOpts)
|
||||
val memOpts = Memory.addMemory(opts.mem.get, javaVer)
|
||||
(evictedJava ++ memOpts, evictedSbt)
|
||||
else Memory.addDefaultMemory(javaOpts, sbtOpts, javaVer, LauncherOptions.defaultMemMb)
|
||||
sbtOpts = finalSbt
|
||||
|
||||
if !opts.noHideJdkWarnings && javaVer >= 25 then
|
||||
sbtOpts = sbtOpts ++ Seq(
|
||||
"--sun-misc-unsafe-memory-access=allow",
|
||||
"--enable-native-access=ALL-UNNAMED"
|
||||
)
|
||||
val javaOptsWithDebug = opts.jvmDebug.fold(finalJava)(port =>
|
||||
finalJava :+ s"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=$port"
|
||||
if !opts.noHideJdkWarnings && javaVer >= 25 then
|
||||
sbtOpts = sbtOpts ++ Seq(
|
||||
"--sun-misc-unsafe-memory-access=allow",
|
||||
"--enable-native-access=ALL-UNNAMED"
|
||||
)
|
||||
val javaOptsWithDebug = opts.jvmDebug.fold(finalJava)(port =>
|
||||
finalJava :+ s"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=$port"
|
||||
)
|
||||
|
||||
Runner.runJvm(javaCmd, javaOptsWithDebug, sbtOpts, sbtJar, bootArgs, opts.verbose)
|
||||
Runner.runJvm(
|
||||
selected,
|
||||
javaOptsWithDebug,
|
||||
sbtOpts,
|
||||
sbtJar,
|
||||
bootArgs,
|
||||
opts.verbose
|
||||
)
|
||||
|
||||
private def shouldRunNativeClient(
|
||||
opts: LauncherOptions,
|
||||
|
|
@ -130,19 +149,22 @@ object Main:
|
|||
)
|
||||
0
|
||||
else if opts.numericVersion then
|
||||
val javaCmd = Runner.findJavaCmd(opts.javaHome)
|
||||
val sbtJar = opts.sbtJar
|
||||
.filter(p => new File(p).isFile)
|
||||
.getOrElse(new File(sbtBinDir, "sbt-launch.jar").getAbsolutePath)
|
||||
if !new File(sbtJar).isFile then
|
||||
System.err.println("[error] Launcher jar not found for version check")
|
||||
1
|
||||
else
|
||||
try
|
||||
val out = Process(Seq(javaCmd, "-jar", sbtJar, "sbtVersion")).!!
|
||||
println(out.linesIterator.toSeq.lastOption.map(_.trim).getOrElse(""))
|
||||
0
|
||||
catch { case _: Exception => 1 }
|
||||
SelectedJava.resolve(opts.javaHome) match
|
||||
case Left(error) => reportJavaHomeError(error)
|
||||
case Right(selected) =>
|
||||
val sbtJar = opts.sbtJar
|
||||
.filter(p => new File(p).isFile)
|
||||
.getOrElse(new File(sbtBinDir, "sbt-launch.jar").getAbsolutePath)
|
||||
if !new File(sbtJar).isFile then
|
||||
System.err.println("[error] Launcher jar not found for version check")
|
||||
1
|
||||
else
|
||||
try
|
||||
val cmd = Seq(selected.javaCmd, "-jar", sbtJar, "sbtVersion")
|
||||
val out = Process(cmd, None, selected.envOverlay*).!!
|
||||
println(out.linesIterator.toSeq.lastOption.map(_.trim).getOrElse(""))
|
||||
0
|
||||
catch { case _: Exception => 1 }
|
||||
else 0
|
||||
|
||||
private def projectSbtVersion(cwd: File): Option[String] =
|
||||
|
|
|
|||
|
|
@ -6,26 +6,7 @@ import scala.sys.process.*
|
|||
|
||||
object Runner:
|
||||
|
||||
def findJavaCmd(javaHome: Option[String]): String =
|
||||
val cmd = javaHome match
|
||||
case Some(h) =>
|
||||
val exe = new File(h, "bin/java.exe")
|
||||
if exe.isFile then exe.getAbsolutePath
|
||||
else
|
||||
sys.env
|
||||
.get("JAVACMD")
|
||||
.orElse(
|
||||
sys.env.get("JAVA_HOME").map(h0 => new File(h0, "bin/java.exe").getAbsolutePath)
|
||||
)
|
||||
.getOrElse("java")
|
||||
case None =>
|
||||
sys.env
|
||||
.get("JAVACMD")
|
||||
.orElse(sys.env.get("JAVA_HOME").map(h => new File(h, "bin/java.exe").getAbsolutePath))
|
||||
.getOrElse("java")
|
||||
cmd.replace("\"", "")
|
||||
|
||||
def javaVersion(javaCmd: String): Int =
|
||||
private[sbtw] def javaVersion(javaCmd: String): Int =
|
||||
try
|
||||
val pb = Process(Seq(javaCmd, "-Xms32M", "-Xmx32M", "-version"))
|
||||
val out = pb.!!
|
||||
|
|
@ -39,11 +20,11 @@ object Runner:
|
|||
catch { case _: Exception => 0 }
|
||||
|
||||
/** Returns the minimum JDK version required for the given sbt version. */
|
||||
def minimumJdkVersion(sbtVersion: Option[String]): Int =
|
||||
private[sbtw] def minimumJdkVersion(sbtVersion: Option[String]): Int =
|
||||
val isSbt2 = sbtVersion.exists(v => v.takeWhile(_.isDigit).toIntOption.exists(_ >= 2))
|
||||
if isSbt2 then 17 else 8
|
||||
|
||||
def buildSbtOpts(opts: LauncherOptions): Seq[String] =
|
||||
private[sbtw] def buildSbtOpts(opts: LauncherOptions): Seq[String] =
|
||||
var s: Seq[String] = Nil
|
||||
if opts.debug then s = s :+ "-debug"
|
||||
if opts.debugInc then s = s :+ "-Dxsbt.inc.debug=true"
|
||||
|
|
@ -71,7 +52,12 @@ object Runner:
|
|||
if opts.jvmClient then s = s :+ "--client"
|
||||
s
|
||||
|
||||
def runNativeClient(sbtBinDir: File, scriptPath: String, opts: LauncherOptions): Int =
|
||||
private[sbtw] def runNativeClient(
|
||||
sbtBinDir: File,
|
||||
scriptPath: String,
|
||||
opts: LauncherOptions,
|
||||
selected: SelectedJava
|
||||
): Int =
|
||||
val sbtn = new File(sbtBinDir, "sbtn-x86_64-pc-win32.exe")
|
||||
if !sbtn.isFile then
|
||||
System.err.println("[error] sbtn-x86_64-pc-win32.exe not found in " + sbtBinDir)
|
||||
|
|
@ -84,11 +70,12 @@ object Runner:
|
|||
if opts.verbose then
|
||||
System.err.println("# running native client")
|
||||
cmd.foreach(a => System.err.println(a))
|
||||
val proc = Process(cmd, None, "SBT_SCRIPT" -> scriptPath)
|
||||
val extraEnv = ("SBT_SCRIPT" -> scriptPath) +: selected.handoffEnv
|
||||
val proc = Process(cmd, None, extraEnv*)
|
||||
proc.!
|
||||
|
||||
def runJvm(
|
||||
javaCmd: String,
|
||||
private[sbtw] def runJvm(
|
||||
selected: SelectedJava,
|
||||
javaOpts: Seq[String],
|
||||
sbtOpts: Seq[String],
|
||||
sbtJar: String,
|
||||
|
|
@ -99,21 +86,23 @@ object Runner:
|
|||
sys.env.get("JAVA_TOOL_OPTIONS").toSeq.flatMap(_.split("\\s+").filter(_.nonEmpty))
|
||||
val jdkOpts = sys.env.get("JDK_JAVA_OPTIONS").toSeq.flatMap(_.split("\\s+").filter(_.nonEmpty))
|
||||
val fullJavaOpts = javaOpts ++ sbtOpts ++ toolOpts ++ jdkOpts
|
||||
val cmd = Seq(javaCmd) ++ fullJavaOpts ++ Seq("-cp", sbtJar, "xsbt.boot.Boot") ++ bootArgs
|
||||
val cmd =
|
||||
Seq(selected.javaCmd) ++ fullJavaOpts ++ Seq("-cp", sbtJar, "xsbt.boot.Boot") ++ bootArgs
|
||||
if verbose then
|
||||
System.err.println("# Executing command line:")
|
||||
cmd.foreach(a => System.err.println(if a.contains(" ") then s""""$a"""" else a))
|
||||
val jpb = new JProcessBuilder(cmd*)
|
||||
jpb.inheritIO()
|
||||
selected.envOverlay.foreach((k, v) => jpb.environment().put(k, v))
|
||||
val p = jpb.start()
|
||||
try
|
||||
p.waitFor()
|
||||
p.exitValue()
|
||||
finally if p.isAlive then p.destroy()
|
||||
|
||||
def shutdownAll(javaCmd: String): Int =
|
||||
private[sbtw] def shutdownAll(selected: SelectedJava): Int =
|
||||
try
|
||||
val jpsOut = Process(Seq("jps", "-lv")).!!
|
||||
val jpsOut = Process(Seq("jps", "-lv"), None, selected.envOverlay*).!!
|
||||
val pids = jpsOut.linesIterator
|
||||
.filter(_.contains("xsbt.boot.Boot"))
|
||||
.flatMap: line =>
|
||||
|
|
@ -127,7 +116,7 @@ object Runner:
|
|||
0
|
||||
catch { case _: Exception => 1 }
|
||||
|
||||
def splitResidual(residual: Seq[String]): (Seq[String], Seq[String]) =
|
||||
private[sbtw] def splitResidual(residual: Seq[String]): (Seq[String], Seq[String]) =
|
||||
var javaOpts: Seq[String] = Nil
|
||||
var bootArgs: Seq[String] = Nil
|
||||
var i = 0
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2023, Scala center
|
||||
* Copyright 2011 - 2022, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbtw
|
||||
|
||||
import java.io.File
|
||||
|
||||
/** The JDK chosen to launch sbt: `javaCmd` to run, plus `envOverlay` so indirect launches use it too. */
|
||||
private[sbtw] final case class SelectedJava(
|
||||
javaCmd: String,
|
||||
envOverlay: Seq[(String, String)],
|
||||
explicit: Boolean = false
|
||||
):
|
||||
/** Overlay for the native-client handoff, plus the one-hop explicit marker. */
|
||||
def handoffEnv: Seq[(String, String)] =
|
||||
if explicit then envOverlay :+ (SelectedJava.explicitMarker -> "1") else envOverlay
|
||||
end SelectedJava
|
||||
|
||||
private[sbtw] object SelectedJava:
|
||||
|
||||
/** One-hop marker telling `sbt.bat`'s `.java-version` block to defer to an explicit `--java-home`. */
|
||||
private[sbtw] val explicitMarker = "SBT_EXPLICIT_JAVA_HOME"
|
||||
|
||||
/** Resolves `--java-home` > `JAVACMD` > `JAVA_HOME` > `PATH`; an invalid `--java-home` is an error. */
|
||||
def resolve(
|
||||
javaHome: Option[String],
|
||||
env: Map[String, String] = sys.env
|
||||
): Either[String, SelectedJava] =
|
||||
javaHome match
|
||||
case Some(home) =>
|
||||
val exe = new File(home, "bin/java.exe")
|
||||
if exe.isFile then Right(fromHome(home, env, explicit = true))
|
||||
else Left(s"""[error] Directory "$home" for JAVA_HOME is not valid""")
|
||||
case None =>
|
||||
getIgnoreCase(env, "JAVACMD").map(stripQuotes) match
|
||||
case Some(cmd) => Right(SelectedJava(cmd, Nil))
|
||||
case None =>
|
||||
getIgnoreCase(env, "JAVA_HOME") match
|
||||
case Some(home) => Right(fromHome(home, env, explicit = false))
|
||||
case None => Right(SelectedJava("java", Nil))
|
||||
|
||||
private def fromHome(home: String, env: Map[String, String], explicit: Boolean): SelectedJava =
|
||||
val absoluteHome = new File(home).getAbsolutePath
|
||||
val cmd = new File(absoluteHome, "bin/java.exe").getAbsolutePath
|
||||
val bin = absoluteHome + File.separator + "bin"
|
||||
val path = getIgnoreCase(env, "PATH") match
|
||||
case Some(old) => bin + File.pathSeparator + old
|
||||
case None => bin
|
||||
val overlay = Seq(
|
||||
keyFor(env, "JAVACMD") -> cmd,
|
||||
keyFor(env, "JAVA_HOME") -> absoluteHome,
|
||||
keyFor(env, "JDK_HOME") -> absoluteHome,
|
||||
keyFor(env, "PATH") -> path
|
||||
)
|
||||
SelectedJava(cmd, overlay, explicit)
|
||||
|
||||
private def getIgnoreCase(env: Map[String, String], key: String): Option[String] =
|
||||
env.collectFirst { case (k, v) if k.equalsIgnoreCase(key) => v }
|
||||
|
||||
/** Existing env key matching `name` case-insensitively, so overlays replace rather than duplicate. */
|
||||
private def keyFor(env: Map[String, String], name: String): String =
|
||||
env.keys.find(_.equalsIgnoreCase(name)).getOrElse(name)
|
||||
|
||||
private def stripQuotes(s: String): String = s.replace("\"", "")
|
||||
end SelectedJava
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2023, Scala center
|
||||
* Copyright 2011 - 2022, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbtw
|
||||
|
||||
object ArgParserSpec extends verify.BasicTestSuite:
|
||||
test("parse should accept --java-home") {
|
||||
val opts = ArgParser.parse(Array("--java-home", "C:\\jdk", "compile")).get
|
||||
assert(opts.javaHome == Some("C:\\jdk"))
|
||||
assert(opts.residual == Seq("compile"))
|
||||
}
|
||||
|
||||
test("parse should accept single-dash -java-home") {
|
||||
val opts = ArgParser.parse(Array("-java-home", "C:\\jdk", "compile")).get
|
||||
assert(opts.javaHome == Some("C:\\jdk"))
|
||||
assert(opts.residual == Seq("compile"))
|
||||
}
|
||||
|
||||
test("parse should accept single-dash -sbt-version") {
|
||||
val opts = ArgParser.parse(Array("-sbt-version", "1.10.7")).get
|
||||
assert(opts.sbtVersion == Some("1.10.7"))
|
||||
}
|
||||
|
||||
test("parse should accept single-dash -mem") {
|
||||
val opts = ArgParser.parse(Array("-mem", "2048")).get
|
||||
assert(opts.mem == Some(2048))
|
||||
}
|
||||
|
||||
test("parse should accept single-dash -no-colors") {
|
||||
val opts = ArgParser.parse(Array("-no-colors")).get
|
||||
assert(opts.noColors)
|
||||
}
|
||||
|
||||
test("parse should accept single-dash -jvm-debug") {
|
||||
val opts = ArgParser.parse(Array("-jvm-debug", "5005")).get
|
||||
assert(opts.jvmDebug == Some(5005))
|
||||
}
|
||||
|
||||
test("parse should detect new alongside -java-home") {
|
||||
val opts = ArgParser.parse(Array("-java-home", "C:\\jdk", "new")).get
|
||||
assert(opts.sbtNew)
|
||||
}
|
||||
|
||||
test("longNames should be derived from the parser grammar") {
|
||||
assert(ArgParser.longNames.contains("java-home"))
|
||||
assert(ArgParser.longNames.contains("client"))
|
||||
assert(ArgParser.longNames.contains("sbt-version"))
|
||||
assert(!ArgParser.longNames.contains("h"))
|
||||
assert(!ArgParser.longNames.contains("<arg>"))
|
||||
assert(!ArgParser.longNames.contains(""))
|
||||
}
|
||||
|
||||
test("normalizeArgs should rewrite single-dash long options to double-dash") {
|
||||
assert(ArgParser.normalizeArgs(Seq("-java-home")) == Seq("--java-home"))
|
||||
assert(ArgParser.normalizeArgs(Seq("-sbt-version")) == Seq("--sbt-version"))
|
||||
assert(ArgParser.normalizeArgs(Seq("-timings")) == Seq("--timings"))
|
||||
}
|
||||
|
||||
test("normalizeArgs should leave other tokens untouched") {
|
||||
val args = Seq("-Dfoo=bar", "-Xmx1G", "-J-Xss2m", "-v", "--java-home", "compile")
|
||||
assert(ArgParser.normalizeArgs(args) == args)
|
||||
}
|
||||
|
||||
test("normalizeArgs should not rewrite past a -- terminator") {
|
||||
assert(ArgParser.normalizeArgs(Seq("--", "-server")) == Seq("--", "-server"))
|
||||
assert(
|
||||
ArgParser.normalizeArgs(Seq("-java-home", "X", "--", "-server")) ==
|
||||
Seq("--java-home", "X", "--", "-server")
|
||||
)
|
||||
}
|
||||
|
||||
test("normalizeArgs should not rewrite a value-taking option's value") {
|
||||
assert(ArgParser.normalizeArgs(Seq("--sbt-dir", "-server")) == Seq("--sbt-dir", "-server"))
|
||||
assert(ArgParser.normalizeArgs(Seq("-sbt-dir", "-server")) == Seq("--sbt-dir", "-server"))
|
||||
}
|
||||
|
||||
test("normalizeArgs should still rewrite the token after a value-less flag") {
|
||||
assert(ArgParser.normalizeArgs(Seq("-no-colors", "-server")) == Seq("--no-colors", "--server"))
|
||||
}
|
||||
end ArgParserSpec
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2023, Scala center
|
||||
* Copyright 2011 - 2022, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbtw
|
||||
|
||||
object ConfigLoaderSpec extends verify.BasicTestSuite:
|
||||
test("tokenize keeps a double-quoted path together") {
|
||||
assert(
|
||||
ConfigLoader.tokenize("""-java-home "C:\Program Files\jdk"""") == Seq(
|
||||
"-java-home",
|
||||
"C:\\Program Files\\jdk"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
test("tokenize keeps a single-quoted path together") {
|
||||
assert(ConfigLoader.tokenize("-sbt-dir '/Users/a dog'") == Seq("-sbt-dir", "/Users/a dog"))
|
||||
}
|
||||
|
||||
test("tokenize splits on unquoted whitespace") {
|
||||
assert(ConfigLoader.tokenize("-mem 2048") == Seq("-mem", "2048"))
|
||||
}
|
||||
|
||||
test("tokenize returns a single token for a bare flag") {
|
||||
assert(ConfigLoader.tokenize("-java-home") == Seq("-java-home"))
|
||||
}
|
||||
|
||||
test("tokenize returns nothing for blank input") {
|
||||
assert(ConfigLoader.tokenize(" ") == Seq.empty)
|
||||
}
|
||||
end ConfigLoaderSpec
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2023, Scala center
|
||||
* Copyright 2011 - 2022, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbtw
|
||||
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
|
||||
object SelectedJavaSpec extends verify.BasicTestSuite:
|
||||
|
||||
private def jdkHome(): File =
|
||||
val home = Files.createTempDirectory("sbtw-jdk").toFile
|
||||
val exe = new File(home, "bin/java.exe")
|
||||
exe.getParentFile.mkdirs()
|
||||
exe.createNewFile()
|
||||
home
|
||||
|
||||
test("resolve should use a valid --java-home") {
|
||||
val home = jdkHome()
|
||||
val selected = SelectedJava.resolve(Some(home.getPath), Map.empty).toOption.get
|
||||
assert(selected.javaCmd == new File(home, "bin/java.exe").getAbsolutePath)
|
||||
}
|
||||
|
||||
test("resolve should reject an invalid --java-home instead of falling back") {
|
||||
val result =
|
||||
SelectedJava.resolve(Some("no-such-dir"), Map("JAVACMD" -> "C:\\jdk\\bin\\java.exe"))
|
||||
assert(result.isLeft)
|
||||
assert(result.swap.exists(_.contains("for JAVA_HOME is not valid")))
|
||||
}
|
||||
|
||||
test("--java-home overlay makes JAVACMD authoritative over an inherited one") {
|
||||
val home = jdkHome()
|
||||
val exe = new File(home, "bin/java.exe").getAbsolutePath
|
||||
val overlay =
|
||||
SelectedJava
|
||||
.resolve(Some(home.getPath), Map("JAVACMD" -> "C:\\old\\bin\\java.exe"))
|
||||
.toOption
|
||||
.get
|
||||
.envOverlay
|
||||
.toMap
|
||||
assert(overlay.get("JAVACMD") == Some(exe))
|
||||
assert(overlay.get("JAVA_HOME") == Some(home.getPath))
|
||||
assert(overlay.get("JDK_HOME") == Some(home.getPath))
|
||||
val expectedPath = home.getPath + File.separator + "bin" + File.pathSeparator + "C:\\old"
|
||||
assert(overlay.get("PATH") == Some(home.getPath + File.separator + "bin"))
|
||||
assert(
|
||||
SelectedJava
|
||||
.resolve(Some(home.getPath), Map("PATH" -> "C:\\old"))
|
||||
.toOption
|
||||
.get
|
||||
.envOverlay
|
||||
.toMap
|
||||
.get("PATH") == Some(expectedPath)
|
||||
)
|
||||
}
|
||||
|
||||
test("resolve should prefer JAVACMD over JAVA_HOME with no overlay") {
|
||||
val env = Map("JAVACMD" -> "C:\\some\\java.exe", "JAVA_HOME" -> "C:\\jdk")
|
||||
val selected = SelectedJava.resolve(None, env).toOption.get
|
||||
assert(selected.javaCmd == "C:\\some\\java.exe")
|
||||
assert(selected.envOverlay.isEmpty)
|
||||
}
|
||||
|
||||
test("resolve should strip quotes from JAVACMD") {
|
||||
val selected =
|
||||
SelectedJava.resolve(None, Map("JAVACMD" -> "\"C:\\some\\java.exe\"")).toOption.get
|
||||
assert(selected.javaCmd == "C:\\some\\java.exe")
|
||||
}
|
||||
|
||||
test("a JDK derived from JAVA_HOME still gets a full overlay (PATH prepend)") {
|
||||
val home = jdkHome().getPath
|
||||
val selected =
|
||||
SelectedJava.resolve(None, Map("JAVA_HOME" -> home, "PATH" -> "/old")).toOption.get
|
||||
assert(selected.javaCmd == new File(home, "bin/java.exe").getAbsolutePath)
|
||||
val overlay = selected.envOverlay.toMap
|
||||
assert(overlay.get("JAVA_HOME") == Some(home))
|
||||
assert(overlay.get("PATH") == Some(home + File.separator + "bin" + File.pathSeparator + "/old"))
|
||||
}
|
||||
|
||||
test("a relative home is exported as an absolute path") {
|
||||
val overlay =
|
||||
SelectedJava.resolve(None, Map("JAVA_HOME" -> "rel-jdk")).toOption.get.envOverlay.toMap
|
||||
assert(new File(overlay("JAVA_HOME")).isAbsolute)
|
||||
assert(new File(overlay("JDK_HOME")).isAbsolute)
|
||||
}
|
||||
|
||||
test("resolve should read JAVACMD case-insensitively") {
|
||||
val selected = SelectedJava.resolve(None, Map("JavaCmd" -> "C:\\x\\java.exe")).toOption.get
|
||||
assert(selected.javaCmd == "C:\\x\\java.exe")
|
||||
assert(selected.envOverlay.isEmpty)
|
||||
}
|
||||
|
||||
test("resolve should read JAVA_HOME case-insensitively") {
|
||||
val home = jdkHome().getPath
|
||||
val selected = SelectedJava.resolve(None, Map("Java_Home" -> home)).toOption.get
|
||||
assert(selected.javaCmd == new File(home, "bin/java.exe").getAbsolutePath)
|
||||
}
|
||||
|
||||
test("resolve should fall back to bare java with no overlay") {
|
||||
val selected = SelectedJava.resolve(None, Map.empty).toOption.get
|
||||
assert(selected.javaCmd == "java")
|
||||
assert(selected.envOverlay.isEmpty)
|
||||
}
|
||||
|
||||
test("overlay should reuse the existing PATH key case") {
|
||||
val overlay = SelectedJava
|
||||
.resolve(Some(jdkHome().getPath), Map("Path" -> "C:\\old"))
|
||||
.toOption
|
||||
.get
|
||||
.envOverlay
|
||||
.toMap
|
||||
assert(overlay.contains("Path"))
|
||||
assert(!overlay.contains("PATH"))
|
||||
}
|
||||
|
||||
test("an explicit --java-home carries the provenance marker only on the handoff") {
|
||||
val selected = SelectedJava.resolve(Some(jdkHome().getPath), Map.empty).toOption.get
|
||||
assert(!selected.envOverlay.toMap.contains(SelectedJava.explicitMarker))
|
||||
assert(selected.handoffEnv.toMap.get(SelectedJava.explicitMarker) == Some("1"))
|
||||
}
|
||||
|
||||
test("a JAVA_HOME-derived selection never carries the provenance marker") {
|
||||
val selected = SelectedJava.resolve(None, Map("JAVA_HOME" -> jdkHome().getPath)).toOption.get
|
||||
assert(!selected.envOverlay.toMap.contains(SelectedJava.explicitMarker))
|
||||
assert(!selected.handoffEnv.toMap.contains(SelectedJava.explicitMarker))
|
||||
}
|
||||
|
||||
test("overlay should replace any-cased alias rather than duplicate it") {
|
||||
val home = jdkHome().getPath
|
||||
val overlay =
|
||||
SelectedJava
|
||||
.resolve(Some(home), Map("javacmd" -> "old", "JavaCmd2" -> "x"))
|
||||
.toOption
|
||||
.get
|
||||
.envOverlay
|
||||
.toMap
|
||||
assert(overlay.contains("javacmd"))
|
||||
assert(!overlay.contains("JAVACMD"))
|
||||
assert(overlay.get("javacmd") == Some(new File(home, "bin/java.exe").getAbsolutePath))
|
||||
assert(overlay.contains("JAVA_HOME"))
|
||||
}
|
||||
end SelectedJavaSpec
|
||||
Loading…
Reference in New Issue