From 95346cf427ea4e78e177c299bbff61b4e0efa219 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 8 Nov 2023 12:17:49 +0100 Subject: [PATCH 1/2] Bump the version to 1.15.0-SNAPSHOT for the upcoming changes. --- ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala index 4d7495cf96..c97b15f22f 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala @@ -17,7 +17,7 @@ import java.util.concurrent.ConcurrentHashMap import scala.util.matching.Regex object ScalaJSVersions extends VersionChecks( - current = "1.14.1-SNAPSHOT", + current = "1.15.0-SNAPSHOT", binaryEmitted = "1.13" ) From b50df89ffcea28ed1d758295a6e12348a3a9568f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 8 Nov 2023 12:26:42 +0100 Subject: [PATCH 2/2] Split the scalalib .sjsir files in a separate artifact scalajs-scalalib. Previously, the `.sjsir` files of the scalalib (and libraryAux) were bundled inside scalajs-library.jar. With the plan to drop forward binary compatibility of the upstream scala-library.jar, this model will not work anymore. We now publish those `.sjsir` files in their own artifact `scalajs-scalalib.jar`. It is versioned *both* with the Scala version number and the Scala.js version number, in a way that will allow Ivy resolution to pick the right one. At the POM level, `scalajs-scalalib` depends on `scalajs-javalib`. `scalajs-library` depends on the other two. However, in terms of *actual* content dependencies, as is, `scalajs-scalalib` also depends on `scalajs-library`. If the former is present on a classpath but not (a recent enough version of) the latter, linking errors can appear. This should not be an issue because any real build that depends on `scalajs-scalalib` will also depend on `scalajs-library`. Moreover, if a more recent `scalajs-scalalib` is picked up by Ivy resolution that implicitly depends on a more recent `scalajs-library`, the library that introduces that dependency would also *explicitly* depend on the more recent `scalajs-library`, and so the latter would also be picked up by Ivy resolution. The sbt plugin explicitly adds a dependency on the `scalajs-scalalib` with a Scala/Scala.js version combination that matches `scalaVersion` and `scalaJSVersion`. This way, if it uses a Scala.js version that was built for Scala 2.13.12 but it itself uses Scala 2.13.15, it will get the back-published `scalajs-library` built for Scala 2.13.15. --- build.sbt | 3 +- project/Build.scala | 114 ++++++++++++------ .../sbtplugin/ScalaJSPluginInternal.scala | 8 ++ scripts/publish.sh | 6 +- 4 files changed, 92 insertions(+), 39 deletions(-) diff --git a/build.sbt b/build.sbt index e3374e3358..e9bdde6b6b 100644 --- a/build.sbt +++ b/build.sbt @@ -14,8 +14,9 @@ val sbtPlugin = Build.plugin val javalibintf = Build.javalibintf val javalibInternal = Build.javalibInternal val javalib = Build.javalib -val scalalib = Build.scalalib +val scalalibInternal = Build.scalalibInternal val libraryAux = Build.libraryAux +val scalalib = Build.scalalib val library = Build.library val testInterface = Build.testInterface val testBridge = Build.testBridge diff --git a/project/Build.scala b/project/Build.scala index 796bc8e4b7..8deb00803d 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -197,7 +197,7 @@ object MyScalaJSPlugin extends AutoPlugin { */ libraryDependencies ~= { libDeps => val blacklist = - Set("scalajs-compiler", "scalajs-library", "scalajs-test-bridge") + Set("scalajs-compiler", "scalajs-library", "scalajs-scalalib", "scalajs-test-bridge") libDeps.filterNot(dep => blacklist.contains(dep.name)) }, @@ -638,7 +638,7 @@ object Build { * - `"semver-spec"` for artifacts whose public API can only break in major releases (e.g., `library`) * * At the moment, we only set the version scheme for artifacts in the - * "library ecosystem", i.e., scalajs-javalib scalajs-library, + * "library ecosystem", i.e., scalajs-javalib, scalajs-scalalib, scalajs-library, * scalajs-test-interface, scalajs-junit-runtime and scalajs-test-bridge. * Artifacts of the "tools ecosystem" do not have a version scheme set, as * the jury is still out on what is the best way to specify them. @@ -749,7 +749,7 @@ object Build { } } - /** Depends on library and, by artificial transitivity, on the javalib. */ + /** Depends on library and, by artificial transitivity, on the javalib and scalalib. */ def dependsOnLibrary2_12: Project = { val library = LocalProject("library2_12") @@ -757,9 +757,9 @@ object Build { val project1 = project .dependsOn(library) - /* Because the javalib's exportsJar is false, but its actual products are - * only in its jar, we must manually add the jar on the internal - * classpath. + /* Because the javalib's and scalalib's exportsJar is false, but their + * actual products are only in their jar, we must manually add the jars + * on the internal classpath. * Once published, only jars are ever used, so this is fine. */ if (isGeneratingForIDE) { @@ -772,6 +772,12 @@ object Build { Test / internalDependencyClasspath += (javalib / Compile / packageBin).value, ) + .settings( + Compile / internalDependencyClasspath += + (scalalib.v2_12 / Compile / packageBin).value, + Test / internalDependencyClasspath += + (scalalib.v2_12 / Compile / packageBin).value, + ) } } @@ -818,21 +824,21 @@ object Build { } } - /** Depends on library and, by transitivity, on the javalib. */ + /** Depends on library and, by artificial transitivity, on the javalib and scalalib. */ def dependsOnLibrary: MultiScalaProject = { // Add a real dependency on the library val project1 = project .dependsOn(library) - /* Because the javalib's exportsJar is false, but its actual products are - * only in its jar, we must manually add the jar on the internal - * classpath. + /* Because the javalib's and scalalib's exportsJar is false, but their + * actual products are only in their jar, we must manually add the jars + * on the internal classpath. * Once published, only jars are ever used, so this is fine. */ if (isGeneratingForIDE) { project1 } else { - // Actually add classpath dependencies on the javalib jar + // Actually add classpath dependencies on the javalib and scalalib jars project1 .settings( Compile / internalDependencyClasspath += @@ -840,6 +846,14 @@ object Build { Test / internalDependencyClasspath += (javalib / Compile / packageBin).value, ) + .zippedSettings(scalalib) { scalalib => + Def.settings( + Compile / internalDependencyClasspath += + (scalalib / Compile / packageBin).value, + Test / internalDependencyClasspath += + (scalalib / Compile / packageBin).value, + ) + } } } @@ -919,7 +933,7 @@ object Build { linkerInterface, linkerInterfaceJS, linker, linkerJS, testAdapter, javalibintf, - javalibInternal, javalib, scalalib, libraryAux, library, + javalibInternal, javalib, scalalibInternal, libraryAux, scalalib, library, testInterface, jUnitRuntime, testBridge, jUnitPlugin, jUnitAsyncJS, jUnitAsyncJVM, jUnitTestOutputsJS, jUnitTestOutputsJVM, helloworld, reversi, testingExample, testSuite, testSuiteJVM, @@ -1358,12 +1372,14 @@ object Build { // JS libs publishLocal in javalib, + publishLocal in scalalib.v2_12, publishLocal in library.v2_12, publishLocal in testInterface.v2_12, publishLocal in testBridge.v2_12, publishLocal in jUnitRuntime.v2_12, publishLocal in irProjectJS.v2_12, + publishLocal in scalalib.v2_13, publishLocal in library.v2_13, publishLocal in testInterface.v2_13, publishLocal in testBridge.v2_13, @@ -1478,7 +1494,7 @@ object Build { * copied from `javalibInternal`. * * This the "public" version of the javalib, as depended on by the `library` - * and published on Maven. + * and `scalalib`, and published on Maven. */ lazy val javalib: Project = Project( id = "javalib", base = file("javalib-public") @@ -1506,8 +1522,13 @@ object Build { }, ) - lazy val scalalib: MultiScalaProject = MultiScalaProject( - id = "scalalib", base = file("scalalib") + /** The project that actually compiles the `scalalib`, but which is not + * exposed. + * + * Instead, its products are copied in `scalalib`. + */ + lazy val scalalibInternal: MultiScalaProject = MultiScalaProject( + id = "scalalibInternal", base = file("scalalib") ).enablePlugins( MyScalaJSPlugin ).settings( @@ -1523,7 +1544,7 @@ object Build { s"https://raw.githubusercontent.com/scala/scala/v${scalaVersion.value}/src/library/") option ++ prev }, - name := "Scala library for Scala.js", + name := "scalajs-scalalib-internal", publishArtifact in Compile := false, NoIDEExport.noIDEExportSettings, delambdafySetting, @@ -1669,12 +1690,54 @@ object Build { recompileAllOrNothingSettings, ).withScalaJSCompiler.dependsOnLibraryNoJar + /** An empty project, without source nor dependencies (other than the javalib), + * whose products are copied from `scalalibInternal` and `libraryAux`. + * + * This the "public" version of the scalalib, as depended on by the `library` + * and published on Maven. + */ + lazy val scalalib: MultiScalaProject = MultiScalaProject( + id = "scalalib", base = file("scalalib-public") + ).dependsOn( + javalib, + ).settings( + commonSettings, + name := "scalajs-scalalib", + publishSettings(Some(VersionScheme.BreakOnMajor)), + + /* The scalalib has a special version number that encodes both the Scala + * version and the Scala.js version. This allows us to back-publish for + * newer versions of Scala and older versions of Scala.js. The Scala + * version comes first so that Ivy resolution will choose 2.13.20+1.15.0 + * over 2.13.18+1.16.0. The former might not be as optimized as the + * latter, but at least it will contain all the binary API that might be + * required. + */ + version := scalaVersion.value + "+" + scalaJSVersion, + + exportJars := false, // very important, otherwise there's a cycle with the `library` + ).zippedSettings(Seq("scalalibInternal", "libraryAux"))(localProjects => + inConfig(Compile)(Seq( + // Use the .sjsir files from scalalibInternal and libraryAux (but not the .class files) + Compile / packageBin / mappings := { + val scalalibInternalMappings = (localProjects(0) / packageBin / mappings).value + val libraryAuxMappings = (localProjects(1) / packageBin / mappings).value + val allMappings = scalalibInternalMappings ++ libraryAuxMappings + allMappings.filter(_._2.endsWith(".sjsir")) + }, + )) + ) + lazy val library: MultiScalaProject = MultiScalaProject( id = "library", base = file("library") ).enablePlugins( MyScalaJSPlugin ).dependsOn( + // Project dependencies javalibintf % Provided, javalib, + ).dependsOn( + // MultiScalaProject dependencies + scalalib, ).settings( commonSettings, publishSettings(Some(VersionScheme.BreakOnMajor)), @@ -1727,25 +1790,6 @@ object Build { */ dependencyClasspath in doc ++= exportedProducts.value, )) - ).zippedSettings(Seq("scalalib", "libraryAux"))(localProjects => - inConfig(Compile)(Seq( - /* Add the .sjsir files from other lib projects - * (but not .class files) - */ - mappings in packageBin := { - val libraryMappings = (mappings in packageBin).value - - val filter = ("*.sjsir": NameFilter) - - val otherProducts = ( - (products in localProjects(0)).value ++ - (products in localProjects(1)).value) - val otherMappings = - otherProducts.flatMap(base => Path.selectSubpaths(base, filter)) - - libraryMappings ++ otherMappings - }, - )) ).withScalaJSCompiler // The Scala.js version of sbt-testing-interface diff --git a/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala b/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala index c45eabf03c..6f1d6f66a4 100644 --- a/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala +++ b/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala @@ -795,6 +795,8 @@ private[sbtplugin] object ScalaJSPluginInternal { scalaOrg %% "scala3-library_sjs1" % scalaV, /* scala3-library_sjs1 depends on some version of scalajs-library_2.13, * but we bump it to be at least scalaJSVersion. + * (It will also depend on some version of scalajs-scalalib_2.13, + * but we do not have to worry about that here.) */ "org.scala-js" % "scalajs-library_2.13" % scalaJSVersion, "org.scala-js" % "scalajs-test-bridge_2.13" % scalaJSVersion % "test" @@ -803,6 +805,12 @@ private[sbtplugin] object ScalaJSPluginInternal { prev ++ Seq( compilerPlugin("org.scala-js" % "scalajs-compiler" % scalaJSVersion cross CrossVersion.full), "org.scala-js" %% "scalajs-library" % scalaJSVersion, + /* scalajs-library depends on some version of scalajs-scalalib, + * but we want to make sure to bump it to be at least the one + * of our own `scalaVersion` (which would have back-published in + * the meantime). + */ + "org.scala-js" %% "scalajs-scalalib" % s"$scalaV+$scalaJSVersion", "org.scala-js" %% "scalajs-test-bridge" % scalaJSVersion % "test" ) } diff --git a/scripts/publish.sh b/scripts/publish.sh index 6a6ac44d00..1158326a4e 100755 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -10,7 +10,7 @@ fi SUFFIXES="2_12 2_13" JAVA_LIBS="javalibintf javalib" -COMPILER="compiler jUnitPlugin" +FULL_SCALA_LIBS="compiler jUnitPlugin scalalib" JS_LIBS="library irJS linkerInterfaceJS linkerJS testInterface testBridge jUnitRuntime" JVM_LIBS="ir linkerInterface linker testAdapter" SCALA_LIBS="$JS_LIBS $JVM_LIBS" @@ -22,10 +22,10 @@ for p in $JAVA_LIBS; do done $CMD $ARGS -# Publish compiler +# Publish artifacts built with the full Scala version for s in $SUFFIXES; do ARGS="" - for p in $COMPILER; do + for p in $FULL_SCALA_LIBS; do ARGS="$ARGS +$p$s/publishSigned" done $CMD $ARGS