summaryrefslogtreecommitdiff
path: root/src/compiler
diff options
context:
space:
mode:
authorJason Zaugg <jzaugg@gmail.com>2017-02-17 09:51:07 +1000
committerJason Zaugg <jzaugg@gmail.com>2017-02-17 19:40:33 +1000
commit09c7edc8a83caaa03127574d38c70a2e5e3b294d (patch)
treee4ed4adea5afc0a8ca7fc28250438901a7cf55aa /src/compiler
parent502e3c6296e5e997549b7e496e0bbfa62c522110 (diff)
downloadscala-09c7edc8a83caaa03127574d38c70a2e5e3b294d.tar.gz
scala-09c7edc8a83caaa03127574d38c70a2e5e3b294d.tar.bz2
scala-09c7edc8a83caaa03127574d38c70a2e5e3b294d.zip
Faster and simpler Java 9 classpath implementation
- Take advantage of the `/packages` index provided by the jrt file system to avoid (expensive) Files.exist for non-existent entries across the full list of modules. - Extends ClassPath directly which leads to a simpler implemnentation that using the base class. - Add a unit test that shows we can read classes and packages from the Java standard library. Fixes scala/scala-dev#306 With this change bootstrap time under Java 9 was comparable to Java 8. Before, it was about 40% slower.
Diffstat (limited to 'src/compiler')
-rw-r--r--src/compiler/scala/tools/nsc/classpath/DirectoryClassPath.scala86
-rw-r--r--src/compiler/scala/tools/util/PathResolver.scala2
2 files changed, 58 insertions, 30 deletions
diff --git a/src/compiler/scala/tools/nsc/classpath/DirectoryClassPath.scala b/src/compiler/scala/tools/nsc/classpath/DirectoryClassPath.scala
index 133a656206..19d880ddb9 100644
--- a/src/compiler/scala/tools/nsc/classpath/DirectoryClassPath.scala
+++ b/src/compiler/scala/tools/nsc/classpath/DirectoryClassPath.scala
@@ -10,9 +10,10 @@ import java.util.function.IntFunction
import java.util
import java.util.Comparator
-import scala.reflect.io.{AbstractFile, PlainFile}
+import scala.reflect.io.{AbstractFile, PlainFile, PlainNioFile}
import scala.tools.nsc.util.{ClassPath, ClassRepresentation}
import FileUtils._
+import scala.collection.JavaConverters._
/**
* A trait allowing to look for classpath entries in directories. It provides common logic for
@@ -121,51 +122,78 @@ trait JFileDirectoryLookup[FileEntryType <: ClassRepresentation] extends Directo
def asClassPathStrings: Seq[String] = Seq(dir.getPath)
}
-object JImageDirectoryLookup {
- import java.nio.file._, java.net.URI, scala.collection.JavaConverters._
- def apply(): List[ClassPath] = {
+object JrtClassPath {
+ import java.nio.file._, java.net.URI
+ def apply(): Option[ClassPath] = {
try {
val fs = FileSystems.getFileSystem(URI.create("jrt:/"))
- val dir: Path = fs.getPath("/modules")
- val modules = Files.list(dir).iterator().asScala.toList
- modules.map(m => new JImageDirectoryLookup(fs, m.getFileName.toString))
+ Some(new JrtClassPath(fs))
} catch {
case _: ProviderNotFoundException | _: FileSystemNotFoundException =>
- Nil
+ None
}
}
}
-class JImageDirectoryLookup(fs: java.nio.file.FileSystem, module: String) extends DirectoryLookup[ClassFileEntryImpl] with NoSourcePaths {
+
+/**
+ * Implementation `ClassPath` based on the JDK 9 encapsulated runtime modules (JEP-220)
+ *
+ * https://bugs.openjdk.java.net/browse/JDK-8066492 is the most up to date reference
+ * for the structure of the jrt:// filesystem.
+ *
+ * The implementation assumes that no classes exist in the empty package.
+ */
+final class JrtClassPath(fs: java.nio.file.FileSystem) extends ClassPath with NoSourcePaths {
import java.nio.file.Path, java.nio.file._
type F = Path
- val dir: Path = fs.getPath("/modules/" + module)
+ private val dir: Path = fs.getPath("/packages")
- protected def emptyFiles: Array[Path] = Array.empty
- protected def getSubDir(packageDirName: String): Option[Path] = {
- val packageDir = dir.resolve(packageDirName)
- if (Files.exists(packageDir) && Files.isDirectory(packageDir)) Some(packageDir)
- else None
+ // e.g. "java.lang" -> Seq("/modules/java.base")
+ private val packageToModuleBases: Map[String, Seq[Path]] = {
+ val ps = Files.newDirectoryStream(dir).iterator().asScala
+ def lookup(pack: Path): Seq[Path] = {
+ Files.list(pack).iterator().asScala.map(l => if (Files.isSymbolicLink(l)) Files.readSymbolicLink(l) else l).toList
+ }
+ ps.map(p => (p.toString.stripPrefix("/packages/"), lookup(p))).toMap
}
- protected def listChildren(dir: Path, filter: Option[Path => Boolean]): Array[Path] = {
- import scala.collection.JavaConverters._
- val f = filter.getOrElse((p: Path) => true)
- Files.list(dir).iterator().asScala.filter(f).toArray[Path]
+
+ override private[nsc] def packages(inPackage: String): Seq[PackageEntry] = {
+ def matches(packageDottedName: String) =
+ if (packageDottedName.contains("."))
+ packageOf(packageDottedName) == inPackage
+ else inPackage == ""
+ packageToModuleBases.keysIterator.filter(matches).map(PackageEntryImpl(_)).toVector
+ }
+ private[nsc] def classes(inPackage: String): Seq[ClassFileEntry] = {
+ if (inPackage == "") Nil
+ else {
+ packageToModuleBases.getOrElse(inPackage, Nil).flatMap(x =>
+ Files.list(x.resolve(inPackage.replace('.', '/'))).iterator().asScala.filter(_.getFileName.toString.endsWith(".class"))).map(x =>
+ ClassFileEntryImpl(new PlainNioFile(x))).toVector
+ }
}
- protected def getName(f: Path): String = f.getFileName.toString
- protected def toAbstractFile(f: Path): AbstractFile = new scala.reflect.io.PlainNioFile(f)
- protected def isPackage(f: Path): Boolean = Files.isDirectory(f) && mayBeValidPackage(f.getFileName.toString)
+
+ override private[nsc] def list(inPackage: String): ClassPathEntries =
+ if (inPackage == "") ClassPathEntries(packages(inPackage), Nil)
+ else ClassPathEntries(packages(inPackage), classes(inPackage))
def asURLs: Seq[URL] = Seq(dir.toUri.toURL)
- def asClassPathStrings: Seq[String] = asURLs.map(_.toString)
+ // We don't yet have a scheme to represent the JDK modules in our `-classpath`.
+ // java models them as entries in the new "module path", we'll probably need to follow this.
+ def asClassPathStrings: Seq[String] = Nil
def findClassFile(className: String): Option[AbstractFile] = {
- val relativePath = FileUtils.dirPath(className) + ".class"
- val classFile = dir.resolve(relativePath)
- if (Files.exists(classFile)) Some(new scala.reflect.io.PlainNioFile(classFile)) else None
+ if (!className.contains(".")) None
+ else {
+ val inPackage = packageOf(className)
+ packageToModuleBases.getOrElse(inPackage, Nil).iterator.flatMap{x =>
+ val file = x.resolve(className.replace('.', '/') + ".class")
+ if (Files.exists(file)) new scala.reflect.io.PlainNioFile(file) :: Nil else Nil
+ }.take(1).toList.headOption
+ }
}
- override protected def createFileEntry(file: AbstractFile): ClassFileEntryImpl = ClassFileEntryImpl(file)
- override protected def isMatchingFile(f: Path): Boolean = Files.isRegularFile(f) && f.getFileName.toString.endsWith(".class")
- override private[nsc] def classes(inPackage: String): Seq[ClassFileEntry] = files(inPackage)
+ private def packageOf(dottedClassName: String): String =
+ dottedClassName.substring(0, dottedClassName.lastIndexOf("."))
}
case class DirectoryClassPath(dir: File) extends JFileDirectoryLookup[ClassFileEntryImpl] with NoSourcePaths {
diff --git a/src/compiler/scala/tools/util/PathResolver.scala b/src/compiler/scala/tools/util/PathResolver.scala
index 188cabbc8d..f845656980 100644
--- a/src/compiler/scala/tools/util/PathResolver.scala
+++ b/src/compiler/scala/tools/util/PathResolver.scala
@@ -234,7 +234,7 @@ final class PathResolver(settings: Settings) {
// Assemble the elements!
def basis = List[Traversable[ClassPath]](
- JImageDirectoryLookup.apply(), // 0. The Java 9 classpath (backed by the jrt:/ virtual system)
+ JrtClassPath.apply(), // 0. The Java 9 classpath (backed by the jrt:/ virtual system, if available)
classesInPath(javaBootClassPath), // 1. The Java bootstrap class path.
contentsOfDirsInPath(javaExtDirs), // 2. The Java extension class path.
classesInExpandedPath(javaUserClassPath), // 3. The Java application class path.