diff --git a/cli/src/org/scalajs/cli/Scalajsld.scala b/cli/src/org/scalajs/cli/Scalajsld.scala index ee3ab76..05140c6 100644 --- a/cli/src/org/scalajs/cli/Scalajsld.scala +++ b/cli/src/org/scalajs/cli/Scalajsld.scala @@ -53,7 +53,8 @@ object Scalajsld { stdLib: Seq[File] = Nil, jsHeader: String = "", logLevel: Level = Level.Info, - importMap: Option[File] = None + importMap: Option[File] = None, + longRunning: Boolean = false ) private def moduleInitializer( @@ -234,6 +235,9 @@ object Scalajsld { opt[String]("jsHeader") .action { (jsHeader, c) => c.copy(jsHeader = jsHeader) } .text("A header that will be added at the top of generated .js files") + opt[Unit]("longRunning") + .action { (_, c) => c.copy(longRunning = true) } + .text("Run linking incrementally every time a line is printed to stdin") opt[Unit]('d', "debug") .action { (_, c) => c.copy(logLevel = Level.Debug) } .text("Debug mode: Show full log") @@ -315,39 +319,53 @@ object Scalajsld { val logger = new ScalaConsoleLogger(options.logLevel) val cache = StandardImpl.irFileCache().newCache - val result = PathIRContainer - .fromClasspath(classpath) - .flatMap(containers => cache.cached(containers._1)) - .flatMap { irFiles: Seq[IRFile] => + val stdinLinesIterator = scala.io.Source.stdin.getLines() - val irImportMappedFiles = options.importMap match { - case None => irFiles - case Some(importMap) => ImportMapJsonIr.remapImports(importMap, irFiles) - } + while({ + val result = PathIRContainer + .fromClasspath(classpath) + .flatMap(containers => cache.cached(containers._1)) + .flatMap { irFiles: Seq[IRFile] => + + val irImportMappedFiles = options.importMap match { + case None => irFiles + case Some(importMap) => ImportMapJsonIr.remapImports(importMap, irFiles) + } - (options.output, options.outputDir) match { - case (Some(jsFile), None) => - (DeprecatedLinkerAPI: DeprecatedLinkerAPI).link( - linker, - irImportMappedFiles.toList, - moduleInitializers, - jsFile, - logger - ) - case (None, Some(outputDir)) => - linker.link( - irImportMappedFiles, - moduleInitializers, - PathOutputDirectory(outputDir.toPath()), - logger - ) - case _ => - throw new AssertionError( - "Either output or outputDir have to be defined." - ) + (options.output, options.outputDir) match { + case (Some(jsFile), None) => + (DeprecatedLinkerAPI: DeprecatedLinkerAPI).link( + linker, + irImportMappedFiles.toList, + moduleInitializers, + jsFile, + logger + ) + case (None, Some(outputDir)) => + linker.link( + irImportMappedFiles, + moduleInitializers, + PathOutputDirectory(outputDir.toPath()), + logger + ) + case _ => + throw new AssertionError( + "Either output or outputDir have to be defined." + ) + } } - } - Await.result(result, Duration.Inf) + Await.result(result, Duration.Inf) + + if (options.longRunning) { + // print SCALA_JS_LINKING_DONE\n everytime one linking succeeds + println("SCALA_JS_LINKING_DONE") + + if (stdinLinesIterator.hasNext) { + stdinLinesIterator.next() + true + } else false + } else false + }) {} } } diff --git a/tests/test/src/org/scalajs/cli/tests/Tests.scala b/tests/test/src/org/scalajs/cli/tests/Tests.scala index a3346c0..9b1f4a7 100644 --- a/tests/test/src/org/scalajs/cli/tests/Tests.scala +++ b/tests/test/src/org/scalajs/cli/tests/Tests.scala @@ -113,6 +113,112 @@ class Tests extends munit.FunSuite { assert(splitRunOutput == "asdf 2") } + test("longRunning") { + val dir = os.temp.dir() + def writePrintlnMain(stringToPrint: String) = { + os.write.over( + dir / "foo.scala", + s"""object Foo { + | def main(args: Array[String]): Unit = { + | println("$stringToPrint") + | } + |} + |""".stripMargin + ) + } + + val scalaJsLibraryCp = getScalaJsLibraryCp(dir) + + os.makeDir.all(dir / "bin") + + def compile() = { + val compileCommand = os.proc( + "cs", + "launch", + "scalac:2.13.14", + "--", + "-classpath", + scalaJsLibraryCp, + s"-Xplugin:${getScalaJsCompilerPlugin(dir)}", + "-d", + "bin", + "foo.scala" + ) + compileCommand.call(cwd = dir, stdin = os.Inherit, stdout = os.Inherit) + } + + val command = os + .proc( + launcher, + "--stdlib", + scalaJsLibraryCp, + "-s", + "-o", + "test.js", + "-mm", + "Foo.main", + "--longRunning", + "bin", + ) + + writePrintlnMain("first version") + compile() + val process = command.spawn(cwd = dir) + + def waitForLinkingToFinish() = { + while({ + val line = process.stdout.readLine() + assert(line != null, "Got null from reading stdout") + line != "SCALA_JS_LINKING_DONE" + }) {} + } + + try { + locally { + waitForLinkingToFinish() + val testJsSize = os.size(dir / "test.js") + val testJsMapSize = os.size(dir / "test.js.map") + assert(testJsSize > 0) + assert(testJsMapSize > 0) + + val runRes = os.proc("node", "test.js").call(cwd = dir) + val runOutput = runRes.out.trim() + assert(runOutput == "first version") + } + + writePrintlnMain("second version") + compile() + + // trigger new linking + process.stdin.writeLine("") + process.stdin.flush() + + waitForLinkingToFinish() + + locally { + val testJsSize = os.size(dir / "test.js") + val testJsMapSize = os.size(dir / "test.js.map") + assert(testJsSize > 0) + assert(testJsMapSize > 0) + + val runRes = os.proc("node", "test.js").call(cwd = dir) + val runOutput = runRes.out.trim() + assertEquals(runOutput, "second version") + } + + // close stdin to allow the process to terminate gracefully + process.stdin.close() + + // wait some time for the process to terminate + Thread.sleep(100) + + assert(!process.isAlive(), "process did not terminate gracefully") + } finally { + + process.close() + } + } + test("fullLinkJs mode does not throw") { val dir = os.temp.dir() os.write(