@@ -4,6 +4,9 @@ import java.io.{ RandomAccessFile, File }
4
4
import java .nio .channels .FileLock
5
5
import scala .reflect .io .Path
6
6
7
+ import org .scalajs .sbtplugin .ScalaJSPlugin
8
+ import org .scalajs .sbtplugin .ScalaJSPlugin .autoImport ._
9
+
7
10
object DottyBuild extends Build {
8
11
9
12
val jenkinsMemLimit = List (" -Xmx1300m" )
@@ -33,6 +36,10 @@ object DottyBuild extends Build {
33
36
)
34
37
}
35
38
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
+
36
43
lazy val `dotty-interfaces` = project.in(file(" interfaces" )).
37
44
settings(
38
45
// Do not append Scala versions to the generated artifacts
@@ -44,6 +51,8 @@ object DottyBuild extends Build {
44
51
lazy val dotty = project.in(file(" ." )).
45
52
dependsOn(`dotty-interfaces`).
46
53
settings(
54
+ overrideScalaVersionSetting,
55
+
47
56
// set sources to src/, tests to test/ and resources to resources/
48
57
scalaSource in Compile := baseDirectory.value / " src" ,
49
58
javaSource in Compile := baseDirectory.value / " src" ,
@@ -105,6 +114,38 @@ object DottyBuild extends Build {
105
114
runTask(Test , " dotty.partest.DPConsoleRunner" , dottyJars + " " + args.mkString(" " ))
106
115
},
107
116
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
+
108
149
// Adjust classpath for running dotty
109
150
mainClass in (Compile , run) := Some (" dotty.tools.dotc.Main" ),
110
151
fork in run := true ,
@@ -144,9 +185,54 @@ object DottyBuild extends Build {
144
185
addCommandAlias(" partest-only-no-bootstrap" , " ;test:package;package; lockPartestFile;test:test-only dotc.tests;runPartestRunner" )
145
186
)
146
187
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
+
147
231
lazy val `dotty-bench` = project.in(file(" bench" )).
148
232
dependsOn(dotty % " compile->test" ).
149
233
settings(
234
+ overrideScalaVersionSetting,
235
+
150
236
baseDirectory in (Test ,run) := (baseDirectory in dotty).value,
151
237
152
238
libraryDependencies ++= Seq (" com.storm-enroute" %% " scalameter" % " 0.6" % Test ,
@@ -198,4 +284,99 @@ object DottyBuild extends Build {
198
284
})
199
285
case None => throw new RuntimeException (" ERROR: sbt getJarPaths: ivyHome not defined" )
200
286
}
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
+ }
201
382
}
0 commit comments