diff --git a/build.sbt b/build.sbt index 6fcde3aed..32a07fe9c 100644 --- a/build.sbt +++ b/build.sbt @@ -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, ) diff --git a/launcher-package/integration-test/bin/java b/launcher-package/integration-test/bin/java index dc584d35b..5dbb4d9eb 100755 --- a/launcher-package/integration-test/bin/java +++ b/launcher-package/integration-test/bin/java @@ -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]) diff --git a/launcher-package/src/universal/bin/sbt.bat b/launcher-package/src/universal/bin/sbt.bat index 6dabbfff4..4dc2416c4 100755 --- a/launcher-package/src/universal/bin/sbt.bat +++ b/launcher-package/src/universal/bin/sbt.bat @@ -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 diff --git a/sbt b/sbt index b0bcd7d4b..9cd528c6c 100755 --- a/sbt +++ b/sbt @@ -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 diff --git a/sbtw/src/main/scala/sbtw/Main.scala b/sbtw/src/main/scala/sbtw/Main.scala index 52e2920b0..fb67b2770 100644 --- a/sbtw/src/main/scala/sbtw/Main.scala +++ b/sbtw/src/main/scala/sbtw/Main.scala @@ -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) diff --git a/sbtw/src/main/scala/sbtw/Runner.scala b/sbtw/src/main/scala/sbtw/Runner.scala index 06bc6d7e9..48bdc328c 100644 --- a/sbtw/src/main/scala/sbtw/Runner.scala +++ b/sbtw/src/main/scala/sbtw/Runner.scala @@ -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" diff --git a/sbtw/src/test/scala/sbtw/RunnerSpec.scala b/sbtw/src/test/scala/sbtw/RunnerSpec.scala new file mode 100644 index 000000000..63f40d01a --- /dev/null +++ b/sbtw/src/test/scala/sbtw/RunnerSpec.scala @@ -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