summaryrefslogtreecommitdiff
path: root/scalajslib
diff options
context:
space:
mode:
authorLi Haoyi <haoyi.sg@gmail.com>2017-12-30 19:01:03 -0800
committerLi Haoyi <haoyi.sg@gmail.com>2017-12-30 20:35:31 -0800
commit356dca0f92931b07e1a80013aefb025b6a7d7d42 (patch)
tree6517cbd15943361cbd896e64a7007c058f20281d /scalajslib
parente84eff79f6f23b9a6518c74ba137ab4ce1347929 (diff)
downloadmill-356dca0f92931b07e1a80013aefb025b6a7d7d42.tar.gz
mill-356dca0f92931b07e1a80013aefb025b6a7d7d42.tar.bz2
mill-356dca0f92931b07e1a80013aefb025b6a7d7d42.zip
`Core` -> `core`, for consistency with SBT naming schemes
`ScalaPlugin` -> `scalalib`, to avoid confusion with Scala compiler plugins `ScalaModule` -> `module`, to be used via `scalalib.Module`, avoid unnecessary duplication in th name prefix `plugin` -> `moduledefs`, to more accurately describe what it does (since it includes `Cacher` as well)
Diffstat (limited to 'scalajslib')
-rw-r--r--scalajslib/bridge_0_6/src/main/scala/mill/scalajslib/bridge/ScalaJSLinkerBridge.scala24
-rw-r--r--scalajslib/bridge_1_0/src/main/scala/mill/scalajslib/bridge/ScalaJSLinkerBridge.scala24
-rw-r--r--scalajslib/src/main/scala/mill/scalajslib/Lib.scala62
-rw-r--r--scalajslib/src/main/scala/mill/scalajslib/ScalaJSLinkerBridge.scala12
-rw-r--r--scalajslib/src/main/scala/mill/scalajslib/ScalaJSModule.scala69
-rw-r--r--scalajslib/src/test/resource/hello-js-world/src/main/scala/Main.scala3
-rw-r--r--scalajslib/src/test/scala/mill/scalajslib/HelloJSWorldTests.scala160
7 files changed, 354 insertions, 0 deletions
diff --git a/scalajslib/bridge_0_6/src/main/scala/mill/scalajslib/bridge/ScalaJSLinkerBridge.scala b/scalajslib/bridge_0_6/src/main/scala/mill/scalajslib/bridge/ScalaJSLinkerBridge.scala
new file mode 100644
index 00000000..4826b1cd
--- /dev/null
+++ b/scalajslib/bridge_0_6/src/main/scala/mill/scalajslib/bridge/ScalaJSLinkerBridge.scala
@@ -0,0 +1,24 @@
+package mill
+package scalajslib
+package bridge
+
+import java.io.File
+
+import org.scalajs.core.tools.io.IRFileCache.IRContainer
+import org.scalajs.core.tools.io.{AtomicWritableFileVirtualJSFile, FileVirtualBinaryFile, FileVirtualScalaJSIRFile, VirtualJarFile}
+import org.scalajs.core.tools.linker.{ModuleInitializer, StandardLinker}
+import org.scalajs.core.tools.logging.ScalaConsoleLogger
+
+class ScalaJSLinkerBridge {
+ def link(sources: Array[File], libraries: Array[File], dest: File, main: String, fullOpt: Boolean): Unit = {
+ val config = StandardLinker.Config().withOptimizer(fullOpt)
+ val linker = StandardLinker(config)
+ val sourceSJSIRs = sources.map(new FileVirtualScalaJSIRFile(_))
+ val jars = libraries.map(jar => IRContainer.Jar(new FileVirtualBinaryFile(jar) with VirtualJarFile))
+ val jarSJSIRs = jars.flatMap(_.jar.sjsirFiles)
+ val destFile = AtomicWritableFileVirtualJSFile(dest)
+ val logger = new ScalaConsoleLogger
+ val initializer = Option(main).map { cls => ModuleInitializer.mainMethodWithArgs(cls, "main") }
+ linker.link(sourceSJSIRs ++ jarSJSIRs, initializer.toSeq, destFile, logger)
+ }
+} \ No newline at end of file
diff --git a/scalajslib/bridge_1_0/src/main/scala/mill/scalajslib/bridge/ScalaJSLinkerBridge.scala b/scalajslib/bridge_1_0/src/main/scala/mill/scalajslib/bridge/ScalaJSLinkerBridge.scala
new file mode 100644
index 00000000..ef896726
--- /dev/null
+++ b/scalajslib/bridge_1_0/src/main/scala/mill/scalajslib/bridge/ScalaJSLinkerBridge.scala
@@ -0,0 +1,24 @@
+package mill
+package scalajslib
+package bridge
+
+import java.io.File
+
+import org.scalajs.core.tools.io._
+import org.scalajs.core.tools.linker.{ModuleInitializer, StandardLinker}
+import org.scalajs.core.tools.logging.ScalaConsoleLogger
+
+class ScalaJSLinkerBridge {
+ def link(sources: Array[File], libraries: Array[File], dest: File, main: String, fullOpt: Boolean): Unit = {
+ val config = StandardLinker.Config().withOptimizer(fullOpt)
+ val linker = StandardLinker(config)
+ val cache = new IRFileCache().newCache
+ val sourceIRs = sources.map(FileVirtualScalaJSIRFile)
+ val irContainers = FileScalaJSIRContainer.fromClasspath(libraries)
+ val libraryIRs = cache.cached(irContainers)
+ val destFile = AtomicWritableFileVirtualJSFile(dest)
+ val logger = new ScalaConsoleLogger
+ val initializer = Option(main).map { cls => ModuleInitializer.mainMethodWithArgs(cls, "main") }
+ linker.link(sourceIRs ++ libraryIRs, initializer.toSeq, destFile, logger)
+ }
+} \ No newline at end of file
diff --git a/scalajslib/src/main/scala/mill/scalajslib/Lib.scala b/scalajslib/src/main/scala/mill/scalajslib/Lib.scala
new file mode 100644
index 00000000..3de37962
--- /dev/null
+++ b/scalajslib/src/main/scala/mill/scalajslib/Lib.scala
@@ -0,0 +1,62 @@
+package mill
+package scalajslib
+
+import java.io.File
+import java.net.URLClassLoader
+
+import ammonite.ops.{Path, mkdir, rm, _}
+import mill.eval.PathRef
+import mill.scalalib.Dep
+import mill.util.Ctx
+
+import scala.collection.breakOut
+import scala.language.reflectiveCalls
+
+private object LinkerBridge {
+ @volatile var scalaInstanceCache = Option.empty[(Long, ScalaJSLinkerBridge)]
+}
+
+object Lib {
+
+ def scalaJSLinkerIvyDep(scalaJSVersion: String): Dep =
+ Dep("com.lihaoyi", s"mill-jsbridge_${scalaJSVersion.replace('.', '_')}", "0.1-SNAPSHOT")
+
+ def scalaJSLinkerBridge(classPath: Seq[Path]): ScalaJSLinkerBridge = {
+ val classloaderSig = classPath.map(p => p.toString().hashCode + p.mtime.toMillis).sum
+ LinkerBridge.scalaInstanceCache match {
+ case Some((`classloaderSig`, linker)) => linker
+ case _ =>
+ val cl = new URLClassLoader(classPath.map(_.toIO.toURI.toURL).toArray)
+ val bridge = cl.loadClass("mill.scalajslib.bridge.ScalaJSLinkerBridge")
+ .getDeclaredConstructor().newInstance().asInstanceOf[ {
+ def link(sources: Array[File], libraries: Array[File], dest: File, main: String, fullOpt: Boolean): Unit
+ }]
+ val linker: ScalaJSLinkerBridge = (sources: Seq[File],
+ libraries: Seq[File],
+ dest: File,
+ main: Option[String],
+ mode: OptimizeMode) =>
+ bridge.link(sources.toArray, libraries.toArray, dest, main.orNull, mode == FullOpt)
+ LinkerBridge.scalaInstanceCache = Some((classloaderSig, linker))
+ linker
+ }
+ }
+
+ def link(main: Option[String],
+ inputPaths: Seq[Path],
+ libraries: Seq[Path],
+ linker: ScalaJSLinkerBridge,
+ mode: OptimizeMode)
+ (implicit ctx: Ctx.DestCtx): PathRef = {
+ val outputPath = ctx.dest.copy(segments = ctx.dest.segments.init :+ (ctx.dest.segments.last + ".js"))
+ rm(outputPath)
+ if (inputPaths.nonEmpty) {
+ mkdir(outputPath / up)
+ val inputFiles: Vector[File] = inputPaths.map(ls).flatMap(_.filter(_.ext == "sjsir")).map(_.toIO)(breakOut)
+ val inputLibraries: Vector[File] = libraries.filter(_.ext == "jar").map(_.toIO)(breakOut)
+ linker.link(inputFiles, inputLibraries, outputPath.toIO, main, mode)
+ }
+ PathRef(outputPath)
+ }
+
+}
diff --git a/scalajslib/src/main/scala/mill/scalajslib/ScalaJSLinkerBridge.scala b/scalajslib/src/main/scala/mill/scalajslib/ScalaJSLinkerBridge.scala
new file mode 100644
index 00000000..448cc16a
--- /dev/null
+++ b/scalajslib/src/main/scala/mill/scalajslib/ScalaJSLinkerBridge.scala
@@ -0,0 +1,12 @@
+package mill.scalajslib
+
+import java.io.File
+
+sealed trait OptimizeMode
+
+object FastOpt extends OptimizeMode
+object FullOpt extends OptimizeMode
+
+trait ScalaJSLinkerBridge {
+ def link(sources: Seq[File], libraries: Seq[File], dest: File, main: Option[String], mode: OptimizeMode): Unit
+}
diff --git a/scalajslib/src/main/scala/mill/scalajslib/ScalaJSModule.scala b/scalajslib/src/main/scala/mill/scalajslib/ScalaJSModule.scala
new file mode 100644
index 00000000..ed0a2c85
--- /dev/null
+++ b/scalajslib/src/main/scala/mill/scalajslib/ScalaJSModule.scala
@@ -0,0 +1,69 @@
+package mill
+package scalajslib
+
+import java.io.File
+
+import ammonite.ops.Path
+import mill.eval.Result.Success
+import mill.scalajslib.Lib._
+import mill.scalalib.Lib.resolveDependencies
+import mill.scalalib.{Dep, PublishModule, Module, TestModule}
+
+trait ScalaJSModule extends scalalib.Module { outer =>
+
+ def scalaJSVersion: T[String]
+
+ private val ReleaseVersion = raw"""(\d+)\.(\d+)\.(\d+)""".r
+ private val MinorSnapshotVersion = raw"""(\d+)\.(\d+)\.([1-9]\d*)-SNAPSHOT""".r
+
+ def scalaJSBinaryVersion = T{
+ scalaJSVersion() match {
+ case ReleaseVersion(major, minor, _) => s"$major.$minor"
+ case MinorSnapshotVersion(major, minor, _) => s"$major.$minor"
+ case _ => scalaJSVersion()
+ }
+ }
+
+ def scalaJSBridgeVersion = T{ scalaJSVersion().split('.').dropRight(1).mkString(".") }
+
+ def scalaJSLinkerClasspath: T[Seq[PathRef]] = T{
+ val jsBridgeKey = "MILL_SCALAJS_BRIDGE_" + scalaJSBridgeVersion().replace('.', '_')
+ val jsBridgePath = sys.props(jsBridgeKey)
+ if (jsBridgePath != null) Success(jsBridgePath.split(File.pathSeparator).map(f => PathRef(Path(f), quick = true)).toVector)
+ else {
+ val dep = scalaJSLinkerIvyDep(scalaJSBridgeVersion())
+ resolveDependencies(
+ repositories,
+ scalaVersion(),
+ scalaBinaryVersion(),
+ Seq(dep)
+ )
+ }
+ }
+
+ def fastOpt = T{
+ val linker = scalaJSLinkerBridge(scalaJSLinkerClasspath().map(_.path))
+ link(mainClass(), Seq(compile().classes.path), compileDepClasspath().map(_.path), linker, FastOpt)
+ }
+
+ def fullOpt = T{
+ val linker = scalaJSLinkerBridge(scalaJSLinkerClasspath().map(_.path))
+ link(mainClass(), Seq(compile().classes.path), compileDepClasspath().map(_.path), linker, FullOpt)
+ }
+
+ override def scalacPluginIvyDeps = T{ Seq(Dep.Point("org.scala-js", "scalajs-compiler", scalaJSVersion())) }
+
+ override def ivyDeps = T{ Seq(Dep("org.scala-js", "scalajs-library", scalaJSVersion())) }
+
+ // publish artifact with name "mill_sjs0.6.4_2.12" instead of "mill_sjs0.6_2.12"
+ def crossFullScalaJSVersion: T[Boolean] = false
+ def artifactScalaJSVersion: T[String] = T {
+ if (crossFullScalaJSVersion()) scalaJSVersion()
+ else scalaJSBinaryVersion()
+ }
+
+ override def artifactId: T[String] = T { s"${artifactName()}_sjs${artifactScalaJSVersion()}_${artifactScalaVersion()}" }
+
+}
+
+trait TestScalaJSModule extends ScalaJSModule with TestModule \ No newline at end of file
diff --git a/scalajslib/src/test/resource/hello-js-world/src/main/scala/Main.scala b/scalajslib/src/test/resource/hello-js-world/src/main/scala/Main.scala
new file mode 100644
index 00000000..60cef56d
--- /dev/null
+++ b/scalajslib/src/test/resource/hello-js-world/src/main/scala/Main.scala
@@ -0,0 +1,3 @@
+object Main extends App {
+ println("Hello " + sys.props("java.vm.name"))
+}
diff --git a/scalajslib/src/test/scala/mill/scalajslib/HelloJSWorldTests.scala b/scalajslib/src/test/scala/mill/scalajslib/HelloJSWorldTests.scala
new file mode 100644
index 00000000..ad392c42
--- /dev/null
+++ b/scalajslib/src/test/scala/mill/scalajslib/HelloJSWorldTests.scala
@@ -0,0 +1,160 @@
+package mill.scalajslib
+
+import java.io.{FileReader, StringWriter}
+import java.util.jar.JarFile
+import javax.script.{ScriptContext, ScriptEngineManager}
+
+import ammonite.ops._
+import mill._
+import mill.define.Cross
+import mill.discover.Discovered
+import mill.scalalib.PublishModule
+import mill.scalalib.publish.{Developer, License, PomSettings, SCM}
+import mill.util.TestEvaluator
+import utest._
+
+import scala.collection.JavaConverters._
+
+trait HelloJSWorldModule extends ScalaJSModule with PublishModule {
+ def basePath = HelloJSWorldTests.workspacePath
+ override def mainClass = Some("Main")
+}
+
+object HelloJSWorld {
+ val build = for {
+ scalaJS <- Cross("0.6.20", "0.6.21", "1.0.0-M2")
+ scala <- Cross("2.11.8", "2.12.3", "2.12.4")
+ } yield
+ new HelloJSWorldModule {
+ def scalaVersion = scala
+ def scalaJSVersion = scalaJS
+ def pomSettings = PomSettings(
+ organization = "com.lihaoyi",
+ description = "hello js world ready for real world publishing",
+ url = "https://github.com/lihaoyi/hello-world-publish",
+ licenses = Seq(
+ License("Apache License, Version 2.0",
+ "http://www.apache.org/licenses/LICENSE-2.0")),
+ scm = SCM(
+ "https://github.com/lihaoyi/hello-world-publish",
+ "scm:git:https://github.com/lihaoyi/hello-world-publish"
+ ),
+ developers =
+ Seq(Developer("lihaoyi", "Li Haoyi", "https://github.com/lihaoyi"))
+ )
+ }
+}
+
+object HelloJSWorldTests extends TestSuite {
+
+ val srcPath = pwd / 'scalajslib / 'src / 'test / 'resource / "hello-js-world"
+ val workspacePath = pwd / 'target / 'workspace / "hello-js-world"
+ val outputPath = workspacePath / 'out
+ val mainObject = workspacePath / 'src / 'main / 'scala / "Main.scala"
+
+ val helloWorldEvaluator = new TestEvaluator(
+ Discovered.mapping(HelloJSWorld),
+ workspacePath
+ )
+
+ class Console {
+ val out = new StringWriter()
+ def log(s: String): Unit = out.append(s)
+ }
+
+ def runJS(path: Path): String = {
+ val engineManager = new ScriptEngineManager
+ val engine = engineManager.getEngineByName("nashorn")
+ val console = new Console
+ engine.getBindings(ScriptContext.ENGINE_SCOPE).put("console", console)
+ engine.eval(new FileReader(path.toIO))
+ console.out.toString
+ }
+
+ def tests: Tests = Tests {
+ prepareWorkspace()
+ 'compile - {
+ def testCompileFromScratch(scalaVersion: String,
+ scalaJSVersion: String): Unit = {
+ val Right((result, evalCount)) =
+ helloWorldEvaluator(HelloJSWorld.build(scalaVersion, scalaJSVersion).compile)
+
+ val outPath = result.classes.path
+ val outputFiles = ls.rec(outPath)
+ val expectedClassfiles = compileClassfiles(outPath)
+ assert(
+ outputFiles.toSet == expectedClassfiles,
+ evalCount > 0
+ )
+
+ // don't recompile if nothing changed
+ val Right((_, unchangedEvalCount)) =
+ helloWorldEvaluator(HelloJSWorld.build(scalaVersion, scalaJSVersion).compile)
+ assert(unchangedEvalCount == 0)
+ }
+
+ 'fromScratch_2124_0621 - testCompileFromScratch("2.12.4", "0.6.21")
+ 'fromScratch_2123_0621 - testCompileFromScratch("2.12.3", "0.6.21")
+ 'fromScratch_2118_0621 - testCompileFromScratch("2.11.8", "0.6.21")
+ 'fromScratch_2124_100M2 - testCompileFromScratch("2.11.8", "1.0.0-M2")
+ }
+
+ def testRun(scalaVersion: String,
+ scalaJSVersion: String,
+ mode: OptimizeMode): Unit = {
+ val task = mode match {
+ case FullOpt => HelloJSWorld.build(scalaVersion, scalaJSVersion).fullOpt
+ case FastOpt => HelloJSWorld.build(scalaVersion, scalaJSVersion).fastOpt
+ }
+ val Right((result, evalCount)) = helloWorldEvaluator(task)
+ val output = runJS(result.path)
+ assert(output == "Hello Scala.js")
+ }
+
+ 'fullOpt - {
+ 'run_2124_0621 - testRun("2.12.4", "0.6.21", FullOpt)
+ 'run_2123_0621 - testRun("2.12.3", "0.6.21", FullOpt)
+ 'run_2118_0621 - testRun("2.11.8", "0.6.21", FullOpt)
+ 'run_2124_100M2 - testRun("2.11.8", "1.0.0-M2", FullOpt)
+ }
+ 'fastOpt - {
+ 'run_2124_0621 - testRun("2.12.4", "0.6.21", FastOpt)
+ 'run_2123_0621 - testRun("2.12.3", "0.6.21", FastOpt)
+ 'run_2118_0621 - testRun("2.11.8", "0.6.21", FastOpt)
+ 'run_2124_100M2 - testRun("2.11.8", "1.0.0-M2", FastOpt)
+ }
+ 'jar - {
+ 'containsSJSIRs - {
+ val Right((result, evalCount)) = helloWorldEvaluator(HelloJSWorld.build("2.12.4", "0.6.21").jar)
+ val jar = result.path
+ val entries = new JarFile(jar.toIO).entries().asScala.map(_.getName)
+ assert(entries.contains("Main$.sjsir"))
+ }
+ }
+ 'publish - {
+ def testArtifactId(scalaVersion: String,
+ scalaJSVersion: String,
+ artifactId: String): Unit = {
+ val Right((result, evalCount)) = helloWorldEvaluator(HelloJSWorld.build(scalaVersion, scalaJSVersion).artifact)
+ assert(result.id == artifactId)
+ }
+ 'artifactId_0621 - testArtifactId("2.12.4", "0.6.21", "hello-js-world_sjs0.6_2.12")
+ 'artifactId_0621 - testArtifactId("2.12.4", "1.0.0-M2", "hello-js-world_sjs1.0.0-M2_2.12")
+ }
+ }
+
+ def compileClassfiles(parentDir: Path) = Set(
+ parentDir / "Main.class",
+ parentDir / "Main$.class",
+ parentDir / "Main$delayedInit$body.class",
+ parentDir / "Main$.sjsir",
+ parentDir / "Main$delayedInit$body.sjsir"
+ )
+
+ def prepareWorkspace(): Unit = {
+ rm(workspacePath)
+ mkdir(workspacePath / up)
+ cp(srcPath, workspacePath)
+ }
+
+}