summaryrefslogtreecommitdiff
path: root/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/ScalaJSPluginInternal.scala
diff options
context:
space:
mode:
Diffstat (limited to 'examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/ScalaJSPluginInternal.scala')
-rw-r--r--examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/ScalaJSPluginInternal.scala598
1 files changed, 598 insertions, 0 deletions
diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/ScalaJSPluginInternal.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/ScalaJSPluginInternal.scala
new file mode 100644
index 0000000..fe97f0b
--- /dev/null
+++ b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/ScalaJSPluginInternal.scala
@@ -0,0 +1,598 @@
+package scala.scalajs.sbtplugin
+
+import sbt._
+import sbt.inc.{IncOptions, ClassfileManager}
+import Keys._
+
+import Implicits._
+import JSUtils._
+
+import scala.scalajs.tools.sem.Semantics
+import scala.scalajs.tools.io.{IO => toolsIO, _}
+import scala.scalajs.tools.classpath._
+import scala.scalajs.tools.classpath.builder._
+import scala.scalajs.tools.jsdep._
+import scala.scalajs.tools.optimizer.{
+ ScalaJSOptimizer,
+ ScalaJSClosureOptimizer,
+ IncOptimizer,
+ ParIncOptimizer
+}
+import scala.scalajs.tools.corelib.CoreJSLibs
+
+import scala.scalajs.tools.env._
+import scala.scalajs.sbtplugin.env.rhino.RhinoJSEnv
+import scala.scalajs.sbtplugin.env.nodejs.NodeJSEnv
+import scala.scalajs.sbtplugin.env.phantomjs.{PhantomJSEnv, PhantomJettyClassLoader}
+
+import scala.scalajs.ir.ScalaJSVersions
+
+import scala.scalajs.sbtplugin.testing.{TestFramework, JSClasspathLoader}
+
+import scala.util.Try
+
+import java.nio.charset.Charset
+import java.net.URLClassLoader
+
+/** Contains settings used by ScalaJSPlugin that should not be automatically
+ * be in the *.sbt file's scope.
+ */
+object ScalaJSPluginInternal {
+
+ import ScalaJSPlugin.autoImport._
+
+ /** Dummy setting to ensure we do not fork in Scala.js run & test. */
+ val scalaJSEnsureUnforked = SettingKey[Boolean]("ensureUnforked",
+ "Scala.js internal: Fails if fork is true.", KeyRanks.Invisible)
+
+ /** Dummy setting to persist Scala.js optimizer */
+ val scalaJSOptimizer = SettingKey[ScalaJSOptimizer]("scalaJSOptimizer",
+ "Scala.js internal: Setting to persist the optimizer", KeyRanks.Invisible)
+
+ /** Internal task to calculate whether a project requests the DOM
+ * (through jsDependencies or requiresDOM) */
+ val scalaJSRequestsDOM = TaskKey[Boolean]("scalaJSRequestsDOM",
+ "Scala.js internal: Whether a project really wants the DOM. " +
+ "Calculated using requiresDOM and jsDependencies", KeyRanks.Invisible)
+
+ /** Default post link environment */
+ val scalaJSDefaultPostLinkJSEnv = TaskKey[JSEnv]("scalaJSDefaultPostLinkJSEnv",
+ "Scala.js internal: Default for postLinkJSEnv", KeyRanks.Invisible)
+
+ /** Lookup key for CompleteClasspath in attribute maps */
+ val scalaJSCompleteClasspath =
+ AttributeKey[CompleteClasspath]("scalaJSCompleteClasspath")
+
+ /** Patches the IncOptions so that .sjsir files are pruned as needed.
+ *
+ * This complicated logic patches the ClassfileManager factory of the given
+ * IncOptions with one that is aware of .sjsir files emitted by the Scala.js
+ * compiler. This makes sure that, when a .class file must be deleted, the
+ * corresponding .sjsir file are also deleted.
+ */
+ def scalaJSPatchIncOptions(incOptions: IncOptions): IncOptions = {
+ val inheritedNewClassfileManager = incOptions.newClassfileManager
+ val newClassfileManager = () => new ClassfileManager {
+ private[this] val inherited = inheritedNewClassfileManager()
+
+ def delete(classes: Iterable[File]): Unit = {
+ inherited.delete(classes flatMap { classFile =>
+ val scalaJSFiles = if (classFile.getPath endsWith ".class") {
+ val f = FileVirtualFile.withExtension(classFile, ".class", ".sjsir")
+ if (f.exists) List(f)
+ else Nil
+ } else Nil
+ classFile :: scalaJSFiles
+ })
+ }
+
+ def generated(classes: Iterable[File]): Unit = inherited.generated(classes)
+ def complete(success: Boolean): Unit = inherited.complete(success)
+ }
+ incOptions.copy(newClassfileManager = newClassfileManager)
+ }
+
+ private def scalaJSOptimizerSetting(key: TaskKey[_]): Setting[_] = (
+ scalaJSOptimizer in key := {
+ val semantics = (scalaJSSemantics in key).value
+ if ((scalaJSOptimizerOptions in key).value.parallel)
+ new ScalaJSOptimizer(semantics, new ParIncOptimizer(_))
+ else
+ new ScalaJSOptimizer(semantics, new IncOptimizer(_))
+ }
+ )
+
+ val scalaJSConfigSettings: Seq[Setting[_]] = Seq(
+ incOptions ~= scalaJSPatchIncOptions
+ ) ++ Seq(
+
+ scalaJSPreLinkClasspath := {
+ val cp = fullClasspath.value
+ val pcp = PartialClasspathBuilder.build(Attributed.data(cp).toList)
+ val ccp = pcp.resolve(jsDependencyFilter.value)
+
+ if (checkScalaJSSemantics.value)
+ ccp.checkCompliance(scalaJSSemantics.value)
+
+ ccp
+ },
+
+ artifactPath in fastOptJS :=
+ ((crossTarget in fastOptJS).value /
+ ((moduleName in fastOptJS).value + "-fastopt.js")),
+
+ scalaJSOptimizerSetting(fastOptJS),
+
+ fastOptJS := {
+ val s = streams.value
+ val output = (artifactPath in fastOptJS).value
+ val taskCache =
+ WritableFileVirtualTextFile(s.cacheDirectory / "fastopt-js")
+
+ IO.createDirectory(output.getParentFile)
+
+ val relSourceMapBase =
+ if ((relativeSourceMaps in fastOptJS).value)
+ Some(output.getParentFile.toURI())
+ else None
+
+ val opts = (scalaJSOptimizerOptions in fastOptJS).value
+
+ import ScalaJSOptimizer._
+ val outCP = (scalaJSOptimizer in fastOptJS).value.optimizeCP(
+ Inputs(input = (scalaJSPreLinkClasspath in fastOptJS).value),
+ OutputConfig(
+ output = WritableFileVirtualJSFile(output),
+ cache = Some(taskCache),
+ wantSourceMap = (emitSourceMaps in fastOptJS).value,
+ relativizeSourceMapBase = relSourceMapBase,
+ checkIR = opts.checkScalaJSIR,
+ disableOptimizer = opts.disableOptimizer,
+ batchMode = opts.batchMode),
+ s.log)
+
+ Attributed.blank(output).put(scalaJSCompleteClasspath, outCP)
+ },
+ fastOptJS <<=
+ fastOptJS.dependsOn(packageJSDependencies, packageScalaJSLauncher),
+
+ artifactPath in fullOptJS :=
+ ((crossTarget in fullOptJS).value /
+ ((moduleName in fullOptJS).value + "-opt.js")),
+
+ scalaJSSemantics in fullOptJS :=
+ (scalaJSSemantics in fastOptJS).value.optimized,
+
+ scalaJSOptimizerSetting(fullOptJS),
+
+ fullOptJS := {
+ val s = streams.value
+ val output = (artifactPath in fullOptJS).value
+ val taskCache =
+ WritableFileVirtualTextFile(s.cacheDirectory / "fullopt-js")
+
+ IO.createDirectory(output.getParentFile)
+
+ val relSourceMapBase =
+ if ((relativeSourceMaps in fullOptJS).value)
+ Some(output.getParentFile.toURI())
+ else None
+
+ val opts = (scalaJSOptimizerOptions in fullOptJS).value
+
+ val semantics = (scalaJSSemantics in fullOptJS).value
+
+ import ScalaJSClosureOptimizer._
+ val outCP = new ScalaJSClosureOptimizer(semantics).optimizeCP(
+ (scalaJSOptimizer in fullOptJS).value,
+ Inputs(ScalaJSOptimizer.Inputs(
+ input = (scalaJSPreLinkClasspath in fullOptJS).value)),
+ OutputConfig(
+ output = WritableFileVirtualJSFile(output),
+ cache = Some(taskCache),
+ wantSourceMap = (emitSourceMaps in fullOptJS).value,
+ relativizeSourceMapBase = relSourceMapBase,
+ checkIR = opts.checkScalaJSIR,
+ disableOptimizer = opts.disableOptimizer,
+ batchMode = opts.batchMode,
+ prettyPrint = opts.prettyPrintFullOptJS),
+ s.log)
+
+ Attributed.blank(output).put(scalaJSCompleteClasspath, outCP)
+ },
+
+ artifactPath in packageScalaJSLauncher :=
+ ((crossTarget in packageScalaJSLauncher).value /
+ ((moduleName in packageScalaJSLauncher).value + "-launcher.js")),
+
+ skip in packageScalaJSLauncher := !persistLauncher.value,
+
+ packageScalaJSLauncher <<= Def.taskDyn {
+ if ((skip in packageScalaJSLauncher).value)
+ Def.task(Attributed.blank((artifactPath in packageScalaJSLauncher).value))
+ else Def.task {
+ mainClass.value map { mainCl =>
+ val file = (artifactPath in packageScalaJSLauncher).value
+ IO.write(file, launcherContent(mainCl), Charset.forName("UTF-8"))
+
+ // Attach the name of the main class used, (ab?)using the name key
+ Attributed(file)(AttributeMap.empty.put(name.key, mainCl))
+ } getOrElse {
+ sys.error("Cannot write launcher file, since there is no or multiple mainClasses")
+ }
+ }
+ },
+
+ artifactPath in packageJSDependencies :=
+ ((crossTarget in packageJSDependencies).value /
+ ((moduleName in packageJSDependencies).value + "-jsdeps.js")),
+
+ packageJSDependencies <<= Def.taskDyn {
+ if ((skip in packageJSDependencies).value)
+ Def.task((artifactPath in packageJSDependencies).value)
+ else Def.task {
+ val cp = scalaJSPreLinkClasspath.value
+ val output = (artifactPath in packageJSDependencies).value
+ val taskCache = WritableFileVirtualJSFile(
+ streams.value.cacheDirectory / "package-js-deps")
+
+ IO.createDirectory(output.getParentFile)
+
+ val outFile = WritableFileVirtualTextFile(output)
+ CacheUtils.cached(cp.version, outFile, Some(taskCache)) {
+ toolsIO.concatFiles(outFile, cp.jsLibs.map(_.lib))
+ }
+
+ output
+ }
+ },
+
+ jsDependencyManifest := {
+ val myModule = thisProject.value.id
+ val config = configuration.value.name
+
+ // Collect all libraries
+ val jsDeps = jsDependencies.value.collect {
+ case dep: JSModuleID if dep.configurations.forall(_ == config) =>
+ dep.jsDep
+ }
+
+ val requiresDOM = jsDependencies.value.exists {
+ case RuntimeDOM(configurations) =>
+ configurations.forall(_ == config)
+ case _ => false
+ }
+
+ val compliantSemantics = scalaJSSemantics.value.compliants
+
+ val manifest = new JSDependencyManifest(new Origin(myModule, config),
+ jsDeps.toList, requiresDOM, compliantSemantics)
+
+ // Write dependency file to class directory
+ val targetDir = classDirectory.value
+ IO.createDirectory(targetDir)
+
+ val file = targetDir / JSDependencyManifest.ManifestFileName
+ val vfile = WritableFileVirtualTextFile(file)
+
+ // Prevent writing if unnecessary to not invalidate dependencies
+ val needWrite = !vfile.exists || {
+ Try {
+ val readManifest = JSDependencyManifest.read(vfile)
+ readManifest != manifest
+ } getOrElse true
+ }
+
+ if (needWrite)
+ JSDependencyManifest.write(manifest, vfile)
+
+ file
+ },
+
+ products <<= products.dependsOn(jsDependencyManifest),
+
+ console <<= console.dependsOn(Def.task(
+ streams.value.log.warn("Scala REPL doesn't work with Scala.js. You " +
+ "are running a JVM REPL. JavaScript things won't work.")
+ )),
+
+ // Give tasks ability to check we are not forking at build reading time
+ scalaJSEnsureUnforked := {
+ if (fork.value)
+ sys.error("Scala.js cannot be run in a forked JVM")
+ else
+ true
+ },
+
+ scalaJSRequestsDOM :=
+ requiresDOM.?.value.getOrElse(scalaJSExecClasspath.value.requiresDOM),
+
+ // Default jsEnv
+ jsEnv <<= Def.taskDyn {
+ scalaJSStage.value match {
+ case Stage.PreLink =>
+ Def.task {
+ preLinkJSEnv.?.value.getOrElse {
+ new RhinoJSEnv(scalaJSSemantics.value,
+ withDOM = scalaJSRequestsDOM.value)
+ }
+ }
+ case Stage.FastOpt | Stage.FullOpt =>
+ Def.task(scalaJSDefaultPostLinkJSEnv.value)
+ }
+ },
+
+ // Wire jsEnv and sources for other stages
+ scalaJSDefaultPostLinkJSEnv := postLinkJSEnv.?.value.getOrElse {
+ if (scalaJSRequestsDOM.value)
+ new PhantomJSEnv(jettyClassLoader = scalaJSPhantomJSClassLoader.value)
+ else
+ new NodeJSEnv
+ },
+
+ scalaJSExecClasspath <<= Def.taskDyn {
+ scalaJSStage.value match {
+ case Stage.PreLink =>
+ Def.task { scalaJSPreLinkClasspath.value }
+ case Stage.FastOpt =>
+ Def.task { fastOptJS.value.get(scalaJSCompleteClasspath).get }
+ case Stage.FullOpt =>
+ Def.task { fullOptJS.value.get(scalaJSCompleteClasspath).get }
+ }
+ }
+ )
+
+ /** Run a class in a given environment using a given launcher */
+ private def jsRun(env: JSEnv, cp: CompleteClasspath, mainCl: String,
+ launcher: VirtualJSFile, jsConsole: JSConsole, log: Logger) = {
+
+ log.info("Running " + mainCl)
+ log.debug(s"with JSEnv of type ${env.getClass()}")
+ log.debug(s"with classpath of type ${cp.getClass}")
+
+ // Actually run code
+ env.jsRunner(cp, launcher, log, jsConsole).run()
+ }
+
+ private def launcherContent(mainCl: String) = {
+ // If we are running in Node.js, we need to bracket select on
+ // global rather than this
+ """((typeof global === "object" && global &&
+ global["Object"] === Object) ? global : this)""" +
+ s"${dot2bracket(mainCl)}().main();\n"
+ }
+
+ private def memLauncher(mainCl: String) = {
+ new MemVirtualJSFile("Generated launcher file")
+ .withContent(launcherContent(mainCl))
+ }
+
+ // These settings will be filtered by the stage dummy tasks
+ val scalaJSRunSettings = Seq(
+ mainClass in scalaJSLauncher := (mainClass in run).value,
+ scalaJSLauncher <<= Def.taskDyn {
+ if (persistLauncher.value)
+ Def.task(packageScalaJSLauncher.value.map(FileVirtualJSFile))
+ else Def.task {
+ (mainClass in scalaJSLauncher).value map { mainClass =>
+ val memLaunch = memLauncher(mainClass)
+ Attributed[VirtualJSFile](memLaunch)(
+ AttributeMap.empty.put(name.key, mainClass))
+ } getOrElse {
+ sys.error("No main class detected.")
+ }
+ }
+ },
+
+ /* We do currently not discover objects containing a
+ *
+ * def main(args: Array[String]): Unit
+ *
+ * Support will be added again, as soon as we can run them
+ * reliably (e.g. without implicitly requiring that an exported
+ *
+ * def main(): Unit
+ *
+ * exists alongside.
+ */
+ discoveredMainClasses := {
+ import xsbt.api.{Discovered, Discovery}
+
+ val jsApp = "scala.scalajs.js.JSApp"
+
+ def isJSApp(discovered: Discovered) =
+ discovered.isModule && discovered.baseClasses.contains(jsApp)
+
+ Discovery(Set(jsApp), Set.empty)(Tests.allDefs(compile.value)) collect {
+ case (definition, discovered) if isJSApp(discovered) =>
+ definition.name
+ }
+ },
+
+ run <<= Def.inputTask {
+ // use assert to prevent warning about pure expr in stat pos
+ assert(scalaJSEnsureUnforked.value)
+
+ val launch = scalaJSLauncher.value
+ val className = launch.get(name.key).getOrElse("<unknown class>")
+ jsRun(jsEnv.value, scalaJSExecClasspath.value, className,
+ launch.data, scalaJSConsole.value, streams.value.log)
+ },
+
+ runMain <<= {
+ // Implicits for parsing
+ import sbinary.DefaultProtocol.StringFormat
+ import Cache.seqFormat
+
+ val parser = Defaults.loadForParser(discoveredMainClasses)((s, names) =>
+ Defaults.runMainParser(s, names getOrElse Nil))
+
+ Def.inputTask {
+ // use assert to prevent warning about pure expr in stat pos
+ assert(scalaJSEnsureUnforked.value)
+
+ val mainCl = parser.parsed._1
+ jsRun(jsEnv.value, scalaJSExecClasspath.value, mainCl,
+ memLauncher(mainCl), scalaJSConsole.value, streams.value.log)
+ }
+ }
+ )
+
+ val scalaJSCompileSettings = (
+ scalaJSConfigSettings ++
+ scalaJSRunSettings
+ )
+
+ val scalaJSTestFrameworkSettings = Seq(
+ // Copied from Defaults, but scoped. We need a JVM loader in
+ // loadedTestFrameworks to find out whether the framework exists.
+ testLoader in loadedTestFrameworks := {
+ TestFramework.createTestLoader(
+ Attributed.data(fullClasspath.value),
+ scalaInstance.value,
+ IO.createUniqueDirectory(taskTemporaryDirectory.value))
+ },
+
+ loadedTestFrameworks := {
+ // use assert to prevent warning about pure expr in stat pos
+ assert(scalaJSEnsureUnforked.value)
+
+ val loader = (testLoader in loadedTestFrameworks).value
+ val isTestFrameworkDefined = try {
+ Class.forName(scalaJSTestFramework.value, false, loader)
+ true
+ } catch {
+ case _: ClassNotFoundException => false
+ }
+ if (isTestFrameworkDefined) {
+ loadedTestFrameworks.value.updated(
+ sbt.TestFramework(classOf[TestFramework].getName),
+ new TestFramework(
+ environment = jsEnv.value,
+ jsConsole = scalaJSConsole.value,
+ testFramework = scalaJSTestFramework.value)
+ )
+ } else {
+ loadedTestFrameworks.value
+ }
+ },
+
+ // Pseudo loader to pass classpath to test framework
+ testLoader := JSClasspathLoader(scalaJSExecClasspath.value)
+ )
+
+ val scalaJSTestBuildSettings = (
+ scalaJSConfigSettings
+ ) ++ (
+ Seq(fastOptJS, fullOptJS, packageScalaJSLauncher,
+ packageJSDependencies) map { packageJSTask =>
+ moduleName in packageJSTask := moduleName.value + "-test"
+ }
+ )
+
+ val scalaJSTestSettings = (
+ scalaJSTestBuildSettings ++
+ scalaJSTestFrameworkSettings
+ )
+
+ val scalaJSDependenciesSettings = Seq(
+ // add all the webjars your jsDependencies depend upon
+ libraryDependencies ++= jsDependencies.value.collect {
+ case JarJSModuleID(module, _) => module
+ }
+ )
+
+ val scalaJSDefaultBuildConfigs = (
+ inConfig(Compile)(scalaJSConfigSettings) ++ // build settings for Compile
+ inConfig(Test)(scalaJSTestBuildSettings) ++
+ scalaJSDependenciesSettings
+ )
+
+ val scalaJSDefaultConfigs = (
+ inConfig(Compile)(scalaJSCompileSettings) ++
+ inConfig(Test)(scalaJSTestSettings) ++
+ scalaJSDependenciesSettings
+ )
+
+ val phantomJSJettyModules = Seq(
+ "org.eclipse.jetty" % "jetty-websocket" % "8.1.16.v20140903",
+ "org.eclipse.jetty" % "jetty-server" % "8.1.16.v20140903"
+ )
+
+ val scalaJSProjectBaseSettings = Seq(
+ relativeSourceMaps := false,
+ persistLauncher := false,
+
+ skip in packageJSDependencies := true,
+
+ scalaJSTestFramework := "org.scalajs.jasminetest.JasmineTestFramework",
+
+ emitSourceMaps := true,
+
+ scalaJSOptimizerOptions := OptimizerOptions(),
+
+ jsDependencies := Seq(),
+ jsDependencyFilter := identity,
+
+ scalaJSSemantics := Semantics.Defaults,
+ checkScalaJSSemantics := true,
+
+ scalaJSConsole := ConsoleJSConsole,
+
+ clean <<= clean.dependsOn(Def.task {
+ // have clean reset incremental optimizer state
+ (scalaJSOptimizer in (Compile, fastOptJS)).value.clean()
+ (scalaJSOptimizer in (Test, fastOptJS)).value.clean()
+ }),
+
+ /* Depend on jetty artifacts in dummy configuration to be able to inject
+ * them into the PhantomJS runner if necessary.
+ * See scalaJSPhantomJSClassLoader
+ */
+ ivyConfigurations += config("phantom-js-jetty").hide,
+ libraryDependencies ++= phantomJSJettyModules.map(_ % "phantom-js-jetty"),
+ scalaJSPhantomJSClassLoader := {
+ val report = update.value
+ val jars = report.select(configurationFilter("phantom-js-jetty"))
+
+ val jettyLoader =
+ new URLClassLoader(jars.map(_.toURI.toURL).toArray, null)
+
+ new PhantomJettyClassLoader(jettyLoader, getClass.getClassLoader)
+ }
+ )
+
+ val scalaJSAbstractSettings: Seq[Setting[_]] = (
+ scalaJSProjectBaseSettings ++
+ scalaJSDefaultConfigs
+ )
+
+ val scalaJSAbstractBuildSettings: Seq[Setting[_]] = (
+ scalaJSProjectBaseSettings ++
+ scalaJSDefaultBuildConfigs
+ )
+
+ val scalaJSReleasesResolver = Resolver.url("scala-js-releases",
+ url("http://dl.bintray.com/content/scala-js/scala-js-releases"))(
+ Resolver.ivyStylePatterns)
+ val scalaJSSnapshotsResolver = Resolver.url("scala-js-snapshots",
+ url("http://repo.scala-js.org/repo/snapshots/"))(
+ Resolver.ivyStylePatterns)
+
+ val scalaJSEcosystemSettings = Seq(
+ // the resolver to find the compiler and library (and others)
+ resolvers ++= Seq(scalaJSReleasesResolver, scalaJSSnapshotsResolver),
+
+ // you will need the Scala.js compiler plugin
+ autoCompilerPlugins := true,
+ addCompilerPlugin(
+ "org.scala-lang.modules.scalajs" % "scalajs-compiler" % scalaJSVersion cross CrossVersion.full),
+
+ // and of course the Scala.js library
+ libraryDependencies += "org.scala-lang.modules.scalajs" %% "scalajs-library" % scalaJSVersion,
+
+ // and you will want to be cross-compiled on the Scala.js binary version
+ crossVersion := ScalaJSCrossVersion.binary
+ )
+
+}