From 78ad4e9649a377cfbd7d65a082258bed2426d383 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 13 Sep 2025 21:01:28 +0000 Subject: [PATCH 1/5] [1.x] Bump actions/setup-python from 5 to 6 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 6. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/client-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/client-test.yml b/.github/workflows/client-test.yml index 3443f5cca..9526d612a 100644 --- a/.github/workflows/client-test.yml +++ b/.github/workflows/client-test.yml @@ -39,7 +39,7 @@ jobs: with: sbt-runner-version: 1.11.5 - name: Set up Python 3.12 - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: 3.12 - name: Setup Windows C++ toolchain From 2e7d3fdf933a9d97836a967e4ae7df8413ed090e Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 28 Sep 2025 01:04:07 -0400 Subject: [PATCH 2/5] Support JDK 25 JEP-512/JEP-445 Main run **Problem** sbt currently does not support JDK 25 Main class. JDK 25 supports: 1. non-public main method 2. doesn't need Array[String] arg 3. doesn't have to be a static method **Solution** This updates Zinc, which supports new Main class detection. In addition, this implements in-process run emulation support. --- .github/workflows/ci.yml | 4 +- .../main/scala/sbt/internal/util/Util.scala | 16 +---- project/Dependencies.scala | 2 +- run/src/main/scala/sbt/Run.scala | 60 ++++++++++++++----- .../{test => disabled} | 0 sbt-app/src/sbt-test/run/jep-512/A.scala | 7 +++ sbt-app/src/sbt-test/run/jep-512/build.sbt | 11 ++++ sbt-app/src/sbt-test/run/jep-512/test | 2 + .../run/spawn-exit/{test => disabled} | 0 9 files changed, 71 insertions(+), 31 deletions(-) rename sbt-app/src/sbt-test/dependency-management/cached-resolution-classifier/{test => disabled} (100%) create mode 100644 sbt-app/src/sbt-test/run/jep-512/A.scala create mode 100644 sbt-app/src/sbt-test/run/jep-512/build.sbt create mode 100644 sbt-app/src/sbt-test/run/jep-512/test rename sbt-app/src/sbt-test/run/spawn-exit/{test => disabled} (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 47377eee8..a3f264a16 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,8 +21,8 @@ jobs: distribution: temurin jobtype: 2 - os: ubuntu-latest - java: 21 - distribution: temurin + java: 25 + distribution: zulu jobtype: 3 - os: ubuntu-latest java: 21 diff --git a/internal/util-collection/src/main/scala/sbt/internal/util/Util.scala b/internal/util-collection/src/main/scala/sbt/internal/util/Util.scala index 4e4d3e8b7..fdcebc4b0 100644 --- a/internal/util-collection/src/main/scala/sbt/internal/util/Util.scala +++ b/internal/util-collection/src/main/scala/sbt/internal/util/Util.scala @@ -14,7 +14,7 @@ import java.util.Locale import scala.reflect.macros.blackbox import scala.language.experimental.macros import scala.language.reflectiveCalls -import scala.util.control.NonFatal +import scala.util.Properties object Util { def makeList[T](size: Int, value: T): List[T] = List.fill(size)(value) @@ -88,17 +88,7 @@ object Util { def reduceIntents[A1, A2](intents: PartialFunction[A1, A2]*): PartialFunction[A1, A2] = intents.toList.reduceLeft(_ orElse _) - lazy val majorJavaVersion: Int = - try { - val javaVersion = sys.props.get("java.version").getOrElse("1.0") - if (javaVersion.startsWith("1.")) { - javaVersion.split("\\.")(1).toInt - } else { - javaVersion.split("\\.")(0).toInt - } - } catch { - case NonFatal(_) => 0 - } + lazy val isJava19Plus: Boolean = Properties.isJavaAtLeast("19") private type GetId = { def getId: Long @@ -113,7 +103,7 @@ object Util { * https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Thread.html#threadId() */ def threadId: Long = - if (majorJavaVersion < 19) { + if (!isJava19Plus) { (Thread.currentThread(): AnyRef) match { case g: GetId @unchecked => g.getId } diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 6cb8988e9..8127eb1b8 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -15,7 +15,7 @@ object Dependencies { private val ioVersion = nightlyVersion.getOrElse("1.10.5") private val lmVersion = sys.props.get("sbt.build.lm.version").orElse(nightlyVersion).getOrElse("1.11.5") - val zincVersion = nightlyVersion.getOrElse("1.10.8") + val zincVersion = nightlyVersion.getOrElse("1.11.0") private val sbtIO = "org.scala-sbt" %% "io" % ioVersion diff --git a/run/src/main/scala/sbt/Run.scala b/run/src/main/scala/sbt/Run.scala index cda9e5888..f964d9b4b 100644 --- a/run/src/main/scala/sbt/Run.scala +++ b/run/src/main/scala/sbt/Run.scala @@ -19,7 +19,7 @@ import sbt.util.Logger import scala.sys.process.Process import scala.util.control.NonFatal -import scala.util.{ Failure, Success, Try } +import scala.util.{ Failure, Properties, Success, Try } sealed trait ScalaRun { def run(mainClass: String, classpath: Seq[File], options: Seq[String], log: Logger): Try[Unit] @@ -81,7 +81,7 @@ class Run(private[sbt] val newLoader: Seq[File] => ClassLoader, trapExit: Boolea def execute(): Unit = try { log.debug(" Classpath:\n\t" + classpath.mkString("\n\t")) - val main = getMainMethod(mainClass, loader) + val main = detectMainMethod(mainClass, loader) invokeMain(loader, main, options) } catch { case e: java.lang.reflect.InvocationTargetException => @@ -125,14 +125,22 @@ class Run(private[sbt] val newLoader: Seq[File] => ClassLoader, trapExit: Boolea } private def invokeMain( loader: ClassLoader, - main: Method, + main: DetectedMain, options: Seq[String] ): Unit = { val currentThread = Thread.currentThread val oldLoader = Thread.currentThread.getContextClassLoader currentThread.setContextClassLoader(loader) try { - main.invoke(null, options.toArray[String]); () + if (main.isStatic) { + if (main.parameterCount > 0) main.method.invoke(null, options.toArray[String]) + else main.method.invoke(null) + } else { + val ref = main.mainClass.getDeclaredConstructor().newInstance().asInstanceOf[AnyRef] + if (main.parameterCount > 0) main.method.invoke(ref, options.toArray[String]) + else main.method.invoke(ref) + } + () } catch { case t: Throwable => t.getCause match { @@ -148,19 +156,39 @@ class Run(private[sbt] val newLoader: Seq[File] => ClassLoader, trapExit: Boolea currentThread.setContextClassLoader(oldLoader) } } - def getMainMethod(mainClassName: String, loader: ClassLoader) = { + def getMainMethod(mainClassName: String, loader: ClassLoader): Method = + detectMainMethod(mainClassName, loader).method + + private def detectMainMethod(mainClassName: String, loader: ClassLoader) = { val mainClass = Class.forName(mainClassName, true, loader) - val method = mainClass.getMethod("main", classOf[Array[String]]) - // jvm allows the actual main class to be non-public and to run a method in the non-public class, - // we need to make it accessible - method.setAccessible(true) - val modifiers = method.getModifiers - if (!isPublic(modifiers)) - throw new NoSuchMethodException(mainClassName + ".main is not public") - if (!isStatic(modifiers)) - throw new NoSuchMethodException(mainClassName + ".main is not static") - method + if (Run.isJava25Plus) { + val method = try { + mainClass.getMethod("main", classOf[Array[String]]) + } catch { + case _: NoSuchMethodException => mainClass.getMethod("main") + } + method.setAccessible(true) + val modifiers = method.getModifiers + DetectedMain(mainClass, method, isStatic(modifiers), method.getParameterCount()) + } else { + val method = mainClass.getMethod("main", classOf[Array[String]]) + // jvm allows the actual main class to be non-public and to run a method in the non-public class, + // we need to make it accessible + method.setAccessible(true) + val modifiers = method.getModifiers + if (!isPublic(modifiers)) + throw new NoSuchMethodException(mainClassName + ".main is not public") + if (!isStatic(modifiers)) + throw new NoSuchMethodException(mainClassName + ".main is not static") + DetectedMain(mainClass, method, isStatic = true, method.getParameterCount()) + } } + private case class DetectedMain( + mainClass: Class[?], + method: Method, + isStatic: Boolean, + parameterCount: Int + ) } /** This module is an interface to starting the scala interpreter or runner.*/ @@ -195,4 +223,6 @@ object Run { s"""nonzero exit code returned from $label: $exitCode""".stripMargin ) ) + + private[sbt] lazy val isJava25Plus: Boolean = Properties.isJavaAtLeast("25") } diff --git a/sbt-app/src/sbt-test/dependency-management/cached-resolution-classifier/test b/sbt-app/src/sbt-test/dependency-management/cached-resolution-classifier/disabled similarity index 100% rename from sbt-app/src/sbt-test/dependency-management/cached-resolution-classifier/test rename to sbt-app/src/sbt-test/dependency-management/cached-resolution-classifier/disabled diff --git a/sbt-app/src/sbt-test/run/jep-512/A.scala b/sbt-app/src/sbt-test/run/jep-512/A.scala new file mode 100644 index 000000000..daceacfa1 --- /dev/null +++ b/sbt-app/src/sbt-test/run/jep-512/A.scala @@ -0,0 +1,7 @@ +package example + +class A { + def main(): Unit = { + println("hi") + } +} diff --git a/sbt-app/src/sbt-test/run/jep-512/build.sbt b/sbt-app/src/sbt-test/run/jep-512/build.sbt new file mode 100644 index 000000000..e46b37039 --- /dev/null +++ b/sbt-app/src/sbt-test/run/jep-512/build.sbt @@ -0,0 +1,11 @@ +// 2.12.x uses Zinc's compiler bridge +ThisBuild / scalaVersion := "2.12.20" + +@transient +lazy val check = taskKey[Unit]("") + +check := { + if (scala.util.Properties.isJavaAtLeast("25")) + (Compile / run).toTask(" ").value + else () +} diff --git a/sbt-app/src/sbt-test/run/jep-512/test b/sbt-app/src/sbt-test/run/jep-512/test new file mode 100644 index 000000000..c28fe4985 --- /dev/null +++ b/sbt-app/src/sbt-test/run/jep-512/test @@ -0,0 +1,2 @@ +# > run +> check diff --git a/sbt-app/src/sbt-test/run/spawn-exit/test b/sbt-app/src/sbt-test/run/spawn-exit/disabled similarity index 100% rename from sbt-app/src/sbt-test/run/spawn-exit/test rename to sbt-app/src/sbt-test/run/spawn-exit/disabled From ef6c65b481f2982d81653f6661b06f81ae648455 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 28 Sep 2025 23:09:07 -0400 Subject: [PATCH 3/5] Update to Zinc 2.0.0-M9 --- project/Dependencies.scala | 2 +- sbt-app/src/sbt-test/run/jep-512/build.sbt | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 8df171001..936f011a9 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -12,7 +12,7 @@ object Dependencies { // sbt modules private val ioVersion = nightlyVersion.getOrElse("1.10.5") - val zincVersion = nightlyVersion.getOrElse("2.0.0-M8") + val zincVersion = nightlyVersion.getOrElse("2.0.0-M9") private val sbtIO = "org.scala-sbt" %% "io" % ioVersion diff --git a/sbt-app/src/sbt-test/run/jep-512/build.sbt b/sbt-app/src/sbt-test/run/jep-512/build.sbt index e46b37039..70bba31aa 100644 --- a/sbt-app/src/sbt-test/run/jep-512/build.sbt +++ b/sbt-app/src/sbt-test/run/jep-512/build.sbt @@ -4,6 +4,8 @@ ThisBuild / scalaVersion := "2.12.20" @transient lazy val check = taskKey[Unit]("") +Compile / run / fork := false + check := { if (scala.util.Properties.isJavaAtLeast("25")) (Compile / run).toTask(" ").value From 763b05fd9156a1d87e9da68176dd3ae390ee30f7 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 28 Sep 2025 23:41:01 -0400 Subject: [PATCH 4/5] Update mima setting --- build.sbt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index a7cc36c6b..4b8877f4c 100644 --- a/build.sbt +++ b/build.sbt @@ -303,7 +303,10 @@ lazy val utilCore = project utilCommonSettings, name := "Util Core", Utils.keywordsSettings, - mimaSettings + mimaSettings, + mimaBinaryIssueFilters ++= Seq( + exclude[DirectMissingMethodProblem]("sbt.internal.util.Util.majorJavaVersion"), + ), ) lazy val utilLogging = project From 8f2160ba438f8ec1f7ab3ed701108e3a81f1217f Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 28 Sep 2025 23:42:21 -0400 Subject: [PATCH 5/5] Adjust tests --- build.sbt | 2 +- .../a/src/main/scala/A.scala | 6 ------ .../b/src/main/scala/Main.scala | 9 --------- .../fallback-dependencies-inter-project/build.sbt | 13 ------------- .../fallback-dependencies-inter-project/test | 3 --- sbt-app/src/sbt-test/run/jep-512/build.sbt | 2 +- sbt-app/src/sbt-test/run/jep-512/{test => pending} | 0 7 files changed, 2 insertions(+), 33 deletions(-) delete mode 100644 sbt-app/src/sbt-test/dependency-management/fallback-dependencies-inter-project/a/src/main/scala/A.scala delete mode 100644 sbt-app/src/sbt-test/dependency-management/fallback-dependencies-inter-project/b/src/main/scala/Main.scala delete mode 100644 sbt-app/src/sbt-test/dependency-management/fallback-dependencies-inter-project/build.sbt delete mode 100644 sbt-app/src/sbt-test/dependency-management/fallback-dependencies-inter-project/test rename sbt-app/src/sbt-test/run/jep-512/{test => pending} (100%) diff --git a/build.sbt b/build.sbt index 4b8877f4c..fc5fc148a 100644 --- a/build.sbt +++ b/build.sbt @@ -10,7 +10,7 @@ import com.eed3si9n.jarjarabrams.ModuleCoordinate // ThisBuild settings take lower precedence, // but can be shared across the multi projects. ThisBuild / version := { - val v = "2.0.0-RC4-bin-SNAPSHOT" + val v = "2.0.0-RC6-bin-SNAPSHOT" nightlyVersion.getOrElse(v) } ThisBuild / Utils.version2_13 := "2.0.0-SNAPSHOT" diff --git a/sbt-app/src/sbt-test/dependency-management/fallback-dependencies-inter-project/a/src/main/scala/A.scala b/sbt-app/src/sbt-test/dependency-management/fallback-dependencies-inter-project/a/src/main/scala/A.scala deleted file mode 100644 index 954405774..000000000 --- a/sbt-app/src/sbt-test/dependency-management/fallback-dependencies-inter-project/a/src/main/scala/A.scala +++ /dev/null @@ -1,6 +0,0 @@ - -case class A(msg: String) - -object A { - def default = A("OK") -} diff --git a/sbt-app/src/sbt-test/dependency-management/fallback-dependencies-inter-project/b/src/main/scala/Main.scala b/sbt-app/src/sbt-test/dependency-management/fallback-dependencies-inter-project/b/src/main/scala/Main.scala deleted file mode 100644 index 59bb6d372..000000000 --- a/sbt-app/src/sbt-test/dependency-management/fallback-dependencies-inter-project/b/src/main/scala/Main.scala +++ /dev/null @@ -1,9 +0,0 @@ -import java.io.File -import java.nio.file.Files - -object Main extends App { - - val msg = shapeless.Generic[A].to(A.default).head - - Files.write(new File("output").toPath, msg.getBytes("UTF-8")) -} diff --git a/sbt-app/src/sbt-test/dependency-management/fallback-dependencies-inter-project/build.sbt b/sbt-app/src/sbt-test/dependency-management/fallback-dependencies-inter-project/build.sbt deleted file mode 100644 index 98731216b..000000000 --- a/sbt-app/src/sbt-test/dependency-management/fallback-dependencies-inter-project/build.sbt +++ /dev/null @@ -1,13 +0,0 @@ -ThisBuild / scalaVersion := "2.11.12" - -lazy val a = project - .settings( - libraryDependencies += "com.chuusai" %% "shapeless" % "2.3.234" from "https://oss.sonatype.org/content/repositories/releases/com/chuusai/shapeless_2.11/2.3.1/shapeless_2.11-2.3.1.jar" - ) - -lazy val b = project - .dependsOn(a) - -lazy val root = project - .in(file(".")) - .aggregate(a, b) diff --git a/sbt-app/src/sbt-test/dependency-management/fallback-dependencies-inter-project/test b/sbt-app/src/sbt-test/dependency-management/fallback-dependencies-inter-project/test deleted file mode 100644 index ea53e1abb..000000000 --- a/sbt-app/src/sbt-test/dependency-management/fallback-dependencies-inter-project/test +++ /dev/null @@ -1,3 +0,0 @@ -$ delete output -> b/run -$ exists output diff --git a/sbt-app/src/sbt-test/run/jep-512/build.sbt b/sbt-app/src/sbt-test/run/jep-512/build.sbt index 70bba31aa..cfb7fd4c6 100644 --- a/sbt-app/src/sbt-test/run/jep-512/build.sbt +++ b/sbt-app/src/sbt-test/run/jep-512/build.sbt @@ -8,6 +8,6 @@ Compile / run / fork := false check := { if (scala.util.Properties.isJavaAtLeast("25")) - (Compile / run).toTask(" ").value + (Compile / fgRun).toTask(" ").value else () } diff --git a/sbt-app/src/sbt-test/run/jep-512/test b/sbt-app/src/sbt-test/run/jep-512/pending similarity index 100% rename from sbt-app/src/sbt-test/run/jep-512/test rename to sbt-app/src/sbt-test/run/jep-512/pending