From cd4004a82d9713bbb0b120aba83b3ed8fc9f1372 Mon Sep 17 00:00:00 2001 From: Sébastien Doeraene Date: Fri, 26 Feb 2016 16:30:39 +0100 Subject: Initial infrastructure and hello world for the Scala.js back-end. The Scala.js back-end can be enabled with the `-scalajs` command-line option. Currently, it adds one phase to the pipeline, which emits .sjsir files from trees. A sandbox project `sjsSandbox`, in `sandbox/scalajs/`, can be used to easily test Scala.js compilation. One can run the `main()` method of the `hello.world` object with > sjsSandbox/run The back-end only contains the bare mimimum to compile the hello world application in the sandbox. Anything else will blow up (for example, primitive method calls). It is a work-in-progress. --- project/Build.scala | 151 ++++++++++++++++++++++++++++++++++++++++++++++++++++ project/plugins.sbt | 2 + 2 files changed, 153 insertions(+) (limited to 'project') diff --git a/project/Build.scala b/project/Build.scala index 66a380e96..4a22f3d82 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -4,6 +4,9 @@ import java.io.{ RandomAccessFile, File } import java.nio.channels.FileLock import scala.reflect.io.Path +import org.scalajs.sbtplugin.ScalaJSPlugin +import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._ + object DottyBuild extends Build { val jenkinsMemLimit = List("-Xmx1300m") @@ -33,6 +36,10 @@ object DottyBuild extends Build { ) } + /** Enforce 2.11.5. Do not let it be upgraded by dependencies. */ + private val overrideScalaVersionSetting = + ivyScala := ivyScala.value.map(_.copy(overrideScalaVersion = true)) + lazy val `dotty-interfaces` = project.in(file("interfaces")). settings( // Do not append Scala versions to the generated artifacts @@ -44,6 +51,8 @@ object DottyBuild extends Build { lazy val dotty = project.in(file(".")). dependsOn(`dotty-interfaces`). settings( + overrideScalaVersionSetting, + // set sources to src/, tests to test/ and resources to resources/ scalaSource in Compile := baseDirectory.value / "src", javaSource in Compile := baseDirectory.value / "src", @@ -74,6 +83,8 @@ object DottyBuild extends Build { "org.scala-lang.modules" %% "scala-partest" % "1.0.11" % "test", "com.novocode" % "junit-interface" % "0.11" % "test", "jline" % "jline" % "2.12"), + libraryDependencies += + "org.scala-js" %% "scalajs-ir" % scalaJSVersion, // enable improved incremental compilation algorithm incOptions := incOptions.value.withNameHashing(true), @@ -144,9 +155,54 @@ object DottyBuild extends Build { addCommandAlias("partest-only-no-bootstrap", ";test:package;package; lockPartestFile;test:test-only dotc.tests;runPartestRunner") ) + /** A sandbox to play with the Scala.js back-end of dotty. + * + * This sandbox is compiled with dotty with support for Scala.js. It can be + * used like any regular Scala.js project. In particular, `fastOptJS` will + * produce a .js file, and `run` will run the JavaScript code with a JS VM. + * + * Simply running `dotty/run -scalajs` without this sandbox is not very + * useful, as that would not provide the linker and JS runners. + */ + lazy val sjsSandbox = project.in(file("sandbox/scalajs")). + enablePlugins(ScalaJSPlugin). + settings( + overrideScalaVersionSetting, + + /* Remove the Scala.js compiler plugin for scalac, and enable the + * Scala.js back-end of dotty instead. + */ + libraryDependencies ~= { deps => + deps.filterNot(_.name.startsWith("scalajs-compiler")) + }, + scalacOptions += "-scalajs", + + // The main class cannot be found automatically due to the empty inc.Analysis + mainClass in Compile := Some("hello.world"), + + // While developing the Scala.js back-end, it is very useful to see the trees dotc gives us + scalacOptions += "-Xprint:labelDef", + + /* Debug-friendly Scala.js optimizer options. + * In particular, typecheck the Scala.js IR found on the classpath. + */ + scalaJSOptimizerOptions ~= { + _.withCheckScalaJSIR(true).withParallel(false) + } + ). + settings(compileWithDottySettings). + settings(inConfig(Compile)(Seq( + /* Make sure jsDependencyManifest runs after compile, otherwise compile + * might remove the entire directory afterwards. + */ + jsDependencyManifest <<= jsDependencyManifest.dependsOn(compile) + ))) + lazy val `dotty-bench` = project.in(file("bench")). dependsOn(dotty % "compile->test"). settings( + overrideScalaVersionSetting, + baseDirectory in (Test,run) := (baseDirectory in dotty).value, libraryDependencies ++= Seq("com.storm-enroute" %% "scalameter" % "0.6" % Test, @@ -198,4 +254,99 @@ object DottyBuild extends Build { }) case None => throw new RuntimeException("ERROR: sbt getJarPaths: ivyHome not defined") } + + // Compile with dotty + lazy val compileWithDottySettings = { + inConfig(Compile)(inTask(compile)(Defaults.runnerTask) ++ Seq( + // Compile with dotty + fork in compile := true, + + compile := { + val inputs = (compileInputs in compile).value + import inputs.config._ + + val s = streams.value + val logger = s.log + val cacheDir = s.cacheDirectory + + // Discover classpaths + + def cpToString(cp: Seq[File]) = + cp.map(_.getAbsolutePath).mkString(java.io.File.pathSeparator) + + val compilerCp = Attributed.data((fullClasspath in (dotty, Compile)).value) + val cpStr = cpToString(classpath ++ compilerCp) + + // List all my dependencies (recompile if any of these changes) + + val allMyDependencies = classpath filterNot (_ == classesDirectory) flatMap { cpFile => + if (cpFile.isDirectory) (cpFile ** "*.class").get + else Seq(cpFile) + } + + // Compile + + val cachedCompile = FileFunction.cached(cacheDir / "compile", + FilesInfo.lastModified, FilesInfo.exists) { dependencies => + + logger.info( + "Compiling %d Scala sources to %s..." format ( + sources.size, classesDirectory)) + + if (classesDirectory.exists) + IO.delete(classesDirectory) + IO.createDirectory(classesDirectory) + + val sourcesArgs = sources.map(_.getAbsolutePath()).toList + + /* run.run() below in doCompile() will emit a call to its + * logger.info("Running dotty.tools.dotc.Main [...]") + * which we do not want to see. We use this patched logger to + * filter out that particular message. + */ + val patchedLogger = new Logger { + def log(level: Level.Value, message: => String) = { + val msg = message + if (level != Level.Info || + !msg.startsWith("Running dotty.tools.dotc.Main")) + logger.log(level, msg) + } + def success(message: => String) = logger.success(message) + def trace(t: => Throwable) = logger.trace(t) + } + + def doCompile(sourcesArgs: List[String]): Unit = { + val run = (runner in compile).value + run.run("dotty.tools.dotc.Main", compilerCp, + "-classpath" :: cpStr :: + "-d" :: classesDirectory.getAbsolutePath() :: + options ++: + sourcesArgs, + patchedLogger) foreach sys.error + } + + // Work around the Windows limitation on command line length. + val isWindows = + System.getProperty("os.name").toLowerCase().indexOf("win") >= 0 + if ((fork in compile).value && isWindows && + (sourcesArgs.map(_.length).sum > 1536)) { + IO.withTemporaryFile("sourcesargs", ".txt") { sourceListFile => + IO.writeLines(sourceListFile, sourcesArgs) + doCompile(List("@"+sourceListFile.getAbsolutePath())) + } + } else { + doCompile(sourcesArgs) + } + + // Output is all files in classesDirectory + (classesDirectory ** AllPassFilter).get.toSet + } + + cachedCompile((sources ++ allMyDependencies).toSet) + + // We do not have dependency analysis when compiling externally + sbt.inc.Analysis.Empty + } + )) + } } diff --git a/project/plugins.sbt b/project/plugins.sbt index f9c0dbf1a..0c8b7737e 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -6,3 +6,5 @@ addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "4.0.0") addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "0.8.0") + +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.7") -- cgit v1.2.3