From a76afe85961ca434afe9d9c2e77a2e01934aeb4f Mon Sep 17 00:00:00 2001 From: Alex Archambault Date: Tue, 10 Feb 2026 14:16:23 +0100 Subject: [PATCH 1/2] WIP Add Mill build --- .github/workflows/ci.yml | 133 +++++++ build.mill | 105 ++++++ community-build/package.mill | 26 ++ compiler/package.mill | 195 ++++++++++ compiler/test/dotty/Properties.scala | 38 +- .../tools/dotc/TastyBootstrapTests.scala | 2 +- .../dotty/tools/vulpix/ParallelTesting.scala | 2 +- .../tools/vulpix/RunnerOrchestration.scala | 2 +- .../tools/vulpix/TestConfiguration.scala | 6 +- interfaces/package.mill | 5 + language-server/package.mill | 68 ++++ library-2/package.mill | 52 +++ library-js/package.mill | 120 +++++++ library/package.mill | 62 ++++ mill | 333 ++++++++++++++++++ mill-build/build.mill | 23 ++ .../CompileClassesPathHelper.scala | 11 + mill-build/src/scala3build/BuildType.scala | 24 ++ mill-build/src/scala3build/Constants.scala | 5 + .../src/scala3build/CrossScala3Module.scala | 7 + .../scala3build/DocumentationWebsite.scala | 59 ++++ .../src/scala3build/LibraryFilesToCopy.scala | 57 +++ .../src/scala3build/Scala3JavaModule.scala | 47 +++ .../src/scala3build/Scala3JsModule.scala | 80 +++++ mill-build/src/scala3build/Scala3Module.scala | 75 ++++ mill-build/src/scala3build/Shading.scala | 79 +++++ .../scala3build/SharedLibrarySettings.scala | 64 ++++ .../SjsUnitConstantHolderGenerator.scala | 43 +++ .../scala3build/StripScala2Annotations.scala | 205 +++++++++++ .../scala3build/TestSuiteLinkerOptions.scala | 39 ++ mill-build/src/scala3build/VersionUtil.scala | 48 +++ mill-build/src/scala3build/Versions.scala | 168 +++++++++ mill.bat | 299 ++++++++++++++++ presentation-compiler-testcases/package.mill | 23 ++ presentation-compiler/package.mill | 132 +++++++ project/Build.scala | 88 ++--- project/Dependencies.scala | 6 +- project/ScalaLibraryPlugin.scala | 98 +++--- project/scripts/mill-repl-test.expect | 9 + repl/package.mill | 64 ++++ sandbox/package.mill | 27 ++ sbt-bridge/package.mill | 30 ++ .../tools/xsbt/CompilerBridgeDriver.java | 6 +- sbt-bridge/src/xsbt/CachedCompilerImpl.java | 70 +++- sbt-test/package.mill | 13 + .../runner/src/dotty/SbtScriptedTests.scala | 3 + scaladoc-js/package.mill | 50 +++ scaladoc-testcases/package.mill | 16 + scaladoc/package.mill | 165 +++++++++ sjs-compiler-tests/package.mill | 48 +++ staging/package.mill | 30 ++ tasty-inspector/package.mill | 14 + tasty/package.mill | 25 ++ test-helpers/package.mill | 18 + tests/package.mill | 3 + tests/run-tasty-inspector/i8364.scala | 2 +- .../stdlibExperimentalDefinitions.scala | 2 +- tests/sjs-junit/package.mill | 201 +++++++++++ 58 files changed, 3501 insertions(+), 124 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 build.mill create mode 100644 community-build/package.mill create mode 100644 compiler/package.mill create mode 100644 interfaces/package.mill create mode 100644 language-server/package.mill create mode 100644 library-2/package.mill create mode 100644 library-js/package.mill create mode 100644 library/package.mill create mode 100755 mill create mode 100644 mill-build/build.mill create mode 100644 mill-build/src/mill/scala3build/CompileClassesPathHelper.scala create mode 100644 mill-build/src/scala3build/BuildType.scala create mode 100644 mill-build/src/scala3build/Constants.scala create mode 100644 mill-build/src/scala3build/CrossScala3Module.scala create mode 100644 mill-build/src/scala3build/DocumentationWebsite.scala create mode 100644 mill-build/src/scala3build/LibraryFilesToCopy.scala create mode 100644 mill-build/src/scala3build/Scala3JavaModule.scala create mode 100644 mill-build/src/scala3build/Scala3JsModule.scala create mode 100644 mill-build/src/scala3build/Scala3Module.scala create mode 100644 mill-build/src/scala3build/Shading.scala create mode 100644 mill-build/src/scala3build/SharedLibrarySettings.scala create mode 100644 mill-build/src/scala3build/SjsUnitConstantHolderGenerator.scala create mode 100644 mill-build/src/scala3build/StripScala2Annotations.scala create mode 100644 mill-build/src/scala3build/TestSuiteLinkerOptions.scala create mode 100644 mill-build/src/scala3build/VersionUtil.scala create mode 100644 mill-build/src/scala3build/Versions.scala create mode 100644 mill.bat create mode 100644 presentation-compiler-testcases/package.mill create mode 100644 presentation-compiler/package.mill create mode 100755 project/scripts/mill-repl-test.expect create mode 100644 repl/package.mill create mode 100644 sandbox/package.mill create mode 100644 sbt-bridge/package.mill create mode 100644 sbt-test/package.mill create mode 100644 sbt-test/runner/src/dotty/SbtScriptedTests.scala create mode 100644 scaladoc-js/package.mill create mode 100644 scaladoc-testcases/package.mill create mode 100644 scaladoc/package.mill create mode 100644 sjs-compiler-tests/package.mill create mode 100644 staging/package.mill create mode 100644 tasty-inspector/package.mill create mode 100644 tasty/package.mill create mode 100644 test-helpers/package.mill create mode 100644 tests/package.mill create mode 100644 tests/sjs-junit/package.mill diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000000..1357742a2a9e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,133 @@ +name: CI +on: + push: + branches: + - mill-build + pull_request: + +jobs: + compile-all: + runs-on: ubuntu-latest + timeout-minutes: 120 + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + # submodules: true + - uses: coursier/setup-action@v2 + - name: No modules with empty sources + run: ./mill -i ci.noEmptySources --tasks __.allSourceFiles + - name: No orphan sources + run: ./mill -i ci.noOrphanSources --sourceTasks '__.{allSourceFiles,compileResources}' + - name: Compile everything + run: ./mill -i __.compile + - name: Pre-upload artifact + run: | + mv ~/.cache/coursier/v1 coursier-cache + mv ~/.cache/mill mill-cache + if test -d ~/.cache/mill/download; then + chmod +x ~/.cache/mill/download/* + fi + - name: Upload build output + uses: actions/upload-artifact@v4 + with: + name: out + path: out + if-no-files-found: error + retention-days: 3 + - name: Upload coursier cache + uses: actions/upload-artifact@v4 + with: + name: coursier-cache + path: coursier-cache + if-no-files-found: error + retention-days: 3 + - name: Upload Mill cache + uses: actions/upload-artifact@v4 + with: + name: mill-cache + path: mill-cache + if-no-files-found: error + retention-days: 3 + + repl-test: + runs-on: ubuntu-latest + needs: compile-all + timeout-minutes: 15 + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + - uses: actions/download-artifact@v4 + with: + name: out + path: out + - uses: actions/download-artifact@v4 + with: + name: coursier-cache + path: coursier-cache + - uses: actions/download-artifact@v4 + with: + name: mill-cache + path: mill-cache + - name: Post-download artifacts + run: | + mkdir -p ~/.cache/coursier + mv coursier-cache ~/.cache/coursier/v1 + mv mill-cache ~/.cache/mill + if test -d ~/.cache/mill/download; then + chmod +x ~/.cache/mill/download/* + fi + - uses: coursier/setup-action@v2 + - name: Install expect + run: | + sudo apt-get update + sudo apt-get install -y expect + expect -v + - name: Prepare for REPL test + run: ./mill -i 'repl[final].compile' + - name: Run REPL test + env: + TERM: dumb + run: project/scripts/mill-repl-test.expect + + tests: + runs-on: ubuntu-latest + needs: compile-all + name: ${{ matrix.BUILD_TYPE }} + strategy: + fail-fast: false + matrix: + BUILD_TYPE: ["bootstrapping", "final"] + TEST: ["test", "full-test"] + timeout-minutes: 120 + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + # submodules: true + - uses: actions/download-artifact@v4 + with: + name: out + path: out + - uses: actions/download-artifact@v4 + with: + name: coursier-cache + path: coursier-cache + - uses: actions/download-artifact@v4 + with: + name: mill-cache + path: mill-cache + - name: Post-download artifacts + run: | + mkdir -p ~/.cache/coursier + mv coursier-cache ~/.cache/coursier/v1 + mv mill-cache ~/.cache/mill + if test -d ~/.cache/mill/download; then + chmod +x ~/.cache/mill/download/* + fi + - uses: coursier/setup-action@v2 + - name: Test + run: ./mill -i '__[${{ matrix.BUILD_TYPE }}].${{ matrix.TEST }}' + env: + DOTTY_CI_RUN: true diff --git a/build.mill b/build.mill new file mode 100644 index 000000000000..9a8c9afc5839 --- /dev/null +++ b/build.mill @@ -0,0 +1,105 @@ +//| mill-version: 1.1.2 +//| bspScriptIgnore: +//| - ".plasmon/" +//| - "bench/" +//| - "bin/" +//| - "community-build/" +//| - "compiler/test/" +//| - "compiler/test-resources/" +//| - "language-server/" +//| - "project/" +//| - "repl/test/" +//| - "repl/test-resources/" +//| - "sandbox/" +//| - "sbt-test/" +//| - "scaladoc/" +//| - "scaladoc-js/" +//| - "sjs-compiler-tests/" +//| - "tests/" + +package build + +import mill.* +import mill.util.Tasks +import mill.api.BuildCtx +import mill.constants.OutFiles + +object ci extends Module { + + val allowEmptySources = Set( + "sjs-compiler-tests.bootstrapping", + "sjs-compiler-tests.final" + ) + + def noEmptySources(tasks: mill.util.Tasks[Seq[PathRef]]) = Task.Command[Unit] { + val values = tasks.value.zip(Task.sequence(tasks.value)()) + val invalid = values + .filter(_._2.isEmpty) + .filter { + case (m, _) => + val moduleName = m.toString.stripSuffix(".allSourceFiles") + !allowEmptySources.contains(moduleName) + } + + if (invalid.nonEmpty) { + System.err.println("Found modules with no sources:") + for ((m, _) <- invalid) + System.err.println(s" $m") + sys.error(s"Found modules with no sources: ${invalid.map(_._1).mkString(", ")}") + } + } + + def noOrphanSources(sourceTasks: Tasks[Seq[PathRef]]) = Task.Command[Unit] { + val allSources = Task.sequence(sourceTasks.value)() + .flatten + .map(_.path) + .filter(_.startsWith(BuildCtx.workspaceRoot)) + .flatMap { path => + if (os.isDir(path)) os.walk(path) + else Seq(path) + } + .map(_.subRelativeTo(BuildCtx.workspaceRoot)) + .toSet + + val ignore = Seq[os.SubPath]( + "bench", + "bench-micro", + "bench-run", + "bin", + "library-aux", // Unused directory? + // See comment in library-js/package.mill about these two + "library-js/src/scala/math/ScalaNumber.scala", + "library-js/src/scala/runtime/BoxesRunTime.scala", + "mill-build", + "project", + "sbt-test", + "tests" + ) + + val outDir = BuildCtx.workspaceRoot / OutFiles.OutFiles.out + val foundFiles = os.walk( + BuildCtx.workspaceRoot, + skip = p => + p.startsWith(outDir) || { + val subPath = p.subRelativeTo(BuildCtx.workspaceRoot) + (subPath.segments.length == 1 && subPath.segments.head.startsWith(".")) || + ignore.exists(subPath.startsWith) + } + ) + val foundSources = foundFiles + .filter(_.last.endsWith(".scala")) + .filter(os.isFile) + .map(_.subRelativeTo(BuildCtx.workspaceRoot)) + + pprint.err.log(foundSources.length) + + val orphanSources = foundSources.filter(!allSources.contains(_)) + + if (orphanSources.nonEmpty) { + System.err.println(" Found orphan sources:") + for (f <- orphanSources) + System.err.println(s" $f") + sys.error("Found orphan sources") + } + } +} diff --git a/community-build/package.mill b/community-build/package.mill new file mode 100644 index 000000000000..e7e8e16e7cea --- /dev/null +++ b/community-build/package.mill @@ -0,0 +1,26 @@ +package build.`community-build` + +import scala3build.* + +import mill.* +import mill.scalalib.* + +object `package` extends Scala3Module { + def moduleDeps = super.moduleDeps ++ Seq( + build.library(buildType) + ) + def buildType = BuildType.Bootstrapping + + def testSrc = Task.Source("test") + + object test extends Scala3Tests { + def testSources = Task { + Seq(testSrc()) + } + + def testArgsDefault = super.testArgsDefault() ++ Seq( + "--include-categories=dotty.communitybuild.TestCategory", + "--run-listener=dotty.communitybuild.FailureSummarizer" + ) + } +} diff --git a/compiler/package.mill b/compiler/package.mill new file mode 100644 index 000000000000..1a9d520791a0 --- /dev/null +++ b/compiler/package.mill @@ -0,0 +1,195 @@ +package build.compiler + +import scala3build.* + +import mill.* +import mill.api.BuildCtx +import mill.scalalib.* + +import java.io.File +import java.nio.charset.StandardCharsets +import java.text.SimpleDateFormat +import java.util.{Arrays, TimeZone} + +trait Compiler extends CrossScala3Module { outer => + def moduleDeps = super.moduleDeps ++ + Seq( + build.interfaces, + build.library(crossValue), + build.tasty(crossValue) + ) + def mainSources = Task.Source("src") + def nonBootstrappedSources = Task.Source("src-non-bootstrapped") + + def testSources = Task.Sources("test") + def testResources = Task.Sources("test-resources") + + def shadedSources = Task { + + val shadedDeps = Seq( + Shading.csDep("org.scala-js", "scalajs-ir_2.13", Versions.scalaJSVersion), + Shading.csDep("com.lihaoyi", "pprint_3", "0.9.3"), + Shading.csDep("com.lihaoyi", "fansi_3", "0.5.1"), + Shading.csDep("com.lihaoyi", "sourcecode_3", "0.4.4") + ) + + // Define patches as a map from search text to replacement text + val patches = Map( + "import scala" -> "import _root_.scala", + " scala.collection." -> " _root_.scala.collection.", + "def apply(c: Char): Trie[T]" -> "def apply(c: Char): Trie[T] | Null", + "var head: Iterator[T] = null" -> "var head: Iterator[T] | Null = null", + "if (head != null && head.hasNext) true" -> "if (head != null && head.nn.hasNext) true", + "head.next()" -> "head.nn.next()", + "abstract class Walker" -> "@scala.annotation.nowarn abstract class Walker", + "object TPrintLowPri" -> "@scala.annotation.nowarn object TPrintLowPri", + "x.toString match{" -> "scala.runtime.ScalaRunTime.stringOf(x) match{" + ) + + val sourceJars = build.interfaces.defaultResolver().classpath( + shadedDeps.map(_.withTransitive(false)), + sources = true + ) + for (jar <- sourceJars) + os.unzip(jar.path, Task.dest) + + val patchUsageCounter = scala.collection.mutable.Map(patches.keys.map(_ -> 0).toSeq*) + for (f <- os.walk(Task.dest) if os.isFile(f) && f.last.endsWith(".scala")) { + val lines = os.read.lines(f).toList + val (isScalaJsStuff, updatedLines) = Shading.replacePackage(Shading.insertUnsafeNullsImport(lines)) { + case "org.scalajs.ir" => "dotty.tools.sjs.ir" + } + var text = updatedLines.mkString(System.lineSeparator()) + if (!isScalaJsStuff && f.last != "CollectionName.scala") { + text = "package dotty.shaded" + System.lineSeparator() + text + + // Apply patches and count usage + for ((search, replacement) <- patches if text.contains(search)) { + text = text.replace(search, replacement) + patchUsageCounter(search) += 1 + } + } + os.write.over(f, text) + } + + // Assert that all patches were applied at least once + val unappliedPatches = patchUsageCounter.filter(_._2 == 0).keys + if (unappliedPatches.nonEmpty) + sys.error(s"Patches were not applied: ${unappliedPatches.mkString(", ")}") + + PathRef(Task.dest) + } + + def sources = Task { + val baseSources = super.sources() + assert(baseSources.exists(_.path == mainSources().path)) + baseSources ++ Seq(shadedSources()) + } + def resources = Task { + super.resources() ++ Seq(compilerPropertiesResources()) + } + + def compilerPropertiesResources = Task(persistent = true) { + val file = Task.dest / "compiler.properties" + val dateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss") + dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")) + val contents = //2.11.11.v20170413-090219-8a413ba7cc + s"""version.number=${build.interfaces.publishVersion()} + |maven.version.number=${build.interfaces.publishVersion()} + |git.hash=${VersionUtil.gitHash} + |copyright.string=Copyright 2002-${VersionUtil.currentYear}, LAMP/EPFL + """.stripMargin.getBytes(StandardCharsets.UTF_8) + + if (!os.isFile(file) || !Arrays.equals(os.read.bytes(file), contents)) + os.write.over(file, contents) + + for (f <- os.list(Task.dest) if f != file) + os.remove.all(f) + + PathRef(Task.dest) + } + + def mvnDeps = super.mvnDeps() ++ Seq( + mvn"org.scala-lang.modules:scala-asm:9.9.0-scala-1", // used by the backend + mvn"org.scala-sbt:compiler-interface:1.10.7", + mvn"org.jline:jline-reader:3.29.0", // used by the REPL + mvn"org.jline:jline-terminal:3.29.0", + mvn"org.jline:jline-terminal-jni:3.29.0" // needed for Windows + ) + + def scalacOptions = Task { + super.scalacOptions() + // Original comment: TODO: Enable fatal warnings after 3.8 because old stdlib has different nullability. + .filter(_ != "-Werror") ++ + Seq( + // Original comment: Note: bench/profiles/projects.yml should be updated accordingly. + "-Yexplicit-nulls", + "-Wsafe-init" + ) + } + + def manifest = super.manifest().add( + "Git-Hash" -> VersionUtil.gitHash + ) + + def mainClass = Some("dotty.tools.dotc.Main") + + object test extends Scala3Tests { + def moduleDeps = super.moduleDeps ++ Seq( + build.`test-helpers`(crossValue) + ) + def testSources = outer.testSources + def compileResources = Task { + super.compileResources() ++ outer.testResources() + } + def mvnDeps = super.mvnDeps() ++ Seq( + mvn"io.get-coursier:coursier_2.13:2.0.16".exclude(("org.scala-lang", "scala-library")) + ) + + def testArgsDefault = super.testArgsDefault() ++ Seq( + "--run-listener=dotty.tools.ContextEscapeDetector", + "--exclude-categories=dotty.VulpixMetaTests" + ) + def testSandboxWorkingDir = false + + def compilerTestForkArgs = Task { + val compilerClassPath = outer.compileClasspath().map(_.path) + def artifactFor(moduleName: String): os.Path = + compilerClassPath + .iterator + .filter(os.isFile) + .filter(_.last.endsWith(".jar")) + .find(_.last.startsWith(moduleName + "-")) + .getOrElse { + sys.error(s"No JAR found for $moduleName in compiler class path $compilerClassPath") + } + + Seq( + s"-Ddotty.tests.dottyCompilerManagedSources=${mainSources().path}", + s"-Ddotty.tests.classes.dottyInterfaces=${build.interfaces.jar().path}", + s"-Ddotty.tests.classes.dottyCompiler=${outer.jar().path}", + s"-Ddotty.tests.classes.tastyCore=${build.tasty(crossValue).jar().path}", + s"-Ddotty.tests.classes.compilerInterface=${artifactFor("compiler-interface")}", + s"-Ddotty.tests.classes.scalaLibrary=${build.library(crossValue).runClasspath().map(_.path).filter(os.exists).mkString(File.pathSeparator)}", + s"-Ddotty.tests.classes.scalaAsm=${artifactFor("scala-asm")}", + s"-Ddotty.tests.classes.jlineTerminal=${artifactFor("jline-terminal")}", + s"-Ddotty.tests.classes.jlineReader=${artifactFor("jline-reader")}", + s"-Ddotty.tests.classes.dottyStaging=${build.staging(crossValue).jar().path}", + s"-Ddotty.tests.classes.dottyTastyInspector=${build.`tasty-inspector`(crossValue).jar().path}", + s"-Ddotty.tools.dotc.semanticdb.test=${BuildCtx.workspaceRoot / "tests/semanticdb"}" + ) + } + + def forkArgs: T[Seq[String]] = Task { + super.forkArgs() ++ compilerTestForkArgs() + } + + def runClasspath = Task { + // really necessary? + val (localCp, managedCp) = super.runClasspath().partition(_.path.startsWith(BuildCtx.workspaceRoot)) + localCp ++ managedCp + } + } +} + +object `package` extends Cross[Compiler](BuildType.allValues) diff --git a/compiler/test/dotty/Properties.scala b/compiler/test/dotty/Properties.scala index 6b529704b832..ea89267bf321 100644 --- a/compiler/test/dotty/Properties.scala +++ b/compiler/test/dotty/Properties.scala @@ -17,12 +17,18 @@ object Properties { private def propIsTrue(name: String): Boolean = sys.props.getOrElse(name, "FALSE") == "TRUE" + private def prop(name: String): String = + sys.props.getOrElse( + name, + sys.error(s"Java property $name not set") + ) + /** Are we running on the CI? */ val isRunByCI: Boolean = sys.env.isDefinedAt("DOTTY_CI_RUN") val testCache: Path = sys.env.get("DOTTY_TEST_CACHE").map(Paths.get(_)).getOrElse { - Paths.get(sys.props("user.home"), ".cache", "dotty", "test") + Paths.get(prop("user.home"), ".cache", "dotty", "test") } /** Tests should run interactive? */ @@ -52,31 +58,31 @@ object Properties { val testsSafeMode: Boolean = sys.props.isDefinedAt("dotty.tests.safemode") /** Extra directory containing sources for the compiler */ - def dottyCompilerManagedSources: Path = Paths.get(sys.props("dotty.tests.dottyCompilerManagedSources")) + def dottyCompilerManagedSources: Path = Paths.get(prop("dotty.tests.dottyCompilerManagedSources")) /** dotty-interfaces jar */ - def dottyInterfaces: String = sys.props("dotty.tests.classes.dottyInterfaces") + def dottyInterfaces: String = prop("dotty.tests.classes.dottyInterfaces") /** dotty-compiler jar */ - def dottyCompiler: String = sys.props("dotty.tests.classes.dottyCompiler") + def dottyCompiler: String = prop("dotty.tests.classes.dottyCompiler") /** dotty-repl jar */ - def dottyRepl: String = sys.props("dotty.tests.classes.dottyRepl") + def dottyRepl: String = prop("dotty.tests.classes.dottyRepl") /** dotty-staging jar */ - def dottyStaging: String = sys.props("dotty.tests.classes.dottyStaging") + def dottyStaging: String = prop("dotty.tests.classes.dottyStaging") /** dotty-tasty-inspector jar */ - def dottyTastyInspector: String = sys.props("dotty.tests.classes.dottyTastyInspector") + def dottyTastyInspector: String = prop("dotty.tests.classes.dottyTastyInspector") /** tasty-core jar */ - def tastyCore: String = sys.props("dotty.tests.classes.tastyCore") + def tastyCore: String = prop("dotty.tests.classes.tastyCore") /** compiler-interface jar */ - def compilerInterface: String = sys.props("dotty.tests.classes.compilerInterface") + def compilerInterface: String = prop("dotty.tests.classes.compilerInterface") /** scala-library jar */ - def scalaLibrary: String = sys.props("dotty.tests.classes.scalaLibrary") + def scalaLibraryClassPath: String = prop("dotty.tests.classes.scalaLibrary") // TODO: Remove this once we migrate the test suite def usingScalaLibraryCCTasty: Boolean = true @@ -85,20 +91,20 @@ object Properties { def usingScalaLibraryTasty: Boolean = true /** scala-asm jar */ - def scalaAsm: String = sys.props("dotty.tests.classes.scalaAsm") + def scalaAsm: String = prop("dotty.tests.classes.scalaAsm") /** jline-terminal jar */ - def jlineTerminal: String = sys.props("dotty.tests.classes.jlineTerminal") + def jlineTerminal: String = prop("dotty.tests.classes.jlineTerminal") /** jline-reader jar */ - def jlineReader: String = sys.props("dotty.tests.classes.jlineReader") + def jlineReader: String = prop("dotty.tests.classes.jlineReader") /** scalajs-javalib jar */ - def scalaJSJavalib: String = sys.props("dotty.tests.classes.scalaJSJavalib") + def scalaJSJavalib: String = prop("dotty.tests.classes.scalaJSJavalib") /** scalajs-scalalib jar */ - def scalaJSScalalib: String = sys.props("dotty.tests.classes.scalaJSScalalib") + def scalaJSScalalib: String = prop("dotty.tests.classes.scalaJSScalalib") /** scalajs-library jar */ - def scalaJSLibrary: String = sys.props("dotty.tests.classes.scalaJSLibrary") + def scalaJSLibrary: String = prop("dotty.tests.classes.scalaJSLibrary") } diff --git a/compiler/test/dotty/tools/dotc/TastyBootstrapTests.scala b/compiler/test/dotty/tools/dotc/TastyBootstrapTests.scala index 49a5c52310ae..7a65ace217c8 100644 --- a/compiler/test/dotty/tools/dotc/TastyBootstrapTests.scala +++ b/compiler/test/dotty/tools/dotc/TastyBootstrapTests.scala @@ -48,7 +48,7 @@ class TastyBootstrapTests { // as well as bootstrapped compiler: defaultOutputDir + dotty1Group + "/dotty1/", // and the other compiler dependencies: - Properties.compilerInterface, Properties.scalaLibrary, Properties.scalaAsm, + Properties.compilerInterface, Properties.scalaLibraryClassPath, Properties.scalaAsm, Properties.dottyInterfaces, Properties.jlineTerminal, Properties.jlineReader, ).mkString(File.pathSeparator), Array("-Ycheck-reentrant", "-language:postfixOps", "-Xsemanticdb") diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index 3a2f261a8e16..8dc0a5b04945 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -736,7 +736,7 @@ trait ParallelTesting extends RunnerOrchestration: assert(testSourcesCompleted == 0, "not allowed to re-use a `CompileRun`") if filteredSources.nonEmpty then val pool = JExecutors.newWorkStealingPool(threadLimit.getOrElse(Runtime.getRuntime.availableProcessors())) - val timer = new Timer() + lazy val timer = new Timer() val logProgress = isInteractive && !suppressAllOutput val start = System.currentTimeMillis() if logProgress then diff --git a/compiler/test/dotty/tools/vulpix/RunnerOrchestration.scala b/compiler/test/dotty/tools/vulpix/RunnerOrchestration.scala index df4d692175f9..a7e9d9d1706e 100644 --- a/compiler/test/dotty/tools/vulpix/RunnerOrchestration.scala +++ b/compiler/test/dotty/tools/vulpix/RunnerOrchestration.scala @@ -199,7 +199,7 @@ trait RunnerOrchestration { */ private def createProcess(): RunnerProcess = val url = classOf[ChildJVMMain].getProtectionDomain.getCodeSource.getLocation - val cp = Paths.get(url.toURI).toString + JFile.pathSeparator + Properties.scalaLibrary + val cp = Paths.get(url.toURI).toString + JFile.pathSeparator + Properties.scalaLibraryClassPath val javaBin = Paths.get(sys.props("java.home"), "bin", "java").toString val args = Seq("-Dfile.encoding=UTF-8", "-Duser.language=en", "-Duser.country=US", "-Xmx1g", "-cp", cp) ++ (if debugMode then Seq("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,quiet=n") else Seq.empty) diff --git a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala index 3463f88a5455..ce13bb789d1a 100644 --- a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala +++ b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala @@ -29,10 +29,10 @@ object TestConfiguration { "-Wconf:id=E222:s", // name=EncodedPackageName don't warn about file names with hyphens ) - val basicClasspath = mkClasspath(List(Properties.scalaLibrary)) + val basicClasspath = mkClasspath(List(Properties.scalaLibraryClassPath)) val withCompilerClasspath = mkClasspath(List( - Properties.scalaLibrary, + Properties.scalaLibraryClassPath, Properties.scalaAsm, Properties.compilerInterface, Properties.dottyInterfaces, @@ -63,7 +63,7 @@ object TestConfiguration { replClassPath + File.pathSeparator + mkClasspath(List(Properties.dottyStaging)) def mkClasspath(classpaths: List[String]): String = - classpaths.map({ p => + classpaths.flatMap(_.split(File.pathSeparator)).map({ p => val file = new java.io.File(p) assert(file.exists, s"File $p couldn't be found.") file.getAbsolutePath diff --git a/interfaces/package.mill b/interfaces/package.mill new file mode 100644 index 000000000000..0c93b924d019 --- /dev/null +++ b/interfaces/package.mill @@ -0,0 +1,5 @@ +package build.interfaces + +import scala3build.* + +object `package` extends Scala3JavaModule diff --git a/language-server/package.mill b/language-server/package.mill new file mode 100644 index 000000000000..0798a1c94cad --- /dev/null +++ b/language-server/package.mill @@ -0,0 +1,68 @@ +package build.`language-server` + +import scala3build.* + +import mill.* +import mill.scalalib.* + +trait LanguageServer extends CrossScala3Module { + def moduleDeps = super.moduleDeps ++ Seq( + build.repl(crossValue) + ) + def mvnDeps = super.mvnDeps() ++ Seq( + mvn"org.eclipse.lsp4j:org.eclipse.lsp4j:0.6.0" + // overridden version below + // Original comment: Work around https://github.com/eclipse/lsp4j/issues/295 + .exclude(("org.eclipse.xtend", "org.eclipse.xtend.lib")), + mvn"org.eclipse.xtend:org.eclipse.xtend.lib:2.16.0", + mvn"com.fasterxml.jackson.core:jackson-databind:${Versions.jacksonVersion}" + ) + def scalacOptions = super.scalacOptions().filter(_ != "-Yexplicit-nulls") + + def testSrc = Task.Source("test") + + object test extends Scala3Tests { + def moduleDeps = super.moduleDeps ++ Seq( + build.`test-helpers`(crossValue) + ) + def libraryPrecomputedRunClasspath: T[Seq[String]] = + build.library(crossValue).precomputedRunClasspath + def libraryRunClasspath: T[Seq[PathRef]] = + Task { + build.library(crossValue) + .runClasspath() + .filter(ref => os.exists(ref.path)) + } + def testSources = Task { + Seq(testSrc(), buildInfoDir()) + } + def buildInfoDir = Task { + val tq = "\"\"\"" + val content = + s"""package dotty.tools.languageserver.util.server + | + |object BuildInfo { + | def ideTestsCompilerVersion = "${publishVersion()}" + | def ideTestsCompilerArguments: Seq[String] = Nil + | def ideTestsDependencyClasspath = ${libraryPrecomputedRunClasspath().map(p => s"new java.io.File($tq$p$tq)").mkString("Seq[java.io.File](", ", ", ")")} + |} + |""".stripMargin + + os.write(Task.dest / "BuildInfo.scala", content) + + PathRef(Task.dest) + } + def runClasspath = Task { + val precomputedLibrary = libraryPrecomputedRunClasspath().map(os.Path(_)) + val actualLibrary = libraryRunClasspath().map(_.path) + if (precomputedLibrary.filter(os.exists) != actualLibrary.filter(os.exists)) + sys.error( + "Warning: standard library class path calculation mismatch: " + + s"pre-computed value $precomputedLibrary differs from actual value $actualLibrary." + ) + super.runClasspath() + } + } +} + +object `package` extends Cross[LanguageServer](BuildType.allValues) diff --git a/library-2/package.mill b/library-2/package.mill new file mode 100644 index 000000000000..8454096666b2 --- /dev/null +++ b/library-2/package.mill @@ -0,0 +1,52 @@ +package build.`library-2` + +import scala3build.* + +import mill.* +import mill.scalalib.* + +object `package` extends ScalaModule { + def scalaVersion = Versions.scala2Version + + def scalaLibraryMvnDeps = Task(Nil) + + def scalacOptions = Task { + val baseOptions = super.scalacOptions() + .filterNot(_ != "--java-output-version:17") + .filterNot(_ != "-Yexplicit-nulls") + .filterNot(_ != "-Wsafe-init") + baseOptions ++ Seq( + "-release:17", + s"-sourcepath:${scala2LibraryDir().path}", + "-opt:local", // Original sbt build comment: Important: local optimization are fine, inlining is prohibited! + ) + } + + def scala2LibraryDir: T[PathRef] = Task { + val sourceJars = defaultResolver().classpath( + Seq(mvn"org.scala-lang:scala-library:${Versions.scala2Version}"), + sources = true + ) + assert(sourceJars.length == 1, s"Expected a single source JAR for scala-library, got $sourceJars") + val sourceJar = sourceJars.head.path + os.unzip(sourceJar, Task.dest) + + // Original sbt build comment: Auxilary source files that cannot be compield + val excludedSourceFiles = Seq[os.SubPath]( + "scala/AnyRef.scala", + "scala/Any.scala", + "scala/Nothing.scala", + "scala/Null.scala", + "scala/Singleton.scala", + ) + + for (f <- excludedSourceFiles) + os.remove(Task.dest / f) + + PathRef(Task.dest) + } + + def sources = Task { + Seq(scala2LibraryDir()) + } +} diff --git a/library-js/package.mill b/library-js/package.mill new file mode 100644 index 000000000000..e5e6b15096c0 --- /dev/null +++ b/library-js/package.mill @@ -0,0 +1,120 @@ +package build.`library-js` + +import scala3build.* + +import mill.* +import mill.api.BuildCtx +import mill.scalalib.* +import mill.util.Jvm + +trait LibraryJs extends SharedLibrarySettings with Scala3JsModule { + def isJS = true + def moduleDeps = super.moduleDeps ++ Seq( + build.library(crossValue) + ) + def compileMvnDeps = super.compileMvnDeps() ++ Seq( + mvn"org.scala-js::scalajs-library:${Versions.scalaJSVersion}" + .withDottyCompat(scalaVersion()) + .exclude(("org.scala-lang", "scala-library")) + ) + def mvnDeps = super.mvnDeps() ++ Seq( + mvn"org.scala-js:scalajs-javalib:${Versions.scalaJSVersion}" + ) + def pomSettings = super.pomSettings().copy( + organization = "org.scala-js" + ) + def artifactName = "scalajs-scalalib_2.13" + + def sources = Task { + val baseSources = super.sources() + val expandedBaseSources = baseSources.flatMap { ref => + os.walk(ref.path) + .filter(os.isFile) + .filter(_.last.endsWith(".scala")) + } + val expandedRelativeBaseSourcesSet = baseSources + .flatMap { ref => + os.walk(ref.path) + .filter(os.isFile) + .filter(_.last.endsWith(".scala")) + .map(_.subRelativeTo(ref.path)) + } + .toSet + val excludedBaseSourcesSet = Set( + os.sub / "scala/runtime/BoxesRunTime.java", + os.sub / "scala/math/ScalaNumber.java" + ) + val librarySources = build.library(crossValue).sources().flatMap { ref => + if (os.isDir(ref.path)) + os.walk(ref.path) + .filter(os.isFile) + .filter { f => + val subPath = f.subRelativeTo(ref.path) + !expandedRelativeBaseSourcesSet.contains(subPath) && + !excludedBaseSourcesSet.contains(subPath) + } + .map(PathRef(_)) + else + Seq(ref) + } + + val filteredExpandedBaseSources = expandedBaseSources.filter { f => + // Excluding those feels like a typo in project/Build.scala, in the original sbt build + // but keeping BoxesRunTime.scala makes some sjs-compiler-tests fail + f.last != "BoxesRunTime.scala" && + f.last != "ScalaNumber.scala" + } + filteredExpandedBaseSources.map(PathRef(_)) ++ librarySources + } + def scalacOptions = Task { + val mapSourceOpt = + if (Versions.isRelease) { + val baseURI = BuildCtx.workspaceRoot.toNIO.toUri.toASCIIString + val dottyVersion = publishVersion() + Seq(s"-scalajs-mapSourceURI:$baseURI->${Constants.dottyGithubRawUserContentUrl}/$dottyVersion/") + } + else + Nil + super.scalacOptions() ++ mapSourceOpt + } + + def patches: T[Seq[PathRef]] = Task { + val cp = defaultResolver().classpath( + Seq( + mvn"org.scala-js:scalajs-scalalib_2.13:${Versions.stdlibBootstrappedVersion}+${Versions.scalaJSVersion}" + .exclude(("org.scala-js", "scalajs-javalib")) + ) + ) + + assert(cp.length == 2) + + cp + } + + def localClassPathForJar = Task { + val destDir = Task.dest + val localCp = localClasspath() + for (entry <- localCp if os.exists(entry.path); f <- os.walk(entry.path) if os.isFile(f)) { + val keep = f.last.endsWith(".sjsir") || + f.last == "UnitOps.tasty" || + f.last == "UnitOps.class" || + f.last == "UnitOps$.class" || + f.last == "AnonFunctionXXL.tasty" || + f.last == "AnonFunctionXXL.class" + if (keep) { + val subPath = f.subRelativeTo(entry.path) + val dest = destDir / subPath + if (!os.exists(dest)) + os.copy(f, dest, createFolders = true) + } + } + PathRef(destDir) + } + def jar: T[PathRef] = Task { + val jar = Task.dest / s"scalajs-scalalib_2.13-${publishVersion()}.jar" + Jvm.createJar(jar, Seq(localClassPathForJar().path), manifest()) + PathRef(jar) + } +} + +object `package` extends Cross[LibraryJs](BuildType.allValues) diff --git a/library/package.mill b/library/package.mill new file mode 100644 index 000000000000..61d39e6770e2 --- /dev/null +++ b/library/package.mill @@ -0,0 +1,62 @@ +package build.library + +import scala3build.* + +import mill.* + +import java.io.File + +trait Library extends SharedLibrarySettings with mill.scala3build.CompileClassesPathHelper { + def scalacOptions = super.scalacOptions() ++ Seq( + "-sourcepath", sources().map(_.path).distinct.mkString(File.pathSeparator) + ) + + def libraryPropertiesResourceDir = Task { + + val shellBanner: String = + """%n ________ ___ / / ___ + |%n / __/ __// _ | / / / _ | + |%n __\\ \\/ /__/ __ |/ /__/ __ | + |%n /____/\\___/_/ |_/____/_/ | | + |%n |/ %s""".stripMargin.replace("\n", "") + + val contents = + s"""version.number=${publishVersion()} + |maven.version.number=${publishVersion()} + |copyright.string=Copyright 2002-${VersionUtil.currentYear}, LAMP/EPFL + |shell.banner=$shellBanner + |""".stripMargin + os.write(Task.dest / "library.properties", contents) + PathRef(Task.dest) + } + + def resources = Task { + super.resources() ++ Seq(libraryPropertiesResourceDir()) + } + + def sources = Task.Sources("src") + + def patches: T[Seq[PathRef]] = Task { + Seq(build.`library-2`.compile().classes) + } + + private def jarSubPath = os.sub / s"scala-library-${Versions.dottyVersion}.jar" + def jar = Task { + val jar0 = super.jar() + val dest = Task.dest / jarSubPath + os.copy(jar0.path, dest) + PathRef(dest) + } + + def runClasspath = + Task { + Seq(jar()) + } + + def precomputedRunClasspath: T[Seq[String]] = + Task { + Seq((jarPath / jarSubPath).toString) + } +} + +object `package` extends Cross[Library](BuildType.allValues) diff --git a/mill b/mill new file mode 100755 index 000000000000..4a0cb640bd7c --- /dev/null +++ b/mill @@ -0,0 +1,333 @@ +#!/usr/bin/env sh + +# This is a wrapper script, that automatically selects or downloads Mill from Maven Central or GitHub release pages. +# +# This script determines the Mill version to use by trying these sources +# - env-variable `MILL_VERSION` +# - local file `.mill-version` +# - local file `.config/mill-version` +# - `mill-version` from YAML fronmatter of current buildfile +# - if accessible, find the latest stable version available on Maven Central (https://repo1.maven.org/maven2) +# - env-variable `DEFAULT_MILL_VERSION` +# +# If a version has the suffix '-native' a native binary will be used. +# If a version has the suffix '-jvm' an executable jar file will be used, requiring an already installed Java runtime. +# If no such suffix is found, the script will pick a default based on version and platform. +# +# Once a version was determined, it tries to use either +# - a system-installed mill, if found and it's version matches +# - an already downloaded version under ~/.cache/mill/download +# +# If no working mill version was found on the system, +# this script downloads a binary file from Maven Central or Github Pages (this is version dependent) +# into a cache location (~/.cache/mill/download). +# +# Mill Project URL: https://github.com/com-lihaoyi/mill +# Script Version: 1.0.0-M1-21-7b6fae-DIRTY892b63e8 +# +# If you want to improve this script, please also contribute your changes back! +# This script was generated from: dist/scripts/src/mill.sh +# +# Licensed under the Apache License, Version 2.0 + +set -e + +if [ "$1" = "--setup-completions" ] ; then + # Need to preserve the first position of those listed options + MILL_FIRST_ARG=$1 + shift +fi + +if [ -z "${DEFAULT_MILL_VERSION}" ] ; then + DEFAULT_MILL_VERSION="0.12.10" +fi + + +if [ -z "${GITHUB_RELEASE_CDN}" ] ; then + GITHUB_RELEASE_CDN="" +fi + + +MILL_REPO_URL="https://github.com/com-lihaoyi/mill" + +if [ -z "${CURL_CMD}" ] ; then + CURL_CMD=curl +fi + +# Explicit commandline argument takes precedence over all other methods +if [ "$1" = "--mill-version" ] ; then + echo "The --mill-version option is no longer supported." 1>&2 +fi + +MILL_BUILD_SCRIPT="" + +if [ -f "build.mill" ] ; then + MILL_BUILD_SCRIPT="build.mill" +elif [ -f "build.mill.scala" ] ; then + MILL_BUILD_SCRIPT="build.mill.scala" +elif [ -f "build.sc" ] ; then + MILL_BUILD_SCRIPT="build.sc" +fi + +# Please note, that if a MILL_VERSION is already set in the environment, +# We reuse it's value and skip searching for a value. + +# If not already set, read .mill-version file +if [ -z "${MILL_VERSION}" ] ; then + if [ -f ".mill-version" ] ; then + MILL_VERSION="$(tr '\r' '\n' < .mill-version | head -n 1 2> /dev/null)" + elif [ -f ".config/mill-version" ] ; then + MILL_VERSION="$(tr '\r' '\n' < .config/mill-version | head -n 1 2> /dev/null)" + elif [ -n "${MILL_BUILD_SCRIPT}" ] ; then + MILL_VERSION="$(cat ${MILL_BUILD_SCRIPT} | grep '//[|] *mill-version: *' | sed 's;//| *mill-version: *;;')" + fi +fi + +MILL_USER_CACHE_DIR="${XDG_CACHE_HOME:-${HOME}/.cache}/mill" + +if [ -z "${MILL_DOWNLOAD_PATH}" ] ; then + MILL_DOWNLOAD_PATH="${MILL_USER_CACHE_DIR}/download" +fi + +# If not already set, try to fetch newest from Github +if [ -z "${MILL_VERSION}" ] ; then + # TODO: try to load latest version from release page + echo "No mill version specified." 1>&2 + echo "You should provide a version via a '//| mill-version: ' comment or a '.mill-version' file." 1>&2 + + mkdir -p "${MILL_DOWNLOAD_PATH}" + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" 2>/dev/null || ( + # we might be on OSX or BSD which don't have -d option for touch + # but probably a -A [-][[hh]mm]SS + touch "${MILL_DOWNLOAD_PATH}/.expire_latest"; touch -A -010000 "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) || ( + # in case we still failed, we retry the first touch command with the intention + # to show the (previously suppressed) error message + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) + + # POSIX shell variant of bash's -nt operator, see https://unix.stackexchange.com/a/449744/6993 + # if [ "${MILL_DOWNLOAD_PATH}/.latest" -nt "${MILL_DOWNLOAD_PATH}/.expire_latest" ] ; then + if [ -n "$(find -L "${MILL_DOWNLOAD_PATH}/.latest" -prune -newer "${MILL_DOWNLOAD_PATH}/.expire_latest")" ]; then + # we know a current latest version + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # we don't know a current latest version + echo "Retrieving latest mill version ..." 1>&2 + LANG=C ${CURL_CMD} -s -i -f -I ${MILL_REPO_URL}/releases/latest 2> /dev/null | grep --ignore-case Location: | sed s'/^.*tag\///' | tr -d '\r\n' > "${MILL_DOWNLOAD_PATH}/.latest" + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # Last resort + MILL_VERSION="${DEFAULT_MILL_VERSION}" + echo "Falling back to hardcoded mill version ${MILL_VERSION}" 1>&2 + else + echo "Using mill version ${MILL_VERSION}" 1>&2 + fi +fi + +MILL_NATIVE_SUFFIX="-native" +MILL_JVM_SUFFIX="-jvm" +FULL_MILL_VERSION=$MILL_VERSION +ARTIFACT_SUFFIX="" +set_artifact_suffix(){ + if [ "$(expr substr $(uname -s) 1 5 2>/dev/null)" = "Linux" ]; then + if [ "$(uname -m)" = "aarch64" ]; then + ARTIFACT_SUFFIX="-native-linux-aarch64" + else + ARTIFACT_SUFFIX="-native-linux-amd64" + fi + elif [ "$(uname)" = "Darwin" ]; then + if [ "$(uname -m)" = "arm64" ]; then + ARTIFACT_SUFFIX="-native-mac-aarch64" + else + ARTIFACT_SUFFIX="-native-mac-amd64" + fi + else + echo "This native mill launcher supports only Linux and macOS." 1>&2 + exit 1 + fi +} + +case "$MILL_VERSION" in + *"$MILL_NATIVE_SUFFIX") + MILL_VERSION=${MILL_VERSION%"$MILL_NATIVE_SUFFIX"} + set_artifact_suffix + ;; + + *"$MILL_JVM_SUFFIX") + MILL_VERSION=${MILL_VERSION%"$MILL_JVM_SUFFIX"} + ;; + + *) + case "$MILL_VERSION" in + 0.1.*) ;; + 0.2.*) ;; + 0.3.*) ;; + 0.4.*) ;; + 0.5.*) ;; + 0.6.*) ;; + 0.7.*) ;; + 0.8.*) ;; + 0.9.*) ;; + 0.10.*) ;; + 0.11.*) ;; + 0.12.*) ;; + *) + set_artifact_suffix + esac + ;; +esac + +MILL="${MILL_DOWNLOAD_PATH}/$MILL_VERSION$ARTIFACT_SUFFIX" + +try_to_use_system_mill() { + if [ "$(uname)" != "Linux" ]; then + return 0 + fi + + MILL_IN_PATH="$(command -v mill || true)" + + if [ -z "${MILL_IN_PATH}" ]; then + return 0 + fi + + SYSTEM_MILL_FIRST_TWO_BYTES=$(head --bytes=2 "${MILL_IN_PATH}") + if [ "${SYSTEM_MILL_FIRST_TWO_BYTES}" = "#!" ]; then + # MILL_IN_PATH is (very likely) a shell script and not the mill + # executable, ignore it. + return 0 + fi + + SYSTEM_MILL_PATH=$(readlink -e "${MILL_IN_PATH}") + SYSTEM_MILL_SIZE=$(stat --format=%s "${SYSTEM_MILL_PATH}") + SYSTEM_MILL_MTIME=$(stat --format=%y "${SYSTEM_MILL_PATH}") + + if [ ! -d "${MILL_USER_CACHE_DIR}" ]; then + mkdir -p "${MILL_USER_CACHE_DIR}" + fi + + SYSTEM_MILL_INFO_FILE="${MILL_USER_CACHE_DIR}/system-mill-info" + if [ -f "${SYSTEM_MILL_INFO_FILE}" ]; then + parseSystemMillInfo() { + LINE_NUMBER="${1}" + # Select the line number of the SYSTEM_MILL_INFO_FILE, cut the + # variable definition in that line in two halves and return + # the value, and finally remove the quotes. + sed -n "${LINE_NUMBER}p" "${SYSTEM_MILL_INFO_FILE}" |\ + cut -d= -f2 |\ + sed 's/"\(.*\)"/\1/' + } + + CACHED_SYSTEM_MILL_PATH=$(parseSystemMillInfo 1) + CACHED_SYSTEM_MILL_VERSION=$(parseSystemMillInfo 2) + CACHED_SYSTEM_MILL_SIZE=$(parseSystemMillInfo 3) + CACHED_SYSTEM_MILL_MTIME=$(parseSystemMillInfo 4) + + if [ "${SYSTEM_MILL_PATH}" = "${CACHED_SYSTEM_MILL_PATH}" ] \ + && [ "${SYSTEM_MILL_SIZE}" = "${CACHED_SYSTEM_MILL_SIZE}" ] \ + && [ "${SYSTEM_MILL_MTIME}" = "${CACHED_SYSTEM_MILL_MTIME}" ]; then + if [ "${CACHED_SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then + MILL="${SYSTEM_MILL_PATH}" + return 0 + else + return 0 + fi + fi + fi + + SYSTEM_MILL_VERSION=$(${SYSTEM_MILL_PATH} --version | head -n1 | sed -n 's/^Mill.*version \(.*\)/\1/p') + + cat < "${SYSTEM_MILL_INFO_FILE}" +CACHED_SYSTEM_MILL_PATH="${SYSTEM_MILL_PATH}" +CACHED_SYSTEM_MILL_VERSION="${SYSTEM_MILL_VERSION}" +CACHED_SYSTEM_MILL_SIZE="${SYSTEM_MILL_SIZE}" +CACHED_SYSTEM_MILL_MTIME="${SYSTEM_MILL_MTIME}" +EOF + + if [ "${SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then + MILL="${SYSTEM_MILL_PATH}" + fi +} +try_to_use_system_mill + +# If not already downloaded, download it +if [ ! -s "${MILL}" ] || [ "$MILL_TEST_DRY_RUN_LAUNCHER_SCRIPT" = "1" ] ; then + case $MILL_VERSION in + 0.0.* | 0.1.* | 0.2.* | 0.3.* | 0.4.* ) + DOWNLOAD_SUFFIX="" + DOWNLOAD_FROM_MAVEN=0 + ;; + 0.5.* | 0.6.* | 0.7.* | 0.8.* | 0.9.* | 0.10.* | 0.11.0-M* ) + DOWNLOAD_SUFFIX="-assembly" + DOWNLOAD_FROM_MAVEN=0 + ;; + *) + DOWNLOAD_SUFFIX="-assembly" + DOWNLOAD_FROM_MAVEN=1 + ;; + esac + case $MILL_VERSION in + 0.12.0 | 0.12.1 | 0.12.2 | 0.12.3 | 0.12.4 | 0.12.5 | 0.12.6 | 0.12.7 | 0.12.8 | 0.12.9 | 0.12.10 | 0.12.11 ) + DOWNLOAD_EXT="jar" + ;; + 0.12.* ) + DOWNLOAD_EXT="exe" + ;; + 0.* ) + DOWNLOAD_EXT="jar" + ;; + *) + DOWNLOAD_EXT="exe" + ;; + esac + + DOWNLOAD_FILE=$(mktemp mill.XXXXXX) + if [ "$DOWNLOAD_FROM_MAVEN" = "1" ] ; then + DOWNLOAD_URL="https://repo1.maven.org/maven2/com/lihaoyi/mill-dist${ARTIFACT_SUFFIX}/${MILL_VERSION}/mill-dist${ARTIFACT_SUFFIX}-${MILL_VERSION}.${DOWNLOAD_EXT}" + else + MILL_VERSION_TAG=$(echo "$MILL_VERSION" | sed -E 's/([^-]+)(-M[0-9]+)?(-.*)?/\1\2/') + DOWNLOAD_URL="${GITHUB_RELEASE_CDN}${MILL_REPO_URL}/releases/download/${MILL_VERSION_TAG}/${MILL_VERSION}${DOWNLOAD_SUFFIX}" + unset MILL_VERSION_TAG + fi + + if [ "$MILL_TEST_DRY_RUN_LAUNCHER_SCRIPT" = "1" ] ; then + echo $DOWNLOAD_URL + echo $MILL + exit 0 + fi + # TODO: handle command not found + echo "Downloading mill ${MILL_VERSION} from ${DOWNLOAD_URL} ..." 1>&2 + ${CURL_CMD} -f -L -o "${DOWNLOAD_FILE}" "${DOWNLOAD_URL}" + chmod +x "${DOWNLOAD_FILE}" + mkdir -p "${MILL_DOWNLOAD_PATH}" + mv "${DOWNLOAD_FILE}" "${MILL}" + + unset DOWNLOAD_FILE + unset DOWNLOAD_SUFFIX +fi + +if [ -z "$MILL_MAIN_CLI" ] ; then + MILL_MAIN_CLI="${0}" +fi + +MILL_FIRST_ARG="" +if [ "$1" = "--bsp" ] || [ "${1#"-i"}" != "$1" ] || [ "$1" = "--interactive" ] || [ "$1" = "--no-server" ] || [ "$1" = "--no-daemon" ] || [ "$1" = "--repl" ] || [ "$1" = "--help" ] ; then + # Need to preserve the first position of those listed options + MILL_FIRST_ARG=$1 + shift +fi + +unset MILL_DOWNLOAD_PATH +unset MILL_OLD_DOWNLOAD_PATH +unset OLD_MILL +unset MILL_VERSION +unset MILL_REPO_URL + +# -D mill.main.cli is for compatibility with Mill 0.10.9 - 0.13.0-M2 +# We don't quote MILL_FIRST_ARG on purpose, so we can expand the empty value without quotes +# shellcheck disable=SC2086 +exec "${MILL}" $MILL_FIRST_ARG -D "mill.main.cli=${MILL_MAIN_CLI}" "$@" diff --git a/mill-build/build.mill b/mill-build/build.mill new file mode 100644 index 000000000000..9dc3a2ff0fd8 --- /dev/null +++ b/mill-build/build.mill @@ -0,0 +1,23 @@ +import mill.Task +import mill.api.{BuildCtx, PathRef} +import mill.meta.MillBuildRootModule +import mill.javalib.* + +object `package` extends MillBuildRootModule { + + def scalaJSVersion = "1.20.1" + + def mvnDeps = Seq( + mvn"com.github.lolgab::mill-mima_mill1:0.2.0", + mvn"org.ow2.asm:asm:9.9", + mvn"org.eclipse.jgit:org.eclipse.jgit:7.5.0.202512021534-r", + mvn"org.scala-js:scalajs-linker_2.13:$scalaJSVersion" + ) + + def sources = Task { + val extraSrcDir = BuildCtx.withFilesystemCheckerDisabled { + PathRef(BuildCtx.workspaceRoot / "tasty/src") + } + super.sources() ++ Seq(extraSrcDir) + } +} diff --git a/mill-build/src/mill/scala3build/CompileClassesPathHelper.scala b/mill-build/src/mill/scala3build/CompileClassesPathHelper.scala new file mode 100644 index 000000000000..a48bea9744e5 --- /dev/null +++ b/mill-build/src/mill/scala3build/CompileClassesPathHelper.scala @@ -0,0 +1,11 @@ +package mill.scala3build + +import mill.api.BuildCtx +import mill.scalalib.* + +trait CompileClassesPathHelper extends ScalaModule { + def compileClassesPath0 = compileClassesPath.resolve(BuildCtx.workspaceRoot / "out" /* FIXME */) + + def jarPath = + resolveRelativeToOut(jar, identity).resolve(BuildCtx.workspaceRoot / "out" /* FIXME */) +} diff --git a/mill-build/src/scala3build/BuildType.scala b/mill-build/src/scala3build/BuildType.scala new file mode 100644 index 000000000000..a4b953f8097a --- /dev/null +++ b/mill-build/src/scala3build/BuildType.scala @@ -0,0 +1,24 @@ +package scala3build + +import mill.api.Cross + +enum BuildType: + case Bootstrapping + case Final + + def asString: String = this match { + case Bootstrapping => "bootstrapping" + case Final => "final" + } + + def isBootstrapping: Boolean = + this == Bootstrapping + +object BuildType: + lazy val allValues = Seq[BuildType]( + BuildType.Bootstrapping, + BuildType.Final + ) + + given Cross.ToSegments[BuildType] = + new Cross.ToSegments(buildType => List(buildType.asString)) diff --git a/mill-build/src/scala3build/Constants.scala b/mill-build/src/scala3build/Constants.scala new file mode 100644 index 000000000000..831e4e55849f --- /dev/null +++ b/mill-build/src/scala3build/Constants.scala @@ -0,0 +1,5 @@ +package scala3build + +object Constants { + val dottyGithubRawUserContentUrl = "https://raw.githubusercontent.com/scala/scala3" +} diff --git a/mill-build/src/scala3build/CrossScala3Module.scala b/mill-build/src/scala3build/CrossScala3Module.scala new file mode 100644 index 000000000000..156309fedd09 --- /dev/null +++ b/mill-build/src/scala3build/CrossScala3Module.scala @@ -0,0 +1,7 @@ +package scala3build + +import mill.* + +trait CrossScala3Module extends Cross.Module[BuildType] with Scala3Module { + def buildType = crossValue +} diff --git a/mill-build/src/scala3build/DocumentationWebsite.scala b/mill-build/src/scala3build/DocumentationWebsite.scala new file mode 100644 index 000000000000..9a7935d80d96 --- /dev/null +++ b/mill-build/src/scala3build/DocumentationWebsite.scala @@ -0,0 +1,59 @@ +package scala3build + +import coursier.cache.FileCache +import coursier.util.Artifact +import mill.api.BuildCtx + +object DocumentationWebsite { + + def generateScaladocTestCasesAssets( + workspaceRoot: os.Path, + contributorsFile: os.Path, + cssContentContributorsSourceBaseFile: os.Path + ): Unit = { + val assetsDir = workspaceRoot / "scaladoc-testcases/docs/_assets" + os.copy( + contributorsFile, + assetsDir / "js/contributors.js" + ) + os.copy( + cssContentContributorsSourceBaseFile / "content-contributors.css", + assetsDir / "css/content-contributors.css" + ) + } + + def generateStaticAssets( + workspaceRoot: os.Path, + contributorsFile: os.Path, + mainFile: os.Path, + cssContentContributorsSourceBaseFile: os.Path, + cssSourceFileBase: os.Path, + destResourceDir: os.Path + ): Unit = { + + val scriptsDir = destResourceDir / "dotty_res/scripts" + val stylesDir = destResourceDir / "dotty_res/styles" + + os.makeDir.all(scriptsDir) + os.makeDir.all(stylesDir) + + os.copy(contributorsFile, scriptsDir / "contributors.js") + os.copy(mainFile, scriptsDir / "scaladoc-scalajs.js") + + for { + cssDir <- Seq(cssSourceFileBase, cssContentContributorsSourceBaseFile) + cssFile <- os.list(cssDir) + if os.isFile(cssFile) && cssFile.last.endsWith(".css") + } + os.copy(cssFile, stylesDir / cssFile.last) + + val cache = FileCache() + val inkuireJs = cache + .file(Artifact(s"https://github.com/VirtusLab/Inkuire/releases/download/${Versions.inkuireVersion}/inkuire.js")) + .run.unsafeRun(true)(using cache.ec) + .fold(throw _, os.Path(_)) + os.copy(inkuireJs, scriptsDir / "inkuire.js") + } +} + + diff --git a/mill-build/src/scala3build/LibraryFilesToCopy.scala b/mill-build/src/scala3build/LibraryFilesToCopy.scala new file mode 100644 index 000000000000..5a400e8c4b02 --- /dev/null +++ b/mill-build/src/scala3build/LibraryFilesToCopy.scala @@ -0,0 +1,57 @@ +package scala3build + +object LibraryFilesToCopy { + + lazy val set = Set( + "scala/Function0", + "scala/Function1", + "scala/Function2", + "scala/Product1", + "scala/Product2", + "scala/Tuple1", + "scala/Tuple2", + "scala/collection/ArrayOps", + "scala/collection/Stepper", + "scala/collection/DoubleStepper", + "scala/collection/IntStepper", + "scala/collection/LongStepper", + "scala/collection/immutable/DoubleVectorStepper", + "scala/collection/immutable/IntVectorStepper", + "scala/collection/immutable/LongVectorStepper", + "scala/collection/immutable/Range", + "scala/jdk/Accumulator", + "scala/jdk/DoubleAccumulator", + "scala/jdk/IntAccumulator", + "scala/jdk/LongAccumulator", + "scala/jdk/FunctionWrappers$FromJavaDoubleBinaryOperator", + "scala/jdk/FunctionWrappers$FromJavaBooleanSupplier", + "scala/jdk/FunctionWrappers$FromJavaDoubleConsumer", + "scala/jdk/FunctionWrappers$FromJavaDoublePredicate", + "scala/jdk/FunctionWrappers$FromJavaDoubleSupplier", + "scala/jdk/FunctionWrappers$FromJavaDoubleToIntFunction", + "scala/jdk/FunctionWrappers$FromJavaDoubleToLongFunction", + "scala/jdk/FunctionWrappers$FromJavaIntBinaryOperator", + "scala/jdk/FunctionWrappers$FromJavaDoubleUnaryOperator", + "scala/jdk/FunctionWrappers$FromJavaIntPredicate", + "scala/jdk/FunctionWrappers$FromJavaIntConsumer", + "scala/jdk/FunctionWrappers$FromJavaIntSupplier", + "scala/jdk/FunctionWrappers$FromJavaIntToDoubleFunction", + "scala/jdk/FunctionWrappers$FromJavaIntToLongFunction", + "scala/jdk/FunctionWrappers$FromJavaIntUnaryOperator", + "scala/jdk/FunctionWrappers$FromJavaLongBinaryOperator", + "scala/jdk/FunctionWrappers$FromJavaLongConsumer", + "scala/jdk/FunctionWrappers$FromJavaLongPredicate", + "scala/jdk/FunctionWrappers$FromJavaLongSupplier", + "scala/jdk/FunctionWrappers$FromJavaLongToDoubleFunction", + "scala/jdk/FunctionWrappers$FromJavaLongToIntFunction", + "scala/jdk/FunctionWrappers$FromJavaLongUnaryOperator", + "scala/runtime/AbstractFunction0", + "scala/runtime/AbstractFunction1", + "scala/runtime/AbstractFunction2", + "scala/runtime/AbstractPartialFunction", + "scala/runtime/NonLocalReturnControl", + "scala/util/Sorting", + "scala/util/hashing/MurmurHash3" + ) + +} diff --git a/mill-build/src/scala3build/Scala3JavaModule.scala b/mill-build/src/scala3build/Scala3JavaModule.scala new file mode 100644 index 000000000000..b176c379310f --- /dev/null +++ b/mill-build/src/scala3build/Scala3JavaModule.scala @@ -0,0 +1,47 @@ +package scala3build + +import com.github.lolgab.mill.mima.Mima +import mill.* +import mill.scalalib.* +import mill.scalalib.publish.* + +trait Scala3JavaModule extends JavaModule with PublishModule with Mima { + trait Scala3JavaTests extends JavaTests with TestModule with TestModule.Junit4 { + def testSources: T[Seq[PathRef]] + def sources = testSources + def testParallelism = false + + def mvnDeps = super.mvnDeps() ++ Seq( + mvn"com.github.sbt:junit-interface:0.13.3" + ) + } + + def jvmId = "17" // force external zinc worker + + def pomSettings = PomSettings( + description = artifactName(), + organization = "org.scala-lang", + url = "https://github.com/scala/scala3", + licenses = Seq(License.`Apache-2.0`), + versionControl = VersionControl.github("scala", "scala3"), + developers = Seq(Developer("scala", "The Scala Team", "https://scala-lang.org", email = "security@scala-lang.org")) + ) + def publishVersion = Versions.dottyVersion + + def javacOptions = super.javacOptions() ++ Seq( + "--release", Versions.minimumJVMVersion + ) + + def manifest = super.manifest().add( + "Automatic-Module-Name" -> + s"${pomSettings().organization.replaceAll("-", ".")}.${artifactName().replaceAll("-", ".")}" + ) + + def runClasspathAsJars: T[Seq[PathRef]] = Task { + resolvedRunMvnDeps().toSeq ++ + transitiveJars() ++ + Seq(jar()) + } + + def runClasspath = runClasspathAsJars +} diff --git a/mill-build/src/scala3build/Scala3JsModule.scala b/mill-build/src/scala3build/Scala3JsModule.scala new file mode 100644 index 000000000000..10f42b6d707e --- /dev/null +++ b/mill-build/src/scala3build/Scala3JsModule.scala @@ -0,0 +1,80 @@ +package scala3build + +import mill.* +import mill.scalajslib.* +import mill.scalajslib.api.* +import mill.scalalib.* +import org.scalajs.linker.interface.* + +trait Scala3JsModule extends Scala3Module with ScalaJSModule { + def scalaJSVersion = Versions.scalaJSVersion + + def enableWebAssembly: Boolean = false + + // Disable annoying "This is often an error due to a missing second colon (:) before the version" messages + def resolvedDepsWarnNonPlatform = false + + def scalaJSConfig = Task.Anon { + var config = super.scalaJSConfig() + .withCheckIR(true) + + if (enableWebAssembly) + config = config + .withModuleKind(ModuleKind.ESModule) + .withExperimentalUseWebAssembly(true) + + config + } + + def jsEnvConfig = Task { + val config = JsEnvConfig.NodeJs() + if (enableWebAssembly) + config.copy( + args = List( + "--experimental-wasm-exnref", + "--experimental-wasm-imported-strings", // Original sbt build comment: for JS string builtins + "--experimental-wasm-jspi", // Original sbt build comment: for JSPI, used by async/await + ) + ) + else + config + } + + def mandatoryMvnDeps = Task { + super.mandatoryMvnDeps() + .filter { dep => + val (org, name) = (dep.dep.module.organization.value, dep.dep.module.name.value) + org != "org.scala-lang" || !name.startsWith("scala3-library") + } + .map { dep => + dep.exclude(("org.scala-lang", "scala-library")) + } + } + + trait Scala3JsTests extends Scala3Tests with ScalaJSTests { + def mvnDeps = Task { + val baseDeps = super.mvnDeps().filter { dep => + val org = dep.dep.module.organization.value + !org.contains("junit") + } + baseDeps ++ Seq( + mvn"org.scala-js:scalajs-junit-test-runtime_2.13:${Versions.scalaJSVersion}" + ) + } + def mandatoryMvnDeps = Task { + super.mandatoryMvnDeps() + .filter { dep => + val (org, name) = (dep.dep.module.organization.value, dep.dep.module.name.value) + org != "org.scala-lang" || !name.startsWith("scala3-library") + } + .map { dep => + dep.exclude(("org.scala-lang", "scala-library")) + } + .filter { dep => + val org = dep.dep.module.organization.value + !org.contains("junit") + } + } + def testFramework = "com.novocode.junit.JUnitFramework" + } +} diff --git a/mill-build/src/scala3build/Scala3Module.scala b/mill-build/src/scala3build/Scala3Module.scala new file mode 100644 index 000000000000..0892456e30e7 --- /dev/null +++ b/mill-build/src/scala3build/Scala3Module.scala @@ -0,0 +1,75 @@ +package scala3build + +import build_.package_ as build +import mill.* +import mill.scalalib.* + +trait Scala3Module extends Scala3JavaModule with ScalaModule { outer => + def buildType: BuildType = BuildType.Final + + def resolutionParams = Task.Anon { + val baseParams = super.resolutionParams() + baseParams.withForceVersion0( + baseParams.forceVersion0.filter(_._1.organization.value != "org.scala-lang") + ) + } + + def scalaVersion = + if (buildType.isBootstrapping) Versions.referenceVersion + else Versions.dottyVersion + + def scalaLibraryMvnDeps = Nil + def scalaCompilerClasspath = + if (buildType.isBootstrapping) + Task { + defaultResolver().classpath( + Seq(mvn"org.scala-lang:scala3-compiler_3:${Versions.referenceVersion}") + ) + } + else + build.compiler(BuildType.Bootstrapping).runClasspathAsJars + + def scalaCompilerBridge = + if (buildType.isBootstrapping) + Task(None) + else + Task { + Some(build.`sbt-bridge`(BuildType.Bootstrapping).jar()) + } + + def scalacOptions = super.scalacOptions() ++ Seq( + "-feature", + "-deprecation", + "-unchecked", + //"-Wconf:cat=deprecation&msg=Unsafe:s", // example usage + //"-Wunused:all", + //"-rewrite", // requires -Werror:false since no rewrites are applied with errors + "-encoding", "UTF8", + "-language:implicitConversions", + "--java-output-version", Versions.minimumJVMVersion + ) + + trait Scala3Tests extends ScalaTests with Scala3JavaTests { self => + + def resolutionParams = Task.Anon { + val baseParams = super.resolutionParams() + baseParams.withForceVersion0( + baseParams.forceVersion0.filter(_._1.organization.value != "org.scala-lang") + ) + } + + def scalaLibraryMvnDeps = outer.scalaLibraryMvnDeps + def scalaCompilerClasspath = outer.scalaCompilerClasspath + + def testArgsDefault = Task { + val extraArgs = if (buildType.isBootstrapping) Seq("--exclude-categories=dotty.BootstrappedOnlyTests") else Nil + super.testArgsDefault() ++ extraArgs + } + } + + def enableBsp = buildType.isBootstrapping || Scala3Module.enableBspForFinalModules +} + +object Scala3Module { + def enableBspForFinalModules = false +} diff --git a/mill-build/src/scala3build/Shading.scala b/mill-build/src/scala3build/Shading.scala new file mode 100644 index 000000000000..7ca1ae703116 --- /dev/null +++ b/mill-build/src/scala3build/Shading.scala @@ -0,0 +1,79 @@ +package scala3build + +object Shading { + + /** Replace package names in package definitions, for shading. + * It assumes the full package def is written on a single line. + * It does not adapt the imports accordingly. + */ + def replacePackage(lines: List[String])(replace: PartialFunction[String, String]): (Boolean, List[String]) = { + def recur(lines: List[String]): (Boolean, List[String]) = + lines match { + case head :: tail => + if (head.startsWith("package ")) { + val packageName = head.stripPrefix("package ").trim + val newPackageNameOpt = replace.lift(packageName) + newPackageNameOpt match { + case Some(newPackageName) => + (true, s"package $newPackageName" :: tail) + case None => + (false, lines) + } + } + else { + val (hasChanges, tail0) = recur(tail) + (hasChanges, head :: tail0) + } + case _ => + (false, lines) + } + recur(lines) + } + + /** Insert UnsafeNulls Import after package */ + def insertUnsafeNullsImport(lines: List[String]): List[String] = { + def recur(ls: List[String], foundPackage: Boolean): List[String] = ls match { + case l :: rest => + val lt = l.trim() + if (foundPackage) { + if (!(lt.isEmpty || lt.startsWith("package "))) + "import scala.language.unsafeNulls" :: ls + else l :: recur(rest, foundPackage) + } else { + if (lt.startsWith("package ")) l +: recur(rest, true) + else l :: recur(rest, foundPackage) + } + case _ => ls + } + recur(lines, false) + } + + def replaceProtobuf(lines: List[String]): List[String] = + // replace imports of `com.google.protobuf.*` with compiler implemented version + lines match { + case l :: rest => + val lt = l.trim() + if (lt.isEmpty || lt.startsWith("package ") || lt.startsWith("import ")) { + val newLine = + if (lt.startsWith("import com.google.protobuf.")) + if (lt == "import com.google.protobuf.CodedInputStream") + "import dotty.tools.dotc.semanticdb.internal.SemanticdbInputStream as CodedInputStream" + else if (lt == "import com.google.protobuf.CodedOutputStream") + "import dotty.tools.dotc.semanticdb.internal.SemanticdbOutputStream as CodedOutputStream" + else + l + else + l + newLine :: replaceProtobuf(rest) + } else + lines // don't check rest of file + case _ => lines + } + + def csDep(org: String, name: String, version: String): coursier.Dependency = + coursier.Dependency( + coursier.Module(coursier.Organization(org), coursier.ModuleName(name), Map.empty), + coursier.version.VersionConstraint(version) + ) + +} diff --git a/mill-build/src/scala3build/SharedLibrarySettings.scala b/mill-build/src/scala3build/SharedLibrarySettings.scala new file mode 100644 index 000000000000..d7a849e2dc34 --- /dev/null +++ b/mill-build/src/scala3build/SharedLibrarySettings.scala @@ -0,0 +1,64 @@ +package scala3build + +import mill.* +import mill.scalalib.* + +import java.util.zip.ZipFile + +import scala.jdk.CollectionConverters.* +import scala.util.Using + +trait SharedLibrarySettings extends CrossScala3Module { + + def isJS: Boolean = false + + // Original comment: TODO: Enable fatal warnings after 3.8 because old stdlib has different nullability. + def scalacOptions = super.scalacOptions() ++ Seq( + "-Yexplicit-nulls" + ) + + def patches: T[Seq[PathRef]] + + def compile = Task { + val res = super.compile() + val updatedClassDir = Task.dest + for (elem <- os.list(res.classes.path)) + os.copy(elem, updatedClassDir / elem.last) + + val keepExtensions = if (isJS) Seq(".class", ".sjsir") else Seq(".class") + + for (jarOrDir <- patches().map(_.path)) { + def process(relPath: os.SubPath, content: => Array[Byte]): Unit = { + val shortName = keepExtensions.foldLeft(relPath.toString)(_.stripSuffix(_)) + val dest = updatedClassDir / relPath + val shouldCopy = LibraryFilesToCopy.set.contains(shortName) || LibraryFilesToCopy.set.exists(n => shortName.startsWith(n + "$")) + if (shouldCopy) { + System.err.println(s"Overwriting $relPath") + StripScala2Annotations.patchFile(updatedClassDir, relPath, content) + } + else if (!os.exists(dest)) { + System.err.println(s"Copying $relPath") + StripScala2Annotations.patchFile(updatedClassDir, relPath, content) + } + } + + System.err.println(s"Copying entries from $jarOrDir") + + if (os.isDir(jarOrDir)) + os.walk(jarOrDir) + .filter(f => keepExtensions.exists(f.last.endsWith)) + .filter(os.isFile) + .sortBy(_.subRelativeTo(jarOrDir).toString) + .foreach { f => + process(f.subRelativeTo(jarOrDir), os.read.bytes(f)) + } + else + Using.resource(new ZipFile(jarOrDir.toIO)) { zf => + for (ent <- zf.entries().asScala if !ent.isDirectory && keepExtensions.exists(ent.getName.endsWith)) + process(os.sub / ent.getName.split('/').toSeq, zf.getInputStream(ent).readAllBytes()) + } + } + + mill.javalib.api.CompilationResult(res.analysisFile, PathRef(updatedClassDir)) + } +} diff --git a/mill-build/src/scala3build/SjsUnitConstantHolderGenerator.scala b/mill-build/src/scala3build/SjsUnitConstantHolderGenerator.scala new file mode 100644 index 000000000000..1040f686a387 --- /dev/null +++ b/mill-build/src/scala3build/SjsUnitConstantHolderGenerator.scala @@ -0,0 +1,43 @@ +package scala3build + +import scala.annotation.tailrec + +object SjsUnitConstantHolderGenerator { + /** Generate a *.scala file that contains the given values as literals. */ + def generate(dir: os.Path, fqn: String, values: (String, Any)*): Seq[os.Path] = { + val (fullPkg@(_ :+ pkg)) :+ objectName = fqn.split('.').toSeq.runtimeChecked + + val out = dir / (objectName + ".scala") + + val defs = for { + (name, value) <- values + } yield { + s"val $name = ${literal(value)}" + } + + val scalaCode = + s""" + package ${fullPkg.mkString(".")} + + private[$pkg] object $objectName { + ${defs.mkString("\n")} + } + """ + + os.write(out, scalaCode) + + Seq(out) + } + + @tailrec + private final def literal(v: Any): String = v match { + case s: String => "raw\"\"\"" + s + "\"\"\"" + case b: Boolean => b.toString + case i: Int => i.toString + case f: os.Path => literal(f.toString) + + case _ => + throw new IllegalArgumentException( + "Unsupported value type: " + v.getClass) + } +} diff --git a/mill-build/src/scala3build/StripScala2Annotations.scala b/mill-build/src/scala3build/StripScala2Annotations.scala new file mode 100644 index 000000000000..d6e4bffadfd1 --- /dev/null +++ b/mill-build/src/scala3build/StripScala2Annotations.scala @@ -0,0 +1,205 @@ +package scala3build + +import dotty.tools.tasty.TastyHeaderUnpickler +import org.objectweb.asm.* + +import java.nio.ByteBuffer + +object StripScala2Annotations { + + // All of this is copied and adapted (os-lib) from project/ScalaLibraryPlugin.scala + + /** Scala 2 pickle annotation descriptors that should be stripped from class files */ + private val Scala2PickleAnnotations = Set( + "Lscala/reflect/ScalaSignature;", + "Lscala/reflect/ScalaLongSignature;" + ) + + /** Scala 2 attribute names that should be stripped from class files */ + private val Scala2PickleAttributes = Set("ScalaSig", "ScalaInlineInfo") + + /** Check if an annotation descriptor is a Scala 2 pickle annotation */ + private def isScala2PickleAnnotation(descriptor: String): Boolean = + Scala2PickleAnnotations.contains(descriptor) + + /* Extract TASTY UUID from class file bytecode, if present */ + private def extractTastyUUIDFromClass(bytes: Array[Byte]): Option[Array[Byte]] = { + var result: Option[Array[Byte]] = None + val tastyPrototype = new Attribute("TASTY") { + override def read(cr: ClassReader, off: Int, len: Int, buf: Array[Char], codeOff: Int, labels: Array[Label]): Attribute = { + if (len == 16) { + val uuid = Array.tabulate[Byte](16)(i => cr.readByte(off + i).toByte) + result = Some(uuid) + } + this + } + } + new ClassReader(bytes).accept( + new ClassVisitor(Opcodes.ASM9) {}, + Array(tastyPrototype), + 0 + ) + result + } + + /* Extract the SourceFile attribute from class file bytecode */ + private def extractSourceFile(bytes: Array[Byte]): Option[String] = { + var sourceFile: Option[String] = None + val visitor = new ClassVisitor(Opcodes.ASM9) { + override def visitSource(source: String, debug: String): Unit = + sourceFile = Option(source) + } + // Note: Don't use SKIP_DEBUG here - SourceFile is debug info and would be skipped + new ClassReader(bytes).accept( + visitor, + ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES + ) + sourceFile + } + + /* Extract the UUID bytes (16 bytes) from a TASTy file. + * + * Uses the official TastyHeaderUnpickler to parse the header and extract the UUID, + * ensuring correctness and validating the TASTy format. + */ + private def extractTastyUUID(tastyBytes: Array[Byte]): Array[Byte] = { + val unpickler = new TastyHeaderUnpickler(tastyBytes) + val header = unpickler.readFullHeader() + val uuid = header.uuid + + // Convert UUID (two longs) to 16-byte array in big-endian format + val buffer = ByteBuffer.allocate(16) + buffer.putLong(uuid.getMostSignificantBits) + buffer.putLong(uuid.getLeastSignificantBits) + buffer.array() + } + + /* Remove Scala 2 Pickles from class file and optionally add TASTY attribute. + * Also ensures the Scala attribute is present for all Scala-compiled classes. + * + * @param bytes the class file bytecode + * @param tastyUUID optional 16-byte UUID from the corresponding .tasty file (only for primary class) + */ + private def patchClassFile(bytes: Array[Byte], tastyUUID: Option[Array[Byte]]): Array[Byte] = { + val reader = new ClassReader(bytes) + val writer = new ClassWriter(0) + // Remove Scala 2 pickles and Scala signatures + val visitor = new ClassVisitor(Opcodes.ASM9, writer) { + override def visitAttribute(attr: Attribute): Unit = { + val shouldRemove = Scala2PickleAttributes.contains(attr.`type`) + if (!shouldRemove) super.visitAttribute(attr) + } + + override def visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor = + if (isScala2PickleAnnotation(desc)) null + else super.visitAnnotation(desc, visible) + } + reader.accept(visitor, 0) + // Only add TASTY attribute for the primary class (not for inner/nested classes) + tastyUUID + .map(new TastyAttribute(_)) + .foreach(writer.visitAttribute) + // Add Scala attribute if not present and this is a Scala-compiled class + def isJavaSourced = extractSourceFile(bytes).exists(_.endsWith(".java")) + if (!hasScalaAttribute(bytes) && !isJavaSourced) { + writer.visitAttribute(new ScalaAttribute) + } + writer.toByteArray + } + + /** Apply the patches to given input file and write the result to the output. + * For .class files, strips Scala 2 pickles and adds TASTY attribute only for primary classes. + * + * The TASTY attribute is only added to the "primary" class for each .tasty file: + * - Inner/nested classes (e.g., Outer$Inner.class) don't get TASTY attribute + * - Companion objects (Foo$.class when Foo.class exists) don't get TASTY attribute + * - Only the class whose name matches the .tasty file name gets the attribute + * - Java source files don't produce .tasty files, so they are skipped + * + * Additionally validates that if the original class file (before patching) had a TASTY + * attribute, the patched version will also have one. This prevents accidentally losing + * TASTY attributes during the patching process. + * + * @param input the input file (.class or .sjsir) + * @param classDirectory the class directory to look for .tasty files + */ + def patchFile(classDirectory: os.Path, dest: os.SubPath, content: Array[Byte]): Unit = { + + val isClass = dest.last.endsWith(".class") + + // Extract the original TASTY UUID if the class file exists and has one + val originalTastyUUIDOpt = + if (isClass && os.exists(classDirectory / dest)) + extractTastyUUIDFromClass(os.read.bytes(classDirectory / dest)) + else + None + + val tastyUUIDOpt = { + // Skip TASTY handling for Java-sourced classes (they don't have .tasty files) + def isJavaSourced = extractSourceFile(content).exists(_.endsWith(".java")) + + if (!isClass || isJavaSourced) None + else { + val classPath = (dest / os.up) / dest.last.stripSuffix(".class") + val basePath = (classPath / os.up) / classPath.last.split('$').head + + val tastyFile = classDirectory / (basePath / os.up) / (basePath.last + ".tasty") + assert(os.exists(tastyFile), s"TASTY file $tastyFile does not exist for $dest") + + // Only add TASTY attribute if this is the primary class (class path equals base path) + // Inner classes, companion objects ($), anonymous classes ($$anon), etc. don't get TASTY attribute + val isPrimaryClass = classPath == basePath + if (isPrimaryClass) Some(extractTastyUUID(os.read.bytes(tastyFile))) + else None + } + } + + // Validation to ensure that no new TASTY attributes are added or removed when compared with unpatched sources + (tastyUUIDOpt, originalTastyUUIDOpt) match { + case (None, None) => () // no TASTY attribute, no problem + case (Some(newUUID), Some(originalUUID)) => + assert(java.util.Arrays.equals(originalUUID, newUUID), + s"TASTY UUID mismatch for $dest: original=${originalUUID.map(b => f"$b%02x").mkString}, new=${newUUID.map(b => f"$b%02x").mkString}." + ) + case (Some(_), None) => sys.error(s"TASTY attribute defined, but not present in unpatched source $dest") + case (None, Some(_)) => sys.error(s"TASTY attribute missing, but present in unpatched $dest") + } + + val finalContent = + if (isClass) patchClassFile(content, tastyUUIDOpt) + else content + os.write.over(classDirectory / dest, finalContent, createFolders = true) + } + + /** Check if class file bytecode contains a Scala attribute */ + private def hasScalaAttribute(bytes: Array[Byte]): Boolean = { + var hasScala = false + val visitor = new ClassVisitor(Opcodes.ASM9) { + override def visitAttribute(attr: Attribute): Unit = { + if (attr.`type` == "Scala") hasScala = true + } + } + new ClassReader(bytes).accept( + visitor, + ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES + ) + hasScala + } + + /** Custom ASM Attribute for TASTY that can be written to class files */ + private class TastyAttribute(val uuid: Array[Byte]) extends Attribute("TASTY") { + override def write(classWriter: ClassWriter, code: Array[Byte], codeLength: Int, maxStack: Int, maxLocals: Int): ByteVector = { + val bv = new ByteVector(uuid.length) + bv.putByteArray(uuid, 0, uuid.length) + bv + } + } + + /** Custom ASM Attribute for Scala attribute marker (empty attribute) */ + private class ScalaAttribute extends Attribute("Scala") { + override def write(classWriter: ClassWriter, code: Array[Byte], codeLength: Int, maxStack: Int, maxLocals: Int): ByteVector = { + // Scala attribute is empty (length = 0x0) + new ByteVector(0) + } + } +} diff --git a/mill-build/src/scala3build/TestSuiteLinkerOptions.scala b/mill-build/src/scala3build/TestSuiteLinkerOptions.scala new file mode 100644 index 000000000000..22edab694e20 --- /dev/null +++ b/mill-build/src/scala3build/TestSuiteLinkerOptions.scala @@ -0,0 +1,39 @@ +package scala3build + +// Original sbt build comment: This file is an exact copy of the file of the same name in scala-js/scala-js + +import org.scalajs.linker.interface._ + +object TestSuiteLinkerOptions { + + def semantics(s: Semantics): Semantics = { + import Semantics.RuntimeClassNameMapper + + s.withRuntimeClassNameMapper( + RuntimeClassNameMapper.keepAll().andThen( + RuntimeClassNameMapper.regexReplace( + raw"""^org\.scalajs\.testsuite\.compiler\.ReflectionTest\$$RenamedTestClass$$""".r, + "renamed.test.Class") + ).andThen( + RuntimeClassNameMapper.regexReplace( + raw"""^org\.scalajs\.testsuite\.compiler\.ReflectionTest\$$Prefix""".r, + "renamed.test.byprefix.") + ).andThen( + RuntimeClassNameMapper.regexReplace( + raw"""^org\.scalajs\.testsuite\.compiler\.ReflectionTest\$$OtherPrefix""".r, + "renamed.test.byotherprefix.") + ) + ) + } + + def moduleInitializers: List[ModuleInitializer] = { + val module = "org.scalajs.testsuite.compiler.ModuleInitializers" + List( + ModuleInitializer.mainMethod(module, "mainNoArgs"), + ModuleInitializer.mainMethodWithArgs(module, "mainWithArgs"), + ModuleInitializer.mainMethodWithArgs(module, "mainWithArgs", List("foo", "bar")), + ModuleInitializer.mainMethod(module + "$NoLinkedClass", "main"), + ModuleInitializer.mainMethod(module + "$WithLinkedClass", "main") + ) + } +} diff --git a/mill-build/src/scala3build/VersionUtil.scala b/mill-build/src/scala3build/VersionUtil.scala new file mode 100644 index 000000000000..a9ae15f3185c --- /dev/null +++ b/mill-build/src/scala3build/VersionUtil.scala @@ -0,0 +1,48 @@ +package scala3build + +import org.eclipse.jgit.storage.file.FileRepositoryBuilder +import org.eclipse.jgit.lib.{Constants, ObjectId, Ref, Repository} +import org.eclipse.jgit.revwalk.{RevCommit, RevWalk} + +import java.text.SimpleDateFormat +import java.util.{Date, TimeZone} + +object VersionUtil { + + // Adapted from sbt-git + private class JGit(repo: Repository) { + def headCommit: ObjectId = + repo.exactRef(Constants.HEAD).getObjectId + + def headCommitSha: String = headCommit.name + + def headCommitDate: Date = { + val walk = new RevWalk(repo) + val commit = walk.parseCommit(headCommit) + val seconds = commit.getCommitTime.toLong + val millis = seconds * 1000L + new Date(millis) + } + } + + private lazy val git = { + val repo = new FileRepositoryBuilder() + .setMustExist(true) + .findGitDir() + .build() + new JGit(repo) + } + + /** Seven letters of the SHA hash is considered enough to uniquely identify a + * commit, albeit extremely large projects - such as the Linux kernel - need + * more letters to stay unique + */ + def gitHash: String = git.headCommitSha.substring(0, 7) + def commitDate: String = { + val format = new SimpleDateFormat("yyyyMMdd") + format.setTimeZone(TimeZone.getTimeZone("UTC")) + format.format(git.headCommitDate) + } + + lazy val currentYear: String = java.util.Calendar.getInstance().get(java.util.Calendar.YEAR).toString +} diff --git a/mill-build/src/scala3build/Versions.scala b/mill-build/src/scala3build/Versions.scala new file mode 100644 index 000000000000..e7fe1b9f355f --- /dev/null +++ b/mill-build/src/scala3build/Versions.scala @@ -0,0 +1,168 @@ +package scala3build + +object Versions { + + /** Version of the Scala compiler used to build the artifacts. + * Reference version should track the latest version pushed to Maven: + * - In main branch it should be the last RC version + * - In release branch it should be the last stable release + * + * Warning: Change of this variable needs to be consulted with `expectedTastyVersion` + */ + val referenceVersion = "3.8.2-RC1" + + /** Version of the Scala compiler targeted in the current release cycle + * Contains a version without RC/SNAPSHOT/NIGHTLY specific suffixes + * Should be updated ONLY after release or cutoff for previous release cycle. + * + * Should only be referred from `dottyVersion` or settings/tasks requiring simplified version string, + * eg. `compatMode` or Windows native distribution version. + * + * Warning: Change of this variable might require updating `expectedTastyVersion` + */ + val developedVersion = "3.8.3" + + /** The version of the compiler including the RC prefix. + * Defined as common base before calculating environment specific suffixes in `dottyVersion` + * + * By default, during development cycle defined as `${developedVersion}-RC1`; + * During release candidate cycle incremented by the release officer before publishing a subsequent RC version; + * During final, stable release is set exactly to `developedVersion`. + */ + val baseVersion = s"$developedVersion-RC1" + + /** The version of TASTY that should be emitted, checked in runtime test + * For defails on how TASTY version should be set see related discussions: + * - https://github.com/scala/scala3/issues/13447#issuecomment-912447107 + * - https://github.com/scala/scala3/issues/14306#issuecomment-1069333516 + * - https://github.com/scala/scala3/pull/19321 + * + * Simplified rules, given 3.$minor.$patch = $developedVersion + * - Major version is always 28 + * - TASTY minor version: + * - in main (NIGHTLY): {if $patch == 0 || ${referenceVersion.matches(raw"3.$minor.0-RC\d")} then $minor else ${minor + 1}} + * - in release branch is always equal to $minor + * - TASTY experimental version: + * - in main (NIGHTLY) is always experimental + * - in release candidate branch is experimental if {patch == 0} + * - in stable release is always non-experimetnal + */ + val expectedTastyVersion = "28.9-experimental-1" + checkReleasedTastyVersion() + + /** Final version of Scala compiler, controlled by environment variables. */ + val dottyVersion = { + if (isRelease) baseVersion + else if (isNightly) { + val formatter = java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd") + val currentDate = + formatter.format(java.time.ZonedDateTime.now(java.time.ZoneId.of("UTC"))) + s"${baseVersion}-bin-${currentDate}-${VersionUtil.gitHash}-NIGHTLY" + } + else s"${baseVersion}-bin-SNAPSHOT" + } + def isRelease = sys.env.get("RELEASEBUILD").contains("yes") + def isNightly = sys.env.get("NIGHTLYBUILD").contains("yes") + + /** Version calculate for `nonbootstrapped` projects */ + val dottyNonBootstrappedVersion = { + // Make sure sbt always computes the scalaBinaryVersion correctly + val bin = if (!dottyVersion.contains("-bin")) "-bin" else "" + dottyVersion + bin + "-nonbootstrapped" + } + + // LTS or Next + val versionLine = "Next" + + // Versions used by the vscode extension to create a new project + // This should be the latest published releases. + // TODO: Have the vscode extension fetch these numbers from the Internet + // instead of hardcoding them ? + val publishedDottyVersion = referenceVersion + val sbtDottyVersion = "0.5.5" + + /** Minor version against which we check binary compatibility. + * + * This must be the earliest published release in the same versioning line. + * For a developedVersion `3.M.P` the mimaPreviousDottyVersion should be set to: + * - `3.M.0` if `P > 0` + * - `3.(M-1).0` if `P = 0` + */ + val mimaPreviousDottyVersion = "3.7.3" // for 3.8.0, we compare against 3.7.3 + + /** LTS version against which we check binary compatibility. + * + * This must be the earliest published release in the LTS versioning line. + * For example, if the latest LTS release is be 3.3.4, then this must be + * set to 3.3.0. + */ + val mimaPreviousLTSDottyVersion = "3.3.0" + + /** Version of the scala-library for which we will generate TASTy. + * + * We should never use a nightly version here to release. + * + * We can use nightly versions to tests the future compatibility in development. + * Nightly versions: https://scala-ci.typesafe.com/ui/native/scala-integration/org/scala-lang + */ + val stdlibBootstrappedVersion = "2.13.16" + + /** Version of the compatible Scala 2.13 scala-library + * Should be updated when we synchronize with the sources of Scala 2. + * + * This version would be used to fetch sources of Scala 2.13 standard library to be used for patching the Scala 3 standard library. + */ + val scala2Version = "2.13.18" + + /* Tests TASTy version invariants during NIGHLY, RC or Stable releases */ + private def checkReleasedTastyVersion(): Unit = { + case class ScalaVersion(minor: Int, patch: Int, isRC: Boolean) + def parseScalaVersion(version: String): ScalaVersion = version.split("\\.|-").take(4) match { + case Array("3", minor, patch) => ScalaVersion(minor.toInt, patch.toInt, false) + case Array("3", minor, patch, _) => ScalaVersion(minor.toInt, patch.toInt, true) + case other => sys.error(s"Invalid Scala base version string: $baseVersion") + } + lazy val version = parseScalaVersion(baseVersion) + lazy val referenceV = parseScalaVersion(referenceVersion) + lazy val (tastyMinor, tastyIsExperimental) = expectedTastyVersion.split("\\.|-").take(4) match { + case Array("28", minor) => (minor.toInt, false) + case Array("28", minor, "experimental", _) => (minor.toInt, true) + case other => sys.error(s"Invalid TASTy version string: $expectedTastyVersion") + } + + if(isNightly) { + assert(tastyIsExperimental, "TASTY needs to be experimental in nightly builds") + val expectedTastyMinor = version.patch match { + case 0 => version.minor + case 1 if referenceV.patch == 0 && referenceV.isRC => + // Special case for a period when reference version is a new unstable minor + // Needed for non_bootstrapped tests requiring either stable tasty or the same experimental version produced by both reference and bootstrapped compiler + assert(version.minor == referenceV.minor, "Expected reference and base version to use the same minor") + version.minor + case _ => version.minor + 1 + } + assert(tastyMinor == expectedTastyMinor, "Invalid TASTy minor version") + } + + if(isRelease) { + assert(version.minor == tastyMinor, "Minor versions of TASTY vesion and Scala version should match in release builds") + assert(!referenceV.isRC, "Stable release needs to use stable compiler version") + if (version.isRC && version.patch == 0) + assert(tastyIsExperimental, "TASTy should be experimental when releasing a new minor version RC") + else + assert(!tastyIsExperimental, "Stable version cannot use experimental TASTY") + } + } + + val scalaJSVersion = "1.20.1" + + /* The Minimum JVM version the artifact should be able to use */ + val minimumJVMVersion = "17" + + val junitJupiter = "5.11.4" + + val inkuireVersion = "v1.0.0-M9" + + val flexmarkVersion = "0.62.2" + val jacksonVersion = "2.15.1" +} diff --git a/mill.bat b/mill.bat new file mode 100644 index 000000000000..f36d81229704 --- /dev/null +++ b/mill.bat @@ -0,0 +1,299 @@ +@echo off + +rem This is a wrapper script, that automatically selects or downloads Mill from Maven Central or GitHub release pages. +rem +rem This script determines the Mill version to use by trying these sources +rem - env-variable `MILL_VERSION` +rem - local file `.mill-version` +rem - local file `.config/mill-version` +rem - `mill-version` from YAML fronmatter of current buildfile +rem - if accessible, find the latest stable version available on Maven Central (https://repo1.maven.org/maven2) +rem - env-variable `DEFAULT_MILL_VERSION` +rem +rem If a version has the suffix '-native' a native binary will be used. +rem If a version has the suffix '-jvm' an executable jar file will be used, requiring an already installed Java runtime. +rem If no such suffix is found, the script will pick a default based on version and platform. +rem +rem Once a version was determined, it tries to use either +rem - a system-installed mill, if found and it's version matches +rem - an already downloaded version under %USERPROFILE%\.mill\download +rem +rem If no working mill version was found on the system, +rem this script downloads a binary file from Maven Central or Github Pages (this is version dependent) +rem into a cache location (%USERPROFILE%\.mill\download). +rem +rem Mill Project URL: https://github.com/com-lihaoyi/mill +rem Script Version: 1.0.0-M1-21-7b6fae-DIRTY892b63e8 +rem +rem If you want to improve this script, please also contribute your changes back! +rem This script was generated from: dist/scripts/src/mill.bat +rem +rem Licensed under the Apache License, Version 2.0 + +rem setlocal seems to be unavailable on Windows 95/98/ME +rem but I don't think we need to support them in 2019 +setlocal enabledelayedexpansion + +if [!DEFAULT_MILL_VERSION!]==[] ( set "DEFAULT_MILL_VERSION=0.12.10" ) + +if [!MILL_GITHUB_RELEASE_CDN!]==[] ( set "MILL_GITHUB_RELEASE_CDN=" ) + +if [!MILL_MAIN_CLI!]==[] ( set "MILL_MAIN_CLI=%~f0" ) + +set "MILL_REPO_URL=https://github.com/com-lihaoyi/mill" + +SET MILL_BUILD_SCRIPT= + +if exist "build.mill" ( + set MILL_BUILD_SCRIPT=build.mill +) else ( + if exist "build.mill.scala" ( + set MILL_BUILD_SCRIPT=build.mill.scala + ) else ( + if exist "build.sc" ( + set MILL_BUILD_SCRIPT=build.sc + ) else ( + rem no-op + ) + ) +) + +if [!MILL_VERSION!]==[] ( + if exist .mill-version ( + set /p MILL_VERSION=<.mill-version + ) else ( + if exist .config\mill-version ( + set /p MILL_VERSION=<.config\mill-version + ) else ( + if not "%MILL_BUILD_SCRIPT%"=="" ( + for /f "tokens=1-2*" %%a in ('findstr /C:"//| mill-version:" %MILL_BUILD_SCRIPT%') do ( + set "MILL_VERSION=%%c" + ) + ) else ( + rem no-op + ) + ) + ) +) + +if [!MILL_VERSION!]==[] set MILL_VERSION=%DEFAULT_MILL_VERSION% + +if [!MILL_DOWNLOAD_PATH!]==[] set MILL_DOWNLOAD_PATH=%USERPROFILE%\.mill\download + +rem without bat file extension, cmd doesn't seem to be able to run it + +set "MILL_NATIVE_SUFFIX=-native" +set "MILL_JVM_SUFFIX=-jvm" +set "FULL_MILL_VERSION=%MILL_VERSION%" +set "MILL_EXT=.bat" +set "ARTIFACT_SUFFIX=" +REM Check if MILL_VERSION contains MILL_NATIVE_SUFFIX +echo !MILL_VERSION! | findstr /C:"%MILL_NATIVE_SUFFIX%" >nul +if !errorlevel! equ 0 ( + set "MILL_VERSION=%MILL_VERSION:-native=%" + REM -native images compiled with graal do not support windows-arm + REM https://github.com/oracle/graal/issues/9215 + IF /I NOT "%PROCESSOR_ARCHITECTURE%"=="ARM64" ( + set "ARTIFACT_SUFFIX=-native-windows-amd64" + set "MILL_EXT=.exe" + ) else ( + rem no-op + ) +) else ( + echo !MILL_VERSION! | findstr /C:"%MILL_JVM_SUFFIX%" >nul + if !errorlevel! equ 0 ( + set "MILL_VERSION=%MILL_VERSION:-jvm=%" + ) else ( + set "SKIP_VERSION=false" + set "MILL_PREFIX=%MILL_VERSION:~0,4%" + if "!MILL_PREFIX!"=="0.1." set "SKIP_VERSION=true" + if "!MILL_PREFIX!"=="0.2." set "SKIP_VERSION=true" + if "!MILL_PREFIX!"=="0.3." set "SKIP_VERSION=true" + if "!MILL_PREFIX!"=="0.4." set "SKIP_VERSION=true" + if "!MILL_PREFIX!"=="0.5." set "SKIP_VERSION=true" + if "!MILL_PREFIX!"=="0.6." set "SKIP_VERSION=true" + if "!MILL_PREFIX!"=="0.7." set "SKIP_VERSION=true" + if "!MILL_PREFIX!"=="0.8." set "SKIP_VERSION=true" + if "!MILL_PREFIX!"=="0.9." set "SKIP_VERSION=true" + set "MILL_PREFIX=%MILL_VERSION:~0,5%" + if "!MILL_PREFIX!"=="0.10." set "SKIP_VERSION=true" + if "!MILL_PREFIX!"=="0.11." set "SKIP_VERSION=true" + if "!MILL_PREFIX!"=="0.12." set "SKIP_VERSION=true" + + if "!SKIP_VERSION!"=="false" ( + IF /I NOT "%PROCESSOR_ARCHITECTURE%"=="ARM64" ( + set "ARTIFACT_SUFFIX=-native-windows-amd64" + set "MILL_EXT=.exe" + ) + ) else ( + rem no-op + ) + ) +) + +set MILL=%MILL_DOWNLOAD_PATH%\!FULL_MILL_VERSION!!MILL_EXT! + +set MILL_RESOLVE_DOWNLOAD= + +if not exist "%MILL%" ( + set MILL_RESOLVE_DOWNLOAD=true +) else ( + if defined MILL_TEST_DRY_RUN_LAUNCHER_SCRIPT ( + set MILL_RESOLVE_DOWNLOAD=true + ) else ( + rem no-op + ) +) + + +if [!MILL_RESOLVE_DOWNLOAD!]==[true] ( + set MILL_VERSION_PREFIX=%MILL_VERSION:~0,4% + set MILL_SHORT_VERSION_PREFIX=%MILL_VERSION:~0,2% + rem Since 0.5.0 + set MILL_DOWNLOAD_SUFFIX=-assembly + rem Since 0.11.0 + set MILL_DOWNLOAD_FROM_MAVEN=1 + if [!MILL_VERSION_PREFIX!]==[0.0.] ( + set MILL_DOWNLOAD_SUFFIX= + set MILL_DOWNLOAD_FROM_MAVEN=0 + ) + if [!MILL_VERSION_PREFIX!]==[0.1.] ( + set MILL_DOWNLOAD_SUFFIX= + set MILL_DOWNLOAD_FROM_MAVEN=0 + ) + if [!MILL_VERSION_PREFIX!]==[0.2.] ( + set MILL_DOWNLOAD_SUFFIX= + set MILL_DOWNLOAD_FROM_MAVEN=0 + ) + if [!MILL_VERSION_PREFIX!]==[0.3.] ( + set MILL_DOWNLOAD_SUFFIX= + set MILL_DOWNLOAD_FROM_MAVEN=0 + ) + if [!MILL_VERSION_PREFIX!]==[0.4.] ( + set MILL_DOWNLOAD_SUFFIX= + set MILL_DOWNLOAD_FROM_MAVEN=0 + ) + if [!MILL_VERSION_PREFIX!]==[0.5.] set MILL_DOWNLOAD_FROM_MAVEN=0 + if [!MILL_VERSION_PREFIX!]==[0.6.] set MILL_DOWNLOAD_FROM_MAVEN=0 + if [!MILL_VERSION_PREFIX!]==[0.7.] set MILL_DOWNLOAD_FROM_MAVEN=0 + if [!MILL_VERSION_PREFIX!]==[0.8.] set MILL_DOWNLOAD_FROM_MAVEN=0 + if [!MILL_VERSION_PREFIX!]==[0.9.] set MILL_DOWNLOAD_FROM_MAVEN=0 + + set MILL_VERSION_PREFIX=%MILL_VERSION:~0,5% + if [!MILL_VERSION_PREFIX!]==[0.10.] set MILL_DOWNLOAD_FROM_MAVEN=0 + + set MILL_VERSION_PREFIX=%MILL_VERSION:~0,8% + if [!MILL_VERSION_PREFIX!]==[0.11.0-M] set MILL_DOWNLOAD_FROM_MAVEN=0 + + set MILL_VERSION_PREFIX=%MILL_VERSION:~0,5% + set DOWNLOAD_EXT=exe + if [!MILL_SHORT_VERSION_PREFIX!]==[0.] set DOWNLOAD_EXT=jar + if [!MILL_VERSION_PREFIX!]==[0.12.] set DOWNLOAD_EXT=exe + if [!MILL_VERSION!]==[0.12.0] set DOWNLOAD_EXT=jar + if [!MILL_VERSION!]==[0.12.1] set DOWNLOAD_EXT=jar + if [!MILL_VERSION!]==[0.12.2] set DOWNLOAD_EXT=jar + if [!MILL_VERSION!]==[0.12.3] set DOWNLOAD_EXT=jar + if [!MILL_VERSION!]==[0.12.4] set DOWNLOAD_EXT=jar + if [!MILL_VERSION!]==[0.12.5] set DOWNLOAD_EXT=jar + if [!MILL_VERSION!]==[0.12.6] set DOWNLOAD_EXT=jar + if [!MILL_VERSION!]==[0.12.7] set DOWNLOAD_EXT=jar + if [!MILL_VERSION!]==[0.12.8] set DOWNLOAD_EXT=jar + if [!MILL_VERSION!]==[0.12.9] set DOWNLOAD_EXT=jar + if [!MILL_VERSION!]==[0.12.10] set DOWNLOAD_EXT=jar + if [!MILL_VERSION!]==[0.12.11] set DOWNLOAD_EXT=jar + + set MILL_VERSION_PREFIX= + set MILL_SHORT_VERSION_PREFIX= + + for /F "delims=- tokens=1" %%A in ("!MILL_VERSION!") do set MILL_VERSION_BASE=%%A + set MILL_VERSION_MILESTONE= + for /F "delims=- tokens=2" %%A in ("!MILL_VERSION!") do set MILL_VERSION_MILESTONE=%%A + set MILL_VERSION_MILESTONE_START=!MILL_VERSION_MILESTONE:~0,1! + if [!MILL_VERSION_MILESTONE_START!]==[M] ( + set MILL_VERSION_TAG=!MILL_VERSION_BASE!-!MILL_VERSION_MILESTONE! + ) else ( + set MILL_VERSION_TAG=!MILL_VERSION_BASE! + ) + if [!MILL_DOWNLOAD_FROM_MAVEN!]==[1] ( + set MILL_DOWNLOAD_URL=https://repo1.maven.org/maven2/com/lihaoyi/mill-dist!ARTIFACT_SUFFIX!/!MILL_VERSION!/mill-dist!ARTIFACT_SUFFIX!-!MILL_VERSION!.!DOWNLOAD_EXT! + ) else ( + set MILL_DOWNLOAD_URL=!MILL_GITHUB_RELEASE_CDN!%MILL_REPO_URL%/releases/download/!MILL_VERSION_TAG!/!MILL_VERSION!!MILL_DOWNLOAD_SUFFIX! + ) + + if defined MILL_TEST_DRY_RUN_LAUNCHER_SCRIPT ( + echo !MILL_DOWNLOAD_URL! + echo !MILL! + exit /b 0 + ) + + rem there seems to be no way to generate a unique temporary file path (on native Windows) + set MILL_DOWNLOAD_FILE=%MILL%.tmp + + echo Downloading mill !MILL_VERSION! from !MILL_DOWNLOAD_URL! ... 1>&2 + + if not exist "%MILL_DOWNLOAD_PATH%" mkdir "%MILL_DOWNLOAD_PATH%" + rem curl is bundled with recent Windows 10 + rem but I don't think we can expect all the users to have it in 2019 + where /Q curl + if !ERRORLEVEL! EQU 0 ( + curl -f -L "!MILL_DOWNLOAD_URL!" -o "!MILL_DOWNLOAD_FILE!" + ) else ( + rem bitsadmin seems to be available on Windows 7 + rem without /dynamic, github returns 403 + rem bitsadmin is sometimes needlessly slow but it looks better with /priority foreground + bitsadmin /transfer millDownloadJob /dynamic /priority foreground "!MILL_DOWNLOAD_URL!" "!MILL_DOWNLOAD_FILE!" + ) + if not exist "!MILL_DOWNLOAD_FILE!" ( + echo Could not download mill !MILL_VERSION! 1>&2 + exit /b 1 + ) + + move /y "!MILL_DOWNLOAD_FILE!" "%MILL%" + + set MILL_DOWNLOAD_FILE= + set MILL_DOWNLOAD_SUFFIX= +) + +set MILL_DOWNLOAD_PATH= +set MILL_VERSION= +set MILL_REPO_URL= + +rem Need to preserve the first position of those listed options +set MILL_FIRST_ARG= +if [%~1%]==[--bsp] ( + set MILL_FIRST_ARG=%1% +) else ( + if [%~1%]==[-i] ( + set MILL_FIRST_ARG=%1% + ) else ( + if [%~1%]==[--interactive] ( + set MILL_FIRST_ARG=%1% + ) else ( + if [%~1%]==[--no-server] ( + set MILL_FIRST_ARG=%1% + ) else ( + if [%~1%]==[--no-daemon] ( + set MILL_FIRST_ARG=%1% + ) else ( + if [%~1%]==[--repl] ( + set MILL_FIRST_ARG=%1% + ) else ( + if [%~1%]==[--help] ( + set MILL_FIRST_ARG=%1% + ) + ) + ) + ) + ) + ) +) +set "MILL_PARAMS=%*%" + +if not [!MILL_FIRST_ARG!]==[] ( + for /f "tokens=1*" %%a in ("%*") do ( + set "MILL_PARAMS=%%b" + ) +) + +rem -D mill.main.cli is for compatibility with Mill 0.10.9 - 0.13.0-M2 +"%MILL%" %MILL_FIRST_ARG% -D "mill.main.cli=%MILL_MAIN_CLI%" %MILL_PARAMS% diff --git a/presentation-compiler-testcases/package.mill b/presentation-compiler-testcases/package.mill new file mode 100644 index 000000000000..7f397ba0cf31 --- /dev/null +++ b/presentation-compiler-testcases/package.mill @@ -0,0 +1,23 @@ +package build.`presentation-compiler-testcases` + +import scala3build.* + +import mill.* + +trait PresentationCompilerTestcases extends CrossScala3Module with mill.scala3build.CompileClassesPathHelper { + def moduleDeps = super.moduleDeps ++ Seq( + build.compiler(crossValue) + ) + def src = Task.Source("src") + def sources = Task { + Seq(src()) + } + + def precomputedRunClasspath: T[Seq[String]] = + Task { + resolvedRunMvnDeps().map(_.path.toString) ++ + Seq(compileClassesPath0.toString) + } +} + +object `package` extends Cross[PresentationCompilerTestcases](BuildType.allValues) diff --git a/presentation-compiler/package.mill b/presentation-compiler/package.mill new file mode 100644 index 000000000000..fcddba224cd3 --- /dev/null +++ b/presentation-compiler/package.mill @@ -0,0 +1,132 @@ +package build.`presentation-compiler` + +import scala3build.* + +import mill.* +import mill.scalalib.* + +trait PresentationCompiler extends CrossScala3Module { + def moduleDeps = super.moduleDeps ++ Seq( + build.compiler(crossValue) + ) + + def libraryPrecomputedRunClasspath: T[Seq[String]] = + build.library(crossValue).precomputedRunClasspath + def pcTestCasesPrecomputedJarPath: T[String] = + (build.`presentation-compiler-testcases`(crossValue).jarPath / "out.jar").toString + def libraryRunClasspath: T[Seq[PathRef]] = + Task { + build.library(crossValue) + .runClasspath() + .filter(ref => os.exists(ref.path)) + } + def pcTestCasesJar: T[PathRef] = + build.`presentation-compiler-testcases`(crossValue).jar + + def buildInfoSources = Task { + val dir = Task.dest + os.write( + dir / "dotty/tools/pc/buildinfo/BuildInfo.scala", + s"""package dotty.tools.pc.buildinfo + | + |object BuildInfo { + | def scalaVersion = "${scalaVersion()}" + |} + |""".stripMargin, + createFolders = true + ) + PathRef(dir) + } + + def mtagsVersion = "1.6.3" + + def src = Task.Source("src") + def testSrc = Task.Source("test") + + def mtagsSharedSources = Task { + val srcPath = defaultResolver().classpath( + Seq(mvn"org.scalameta:mtags-shared_${Versions.scala2Version}:$mtagsVersion".dep.withTransitive(false)), + sources = true + ) + assert(srcPath.length == 1, "Expected a single source JAR") + val srcJar = srcPath.head.path + os.unzip(srcJar, Task.dest) + + for (f <- os.walk(Task.dest) if os.isFile(f) && f.last.endsWith(".scala")) { + val lines = os.read.lines(f).toList + val updatedLines = Shading.insertUnsafeNullsImport(Shading.replaceProtobuf(lines)) + os.write.over(f, updatedLines.mkString(System.lineSeparator())) + } + + PathRef(Task.dest) + } + + def sources = Task { + Seq(src(), mtagsSharedSources(), buildInfoSources()) + } + + def mvnDeps = super.mvnDeps() ++ Seq( + mvn"org.lz4:lz4-java:1.8.0", + mvn"io.get-coursier:interface:1.0.28", + mvn"org.scalameta:mtags-interfaces:$mtagsVersion", + mvn"com.google.guava:guava:33.2.1-jre" + ) + def scalacOptions = super.scalacOptions() ++ Seq( + "-source", "3.3", // Original comment: To avoid fatal migration warnings + "-Yexplicit-nulls", "-Wsafe-init" + ) + + object test extends Scala3Tests { + def moduleDeps = super.moduleDeps ++ Seq( + build.`presentation-compiler-testcases`(crossValue) + ) + def testSources = Task { + Seq(testSrc(), testBuildInfoSources()) + } + + def testBuildInfoSources = Task { + val dir = Task.dest + val tq = "\"\"\"" + // TODO Add scala3-presentation-compiler-testcases cp too + val testDepCp = libraryPrecomputedRunClasspath() :+ pcTestCasesPrecomputedJarPath() + os.write( + dir / "dotty/tools/pc/tests/buildinfo/BuildInfo.scala", + s"""package dotty.tools.pc.tests.buildinfo + | + |import java.io.File + | + |object BuildInfo { + | def scalaVersion = "${scalaVersion()}" + | def ideTestsDependencyClasspath = ${testDepCp.map(p => s"new File($tq$p$tq)").mkString("Seq[File](", ", ", ")")} + |} + |""".stripMargin, + createFolders = true + ) + PathRef(dir) + } + + def mvnDeps = super.mvnDeps() ++ Seq( + mvn"org.hamcrest:hamcrest-core:1.3" + ) + + def runClasspath = Task { + val precomputedLibrary = libraryPrecomputedRunClasspath().map(os.Path(_)) + val actualLibrary = libraryRunClasspath().map(_.path) + if (precomputedLibrary.filter(os.exists) != actualLibrary.filter(os.exists)) + sys.error( + "Warning: standard library class path calculation mismatch: " + + s"pre-computed value $precomputedLibrary differs from actual value $actualLibrary." + ) + val precomputedPcTestCases = os.Path(pcTestCasesPrecomputedJarPath()) + val actualPcTestCases = pcTestCasesJar().path + if (precomputedPcTestCases != actualPcTestCases) + sys.error( + "Warning: PC testcases class path calculation mismatch: " + + s"pre-computed value $precomputedPcTestCases differs from actual value $actualPcTestCases." + ) + super.runClasspath() + } + } +} + +object `package` extends Cross[PresentationCompiler](BuildType.allValues) diff --git a/project/Build.scala b/project/Build.scala index 8dc83ec3a3fe..33618d4dcb0a 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -59,7 +59,7 @@ object Build { * * Warning: Change of this variable needs to be consulted with `expectedTastyVersion` */ - val referenceVersion = "3.8.2-RC1" + val referenceVersion = "3.8.2-RC1" // Redefined in mill-build/src/scala3build/Versions.scala /** Version of the Scala compiler targeted in the current release cycle * Contains a version without RC/SNAPSHOT/NIGHTLY specific suffixes @@ -70,7 +70,7 @@ object Build { * * Warning: Change of this variable might require updating `expectedTastyVersion` */ - val developedVersion = "3.8.3" + val developedVersion = "3.8.3" // Redefined in mill-build/src/scala3build/Versions.scala /** The version of the compiler including the RC prefix. * Defined as common base before calculating environment specific suffixes in `dottyVersion` @@ -79,7 +79,7 @@ object Build { * During release candidate cycle incremented by the release officer before publishing a subsequent RC version; * During final, stable release is set exactly to `developedVersion`. */ - val baseVersion = s"$developedVersion-RC1" + val baseVersion = s"$developedVersion-RC1" // Redefined in mill-build/src/scala3build/Versions.scala /** The version of TASTY that should be emitted, checked in runtime test * For defails on how TASTY version should be set see related discussions: @@ -97,7 +97,7 @@ object Build { * - in release candidate branch is experimental if {patch == 0} * - in stable release is always non-experimetnal */ - val expectedTastyVersion = "28.9-experimental-1" + val expectedTastyVersion = "28.9-experimental-1" // Redefined in mill-build/src/scala3build/Versions.scala checkReleasedTastyVersion() /** Final version of Scala compiler, controlled by environment variables. */ @@ -1075,8 +1075,8 @@ object Build { "com.lihaoyi" %% "fansi" % "0.5.1", "com.lihaoyi" %% "sourcecode" % "0.4.4", "com.github.sbt" % "junit-interface" % "0.13.3" % Test, - "io.get-coursier" % "interface" % "1.0.28", // used by the REPL for dependency resolution - "org.virtuslab" % "using_directives" % "1.1.4", // used by the REPL for parsing magic comments + "io.get-coursier" % "interface" % "1.0.28", // used by the REPL for dependency resolution // copied in build.mill + "org.virtuslab" % "using_directives" % "1.1.4", // used by the REPL for parsing magic comments // copied in build.mill ), // Configure to use the non-bootstrapped compiler bootstrappedScalaInstanceSettings, @@ -1354,9 +1354,9 @@ object Build { // More info here: https://github.com/scala-js/scala-js/issues/5217 Compile / packageBin / mappings := { (Compile / packageBin / mappings).value.filter(file => - file._2.endsWith(".sjsir") - || file._2.endsWith("UnitOps.tasty") || file._2.endsWith("UnitOps.class") || file._2.endsWith("UnitOps$.class") - || file._2.endsWith("AnonFunctionXXL.tasty") || file._2.endsWith("AnonFunctionXXL.class")) + file._2.endsWith(".sjsir") // copied in build.mill + || file._2.endsWith("UnitOps.tasty") || file._2.endsWith("UnitOps.class") || file._2.endsWith("UnitOps$.class") // copied in build.mill + || file._2.endsWith("AnonFunctionXXL.tasty") || file._2.endsWith("AnonFunctionXXL.class")) // copied in build.mill }, libraryDependencies += ("org.scala-js" %% "scalajs-library" % scalaJSVersion % Provided).cross(CrossVersion.for3Use2_13), libraryDependencies += ("org.scala-js" % "scalajs-javalib" % scalaJSVersion), @@ -1528,9 +1528,9 @@ object Build { // All the dependencies needed by the compiler libraryDependencies ++= Seq( "com.github.sbt" % "junit-interface" % "0.13.3" % Test, - "org.scala-lang.modules" % "scala-asm" % "9.9.0-scala-1", + "org.scala-lang.modules" % "scala-asm" % "9.9.0-scala-1", // Copied in Mill build Dependencies.compilerInterface, - ("io.get-coursier" %% "coursier" % "2.0.16" % Test).cross(CrossVersion.for3Use2_13), + ("io.get-coursier" %% "coursier" % "2.0.16" % Test).cross(CrossVersion.for3Use2_13), // Copied in Mill build ), // Specify the default entry point of the compiler Compile / mainClass := Some("dotty.tools.dotc.Main"), @@ -1657,10 +1657,10 @@ object Build { Test / unmanagedResourceDirectories += baseDirectory.value / "test-resources", // All the dependencies needed by the compiler libraryDependencies ++= Seq( - "org.scala-lang.modules" % "scala-asm" % "9.9.0-scala-1", + "org.scala-lang.modules" % "scala-asm" % "9.9.0-scala-1", // Copied in Mill build Dependencies.compilerInterface, "com.github.sbt" % "junit-interface" % "0.13.3" % Test, - ("io.get-coursier" %% "coursier" % "2.0.16" % Test).cross(CrossVersion.for3Use2_13), + ("io.get-coursier" %% "coursier" % "2.0.16" % Test).cross(CrossVersion.for3Use2_13), // Copied in Mill build ), // Specify the default entry point of the compiler Compile / mainClass := Some("dotty.tools.dotc.Main"), @@ -1785,8 +1785,8 @@ object Build { Test / unmanagedSourceDirectories := Seq(baseDirectory.value / "test"), // All the dependencies needed by the doctool libraryDependencies ++= Dependencies.flexmarkDeps ++ Seq( - "nl.big-o" % "liqp" % "0.8.2", - "org.jsoup" % "jsoup" % "1.17.2", // Needed to process .html files for static site + "nl.big-o" % "liqp" % "0.8.2", // copied in build.mill + "org.jsoup" % "jsoup" % "1.17.2", // Needed to process .html files for static site // copied in build.mill Dependencies.`jackson-dataformat-yaml`, "com.github.sbt" % "junit-interface" % "0.13.3" % Test, ), @@ -2051,15 +2051,15 @@ object Build { BuildInfoPlugin.buildInfoDefaultSettings lazy val presentationCompilerSettings = { - val mtagsVersion = "1.6.3" + val mtagsVersion = "1.6.3" // copied in Plasmon build.mill Seq( libraryDependencies ++= Seq( - "org.lz4" % "lz4-java" % "1.8.0", - "io.get-coursier" % "interface" % "1.0.18", + "org.lz4" % "lz4-java" % "1.8.0", // copied in build.mill + "io.get-coursier" % "interface" % "1.0.18", // copied in build.mill "org.scalameta" % "mtags-interfaces" % mtagsVersion, - "com.google.guava" % "guava" % "33.2.1-jre", + "com.google.guava" % "guava" % "33.2.1-jre", // copied in build.mill ), - libraryDependencies += ("org.scalameta" % s"mtags-shared_${ScalaLibraryPlugin.scala2Version}" % mtagsVersion % SourceDeps), + libraryDependencies += ("org.scalameta" % s"mtags-shared_${ScalaLibraryPlugin.scala2Version}" % mtagsVersion % SourceDeps), // copied in build.mill ivyConfigurations += SourceDeps.hide, transitiveClassifiers := Seq("sources"), publishLocal := publishLocal.dependsOn( // It is best to publish all together. It is not rare to make changes in both compiler / presentation compiler and it can get misaligned @@ -2113,11 +2113,11 @@ object Build { settings(commonBootstrappedSettings). settings( libraryDependencies ++= Seq( - "org.eclipse.lsp4j" % "org.eclipse.lsp4j" % "0.6.0", + "org.eclipse.lsp4j" % "org.eclipse.lsp4j" % "0.6.0", // copied in Mill Dependencies.`jackson-databind` ), // Work around https://github.com/eclipse/lsp4j/issues/295 - dependencyOverrides += "org.eclipse.xtend" % "org.eclipse.xtend.lib" % "2.16.0", + dependencyOverrides += "org.eclipse.xtend" % "org.eclipse.xtend.lib" % "2.16.0", // copied in Mill // Exclude the dependency that is resolved transively, the stdlib // is a project dependency instead excludeDependencies += "org.scala-lang" %% "scala3-library", @@ -2324,35 +2324,35 @@ object Build { else (dir / subdir ** "*.scala").get ( - (dir / "shared/src/test/scala" ** (("*.scala": FileFilter) - -- "ReflectiveCallTest.scala" // uses many forms of structural calls that are not allowed in Scala 3 anymore - -- "UTF16Test.scala" // refutable pattern match - -- "CharsetTest.scala" // bogus @tailrec that Scala 2 ignores but Scala 3 flags as an error - -- "ClassDiffersOnlyInCaseTest.scala" // looks like the Scala 3 compiler itself does not deal with that + (dir / "shared/src/test/scala" ** (("*.scala": FileFilter) // Copied in Mill build + -- "ReflectiveCallTest.scala" // uses many forms of structural calls that are not allowed in Scala 3 anymore // Copied in Mill build + -- "UTF16Test.scala" // refutable pattern match // Copied in Mill build + -- "CharsetTest.scala" // bogus @tailrec that Scala 2 ignores but Scala 3 flags as an error // Copied in Mill build + -- "ClassDiffersOnlyInCaseTest.scala" // looks like the Scala 3 compiler itself does not deal with that // Copied in Mill build )).get - ++ (dir / "shared/src/test/require-sam" ** "*.scala").get - ++ (dir / "shared/src/test/require-jdk8" ** "*.scala").get - ++ (dir / "shared/src/test/require-jdk7" ** "*.scala").get + ++ (dir / "shared/src/test/require-sam" ** "*.scala").get // Copied in Mill build + ++ (dir / "shared/src/test/require-jdk8" ** "*.scala").get // Copied in Mill build + ++ (dir / "shared/src/test/require-jdk7" ** "*.scala").get // Copied in Mill build - ++ (dir / "js/src/test/scala" ** (("*.scala": FileFilter) - -- "StackTraceTest.scala" // would require `npm install source-map-support` - -- "UnionTypeTest.scala" // requires the Scala 2 macro defined in Typechecking*.scala + ++ (dir / "js/src/test/scala" ** (("*.scala": FileFilter) // Copied in Mill build + -- "StackTraceTest.scala" // would require `npm install source-map-support` // Copied in Mill build + -- "UnionTypeTest.scala" // requires the Scala 2 macro defined in Typechecking*.scala // Copied in Mill build )).get - ++ (dir / "js/src/test/require-2.12" ** "*.scala").get - ++ (dir / "js/src/test/require-new-target" ** "*.scala").get - ++ (dir / "js/src/test/require-sam" ** "*.scala").get - ++ (dir / "js/src/test/scala-new-collections" ** "*.scala").get + ++ (dir / "js/src/test/require-2.12" ** "*.scala").get // Copied in Mill build + ++ (dir / "js/src/test/require-new-target" ** "*.scala").get // Copied in Mill build + ++ (dir / "js/src/test/require-sam" ** "*.scala").get // Copied in Mill build + ++ (dir / "js/src/test/scala-new-collections" ** "*.scala").get // Copied in Mill build - ++ conditionally(!hasModules, "js/src/test/require-no-modules") - ++ conditionally(hasModules, "js/src/test/require-modules") - ++ conditionally(hasModules && !linkerConfig.closureCompiler && !isWebAssembly, "js/src/test/require-multi-modules") - ++ conditionally(moduleKind == ModuleKind.ESModule, "js/src/test/require-dynamic-import") - ++ conditionally(moduleKind == ModuleKind.ESModule, "js/src/test/require-esmodule") + ++ conditionally(!hasModules, "js/src/test/require-no-modules") // Copied in Mill build + ++ conditionally(hasModules, "js/src/test/require-modules") // Copied in Mill build + ++ conditionally(hasModules && !linkerConfig.closureCompiler && !isWebAssembly, "js/src/test/require-multi-modules") // Copied in Mill build + ++ conditionally(moduleKind == ModuleKind.ESModule, "js/src/test/require-dynamic-import") // Copied in Mill build + ++ conditionally(moduleKind == ModuleKind.ESModule, "js/src/test/require-esmodule") // Copied in Mill build - ++ conditionally(hasAsyncAwait, "js/src/test/require-async-await") - ++ conditionally(hasAsyncAwait && isWebAssembly, "js/src/test/require-orphan-await") + ++ conditionally(hasAsyncAwait, "js/src/test/require-async-await") // Copied in Mill build + ++ conditionally(hasAsyncAwait && isWebAssembly, "js/src/test/require-orphan-await") // Copied in Mill build ) }, diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 22871db0570a..b8688b153a6c 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -4,14 +4,14 @@ import sbt._ * to ensure the same version of the dependency is used in all projects */ object Dependencies { - private val jacksonVersion = "2.15.1" + private val jacksonVersion = "2.15.1" // copied in build.mill val `jackson-databind` = "com.fasterxml.jackson.core" % "jackson-databind" % jacksonVersion val `jackson-dataformat-yaml` = "com.fasterxml.jackson.dataformat" % "jackson-dataformat-yaml" % jacksonVersion // Freeze on 0.62.x as 0.64.0 requires Java 11 - private val flexmarkVersion = "0.62.2" + private val flexmarkVersion = "0.62.2" // copied in build.mill val flexmarkDeps = Seq( "com.vladsch.flexmark" % "flexmark" % flexmarkVersion, @@ -28,5 +28,5 @@ object Dependencies { "com.vladsch.flexmark" % "flexmark-ext-yaml-front-matter" % flexmarkVersion, ) - val compilerInterface = "org.scala-sbt" % "compiler-interface" % "1.10.7" + val compilerInterface = "org.scala-sbt" % "compiler-interface" % "1.10.7" // copied in build.mill } diff --git a/project/ScalaLibraryPlugin.scala b/project/ScalaLibraryPlugin.scala index c37a6dae6c3f..b723b10f7512 100644 --- a/project/ScalaLibraryPlugin.scala +++ b/project/ScalaLibraryPlugin.scala @@ -358,55 +358,55 @@ object ScalaLibraryPlugin extends AutoPlugin { } private lazy val filesToCopy = Set( - "scala/Function0", - "scala/Function1", - "scala/Function2", - "scala/Product1", - "scala/Product2", - "scala/Tuple1", - "scala/Tuple2", - "scala/collection/ArrayOps", - "scala/collection/Stepper", - "scala/collection/DoubleStepper", - "scala/collection/IntStepper", - "scala/collection/LongStepper", - "scala/collection/immutable/DoubleVectorStepper", - "scala/collection/immutable/IntVectorStepper", - "scala/collection/immutable/LongVectorStepper", - "scala/collection/immutable/Range", - "scala/jdk/Accumulator", - "scala/jdk/DoubleAccumulator", - "scala/jdk/IntAccumulator", - "scala/jdk/LongAccumulator", - "scala/jdk/FunctionWrappers$FromJavaDoubleBinaryOperator", - "scala/jdk/FunctionWrappers$FromJavaBooleanSupplier", - "scala/jdk/FunctionWrappers$FromJavaDoubleConsumer", - "scala/jdk/FunctionWrappers$FromJavaDoublePredicate", - "scala/jdk/FunctionWrappers$FromJavaDoubleSupplier", - "scala/jdk/FunctionWrappers$FromJavaDoubleToIntFunction", - "scala/jdk/FunctionWrappers$FromJavaDoubleToLongFunction", - "scala/jdk/FunctionWrappers$FromJavaIntBinaryOperator", - "scala/jdk/FunctionWrappers$FromJavaDoubleUnaryOperator", - "scala/jdk/FunctionWrappers$FromJavaIntPredicate", - "scala/jdk/FunctionWrappers$FromJavaIntConsumer", - "scala/jdk/FunctionWrappers$FromJavaIntSupplier", - "scala/jdk/FunctionWrappers$FromJavaIntToDoubleFunction", - "scala/jdk/FunctionWrappers$FromJavaIntToLongFunction", - "scala/jdk/FunctionWrappers$FromJavaIntUnaryOperator", - "scala/jdk/FunctionWrappers$FromJavaLongBinaryOperator", - "scala/jdk/FunctionWrappers$FromJavaLongConsumer", - "scala/jdk/FunctionWrappers$FromJavaLongPredicate", - "scala/jdk/FunctionWrappers$FromJavaLongSupplier", - "scala/jdk/FunctionWrappers$FromJavaLongToDoubleFunction", - "scala/jdk/FunctionWrappers$FromJavaLongToIntFunction", - "scala/jdk/FunctionWrappers$FromJavaLongUnaryOperator", - "scala/runtime/AbstractFunction0", - "scala/runtime/AbstractFunction1", - "scala/runtime/AbstractFunction2", - "scala/runtime/AbstractPartialFunction", - "scala/runtime/NonLocalReturnControl", - "scala/util/Sorting", - "scala/util/hashing/MurmurHash3", + "scala/Function0", // copied in Mill build + "scala/Function1", // copied in Mill build + "scala/Function2", // copied in Mill build + "scala/Product1", // copied in Mill build + "scala/Product2", // copied in Mill build + "scala/Tuple1", // copied in Mill build + "scala/Tuple2", // copied in Mill build + "scala/collection/ArrayOps", // copied in Mill build + "scala/collection/Stepper", // copied in Mill build + "scala/collection/DoubleStepper", // copied in Mill build + "scala/collection/IntStepper", // copied in Mill build + "scala/collection/LongStepper", // copied in Mill build + "scala/collection/immutable/DoubleVectorStepper", // copied in Mill build + "scala/collection/immutable/IntVectorStepper", // copied in Mill build + "scala/collection/immutable/LongVectorStepper", // copied in Mill build + "scala/collection/immutable/Range", // copied in Mill build + "scala/jdk/Accumulator", // copied in Mill build + "scala/jdk/DoubleAccumulator", // copied in Mill build + "scala/jdk/IntAccumulator", // copied in Mill build + "scala/jdk/LongAccumulator", // copied in Mill build + "scala/jdk/FunctionWrappers$FromJavaDoubleBinaryOperator", // copied in Mill build + "scala/jdk/FunctionWrappers$FromJavaBooleanSupplier", // copied in Mill build + "scala/jdk/FunctionWrappers$FromJavaDoubleConsumer", // copied in Mill build + "scala/jdk/FunctionWrappers$FromJavaDoublePredicate", // copied in Mill build + "scala/jdk/FunctionWrappers$FromJavaDoubleSupplier", // copied in Mill build + "scala/jdk/FunctionWrappers$FromJavaDoubleToIntFunction", // copied in Mill build + "scala/jdk/FunctionWrappers$FromJavaDoubleToLongFunction", // copied in Mill build + "scala/jdk/FunctionWrappers$FromJavaIntBinaryOperator", // copied in Mill build + "scala/jdk/FunctionWrappers$FromJavaDoubleUnaryOperator", // copied in Mill build + "scala/jdk/FunctionWrappers$FromJavaIntPredicate", // copied in Mill build + "scala/jdk/FunctionWrappers$FromJavaIntConsumer", // copied in Mill build + "scala/jdk/FunctionWrappers$FromJavaIntSupplier", // copied in Mill build + "scala/jdk/FunctionWrappers$FromJavaIntToDoubleFunction", // copied in Mill build + "scala/jdk/FunctionWrappers$FromJavaIntToLongFunction", // copied in Mill build + "scala/jdk/FunctionWrappers$FromJavaIntUnaryOperator", // copied in Mill build + "scala/jdk/FunctionWrappers$FromJavaLongBinaryOperator", // copied in Mill build + "scala/jdk/FunctionWrappers$FromJavaLongConsumer", // copied in Mill build + "scala/jdk/FunctionWrappers$FromJavaLongPredicate", // copied in Mill build + "scala/jdk/FunctionWrappers$FromJavaLongSupplier", // copied in Mill build + "scala/jdk/FunctionWrappers$FromJavaLongToDoubleFunction", // copied in Mill build + "scala/jdk/FunctionWrappers$FromJavaLongToIntFunction", // copied in Mill build + "scala/jdk/FunctionWrappers$FromJavaLongUnaryOperator", // copied in Mill build + "scala/runtime/AbstractFunction0", // copied in Mill build + "scala/runtime/AbstractFunction1", // copied in Mill build + "scala/runtime/AbstractFunction2", // copied in Mill build + "scala/runtime/AbstractPartialFunction", // copied in Mill build + "scala/runtime/NonLocalReturnControl", // copied in Mill build + "scala/util/Sorting", // copied in Mill build + "scala/util/hashing/MurmurHash3", // copied in Mill build ) /** Extract the SourceFile attribute from class file bytecode */ diff --git a/project/scripts/mill-repl-test.expect b/project/scripts/mill-repl-test.expect new file mode 100755 index 000000000000..bb644263ef2b --- /dev/null +++ b/project/scripts/mill-repl-test.expect @@ -0,0 +1,9 @@ +#!/usr/bin/expect -f +set timeout 10 +spawn ./mill -i "repl\[final\]" -color never +expect "scala>" { send "2\n" } +expect "val res0: Int = 2" { } +expect "scala>" { send ":q\n" } +expect eof +catch wait result +exit [lindex $result 3] diff --git a/repl/package.mill b/repl/package.mill new file mode 100644 index 000000000000..9a17e432db60 --- /dev/null +++ b/repl/package.mill @@ -0,0 +1,64 @@ +package build.repl + +import scala3build.* + +import mill.* +import mill.scalalib.* + +trait Repl extends CrossScala3Module { outer => + def src = Task.Source("src") + def testSrc = Task.Source("test") + def testResources = Task.Source("test-resources") + + def sources = Task { + Seq(src()) + } + def moduleDeps = super.moduleDeps ++ Seq( + build.compiler(crossValue) + ) + def mvnDeps = super.mvnDeps() ++ Seq( + mvn"org.jline:jline-reader:3.29.0", + mvn"org.jline:jline-terminal:3.29.0", + mvn"org.jline:jline-terminal-jni:3.29.0", + mvn"com.lihaoyi::pprint:0.9.3".exclude(("org.scala-lang", "*")), + mvn"com.lihaoyi::fansi:0.5.1".exclude(("org.scala-lang", "*")), + mvn"com.lihaoyi::sourcecode:0.4.4".exclude(("org.scala-lang", "*")), + mvn"io.get-coursier:interface:1.0.28", // Original comment: used by the REPL for dependency resolution + mvn"org.virtuslab:using_directives:1.1.4" + ) + def defaultTask() = "runRepl" + def runRepl(args: String*) = { + if (!mill.constants.Util.hasConsole()) + sys.error("Refusing to run the REPL without an interactive terminal. Run Mill like './mill -i ...' in order to run the REPL") + run(Task.Anon(mill.api.Args("-usejavacp", args))) + } + object test extends Scala3Tests { + def moduleDeps = super.moduleDeps ++ Seq( + build.compiler(crossValue).test + ) + def testSources = Seq(testSrc()) + def compileResources = + // Getting + // Could not detect the parent class of task repl.bootstrapping.test.compileResources.super.test. Please report this at https://github.com/com-lihaoyi/mill/issues/new/choose . + // when adding + // super[JavaModule].compileResources() ++ + Seq(testResources()) + def mvnDeps = super.mvnDeps() ++ Seq( + mvn"com.github.sbt:junit-interface:0.13.3" + ) + + def replTestForkArgs = Task { + Seq( + s"-Ddotty.tests.classes.dottyRepl=${outer.jar().path}" + ) + } + + def forkArgs: T[Seq[String]] = Task { + super.forkArgs() ++ + build.compiler(crossValue).test.compilerTestForkArgs() ++ + replTestForkArgs() + } + } +} + +object `package` extends Cross[Repl](BuildType.allValues) diff --git a/sandbox/package.mill b/sandbox/package.mill new file mode 100644 index 000000000000..2e6ffb411fef --- /dev/null +++ b/sandbox/package.mill @@ -0,0 +1,27 @@ +package build.sandbox + +import scala3build.* + +import mill.* + +trait SandboxScalajs extends CrossScala3Module with Scala3JsModule { + def moduleDeps = super.moduleDeps ++ Seq( + build.`library-js`(crossValue) + ) + def src = Task.Source("src") + def testSrc = Task.Source("test") + + def sources = Task { + Seq(src()) + } + + def scalaJSUseMainModuleInitializer = true + + object test extends Scala3JsTests { + def testSources = Task { + Seq(testSrc()) + } + } +} + +object scalajs extends Cross[SandboxScalajs](BuildType.allValues) diff --git a/sbt-bridge/package.mill b/sbt-bridge/package.mill new file mode 100644 index 000000000000..1ebb9387a8d4 --- /dev/null +++ b/sbt-bridge/package.mill @@ -0,0 +1,30 @@ +package build.`sbt-bridge` + +import scala3build.* + +import mill.* +import mill.scalalib.* + +trait SbtBridge extends CrossScala3Module { outer => + def moduleDeps = super.moduleDeps ++ Seq( + build.compiler(crossValue) + ) + def sources = Task.Sources("src") + def testSources = Task.Sources("test") + def mvnDeps = super.mvnDeps() ++ Seq( + mvn"org.scala-sbt:compiler-interface:1.10.7" + ) + + object test extends Scala3Tests { + def moduleDeps = super.moduleDeps ++ Seq( + build.`test-helpers`(crossValue) + ) + def testSources = outer.testSources + def mvnDeps = super.mvnDeps() ++ Seq( + mvn"org.scala-sbt:zinc-apiinfo_2.13:1.8.0" + .exclude(("org.scala-lang", "*")) + ) + } +} + +object `package` extends Cross[SbtBridge](BuildType.allValues) diff --git a/sbt-bridge/src/dotty/tools/xsbt/CompilerBridgeDriver.java b/sbt-bridge/src/dotty/tools/xsbt/CompilerBridgeDriver.java index fbbe68235d13..3935dc5bf835 100644 --- a/sbt-bridge/src/dotty/tools/xsbt/CompilerBridgeDriver.java +++ b/sbt-bridge/src/dotty/tools/xsbt/CompilerBridgeDriver.java @@ -61,7 +61,7 @@ public boolean sourcesRequired() { return false; } - private static VirtualFile asVirtualFile(SourceFile sourceFile, DelegatingReporter reporter, + public static VirtualFile asVirtualFile(SourceFile sourceFile, DelegatingReporter reporter, HashMap lookup) { return lookup.computeIfAbsent(sourceFile.file(), path -> { reportMissingFile(reporter, sourceFile); @@ -128,7 +128,7 @@ synchronized public void run( .setIncCallback(incCallback) .setProgressCallback(progressCallback); - Contexts.Context context = setup(args, initialCtx).map(t -> t._2).getOrElse(() -> initialCtx); + Contexts.Context context = setup(args, initialCtx).map(t -> t._2()).getOrElse(() -> initialCtx); if (ScalacCommand.isHelpFlag(context.settings(), context.settingsState())) { throw new InterfaceCompileFailed(args, new Problem[0], StopInfoError); @@ -172,7 +172,7 @@ synchronized public void run( } } - private static AbstractFile asDottyFile(VirtualFile virtualFile) { + public static AbstractFile asDottyFile(VirtualFile virtualFile) { if (virtualFile instanceof PathBasedFile) { java.nio.file.Path path = ((PathBasedFile) virtualFile).toPath(); return new PlainFile(new Path(path)); diff --git a/sbt-bridge/src/xsbt/CachedCompilerImpl.java b/sbt-bridge/src/xsbt/CachedCompilerImpl.java index c9d4c50485ed..ccbce5a0ccf4 100644 --- a/sbt-bridge/src/xsbt/CachedCompilerImpl.java +++ b/sbt-bridge/src/xsbt/CachedCompilerImpl.java @@ -7,16 +7,21 @@ import xsbti.compile.*; import java.io.File; +import java.util.Arrays; +import java.util.HashMap; import dotty.tools.dotc.core.Contexts.Context; import dotty.tools.dotc.core.Contexts.ContextBase; import dotty.tools.dotc.Main; +import dotty.tools.io.AbstractFile; import dotty.tools.xsbt.InterfaceCompileFailed; import dotty.tools.xsbt.DelegatingReporter; import dotty.tools.xsbt.OldIncrementalCallback; import dotty.tools.dotc.sbt.interfaces.IncrementalCallback; +import scala.collection.mutable.ListBuffer; + // deprecation warnings are suppressed because scala3-sbt-bridge must stay compatible with Zinc 1.3 // see https://github.com/scala/scala3/issues/10816 @SuppressWarnings("deprecation") @@ -63,7 +68,70 @@ synchronized public void run(File[] sources, DependencyChanges changes, Analysis return msg; }); - IncrementalCallback incCallback = new OldIncrementalCallback(callback); + ListBuffer sourcesBuffer = new ListBuffer<>(); + HashMap lookup = new HashMap<>(sources.length, 0.25f); + + VirtualFile[] sortedSources = new VirtualFile[sources.length]; + for (int i = 0; i < sources.length; i++) { + final int i0 = i; + sortedSources[i] = new PathBasedFile() { + public java.nio.file.Path toPath() { + return sources[i0].toPath(); + } + public java.io.InputStream input() { + try { + return java.nio.file.Files.newInputStream(toPath()); + } catch (java.io.IOException ex) { + throw new RuntimeException(ex); + } + } + + public long contentHash() { + try { + int murmurHash3 = scala.util.hashing.MurmurHash3$.MODULE$.bytesHash(java.nio.file.Files.readAllBytes(toPath())); + return (long) murmurHash3; + } catch (java.io.IOException ex) { + throw new RuntimeException(ex); + } + } + + public String id() { + return toPath().toString(); + } + public String name() { + return toPath().getFileName().toString(); + } + public String[] names() { + return new String[] { name() }; + } + }; + } + Arrays.sort(sortedSources, (x0, x1) -> x0.id().compareTo(x1.id())); + + for (int i = 0; i < sources.length; i++) { + VirtualFile source = sortedSources[i]; + AbstractFile abstractFile = dotty.tools.xsbt.CompilerBridgeDriver.asDottyFile(source); + sourcesBuffer.append(abstractFile); + lookup.put(abstractFile, source); + } + + DelegatingReporter reporter0 = new DelegatingReporter(delegate, sourceFile -> { + // TODO: possible situation here where we use -from-tasty and TASTy source files but + // the reporter log is associated to a Scala source file? + + // Zinc will use the output of this function to possibly lookup a mapped virtual file, + // e.g. convert `${ROOT}/Foo.scala` to `/path/to/Foo.scala` if it exists in the lookup map. + VirtualFile vf = lookup.get(sourceFile.file()); + if (vf != null) + return vf.id(); + else + // follow Zinc, which uses the path of the source file as a fallback. + return sourceFile.path(); + }); + + IncrementalCallback incCallback = new dotty.tools.xsbt.IncrementalCallback(callback, sourceFile -> + dotty.tools.xsbt.CompilerBridgeDriver.asVirtualFile(sourceFile, reporter0, lookup) + ); Context ctx = new ContextBase().initialCtx().fresh() .setIncCallback(incCallback) diff --git a/sbt-test/package.mill b/sbt-test/package.mill new file mode 100644 index 000000000000..47b19f5c7fd9 --- /dev/null +++ b/sbt-test/package.mill @@ -0,0 +1,13 @@ +package build.`sbt-test` + +import scala3build.* + +import mill.* + +trait Runner extends CrossScala3Module { + def moduleDeps = super.moduleDeps ++ Seq( + build.library(crossValue) + ) +} + +object runner extends Cross[Runner](BuildType.allValues) diff --git a/sbt-test/runner/src/dotty/SbtScriptedTests.scala b/sbt-test/runner/src/dotty/SbtScriptedTests.scala new file mode 100644 index 000000000000..db978a47c34e --- /dev/null +++ b/sbt-test/runner/src/dotty/SbtScriptedTests.scala @@ -0,0 +1,3 @@ +package dotty + +object SbtScriptedTests diff --git a/scaladoc-js/package.mill b/scaladoc-js/package.mill new file mode 100644 index 000000000000..05e9bfc3fe6d --- /dev/null +++ b/scaladoc-js/package.mill @@ -0,0 +1,50 @@ +package build.`scaladoc-js` + +import scala3build.* + +import mill.* +import mill.scalalib.* + +object `package` extends Module { + trait Common extends CrossScala3Module with Scala3JsModule { + def scalaJSVersion = Versions.scalaJSVersion + def moduleDeps = super.moduleDeps ++ Seq( + build.`library-js`(crossValue) + ) + def mvnDeps = super.mvnDeps() ++ Seq( + mvn"org.scala-js::scalajs-dom::2.8.0" + ) + + def css = Task.Source(os.sub / "css") + } + object common extends Cross[Common](BuildType.allValues) + + trait Contributors extends CrossScala3Module with Scala3JsModule { + def scalaJSVersion = Versions.scalaJSVersion + def moduleDeps = super.moduleDeps ++ Seq( + common(crossValue) + ) + def scalaJSUseMainModuleInitializer = true + def css = Task.Source(os.sub / "css") + } + object contributors extends Cross[Contributors](BuildType.allValues) + + trait Main extends CrossScala3Module with Scala3JsModule { + def testSrc = Task.Source("test") + + def scalaJSVersion = Versions.scalaJSVersion + def moduleDeps = super.moduleDeps ++ Seq( + common(crossValue) + ) + def scalaJSUseMainModuleInitializer = true + // scalacOptions -= "-Yexplicit-nulls" + + object test extends Scala3JsTests { + def moduleDeps = super.moduleDeps ++ Seq( + build.`test-helpers`(crossValue) + ) + def testSources = Seq(testSrc()) + } + } + object main extends Cross[Main](BuildType.allValues) +} diff --git a/scaladoc-testcases/package.mill b/scaladoc-testcases/package.mill new file mode 100644 index 000000000000..3a2df2007e6e --- /dev/null +++ b/scaladoc-testcases/package.mill @@ -0,0 +1,16 @@ +package build.`scaladoc-testcases` + +import scala3build.* + +import mill.* + +trait ScaladocTestcases extends CrossScala3Module with mill.scala3build.CompileClassesPathHelper { + def moduleDeps = super.moduleDeps ++ Seq( + build.compiler(crossValue) + ) + def sources = Task(Seq(baseSources())) + + def baseSources = Task.Source("src") +} + +object `package` extends Cross[ScaladocTestcases](BuildType.allValues) diff --git a/scaladoc/package.mill b/scaladoc/package.mill new file mode 100644 index 000000000000..901b0aae1f68 --- /dev/null +++ b/scaladoc/package.mill @@ -0,0 +1,165 @@ +package build.scaladoc + +import scala3build.* + +import mill.* +import mill.api.BuildCtx +import mill.scalalib.* + +trait Scaladoc extends CrossScala3Module { scaladoc => + def moduleDeps = super.moduleDeps ++ Seq( + build.compiler(crossValue), + build.`tasty-inspector`(crossValue), + ) + + def src = Task.Source("src") + def testSrc = Task.Source("test") + + def `test-documentations` = Task.Source("test-documentations") + + def buildInfoSources = Task { + val dir = Task.dest + os.write( + dir / "dotty/tools/scaladoc/BuildInfo.scala", + s"""package dotty.tools.scaladoc + | + |object BuildInfo { + | def version = "${Versions.dottyVersion}" + |} + |""".stripMargin, + createFolders = true + ) + PathRef(dir) + } + + def sources = Task { + Seq(src(), buildInfoSources()) + } + def testSources = Task { + Seq(testSrc(), buildInfoTestSources()) + } + def scalacOptions = super.scalacOptions() ++ Seq( + "-experimental" + ) + def mvnDeps = super.mvnDeps() ++ Seq( + mvn"com.vladsch.flexmark:flexmark:${Versions.flexmarkVersion}", + mvn"com.vladsch.flexmark:flexmark-util-ast:${Versions.flexmarkVersion}", + mvn"com.vladsch.flexmark:flexmark-util-data:${Versions.flexmarkVersion}", + mvn"com.vladsch.flexmark:flexmark-util-html:${Versions.flexmarkVersion}", + mvn"com.vladsch.flexmark:flexmark-ext-anchorlink:${Versions.flexmarkVersion}", + mvn"com.vladsch.flexmark:flexmark-ext-autolink:${Versions.flexmarkVersion}", + mvn"com.vladsch.flexmark:flexmark-ext-emoji:${Versions.flexmarkVersion}", + mvn"com.vladsch.flexmark:flexmark-ext-gfm-strikethrough:${Versions.flexmarkVersion}", + mvn"com.vladsch.flexmark:flexmark-ext-gfm-tasklist:${Versions.flexmarkVersion}", + mvn"com.vladsch.flexmark:flexmark-ext-wikilink:${Versions.flexmarkVersion}", + mvn"com.vladsch.flexmark:flexmark-ext-tables:${Versions.flexmarkVersion}", + mvn"com.vladsch.flexmark:flexmark-ext-yaml-front-matter:${Versions.flexmarkVersion}", + mvn"nl.big-o:liqp:0.8.2", + mvn"org.jsoup:jsoup:1.17.2", // Original comment: Needed to process .html files for static site + mvn"com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:${Versions.jacksonVersion}" + ) + def mainClass = Some("dotty.tools.scaladoc.Main") + + def scaladocTestcasesCompile: T[mill.javalib.api.CompilationResult] = + build.`scaladoc-testcases`(crossValue).compile + def scaladocTestcasesCompileClassesPath: os.Path = + build.`scaladoc-testcases`(crossValue).compileClassesPath0 + def buildInfoTestSources = Task { + val dir = Task.dest + val tq = "\"\"\"" + os.write( + dir / "dotty/tools/scaladoc/test/BuildInfo.scala", + s"""package dotty.tools.scaladoc.test + | + |object BuildInfo { + | def test_testcasesOutputDir: Seq[String] = Seq($tq$scaladocTestcasesCompileClassesPath$tq) + | def test_testcasesSourceRoot: String = $tq${build.`scaladoc-testcases`(crossValue).baseSources().path}$tq + | def testDocumentationRoot: String = $tq${`test-documentations`().path}$tq + |} + |""".stripMargin, + createFolders = true + ) + PathRef(dir) + } + + def mainResourceDir = Task.Source(os.sub / "resources") + + def bundleCssResources = Task { + val cssSubDir = os.sub / "dotty_res/styles/theme" + val cssDir = mainResourceDir().path / cssSubDir + + for (dir <- os.walk(cssDir, includeTarget = true).filter(os.isDir)) { + val subPath = dir.subRelativeTo(cssDir) + val cssFiles = os.list(dir).filter(_.last.endsWith(".css")).filter(os.isFile) + val bundleContent = geny.Generator.from(cssFiles).map(os.read.bytes) + val bundlePath = Task.dest / cssSubDir / subPath / "bundle.css" + os.write(bundlePath, bundleContent, createFolders = true) + } + + PathRef(Task.dest) + } + + def generateScaladocTestCasesAssets() = Task.Command { + BuildCtx.withFilesystemCheckerDisabled { + DocumentationWebsite.generateScaladocTestCasesAssets( + BuildCtx.workspaceRoot, + build.`scaladoc-js`.contributors(crossValue).fullLinkJS().dest.path, + build.`scaladoc-js`.contributors(crossValue).css().path + ) + } + } + + def websiteAssets = Task { + val dest = Task.dest + DocumentationWebsite.generateStaticAssets( + BuildCtx.workspaceRoot, + build.`scaladoc-js`.contributors(crossValue).fullLinkJS().dest.path, + build.`scaladoc-js`.main(crossValue).fullLinkJS().dest.path, + build.`scaladoc-js`.contributors(crossValue).css().path, + build.`scaladoc-js`.common(crossValue).css().path, + dest + ) + PathRef(dest) + } + + def resources = Task { + generateScaladocTestCasesAssets() + + super.resources() ++ Seq( + bundleCssResources(), + websiteAssets() + ) + } + + object test extends Scala3Tests { + def moduleDeps = super.moduleDeps ++ Seq( + build.`test-helpers`(crossValue) + ) + def testSources = scaladoc.testSources + def runClasspath = Task { + // Ensure scaladoc-testcases is compiled, so that the directory + // we refer to via scaladocTestcasesCompileClassesPath exists + val fromCompile = scaladocTestcasesCompile().classes.path + val beforeCompile = scaladocTestcasesCompileClassesPath + if (fromCompile != beforeCompile) + sys.error( + "Warning: scaladoc-testcases compilation output dir calculation mismatch: " + + s"pre-computed value $beforeCompile differs from actual value $fromCompile." + ) + super.runClasspath() + } + } + + def testSourceLinksSrc = Task.Source("test-source-links") + object `test-source-links` extends Scala3Tests { + def moduleDeps = super.moduleDeps ++ Seq( + build.scaladoc(crossValue).test, + build.`test-helpers`(crossValue) + ) + def testSources = Task { + Seq(testSourceLinksSrc()) + } + } +} + +object `package` extends Cross[Scaladoc](BuildType.allValues) diff --git a/sjs-compiler-tests/package.mill b/sjs-compiler-tests/package.mill new file mode 100644 index 000000000000..ecfd7b07d8d1 --- /dev/null +++ b/sjs-compiler-tests/package.mill @@ -0,0 +1,48 @@ +package build.`sjs-compiler-tests` + +import scala3build.* + +import mill.* +import mill.scalalib.* + +trait SjsCompilerTests extends CrossScala3Module { + + def testSrc = Task.Source("test/scala") + + object test extends Scala3Tests { + def moduleDeps = super.moduleDeps ++ Seq( + build.compiler(crossValue).test + ) + def testSources = Task { + Seq(testSrc()) + } + def mvnDeps = super.mvnDeps() ++ Seq( + mvn"org.scala-js:scalajs-linker_2.13:${Versions.scalaJSVersion}", + mvn"org.scala-js:scalajs-env-nodejs_2.13:1.3.0" + ) + def testSandboxWorkingDir = false + def forkArgs = Task { + + val libraryJsClassPath = build.`library-js`(crossValue).compileClasspath().map(_.path) + def libraryJsArtifactFor(moduleName: String): os.Path = + libraryJsClassPath + .iterator + .filter(os.isFile) + .filter(_.last.endsWith(".jar")) + .find(_.last.startsWith(moduleName + "-")) + .getOrElse { + sys.error(s"No JAR found for $moduleName in library-js class path $libraryJsClassPath") + } + + super.forkArgs() ++ + build.compiler(crossValue).test.compilerTestForkArgs() ++ + Seq( + s"-Ddotty.tests.classes.scalaJSScalalib=${build.`library-js`(crossValue).jar().path}", + s"-Ddotty.tests.classes.scalaJSJavalib=${libraryJsArtifactFor("scalajs-javalib")}", + s"-Ddotty.tests.classes.scalaJSLibrary=${libraryJsArtifactFor("scalajs-library_2.13")}" + ) + } + } +} + +object `package` extends Cross[SjsCompilerTests](BuildType.allValues) diff --git a/staging/package.mill b/staging/package.mill new file mode 100644 index 000000000000..aca657edf7f1 --- /dev/null +++ b/staging/package.mill @@ -0,0 +1,30 @@ +package build.staging + +import scala3build.* + +import mill.* + +trait Staging extends CrossScala3Module { outer => + def moduleDeps = super.moduleDeps ++ Seq(build.compiler(crossValue)) + + def testSources = Task.Sources("test") + def testResources = Task.Sources("test-resources") + + object test extends Scala3Tests { + def moduleDeps = super.moduleDeps ++ Seq( + // not sure why, the sbt build only has a dependency on the compiler tests here + // but depending on the repl tests is required for compilation for us + build.repl(crossValue).test + ) + def testSources = outer.testSources + def resources = super.resources() ++ testResources() + + def forkArgs: T[Seq[String]] = Task { + super.forkArgs() ++ + build.compiler(crossValue).test.compilerTestForkArgs() ++ + build.repl(crossValue).test.replTestForkArgs() + } + } +} + +object `package` extends Cross[Staging](BuildType.allValues) diff --git a/tasty-inspector/package.mill b/tasty-inspector/package.mill new file mode 100644 index 000000000000..e5403471cab2 --- /dev/null +++ b/tasty-inspector/package.mill @@ -0,0 +1,14 @@ +package build.`tasty-inspector` + +import scala3build.* + +import mill.* + +trait TastyInspector extends CrossScala3Module { + def compileModuleDeps = super.compileModuleDeps ++ Seq( + build.compiler(crossValue) + ) + def sources = Task.Sources("src") +} + +object `package` extends Cross[TastyInspector](BuildType.allValues) diff --git a/tasty/package.mill b/tasty/package.mill new file mode 100644 index 000000000000..7373c4527576 --- /dev/null +++ b/tasty/package.mill @@ -0,0 +1,25 @@ +package build.tasty + +import scala3build.* + +import mill.* + +trait Tasty extends CrossScala3Module { outer => + def moduleDeps = super.moduleDeps ++ Seq( + build.library(crossValue) // why do we need an explicit crossValue here? + ) + def sources = Task.Sources("src") + def scalacOptions = super.scalacOptions() ++ Seq( + "-source:3.0-migration" + ) + + def testSources = Task.Sources("test") + object test extends Scala3Tests { + def testSources = outer.testSources + def forkEnv = super.forkEnv() ++ Seq( + "EXPECTED_TASTY_VERSION" -> Versions.expectedTastyVersion + ) + } +} + +object `package` extends Cross[Tasty](BuildType.allValues) diff --git a/test-helpers/package.mill b/test-helpers/package.mill new file mode 100644 index 000000000000..20af257c5250 --- /dev/null +++ b/test-helpers/package.mill @@ -0,0 +1,18 @@ +package build.`test-helpers` + +import scala3build.* + +import mill.* +import mill.scalalib.* + +trait TestHelpers extends CrossScala3Module { + def moduleDeps = super.moduleDeps ++ Seq( + build.compiler(crossValue) + ) + def sources = Task.Sources("src") + def mvnDeps = super.mvnDeps() ++ Seq( + mvn"org.junit.jupiter:junit-jupiter-api:${Versions.junitJupiter}" + ) +} + +object `package` extends Cross[TestHelpers](BuildType.allValues) diff --git a/tests/package.mill b/tests/package.mill new file mode 100644 index 000000000000..3db26553866e --- /dev/null +++ b/tests/package.mill @@ -0,0 +1,3 @@ +package build.tests + +// dummy package so that tests/sjs-unit/package.mill is taken into account by Mill diff --git a/tests/run-tasty-inspector/i8364.scala b/tests/run-tasty-inspector/i8364.scala index d1c72dcccaf3..0731a2dafdf0 100644 --- a/tests/run-tasty-inspector/i8364.scala +++ b/tests/run-tasty-inspector/i8364.scala @@ -11,7 +11,7 @@ import scala.tasty.inspector.* // Artefact of the current test infrastructure // TODO improve infrastructure to avoid needing this code on each test - val libJarClasspath = dotty.tools.dotc.util.ClasspathFromClassloader(this.getClass.getClassLoader).split(java.io.File.pathSeparator).find(x => x.contains("scala-library-bootstrapped") && x.endsWith(".jar")).get + val libJarClasspath = dotty.tools.dotc.util.ClasspathFromClassloader(this.getClass.getClassLoader).split(java.io.File.pathSeparator).find(x => x.contains("scala-library-") && x.endsWith(".jar")).get TastyInspector.inspectTastyFilesInJar(libJarClasspath)(inspector) } diff --git a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala index fa4caf42c10f..86509760b0c2 100644 --- a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala @@ -149,7 +149,7 @@ val experimentalDefinitionInLibrary = Set( // Artefact of the current test infrastructure // TODO improve infrastructure to avoid needing this code on each test - val libJarClasspath = dotty.tools.dotc.util.ClasspathFromClassloader(this.getClass.getClassLoader).split(java.io.File.pathSeparator).find(x => x.contains("scala-library-bootstrapped") && x.endsWith(".jar")).get + val libJarClasspath = dotty.tools.dotc.util.ClasspathFromClassloader(this.getClass.getClassLoader).split(java.io.File.pathSeparator).find(x => x.contains("scala-library-") && x.endsWith(".jar")).get TastyInspector.inspectTastyFilesInJar(libJarClasspath)(inspector) } diff --git a/tests/sjs-junit/package.mill b/tests/sjs-junit/package.mill new file mode 100644 index 000000000000..1660b56fac3f --- /dev/null +++ b/tests/sjs-junit/package.mill @@ -0,0 +1,201 @@ +package build.tests.`sjs-junit` + +import scala3build.* + +import mill.* +import mill.scalajslib.* +import mill.scalalib.* +import org.scalajs.linker.interface.* + +trait SjsUnit extends CrossScala3Module with Scala3JsModule { + def moduleDeps = super.moduleDeps ++ Seq( + build.`library-js`(crossValue) + ) + def mvnDeps = super.mvnDeps() ++ Seq( + mvn"org.scala-js:scalajs-junit-test-runtime_2.13:${Versions.scalaJSVersion}" + ) + + def scalaJSConfig = Task.Anon { + super.scalaJSConfig() + .withSemantics(TestSuiteLinkerOptions.semantics) + } + + def scalacOptions = Task { + super.scalacOptions().filter { opt => + opt != "-Werror" && opt != "-deprecation" && opt != "-Yexplicit-nulls" + } ++ + Seq( + "-Ycheck:prepjsinterop,explicitJSClasses,addLocalJSFakeNews" + ) + } + + def scalaJsSources = Task { + import org.eclipse.jgit.api._ + import org.eclipse.jgit.lib._ + + val ver = Versions.scalaJSVersion + val dest = Task.dest + + System.err.println(s"Fetching Scala.js source version $ver") + + new CloneCommand() + .setDirectory(dest.toIO) + .setURI("https://github.com/scala-js/scala-js.git") + // .setNoCheckout(true) + .call() + + val git = Git.open(dest.toIO) + System.err.println(s"Checking out Scala.js source version $ver") + git.getRepository().getConfig().setEnum("core", null, "autocrlf", CoreConfig.AutoCRLF.FALSE) + git.checkout().setName(s"v$ver").call() + + // no need to keep this, plus this confuses some git commands like git clean + os.remove.all(dest / ".git") + + PathRef(dest) + } + + def buildInfoSources = Task { + + val moduleKind0 = moduleKind() + val sems = Semantics.Defaults // might not work in fast opt + + val useClosure = // isFullLinkJS && + moduleKind0 != ModuleKind.ESModule + + SjsUnitConstantHolderGenerator.generate( + Task.dest, + "org.scalajs.testsuite.utils.BuildInfo", + "scalaVersion" -> scalaVersion(), + "hasSourceMaps" -> false, //DottyJSPlugin.wantSourceMaps.value (original sbt build comment) + "isNoModule" -> (moduleKind0 == ModuleKind.NoModule), + "isESModule" -> (moduleKind0 == ModuleKind.ESModule), + "isCommonJSModule" -> (moduleKind0 == ModuleKind.CommonJSModule), + "usesClosureCompiler" -> useClosure, + "hasMinifiedNames" -> (useClosure || scalaJSMinify()), + "compliantAsInstanceOfs" -> (sems.asInstanceOfs == CheckedBehavior.Compliant), + "compliantArrayIndexOutOfBounds" -> (sems.arrayIndexOutOfBounds == CheckedBehavior.Compliant), + "compliantArrayStores" -> (sems.arrayStores == CheckedBehavior.Compliant), + "compliantNegativeArraySizes" -> (sems.negativeArraySizes == CheckedBehavior.Compliant), + "compliantNullPointers" -> (sems.nullPointers == CheckedBehavior.Compliant), + "compliantStringIndexOutOfBounds" -> (sems.stringIndexOutOfBounds == CheckedBehavior.Compliant), + "compliantModuleInit" -> (sems.moduleInit == CheckedBehavior.Compliant), + "productionMode" -> sems.productionMode, + "esVersion" -> 5, // FIXME esFeatures().esVersion.edition, + "useECMAScript2015Semantics" -> false, // FIXME esFeatures().useECMAScript2015Semantics, + "isWebAssembly" -> scalaJSExperimentalUseWebAssembly() + ) + + PathRef(Task.dest) + } + + def sources = Task { + super.sources() ++ Seq(buildInfoSources()) ++ + os.walk(scalaJsSources().path / "test-suite/js/src/main/scala") + .filter(_.last.endsWith(".scala")) + .filter(!_.last.startsWith("Typechecking")) + .filter(os.isFile) + .map(PathRef(_)) ++ + os.walk(scalaJsSources().path / "junit-async/js/src/main/scala") + .filter(_.last.endsWith(".scala")) + .filter(os.isFile) + .map(PathRef(_)) + } + + def testSrc = Task.Source("test") + + trait SjsUnitTests extends Scala3JsTests { + def isFull: Boolean = false + def configTask = if (isFull) fullScalaJSConfig else fastScalaJSConfig + def testSources = Task { + val linkerConfig = configTask() + + val moduleKind = linkerConfig.moduleKind + val hasModules = moduleKind != ModuleKind.NoModule + val hasAsyncAwait = linkerConfig.esFeatures.esVersion >= ESVersion.ES2017 + val isWebAssembly = linkerConfig.experimentalUseWebAssembly + + val testSuiteDir = scalaJsSources().path / "test-suite" + def scalaFilesUnder(dir: os.SubPath): Seq[os.Path] = + os.walk(testSuiteDir / dir) + .filter(_.last.endsWith(".scala")) + .filter(os.isFile) + def scalaFilesIf(cond: Boolean, dir: os.SubPath): Seq[os.Path] = + if (cond) scalaFilesUnder(dir) else Nil + + val extra = Seq( + scalaFilesUnder("shared/src/test/scala") + .filter(_.last != "ReflectiveCallTest.scala") // Original comment: uses many forms of structural calls that are not allowed in Scala 3 anymore + .filter(_.last != "UTF16Test.scala") // Original comment: refutable pattern match + .filter(_.last != "CharsetTest.scala") // Original comment: bogus @tailrec that Scala 2 ignores but Scala 3 flags as an error + .filter(_.last != "ClassDiffersOnlyInCaseTest.scala"), // Original comment: looks like the Scala 3 compiler itself does not deal with that + // scalaFilesUnder("shared/src/test/require-sam"), + // scalaFilesUnder("shared/src/test/require-jdk8"), + // scalaFilesUnder("shared/src/test/require-jdk7"), + scalaFilesUnder("js/src/test/scala") + .filter(_.last != "StackTraceTest.scala") // Original comment: would require `npm install source-map-support` + .filter(_.last != "UnionTypeTest.scala"), // Original comment: requires the Scala 2 macro defined in Typechecking*.scala + // scalaFilesUnder("js/src/test/require-2.12"), + scalaFilesUnder("js/src/test/require-new-target"), + // scalaFilesUnder("js/src/test/require-sam"), + scalaFilesUnder("js/src/test/scala-new-collections"), + scalaFilesIf(!hasModules, "js/src/test/require-no-modules"), + scalaFilesIf(hasModules, "js/src/test/require-modules"), + scalaFilesIf(hasModules && !linkerConfig.closureCompiler && !isWebAssembly, "js/src/test/require-multi-modules"), + scalaFilesIf(moduleKind == ModuleKind.ESModule, "js/src/test/require-dynamic-import"), + scalaFilesIf(moduleKind == ModuleKind.ESModule, "js/src/test/require-esmodule"), + scalaFilesIf(hasAsyncAwait, "js/src/test/require-async-await"), + scalaFilesIf(hasAsyncAwait && isWebAssembly, "js/src/test/require-orphan-await") + ) + Seq(testSrc()) ++ extra.flatten.map(PathRef(_)) + } + def scalacOptions = super.scalacOptions() ++ Seq( + "-scalajs-genStaticForwardersForNonTopLevelObjects" + ) + + /* For some reason, in Scala 3, the implementation of IterableDefaultTest + * resolves to `scala.collection.ArrayOps.ArrayIterator`, whose `next()` + * method is not compliant when called past the last element on Scala.js. + * It relies on catching an `ArrayIndexOutOfBoundsException`. + * We have to ignore it here. + */ + // Test / testOptions := Seq(Tests.Filter(_ != "org.scalajs.testsuite.javalib.lang.IterableDefaultTest")), + + def resources = Task { + val testDir = scalaJsSources().path / "test-suite/js/src/test" + def jsFilesUnder(dir: os.SubPath): Seq[os.Path] = + os.walk(testDir / dir) + .filter(_.last.endsWith(".js")) + .filter(os.isFile) + + val common = jsFilesUnder("resources") + + val moduleSpecific = configTask().moduleKind match { + case ModuleKind.NoModule => Nil + case ModuleKind.CommonJSModule => jsFilesUnder("resources-commonjs") + case ModuleKind.ESModule => jsFilesUnder("resources-esmodule") + } + + for (f <- common ++ moduleSpecific) + os.copy(f, Task.dest / f.last) + + super.resources() ++ Seq(PathRef(Task.dest)) + } + + def testModuleInitializers = Task.Anon { + super.testModuleInitializers() ++ TestSuiteLinkerOptions.moduleInitializers + } + + def scalaJsTestInputs = Task.Anon { + val resourceDir = scalaJsSources().path / "test-suite/js/src/test/resources" + val f = (resourceDir / "NonNativeJSTypeTestNatives.js").toNIO + org.scalajs.jsenv.Input.Script(f) +: super.scalaJsTestInputs() + } + } + object test extends SjsUnitTests + object `full-test` extends SjsUnitTests { + def isFull = true + } +} + +object `package` extends Cross[SjsUnit](BuildType.allValues) From cc3e66c9884d21a33561fdb2dee3fce1a12d7af7 Mon Sep 17 00:00:00 2001 From: Alex Archambault Date: Wed, 28 Jan 2026 12:11:03 +0100 Subject: [PATCH 2/2] Use dev branch of Mill on CI --- .github/scripts/setup-dev-mill.sh | 21 +++++++++++++++++++++ .github/workflows/ci.yml | 27 +++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100755 .github/scripts/setup-dev-mill.sh diff --git a/.github/scripts/setup-dev-mill.sh b/.github/scripts/setup-dev-mill.sh new file mode 100755 index 000000000000..b242bbc87c6a --- /dev/null +++ b/.github/scripts/setup-dev-mill.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -e + +if [ ! -e "$HOME/.ivy2/local/mill-proguard.jar" ]; then + MILL_COMMIT="de17c958012" + + # Adding an extra directory "thing", to work around some Mill issue with scripts + mkdir thing + cd thing + git clone https://github.com/alexarchambault/mill.git -b scala3-new mill-scala3 + cd mill-scala3 + git switch --detach "$MILL_COMMIT" + ./mill -i dist.installLocal + mv mill-proguard.jar "$HOME/.ivy2/local/" + cd .. + cd .. + rm -rf thing +fi + +ln -sf "$HOME/.ivy2/local/mill-proguard.jar" mill +chmod +x mill diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1357742a2a9e..4d5d02da4e7e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,8 @@ jobs: fetch-depth: 0 # submodules: true - uses: coursier/setup-action@v2 + - name: Setup Mill + run: .github/scripts/setup-dev-mill.sh - name: No modules with empty sources run: ./mill -i ci.noEmptySources --tasks __.allSourceFiles - name: No orphan sources @@ -28,6 +30,8 @@ jobs: if test -d ~/.cache/mill/download; then chmod +x ~/.cache/mill/download/* fi + mkdir -p ~/.ivy2/local + mv ~/.ivy2/local ivy2-local - name: Upload build output uses: actions/upload-artifact@v4 with: @@ -49,6 +53,13 @@ jobs: path: mill-cache if-no-files-found: error retention-days: 3 + - name: Upload ivy2 local + uses: actions/upload-artifact@v4 + with: + name: ivy2-local + path: ivy2-local + if-no-files-found: error + retention-days: 3 repl-test: runs-on: ubuntu-latest @@ -70,6 +81,10 @@ jobs: with: name: mill-cache path: mill-cache + - uses: actions/download-artifact@v4 + with: + name: ivy2-local + path: ivy2-local - name: Post-download artifacts run: | mkdir -p ~/.cache/coursier @@ -78,12 +93,16 @@ jobs: if test -d ~/.cache/mill/download; then chmod +x ~/.cache/mill/download/* fi + mkdir -p ~/.ivy2 + mv ivy2-local ~/.ivy2/local - uses: coursier/setup-action@v2 - name: Install expect run: | sudo apt-get update sudo apt-get install -y expect expect -v + - name: Setup Mill + run: .github/scripts/setup-dev-mill.sh - name: Prepare for REPL test run: ./mill -i 'repl[final].compile' - name: Run REPL test @@ -118,6 +137,10 @@ jobs: with: name: mill-cache path: mill-cache + - uses: actions/download-artifact@v4 + with: + name: ivy2-local + path: ivy2-local - name: Post-download artifacts run: | mkdir -p ~/.cache/coursier @@ -126,7 +149,11 @@ jobs: if test -d ~/.cache/mill/download; then chmod +x ~/.cache/mill/download/* fi + mkdir -p ~/.ivy2 + mv ivy2-local ~/.ivy2/local - uses: coursier/setup-action@v2 + - name: Setup Mill + run: .github/scripts/setup-dev-mill.sh - name: Test run: ./mill -i '__[${{ matrix.BUILD_TYPE }}].${{ matrix.TEST }}' env: