[2.x] fix: Honor JAVA_HOME in the Unix launcher (#9825)

Problem: The Unix launcher ignores JAVA_HOME and selects Java from PATH when JAVACMD is absent.

Solution: Check JAVA_HOME/bin/java before falling back to PATH while preserving explicit --java-home and JAVACMD precedence.

Fixes #9794
This commit is contained in:
Anatolii Kmetiuk
2026-09-25 14:30:25 -04:00
committed by GitHub
parent a015c666df
commit 79c7d9d728
3 changed files with 56 additions and 0 deletions
@@ -9,6 +9,8 @@
package example.test
import java.io.File
import sbt.io.IO
import scala.sys.process.{ Process, ProcessLogger }
/**
* First-hop propagation guard: an explicit `-java-home` must be the JDK the Windows launcher runs
@@ -33,4 +35,55 @@ object JavaHomeScriptTest extends verify.BasicTestSuite with ShellScriptUtil:
out.exists(_.contains(javaExe)),
s"launcher should run $javaExe; command echo was: ${out.mkString(" | ")}"
)
List(
("JAVA_HOME overrides PATH", "", "home", "", "home"),
("JAVACMD overrides JAVA_HOME", "command", "home", "", "command"),
("invalid JAVACMD falls back to JAVA_HOME", "missing", "home", "", "home"),
("empty JAVA_HOME falls back to PATH", "", "", "", "path"),
("invalid JAVA_HOME falls back to PATH", "", "missing", "", "path"),
("invalid JAVACMD and JAVA_HOME fall back to PATH", "missing", "missing", "", "path"),
("--java-home overrides environment", "command", "home", "option", "option"),
).foreach: (name, command, home, option, expected) =>
test(name):
checkJavaSelection(command, home, option, expected)
private def checkJavaSelection(
command: String,
home: String,
option: String,
expected: String
): Unit =
if isWindows then cancel("Bash launcher Java selection")
else
IO.withTemporaryDirectory: dir =>
def javaHome(name: String): File = new File(dir, s"$name jdk")
def java(name: String): File = new File(javaHome(name), "bin/java")
List("path", "home", "command", "option").foreach: name =>
IO.write(
java(name),
IO.read(new File(javaBinDir, "java"))
.replace("else:\n", s"else:\n print('SELECTED: $name')\n")
)
Predef.assert(java(name).setExecutable(true))
IO.write(new File(dir, "build.sbt"), "")
val env = Seq(
"PATH" -> s"${java("path").getParent}${File.pathSeparator}${sys.env("PATH")}",
"JAVACMD" -> (if command.isEmpty then "" else java(command).getAbsolutePath),
"JAVA_HOME" -> (if home.isEmpty then "" else javaHome(home).getAbsolutePath),
"JAVA_OPTS" -> "",
"SBT_OPTS" -> "",
"JAVA_TOOL_OPTIONS" -> "",
"XDG_CONFIG_HOME" -> dir.getAbsolutePath,
"SBT_ETC_FILE" -> new File(dir, "missing-sbtopts").getAbsolutePath,
)
val args = if option.isEmpty then Seq.empty
else Seq("--java-home", javaHome(option).getAbsolutePath)
val output = scala.collection.mutable.ListBuffer.empty[String]
val exit = Process(
Seq(sbtScript.getAbsolutePath, "--server") ++ args ++ Seq("compile"),
dir,
env*
).!(ProcessLogger(line => output += line, line => output += line))
Predef.assert(exit == 0, output.mkString("\n"))
Predef.assert(output.contains(s"SELECTED: $expected"), output.mkString("\n"))
end JavaHomeScriptTest
@@ -173,6 +173,7 @@ trait ShellScriptUtil extends BasicTestSuite:
envVars("JAVACMD") = new File(javaBinDir, "java").getAbsolutePath()
envVars("JAVA_HOME") = javaHomeEnv
else
envVars("JAVACMD") = new File(javaBinDir, "java").getAbsolutePath()
envVars("PATH") = javaBinDir + File.pathSeparator + path
envVars("JAVA_HOME") = javaHomeEnv
val cmd =
+2
View File
@@ -995,6 +995,8 @@ sbt_file_opts=()
if [[ -f "$JAVACMD" ]]; then
java_cmd="$JAVACMD"
elif [[ -n "$JAVA_HOME" && -f "$JAVA_HOME/bin/java" ]]; then
java_cmd="$JAVA_HOME/bin/java"
fi
# Pull in the machine-wide settings configuration.