[2.x] fix: Runner should fail on JDK < 17 for sbt 2.x (#8825)

**Problem**
Running sbt 2.x with JDK 8 produces a confusing "server was not
detected" error because the JDK version check only required JDK 8+
and only ran in the non-native-client path.

**Solution**
Move java_version detection before the native client decision and add
checkJava17ForSbt2 that requires JDK 17+ when sbt major version >= 2.

Fixes #8813

* [2.x] fix: Fail early when sbt 2.x is run with JDK < 17 (sbtw)

Move JDK version check before native client decision in sbtw and
require JDK 17+ when build.properties declares sbt 2.x.

* [2.x] fix: Fail early when sbt 2.x is run with JDK < 17 (sbt.bat)

Move checkjava before native client decision in sbt.bat and require
JDK 17+ when build.properties declares sbt 2.x.

* [2.x] test: Add minimumJdkVersion helper and unit tests for sbtw

Extract JDK version check logic into Runner.minimumJdkVersion for
testability. Add RunnerSpec with tests for sbt 1.x, 2.x, and 3.x
version detection.

* [2.x] test: Bump fake java to JDK 17 for integration tests

The fake java script used by launcher integration tests reported
JDK 8. Since sbt 2.x now requires JDK 17+, the citest2 (sbt 2.x)
integration tests would fail with the new JDK version check.

* Simulate JDK 9+ rt.jar handling in fake java script

Instead of silently ignoring --rt-ext-dir (which causes sbt.bat
to mkdir on an empty string), properly simulate JDK 9+ behavior
by creating a temp directory with java9-rt-ext- prefix and a
dummy rt.jar inside it.
This commit is contained in:
Dream 2026-02-27 11:43:24 -05:00 committed by GitHub
parent 034834bd71
commit 28d877f633
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 92 additions and 11 deletions

View File

@ -976,6 +976,7 @@ lazy val sbtwProj = (project in file("sbtw"))
crossPaths := false,
Compile / mainClass := Some("sbtw.Main"),
libraryDependencies += "com.github.scopt" %% "scopt" % "4.1.0",
libraryDependencies += scalaVerify % Test,
Utils.noPublish,
)

View File

@ -1,9 +1,28 @@
#!/usr/bin/env python3
import os
import sys
import tempfile
if '--version' in sys.argv or '-version' in sys.argv:
print('openjdk version "1.8.0_212"')
print('openjdk version "17.0.12" 2024-07-16')
elif '--rt-ext-dir' in sys.argv:
# Simulate JDK 9+ rt.jar ext dir: output a directory path containing
# "java9-rt-ext-" that the launcher scripts look for via grep/findstr.
ext_dir = os.path.join(tempfile.gettempdir(), 'java9-rt-ext-fake')
os.makedirs(ext_dir, exist_ok=True)
# Create a dummy rt.jar so the launcher won't try to --export-rt
rt_jar = os.path.join(ext_dir, 'rt.jar')
if not os.path.exists(rt_jar):
open(rt_jar, 'w').close()
print(ext_dir)
elif '--export-rt' in sys.argv:
# Simulate rt.jar export: create the file at the specified path
idx = sys.argv.index('--export-rt')
if idx + 1 < len(sys.argv):
rt_path = sys.argv[idx + 1]
os.makedirs(os.path.dirname(rt_path), exist_ok=True)
open(rt_path, 'w').close()
else:
for arg in sys.argv[1:]:
print(repr(arg)[1:-1])

View File

@ -617,13 +617,13 @@ if !sbt_args_print_sbt_script_version! equ 1 (
goto :eof
)
call :checkjava
if !run_native_client! equ 1 if not defined sbt_args_print_version (
goto :runnative !SBT_ARGS!
goto :eof
)
call :checkjava
if defined sbt_args_sbt_jar (
set "sbt_jar=!sbt_args_sbt_jar!"
) else (
@ -1008,9 +1008,24 @@ exit /B 0
:checkjava
set /a required_version=8
rem sbt 2.x requires JDK 17+
set "_sbt_check_ver=!build_props_sbt_version!"
if not defined _sbt_check_ver set "_sbt_check_ver=!init_sbt_version!"
if defined _sbt_check_ver (
for /F "delims=.-_ tokens=1" %%m in ("!_sbt_check_ver!") do (
if %%m GEQ 2 set /a required_version=17
)
)
set "_sbt_check_ver="
if /I !JAVA_VERSION! GEQ !required_version! (
exit /B 0
)
if !required_version! GEQ 17 (
>&2 echo.
>&2 echo [error] sbt 2.x requires JDK 17 or above, but you have JDK !JAVA_VERSION!
>&2 echo.
exit /B 1
)
>&2 echo.
>&2 echo The Java Development Kit ^(JDK^) installation you have is not up to date.
>&2 echo sbt requires at least version !required_version!+, you have

18
sbt
View File

@ -506,6 +506,18 @@ checkJava() {
fi
}
# sbt 2.x requires JDK 17+
checkJava17ForSbt2() {
local sbtV="$build_props_sbt_version"
[[ "$sbtV" == "" ]] && sbtV="$init_sbt_version"
[[ "$sbtV" == "" ]] && return
local sbtMajor=$(echo "$sbtV" | sed 's/^\([0-9]*\).*/\1/')
if (( sbtMajor >= 2 )) && [[ "$java_version" != "no_java" ]] && (( java_version < 17 )); then
echoerr "[error] sbt 2.x requires JDK 17 or above, but you have JDK $java_version"
exit 1
fi
}
copyRt() {
local at_least_9="$(expr $java_version ">=" 9)"
if [[ "$at_least_9" == "1" ]]; then
@ -916,13 +928,15 @@ if [[ $print_sbt_script_version ]]; then
exit 0
fi
java_version="$(jdk_version)"
vlog "[process_args] java_version = '$java_version'"
checkJava17ForSbt2
if [[ "$(isRunNativeClient)" == "true" ]] && [[ -z "$print_version" ]]; then
set -- "${residual_args[@]}"
argumentCount=$#
runNativeClient
else
java_version="$(jdk_version)"
vlog "[process_args] java_version = '$java_version'"
addDefaultMemory
addSbtScriptProperty
addJdkWorkaround

View File

@ -41,6 +41,16 @@ object Main:
return 1
val buildPropsVersion = ConfigLoader.sbtVersionFromBuildProperties(cwd)
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)
return 1
val clientOpt = opts.client || sys.env.get("SBT_NATIVE_CLIENT").contains("true")
val useNativeClient = shouldRunNativeClient(opts.copy(client = clientOpt), buildPropsVersion)
@ -48,12 +58,6 @@ object Main:
val scriptPath = sbtBinDir.getAbsolutePath.replace("\\", "/") + "/sbt.bat"
return Runner.runNativeClient(sbtBinDir, scriptPath, opts)
val javaCmd = Runner.findJavaCmd(opts.javaHome)
val javaVer = Runner.javaVersion(javaCmd)
if javaVer > 0 && javaVer < 8 then
System.err.println("[error] sbt requires at least JDK 8+, you have " + javaVer)
return 1
val sbtJar = opts.sbtJar
.filter(p => new File(p).isFile)
.getOrElse(new File(sbtBinDir, "sbt-launch.jar").getAbsolutePath)

View File

@ -38,6 +38,11 @@ object Runner:
else major
catch { case _: Exception => 0 }
/** Returns the minimum JDK version required for the given sbt version. */
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] =
var s: Seq[String] = Nil
if opts.debug then s = s :+ "-debug"

View File

@ -0,0 +1,23 @@
package sbtw
object RunnerSpec extends verify.BasicTestSuite:
test("minimumJdkVersion should require JDK 17 for sbt 2.x") {
assert(Runner.minimumJdkVersion(Some("2.0.0-RC9")) == 17)
}
test("minimumJdkVersion should require JDK 17 for sbt 2.x snapshot") {
assert(Runner.minimumJdkVersion(Some("2.0.0-SNAPSHOT")) == 17)
}
test("minimumJdkVersion should require JDK 8 for sbt 1.x") {
assert(Runner.minimumJdkVersion(Some("1.10.7")) == 8)
}
test("minimumJdkVersion should require JDK 8 when version is absent") {
assert(Runner.minimumJdkVersion(None) == 8)
}
test("minimumJdkVersion should require JDK 17 for future sbt 3.x") {
assert(Runner.minimumJdkVersion(Some("3.0.0")) == 17)
}
end RunnerSpec