diff options
Diffstat (limited to 'tools/shared/src/main/scala/scala/scalajs/tools/classpath')
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) + } + } +} |