Skip to content

Commit a509267

Browse files
committed
Merge pull request #1126 from sjrd/scalajs
Initial infrastructure and hello world for the Scala.js back-end.
2 parents 6f82c22 + 9024792 commit a509267

18 files changed

+2221
-11
lines changed

project/Build.scala

Lines changed: 181 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
val jenkinsMemLimit = List("-Xmx1300m")
@@ -33,6 +36,10 @@ object DottyBuild extends Build {
3336
)
3437
}
3538

39+
/** Enforce 2.11.5. Do not let it be upgraded by dependencies. */
40+
private val overrideScalaVersionSetting =
41+
ivyScala := ivyScala.value.map(_.copy(overrideScalaVersion = true))
42+
3643
lazy val `dotty-interfaces` = project.in(file("interfaces")).
3744
settings(
3845
// Do not append Scala versions to the generated artifacts
@@ -44,6 +51,8 @@ object DottyBuild extends Build {
4451
lazy val dotty = project.in(file(".")).
4552
dependsOn(`dotty-interfaces`).
4653
settings(
54+
overrideScalaVersionSetting,
55+
4756
// set sources to src/, tests to test/ and resources to resources/
4857
scalaSource in Compile := baseDirectory.value / "src",
4958
javaSource in Compile := baseDirectory.value / "src",
@@ -105,6 +114,38 @@ object DottyBuild extends Build {
105114
runTask(Test, "dotty.partest.DPConsoleRunner", dottyJars + " " + args.mkString(" "))
106115
},
107116

117+
/* Add the sources of scalajs-ir.
118+
* To guarantee that dotty can bootstrap without depending on a version
119+
* of scalajs-ir built with a different Scala compiler, we add its
120+
* sources instead of depending on the binaries.
121+
*/
122+
ivyConfigurations += config("sourcedeps").hide,
123+
libraryDependencies +=
124+
"org.scala-js" %% "scalajs-ir" % scalaJSVersion % "sourcedeps",
125+
sourceGenerators in Compile += Def.task {
126+
val s = streams.value
127+
val cacheDir = s.cacheDirectory
128+
val trgDir = (sourceManaged in Compile).value / "scalajs-ir-src"
129+
130+
val report = updateClassifiers.value
131+
val scalaJSIRSourcesJar = report.select(
132+
configuration = Set("sourcedeps"),
133+
module = (_: ModuleID).name.startsWith("scalajs-ir_"),
134+
artifact = artifactFilter(`type` = "src")).headOption.getOrElse {
135+
sys.error(s"Could not fetch scalajs-ir sources")
136+
}
137+
138+
FileFunction.cached(cacheDir / s"fetchScalaJSIRSource",
139+
FilesInfo.lastModified, FilesInfo.exists) { dependencies =>
140+
s.log.info(s"Unpacking scalajs-ir sources to $trgDir...")
141+
if (trgDir.exists)
142+
IO.delete(trgDir)
143+
IO.createDirectory(trgDir)
144+
IO.unzip(scalaJSIRSourcesJar, trgDir)
145+
(trgDir ** "*.scala").get.toSet
146+
} (Set(scalaJSIRSourcesJar)).toSeq
147+
}.taskValue,
148+
108149
// Adjust classpath for running dotty
109150
mainClass in (Compile, run) := Some("dotty.tools.dotc.Main"),
110151
fork in run := true,
@@ -144,9 +185,54 @@ object DottyBuild extends Build {
144185
addCommandAlias("partest-only-no-bootstrap", ";test:package;package; lockPartestFile;test:test-only dotc.tests;runPartestRunner")
145186
)
146187

188+
/** A sandbox to play with the Scala.js back-end of dotty.
189+
*
190+
* This sandbox is compiled with dotty with support for Scala.js. It can be
191+
* used like any regular Scala.js project. In particular, `fastOptJS` will
192+
* produce a .js file, and `run` will run the JavaScript code with a JS VM.
193+
*
194+
* Simply running `dotty/run -scalajs` without this sandbox is not very
195+
* useful, as that would not provide the linker and JS runners.
196+
*/
197+
lazy val sjsSandbox = project.in(file("sandbox/scalajs")).
198+
enablePlugins(ScalaJSPlugin).
199+
settings(
200+
overrideScalaVersionSetting,
201+
202+
/* Remove the Scala.js compiler plugin for scalac, and enable the
203+
* Scala.js back-end of dotty instead.
204+
*/
205+
libraryDependencies ~= { deps =>
206+
deps.filterNot(_.name.startsWith("scalajs-compiler"))
207+
},
208+
scalacOptions += "-scalajs",
209+
210+
// The main class cannot be found automatically due to the empty inc.Analysis
211+
mainClass in Compile := Some("hello.world"),
212+
213+
// While developing the Scala.js back-end, it is very useful to see the trees dotc gives us
214+
scalacOptions += "-Xprint:labelDef",
215+
216+
/* Debug-friendly Scala.js optimizer options.
217+
* In particular, typecheck the Scala.js IR found on the classpath.
218+
*/
219+
scalaJSOptimizerOptions ~= {
220+
_.withCheckScalaJSIR(true).withParallel(false)
221+
}
222+
).
223+
settings(compileWithDottySettings).
224+
settings(inConfig(Compile)(Seq(
225+
/* Make sure jsDependencyManifest runs after compile, otherwise compile
226+
* might remove the entire directory afterwards.
227+
*/
228+
jsDependencyManifest <<= jsDependencyManifest.dependsOn(compile)
229+
)))
230+
147231
lazy val `dotty-bench` = project.in(file("bench")).
148232
dependsOn(dotty % "compile->test").
149233
settings(
234+
overrideScalaVersionSetting,
235+
150236
baseDirectory in (Test,run) := (baseDirectory in dotty).value,
151237

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

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)