Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --longRunning option to link incrementally in a long living process #64

Merged
merged 1 commit into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 49 additions & 31 deletions cli/src/org/scalajs/cli/Scalajsld.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
}) {}
}
}

Expand Down
106 changes: 106 additions & 0 deletions tests/test/src/org/scalajs/cli/tests/Tests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Loading