Skip to content

Commit ae93d14

Browse files
committed
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.
1 parent dd12d25 commit ae93d14

File tree

12 files changed

+2152
-1
lines changed

12 files changed

+2152
-1
lines changed

project/Build.scala

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import java.io.{ RandomAccessFile, File }
44
import java.nio.channels.FileLock
55
import scala.reflect.io.Path
66

7+
import org.scalajs.sbtplugin.ScalaJSPlugin
8+
import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._
9+
710
object DottyBuild extends Build {
811

912
// Currently, this cannot be increased without hitting the maximum amount of memory
@@ -35,6 +38,10 @@ object DottyBuild extends Build {
3538
)
3639
}
3740

41+
/** Enforce 2.11.5. Do not let it be upgraded by dependencies. */
42+
private val overrideScalaVersionSetting =
43+
ivyScala := ivyScala.value.map(_.copy(overrideScalaVersion = true))
44+
3845
lazy val `dotty-interfaces` = project.in(file("interfaces")).
3946
settings(
4047
// Do not append Scala versions to the generated artifacts
@@ -46,6 +53,8 @@ object DottyBuild extends Build {
4653
lazy val dotty = project.in(file(".")).
4754
dependsOn(`dotty-interfaces`).
4855
settings(
56+
overrideScalaVersionSetting,
57+
4958
// set sources to src/, tests to test/ and resources to resources/
5059
scalaSource in Compile := baseDirectory.value / "src",
5160
javaSource in Compile := baseDirectory.value / "src",
@@ -76,6 +85,8 @@ object DottyBuild extends Build {
7685
"org.scala-lang.modules" %% "scala-partest" % "1.0.11" % "test",
7786
"com.novocode" % "junit-interface" % "0.11" % "test",
7887
"jline" % "jline" % "2.12"),
88+
libraryDependencies +=
89+
"org.scala-js" %% "scalajs-ir" % scalaJSVersion,
7990

8091
// enable improved incremental compilation algorithm
8192
incOptions := incOptions.value.withNameHashing(true),
@@ -146,9 +157,54 @@ object DottyBuild extends Build {
146157
addCommandAlias("partest-only-no-bootstrap", ";test:package;package; lockPartestFile;test:test-only dotc.tests;runPartestRunner")
147158
)
148159

160+
/** A sandbox to play with the Scala.js back-end of dotty.
161+
*
162+
* This sandbox is compiled with dotty with support for Scala.js. It can be
163+
* used like any regular Scala.js project. In particular, `fastOptJS` will
164+
* produce a .js file, and `run` will run the JavaScript code with a JS VM.
165+
*
166+
* Simply running `dotty/run -scalajs` without this sandbox is not very
167+
* useful, as that would not provide the linker and JS runners.
168+
*/
169+
lazy val sjsSandbox = project.in(file("sandbox/scalajs")).
170+
enablePlugins(ScalaJSPlugin).
171+
settings(
172+
overrideScalaVersionSetting,
173+
174+
/* Remove the Scala.js compiler plugin for scalac, and enable the
175+
* Scala.js back-end of dotty instead.
176+
*/
177+
libraryDependencies ~= { deps =>
178+
deps.filterNot(_.name.startsWith("scalajs-compiler"))
179+
},
180+
scalacOptions += "-scalajs",
181+
182+
// The main class cannot be found automatically due to the empty inc.Analysis
183+
mainClass in Compile := Some("hello.world"),
184+
185+
// While developing the Scala.js back-end, it is very useful to see the trees dotc gives us
186+
scalacOptions += "-Xprint:labelDef",
187+
188+
/* Debug-friendly Scala.js optimizer options.
189+
* In particular, typecheck the Scala.js IR found on the classpath.
190+
*/
191+
scalaJSOptimizerOptions ~= {
192+
_.withCheckScalaJSIR(true).withParallel(false)
193+
}
194+
).
195+
settings(compileWithDottySettings).
196+
settings(inConfig(Compile)(Seq(
197+
/* Make sure jsDependencyManifest runs after compile, otherwise compile
198+
* might remove the entire directory afterwards.
199+
*/
200+
jsDependencyManifest <<= jsDependencyManifest.dependsOn(compile)
201+
)))
202+
149203
lazy val `dotty-bench` = project.in(file("bench")).
150204
dependsOn(dotty % "compile->test").
151205
settings(
206+
overrideScalaVersionSetting,
207+
152208
baseDirectory in (Test,run) := (baseDirectory in dotty).value,
153209

154210
libraryDependencies ++= Seq("com.storm-enroute" %% "scalameter" % "0.6" % Test,
@@ -200,4 +256,99 @@ object DottyBuild extends Build {
200256
})
201257
case None => throw new RuntimeException("ERROR: sbt getJarPaths: ivyHome not defined")
202258
}
259+
260+
// Compile with dotty
261+
lazy val compileWithDottySettings = {
262+
inConfig(Compile)(inTask(compile)(Defaults.runnerTask) ++ Seq(
263+
// Compile with dotty
264+
fork in compile := true,
265+
266+
compile := {
267+
val inputs = (compileInputs in compile).value
268+
import inputs.config._
269+
270+
val s = streams.value
271+
val logger = s.log
272+
val cacheDir = s.cacheDirectory
273+
274+
// Discover classpaths
275+
276+
def cpToString(cp: Seq[File]) =
277+
cp.map(_.getAbsolutePath).mkString(java.io.File.pathSeparator)
278+
279+
val compilerCp = Attributed.data((fullClasspath in (dotty, Compile)).value)
280+
val cpStr = cpToString(classpath ++ compilerCp)
281+
282+
// List all my dependencies (recompile if any of these changes)
283+
284+
val allMyDependencies = classpath filterNot (_ == classesDirectory) flatMap { cpFile =>
285+
if (cpFile.isDirectory) (cpFile ** "*.class").get
286+
else Seq(cpFile)
287+
}
288+
289+
// Compile
290+
291+
val cachedCompile = FileFunction.cached(cacheDir / "compile",
292+
FilesInfo.lastModified, FilesInfo.exists) { dependencies =>
293+
294+
logger.info(
295+
"Compiling %d Scala sources to %s..." format (
296+
sources.size, classesDirectory))
297+
298+
if (classesDirectory.exists)
299+
IO.delete(classesDirectory)
300+
IO.createDirectory(classesDirectory)
301+
302+
val sourcesArgs = sources.map(_.getAbsolutePath()).toList
303+
304+
/* run.run() below in doCompile() will emit a call to its
305+
* logger.info("Running dotty.tools.dotc.Main [...]")
306+
* which we do not want to see. We use this patched logger to
307+
* filter out that particular message.
308+
*/
309+
val patchedLogger = new Logger {
310+
def log(level: Level.Value, message: => String) = {
311+
val msg = message
312+
if (level != Level.Info ||
313+
!msg.startsWith("Running dotty.tools.dotc.Main"))
314+
logger.log(level, msg)
315+
}
316+
def success(message: => String) = logger.success(message)
317+
def trace(t: => Throwable) = logger.trace(t)
318+
}
319+
320+
def doCompile(sourcesArgs: List[String]): Unit = {
321+
val run = (runner in compile).value
322+
run.run("dotty.tools.dotc.Main", compilerCp,
323+
"-classpath" :: cpStr ::
324+
"-d" :: classesDirectory.getAbsolutePath() ::
325+
options ++:
326+
sourcesArgs,
327+
patchedLogger) foreach sys.error
328+
}
329+
330+
// Work around the Windows limitation on command line length.
331+
val isWindows =
332+
System.getProperty("os.name").toLowerCase().indexOf("win") >= 0
333+
if ((fork in compile).value && isWindows &&
334+
(sourcesArgs.map(_.length).sum > 1536)) {
335+
IO.withTemporaryFile("sourcesargs", ".txt") { sourceListFile =>
336+
IO.writeLines(sourceListFile, sourcesArgs)
337+
doCompile(List("@"+sourceListFile.getAbsolutePath()))
338+
}
339+
} else {
340+
doCompile(sourcesArgs)
341+
}
342+
343+
// Output is all files in classesDirectory
344+
(classesDirectory ** AllPassFilter).get.toSet
345+
}
346+
347+
cachedCompile((sources ++ allMyDependencies).toSet)
348+
349+
// We do not have dependency analysis when compiling externally
350+
sbt.inc.Analysis.Empty
351+
}
352+
))
353+
}
203354
}

project/plugins.sbt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@
66
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "4.0.0")
77

88
addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "0.8.0")
9+
10+
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.7")

sandbox/scalajs/hello.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package hello
2+
3+
import scala.scalajs.js
4+
5+
trait MyTrait {
6+
val x = 5
7+
def foo(y: Int) = x
8+
}
9+
10+
object world extends js.JSApp with MyTrait {
11+
def main(): Unit = {
12+
println("hello dotty.js!")
13+
println(foo(4))
14+
}
15+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package dotty.tools.backend.sjs
2+
3+
import dotty.tools.dotc.core._
4+
import Contexts._
5+
import Phases._
6+
7+
/** Generates Scala.js IR files for the compilation unit. */
8+
class GenSJSIR extends Phase {
9+
def phaseName: String = "genSJSIR"
10+
11+
def run(implicit ctx: Context): Unit = {
12+
new JSCodeGen().run()
13+
}
14+
}

0 commit comments

Comments
 (0)