Skip to content

Commit 952da60

Browse files
committed
Merge pull request scala#5057 from lrytz/flatClasspath
Enable -YclasspathImpl:flat by default
2 parents 6dbdb09 + f8950c1 commit 952da60

18 files changed

+384
-230
lines changed

src/compiler/scala/tools/nsc/Global.scala

Lines changed: 141 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,29 @@ package scala
77
package tools
88
package nsc
99

10-
import java.io.{ File, IOException, FileNotFoundException }
10+
import java.io.{File, FileNotFoundException, IOException}
1111
import java.net.URL
12-
import java.nio.charset.{ Charset, CharsetDecoder, IllegalCharsetNameException, UnsupportedCharsetException }
13-
import scala.collection.{ mutable, immutable }
14-
import io.{ SourceReader, AbstractFile, Path }
12+
import java.nio.charset.{Charset, CharsetDecoder, IllegalCharsetNameException, UnsupportedCharsetException}
13+
import scala.collection.{immutable, mutable}
14+
import io.{AbstractFile, Path, SourceReader}
1515
import reporters.Reporter
16-
import util.{ ClassFileLookup, ClassPath, MergedClassPath, StatisticsInfo, returning }
16+
import util.{ClassFileLookup, ClassPath, StatisticsInfo, returning}
1717
import scala.reflect.ClassTag
18-
import scala.reflect.internal.util.{ ScalaClassLoader, SourceFile, NoSourceFile, BatchSourceFile, ScriptSourceFile }
18+
import scala.reflect.internal.util.{BatchSourceFile, NoSourceFile, ScalaClassLoader, ScriptSourceFile, SourceFile}
1919
import scala.reflect.internal.pickling.PickleBuffer
20-
import symtab.{ Flags, SymbolTable, SymbolTrackers }
20+
import symtab.{Flags, SymbolTable, SymbolTrackers}
2121
import symtab.classfile.Pickler
2222
import plugins.Plugins
2323
import ast._
2424
import ast.parser._
2525
import typechecker._
2626
import transform.patmat.PatternMatching
2727
import transform._
28-
import backend.{ ScalaPrimitives, JavaPlatform }
28+
import backend.{JavaPlatform, ScalaPrimitives}
2929
import backend.jvm.GenBCode
3030
import scala.language.postfixOps
3131
import scala.tools.nsc.ast.{TreeGen => AstTreeGen}
32-
import scala.tools.nsc.classpath.FlatClassPath
32+
import scala.tools.nsc.classpath._
3333
import scala.tools.nsc.settings.ClassPathRepresentationType
3434

3535
class Global(var currentSettings: Settings, var reporter: Reporter)
@@ -102,9 +102,6 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
102102
type ThisPlatform = JavaPlatform { val global: Global.this.type }
103103
lazy val platform: ThisPlatform = new GlobalPlatform
104104

105-
type PlatformClassPath = ClassPath[AbstractFile]
106-
type OptClassPath = Option[PlatformClassPath]
107-
108105
def classPath: ClassFileLookup[AbstractFile] = settings.YclasspathImpl.value match {
109106
case ClassPathRepresentationType.Flat => flatClassPath
110107
case ClassPathRepresentationType.Recursive => recursiveClassPath
@@ -771,13 +768,17 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
771768

772769
/** Extend classpath of `platform` and rescan updated packages. */
773770
def extendCompilerClassPath(urls: URL*): Unit = {
774-
if (settings.YclasspathImpl.value == ClassPathRepresentationType.Flat)
775-
throw new UnsupportedOperationException("Flat classpath doesn't support extending the compiler classpath")
776-
777-
val newClassPath = platform.classPath.mergeUrlsIntoClassPath(urls: _*)
778-
platform.currentClassPath = Some(newClassPath)
779-
// Reload all specified jars into this compiler instance
780-
invalidateClassPathEntries(urls.map(_.getPath): _*)
771+
if (settings.YclasspathImpl.value == ClassPathRepresentationType.Flat) {
772+
val urlClasspaths = urls.map(u => FlatClassPathFactory.newClassPath(AbstractFile.getURL(u), settings))
773+
val newClassPath = AggregateFlatClassPath.createAggregate(platform.flatClassPath +: urlClasspaths : _*)
774+
platform.currentFlatClassPath = Some(newClassPath)
775+
invalidateClassPathEntries(urls.map(_.getPath): _*)
776+
} else {
777+
val newClassPath = platform.classPath.mergeUrlsIntoClassPath(urls: _*)
778+
platform.currentClassPath = Some(newClassPath)
779+
// Reload all specified jars into this compiler instance
780+
invalidateClassPathEntries(urls.map(_.getPath): _*)
781+
}
781782
}
782783

783784
// ------------ Invalidations ---------------------------------
@@ -809,43 +810,60 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
809810
* entries on the classpath.
810811
*/
811812
def invalidateClassPathEntries(paths: String*): Unit = {
812-
if (settings.YclasspathImpl.value == ClassPathRepresentationType.Flat)
813-
throw new UnsupportedOperationException("Flat classpath doesn't support the classpath invalidation")
814-
815-
implicit object ClassPathOrdering extends Ordering[PlatformClassPath] {
816-
def compare(a:PlatformClassPath, b:PlatformClassPath) = a.asClassPathString compare b.asClassPathString
813+
implicit object ClassPathOrdering extends Ordering[ClassFileLookup[AbstractFile]] {
814+
def compare(a:ClassFileLookup[AbstractFile], b:ClassFileLookup[AbstractFile]) = a.asClassPathString compare b.asClassPathString
817815
}
818816
val invalidated, failed = new mutable.ListBuffer[ClassSymbol]
819-
classPath match {
820-
case cp: MergedClassPath[_] =>
821-
def assoc(path: String): List[(PlatformClassPath, PlatformClassPath)] = {
822-
val dir = AbstractFile.getDirectory(path)
823-
val canonical = dir.canonicalPath
824-
def matchesCanonical(e: ClassPath[_]) = e.origin match {
825-
case Some(opath) =>
826-
AbstractFile.getDirectory(opath).canonicalPath == canonical
827-
case None =>
828-
false
829-
}
830-
cp.entries find matchesCanonical match {
831-
case Some(oldEntry) =>
832-
List(oldEntry -> cp.context.newClassPath(dir))
833-
case None =>
834-
error(s"Error adding entry to classpath. During invalidation, no entry named $path in classpath $classPath")
835-
List()
836-
}
837-
}
838-
val subst = immutable.TreeMap(paths flatMap assoc: _*)
839-
if (subst.nonEmpty) {
840-
platform updateClassPath subst
841-
informProgress(s"classpath updated on entries [${subst.keys mkString ","}]")
842-
def mkClassPath(elems: Iterable[PlatformClassPath]): PlatformClassPath =
843-
if (elems.size == 1) elems.head
844-
else new MergedClassPath(elems, recursiveClassPath.context)
845-
val oldEntries = mkClassPath(subst.keys)
846-
val newEntries = mkClassPath(subst.values)
847-
mergeNewEntries(newEntries, RootClass, Some(recursiveClassPath), Some(oldEntries), invalidated, failed)
848-
}
817+
818+
def assoc(path: String): Option[(ClassFileLookup[AbstractFile], ClassFileLookup[AbstractFile])] = {
819+
def origin(lookup: ClassFileLookup[AbstractFile]): Option[String] = lookup match {
820+
case cp: ClassPath[_] => cp.origin
821+
case cp: JFileDirectoryLookup[_] => Some(cp.dir.getPath)
822+
case cp: ZipArchiveFileLookup[_] => Some(cp.zipFile.getPath)
823+
case _ => None
824+
}
825+
826+
def entries(lookup: ClassFileLookup[AbstractFile]): Seq[ClassFileLookup[AbstractFile]] = lookup match {
827+
case cp: ClassPath[_] => cp.entries
828+
case cp: AggregateFlatClassPath => cp.aggregates
829+
case cp: FlatClassPath => Seq(cp)
830+
}
831+
832+
val dir = AbstractFile.getDirectory(path) // if path is a `jar`, this is a FileZipArchive (isDirectory is true)
833+
val canonical = dir.canonicalPath // this is the canonical path of the .jar
834+
def matchesCanonical(e: ClassFileLookup[AbstractFile]) = origin(e) match {
835+
case Some(opath) =>
836+
AbstractFile.getDirectory(opath).canonicalPath == canonical
837+
case None =>
838+
false
839+
}
840+
entries(classPath) find matchesCanonical match {
841+
case Some(oldEntry) =>
842+
Some(oldEntry -> ClassFileLookup.createForFile(dir, classPath, settings))
843+
case None =>
844+
error(s"Error adding entry to classpath. During invalidation, no entry named $path in classpath $classPath")
845+
None
846+
}
847+
}
848+
val subst = immutable.TreeMap(paths flatMap assoc: _*)
849+
if (subst.nonEmpty) {
850+
platform updateClassPath subst
851+
informProgress(s"classpath updated on entries [${subst.keys mkString ","}]")
852+
def mkClassPath(elems: Iterable[ClassFileLookup[AbstractFile]]): ClassFileLookup[AbstractFile] =
853+
if (elems.size == 1) elems.head
854+
else ClassFileLookup.createAggregate(elems, classPath)
855+
val oldEntries = mkClassPath(subst.keys)
856+
val newEntries = mkClassPath(subst.values)
857+
classPath match {
858+
case rcp: ClassPath[_] => mergeNewEntriesRecursive(
859+
newEntries.asInstanceOf[ClassPath[AbstractFile]], RootClass, Some(rcp), Some(oldEntries.asInstanceOf[ClassPath[AbstractFile]]),
860+
invalidated, failed)
861+
862+
case fcp: FlatClassPath => mergeNewEntriesFlat(
863+
RootClass, "",
864+
oldEntries.asInstanceOf[FlatClassPath], newEntries.asInstanceOf[FlatClassPath], fcp,
865+
invalidated, failed)
866+
}
849867
}
850868
def show(msg: String, syms: scala.collection.Traversable[Symbol]) =
851869
if (syms.nonEmpty)
@@ -876,22 +894,22 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
876894
*
877895
* Here, old means classpath, and sym means symboltable. + is presence of an entry in its column, - is absence.
878896
*/
879-
private def mergeNewEntries(newEntries: PlatformClassPath, root: ClassSymbol,
880-
allEntries: OptClassPath, oldEntries: OptClassPath,
897+
private def mergeNewEntriesRecursive(newEntries: ClassPath[AbstractFile], root: ClassSymbol,
898+
allEntries: Option[ClassPath[AbstractFile]], oldEntries: Option[ClassPath[AbstractFile]],
881899
invalidated: mutable.ListBuffer[ClassSymbol], failed: mutable.ListBuffer[ClassSymbol]) {
882900
ifDebug(informProgress(s"syncing $root, $oldEntries -> $newEntries"))
883901

884-
val getName: ClassPath[AbstractFile] => String = (_.name)
885-
def hasClasses(cp: OptClassPath) = cp.isDefined && cp.get.classes.nonEmpty
902+
val getPackageName: ClassPath[AbstractFile] => String = _.name
903+
def hasClasses(cp: Option[ClassPath[AbstractFile]]) = cp.isDefined && cp.get.classes.nonEmpty
886904
def invalidateOrRemove(root: ClassSymbol) = {
887905
allEntries match {
888906
case Some(cp) => root setInfo new loaders.PackageLoader(cp)
889907
case None => root.owner.info.decls unlink root.sourceModule
890908
}
891909
invalidated += root
892910
}
893-
def subPackage(cp: PlatformClassPath, name: String): OptClassPath =
894-
cp.packages find (cp1 => getName(cp1) == name)
911+
def subPackage(cp: ClassPath[AbstractFile], name: String): Option[ClassPath[AbstractFile]] =
912+
cp.packages find (cp1 => getPackageName(cp1) == name)
895913

896914
val classesFound = hasClasses(oldEntries) || newEntries.classes.nonEmpty
897915
if (classesFound && !isSystemPackageClass(root)) {
@@ -901,22 +919,81 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
901919
if (root.isRoot) invalidateOrRemove(EmptyPackageClass)
902920
else failed += root
903921
}
904-
if (!oldEntries.isDefined) invalidateOrRemove(root)
922+
if (oldEntries.isEmpty) invalidateOrRemove(root)
905923
else
906-
for (pstr <- newEntries.packages.map(getName)) {
924+
for (pstr <- newEntries.packages.map(getPackageName)) {
907925
val pname = newTermName(pstr)
908926
val pkg = (root.info decl pname) orElse {
909927
// package does not exist in symbol table, create symbol to track it
910-
assert(!subPackage(oldEntries.get, pstr).isDefined)
928+
assert(subPackage(oldEntries.get, pstr).isEmpty)
911929
loaders.enterPackage(root, pstr, new loaders.PackageLoader(allEntries.get))
912930
}
913-
mergeNewEntries(subPackage(newEntries, pstr).get, pkg.moduleClass.asClass,
931+
mergeNewEntriesRecursive(subPackage(newEntries, pstr).get, pkg.moduleClass.asClass,
914932
subPackage(allEntries.get, pstr), subPackage(oldEntries.get, pstr),
915933
invalidated, failed)
916934
}
917935
}
918936
}
919937

938+
/**
939+
* Merges new classpath entries into the symbol table
940+
*
941+
* @param packageClass The ClassSymbol for the package being updated
942+
* @param fullPackageName The full name of the package being updated
943+
* @param oldEntries The classpath that was removed, it is no longer part of fullClasspath
944+
* @param newEntries The classpath that was added, it is already part of fullClasspath
945+
* @param fullClasspath The full classpath, equivalent to global.classPath
946+
* @param invalidated A ListBuffer collecting the invalidated package classes
947+
* @param failed A ListBuffer collecting system package classes which could not be invalidated
948+
*
949+
* If either oldEntries or newEntries contains classes in the current package, the package symbol
950+
* is re-initialized to a fresh package loader, provided that a corresponding package exists in
951+
* fullClasspath. Otherwise it is removed.
952+
*
953+
* Otherwise, sub-packages in newEntries are looked up in the symbol table (created if
954+
* non-existent) and the merge function is called recursively.
955+
*/
956+
private def mergeNewEntriesFlat(
957+
packageClass: ClassSymbol, fullPackageName: String,
958+
oldEntries: FlatClassPath, newEntries: FlatClassPath, fullClasspath: FlatClassPath,
959+
invalidated: mutable.ListBuffer[ClassSymbol], failed: mutable.ListBuffer[ClassSymbol]): Unit = {
960+
ifDebug(informProgress(s"syncing $packageClass, $oldEntries -> $newEntries"))
961+
962+
def packageExists(cp: FlatClassPath): Boolean = {
963+
val (parent, _) = PackageNameUtils.separatePkgAndClassNames(fullPackageName)
964+
cp.packages(parent).exists(_.name == fullPackageName)
965+
}
966+
967+
def invalidateOrRemove(pkg: ClassSymbol) = {
968+
if (packageExists(fullClasspath))
969+
pkg setInfo new loaders.PackageLoaderUsingFlatClassPath(fullPackageName, fullClasspath)
970+
else
971+
pkg.owner.info.decls unlink pkg.sourceModule
972+
invalidated += pkg
973+
}
974+
975+
val classesFound = oldEntries.classes(fullPackageName).nonEmpty || newEntries.classes(fullPackageName).nonEmpty
976+
if (classesFound) {
977+
// if the package contains classes either in oldEntries or newEntries, the package is invalidated (or removed if there are no more classes in it)
978+
if (!isSystemPackageClass(packageClass)) invalidateOrRemove(packageClass)
979+
else if (packageClass.isRoot) invalidateOrRemove(EmptyPackageClass)
980+
else failed += packageClass
981+
} else {
982+
// no new or removed classes in the current package
983+
for (p <- newEntries.packages(fullPackageName)) {
984+
val (_, subPackageName) = PackageNameUtils.separatePkgAndClassNames(p.name)
985+
val subPackage = packageClass.info.decl(newTermName(subPackageName)) orElse {
986+
// package does not exist in symbol table, create a new symbol
987+
loaders.enterPackage(packageClass, subPackageName, new loaders.PackageLoaderUsingFlatClassPath(p.name, fullClasspath))
988+
}
989+
mergeNewEntriesFlat(
990+
subPackage.moduleClass.asClass, p.name,
991+
oldEntries, newEntries, fullClasspath,
992+
invalidated, failed)
993+
}
994+
}
995+
}
996+
920997
// ----------- Runs ---------------------------------------
921998

922999
private var curRun: Run = null

src/compiler/scala/tools/nsc/backend/JavaPlatform.scala

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ package scala.tools.nsc
77
package backend
88

99
import io.AbstractFile
10-
import scala.tools.nsc.classpath.FlatClassPath
10+
import scala.tools.nsc.classpath.{AggregateFlatClassPath, FlatClassPath}
1111
import scala.tools.nsc.settings.ClassPathRepresentationType
12-
import scala.tools.nsc.util.{ ClassPath, DeltaClassPath, MergedClassPath }
12+
import scala.tools.nsc.util.{ClassFileLookup, ClassPath, MergedClassPath}
1313
import scala.tools.util.FlatClassPathResolver
1414
import scala.tools.util.PathResolver
1515

@@ -29,16 +29,29 @@ trait JavaPlatform extends Platform {
2929
currentClassPath.get
3030
}
3131

32-
private[nsc] lazy val flatClassPath: FlatClassPath = {
32+
private[nsc] var currentFlatClassPath: Option[FlatClassPath] = None
33+
34+
private[nsc] def flatClassPath: FlatClassPath = {
3335
assert(settings.YclasspathImpl.value == ClassPathRepresentationType.Flat,
3436
"To use flat classpath representation you must enable it with -YclasspathImpl:flat compiler option.")
3537

36-
new FlatClassPathResolver(settings).result
38+
if (currentFlatClassPath.isEmpty) currentFlatClassPath = Some(new FlatClassPathResolver(settings).result)
39+
currentFlatClassPath.get
3740
}
3841

3942
/** Update classpath with a substituted subentry */
40-
def updateClassPath(subst: Map[ClassPath[AbstractFile], ClassPath[AbstractFile]]) =
41-
currentClassPath = Some(new DeltaClassPath(currentClassPath.get, subst))
43+
def updateClassPath(subst: Map[ClassFileLookup[AbstractFile], ClassFileLookup[AbstractFile]]) = global.classPath match {
44+
case cp: ClassPath[AbstractFile] =>
45+
val s = subst.asInstanceOf[Map[ClassPath[AbstractFile], ClassPath[AbstractFile]]]
46+
currentClassPath = Some(new MergedClassPath(cp.entries map (e => s.getOrElse(e, e)), cp.context))
47+
48+
case AggregateFlatClassPath(entries) =>
49+
val s = subst.asInstanceOf[Map[FlatClassPath, FlatClassPath]]
50+
currentFlatClassPath = Some(AggregateFlatClassPath(entries map (e => s.getOrElse(e, e))))
51+
52+
case cp: FlatClassPath =>
53+
currentFlatClassPath = Some(subst.getOrElse(cp, cp).asInstanceOf[FlatClassPath])
54+
}
4255

4356
def platformPhases = List(
4457
flatten, // get rid of inner classes

src/compiler/scala/tools/nsc/backend/Platform.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
package scala.tools.nsc
77
package backend
88

9-
import util.ClassPath
9+
import util.{ClassFileLookup, ClassPath}
1010
import io.AbstractFile
1111
import scala.tools.nsc.classpath.FlatClassPath
1212

@@ -23,7 +23,7 @@ trait Platform {
2323
private[nsc] def flatClassPath: FlatClassPath
2424

2525
/** Update classpath with a substitution that maps entries to entries */
26-
def updateClassPath(subst: Map[ClassPath[AbstractFile], ClassPath[AbstractFile]])
26+
def updateClassPath(subst: Map[ClassFileLookup[AbstractFile], ClassFileLookup[AbstractFile]])
2727

2828
/** Any platform-specific phases. */
2929
def platformPhases: List[SubComponent]

src/compiler/scala/tools/nsc/classpath/AggregateFlatClassPath.scala

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import scala.tools.nsc.util.ClassRepresentation
1818
* @param aggregates classpath instances containing entries which this class processes
1919
*/
2020
case class AggregateFlatClassPath(aggregates: Seq[FlatClassPath]) extends FlatClassPath {
21-
2221
override def findClassFile(className: String): Option[AbstractFile] = {
2322
@tailrec
2423
def find(aggregates: Seq[FlatClassPath]): Option[AbstractFile] =
@@ -37,16 +36,19 @@ case class AggregateFlatClassPath(aggregates: Seq[FlatClassPath]) extends FlatCl
3736
@tailrec
3837
def findEntry[T <: ClassRepClassPathEntry](aggregates: Seq[FlatClassPath], getEntries: FlatClassPath => Seq[T]): Option[T] =
3938
if (aggregates.nonEmpty) {
40-
val entry = getEntries(aggregates.head)
41-
.find(_.name == simpleClassName)
39+
val entry = getEntries(aggregates.head).find(_.name == simpleClassName)
4240
if (entry.isDefined) entry
4341
else findEntry(aggregates.tail, getEntries)
4442
} else None
4543

4644
val classEntry = findEntry(aggregates, classesGetter(pkg))
4745
val sourceEntry = findEntry(aggregates, sourcesGetter(pkg))
4846

49-
mergeClassesAndSources(classEntry.toList, sourceEntry.toList).headOption
47+
(classEntry, sourceEntry) match {
48+
case (Some(c), Some(s)) => Some(ClassAndSourceFilesEntry(c.file, s.file))
49+
case (c @ Some(_), _) => c
50+
case (_, s) => s
51+
}
5052
}
5153

5254
override def asURLs: Seq[URL] = aggregates.flatMap(_.asURLs)
@@ -123,3 +125,15 @@ case class AggregateFlatClassPath(aggregates: Seq[FlatClassPath]) extends FlatCl
123125
private def classesGetter(pkg: String) = (cp: FlatClassPath) => cp.classes(pkg)
124126
private def sourcesGetter(pkg: String) = (cp: FlatClassPath) => cp.sources(pkg)
125127
}
128+
129+
object AggregateFlatClassPath {
130+
def createAggregate(parts: FlatClassPath*): FlatClassPath = {
131+
val elems = new ArrayBuffer[FlatClassPath]()
132+
parts foreach {
133+
case AggregateFlatClassPath(ps) => elems ++= ps
134+
case p => elems += p
135+
}
136+
if (elems.size == 1) elems.head
137+
else AggregateFlatClassPath(elems.toIndexedSeq)
138+
}
139+
}

0 commit comments

Comments
 (0)