summaryrefslogtreecommitdiff
path: root/tools/shared/src/main/scala/scala/scalajs/tools/classpath
diff options
context:
space:
mode:
Diffstat (limited to 'tools/shared/src/main/scala/scala/scalajs/tools/classpath')
-rw-r--r--tools/shared/src/main/scala/scala/scalajs/tools/classpath/CompleteClasspath.scala35
-rw-r--r--tools/shared/src/main/scala/scala/scalajs/tools/classpath/ComplianceRequirement.scala7
-rw-r--r--tools/shared/src/main/scala/scala/scalajs/tools/classpath/Exceptions.scala42
-rw-r--r--tools/shared/src/main/scala/scala/scalajs/tools/classpath/IRClasspath.scala69
-rw-r--r--tools/shared/src/main/scala/scala/scalajs/tools/classpath/LinkedClasspath.scala26
-rw-r--r--tools/shared/src/main/scala/scala/scalajs/tools/classpath/PartialClasspath.scala99
-rw-r--r--tools/shared/src/main/scala/scala/scalajs/tools/classpath/ResolvedJSDependency.scala10
-rw-r--r--tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/AbstractJarLibClasspathBuilder.scala53
-rw-r--r--tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/AbstractPartialClasspathBuilder.scala47
-rw-r--r--tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/ClasspathContentHandler.scala25
-rw-r--r--tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/ClasspathElementsTraverser.scala38
-rw-r--r--tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/DirTraverser.scala60
-rw-r--r--tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/FileSystem.scala57
-rw-r--r--tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/JarTraverser.scala85
14 files changed, 653 insertions, 0 deletions
diff --git a/tools/shared/src/main/scala/scala/scalajs/tools/classpath/CompleteClasspath.scala b/tools/shared/src/main/scala/scala/scalajs/tools/classpath/CompleteClasspath.scala
new file mode 100644
index 0000000..6646a7b
--- /dev/null
+++ b/tools/shared/src/main/scala/scala/scalajs/tools/classpath/CompleteClasspath.scala
@@ -0,0 +1,35 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.classpath
+
+import scala.collection.immutable.Seq
+
+import scala.scalajs.tools.io.VirtualJSFile
+import scala.scalajs.tools.jsdep.ResolutionInfo
+
+/** A classpath where nothing is missing. Therefore:
+ * - All JS libraries are resolved and ordered
+ * - The CoreJSLibs are present
+ * - Nothing can be added anymore
+ */
+abstract class CompleteClasspath(
+ /** Resolved JS libraries */
+ val jsLibs: Seq[ResolvedJSDependency],
+ val requiresDOM: Boolean,
+ val version: Option[String]
+) {
+
+ /** Fully linked Scala.js code */
+ def scalaJSCode: VirtualJSFile
+
+ /** All code in this complete classpath */
+ final def allCode: Seq[VirtualJSFile] = jsLibs.map(_.lib) :+ scalaJSCode
+
+}
diff --git a/tools/shared/src/main/scala/scala/scalajs/tools/classpath/ComplianceRequirement.scala b/tools/shared/src/main/scala/scala/scalajs/tools/classpath/ComplianceRequirement.scala
new file mode 100644
index 0000000..f6ec36f
--- /dev/null
+++ b/tools/shared/src/main/scala/scala/scalajs/tools/classpath/ComplianceRequirement.scala
@@ -0,0 +1,7 @@
+package scala.scalajs.tools.classpath
+
+import scala.scalajs.tools.jsdep.Origin
+
+/** Expresses a requirement for a given semantic to be compliant */
+final class ComplianceRequirement(
+ val semantics: String, val origins: List[Origin])
diff --git a/tools/shared/src/main/scala/scala/scalajs/tools/classpath/Exceptions.scala b/tools/shared/src/main/scala/scala/scalajs/tools/classpath/Exceptions.scala
new file mode 100644
index 0000000..62bf75f
--- /dev/null
+++ b/tools/shared/src/main/scala/scala/scalajs/tools/classpath/Exceptions.scala
@@ -0,0 +1,42 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.classpath
+
+import scala.scalajs.tools.jsdep.ResolutionInfo
+
+class MissingJSLibException(val dependencies: List[ResolutionInfo])
+ extends Exception(MissingJSLibException.mkMsg(dependencies))
+
+object MissingJSLibException {
+ private def mkMsg(deps: List[ResolutionInfo]): String = {
+ val msg = new StringBuilder()
+ msg.append("Missing dependencies: \n")
+ for (d <- deps) {
+ msg.append(s"- ${d.resourceName}")
+ msg.append(s" originating from: ${d.origins.mkString(", ")}\n")
+ }
+ msg.toString()
+ }
+}
+
+class BadComplianceException(val unmet: List[ComplianceRequirement])
+ extends Exception(BadComplianceException.mkMsg(unmet))
+
+object BadComplianceException {
+ private def mkMsg(unmets: List[ComplianceRequirement]): String = {
+ val msg = new StringBuilder()
+ msg.append("Unmet required semantic compliance(s): \n")
+ for (unmet <- unmets) {
+ msg.append(s"- ${unmet.semantics}")
+ msg.append(s" originating from: ${unmet.origins.mkString(", ")}\n")
+ }
+ msg.toString
+ }
+}
diff --git a/tools/shared/src/main/scala/scala/scalajs/tools/classpath/IRClasspath.scala b/tools/shared/src/main/scala/scala/scalajs/tools/classpath/IRClasspath.scala
new file mode 100644
index 0000000..a92293b
--- /dev/null
+++ b/tools/shared/src/main/scala/scala/scalajs/tools/classpath/IRClasspath.scala
@@ -0,0 +1,69 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.classpath
+
+import scala.collection.immutable.{Seq, Traversable}
+
+import scala.scalajs.tools.sem.Semantics
+import scala.scalajs.tools.io._
+import scala.scalajs.tools.logging._
+import scala.scalajs.tools.optimizer.ScalaJSOptimizer
+import scala.scalajs.tools.jsdep.ResolutionInfo
+
+/** A [[CompleteClasspath]] that contains only IR as scalaJSCode */
+final class IRClasspath(
+ /** The JS libraries the IR code depends on */
+ jsLibs: Seq[ResolvedJSDependency],
+ val requiredCompliance: Traversable[ComplianceRequirement],
+ /** The IR itself. Ancestor count is used for later ordering */
+ val scalaJSIR: Traversable[VirtualScalaJSIRFile],
+ requiresDOM: Boolean,
+ version: Option[String]
+) extends CompleteClasspath(jsLibs, requiresDOM, version) {
+
+ /** Orders and optimizes the contained IR.
+ *
+ * Consider using [[ScalaJSOptimizer]] for a canonical way to do so. It
+ * allows to persist the resulting file and create a source map, as well as
+ * using non-default [[Semantics]].
+ */
+ override lazy val scalaJSCode: VirtualJSFile = {
+ import ScalaJSOptimizer._
+
+ val outName = "temporary-fastOpt.js"
+
+ if (scalaJSIR.nonEmpty) {
+ val semantics = Semantics.compliantTo(requiredCompliance.map(_.semantics))
+ val output = WritableMemVirtualJSFile(outName)
+ new ScalaJSOptimizer(semantics).optimizeCP(
+ Inputs(this),
+ OutputConfig(output),
+ NullLogger)
+ output
+ } else {
+ // We cannot run the optimizer without IR, because it will complain about
+ // java.lang.Object missing. However, an empty JS file is perfectly valid
+ // for no IR at all.
+ VirtualJSFile.empty(outName)
+ }
+ }
+
+ /** Checks whether the given semantics are compliant with the requirements of
+ * this CompleteClasspath. Throws an exception otherwise.
+ */
+ final def checkCompliance(semantics: Semantics): Unit = {
+ val unmet = requiredCompliance filterNot { compliance =>
+ semantics.isCompliant(compliance.semantics)
+ }
+
+ if (unmet.nonEmpty)
+ throw new BadComplianceException(unmet.toList)
+ }
+}
diff --git a/tools/shared/src/main/scala/scala/scalajs/tools/classpath/LinkedClasspath.scala b/tools/shared/src/main/scala/scala/scalajs/tools/classpath/LinkedClasspath.scala
new file mode 100644
index 0000000..3ace785
--- /dev/null
+++ b/tools/shared/src/main/scala/scala/scalajs/tools/classpath/LinkedClasspath.scala
@@ -0,0 +1,26 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.classpath
+
+import scala.scalajs.tools.io.VirtualJSFile
+import scala.scalajs.tools.jsdep.ResolutionInfo
+
+import scala.collection.immutable.Seq
+
+/** A [[CompleteClasspath]] that is fully linked (either with the
+ * [[ScalaJSOptimizer]] or the Closure Optimizer. It contains only a single
+ * file that is scalaJSCode.
+ */
+final class LinkedClasspath(
+ jsLibs: Seq[ResolvedJSDependency],
+ val scalaJSCode: VirtualJSFile,
+ requiresDOM: Boolean,
+ version: Option[String]
+) extends CompleteClasspath(jsLibs, requiresDOM, version)
diff --git a/tools/shared/src/main/scala/scala/scalajs/tools/classpath/PartialClasspath.scala b/tools/shared/src/main/scala/scala/scalajs/tools/classpath/PartialClasspath.scala
new file mode 100644
index 0000000..949cd6e
--- /dev/null
+++ b/tools/shared/src/main/scala/scala/scalajs/tools/classpath/PartialClasspath.scala
@@ -0,0 +1,99 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.classpath
+
+import scala.collection.immutable.{Seq, Traversable}
+
+import scala.scalajs.tools.jsdep._
+import scala.scalajs.tools.io._
+import scala.scalajs.tools.corelib.CoreJSLibs
+
+/** A partial Scala.js classpath is a collection of:
+ * - Scala.js binary files *.sjsir
+ * - Native JavaScript libraries
+ * - Description of dependencies on other JavaScript libraries
+ *
+ * PartialClasspaths can be combined (using [[merge]]) and eventually resolved
+ * to a [[CompleteIRClasspath]]
+ */
+final class PartialClasspath(
+ /** Description of JS libraries the content of this classpath depends on */
+ val dependencies: Traversable[JSDependencyManifest],
+ /** JS libraries this partial classpath provides */
+ val availableLibs: Map[String, VirtualJSFile],
+ /** Scala.js IR contained in this PartialClasspath (unordered) */
+ val scalaJSIR: Traversable[VirtualScalaJSIRFile],
+ val version: Option[String]
+) {
+ import PartialClasspath.DependencyFilter
+
+ /** Merges another [[PartialClasspath]] with this one. This means:
+ * - Concatenate/merge dependencies
+ * - Merge availableLibs (libs in that shadow libs in this)
+ * - Merge Scala.js IR
+ */
+ def merge(that: PartialClasspath): PartialClasspath = {
+ new PartialClasspath(
+ this.dependencies ++ that.dependencies,
+ this.availableLibs ++ that.availableLibs,
+ this.scalaJSIR ++ that.scalaJSIR,
+ CacheUtils.joinVersions(this.version, that.version))
+ }
+
+ /** Construct a [[IRClasspath]] out of this [[PartialClasspath]] by
+ * resolving library dependencies (and failing if they are not met)
+ */
+ def resolve(filter: DependencyFilter = identity): IRClasspath = {
+ new IRClasspath(resolveDependencies(filter), mergeCompliance(), scalaJSIR,
+ dependencies.exists(_.requiresDOM), version)
+ }
+
+ /** Constructs an ordered list of JS libraries to include. Fails if:
+ * - Dependencies have cycles
+ * - Not all dependencies are available
+ */
+ protected def resolveDependencies(
+ filter: DependencyFilter): List[ResolvedJSDependency] = {
+ val flatDeps = filter(dependencies.flatMap(_.flatten))
+ val includeList = JSDependencyManifest.createIncludeList(flatDeps)
+
+ val missingDeps = includeList.filterNot { info =>
+ availableLibs.contains(info.resourceName)
+ }
+
+ if (missingDeps.nonEmpty)
+ throw new MissingJSLibException(missingDeps)
+
+ for (info <- includeList)
+ yield new ResolvedJSDependency(availableLibs(info.resourceName), info)
+ }
+
+ protected def mergeCompliance(): Traversable[ComplianceRequirement] = {
+ val flatTups = for {
+ dependency <- dependencies
+ semantics <- dependency.compliantSemantics
+ } yield (semantics, dependency.origin)
+
+ for {
+ (semantics, tups) <- flatTups.groupBy(_._1)
+ } yield new ComplianceRequirement(semantics, tups.map(_._2).toList)
+ }
+
+}
+
+object PartialClasspath {
+
+ type DependencyFilter =
+ Traversable[FlatJSDependency] => Traversable[FlatJSDependency]
+
+ /** Creates an empty PartialClasspath */
+ def empty: PartialClasspath =
+ new PartialClasspath(Nil, Map.empty, Nil, Some(""))
+}
diff --git a/tools/shared/src/main/scala/scala/scalajs/tools/classpath/ResolvedJSDependency.scala b/tools/shared/src/main/scala/scala/scalajs/tools/classpath/ResolvedJSDependency.scala
new file mode 100644
index 0000000..12ae8dc
--- /dev/null
+++ b/tools/shared/src/main/scala/scala/scalajs/tools/classpath/ResolvedJSDependency.scala
@@ -0,0 +1,10 @@
+package scala.scalajs.tools.classpath
+
+import scala.scalajs.tools.io._
+import scala.scalajs.tools.jsdep._
+
+/** A dependency on a native JavaScript library that has been successfully
+ * resolved
+ */
+final class ResolvedJSDependency(
+ val lib: VirtualJSFile, val info: ResolutionInfo)
diff --git a/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/AbstractJarLibClasspathBuilder.scala b/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/AbstractJarLibClasspathBuilder.scala
new file mode 100644
index 0000000..77f8509
--- /dev/null
+++ b/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/AbstractJarLibClasspathBuilder.scala
@@ -0,0 +1,53 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.classpath.builder
+
+import scala.collection.mutable
+
+import scala.scalajs.tools.io._
+import scala.scalajs.tools.jsdep.JSDependencyManifest
+import scala.scalajs.tools.classpath._
+
+/** reads a ScalaJS library JAR into a CP
+ * - IR files go to scalaJSCode
+ * - JS files go to availableLibs
+ * - Reads a potential top-level JS_DEPENDENCIES file
+ */
+trait AbstractJarLibClasspathBuilder extends JarTraverser {
+
+ private val irFiles = mutable.ListBuffer.empty[VirtualScalaJSIRFile]
+ private val jsFiles = mutable.Map.empty[String, VirtualJSFile]
+ private var dependency: Option[JSDependencyManifest] = None
+
+ def build(jar: File): PartialClasspath = {
+ val v = traverseJar(jar)
+ new PartialClasspath(dependency.toList,
+ jsFiles.toMap, irFiles.toList, Some(v))
+ }
+
+ override protected def handleIR(relPath: String,
+ ir: => VirtualScalaJSIRFile): Unit = {
+ // We don't need to implement shadowing here: We have only a single JAR
+ irFiles += ir
+ }
+
+ override protected def handleJS(js: => VirtualJSFile): Unit = {
+ val file = js
+ if (!jsFiles.contains(file.name))
+ jsFiles += file.name -> file
+ }
+
+ override protected def handleDepManifest(m: => JSDependencyManifest): Unit = {
+ if (dependency.isDefined)
+ sys.error("A JAR cannot have multiple JS dependency manifests")
+ dependency = Some(m)
+ }
+
+}
diff --git a/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/AbstractPartialClasspathBuilder.scala b/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/AbstractPartialClasspathBuilder.scala
new file mode 100644
index 0000000..9889f4c
--- /dev/null
+++ b/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/AbstractPartialClasspathBuilder.scala
@@ -0,0 +1,47 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.classpath.builder
+
+import scala.scalajs.tools.jsdep.JSDependencyManifest
+import scala.scalajs.tools.classpath._
+import scala.scalajs.tools.io._
+
+import scala.collection.mutable
+import scala.collection.immutable.Seq
+
+trait AbstractPartialClasspathBuilder extends ClasspathContentHandler
+ with ClasspathElementsTraverser {
+
+ private val jsDepManifests = mutable.ListBuffer.empty[JSDependencyManifest]
+ private val irFiles = mutable.Map.empty[String, VirtualScalaJSIRFile]
+ private val otherJSFiles = mutable.Map.empty[String, VirtualJSFile]
+
+ override protected def handleIR(relPath: String,
+ ir: => VirtualScalaJSIRFile): Unit = {
+ if (!irFiles.contains(relPath))
+ irFiles += relPath -> ir
+ }
+
+ override protected def handleJS(js: => VirtualJSFile): Unit = {
+ val file = js
+ if (!otherJSFiles.contains(file.name))
+ otherJSFiles += file.name -> file
+ }
+
+ override protected def handleDepManifest(m: => JSDependencyManifest): Unit = {
+ jsDepManifests += m
+ }
+
+ def build(cp: Seq[File]): PartialClasspath = {
+ val version = traverseClasspathElements(cp)
+ new PartialClasspath(jsDepManifests.toList, otherJSFiles.toMap,
+ irFiles.values.toList, Some(version))
+ }
+}
diff --git a/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/ClasspathContentHandler.scala b/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/ClasspathContentHandler.scala
new file mode 100644
index 0000000..71106c2
--- /dev/null
+++ b/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/ClasspathContentHandler.scala
@@ -0,0 +1,25 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.classpath.builder
+
+import scala.scalajs.tools.io._
+import scala.scalajs.tools.jsdep._
+import scala.scalajs.tools.classpath._
+
+import java.io._
+
+import scala.collection.immutable.Seq
+
+/** Base-trait used by traversers to handle content with callbacks */
+trait ClasspathContentHandler {
+ protected def handleIR(relPath: String, ir: => VirtualScalaJSIRFile): Unit
+ protected def handleJS(js: => VirtualJSFile): Unit
+ protected def handleDepManifest(m: => JSDependencyManifest): Unit
+}
diff --git a/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/ClasspathElementsTraverser.scala b/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/ClasspathElementsTraverser.scala
new file mode 100644
index 0000000..9403ce3
--- /dev/null
+++ b/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/ClasspathElementsTraverser.scala
@@ -0,0 +1,38 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.classpath.builder
+
+import scala.scalajs.tools.io._
+
+/** A helper trait to traverse an arbitrary classpath element
+ * (i.e. a JAR or a directory).
+ */
+trait ClasspathElementsTraverser extends JarTraverser
+ with DirTraverser
+ with FileSystem {
+
+ protected def traverseClasspathElements(cp: Seq[File]): String =
+ CacheUtils.joinVersions(cp.map(readEntriesInClasspathElement _): _*)
+
+ /** Adds the Scala.js classpath entries in a directory or jar.
+ * Returns the accumulated version
+ */
+ private def readEntriesInClasspathElement(element: File): String = {
+ if (!exists(element))
+ getDummyVersion(element)
+ else if (isDirectory(element))
+ traverseDir(element)
+ else if (isJARFile(element))
+ traverseJar(element)
+ else
+ sys.error(s"$element (in classpath) exists and is neither JAR or directory")
+ }
+
+}
diff --git a/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/DirTraverser.scala b/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/DirTraverser.scala
new file mode 100644
index 0000000..6609b29
--- /dev/null
+++ b/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/DirTraverser.scala
@@ -0,0 +1,60 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.classpath.builder
+
+import scala.scalajs.tools.io._
+import scala.scalajs.tools.jsdep.JSDependencyManifest
+
+import scala.collection.mutable
+
+trait DirTraverser extends ClasspathContentHandler with FileSystem {
+
+ /** Traverses elements, returns a version string */
+ protected def traverseDir(dir: File): String = {
+ val versions = mutable.SortedSet.empty[String]
+
+ recurseDir(dir, "", versions)
+
+ // Construct version
+ CacheUtils.joinVersions(versions.toSeq: _*)
+ }
+
+ /** Recursively adds the Scala.js classpath entries in a directory */
+ private def recurseDir(dir: File, dirPath: String,
+ versions: mutable.SortedSet[String]): Unit = {
+ val files = listFiles(dir)
+ for (file <- files) {
+ val name = getName(file)
+ if (isDirectory(file)) {
+ recurseDir(file, dirPath + name + "/", versions)
+ } else {
+ val path = dirPath + name
+ path match {
+ case JSDependencyManifest.ManifestFileName =>
+ versions += getGlobalVersion(file)
+ val reader = toReader(file)
+ try handleDepManifest(JSDependencyManifest.read(reader))
+ finally reader.close()
+
+ case _ if isJSFile(file) =>
+ versions += getGlobalVersion(file)
+ handleJS(toJSFile(file))
+
+ case _ if isIRFile(file) =>
+ versions += getGlobalVersion(file)
+ handleIR(path, toIRFile(file))
+
+ case _ => // ignore other files
+ }
+ }
+ }
+ }
+
+}
diff --git a/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/FileSystem.scala b/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/FileSystem.scala
new file mode 100644
index 0000000..99a8ca2
--- /dev/null
+++ b/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/FileSystem.scala
@@ -0,0 +1,57 @@
+package scala.scalajs.tools.classpath.builder
+
+import scala.scalajs.tools.io._
+
+import scala.collection.immutable.Traversable
+
+import java.io.{InputStream, Reader}
+
+/** Abstraction of a FileSystem, so classpath builders can be used with virtual
+ * file systems
+ */
+trait FileSystem {
+
+ type File
+
+ /** Dummy version constant to identify files for which a version can not be
+ * found.
+ * This constant should never collide with the result of getVersion.
+ */
+ val DummyVersion: String
+
+ def isDirectory(f: File): Boolean
+ def isFile(f: File): Boolean
+ def isJSFile(f: File): Boolean
+ def isIRFile(f: File): Boolean
+ def isJARFile(f: File): Boolean
+ def exists(f: File): Boolean
+
+ def getName(f: File): String
+ /** A string that uniquely identifies this file's location */
+ def getAbsolutePath(f: File): String
+ /** A string that identifies the version of a file: If it equals the version
+ * of another file with the same absolute path, the two files must be equal.
+ * This is usually the lastModified date, but ordering is not required
+ */
+ def getVersion(f: File): String
+ /** A string that globally identifies the version of a file: If it equals the
+ * global version of any other file, they must equal.
+ */
+ def getGlobalVersion(f: File): String =
+ CacheUtils.joinVersions(getAbsolutePath(f), getVersion(f))
+
+ /** A string that globally identifies a file for which a version can not be
+ * found. Example: a file that does not exists.
+ */
+ def getDummyVersion(f: File): String =
+ CacheUtils.joinVersions(getAbsolutePath(f), DummyVersion)
+
+ /** List files in a directory */
+ def listFiles(d: File): Traversable[File]
+
+ def toJSFile(f: File): VirtualJSFile
+ def toIRFile(f: File): VirtualScalaJSIRFile
+ def toReader(f: File): Reader
+ def toInputStream(f: File): InputStream
+
+}
diff --git a/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/JarTraverser.scala b/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/JarTraverser.scala
new file mode 100644
index 0000000..bbf5270
--- /dev/null
+++ b/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/JarTraverser.scala
@@ -0,0 +1,85 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.classpath.builder
+
+import scala.collection.mutable
+import scala.annotation.tailrec
+
+import java.util.zip._
+import java.io.{InputStream, InputStreamReader, Reader}
+
+import scala.scalajs.tools.io._
+import scala.scalajs.tools.jsdep.JSDependencyManifest
+
+trait JarTraverser extends ClasspathContentHandler with FileSystem {
+
+ private val jsFiles = mutable.Map.empty[String, MemVirtualJSFile]
+
+ /** Traverse a Jar and return a version */
+ protected def traverseJar(jar: File): String = {
+ val zipStream = new ZipInputStream(toInputStream(jar))
+
+ try readEntries(zipStream, getAbsolutePath(jar))
+ finally zipStream.close()
+
+ for {
+ (_, jsFile) <- jsFiles
+ if jsFile.content != "" // drop if this is just a lone sourcemap
+ } handleJS(jsFile)
+
+ getGlobalVersion(jar)
+ }
+
+ private def getOrCreateJSFile(relPath: String, fullPath: String, fn: String) =
+ jsFiles.getOrElseUpdate(relPath, new MemVirtualJSFile(fullPath) {
+ override val name = fn
+ })
+
+ @tailrec
+ private def readEntries(in: ZipInputStream, jarPath: String): Unit = {
+ val entry = in.getNextEntry()
+ if (entry != null) {
+ readEntry(entry, in, jarPath)
+ readEntries(in, jarPath)
+ }
+ }
+
+ private def readEntry(entry: ZipEntry, in: InputStream, jarPath: String) = {
+ val longName = entry.getName
+ val shortName = VirtualFile.nameFromPath(longName)
+ val fullPath = jarPath + "#" + longName
+
+ def entryReader: Reader = new InputStreamReader(in, "UTF-8")
+ def entryContent: String = IO.readInputStreamToString(in)
+ def entryBinaryContent: Array[Byte] = IO.readInputStreamToByteArray(in)
+ def entryVersion: Option[String] = Some(entry.getTime.toString)
+
+ if (longName == JSDependencyManifest.ManifestFileName)
+ handleDepManifest(JSDependencyManifest.read(entryReader))
+ else if (longName.endsWith(".js")) {
+ val relPath = longName
+ getOrCreateJSFile(relPath, fullPath, shortName)
+ .withContent(entryContent)
+ .withVersion(entryVersion)
+ } else if (longName.endsWith(".js.map")) {
+ // assume the source map of a JS file
+ val relPath = longName.dropRight(".map".length)
+ getOrCreateJSFile(relPath, fullPath, shortName)
+ .withSourceMap(Some(entryContent))
+ } else if (longName.endsWith(".sjsir")) {
+ val vf = new MemVirtualSerializedScalaJSIRFile(fullPath) {
+ override val name = shortName
+ }.withContent(entryBinaryContent)
+ .withVersion(entryVersion)
+
+ handleIR(longName, vf)
+ }
+ }
+}