From cb916274615b95dafaf3f168991024dde0976e48 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Mon, 22 Dec 2025 13:55:40 +0100 Subject: [PATCH 1/6] Add task to validate expected empty jars --- project/Build.scala | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/project/Build.scala b/project/Build.scala index bbb069c072c4..2f6b6db0b18c 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1255,6 +1255,7 @@ object Build { lazy val `scala3-library-bootstrapped-new` = project.in(file("library")) .dependsOn(`scala-library-bootstrapped`) .settings(publishSettings) + .settings(emptyJarPackageSettings) .settings( name := "scala3-library-bootstrapped", moduleName := "scala3-library", @@ -1392,6 +1393,7 @@ object Build { lazy val `scala3-library-sjs` = project.in(file("library-js")) .dependsOn(`scala-library-sjs`) .settings(publishSettings) + .settings(emptyJarPackageSettings) // Validate JAR is empty (only META-INF) .settings( name := "scala3-library-sjs", moduleName := "scala3-library_sjs1", @@ -2831,6 +2833,43 @@ object Build { assert(!tastyIsExperimental, "Stable version cannot use experimental TASTY") } } + /** Helper to validate JAR contents */ + private def validateJarIsEmpty(jar: File): File = { + val jarFile = new java.util.jar.JarFile(jar) + try { + import scala.jdk.CollectionConverters._ + val nonMetaInfEntries = jarFile.entries().asScala + .map(_.getName) + .filterNot(name => name.startsWith("META-INF/") || name == "META-INF") + .toList + + if (nonMetaInfEntries.nonEmpty) { + val entriesList = nonMetaInfEntries.take(10).mkString("\n - ", "\n - ", "") + val truncated = if (nonMetaInfEntries.size > 10) s"\n ... and ${nonMetaInfEntries.size - 10} more" else "" + sys.error( + s"""JAR ${jar.getName} should only contain META-INF entries but found ${nonMetaInfEntries.size} other entries:$entriesList$truncated + | + |This artifact is intended to be an empty placeholder that only declares dependencies. + |If you need to add content, please verify this is intentional.""".stripMargin + ) + } + } finally jarFile.close() + jar + } + + /** Settings for projects that should produce empty JARs (only META-INF allowed). + * These are dependency placeholder projects like scala3-library-bootstrapped-new and scala3-library-sjs. + * Validates: .jar, -sources.jar, and -javadoc.jar + */ + lazy val emptyJarPackageSettings = Def.settings( + Compile / sources := Seq(), + Compile / resources := Seq(), + Test / sources := Seq(), + Test / resources := Seq(), + Compile / packageBin := (Compile / packageBin).map(validateJarIsEmpty).value, + Compile / packageSrc := (Compile / packageSrc).map(validateJarIsEmpty).value, + Compile / packageDoc := (Compile / packageDoc).map(validateJarIsEmpty).value, + ) } object ScaladocConfigs { From 5e1747b2e7f0d8744549d9405896e765f82e4c71 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Mon, 22 Dec 2025 13:57:59 +0100 Subject: [PATCH 2/6] Don't delegate to `scala-library-bootstrapped` to build libraries --- project/Build.scala | 35 ++++++++--------------------------- 1 file changed, 8 insertions(+), 27 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 2f6b6db0b18c..252c1fd2cc32 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1255,7 +1255,6 @@ object Build { lazy val `scala3-library-bootstrapped-new` = project.in(file("library")) .dependsOn(`scala-library-bootstrapped`) .settings(publishSettings) - .settings(emptyJarPackageSettings) .settings( name := "scala3-library-bootstrapped", moduleName := "scala3-library", @@ -1267,20 +1266,10 @@ object Build { crossPaths := true, // org.scala-lang:scala3-library has a crosspath // Do not depend on the `org.scala-lang:scala3-library` automatically, we manually depend on `scala-library-bootstrapped` autoScalaLibrary := false, - // Drop all the scala tools in this project, so we can never generate any bytecode, or documentation - managedScalaInstance := false, + // Configure to use the non-bootstrapped compiler + bootstrappedScalaInstanceSettings, // This Project only has a dependency to `org.scala-lang:scala-library:*.**.**-bootstrapped` - Compile / sources := Seq(), - Compile / resources := Seq(), - Test / sources := Seq(), - Test / resources := Seq(), - // Bridge the common task to call the ones of the actual library project - Compile / compile := (`scala-library-bootstrapped` / Compile / compile).value, - Compile / doc := (`scala-library-bootstrapped` / Compile / doc).value, - Compile / run := (`scala-library-bootstrapped` / Compile / run).evaluated, - Test / compile := (`scala-library-bootstrapped` / Test / compile).value, - Test / doc := (`scala-library-bootstrapped` / Test / doc).value, - Test / run := (`scala-library-bootstrapped` / Test / run).evaluated, + emptyJarPackageSettings, // Packaging configuration of the stdlib Compile / publishArtifact := true, Test / publishArtifact := false, @@ -1405,20 +1394,10 @@ object Build { crossPaths := true, // org.scala-lang:scala3-library_sjs1 has a crosspath // Do not depend on the `org.scala-lang:scala3-library` automatically, we manually depend on `scala-library-bootstrapped` autoScalaLibrary := false, - // Drop all the scala tools in this project, so we can never generate any bytecode, or documentation - managedScalaInstance := false, + // Configure to use the non-bootstrapped compiler + bootstrappedScalaInstanceSettings, // This Project only has a dependency to `org.scala-js:scalajs-scalalib:*.**.**-bootstrapped` - Compile / sources := Seq(), - Compile / resources := Seq(), - Test / sources := Seq(), - Test / resources := Seq(), - // Bridge the common task to call the ones of the actual library project - Compile / compile := (`scala-library-sjs` / Compile / compile).value, - Compile / doc := (`scala-library-sjs` / Compile / doc).value, - Compile / run := (`scala-library-sjs` / Compile / run).evaluated, - Test / compile := (`scala-library-sjs` / Test / compile).value, - Test / doc := (`scala-library-sjs` / Test / doc).value, - Test / run := (`scala-library-sjs` / Test / run).evaluated, + emptyJarPackageSettings // Packaging configuration of the stdlib Compile / publishArtifact := true, Test / publishArtifact := false, @@ -2833,6 +2812,7 @@ object Build { assert(!tastyIsExperimental, "Stable version cannot use experimental TASTY") } } + /** Helper to validate JAR contents */ private def validateJarIsEmpty(jar: File): File = { val jarFile = new java.util.jar.JarFile(jar) @@ -2991,4 +2971,5 @@ object ScaladocConfigs { .withTargets((`scala-library-bootstrapped` / Compile / products).value.map(_.getAbsolutePath)) } + } From afb1877f39bbeff5f07a56c17b8f3205ee01e0b0 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Mon, 22 Dec 2025 14:00:09 +0100 Subject: [PATCH 3/6] Rename emptyJarPackageSettings -> emptyPublishedJarSettings --- project/Build.scala | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 252c1fd2cc32..0ce39a41c19c 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1269,7 +1269,7 @@ object Build { // Configure to use the non-bootstrapped compiler bootstrappedScalaInstanceSettings, // This Project only has a dependency to `org.scala-lang:scala-library:*.**.**-bootstrapped` - emptyJarPackageSettings, + emptyPublishedJarSettings, // Validate JAR is empty (only META-INF) // Packaging configuration of the stdlib Compile / publishArtifact := true, Test / publishArtifact := false, @@ -1382,7 +1382,6 @@ object Build { lazy val `scala3-library-sjs` = project.in(file("library-js")) .dependsOn(`scala-library-sjs`) .settings(publishSettings) - .settings(emptyJarPackageSettings) // Validate JAR is empty (only META-INF) .settings( name := "scala3-library-sjs", moduleName := "scala3-library_sjs1", @@ -1397,7 +1396,7 @@ object Build { // Configure to use the non-bootstrapped compiler bootstrappedScalaInstanceSettings, // This Project only has a dependency to `org.scala-js:scalajs-scalalib:*.**.**-bootstrapped` - emptyJarPackageSettings + emptyPublishedJarSettings // Validate JAR is empty (only META-INF) // Packaging configuration of the stdlib Compile / publishArtifact := true, Test / publishArtifact := false, @@ -2841,7 +2840,7 @@ object Build { * These are dependency placeholder projects like scala3-library-bootstrapped-new and scala3-library-sjs. * Validates: .jar, -sources.jar, and -javadoc.jar */ - lazy val emptyJarPackageSettings = Def.settings( + lazy val emptyPublishedJarSettings = Def.settings( Compile / sources := Seq(), Compile / resources := Seq(), Test / sources := Seq(), From c3f5ec60f54708a7748b915bf33e4cb269a85f63 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Mon, 22 Dec 2025 14:04:28 +0100 Subject: [PATCH 4/6] Don't delegate also in scala3-library-nonbootstrapped --- project/Build.scala | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 0ce39a41c19c..9d6913638a92 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1184,19 +1184,9 @@ object Build { autoScalaLibrary := false, // Drop all the scala tools in this project, so we can never generate any bytecode, or documentation managedScalaInstance := false, + fetchedScalaInstanceSettings(scalaVersion), // This Project only has a dependency to `org.scala-lang:scala-library:*.**.**-nonbootstrapped` - Compile / sources := Seq(), - Compile / resources := Seq(), - Test / sources := Seq(), - Test / resources := Seq(), - // Bridge the common task to call the ones of the actual library project - Compile / compile := (`scala-library-nonbootstrapped` / Compile / compile).value, - Compile / doc := (`scala-library-nonbootstrapped` / Compile / doc).value, - Compile / run := (`scala-library-nonbootstrapped` / Compile / run).evaluated, - Test / compile := (`scala-library-nonbootstrapped` / Test / compile).value, - Test / doc := (`scala-library-nonbootstrapped` / Test / doc).value, - Test / run := (`scala-library-nonbootstrapped` / Test / run).evaluated, - Test / test := (`scala-library-nonbootstrapped` / Test / test).value, + emptyPublishedJarSettings, // Validate JAR is empty (only META-INF) // Packaging configuration of the stdlib Compile / publishArtifact := true, Test / publishArtifact := false, @@ -1396,7 +1386,7 @@ object Build { // Configure to use the non-bootstrapped compiler bootstrappedScalaInstanceSettings, // This Project only has a dependency to `org.scala-js:scalajs-scalalib:*.**.**-bootstrapped` - emptyPublishedJarSettings // Validate JAR is empty (only META-INF) + emptyPublishedJarSettings, // Validate JAR is empty (only META-INF) // Packaging configuration of the stdlib Compile / publishArtifact := true, Test / publishArtifact := false, @@ -1544,7 +1534,7 @@ object Build { // sbt adds all the projects to scala-tool config which breaks building the scalaInstance // as a workaround, I build it manually by only adding the compiler managedScalaInstance := false, - fetchedScalaInstanceSettings(Def.setting(referenceVersion)), + fetchedScalaInstanceSettings(scalaVersion), scalaCompilerBridgeBinaryJar := { val lm = dependencyResolution.value val log = streams.value.log From eb5219f2a0499e9888a09b6d7bd1c704d276c085 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Mon, 22 Dec 2025 14:06:51 +0100 Subject: [PATCH 5/6] Simplifiy fechedScalaInstanceSettings - always use scalaVersion.value --- project/Build.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 9d6913638a92..6b522f24c386 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -455,13 +455,13 @@ object Build { // Creates a scalaInstance by fetching the compiler from Maven. // Used by non-bootstrapped projects that need a published compiler. - def fetchedScalaInstanceSettings(version: Def.Initialize[String]) = Def.settings( + def fetchedScalaInstanceSettings = Def.settings( // sbt adds all the projects to scala-tool config which breaks building the scalaInstance // as a workaround, we build it manually by only adding the compiler scalaInstance := { val lm = dependencyResolution.value val log = streams.value.log - val ver = version.value + val ver = scalaVersion.value val retrieveDir = streams.value.cacheDirectory / "scala3-compiler" / ver val comp = lm.retrieve("org.scala-lang" % "scala3-compiler_3" % ver, scalaModuleInfo = None, retrieveDir, log) @@ -827,7 +827,7 @@ object Build { publish / skip := false, // Project specific target folder. sbt doesn't like having two projects using the same target folder target := target.value / "scala3-sbt-bridge-nonbootstrapped", - fetchedScalaInstanceSettings(scalaVersion), + fetchedScalaInstanceSettings, ) // ============================================================================================== @@ -1184,7 +1184,7 @@ object Build { autoScalaLibrary := false, // Drop all the scala tools in this project, so we can never generate any bytecode, or documentation managedScalaInstance := false, - fetchedScalaInstanceSettings(scalaVersion), + fetchedScalaInstanceSettings, // This Project only has a dependency to `org.scala-lang:scala-library:*.**.**-nonbootstrapped` emptyPublishedJarSettings, // Validate JAR is empty (only META-INF) // Packaging configuration of the stdlib @@ -1431,7 +1431,7 @@ object Build { publish / skip := false, // Project specific target folder. sbt doesn't like having two projects using the same target folder target := target.value / "tasty-core-nonbootstrapped", - fetchedScalaInstanceSettings(scalaVersion), + fetchedScalaInstanceSettings, // Add configuration of the test Test / envVars ++= Map( "EXPECTED_TASTY_VERSION" -> expectedTastyVersion, @@ -1534,7 +1534,7 @@ object Build { // sbt adds all the projects to scala-tool config which breaks building the scalaInstance // as a workaround, I build it manually by only adding the compiler managedScalaInstance := false, - fetchedScalaInstanceSettings(scalaVersion), + fetchedScalaInstanceSettings, scalaCompilerBridgeBinaryJar := { val lm = dependencyResolution.value val log = streams.value.log From 00be870ac19357974a46d3557fef9aa4749c7d53 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Mon, 22 Dec 2025 14:07:24 +0100 Subject: [PATCH 6/6] Ensure that `scala3-compiler-nonboostraped` scala3-sbt-bridge version matches the used scalaVersion --- project/Build.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 6b522f24c386..4bb241148d96 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1538,9 +1538,10 @@ object Build { scalaCompilerBridgeBinaryJar := { val lm = dependencyResolution.value val log = streams.value.log - val retrieveDir = streams.value.cacheDirectory / "scala3-sbt-bridge" / referenceVersion + val version = scalaVersion.value + val retrieveDir = streams.value.cacheDirectory / "scala3-sbt-bridge" / version val comp = lm.retrieve("org.scala-lang" % "scala3-sbt-bridge" % - referenceVersion, scalaModuleInfo = None, retrieveDir, log) + version, scalaModuleInfo = None, retrieveDir, log) .fold(w => throw w.resolveException, identity) Some(comp(0)) },