Skip to content

Commit 70e6665

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 6e63805 commit 70e6665

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,8 +38,14 @@ 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 = project.in(file(".")).
3946
settings(
47+
overrideScalaVersionSetting,
48+
4049
// set sources to src/, tests to test/ and resources to resources/
4150
scalaSource in Compile := baseDirectory.value / "src",
4251
javaSource in Compile := baseDirectory.value / "src",
@@ -67,6 +76,8 @@ object DottyBuild extends Build {
6776
"org.scala-lang.modules" %% "scala-partest" % "1.0.11" % "test",
6877
"com.novocode" % "junit-interface" % "0.11" % "test",
6978
"jline" % "jline" % "2.12"),
79+
libraryDependencies +=
80+
"org.scala-js" %% "scalajs-ir" % scalaJSVersion,
7081

7182
// enable improved incremental compilation algorithm
7283
incOptions := incOptions.value.withNameHashing(true),
@@ -137,9 +148,54 @@ object DottyBuild extends Build {
137148
addCommandAlias("partest-only-no-bootstrap", ";test:package;package; lockPartestFile;test:test-only dotc.tests;runPartestRunner")
138149
)
139150

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

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

project/plugins.sbt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "0.7.0" excludeAll(
1010
ExclusionRule(organization = "com.danieltrinh")))
1111

1212
libraryDependencies += "org.scalariform" %% "scalariform" % "0.1.7"
13+
14+
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)