Skip to content

Commit 746ebc8

Browse files
authored
chore: port Scala.js to the new build (#23741)
Port the implementation of Scala.js scalalib to the new build NOTE: We still need to overwrite the `.sjsir` of specialized classes (as we do when we overwrite `.class`files)
2 parents 88961de + c067763 commit 746ebc8

File tree

7 files changed

+228
-4
lines changed

7 files changed

+228
-4
lines changed

.github/workflows/stdlib.yaml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,49 @@ jobs:
219219
- name: Compile `scala3-tasty-inspector`
220220
run: ./project/scripts/sbt scala3-tasty-inspector-new/compile
221221

222+
scala-library-sjs:
223+
runs-on: ubuntu-latest
224+
## Add when we add support for caching here
225+
##needs: [scala3-library-nonbootstrapped,
226+
## tasty-core-nonbootstrapped,
227+
##  scala3-compiler-nonbootstrapped,
228+
##  scala3-sbt-bridge-nonbootstrapped]
229+
steps:
230+
- name: Git Checkout
231+
uses: actions/checkout@v5
232+
233+
- name: Set up JDK 17
234+
uses: actions/setup-java@v4
235+
with:
236+
distribution: 'temurin'
237+
java-version: 17
238+
cache: 'sbt'
239+
- uses: sbt/setup-sbt@v1
240+
- name: Compile `scala3-staging`
241+
run: ./project/scripts/sbt scala3-staging-new/compile
242+
- name: Compile `scala-library` for Scala.js
243+
run: ./project/scripts/sbt scala-library-sjs/compile
244+
245+
scala3-library-sjs:
246+
runs-on: ubuntu-latest
247+
## Add when we add support for caching here
248+
##needs: [scala-library-sjs]
249+
steps:
250+
- name: Git Checkout
251+
uses: actions/checkout@v5
252+
253+
- name: Set up JDK 17
254+
uses: actions/setup-java@v4
255+
with:
256+
distribution: 'temurin'
257+
java-version: 17
258+
cache: 'sbt'
259+
- uses: sbt/setup-sbt@v1
260+
- name: Compile `scala3-staging`
261+
run: ./project/scripts/sbt scala3-staging-new/compile
262+
- name: Compile `scala3-library` for Scala.js
263+
run: ./project/scripts/sbt scala3-library-sjs/compile
264+
222265
#################################################################################################
223266
########################################### TEST JOBS ###########################################
224267
#################################################################################################

build.sbt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ val `scala3-compiler` = Build.`scala3-compiler`
77
val `scala3-compiler-nonbootstrapped` = Build.`scala3-compiler-nonbootstrapped`
88
val `scala3-compiler-bootstrapped-new` = Build.`scala3-compiler-bootstrapped-new`
99
val `scala3-compiler-bootstrapped` = Build.`scala3-compiler-bootstrapped`
10+
val `scala-library-sjs` = Build.`scala-library-sjs`
11+
val `scala3-library-sjs` = Build.`scala3-library-sjs`
1012
val `scala-library-nonbootstrapped` = Build.`scala-library-nonbootstrapped`
1113
val `scala3-library-nonbootstrapped` = Build.`scala3-library-nonbootstrapped`
1214
val `scala-library-bootstrapped` = Build.`scala-library-bootstrapped`

library-js/src/scala/Array.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ object Array {
6868
/**
6969
* Returns a new [[scala.collection.mutable.ArrayBuilder]].
7070
*/
71-
def newBuilder[T](implicit t: ClassTag[T]): ArrayBuilder[T] = ArrayBuilder.make[T](t)
71+
def newBuilder[T](implicit t: ClassTag[T]): ArrayBuilder[T] = ArrayBuilder.make[T](using t)
7272

7373
def from[A : ClassTag](it: IterableOnce[A]): Array[A] = {
7474
val n = it.knownSize

library-js/src/scala/Console.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,5 +280,5 @@ object Console extends AnsiColor {
280280
* @throws java.lang.IllegalArgumentException if there was a problem with the format string or arguments
281281
* @group console-output
282282
*/
283-
def printf(text: String, args: Any*): Unit = { out.print(text format (args : _*)) }
283+
def printf(text: String, args: Any*): Unit = { out.print(text.format(args*)) }
284284
}

library/src/scala/collection/StringOps.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -966,7 +966,7 @@ final class StringOps(private val s: String) extends AnyVal {
966966
else new WrappedString(s).toArray[B]
967967

968968
private[this] def unwrapArg(arg: Any): AnyRef = arg match {
969-
case x: ScalaNumber => x.underlying
969+
case x: ScalaNumber => x.underlying()
970970
case x => x.asInstanceOf[AnyRef]
971971
}
972972

project/Build.scala

Lines changed: 151 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ import sbttastymima.TastyMiMaPlugin.autoImport._
4040

4141
import scala.util.Properties.isJavaAtLeast
4242

43+
import scala.xml.{Node => XmlNode, NodeSeq => XmlNodeSeq, _}
44+
import scala.xml.transform.{RewriteRule, RuleTransformer}
45+
4346
import org.portablescala.sbtplatformdeps.PlatformDepsPlugin.autoImport._
4447

4548
object Build {
@@ -1451,7 +1454,7 @@ object Build {
14511454
lazy val `scala3-bootstrapped-new` = project
14521455
.aggregate(`scala3-interfaces`, `scala3-library-bootstrapped-new` , `scala-library-bootstrapped`,
14531456
`tasty-core-bootstrapped-new`, `scala3-compiler-bootstrapped-new`, `scala3-sbt-bridge-bootstrapped`,
1454-
`scala3-staging-new`, `scala3-tasty-inspector-new`)
1457+
`scala3-staging-new`, `scala3-tasty-inspector-new`, `scala-library-sjs`, `scala3-library-sjs`)
14551458
.settings(
14561459
name := "scala3-bootstrapped",
14571460
moduleName := "scala3-bootstrapped",
@@ -1833,6 +1836,153 @@ object Build {
18331836
target := target.value / "scala3-library-bootstrapped",
18341837
)
18351838

1839+
/* Configuration of the org.scala-js:scalajs-scalalib_2.13:*.**.**-bootstrapped project */
1840+
lazy val `scala-library-sjs` = project.in(file("library-js"))
1841+
// We add a dependency to the JVM library to have the classfile available
1842+
// (as they are not part of this artifact)
1843+
.dependsOn(`scala3-library-bootstrapped-new`)
1844+
.settings(
1845+
name := "scala-library-sjs",
1846+
organization := "org.scala-js",
1847+
// This is very tricky here since this is a Scala 3 project, but to be able to smoothly
1848+
// migrate the ecosystem, we need to be able to evict the Scala 2 library from the classpath.
1849+
// The problem is that the Scala 2 library for Scala.js has a _2.13 in the module's name, so we need
1850+
// to release Scala 3 for Scala.js with the same _2.13 instead of the _3.
1851+
// Yes, I know, this is weird and feels wrong.
1852+
moduleName := "scalajs-scalalib_2.13",
1853+
version := dottyVersion,
1854+
versionScheme := Some("semver-spec"),
1855+
crossPaths := false,
1856+
// sbt defaults to scala 2.12.x and metals will report issues as it doesn't consider the project a scala 3 project
1857+
// (not the actual version we use to compile the project)
1858+
scalaVersion := referenceVersion,
1859+
// Add the source directories for the stdlib (non-boostrapped)
1860+
Compile / unmanagedSourceDirectories := Seq(baseDirectory.value / "src"),
1861+
Compile / unmanagedSourceDirectories ++=
1862+
(`scala-library-bootstrapped` / Compile / unmanagedSourceDirectories).value,
1863+
// NOTE: The only difference here is that we drop `-Werror` and semanticDB for now
1864+
Compile / scalacOptions := Seq("-deprecation", "-feature", "-unchecked", "-encoding", "UTF8", "-language:implicitConversions", "-nowarn"),
1865+
Compile / scalacOptions += "-Yno-stdlib-patches",
1866+
Compile / scalacOptions += "-scalajs",
1867+
// Packaging configuration of the stdlib
1868+
Compile / packageBin / publishArtifact := true,
1869+
Compile / packageDoc / publishArtifact := false,
1870+
Compile / packageSrc / publishArtifact := true,
1871+
// Only publish compilation artifacts, no test artifacts
1872+
Test / publishArtifact := false,
1873+
// Do not allow to publish this project for now
1874+
publish / skip := false,
1875+
// Take into account the source files from the `library` folder
1876+
// but give the priority to the files in `library-js` that override files in `library`
1877+
Compile / sources := {
1878+
val files = (Compile / sources).value
1879+
val overwrittenSources =
1880+
(files ++ Seq(
1881+
baseDirectory.value / "src" / "scala" / "runtime" / "BoxesRunTime.java",
1882+
baseDirectory.value / "src" / "scala" / "math" / "ScalaNumber.java",
1883+
))
1884+
.flatMap(_.relativeTo(baseDirectory.value / "src")).toSet
1885+
1886+
files.filterNot(file =>
1887+
file.relativeTo((`scala-library-bootstrapped` / baseDirectory).value / "src")
1888+
.exists(overwrittenSources.contains))
1889+
1890+
},
1891+
// Drop all the tasty files and the classfiles when packaging bu the scalajs exclusive classes
1892+
// More info here: https://github.com/scala-js/scala-js/issues/5217
1893+
Compile / packageBin / mappings := {
1894+
(Compile / packageBin / mappings).value.filter(file =>
1895+
file._2.endsWith(".sjsir")
1896+
|| file._2.endsWith("UnitOps.tasty") || file._2.endsWith("UnitOps.class") || file._2.endsWith("UnitOps$.class")
1897+
|| file._2.endsWith("AnonFunctionXXL.tasty") || file._2.endsWith("AnonFunctionXXL.class"))
1898+
},
1899+
libraryDependencies += ("org.scala-js" %% "scalajs-library" % scalaJSVersion % Provided).cross(CrossVersion.for3Use2_13),
1900+
libraryDependencies += ("org.scala-js" % "scalajs-javalib" % scalaJSVersion),
1901+
// Project specific target folder. sbt doesn't like having two projects using the same target folder
1902+
target := target.value / "scala-library",
1903+
// we need to have the `scala-library` artifact in the classpath for `ScalaLibraryPlugin` to work
1904+
// this was the only way to not get the artifact evicted by sbt. Even a custom configuration didn't work
1905+
// NOTE: true is the default value, just making things clearer here
1906+
managedScalaInstance := true,
1907+
autoScalaLibrary := false,
1908+
// Configure the nonbootstrapped compiler
1909+
scalaInstance := {
1910+
val externalCompilerDeps = (`scala3-compiler-nonbootstrapped` / Compile / externalDependencyClasspath).value.map(_.data).toSet
1911+
1912+
// IMPORTANT: We need to use actual jars to form the ScalaInstance and not
1913+
// just directories containing classfiles because sbt maintains a cache of
1914+
// compiler instances. This cache is invalidated based on timestamps
1915+
// however this is only implemented on jars, directories are never
1916+
// invalidated.
1917+
val tastyCore = (`tasty-core-nonbootstrapped` / Compile / packageBin).value
1918+
val scalaLibrary = (`scala-library-nonbootstrapped` / Compile / packageBin).value
1919+
val scala3Interfaces = (`scala3-interfaces` / Compile / packageBin).value
1920+
val scala3Compiler = (`scala3-compiler-nonbootstrapped` / Compile / packageBin).value
1921+
1922+
Defaults.makeScalaInstance(
1923+
dottyNonBootstrappedVersion,
1924+
libraryJars = Array(scalaLibrary),
1925+
allCompilerJars = Seq(tastyCore, scala3Interfaces, scala3Compiler) ++ externalCompilerDeps,
1926+
allDocJars = Seq.empty,
1927+
state.value,
1928+
scalaInstanceTopLoader.value
1929+
)
1930+
},
1931+
scalaCompilerBridgeBinaryJar := {
1932+
Some((`scala3-sbt-bridge-nonbootstrapped` / Compile / packageBin).value)
1933+
},
1934+
// See https://stackoverflow.com/a/51416386
1935+
pomPostProcess := { (node: XmlNode) =>
1936+
new RuleTransformer(new RewriteRule {
1937+
override def transform(node: XmlNode): XmlNodeSeq = node match {
1938+
case e: Elem if e.label == "dependency" && e.child.exists(child => child.label == "artifactId" && child.text == "scalajs-library_2.13") =>
1939+
XmlNodeSeq.Empty
1940+
case _ => node
1941+
}
1942+
}).transform(node).head
1943+
},
1944+
)
1945+
1946+
/* Configuration of the org.scala-lang:scala3-library_sjs1_3:*.**.**-bootstrapped project */
1947+
lazy val `scala3-library-sjs` = project.in(file("library-js"))
1948+
.dependsOn(`scala-library-sjs`)
1949+
.settings(
1950+
name := "scala3-library-sjs",
1951+
moduleName := "scala3-library_sjs1",
1952+
version := dottyVersion,
1953+
versionScheme := Some("semver-spec"),
1954+
// sbt defaults to scala 2.12.x and metals will report issues as it doesn't consider the project a scala 3 project
1955+
// (not the actual version we use to compile the project)
1956+
scalaVersion := referenceVersion,
1957+
crossPaths := true, // org.scala-lang:scala3-library_sjs1 has a crosspath
1958+
// Do not depend on the `org.scala-lang:scala3-library` automatically, we manually depend on `scala-library-bootstrapped`
1959+
autoScalaLibrary := false,
1960+
// Drop all the scala tools in this project, so we can never generate any bytecode, or documentation
1961+
managedScalaInstance := false,
1962+
// This Project only has a dependency to `org.scala-js:scalajs-scalalib:*.**.**-bootstrapped`
1963+
Compile / sources := Seq(),
1964+
Compile / resources := Seq(),
1965+
Test / sources := Seq(),
1966+
Test / resources := Seq(),
1967+
// Bridge the common task to call the ones of the actual library project
1968+
Compile / compile := (`scala-library-sjs` / Compile / compile).value,
1969+
Compile / doc := (`scala-library-sjs` / Compile / doc).value,
1970+
Compile / run := (`scala-library-sjs` / Compile / run).evaluated,
1971+
Test / compile := (`scala-library-sjs` / Test / compile).value,
1972+
Test / doc := (`scala-library-sjs` / Test / doc).value,
1973+
Test / run := (`scala-library-sjs` / Test / run).evaluated,
1974+
// Packaging configuration of the stdlib
1975+
Compile / packageBin / publishArtifact := true,
1976+
Compile / packageDoc / publishArtifact := false,
1977+
Compile / packageSrc / publishArtifact := true,
1978+
// Only publish compilation artifacts, no test artifacts
1979+
Test / publishArtifact := false,
1980+
// Do not allow to publish this project for now
1981+
publish / skip := false,
1982+
// Project specific target folder. sbt doesn't like having two projects using the same target folder
1983+
target := target.value / "scala3-library",
1984+
)
1985+
18361986
// ==============================================================================================
18371987
// ===================================== TASTY CORE LIBRARY =====================================
18381988
// ==============================================================================================

project/ScalaLibraryPlugin.scala

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@ object ScalaLibraryPlugin extends AutoPlugin {
1111

1212
override def trigger = noTrigger
1313

14+
private val scala2Version = "2.13.16"
15+
private val scalaJSVersion = "1.19.0"
16+
1417
val fetchScala2ClassFiles = taskKey[(Set[File], File)]("Fetch the files to use that were compiled with Scala 2")
18+
val fetchScala2SJSIR = taskKey[(Set[File], File)]("Fetch the .sjsir to use from Scala 2")
1519

1620
override def projectSettings = Seq (
1721
fetchScala2ClassFiles := {
@@ -37,6 +41,31 @@ object ScalaLibraryPlugin extends AutoPlugin {
3741
(target ** "*.class").get.toSet
3842
} (Set(scalaLibraryBinaryJar)), target)
3943

44+
},
45+
fetchScala2SJSIR := {
46+
val stream = streams.value
47+
val lm = dependencyResolution.value
48+
val log = stream.log
49+
val cache = stream.cacheDirectory
50+
val retrieveDir = cache / "scalajs-scalalib" / scalaVersion.value
51+
val comp = lm.retrieve("org.scala-js" % "scalajs-scalalib_2.13" % s"$scala2Version+$scalaJSVersion", scalaModuleInfo = None, retrieveDir, log)
52+
.fold(w => throw w.resolveException, identity)
53+
54+
println(comp(0))
55+
56+
val target = cache / "scala-library-sjsir"
57+
58+
59+
if (!target.exists()) {
60+
IO.createDirectory(target)
61+
}
62+
63+
(FileFunction.cached(cache / "fetch-scala-library-sjsir", FilesInfo.lastModified, FilesInfo.exists) { _ =>
64+
stream.log.info(s"Unpacking scalajs-scalalib binaries to persistent directory: ${target.getAbsolutePath}")
65+
IO.unzip(comp(0), target)
66+
(target ** "*.sjsir").get.toSet
67+
} (Set(comp(0))), target)
68+
4069
},
4170
(Compile / manipulateBytecode) := {
4271
val stream = streams.value

0 commit comments

Comments
 (0)