From 13ba1a12efc2ff4aaeedcf626989e44a230d2f07 Mon Sep 17 00:00:00 2001 From: Lorenzo Gabriele Date: Fri, 28 Jan 2022 23:23:20 +0100 Subject: [PATCH 1/2] Link Scala.js using Directory path when available This doesn't change any API but makes the Scala.js emit top level exports. --- scalajslib/src/ScalaJSModule.scala | 3 +- .../resources/top-level-exports/src/App.scala | 26 ++++++++++ .../test/src/TopLevelExportsTests.scala | 49 +++++++++++++++++++ .../worker/1/src/ScalaJSWorkerImpl.scala | 47 ++++++++++++------ 4 files changed, 108 insertions(+), 17 deletions(-) create mode 100644 scalajslib/test/resources/top-level-exports/src/App.scala create mode 100644 scalajslib/test/src/TopLevelExportsTests.scala diff --git a/scalajslib/src/ScalaJSModule.scala b/scalajslib/src/ScalaJSModule.scala index cf8ae149b64..046995289dc 100644 --- a/scalajslib/src/ScalaJSModule.scala +++ b/scalajslib/src/ScalaJSModule.scala @@ -134,10 +134,9 @@ trait ScalaJSModule extends scalalib.ScalaModule { outer => moduleKind: ModuleKind, esFeatures: ESFeatures )(implicit ctx: Ctx): Result[PathRef] = { - val outputPath = ctx.dest / "out.js" + val outputPath = ctx.dest os.makeDir.all(ctx.dest) - os.remove.all(outputPath) val classpath = runClasspath.map(_.path) val sjsirFiles = classpath diff --git a/scalajslib/test/resources/top-level-exports/src/App.scala b/scalajslib/test/resources/top-level-exports/src/App.scala new file mode 100644 index 00000000000..3d17eea300b --- /dev/null +++ b/scalajslib/test/resources/top-level-exports/src/App.scala @@ -0,0 +1,26 @@ +package my.app + +import scala.collection.mutable +import scala.scalajs.js.annotation._ + +object AppA { + @JSExportTopLevel(name = "start", moduleID = "a") + def a(): Unit = println("hello from a") +} + +object AppB { + private val x = mutable.Set.empty[String] + + @JSExportTopLevel(name = "start", moduleID = "b") + def b(): Unit = { + println("hello from b") + println(x) + } + + def main(): Unit = x.add("something") +} +object App { + def main(args: Array[String]): Unit = { + println("Hello") + } +} diff --git a/scalajslib/test/src/TopLevelExportsTests.scala b/scalajslib/test/src/TopLevelExportsTests.scala new file mode 100644 index 00000000000..dfa35565016 --- /dev/null +++ b/scalajslib/test/src/TopLevelExportsTests.scala @@ -0,0 +1,49 @@ +package mill.scalajslib + +import mill._ +import mill.define.Discover +import mill.scalajslib.api._ +import mill.util.{TestEvaluator, TestUtil} +import utest._ + +object TopLevelExportsTests extends TestSuite { + val workspacePath = TestUtil.getOutPathStatic() / "top-level-exports" + + object TopLevelExportsModule extends TestUtil.BaseModule { + + object topLevelExportsModule extends ScalaJSModule { + override def millSourcePath = workspacePath + override def scalaVersion = sys.props.getOrElse("TEST_SCALA_2_13_VERSION", ???) + override def scalaJSVersion = "1.8.0" + override def moduleKind = ModuleKind.ESModule + } + + override lazy val millDiscover = Discover[this.type] + } + + val millSourcePath = os.pwd / "scalajslib" / "test" / "resources" / "top-level-exports" + + val evaluator = TestEvaluator.static(TopLevelExportsModule) + + val tests: Tests = Tests { + prepareWorkspace() + + test("top level exports") { + println(evaluator(TopLevelExportsModule.topLevelExportsModule.sources)) + val Right((PathRef(outFile, _, _), _)) = + evaluator(TopLevelExportsModule.topLevelExportsModule.fastOpt) + assert(os.exists(outFile)) + assert(os.exists(outFile / os.up / "a.js")) + assert(os.exists(outFile / os.up / "a.js.map")) + assert(os.exists(outFile / os.up / "b.js")) + assert(os.exists(outFile / os.up / "b.js.map")) + } + } + + def prepareWorkspace(): Unit = { + os.remove.all(workspacePath) + os.makeDir.all(workspacePath / os.up) + os.copy(millSourcePath, workspacePath) + } + +} diff --git a/scalajslib/worker/1/src/ScalaJSWorkerImpl.scala b/scalajslib/worker/1/src/ScalaJSWorkerImpl.scala index 19b7eca89b5..3080522c85d 100644 --- a/scalajslib/worker/1/src/ScalaJSWorkerImpl.scala +++ b/scalajslib/worker/1/src/ScalaJSWorkerImpl.scala @@ -8,7 +8,13 @@ import java.io.File import mill.api.{Result, internal} import mill.scalajslib.api.{ESFeatures, ESVersion, JsEnvConfig, ModuleKind} import org.scalajs.ir.ScalaJSVersions -import org.scalajs.linker.{PathIRContainer, PathIRFile, PathOutputFile, StandardImpl} +import org.scalajs.linker.{ + PathIRContainer, + PathIRFile, + PathOutputDirectory, + PathOutputFile, + StandardImpl +} import org.scalajs.linker.interface.{ ESFeatures => ScalaJSESFeatures, ESVersion => ScalaJSESVersion, @@ -32,6 +38,10 @@ class ScalaJSWorkerImpl extends mill.scalajslib.api.ScalaJSWorkerApi { esFeatures: ESFeatures, dest: File ) + private def minorIsGreaterThan(number: Int) = ScalaJSVersions.binaryEmitted match { + case s"1.$n" if n.toIntOption.exists(_ < number) => false + case _ => true + } private object ScalaJSLinker { private val cache = mutable.Map.empty[LinkerInput, WeakReference[Linker]] def reuseOrCreate(input: LinkerInput): Linker = cache.get(input) match { @@ -41,10 +51,6 @@ class ScalaJSWorkerImpl extends mill.scalajslib.api.ScalaJSWorkerApi { cache.update(input, WeakReference(newLinker)) newLinker } - private def minorIsGreaterThan(number: Int) = ScalaJSVersions.binaryEmitted match { - case s"1.$n" if n.toIntOption.exists(_ < number) => false - case _ => true - } private def createLinker(input: LinkerInput): Linker = { val semantics = input.fullOpt match { case true => Semantics.Defaults.optimized @@ -111,18 +117,12 @@ class ScalaJSWorkerImpl extends mill.scalajslib.api.ScalaJSWorkerApi { esFeatures: ESFeatures ) = { import scala.concurrent.ExecutionContext.Implicits.global - val linker = - ScalaJSLinker.reuseOrCreate(LinkerInput(fullOpt, moduleKind, esFeatures, dest)) + val outFile = new File(dest, "out.js") + val linker = ScalaJSLinker.reuseOrCreate(LinkerInput(fullOpt, moduleKind, esFeatures, dest)) val cache = StandardImpl.irFileCache().newCache val sourceIRsFuture = Future.sequence(sources.toSeq.map(f => PathIRFile(f.toPath()))) val irContainersPairs = PathIRContainer.fromClasspath(libraries.map(_.toPath())) val libraryIRsFuture = irContainersPairs.flatMap(pair => cache.cached(pair._1)) - val jsFile = dest.toPath() - val sourceMap = jsFile.resolveSibling(jsFile.getFileName + ".map") - val linkerOutput = LinkerOutput(PathOutputFile(jsFile)) - .withJSFileURI(java.net.URI.create(jsFile.getFileName.toString)) - .withSourceMap(PathOutputFile(sourceMap)) - .withSourceMapURI(java.net.URI.create(sourceMap.getFileName.toString)) val logger = new ScalaConsoleLogger val mainInitializer = Option(main).map { cls => ModuleInitializer.mainMethodWithArgs(cls, "main") @@ -136,9 +136,26 @@ class ScalaJSWorkerImpl extends mill.scalajslib.api.ScalaJSWorkerApi { val resultFuture = (for { sourceIRs <- sourceIRsFuture libraryIRs <- libraryIRsFuture - _ <- linker.link(sourceIRs ++ libraryIRs, moduleInitializers, linkerOutput, logger) + _ <- + if (minorIsGreaterThan(2)) { + val linkerOutput = PathOutputDirectory(dest.toPath()) + linker.link( + sourceIRs ++ libraryIRs, + moduleInitializers.map(_.withModuleID("out")), + linkerOutput, + logger + ) + } else { + val jsFile = outFile.toPath() + val sourceMap = jsFile.resolveSibling(jsFile.getFileName + ".map") + val linkerOutput = LinkerOutput(PathOutputFile(jsFile)) + .withJSFileURI(java.net.URI.create(jsFile.getFileName.toString)) + .withSourceMap(PathOutputFile(sourceMap)) + .withSourceMapURI(java.net.URI.create(sourceMap.getFileName.toString)) + linker.link(sourceIRs ++ libraryIRs, moduleInitializers, linkerOutput, logger) + } } yield { - Result.Success(dest) + Result.Success(outFile) }).recover { case e: org.scalajs.linker.interface.LinkingException => Result.Failure(e.getMessage) From c5b385939262c1bbfd40f083ed5520a6dcfac858 Mon Sep 17 00:00:00 2001 From: Lorenzo Gabriele Date: Sat, 29 Jan 2022 00:07:27 +0100 Subject: [PATCH 2/2] Fix ScalaJS Worker 0.6 --- scalajslib/worker/0.6/src/ScalaJSWorkerImpl.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scalajslib/worker/0.6/src/ScalaJSWorkerImpl.scala b/scalajslib/worker/0.6/src/ScalaJSWorkerImpl.scala index 0f740c04c75..89a6edc093c 100644 --- a/scalajslib/worker/0.6/src/ScalaJSWorkerImpl.scala +++ b/scalajslib/worker/0.6/src/ScalaJSWorkerImpl.scala @@ -72,7 +72,7 @@ class ScalaJSWorkerImpl extends mill.scalajslib.api.ScalaJSWorkerApi { def link( sources: Array[File], libraries: Array[File], - dest: File, + destDir: File, main: String, testBridgeInit: Boolean, // ignored in 0.6 fullOpt: Boolean, @@ -84,6 +84,7 @@ class ScalaJSWorkerImpl extends mill.scalajslib.api.ScalaJSWorkerApi { val jars = libraries.map(jar => IRContainer.Jar(new FileVirtualBinaryFile(jar) with VirtualJarFile)) val jarSJSIRs = jars.flatMap(_.jar.sjsirFiles) + val dest = new File(destDir, "out.js") val destFile = AtomicWritableFileVirtualJSFile(dest) val logger = new ScalaConsoleLogger val initializer = Option(main).map { cls => ModuleInitializer.mainMethodWithArgs(cls, "main") }