diff options
52 files changed, 2217 insertions, 315 deletions
diff --git a/bincompat-backward.whitelist.conf b/bincompat-backward.whitelist.conf index 56d5b0135c..a1706d103d 100644 --- a/bincompat-backward.whitelist.conf +++ b/bincompat-backward.whitelist.conf @@ -203,6 +203,11 @@ filter { { matchName="scala.reflect.runtime.ThreadLocalStorage#MyThreadLocalStorage.values" problemName=MissingMethodProblem + }, + // the below method was the unused private (sic!) method but the compatibility checker was complaining about it + { + matchName="scala.reflect.io.ZipArchive.scala$reflect$io$ZipArchive$$walkIterator" + problemName=MissingMethodProblem } ] } diff --git a/bincompat-forward.whitelist.conf b/bincompat-forward.whitelist.conf index 53401eefad..13e4f4ba85 100644 --- a/bincompat-forward.whitelist.conf +++ b/bincompat-forward.whitelist.conf @@ -292,6 +292,28 @@ filter { { matchName="scala.collection.immutable.Stream.scala$collection$immutable$Stream$$loop$2" problemName=MissingMethodProblem + }, + // changes needed by ZipArchiveFileLookup (the flat classpath representation) + { + matchName="scala.reflect.io.FileZipArchive.allDirs" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.io.FileZipArchive.root" + problemName=MissingMethodProblem + }, + // introduced the harmless method (instead of the repeated code in several places) + { + matchName="scala.reflect.runtime.Settings#MultiStringSetting.valueSetByUser" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.runtime.Settings#BooleanSetting.valueSetByUser" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.runtime.Settings#IntSetting.valueSetByUser" + problemName=MissingMethodProblem } ] } diff --git a/src/compiler/scala/reflect/macros/contexts/Infrastructure.scala b/src/compiler/scala/reflect/macros/contexts/Infrastructure.scala index df7aa4d2be..7088058145 100644 --- a/src/compiler/scala/reflect/macros/contexts/Infrastructure.scala +++ b/src/compiler/scala/reflect/macros/contexts/Infrastructure.scala @@ -12,5 +12,5 @@ trait Infrastructure { def compilerSettings: List[String] = universe.settings.recreateArgs - def classPath: List[java.net.URL] = global.classPath.asURLs + def classPath: List[java.net.URL] = global.classPath.asURLs.toList } diff --git a/src/compiler/scala/tools/nsc/ClassPathMemoryConsumptionTester.scala b/src/compiler/scala/tools/nsc/ClassPathMemoryConsumptionTester.scala new file mode 100644 index 0000000000..2faf6c6272 --- /dev/null +++ b/src/compiler/scala/tools/nsc/ClassPathMemoryConsumptionTester.scala @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2014 Contributor. All rights reserved. + */ +package scala.tools.nsc + +import scala.io.StdIn.readLine + +/** + * Simple application to check out amount of memory used by chosen classpath representation. + * It allows us to create many scalac-like calls based on specified parameters, where each main retains Global. + * And we need additional tool (e.g. profiler) to measure memory consumption itself. + */ +object ClassPathMemoryConsumptionTester { + + private class TestSettings extends Settings { + val requiredInstances = IntSetting("-requiredInstances", + "Determine how many times classpath should be loaded", 10, Some((1, 10000)), (_: String) => None) + } + + private class MainRetainsGlobal extends scala.tools.nsc.MainClass { + var retainedGlobal: Global = _ + override def doCompile(compiler: Global) { + retainedGlobal = compiler + super.doCompile(compiler) + } + } + + def main(args: Array[String]): Unit = { + if (args contains "-help") usage() + else doTest(args) + } + + private def doTest(args: Array[String]) = { + val settings = loadSettings(args.toList) + + val mains = (1 to settings.requiredInstances.value) map (_ => new MainRetainsGlobal) + + // we need original settings without additional params to be able to use them later + val baseArgs = argsWithoutRequiredInstances(args) + + println(s"Loading classpath ${settings.requiredInstances.value} times") + val startTime = System.currentTimeMillis() + + mains map (_.process(baseArgs)) + + val elapsed = System.currentTimeMillis() - startTime + println(s"Operation finished - elapsed $elapsed ms") + println("Memory consumption can be now measured") + + var textFromStdIn = "" + while (textFromStdIn.toLowerCase != "exit") + textFromStdIn = readLine("Type 'exit' to close application: ") + } + + /** + * Prints usage information + */ + private def usage(): Unit = + println( """Use classpath and sourcepath options like in the case of e.g. 'scala' command. + | There's also one additional option: + | -requiredInstances <int value> Determine how many times classpath should be loaded + """.stripMargin.trim) + + private def loadSettings(args: List[String]) = { + val settings = new TestSettings() + settings.processArguments(args, processAll = true) + if (settings.classpath.isDefault) + settings.classpath.value = sys.props("java.class.path") + settings + } + + private def argsWithoutRequiredInstances(args: Array[String]) = { + val instancesIndex = args.indexOf("-requiredInstances") + if (instancesIndex == -1) args + else args.dropRight(args.length - instancesIndex) ++ args.drop(instancesIndex + 2) + } +} diff --git a/src/compiler/scala/tools/nsc/GenericRunnerSettings.scala b/src/compiler/scala/tools/nsc/GenericRunnerSettings.scala index ad75d02bff..1289d55c37 100644 --- a/src/compiler/scala/tools/nsc/GenericRunnerSettings.scala +++ b/src/compiler/scala/tools/nsc/GenericRunnerSettings.scala @@ -5,10 +5,11 @@ package scala.tools.nsc -import scala.tools.util.PathResolver +import java.net.URL +import scala.tools.util.PathResolverFactory class GenericRunnerSettings(error: String => Unit) extends Settings(error) { - def classpathURLs = new PathResolver(this).asURLs + def classpathURLs: Seq[URL] = PathResolverFactory.create(this).resultAsURLs val howtorun = ChoiceSetting( diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index e62dfd00a6..733664c30a 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -14,11 +14,10 @@ import scala.compat.Platform.currentTime import scala.collection.{ mutable, immutable } import io.{ SourceReader, AbstractFile, Path } import reporters.{ Reporter, ConsoleReporter } -import util.{ ClassPath, MergedClassPath, StatisticsInfo, returning, stackTraceString } +import util.{ ClassFileLookup, ClassPath, MergedClassPath, StatisticsInfo, returning } import scala.reflect.ClassTag -import scala.reflect.internal.util.{ OffsetPosition, SourceFile, NoSourceFile, BatchSourceFile, ScriptSourceFile } -import scala.reflect.internal.pickling.{ PickleBuffer, PickleFormat } -import scala.reflect.io.VirtualFile +import scala.reflect.internal.util.{ SourceFile, NoSourceFile, BatchSourceFile, ScriptSourceFile } +import scala.reflect.internal.pickling.PickleBuffer import symtab.{ Flags, SymbolTable, SymbolTrackers } import symtab.classfile.Pickler import plugins.Plugins @@ -35,6 +34,8 @@ import backend.opt.{ Inliners, InlineExceptionHandlers, ConstantOptimization, Cl import backend.icode.analysis._ import scala.language.postfixOps import scala.tools.nsc.ast.{TreeGen => AstTreeGen} +import scala.tools.nsc.classpath.FlatClassPath +import scala.tools.nsc.settings.ClassPathRepresentationType class Global(var currentSettings: Settings, var reporter: Reporter) extends SymbolTable @@ -58,7 +59,12 @@ class Global(var currentSettings: Settings, var reporter: Reporter) class GlobalMirror extends Roots(NoSymbol) { val universe: self.type = self - def rootLoader: LazyType = new loaders.PackageLoader(classPath) + def rootLoader: LazyType = { + settings.YclasspathImpl.value match { + case ClassPathRepresentationType.Flat => new loaders.PackageLoaderUsingFlatClassPath(FlatClassPath.RootPackage, flatClassPath) + case ClassPathRepresentationType.Recursive => new loaders.PackageLoader(recursiveClassPath) + } + } override def toString = "compiler mirror" } implicit val MirrorTag: ClassTag[Mirror] = ClassTag[Mirror](classOf[GlobalMirror]) @@ -104,7 +110,14 @@ class Global(var currentSettings: Settings, var reporter: Reporter) type PlatformClassPath = ClassPath[AbstractFile] type OptClassPath = Option[PlatformClassPath] - def classPath: PlatformClassPath = platform.classPath + def classPath: ClassFileLookup[AbstractFile] = settings.YclasspathImpl.value match { + case ClassPathRepresentationType.Flat => flatClassPath + case ClassPathRepresentationType.Recursive => recursiveClassPath + } + + private def recursiveClassPath: ClassPath[AbstractFile] = platform.classPath + + private def flatClassPath: FlatClassPath = platform.flatClassPath // sub-components -------------------------------------------------- @@ -319,7 +332,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) None } - val charset = ( if (settings.encoding.isSetByUser) Some(settings.encoding.value) else None ) flatMap loadCharset getOrElse { + val charset = settings.encoding.valueSetByUser flatMap loadCharset getOrElse { settings.encoding.value = defaultEncoding // A mandatory charset Charset.forName(defaultEncoding) } @@ -334,16 +347,16 @@ class Global(var currentSettings: Settings, var reporter: Reporter) } } - ( if (settings.sourceReader.isSetByUser) Some(settings.sourceReader.value) else None ) flatMap loadReader getOrElse { + settings.sourceReader.valueSetByUser flatMap loadReader getOrElse { new SourceReader(charset.newDecoder(), reporter) } } - if (settings.verbose || settings.Ylogcp) { + if (settings.verbose || settings.Ylogcp) reporter.echo( - s"[search path for source files: ${classPath.sourcepaths.mkString(",")}]\n"+ - s"[search path for class files: ${classPath.asClasspathString}") - } + s"[search path for source files: ${classPath.asSourcePathString}]\n" + + s"[search path for class files: ${classPath.asClassPathString}]" + ) // The current division between scala.reflect.* and scala.tools.nsc.* is pretty // clunky. It is often difficult to have a setting influence something without having @@ -846,6 +859,9 @@ class Global(var currentSettings: Settings, var reporter: Reporter) /** Extend classpath of `platform` and rescan updated packages. */ def extendCompilerClassPath(urls: URL*): Unit = { + if (settings.YclasspathImpl.value == ClassPathRepresentationType.Flat) + throw new UnsupportedOperationException("Flat classpath doesn't support extending the compiler classpath") + val newClassPath = platform.classPath.mergeUrlsIntoClassPath(urls: _*) platform.currentClassPath = Some(newClassPath) // Reload all specified jars into this compiler instance @@ -881,8 +897,11 @@ class Global(var currentSettings: Settings, var reporter: Reporter) * entries on the classpath. */ def invalidateClassPathEntries(paths: String*): Unit = { + if (settings.YclasspathImpl.value == ClassPathRepresentationType.Flat) + throw new UnsupportedOperationException("Flat classpath doesn't support the classpath invalidation") + implicit object ClassPathOrdering extends Ordering[PlatformClassPath] { - def compare(a:PlatformClassPath, b:PlatformClassPath) = a.asClasspathString compare b.asClasspathString + def compare(a:PlatformClassPath, b:PlatformClassPath) = a.asClassPathString compare b.asClassPathString } val invalidated, failed = new mutable.ListBuffer[ClassSymbol] classPath match { @@ -910,10 +929,10 @@ class Global(var currentSettings: Settings, var reporter: Reporter) informProgress(s"classpath updated on entries [${subst.keys mkString ","}]") def mkClassPath(elems: Iterable[PlatformClassPath]): PlatformClassPath = if (elems.size == 1) elems.head - else new MergedClassPath(elems, classPath.context) + else new MergedClassPath(elems, recursiveClassPath.context) val oldEntries = mkClassPath(subst.keys) val newEntries = mkClassPath(subst.values) - mergeNewEntries(newEntries, RootClass, Some(classPath), Some(oldEntries), invalidated, failed) + mergeNewEntries(newEntries, RootClass, Some(recursiveClassPath), Some(oldEntries), invalidated, failed) } } def show(msg: String, syms: scala.collection.Traversable[Symbol]) = diff --git a/src/compiler/scala/tools/nsc/ObjectRunner.scala b/src/compiler/scala/tools/nsc/ObjectRunner.scala index 95264aeda6..7c14f4943f 100644 --- a/src/compiler/scala/tools/nsc/ObjectRunner.scala +++ b/src/compiler/scala/tools/nsc/ObjectRunner.scala @@ -18,14 +18,14 @@ trait CommonRunner { * @throws NoSuchMethodException * @throws InvocationTargetException */ - def run(urls: List[URL], objectName: String, arguments: Seq[String]) { + def run(urls: Seq[URL], objectName: String, arguments: Seq[String]) { (ScalaClassLoader fromURLs urls).run(objectName, arguments) } /** Catches exceptions enumerated by run (in the case of InvocationTargetException, * unwrapping it) and returns it any thrown in Left(x). */ - def runAndCatch(urls: List[URL], objectName: String, arguments: Seq[String]): Either[Throwable, Boolean] = { + def runAndCatch(urls: Seq[URL], objectName: String, arguments: Seq[String]): Either[Throwable, Boolean] = { try { run(urls, objectName, arguments) ; Right(true) } catch { case e: Throwable => Left(unwrap(e)) } } diff --git a/src/compiler/scala/tools/nsc/PhaseAssembly.scala b/src/compiler/scala/tools/nsc/PhaseAssembly.scala index cfb4cd23a1..1eb6c9da2c 100644 --- a/src/compiler/scala/tools/nsc/PhaseAssembly.scala +++ b/src/compiler/scala/tools/nsc/PhaseAssembly.scala @@ -199,7 +199,7 @@ trait PhaseAssembly { // Add all phases in the set to the graph val graph = phasesSetToDepGraph(phasesSet) - val dot = if (settings.genPhaseGraph.isSetByUser) Some(settings.genPhaseGraph.value) else None + val dot = settings.genPhaseGraph.valueSetByUser // Output the phase dependency graph at this stage def dump(stage: Int) = dot foreach (n => graphToDotFile(graph, s"$n-$stage.dot")) diff --git a/src/compiler/scala/tools/nsc/ScriptRunner.scala b/src/compiler/scala/tools/nsc/ScriptRunner.scala index 7d5c6f6fff..6d24b31531 100644 --- a/src/compiler/scala/tools/nsc/ScriptRunner.scala +++ b/src/compiler/scala/tools/nsc/ScriptRunner.scala @@ -8,7 +8,10 @@ package tools.nsc import io.{ AbstractFile, Directory, File, Path } import java.io.IOException +import scala.tools.nsc.classpath.DirectoryFlatClassPath import scala.tools.nsc.reporters.{Reporter,ConsoleReporter} +import scala.tools.nsc.settings.ClassPathRepresentationType +import scala.tools.nsc.util.ClassPath.DefaultJavaContext import util.Exceptional.unwrap /** An object that runs Scala code in script files. @@ -112,8 +115,10 @@ class ScriptRunner extends HasCompileSocket { } def hasClassToRun(d: Directory): Boolean = { - import util.ClassPath.{ DefaultJavaContext => ctx } - val cp = ctx.newClassPath(AbstractFile.getDirectory(d)) + val cp = settings.YclasspathImpl.value match { + case ClassPathRepresentationType.Recursive => DefaultJavaContext.newClassPath(AbstractFile.getDirectory(d)) + case ClassPathRepresentationType.Flat => DirectoryFlatClassPath(d.jfile) + } cp.findClass(mainClass).isDefined } diff --git a/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala b/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala index 4877bd9b80..6bd123c51f 100644 --- a/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala +++ b/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala @@ -7,7 +7,10 @@ package scala.tools.nsc package backend import io.AbstractFile -import util.{ClassPath,MergedClassPath,DeltaClassPath} +import scala.tools.nsc.classpath.FlatClassPath +import scala.tools.nsc.settings.ClassPathRepresentationType +import scala.tools.nsc.util.{ ClassPath, DeltaClassPath, MergedClassPath } +import scala.tools.util.FlatClassPathResolver import scala.tools.util.PathResolver trait JavaPlatform extends Platform { @@ -19,10 +22,20 @@ trait JavaPlatform extends Platform { private[nsc] var currentClassPath: Option[MergedClassPath[AbstractFile]] = None def classPath: ClassPath[AbstractFile] = { + assert(settings.YclasspathImpl.value == ClassPathRepresentationType.Recursive, + "To use recursive classpath representation you must enable it with -YclasspathImpl:recursive compiler option.") + if (currentClassPath.isEmpty) currentClassPath = Some(new PathResolver(settings).result) currentClassPath.get } + private[nsc] lazy val flatClassPath: FlatClassPath = { + assert(settings.YclasspathImpl.value == ClassPathRepresentationType.Flat, + "To use flat classpath representation you must enable it with -YclasspathImpl:flat compiler option.") + + new FlatClassPathResolver(settings).result + } + /** Update classpath with a substituted subentry */ def updateClassPath(subst: Map[ClassPath[AbstractFile], ClassPath[AbstractFile]]) = currentClassPath = Some(new DeltaClassPath(currentClassPath.get, subst)) diff --git a/src/compiler/scala/tools/nsc/backend/Platform.scala b/src/compiler/scala/tools/nsc/backend/Platform.scala index 439cc1efb8..c3bc213be1 100644 --- a/src/compiler/scala/tools/nsc/backend/Platform.scala +++ b/src/compiler/scala/tools/nsc/backend/Platform.scala @@ -8,6 +8,7 @@ package backend import util.ClassPath import io.AbstractFile +import scala.tools.nsc.classpath.FlatClassPath /** The platform dependent pieces of Global. */ @@ -15,9 +16,12 @@ trait Platform { val symbolTable: symtab.SymbolTable import symbolTable._ - /** The compiler classpath. */ + /** The old, recursive implementation of compiler classpath. */ def classPath: ClassPath[AbstractFile] + /** The new implementation of compiler classpath. */ + private[nsc] def flatClassPath: FlatClassPath + /** Update classpath with a substitution that maps entries to entries */ def updateClassPath(subst: Map[ClassPath[AbstractFile], ClassPath[AbstractFile]]) diff --git a/src/compiler/scala/tools/nsc/backend/icode/ICodes.scala b/src/compiler/scala/tools/nsc/backend/icode/ICodes.scala index bc35a9e7de..10f0c6ee00 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/ICodes.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/ICodes.scala @@ -113,7 +113,8 @@ abstract class ICodes extends AnyRef global.loaders.lookupMemberAtTyperPhaseIfPossible(sym, name) lazy val symbolTable: global.type = global lazy val loaders: global.loaders.type = global.loaders - def classPath: util.ClassPath[AbstractFile] = ICodes.this.global.platform.classPath + + def classFileLookup: util.ClassFileLookup[AbstractFile] = global.classPath } /** A phase which works on icode. */ diff --git a/src/compiler/scala/tools/nsc/classpath/AggregateFlatClassPath.scala b/src/compiler/scala/tools/nsc/classpath/AggregateFlatClassPath.scala new file mode 100644 index 0000000000..3f06264e3c --- /dev/null +++ b/src/compiler/scala/tools/nsc/classpath/AggregateFlatClassPath.scala @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2014 Contributor. All rights reserved. + */ +package scala.tools.nsc.classpath + +import java.net.URL +import scala.annotation.tailrec +import scala.collection.mutable.ArrayBuffer +import scala.reflect.io.AbstractFile +import scala.tools.nsc.util.ClassPath +import scala.tools.nsc.util.ClassRepresentation + +/** + * A classpath unifying multiple class- and sourcepath entries. + * Flat classpath can obtain entries for classes and sources independently + * so it tries to do operations quite optimally - iterating only these collections + * which are needed in the given moment and only as far as it's necessary. + * @param aggregates classpath instances containing entries which this class processes + */ +case class AggregateFlatClassPath(aggregates: Seq[FlatClassPath]) extends FlatClassPath { + + override def findClassFile(className: String): Option[AbstractFile] = { + @tailrec + def find(aggregates: Seq[FlatClassPath]): Option[AbstractFile] = + if (aggregates.nonEmpty) { + val classFile = aggregates.head.findClassFile(className) + if (classFile.isDefined) classFile + else find(aggregates.tail) + } else None + + find(aggregates) + } + + override def findClass(className: String): Option[ClassRepresentation[AbstractFile]] = { + val (pkg, simpleClassName) = PackageNameUtils.separatePkgAndClassNames(className) + + @tailrec + def findEntry[T <: ClassRepClassPathEntry](aggregates: Seq[FlatClassPath], getEntries: FlatClassPath => Seq[T]): Option[T] = + if (aggregates.nonEmpty) { + val entry = getEntries(aggregates.head) + .find(_.name == simpleClassName) + if (entry.isDefined) entry + else findEntry(aggregates.tail, getEntries) + } else None + + val classEntry = findEntry(aggregates, classesGetter(pkg)) + val sourceEntry = findEntry(aggregates, sourcesGetter(pkg)) + + mergeClassesAndSources(classEntry.toList, sourceEntry.toList).headOption + } + + override def asURLs: Seq[URL] = aggregates.flatMap(_.asURLs) + + override def asClassPathStrings: Seq[String] = aggregates.map(_.asClassPathString).distinct + + override def asSourcePathString: String = ClassPath.join(aggregates map (_.asSourcePathString): _*) + + override private[nsc] def packages(inPackage: String): Seq[PackageEntry] = { + val aggregatedPackages = aggregates.flatMap(_.packages(inPackage)).distinct + aggregatedPackages + } + + override private[nsc] def classes(inPackage: String): Seq[ClassFileEntry] = + getDistinctEntries(classesGetter(inPackage)) + + override private[nsc] def sources(inPackage: String): Seq[SourceFileEntry] = + getDistinctEntries(sourcesGetter(inPackage)) + + override private[nsc] def list(inPackage: String): FlatClassPathEntries = { + val (packages, classesAndSources) = aggregates.map(_.list(inPackage)).unzip + val distinctPackages = packages.flatten.distinct + val distinctClassesAndSources = mergeClassesAndSources(classesAndSources: _*) + FlatClassPathEntries(distinctPackages, distinctClassesAndSources) + } + + /** + * Returns only one entry for each name. If there's both a source and a class entry, it + * creates an entry containing both of them. If there would be more than one class or source + * entries for the same class it always would use the first entry of each type found on a classpath. + */ + private def mergeClassesAndSources(entries: Seq[ClassRepClassPathEntry]*): Seq[ClassRepClassPathEntry] = { + // based on the implementation from MergedClassPath + var count = 0 + val indices = collection.mutable.HashMap[String, Int]() + val mergedEntries = new ArrayBuffer[ClassRepClassPathEntry](1024) + + for { + partOfEntries <- entries + entry <- partOfEntries + } { + val name = entry.name + if (indices contains name) { + val index = indices(name) + val existing = mergedEntries(index) + + if (existing.binary.isEmpty && entry.binary.isDefined) + mergedEntries(index) = ClassAndSourceFilesEntry(entry.binary.get, existing.source.get) + if (existing.source.isEmpty && entry.source.isDefined) + mergedEntries(index) = ClassAndSourceFilesEntry(existing.binary.get, entry.source.get) + } + else { + indices(name) = count + mergedEntries += entry + count += 1 + } + } + mergedEntries.toIndexedSeq + } + + private def getDistinctEntries[EntryType <: ClassRepClassPathEntry](getEntries: FlatClassPath => Seq[EntryType]): Seq[EntryType] = { + val seenNames = collection.mutable.HashSet[String]() + val entriesBuffer = new ArrayBuffer[EntryType](1024) + for { + cp <- aggregates + entry <- getEntries(cp) if !seenNames.contains(entry.name) + } { + entriesBuffer += entry + seenNames += entry.name + } + entriesBuffer.toIndexedSeq + } + + private def classesGetter(pkg: String) = (cp: FlatClassPath) => cp.classes(pkg) + private def sourcesGetter(pkg: String) = (cp: FlatClassPath) => cp.sources(pkg) +} diff --git a/src/compiler/scala/tools/nsc/classpath/ClassPathFactory.scala b/src/compiler/scala/tools/nsc/classpath/ClassPathFactory.scala new file mode 100644 index 0000000000..9bf4e3f779 --- /dev/null +++ b/src/compiler/scala/tools/nsc/classpath/ClassPathFactory.scala @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2014 Contributor. All rights reserved. + */ +package scala.tools.nsc.classpath + +import scala.reflect.io.AbstractFile +import scala.tools.nsc.util.ClassPath + +/** + * A trait that contains factory methods for classpath elements of type T. + * + * The logic has been abstracted from ClassPath#ClassPathContext so it's possible + * to have common trait that supports both recursive and flat classpath representations. + * + * Therefore, we expect that T will be either ClassPath[U] or FlatClassPath. + */ +trait ClassPathFactory[T] { + + /** + * Create a new classpath based on the abstract file. + */ + def newClassPath(file: AbstractFile): T + + /** + * Creators for sub classpaths which preserve this context. + */ + def sourcesInPath(path: String): List[T] + + def expandPath(path: String, expandStar: Boolean = true): List[String] = ClassPath.expandPath(path, expandStar) + + def expandDir(extdir: String): List[String] = ClassPath.expandDir(extdir) + + def contentsOfDirsInPath(path: String): List[T] = + for { + dir <- expandPath(path, expandStar = false) + name <- expandDir(dir) + entry <- Option(AbstractFile.getDirectory(name)) + } yield newClassPath(entry) + + def classesInExpandedPath(path: String): IndexedSeq[T] = + classesInPathImpl(path, expand = true).toIndexedSeq + + def classesInPath(path: String) = classesInPathImpl(path, expand = false) + + def classesInManifest(useManifestClassPath: Boolean) = + if (useManifestClassPath) ClassPath.manifests.map(url => newClassPath(AbstractFile getResources url)) + else Nil + + // Internal + protected def classesInPathImpl(path: String, expand: Boolean) = + for { + file <- expandPath(path, expand) + dir <- Option(AbstractFile.getDirectory(file)) + } yield newClassPath(dir) +} diff --git a/src/compiler/scala/tools/nsc/classpath/DirectoryFlatClassPath.scala b/src/compiler/scala/tools/nsc/classpath/DirectoryFlatClassPath.scala new file mode 100644 index 0000000000..81d2f7320f --- /dev/null +++ b/src/compiler/scala/tools/nsc/classpath/DirectoryFlatClassPath.scala @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2014 Contributor. All rights reserved. + */ +package scala.tools.nsc.classpath + +import java.io.File +import java.io.FileFilter +import java.net.URL +import scala.reflect.io.AbstractFile +import scala.reflect.io.PlainFile +import scala.tools.nsc.util.ClassRepresentation +import FileUtils._ + +/** + * A trait allowing to look for classpath entries of given type in directories. + * It provides common logic for classes handling class and source files. + * It makes use of the fact that in the case of nested directories it's easy to find a file + * when we have a name of a package. + */ +trait DirectoryFileLookup[FileEntryType <: ClassRepClassPathEntry] extends FlatClassPath { + val dir: File + assert(dir != null, "Directory file in DirectoryFileLookup cannot be null") + + override def asURLs: Seq[URL] = Seq(dir.toURI.toURL) + override def asClassPathStrings: Seq[String] = Seq(dir.getPath) + + import FlatClassPath.RootPackage + private def getDirectory(forPackage: String): Option[File] = { + if (forPackage == RootPackage) { + Some(dir) + } else { + val packageDirName = FileUtils.dirPath(forPackage) + val packageDir = new File(dir, packageDirName) + if (packageDir.exists && packageDir.isDirectory) { + Some(packageDir) + } else None + } + } + + override private[nsc] def packages(inPackage: String): Seq[PackageEntry] = { + val dirForPackage = getDirectory(inPackage) + val nestedDirs: Array[File] = dirForPackage match { + case None => Array.empty + case Some(directory) => directory.listFiles(DirectoryFileLookup.packageDirectoryFileFilter) + } + val prefix = PackageNameUtils.packagePrefix(inPackage) + val entries = nestedDirs map { file => + PackageEntryImpl(prefix + file.getName) + } + entries + } + + protected def files(inPackage: String): Seq[FileEntryType] = { + val dirForPackage = getDirectory(inPackage) + val files: Array[File] = dirForPackage match { + case None => Array.empty + case Some(directory) => directory.listFiles(fileFilter) + } + val entries = files map { file => + val wrappedFile = new scala.reflect.io.File(file) + createFileEntry(new PlainFile(wrappedFile)) + } + entries + } + + override private[nsc] def list(inPackage: String): FlatClassPathEntries = { + val dirForPackage = getDirectory(inPackage) + val files: Array[File] = dirForPackage match { + case None => Array.empty + case Some(directory) => directory.listFiles() + } + val packagePrefix = PackageNameUtils.packagePrefix(inPackage) + val packageBuf = collection.mutable.ArrayBuffer.empty[PackageEntry] + val fileBuf = collection.mutable.ArrayBuffer.empty[FileEntryType] + for (file <- files) { + if (file.isPackage) { + val pkgEntry = PackageEntryImpl(packagePrefix + file.getName) + packageBuf += pkgEntry + } else if (fileFilter.accept(file)) { + val wrappedFile = new scala.reflect.io.File(file) + val abstractFile = new PlainFile(wrappedFile) + fileBuf += createFileEntry(abstractFile) + } + } + FlatClassPathEntries(packageBuf, fileBuf) + } + + protected def createFileEntry(file: AbstractFile): FileEntryType + protected def fileFilter: FileFilter +} + +object DirectoryFileLookup { + + private[classpath] object packageDirectoryFileFilter extends FileFilter { + override def accept(pathname: File): Boolean = pathname.isPackage + } +} + +case class DirectoryFlatClassPath(dir: File) + extends DirectoryFileLookup[ClassFileEntryImpl] + with NoSourcePaths { + + override def findClass(className: String): Option[ClassRepresentation[AbstractFile]] = findClassFile(className) map ClassFileEntryImpl + + override def findClassFile(className: String): Option[AbstractFile] = { + val relativePath = FileUtils.dirPath(className) + val classFile = new File(s"$dir/$relativePath.class") + if (classFile.exists) { + val wrappedClassFile = new scala.reflect.io.File(classFile) + val abstractClassFile = new PlainFile(wrappedClassFile) + Some(abstractClassFile) + } else None + } + + override protected def createFileEntry(file: AbstractFile): ClassFileEntryImpl = ClassFileEntryImpl(file) + override protected def fileFilter: FileFilter = DirectoryFlatClassPath.classFileFilter + + override private[nsc] def classes(inPackage: String): Seq[ClassFileEntry] = files(inPackage) +} + +object DirectoryFlatClassPath { + + private val classFileFilter = new FileFilter { + override def accept(pathname: File): Boolean = pathname.isClass + } +} + +case class DirectoryFlatSourcePath(dir: File) + extends DirectoryFileLookup[SourceFileEntryImpl] + with NoClassPaths { + + override def asSourcePathString: String = asClassPathString + + override protected def createFileEntry(file: AbstractFile): SourceFileEntryImpl = SourceFileEntryImpl(file) + override protected def fileFilter: FileFilter = DirectoryFlatSourcePath.sourceFileFilter + + override def findClass(className: String): Option[ClassRepresentation[AbstractFile]] = { + findSourceFile(className) map SourceFileEntryImpl + } + + private def findSourceFile(className: String): Option[AbstractFile] = { + val relativePath = FileUtils.dirPath(className) + val sourceFile = Stream("scala", "java") + .map(ext => new File(s"$dir/$relativePath.$ext")) + .collectFirst { case file if file.exists() => file } + + sourceFile.map { file => + val wrappedSourceFile = new scala.reflect.io.File(file) + val abstractSourceFile = new PlainFile(wrappedSourceFile) + abstractSourceFile + } + } + + override private[nsc] def sources(inPackage: String): Seq[SourceFileEntry] = files(inPackage) +} + +object DirectoryFlatSourcePath { + + private val sourceFileFilter = new FileFilter { + override def accept(pathname: File): Boolean = endsScalaOrJava(pathname.getName) + } +} diff --git a/src/compiler/scala/tools/nsc/classpath/FileUtils.scala b/src/compiler/scala/tools/nsc/classpath/FileUtils.scala new file mode 100644 index 0000000000..ee2528e15c --- /dev/null +++ b/src/compiler/scala/tools/nsc/classpath/FileUtils.scala @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2014 Contributor. All rights reserved. + */ +package scala.tools.nsc.classpath + +import java.io.{ File => JFile } +import java.net.URL +import scala.reflect.internal.FatalError +import scala.reflect.io.AbstractFile + +/** + * Common methods related to Java files and abstract files used in the context of classpath + */ +object FileUtils { + implicit class AbstractFileOps(val file: AbstractFile) extends AnyVal { + def isPackage: Boolean = file.isDirectory && mayBeValidPackage(file.name) + + def isClass: Boolean = !file.isDirectory && file.hasExtension("class") + + def isScalaOrJavaSource: Boolean = !file.isDirectory && (file.hasExtension("scala") || file.hasExtension("java")) + + // TODO do we need to check also other files using ZipMagicNumber like in scala.tools.nsc.io.Jar.isJarOrZip? + def isJarOrZip: Boolean = file.hasExtension("jar") || file.hasExtension("zip") + + /** + * Safe method returning a sequence containing one URL representing this file, when underlying file exists, + * and returning given default value in other case + */ + def toURLs(default: => Seq[URL] = Seq.empty): Seq[URL] = if (file.file == null) default else Seq(file.toURL) + } + + implicit class FileOps(val file: JFile) extends AnyVal { + def isPackage: Boolean = file.isDirectory && mayBeValidPackage(file.getName) + + def isClass: Boolean = file.isFile && file.getName.endsWith(".class") + } + + def stripSourceExtension(fileName: String): String = { + if (endsScala(fileName)) stripClassExtension(fileName) + else if (endsJava(fileName)) stripJavaExtension(fileName) + else throw new FatalError("Unexpected source file ending: " + fileName) + } + + def dirPath(forPackage: String) = forPackage.replace('.', '/') + + def endsClass(fileName: String): Boolean = + fileName.length > 6 && fileName.substring(fileName.length - 6) == ".class" + + def endsScalaOrJava(fileName: String): Boolean = + endsScala(fileName) || endsJava(fileName) + + def endsJava(fileName: String): Boolean = + fileName.length > 5 && fileName.substring(fileName.length - 5) == ".java" + + def endsScala(fileName: String): Boolean = + fileName.length > 6 && fileName.substring(fileName.length - 6) == ".scala" + + def stripClassExtension(fileName: String): String = + fileName.substring(0, fileName.length - 6) // equivalent of fileName.length - ".class".length + + def stripJavaExtension(fileName: String): String = + fileName.substring(0, fileName.length - 5) + + // probably it should match a pattern like [a-z_]{1}[a-z0-9_]* but it cannot be changed + // because then some tests in partest don't pass + private def mayBeValidPackage(dirName: String): Boolean = + (dirName != "META-INF") && (dirName != "") && (dirName.charAt(0) != '.') +} diff --git a/src/compiler/scala/tools/nsc/classpath/FlatClassPath.scala b/src/compiler/scala/tools/nsc/classpath/FlatClassPath.scala new file mode 100644 index 0000000000..26b5429e23 --- /dev/null +++ b/src/compiler/scala/tools/nsc/classpath/FlatClassPath.scala @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2014 Contributor. All rights reserved. + */ +package scala.tools.nsc.classpath + +import scala.reflect.io.AbstractFile +import scala.tools.nsc.util.{ ClassFileLookup, ClassPath, ClassRepresentation } + +/** + * A base trait for the particular flat classpath representation implementations. + * + * We call this variant of a classpath representation flat because it's possible to + * query the whole classpath using just single instance extending this trait. + * + * This is an alternative design compared to scala.tools.nsc.util.ClassPath + */ +trait FlatClassPath extends ClassFileLookup[AbstractFile] { + /** Empty string represents root package */ + private[nsc] def packages(inPackage: String): Seq[PackageEntry] + private[nsc] def classes(inPackage: String): Seq[ClassFileEntry] + private[nsc] def sources(inPackage: String): Seq[SourceFileEntry] + + /** Allows to get entries for packages and classes merged with sources possibly in one pass. */ + private[nsc] def list(inPackage: String): FlatClassPathEntries + + // A default implementation which should be overriden, if we can create more efficient + // solution for given type of FlatClassPath + override def findClass(className: String): Option[ClassRepresentation[AbstractFile]] = { + val (pkg, simpleClassName) = PackageNameUtils.separatePkgAndClassNames(className) + + val foundClassFromClassFiles = classes(pkg) + .find(_.name == simpleClassName) + + def findClassInSources = sources(pkg) + .find(_.name == simpleClassName) + + foundClassFromClassFiles orElse findClassInSources + } + + override def asClassPathString: String = ClassPath.join(asClassPathStrings: _*) + def asClassPathStrings: Seq[String] +} + +object FlatClassPath { + val RootPackage = "" +} + +case class FlatClassPathEntries(packages: Seq[PackageEntry], classesAndSources: Seq[ClassRepClassPathEntry]) + +object FlatClassPathEntries { + import scala.language.implicitConversions + // to have working unzip method + implicit def entry2Tuple(entry: FlatClassPathEntries) = (entry.packages, entry.classesAndSources) +} + +sealed trait ClassRepClassPathEntry extends ClassRepresentation[AbstractFile] + +trait ClassFileEntry extends ClassRepClassPathEntry { + def file: AbstractFile +} + +trait SourceFileEntry extends ClassRepClassPathEntry { + def file: AbstractFile +} + +trait PackageEntry { + def name: String +} + +private[nsc] case class ClassFileEntryImpl(file: AbstractFile) extends ClassFileEntry { + override def name = FileUtils.stripClassExtension(file.name) // class name + + override def binary: Option[AbstractFile] = Some(file) + override def source: Option[AbstractFile] = None +} + +private[nsc] case class SourceFileEntryImpl(file: AbstractFile) extends SourceFileEntry { + override def name = FileUtils.stripSourceExtension(file.name) + + override def binary: Option[AbstractFile] = None + override def source: Option[AbstractFile] = Some(file) +} + +private[nsc] case class ClassAndSourceFilesEntry(classFile: AbstractFile, srcFile: AbstractFile) extends ClassRepClassPathEntry { + override def name = FileUtils.stripClassExtension(classFile.name) + + override def binary: Option[AbstractFile] = Some(classFile) + override def source: Option[AbstractFile] = Some(srcFile) +} + +private[nsc] case class PackageEntryImpl(name: String) extends PackageEntry + +private[nsc] trait NoSourcePaths { + def asSourcePathString: String = "" + private[nsc] def sources(inPackage: String): Seq[SourceFileEntry] = Seq.empty +} + +private[nsc] trait NoClassPaths { + def findClassFile(className: String): Option[AbstractFile] = None + private[nsc] def classes(inPackage: String): Seq[ClassFileEntry] = Seq.empty +} diff --git a/src/compiler/scala/tools/nsc/classpath/FlatClassPathFactory.scala b/src/compiler/scala/tools/nsc/classpath/FlatClassPathFactory.scala new file mode 100644 index 0000000000..7f67381d4d --- /dev/null +++ b/src/compiler/scala/tools/nsc/classpath/FlatClassPathFactory.scala @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2014 Contributor. All rights reserved. + */ +package scala.tools.nsc.classpath + +import scala.tools.nsc.Settings +import scala.tools.nsc.io.AbstractFile +import scala.tools.nsc.util.ClassPath +import FileUtils.AbstractFileOps + +/** + * Provides factory methods for flat classpath. When creating classpath instances for a given path, + * it uses proper type of classpath depending on a types of particular files containing sources or classes. + */ +class FlatClassPathFactory(settings: Settings) extends ClassPathFactory[FlatClassPath] { + + override def newClassPath(file: AbstractFile): FlatClassPath = + if (file.isJarOrZip) + ZipAndJarFlatClassPathFactory.create(file, settings) + else if (file.isDirectory) + new DirectoryFlatClassPath(file.file) + else + sys.error(s"Unsupported classpath element: $file") + + override def sourcesInPath(path: String): List[FlatClassPath] = + for { + file <- expandPath(path, expandStar = false) + dir <- Option(AbstractFile getDirectory file) + } yield createSourcePath(dir) + + private def createSourcePath(file: AbstractFile): FlatClassPath = + if (file.isJarOrZip) + ZipAndJarFlatSourcePathFactory.create(file, settings) + else if (file.isDirectory) + new DirectoryFlatSourcePath(file.file) + else + sys.error(s"Unsupported sourcepath element: $file") +} diff --git a/src/compiler/scala/tools/nsc/classpath/PackageNameUtils.scala b/src/compiler/scala/tools/nsc/classpath/PackageNameUtils.scala new file mode 100644 index 0000000000..c907d565d2 --- /dev/null +++ b/src/compiler/scala/tools/nsc/classpath/PackageNameUtils.scala @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2014 Contributor. All rights reserved. + */ +package scala.tools.nsc.classpath + +import scala.tools.nsc.classpath.FlatClassPath.RootPackage + +/** + * Common methods related to package names represented as String + */ +object PackageNameUtils { + + /** + * @param fullClassName full class name with package + * @return (package, simple class name) + */ + def separatePkgAndClassNames(fullClassName: String): (String, String) = { + val lastDotIndex = fullClassName.lastIndexOf('.') + if (lastDotIndex == -1) + (RootPackage, fullClassName) + else + (fullClassName.substring(0, lastDotIndex), fullClassName.substring(lastDotIndex + 1)) + } + + def packagePrefix(inPackage: String): String = if (inPackage == RootPackage) "" else inPackage + "." +} diff --git a/src/compiler/scala/tools/nsc/classpath/ZipAndJarFileLookupFactory.scala b/src/compiler/scala/tools/nsc/classpath/ZipAndJarFileLookupFactory.scala new file mode 100644 index 0000000000..84e21a3ccd --- /dev/null +++ b/src/compiler/scala/tools/nsc/classpath/ZipAndJarFileLookupFactory.scala @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2014 Contributor. All rights reserved. + */ +package scala.tools.nsc.classpath + +import java.io.File +import java.net.URL +import scala.annotation.tailrec +import scala.reflect.io.{ AbstractFile, FileZipArchive, ManifestResources } +import scala.tools.nsc.Settings +import FileUtils._ + +/** + * A trait providing an optional cache for classpath entries obtained from zip and jar files. + * It's possible to create such a cache assuming that entries in such files won't change (at + * least will be the same each time we'll load classpath during the lifetime of JVM process) + * - unlike class and source files in directories, which can be modified and recompiled. + * It allows us to e.g. reduce significantly memory used by PresentationCompilers in Scala IDE + * when there are a lot of projects having a lot of common dependencies. + */ +sealed trait ZipAndJarFileLookupFactory { + + private val cache = collection.mutable.Map.empty[AbstractFile, FlatClassPath] + + def create(zipFile: AbstractFile, settings: Settings): FlatClassPath = { + if (settings.YdisableFlatCpCaching) createForZipFile(zipFile) + else createUsingCache(zipFile, settings) + } + + protected def createForZipFile(zipFile: AbstractFile): FlatClassPath + + private def createUsingCache(zipFile: AbstractFile, settings: Settings): FlatClassPath = cache.synchronized { + def newClassPathInstance = { + if (settings.verbose || settings.Ylogcp) + println(s"$zipFile is not yet in the classpath cache") + createForZipFile(zipFile) + } + cache.getOrElseUpdate(zipFile, newClassPathInstance) + } +} + +/** + * Manages creation of flat classpath for class files placed in zip and jar files. + * It should be the only way of creating them as it provides caching. + */ +object ZipAndJarFlatClassPathFactory extends ZipAndJarFileLookupFactory { + + private case class ZipArchiveFlatClassPath(zipFile: File) + extends ZipArchiveFileLookup[ClassFileEntryImpl] + with NoSourcePaths { + + override def findClassFile(className: String): Option[AbstractFile] = { + val (pkg, simpleClassName) = PackageNameUtils.separatePkgAndClassNames(className) + classes(pkg).find(_.name == simpleClassName).map(_.file) + } + + override private[nsc] def classes(inPackage: String): Seq[ClassFileEntry] = files(inPackage) + + override protected def createFileEntry(file: FileZipArchive#Entry): ClassFileEntryImpl = ClassFileEntryImpl(file) + override protected def isRequiredFileType(file: AbstractFile): Boolean = file.isClass + } + + /** + * This type of classpath is closly related to the support for JSR-223. + * Its usage can be observed e.g. when running: + * jrunscript -classpath scala-compiler.jar;scala-reflect.jar;scala-library.jar -l scala + * with a particularly prepared scala-library.jar. It should have all classes listed in the manifest like e.g. this entry: + * Name: scala/Function2$mcFJD$sp.class + */ + private case class ManifestResourcesFlatClassPath(file: ManifestResources) + extends FlatClassPath + with NoSourcePaths { + + override def findClassFile(className: String): Option[AbstractFile] = { + val (pkg, simpleClassName) = PackageNameUtils.separatePkgAndClassNames(className) + classes(pkg).find(_.name == simpleClassName).map(_.file) + } + + override def asClassPathStrings: Seq[String] = Seq(file.path) + + override def asURLs: Seq[URL] = file.toURLs() + + import ManifestResourcesFlatClassPath.PackageFileInfo + import ManifestResourcesFlatClassPath.PackageInfo + + /** + * A cache mapping package name to abstract file for package directory and subpackages of given package. + * + * ManifestResources can iterate through the collections of entries from e.g. remote jar file. + * We can't just specify the path to the concrete directory etc. so we can't just 'jump' into + * given package, when it's needed. On the other hand we can iterate over entries to get + * AbstractFiles, iterate over entries of these files etc. + * + * Instead of traversing a tree of AbstractFiles once and caching all entries or traversing each time, + * when we need subpackages of a given package or its classes, we traverse once and cache only packages. + * Classes for given package can be then easily loaded when they are needed. + */ + private lazy val cachedPackages: collection.mutable.HashMap[String, PackageFileInfo] = { + val packages = collection.mutable.HashMap[String, PackageFileInfo]() + + def getSubpackages(dir: AbstractFile): List[AbstractFile] = + (for (file <- dir if file.isPackage) yield file)(collection.breakOut) + + @tailrec + def traverse(packagePrefix: String, + filesForPrefix: List[AbstractFile], + subpackagesQueue: collection.mutable.Queue[PackageInfo]): Unit = filesForPrefix match { + case pkgFile :: remainingFiles => + val subpackages = getSubpackages(pkgFile) + val fullPkgName = packagePrefix + pkgFile.name + packages.put(fullPkgName, PackageFileInfo(pkgFile, subpackages)) + val newPackagePrefix = fullPkgName + "." + subpackagesQueue.enqueue(PackageInfo(newPackagePrefix, subpackages)) + traverse(packagePrefix, remainingFiles, subpackagesQueue) + case Nil if subpackagesQueue.nonEmpty => + val PackageInfo(packagePrefix, filesForPrefix) = subpackagesQueue.dequeue() + traverse(packagePrefix, filesForPrefix, subpackagesQueue) + case _ => + } + + val subpackages = getSubpackages(file) + packages.put(FlatClassPath.RootPackage, PackageFileInfo(file, subpackages)) + traverse(FlatClassPath.RootPackage, subpackages, collection.mutable.Queue()) + packages + } + + override private[nsc] def packages(inPackage: String): Seq[PackageEntry] = cachedPackages.get(inPackage) match { + case None => Seq.empty + case Some(PackageFileInfo(_, subpackages)) => + val prefix = PackageNameUtils.packagePrefix(inPackage) + subpackages.map(packageFile => PackageEntryImpl(prefix + packageFile.name)) + } + + override private[nsc] def classes(inPackage: String): Seq[ClassFileEntry] = cachedPackages.get(inPackage) match { + case None => Seq.empty + case Some(PackageFileInfo(pkg, _)) => + (for (file <- pkg if file.isClass) yield ClassFileEntryImpl(file))(collection.breakOut) + } + + override private[nsc] def list(inPackage: String): FlatClassPathEntries = FlatClassPathEntries(packages(inPackage), classes(inPackage)) + } + + private object ManifestResourcesFlatClassPath { + case class PackageFileInfo(packageFile: AbstractFile, subpackages: Seq[AbstractFile]) + case class PackageInfo(packageName: String, subpackages: List[AbstractFile]) + } + + override protected def createForZipFile(zipFile: AbstractFile): FlatClassPath = + if (zipFile.file == null) createWithoutUnderlyingFile(zipFile) + else ZipArchiveFlatClassPath(zipFile.file) + + private def createWithoutUnderlyingFile(zipFile: AbstractFile) = zipFile match { + case manifestRes: ManifestResources => + ManifestResourcesFlatClassPath(manifestRes) + case _ => + val errorMsg = s"Abstract files which don't have an underlying file and are not ManifestResources are not supported. There was $zipFile" + throw new IllegalArgumentException(errorMsg) + } +} + +/** + * Manages creation of flat classpath for source files placed in zip and jar files. + * It should be the only way of creating them as it provides caching. + */ +object ZipAndJarFlatSourcePathFactory extends ZipAndJarFileLookupFactory { + + private case class ZipArchiveFlatSourcePath(zipFile: File) + extends ZipArchiveFileLookup[SourceFileEntryImpl] + with NoClassPaths { + + override def asSourcePathString: String = asClassPathString + + override private[nsc] def sources(inPackage: String): Seq[SourceFileEntry] = files(inPackage) + + override protected def createFileEntry(file: FileZipArchive#Entry): SourceFileEntryImpl = SourceFileEntryImpl(file) + override protected def isRequiredFileType(file: AbstractFile): Boolean = file.isScalaOrJavaSource + } + + override protected def createForZipFile(zipFile: AbstractFile): FlatClassPath = ZipArchiveFlatSourcePath(zipFile.file) +} diff --git a/src/compiler/scala/tools/nsc/classpath/ZipArchiveFileLookup.scala b/src/compiler/scala/tools/nsc/classpath/ZipArchiveFileLookup.scala new file mode 100644 index 0000000000..1d0de57779 --- /dev/null +++ b/src/compiler/scala/tools/nsc/classpath/ZipArchiveFileLookup.scala @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2014 Contributor. All rights reserved. + */ +package scala.tools.nsc.classpath + +import java.io.File +import java.net.URL +import scala.collection.Seq +import scala.reflect.io.AbstractFile +import scala.reflect.io.FileZipArchive +import FileUtils.AbstractFileOps + +/** + * A trait allowing to look for classpath entries of given type in zip and jar files. + * It provides common logic for classes handling class and source files. + * It's aware of things like e.g. META-INF directory which is correctly skipped. + */ +trait ZipArchiveFileLookup[FileEntryType <: ClassRepClassPathEntry] extends FlatClassPath { + val zipFile: File + + assert(zipFile != null, "Zip file in ZipArchiveFileLookup cannot be null") + + override def asURLs: Seq[URL] = Seq(zipFile.toURI.toURL) + override def asClassPathStrings: Seq[String] = Seq(zipFile.getPath) + + private val archive = new FileZipArchive(zipFile) + + override private[nsc] def packages(inPackage: String): Seq[PackageEntry] = { + val prefix = PackageNameUtils.packagePrefix(inPackage) + for { + dirEntry <- findDirEntry(inPackage).toSeq + entry <- dirEntry.iterator if entry.isPackage + } yield PackageEntryImpl(prefix + entry.name) + } + + protected def files(inPackage: String): Seq[FileEntryType] = + for { + dirEntry <- findDirEntry(inPackage).toSeq + entry <- dirEntry.iterator if isRequiredFileType(entry) + } yield createFileEntry(entry) + + override private[nsc] def list(inPackage: String): FlatClassPathEntries = { + val foundDirEntry = findDirEntry(inPackage) + + foundDirEntry map { dirEntry => + val pkgBuf = collection.mutable.ArrayBuffer.empty[PackageEntry] + val fileBuf = collection.mutable.ArrayBuffer.empty[FileEntryType] + val prefix = PackageNameUtils.packagePrefix(inPackage) + + for (entry <- dirEntry.iterator) { + if (entry.isPackage) + pkgBuf += PackageEntryImpl(prefix + entry.name) + else if (isRequiredFileType(entry)) + fileBuf += createFileEntry(entry) + } + FlatClassPathEntries(pkgBuf, fileBuf) + } getOrElse FlatClassPathEntries(Seq.empty, Seq.empty) + } + + private def findDirEntry(pkg: String) = { + val dirName = s"${FileUtils.dirPath(pkg)}/" + archive.allDirs.get(dirName) + } + + protected def createFileEntry(file: FileZipArchive#Entry): FileEntryType + protected def isRequiredFileType(file: AbstractFile): Boolean +} diff --git a/src/compiler/scala/tools/nsc/plugins/Plugins.scala b/src/compiler/scala/tools/nsc/plugins/Plugins.scala index 6e3d013e52..4b1805479d 100644 --- a/src/compiler/scala/tools/nsc/plugins/Plugins.scala +++ b/src/compiler/scala/tools/nsc/plugins/Plugins.scala @@ -7,7 +7,7 @@ package scala.tools.nsc package plugins -import scala.reflect.io.{ File, Path } +import scala.reflect.io.Path import scala.tools.nsc.util.ClassPath import scala.tools.util.PathResolver.Defaults diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index d6650595eb..18e639b81c 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -202,6 +202,9 @@ trait ScalaSettings extends AbsScalaSettings val YmethodInfer = BooleanSetting ("-Yinfer-argument-types", "Infer types for arguments of overriden methods.") val etaExpandKeepsStar = BooleanSetting ("-Yeta-expand-keeps-star", "Eta-expand varargs methods to T* rather than Seq[T]. This is a temporary option to ease transition.").withDeprecationMessage(removalIn212) val inferByName = BooleanSetting ("-Yinfer-by-name", "Allow inference of by-name types. This is a temporary option to ease transition. See SI-7899.").withDeprecationMessage(removalIn212) + val YclasspathImpl = ChoiceSetting ("-YclasspathImpl", "implementation", "Choose classpath scanning method.", List(ClassPathRepresentationType.Recursive, ClassPathRepresentationType.Flat), ClassPathRepresentationType.Recursive) + val YdisableFlatCpCaching = BooleanSetting ("-YdisableFlatCpCaching", "Do not cache flat classpath representation of classpath elements from jars across compiler instances.") + val YvirtClasses = false // too embryonic to even expose as a -Y //BooleanSetting ("-Yvirtual-classes", "Support virtual classes") val YdisableUnreachablePrevention = BooleanSetting("-Ydisable-unreachable-prevention", "Disable the prevention of unreachable blocks in code generation.") val YnoLoadImplClass = BooleanSetting ("-Yno-load-impl-class", "Do not load $class.class files.") @@ -329,3 +332,8 @@ trait ScalaSettings extends AbsScalaSettings val Discard = "discard" } } + +object ClassPathRepresentationType { + val Flat = "flat" + val Recursive = "recursive" +} diff --git a/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala b/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala index 82c2a4d6ed..9af3efbece 100644 --- a/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala +++ b/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala @@ -6,13 +6,15 @@ package scala.tools.nsc package symtab +import classfile.ClassfileParser import java.io.IOException import scala.compat.Platform.currentTime -import scala.tools.nsc.util.{ ClassPath } -import classfile.ClassfileParser import scala.reflect.internal.MissingRequirementError import scala.reflect.internal.util.Statistics import scala.reflect.io.{ AbstractFile, NoAbstractFile } +import scala.tools.nsc.classpath.FlatClassPath +import scala.tools.nsc.settings.ClassPathRepresentationType +import scala.tools.nsc.util.{ ClassPath, ClassRepresentation } /** This class ... * @@ -86,8 +88,7 @@ abstract class SymbolLoaders { // require yjp.jar at runtime. See SI-2089. if (settings.termConflict.isDefault) throw new TypeError( - root+" contains object and package with same name: "+ - name+"\none of them needs to be removed from classpath" + s"$root contains object and package with same name: $name\none of them needs to be removed from classpath" ) else if (settings.termConflict.value == "package") { warning( @@ -154,7 +155,7 @@ abstract class SymbolLoaders { /** Initialize toplevel class and module symbols in `owner` from class path representation `classRep` */ - def initializeFromClassPath(owner: Symbol, classRep: ClassPath[AbstractFile]#ClassRep) { + def initializeFromClassPath(owner: Symbol, classRep: ClassRepresentation[AbstractFile]) { ((classRep.binary, classRep.source) : @unchecked) match { case (Some(bin), Some(src)) if platform.needCompile(bin, src) && !binaryOnly(owner, classRep.name) => @@ -250,7 +251,7 @@ abstract class SymbolLoaders { * Load contents of a package */ class PackageLoader(classpath: ClassPath[AbstractFile]) extends SymbolLoader with FlagAgnosticCompleter { - protected def description = "package loader "+ classpath.name + protected def description = s"package loader ${classpath.name}" protected def doComplete(root: Symbol) { assert(root.isPackageClass, root) @@ -276,6 +277,39 @@ abstract class SymbolLoaders { } } + /** + * Loads contents of a package + */ + class PackageLoaderUsingFlatClassPath(packageName: String, classPath: FlatClassPath) extends SymbolLoader with FlagAgnosticCompleter { + protected def description = { + val shownPackageName = if (packageName == FlatClassPath.RootPackage) "<root package>" else packageName + s"package loader $shownPackageName" + } + + protected def doComplete(root: Symbol) { + assert(root.isPackageClass, root) + root.setInfo(new PackageClassInfoType(newScope, root)) + + val classPathEntries = classPath.list(packageName) + + if (!root.isRoot) + for (entry <- classPathEntries.classesAndSources) initializeFromClassPath(root, entry) + if (!root.isEmptyPackageClass) { + for (pkg <- classPathEntries.packages) { + val fullName = pkg.name + + val name = + if (packageName == FlatClassPath.RootPackage) fullName + else fullName.substring(packageName.length + 1) + val packageLoader = new PackageLoaderUsingFlatClassPath(fullName, classPath) + enterPackage(root, name, packageLoader) + } + + openPackageModule(root) + } + } + } + class ClassfileLoader(val classfile: AbstractFile) extends SymbolLoader with FlagAssigningCompleter { private object classfileParser extends { val symbolTable: SymbolLoaders.this.symbolTable.type = SymbolLoaders.this.symbolTable @@ -293,8 +327,13 @@ abstract class SymbolLoaders { * */ private type SymbolLoadersRefined = SymbolLoaders { val symbolTable: classfileParser.symbolTable.type } + val loaders = SymbolLoaders.this.asInstanceOf[SymbolLoadersRefined] - val classPath = platform.classPath + + override def classFileLookup: util.ClassFileLookup[AbstractFile] = settings.YclasspathImpl.value match { + case ClassPathRepresentationType.Recursive => platform.classPath + case ClassPathRepresentationType.Flat => platform.flatClassPath + } } protected def description = "class file "+ classfile.toString diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala index 14be8374b9..1abbdb50b0 100644 --- a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala +++ b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala @@ -16,8 +16,7 @@ import scala.annotation.switch import scala.reflect.internal.{ JavaAccFlags } import scala.reflect.internal.pickling.{PickleBuffer, ByteCodecs} import scala.tools.nsc.io.AbstractFile - -import util.ClassPath +import scala.tools.nsc.util.ClassFileLookup /** This abstract class implements a class file parser. * @@ -43,8 +42,8 @@ abstract class ClassfileParser { */ protected def lookupMemberAtTyperPhaseIfPossible(sym: Symbol, name: Name): Symbol - /** The compiler classpath. */ - def classPath: ClassPath[AbstractFile] + /** The way of the class file lookup used by the compiler. */ + def classFileLookup: ClassFileLookup[AbstractFile] import definitions._ import scala.reflect.internal.ClassfileConstants._ @@ -352,7 +351,7 @@ abstract class ClassfileParser { } private def loadClassSymbol(name: Name): Symbol = { - val file = classPath findClassFile ("" +name) getOrElse { + val file = classFileLookup findClassFile name.toString getOrElse { // SI-5593 Scaladoc's current strategy is to visit all packages in search of user code that can be documented // therefore, it will rummage through the classpath triggering errors whenever it encounters package objects // that are not in their correct place (see bug for details) @@ -1047,8 +1046,8 @@ abstract class ClassfileParser { for (entry <- innerClasses.entries) { // create a new class member for immediate inner classes if (entry.outerName == currentClass) { - val file = classPath.findClassFile(entry.externalName.toString) getOrElse { - throw new AssertionError(entry.externalName) + val file = classFileLookup.findClassFile(entry.externalName.toString) getOrElse { + throw new AssertionError(s"Class file for ${entry.externalName} not found") } enterClassAndModule(entry, file) } diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/ICodeReader.scala b/src/compiler/scala/tools/nsc/symtab/classfile/ICodeReader.scala index cbe427775a..bd1fa4e707 100644 --- a/src/compiler/scala/tools/nsc/symtab/classfile/ICodeReader.scala +++ b/src/compiler/scala/tools/nsc/symtab/classfile/ICodeReader.scala @@ -130,7 +130,7 @@ abstract class ICodeReader extends ClassfileParser { log("ICodeReader reading " + cls) val name = cls.javaClassName - classPath.findClassFile(name) match { + classFileLookup.findClassFile(name) match { case Some(classFile) => parse(classFile, cls) case _ => MissingRequirementError.notFound("Could not find bytecode for " + cls) } diff --git a/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala b/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala index 2b7c6cca2c..f786ffb8f3 100644 --- a/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala +++ b/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala @@ -8,6 +8,7 @@ package transform import symtab._ import Flags._ +import scala.tools.nsc.util.ClassPath abstract class AddInterfaces extends InfoTransform { self: Erasure => import global._ // the global environment @@ -67,25 +68,30 @@ abstract class AddInterfaces extends InfoTransform { self: Erasure => val implName = tpnme.implClassName(iface.name) val implFlags = (iface.flags & ~(INTERFACE | lateINTERFACE)) | IMPLCLASS - val impl0 = ( + val impl0 = { if (!inClass) NoSymbol - else iface.owner.info.decl(implName) match { - case NoSymbol => NoSymbol - case implSym => - // Unlink a pre-existing symbol only if the implementation class is - // visible on the compilation classpath. In general this is true under - // -optimise and not otherwise, but the classpath can use arbitrary - // logic so the classpath must be queried. - if (classPath.context.isValidName(implName + ".class")) { - iface.owner.info.decls unlink implSym - NoSymbol - } - else { - log(s"not unlinking $iface's existing implClass ${implSym.name} because it is not on the classpath.") - implSym - } + else { + val typeInfo = iface.owner.info + typeInfo.decl(implName) match { + case NoSymbol => NoSymbol + case implSym => + // Unlink a pre-existing symbol only if the implementation class is + // visible on the compilation classpath. In general this is true under + // -optimise and not otherwise, but the classpath can use arbitrary + // logic so the classpath must be queried. + // TODO this is not taken into account by flat classpath yet + classPath match { + case cp: ClassPath[_] if !cp.context.isValidName(implName + ".class") => + log(s"not unlinking $iface's existing implClass ${implSym.name} because it is not on the classpath.") + implSym + case _ => + typeInfo.decls unlink implSym + NoSymbol + } + } } - ) + } + val impl = impl0 orElse { val impl = iface.owner.newImplClass(implName, iface.pos, implFlags) if (iface.thisSym != iface) { @@ -345,6 +351,7 @@ abstract class AddInterfaces extends InfoTransform { self: Erasure => while (owner != sym && owner != impl) owner = owner.owner; if (owner == impl) This(impl) setPos tree.pos else tree + //TODO what about this commented out code? /* !!! case Super(qual, mix) => val mix1 = mix diff --git a/src/compiler/scala/tools/nsc/util/ClassFileLookup.scala b/src/compiler/scala/tools/nsc/util/ClassFileLookup.scala new file mode 100644 index 0000000000..4451651229 --- /dev/null +++ b/src/compiler/scala/tools/nsc/util/ClassFileLookup.scala @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2014 Contributor. All rights reserved. + */ +package scala.tools.nsc.util + +import scala.tools.nsc.io.AbstractFile +import java.net.URL + +/** + * Simple interface that allows us to abstract over how class file lookup is performed + * in different classpath representations. + */ +// TODO at the end, after the possible removal of the old classpath representation, this class shouldn't be generic +// T should be just changed to AbstractFile +trait ClassFileLookup[T] { + def findClassFile(name: String): Option[AbstractFile] + + /** + * It returns both classes from class file and source files (as our base ClassRepresentation). + * So note that it's not so strictly related to findClassFile. + */ + def findClass(name: String): Option[ClassRepresentation[T]] + + /** + * A sequence of URLs representing this classpath. + */ + def asURLs: Seq[URL] + + /** The whole classpath in the form of one String. + */ + def asClassPathString: String + + // for compatibility purposes + @deprecated("Use asClassPathString instead of this one", "2.11.5") + def asClasspathString: String = asClassPathString + + /** The whole sourcepath in the form of one String. + */ + def asSourcePathString: String +} + +/** + * Represents classes which can be loaded with a ClassfileLoader and/or SourcefileLoader. + */ +// TODO at the end, after the possible removal of the old classpath implementation, this class shouldn't be generic +// T should be just changed to AbstractFile +trait ClassRepresentation[T] { + def binary: Option[T] + def source: Option[AbstractFile] + + def name: String +} + +object ClassRepresentation { + def unapply[T](classRep: ClassRepresentation[T]): Option[(Option[T], Option[AbstractFile])] = + Some((classRep.binary, classRep.source)) +} diff --git a/src/compiler/scala/tools/nsc/util/ClassPath.scala b/src/compiler/scala/tools/nsc/util/ClassPath.scala index e78dee5eee..8d4d07759f 100644 --- a/src/compiler/scala/tools/nsc/util/ClassPath.scala +++ b/src/compiler/scala/tools/nsc/util/ClassPath.scala @@ -7,16 +7,18 @@ package scala.tools.nsc package util +import io.{ AbstractFile, Directory, File, Jar } +import java.net.MalformedURLException import java.net.URL +import java.util.regex.PatternSyntaxException import scala.collection.{ mutable, immutable } -import io.{ File, Directory, Path, Jar, AbstractFile } import scala.reflect.internal.util.StringOps.splitWhere -import Jar.isJarOrZip +import scala.tools.nsc.classpath.FileUtils + import File.pathSeparator -import scala.collection.convert.WrapAsScala.enumerationAsScalaIterator -import java.net.MalformedURLException -import java.util.regex.PatternSyntaxException -import scala.reflect.runtime.ReflectionUtils +import FileUtils.endsClass +import FileUtils.endsScalaOrJava +import Jar.isJarOrZip /** <p> * This module provides star expansion of '-classpath' option arguments, behaves the same as @@ -89,7 +91,7 @@ object ClassPath { /** A class modeling aspects of a ClassPath which should be * propagated to any classpaths it creates. */ - abstract class ClassPathContext[T] { + abstract class ClassPathContext[T] extends classpath.ClassPathFactory[ClassPath[T]] { /** A filter which can be used to exclude entities from the classpath * based on their name. */ @@ -99,75 +101,47 @@ object ClassPath { */ def validClassFile(name: String) = endsClass(name) && isValidName(name) def validPackage(name: String) = (name != "META-INF") && (name != "") && (name.charAt(0) != '.') - def validSourceFile(name: String) = endsScala(name) || endsJava(name) + def validSourceFile(name: String) = endsScalaOrJava(name) /** From the representation to its identifier. */ def toBinaryName(rep: T): String - /** Create a new classpath based on the abstract file. - */ - def newClassPath(file: AbstractFile): ClassPath[T] - - /** Creators for sub classpaths which preserve this context. - */ def sourcesInPath(path: String): List[ClassPath[T]] = for (file <- expandPath(path, expandStar = false) ; dir <- Option(AbstractFile getDirectory file)) yield new SourcePath[T](dir, this) - - def contentsOfDirsInPath(path: String): List[ClassPath[T]] = - for (dir <- expandPath(path, expandStar = false) ; name <- expandDir(dir) ; entry <- Option(AbstractFile getDirectory name)) yield - newClassPath(entry) - - def classesInExpandedPath(path: String): IndexedSeq[ClassPath[T]] = - classesInPathImpl(path, expand = true).toIndexedSeq - - def classesInPath(path: String) = classesInPathImpl(path, expand = false) - - // Internal - private def classesInPathImpl(path: String, expand: Boolean) = - for (file <- expandPath(path, expand) ; dir <- Option(AbstractFile getDirectory file)) yield - newClassPath(dir) - - def classesInManifest(used: Boolean) = - if (used) for (url <- manifests) yield newClassPath(AbstractFile getResources url) else Nil } - def manifests = Thread.currentThread().getContextClassLoader().getResources("META-INF/MANIFEST.MF").filter(_.getProtocol() == "jar").toList + def manifests: List[java.net.URL] = { + import scala.collection.convert.WrapAsScala.enumerationAsScalaIterator + Thread.currentThread().getContextClassLoader() + .getResources("META-INF/MANIFEST.MF") + .filter(_.getProtocol == "jar").toList + } class JavaContext extends ClassPathContext[AbstractFile] { def toBinaryName(rep: AbstractFile) = { val name = rep.name assert(endsClass(name), name) - name.substring(0, name.length - 6) + FileUtils.stripClassExtension(name) } + def newClassPath(dir: AbstractFile) = new DirectoryClassPath(dir, this) } object DefaultJavaContext extends JavaContext - private def endsClass(s: String) = s.length > 6 && s.substring(s.length - 6) == ".class" - private def endsScala(s: String) = s.length > 6 && s.substring(s.length - 6) == ".scala" - private def endsJava(s: String) = s.length > 5 && s.substring(s.length - 5) == ".java" - /** From the source file to its identifier. */ - def toSourceName(f: AbstractFile): String = { - val name = f.name - - if (endsScala(name)) name.substring(0, name.length - 6) - else if (endsJava(name)) name.substring(0, name.length - 5) - else throw new FatalError("Unexpected source file ending: " + name) - } + def toSourceName(f: AbstractFile): String = FileUtils.stripSourceExtension(f.name) } + import ClassPath._ /** * Represents a package which contains classes and other packages */ -abstract class ClassPath[T] { - type AnyClassRep = ClassPath[T]#ClassRep - +abstract class ClassPath[T] extends ClassFileLookup[T] { /** * The short name of the package (without prefix) */ @@ -179,21 +153,13 @@ abstract class ClassPath[T] { */ def origin: Option[String] = None - /** A list of URLs representing this classpath. - */ - def asURLs: List[URL] - - /** The whole classpath in the form of one String. - */ - def asClasspathString: String - /** Info which should be propagated to any sub-classpaths. */ def context: ClassPathContext[T] /** Lists of entities. */ - def classes: IndexedSeq[AnyClassRep] + def classes: IndexedSeq[ClassRepresentation[T]] def packages: IndexedSeq[ClassPath[T]] def sourcepaths: IndexedSeq[AbstractFile] @@ -217,7 +183,7 @@ abstract class ClassPath[T] { /** * Represents classes which can be loaded with a ClassfileLoader and/or SourcefileLoader. */ - case class ClassRep(binary: Option[T], source: Option[AbstractFile]) { + case class ClassRep(binary: Option[T], source: Option[AbstractFile]) extends ClassRepresentation[T] { def name: String = binary match { case Some(x) => context.toBinaryName(x) case _ => @@ -236,25 +202,27 @@ abstract class ClassPath[T] { * Find a ClassRep given a class name of the form "package.subpackage.ClassName". * Does not support nested classes on .NET */ - def findClass(name: String): Option[AnyClassRep] = + override def findClass(name: String): Option[ClassRepresentation[T]] = splitWhere(name, _ == '.', doDropIndex = true) match { case Some((pkg, rest)) => val rep = packages find (_.name == pkg) flatMap (_ findClass rest) rep map { - case x: ClassRep => x + case x: ClassRepresentation[T] => x case x => throw new FatalError("Unexpected ClassRep '%s' found searching for name '%s'".format(x, name)) } case _ => classes find (_.name == name) } - def findClassFile(name: String): Option[AbstractFile] = + override def findClassFile(name: String): Option[AbstractFile] = findClass(name) match { - case Some(ClassRep(Some(x: AbstractFile), _)) => Some(x) + case Some(ClassRepresentation(Some(x: AbstractFile), _)) => Some(x) case _ => None } - def sortString = join(split(asClasspathString).sorted: _*) + override def asSourcePathString: String = sourcepaths.mkString(pathSeparator) + + def sortString = join(split(asClassPathString).sorted: _*) override def equals(that: Any) = that match { case x: ClassPath[_] => this.sortString == x.sortString case _ => false @@ -266,10 +234,12 @@ abstract class ClassPath[T] { * A Classpath containing source files */ class SourcePath[T](dir: AbstractFile, val context: ClassPathContext[T]) extends ClassPath[T] { + import FileUtils.AbstractFileOps + def name = dir.name override def origin = dir.underlyingSource map (_.path) - def asURLs = if (dir.file == null) Nil else List(dir.toURL) - def asClasspathString = dir.path + def asURLs = dir.toURLs() + def asClassPathString = dir.path val sourcepaths: IndexedSeq[AbstractFile] = IndexedSeq(dir) private def traverse() = { @@ -292,10 +262,12 @@ class SourcePath[T](dir: AbstractFile, val context: ClassPathContext[T]) extends * A directory (or a .jar file) containing classfiles and packages */ class DirectoryClassPath(val dir: AbstractFile, val context: ClassPathContext[AbstractFile]) extends ClassPath[AbstractFile] { + import FileUtils.AbstractFileOps + def name = dir.name override def origin = dir.underlyingSource map (_.path) - def asURLs = if (dir.file == null) List(new URL(name)) else List(dir.toURL) - def asClasspathString = dir.path + def asURLs = dir.toURLs(default = Seq(new URL(name))) + def asClassPathString = dir.path val sourcepaths: IndexedSeq[AbstractFile] = IndexedSeq() // calculates (packages, classes) in one traversal. @@ -342,6 +314,7 @@ class MergedClassPath[T]( override val entries: IndexedSeq[ClassPath[T]], val context: ClassPathContext[T]) extends ClassPath[T] { + def this(entries: TraversableOnce[ClassPath[T]], context: ClassPathContext[T]) = this(entries.toIndexedSeq, context) @@ -350,12 +323,12 @@ extends ClassPath[T] { lazy val sourcepaths: IndexedSeq[AbstractFile] = entries flatMap (_.sourcepaths) override def origin = Some(entries map (x => x.origin getOrElse x.name) mkString ("Merged(", ", ", ")")) - override def asClasspathString: String = join(entries map (_.asClasspathString) : _*) + override def asClassPathString: String = join(entries map (_.asClassPathString) : _*) - lazy val classes: IndexedSeq[AnyClassRep] = { + lazy val classes: IndexedSeq[ClassRepresentation[T]] = { var count = 0 val indices = mutable.HashMap[String, Int]() - val cls = new mutable.ArrayBuffer[AnyClassRep](1024) + val cls = new mutable.ArrayBuffer[ClassRepresentation[T]](1024) for (e <- entries; c <- e.classes) { val name = c.name @@ -364,9 +337,9 @@ extends ClassPath[T] { val existing = cls(idx) if (existing.binary.isEmpty && c.binary.isDefined) - cls(idx) = existing.copy(binary = c.binary) + cls(idx) = ClassRep(binary = c.binary, source = existing.source) if (existing.source.isEmpty && c.source.isDefined) - cls(idx) = existing.copy(source = c.source) + cls(idx) = ClassRep(binary = existing.binary, source = c.source) } else { indices(name) = count @@ -404,10 +377,12 @@ extends ClassPath[T] { } new MergedClassPath[T](newEntries, context) } + def show() { println("ClassPath %s has %d entries and results in:\n".format(name, entries.size)) - asClasspathString split ':' foreach (x => println(" " + x)) + asClassPathString split ':' foreach (x => println(" " + x)) } + override def toString() = "merged classpath "+ entries.mkString("(", "\n", ")") } diff --git a/src/compiler/scala/tools/reflect/ReflectMain.scala b/src/compiler/scala/tools/reflect/ReflectMain.scala index 3ae21b6b98..8d8418945a 100644 --- a/src/compiler/scala/tools/reflect/ReflectMain.scala +++ b/src/compiler/scala/tools/reflect/ReflectMain.scala @@ -1,17 +1,17 @@ package scala.tools package reflect +import scala.reflect.internal.util.ScalaClassLoader import scala.tools.nsc.Driver import scala.tools.nsc.Global import scala.tools.nsc.Settings -import scala.tools.nsc.util.ScalaClassLoader -import scala.tools.util.PathResolver +import scala.tools.util.PathResolverFactory object ReflectMain extends Driver { private def classloaderFromSettings(settings: Settings) = { - val classpath = new PathResolver(settings).result - ScalaClassLoader.fromURLs(classpath.asURLs, getClass.getClassLoader) + val classPathURLs = PathResolverFactory.create(settings).resultAsURLs + ScalaClassLoader.fromURLs(classPathURLs, getClass.getClassLoader) } override def newCompiler(): Global = new ReflectGlobal(settings, reporter, classloaderFromSettings(settings)) diff --git a/src/compiler/scala/tools/util/PathResolver.scala b/src/compiler/scala/tools/util/PathResolver.scala index 5526660509..8e5b1e0a5c 100644 --- a/src/compiler/scala/tools/util/PathResolver.scala +++ b/src/compiler/scala/tools/util/PathResolver.scala @@ -7,14 +7,17 @@ package scala package tools package util +import java.net.URL import scala.tools.reflect.WrappedProperties.AccessControl -import scala.tools.nsc.{ Settings } -import scala.tools.nsc.util.{ ClassPath, JavaClassPath } +import scala.tools.nsc.Settings +import scala.tools.nsc.util.{ ClassFileLookup, ClassPath, JavaClassPath } import scala.reflect.io.{ File, Directory, Path, AbstractFile } import scala.reflect.runtime.ReflectionUtils import ClassPath.{ JavaContext, DefaultJavaContext, join, split } import PartialFunction.condOpt import scala.language.postfixOps +import scala.tools.nsc.classpath.{ AggregateFlatClassPath, ClassPathFactory, FlatClassPath, FlatClassPathFactory } +import scala.tools.nsc.settings.ClassPathRepresentationType // Loosely based on the draft specification at: // https://wiki.scala-lang.org/display/SIW/Classpath @@ -48,9 +51,8 @@ object PathResolver { /** Values found solely by inspecting environment or property variables. */ object Environment { - private def searchForBootClasspath = ( + private def searchForBootClasspath = systemProperties find (_._1 endsWith ".boot.class.path") map (_._2) getOrElse "" - ) /** Environment variables which java pays attention to so it * seems we do as well. @@ -104,7 +106,7 @@ object PathResolver { else if (scalaLibAsDir.isDirectory) scalaLibAsDir.path else "" - // XXX It must be time for someone to figure out what all these things + // TODO It must be time for someone to figure out what all these things // are intended to do. This is disabled here because it was causing all // the scala jars to end up on the classpath twice: one on the boot // classpath as set up by the runner (or regular classpath under -nobootcp) @@ -170,39 +172,48 @@ object PathResolver { !ReflectionUtils.scalacShouldntLoadClassfile(name) } - // called from scalap + @deprecated("This method is no longer used be scalap and will be deleted", "2.11.5") def fromPathString(path: String, context: JavaContext = DefaultJavaContext): JavaClassPath = { val s = new Settings() s.classpath.value = path - new PathResolver(s, context) result + new PathResolver(s, context).result } /** With no arguments, show the interesting values in Environment and Defaults. * If there are arguments, show those in Calculated as if those options had been * given to a scala runner. */ - def main(args: Array[String]): Unit = { + def main(args: Array[String]): Unit = if (args.isEmpty) { println(Environment) println(Defaults) - } - else { + } else { val settings = new Settings() val rest = settings.processArguments(args.toList, processAll = false)._2 - val pr = new PathResolver(settings) - println(" COMMAND: 'scala %s'".format(args.mkString(" "))) + val pr = PathResolverFactory.create(settings) + println("COMMAND: 'scala %s'".format(args.mkString(" "))) println("RESIDUAL: 'scala %s'\n".format(rest.mkString(" "))) - pr.result.show() + + pr.result match { + case cp: JavaClassPath => + cp.show() + case cp: AggregateFlatClassPath => + println(s"ClassPath has ${cp.aggregates.size} entries and results in:\n${cp.asClassPathStrings}") + } } - } } -class PathResolver(settings: Settings, context: JavaContext) { - import PathResolver.{ Defaults, Environment, AsLines, MkLines, ppcp } +trait PathResolverResult { + def result: ClassFileLookup[AbstractFile] - def this(settings: Settings) = this(settings, - if (settings.YnoLoadImplClass) PathResolver.NoImplClassJavaContext - else DefaultJavaContext) + def resultAsURLs: Seq[URL] = result.asURLs +} + +abstract class PathResolverBase[BaseClassPathType <: ClassFileLookup[AbstractFile], ResultClassPathType <: BaseClassPathType] +(settings: Settings, classPathFactory: ClassPathFactory[BaseClassPathType]) + extends PathResolverResult { + + import PathResolver.{ AsLines, Defaults, ppcp } private def cmdLineOrElse(name: String, alt: String) = { (commandLineFor(name) match { @@ -232,6 +243,7 @@ class PathResolver(settings: Settings, context: JavaContext) { def javaUserClassPath = if (useJavaClassPath) Defaults.javaUserClassPath else "" def scalaBootClassPath = cmdLineOrElse("bootclasspath", Defaults.scalaBootClassPath) def scalaExtDirs = cmdLineOrElse("extdirs", Defaults.scalaExtDirs) + /** Scaladoc doesn't need any bootstrapping, otherwise will create errors such as: * [scaladoc] ../scala-trunk/src/reflect/scala/reflect/macros/Reifiers.scala:89: error: object api is not a member of package reflect * [scaladoc] case class ReificationException(val pos: reflect.api.PositionApi, val msg: String) extends Throwable(msg) @@ -250,16 +262,14 @@ class PathResolver(settings: Settings, context: JavaContext) { * - Otherwise, if CLASSPATH is set, it is that * - If neither of those, then "." is used. */ - def userClassPath = ( - if (!settings.classpath.isDefault) - settings.classpath.value + def userClassPath = + if (!settings.classpath.isDefault) settings.classpath.value else sys.env.getOrElse("CLASSPATH", ".") - ) - import context._ + import classPathFactory._ // Assemble the elements! - def basis = List[Traversable[ClassPath[AbstractFile]]]( + def basis = List[Traversable[BaseClassPathType]]( classesInPath(javaBootClassPath), // 1. The Java bootstrap class path. contentsOfDirsInPath(javaExtDirs), // 2. The Java extension class path. classesInExpandedPath(javaUserClassPath), // 3. The Java application class path. @@ -278,7 +288,7 @@ class PathResolver(settings: Settings, context: JavaContext) { | javaBootClassPath = ${ppcp(javaBootClassPath)} | javaExtDirs = ${ppcp(javaExtDirs)} | javaUserClassPath = ${ppcp(javaUserClassPath)} - | useJavaClassPath = $useJavaClassPath + | useJavaClassPath = $useJavaClassPath | scalaBootClassPath = ${ppcp(scalaBootClassPath)} | scalaExtDirs = ${ppcp(scalaExtDirs)} | userClassPath = ${ppcp(userClassPath)} @@ -288,8 +298,10 @@ class PathResolver(settings: Settings, context: JavaContext) { def containers = Calculated.containers - lazy val result = { - val cp = new JavaClassPath(containers.toIndexedSeq, context) + import PathResolver.MkLines + + def result: ResultClassPathType = { + val cp = computeResult() if (settings.Ylogcp) { Console print f"Classpath built from ${settings.toConciseString} %n" Console print s"Defaults: ${PathResolver.Defaults}" @@ -301,5 +313,37 @@ class PathResolver(settings: Settings, context: JavaContext) { cp } - def asURLs = result.asURLs + @deprecated("Use resultAsURLs instead of this one", "2.11.5") + def asURLs: List[URL] = resultAsURLs.toList + + protected def computeResult(): ResultClassPathType +} + +class PathResolver(settings: Settings, context: JavaContext) + extends PathResolverBase[ClassPath[AbstractFile], JavaClassPath](settings, context) { + + def this(settings: Settings) = + this(settings, + if (settings.YnoLoadImplClass) PathResolver.NoImplClassJavaContext + else DefaultJavaContext) + + override protected def computeResult(): JavaClassPath = + new JavaClassPath(containers.toIndexedSeq, context) +} + +class FlatClassPathResolver(settings: Settings, flatClassPathFactory: ClassPathFactory[FlatClassPath]) + extends PathResolverBase[FlatClassPath, AggregateFlatClassPath](settings, flatClassPathFactory) { + + def this(settings: Settings) = this(settings, new FlatClassPathFactory(settings)) + + override protected def computeResult(): AggregateFlatClassPath = AggregateFlatClassPath(containers.toIndexedSeq) +} + +object PathResolverFactory { + + def create(settings: Settings): PathResolverResult = + settings.YclasspathImpl.value match { + case ClassPathRepresentationType.Flat => new FlatClassPathResolver(settings) + case ClassPathRepresentationType.Recursive => new PathResolver(settings) + } } diff --git a/src/interactive/scala/tools/nsc/interactive/Global.scala b/src/interactive/scala/tools/nsc/interactive/Global.scala index 7df809b6ff..5d00141e6a 100644 --- a/src/interactive/scala/tools/nsc/interactive/Global.scala +++ b/src/interactive/scala/tools/nsc/interactive/Global.scala @@ -128,8 +128,8 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") else NullLogger import log.logreplay - debugLog("logger: " + log.getClass + " writing to " + (new java.io.File(logName)).getAbsolutePath) - debugLog("classpath: "+classPath) + debugLog(s"logger: ${log.getClass} writing to ${(new java.io.File(logName)).getAbsolutePath}") + debugLog(s"classpath: $classPath") private var curTime = System.nanoTime private def timeStep = { diff --git a/src/partest-extras/scala/tools/partest/BytecodeTest.scala b/src/partest-extras/scala/tools/partest/BytecodeTest.scala index 3261cada37..37ef4684ef 100644 --- a/src/partest-extras/scala/tools/partest/BytecodeTest.scala +++ b/src/partest-extras/scala/tools/partest/BytecodeTest.scala @@ -116,10 +116,8 @@ abstract class BytecodeTest { sys.error(s"Didn't find method '$name' in class '${classNode.name}'") protected def loadClassNode(name: String, skipDebugInfo: Boolean = true): ClassNode = { - val classBytes: InputStream = (for { - classRep <- classpath.findClass(name) - binary <- classRep.binary - } yield binary.input) getOrElse sys.error(s"failed to load class '$name'; classpath = $classpath") + val classBytes: InputStream = classpath.findClassFile(name).map(_.input) + .getOrElse(sys.error(s"failed to load class '$name'; classpath = $classpath")) val cr = new ClassReader(classBytes) val cn = new ClassNode() diff --git a/src/reflect/scala/reflect/internal/SymbolTable.scala b/src/reflect/scala/reflect/internal/SymbolTable.scala index ed5c68fe82..01e4cdf367 100644 --- a/src/reflect/scala/reflect/internal/SymbolTable.scala +++ b/src/reflect/scala/reflect/internal/SymbolTable.scala @@ -338,7 +338,6 @@ abstract class SymbolTable extends macros.Universe case _ => false } if (pkgModule.isModule && !fromSource) { - // println("open "+pkgModule)//DEBUG openPackageModule(pkgModule, pkgClass) } } diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index c665f2b91a..8e86e6fad9 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -181,7 +181,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => type AccessBoundaryType = Symbol type AnnotationType = AnnotationInfo - // TODO - don't allow names to be renamed in this unstructured a fashion. + // TODO - don't allow names to be renamed in this unstructured fashion. // Rename as little as possible. Enforce invariants on all renames. type TypeOfClonedSymbol >: Null <: Symbol { type NameType = Symbol.this.NameType } @@ -1461,11 +1461,9 @@ trait Symbols extends api.Symbols { self: SymbolTable => def info: Type = try { var cnt = 0 while (validTo == NoPeriod) { - //if (settings.debug.value) System.out.println("completing " + this);//DEBUG assert(infos ne null, this.name) assert(infos.prev eq null, this.name) val tp = infos.info - //if (settings.debug.value) System.out.println("completing " + this.rawname + tp.getClass());//debug if ((_rawflags & LOCKED) != 0L) { // rolled out once for performance lock { @@ -1474,6 +1472,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => } } else { _rawflags |= LOCKED + // TODO another commented out lines - this should be solved in one way or another // activeLocks += 1 // lockedSyms += this } diff --git a/src/reflect/scala/reflect/internal/settings/MutableSettings.scala b/src/reflect/scala/reflect/internal/settings/MutableSettings.scala index a494c7f0d0..38893d8db3 100644 --- a/src/reflect/scala/reflect/internal/settings/MutableSettings.scala +++ b/src/reflect/scala/reflect/internal/settings/MutableSettings.scala @@ -31,6 +31,9 @@ abstract class MutableSettings extends AbsSettings { v = arg postSetHook() } + + /** Returns Some(value) in the case of a value set by user and None otherwise. */ + def valueSetByUser: Option[T] = if (isSetByUser) Some(value) else None } def Xexperimental: BooleanSetting diff --git a/src/reflect/scala/reflect/io/VirtualFile.scala b/src/reflect/scala/reflect/io/VirtualFile.scala index 45f38db745..1cb4f2fe6f 100644 --- a/src/reflect/scala/reflect/io/VirtualFile.scala +++ b/src/reflect/scala/reflect/io/VirtualFile.scala @@ -75,10 +75,10 @@ class VirtualFile(val name: String, override val path: String) extends AbstractF } /** Does this abstract file denote an existing file? */ - def create() { unsupported() } + def create(): Unit = unsupported() /** Delete the underlying file or directory (recursively). */ - def delete() { unsupported() } + def delete(): Unit = unsupported() /** * Returns the abstract file in this abstract directory with the diff --git a/src/reflect/scala/reflect/io/ZipArchive.scala b/src/reflect/scala/reflect/io/ZipArchive.scala index 8260189459..0c63acb86c 100644 --- a/src/reflect/scala/reflect/io/ZipArchive.scala +++ b/src/reflect/scala/reflect/io/ZipArchive.scala @@ -74,12 +74,6 @@ abstract class ZipArchive(override val file: JFile) extends AbstractFile with Eq def container = unsupported() def absolute = unsupported() - private def walkIterator(its: Iterator[AbstractFile]): Iterator[AbstractFile] = { - its flatMap { f => - if (f.isDirectory) walkIterator(f.iterator) - else Iterator(f) - } - } /** ''Note: This library is considered experimental and should not be used unless you know what you are doing.'' */ sealed abstract class Entry(path: String) extends VirtualFile(baseName(path), path) { // have to keep this name for compat with sbt's compiler-interface @@ -87,6 +81,7 @@ abstract class ZipArchive(override val file: JFile) extends AbstractFile with Eq override def underlyingSource = Some(self) override def toString = self.path + "(" + path + ")" } + /** ''Note: This library is considered experimental and should not be used unless you know what you are doing.'' */ class DirEntry(path: String) extends Entry(path) { val entries = mutable.HashMap[String, Entry]() @@ -125,14 +120,15 @@ abstract class ZipArchive(override val file: JFile) extends AbstractFile with Eq } /** ''Note: This library is considered experimental and should not be used unless you know what you are doing.'' */ final class FileZipArchive(file: JFile) extends ZipArchive(file) { - def iterator: Iterator[Entry] = { + lazy val (root, allDirs) = { + val root = new DirEntry("/") + val dirs = mutable.HashMap[String, DirEntry]("/" -> root) val zipFile = try { new ZipFile(file) } catch { case ioe: IOException => throw new IOException("Error accessing " + file.getPath, ioe) } - val root = new DirEntry("/") - val dirs = mutable.HashMap[String, DirEntry]("/" -> root) + val enum = zipFile.entries() while (enum.hasMoreElements) { @@ -150,11 +146,11 @@ final class FileZipArchive(file: JFile) extends ZipArchive(file) { dir.entries(f.name) = f } } - - try root.iterator - finally dirs.clear() + (root, dirs) } + def iterator: Iterator[Entry] = root.iterator + def name = file.getName def path = file.getPath def input = File(file).inputStream() @@ -244,11 +240,9 @@ final class ManifestResources(val url: URL) extends ZipArchive(null) { val manifest = new Manifest(input) val iter = manifest.getEntries().keySet().iterator().filter(_.endsWith(".class")).map(new ZipEntry(_)) - while (iter.hasNext) { - val zipEntry = iter.next() + for (zipEntry <- iter) { val dir = getDir(dirs, zipEntry) - if (zipEntry.isDirectory) dir - else { + if (!zipEntry.isDirectory) { class FileEntry() extends Entry(zipEntry.getName) { override def lastModified = zipEntry.getTime() override def input = resourceInputStream(path) @@ -284,14 +278,14 @@ final class ManifestResources(val url: URL) extends ZipArchive(null) { private def resourceInputStream(path: String): InputStream = { new FilterInputStream(null) { override def read(): Int = { - if(in == null) in = Thread.currentThread().getContextClassLoader().getResourceAsStream(path); + if(in == null) in = Thread.currentThread().getContextClassLoader().getResourceAsStream(path) if(in == null) throw new RuntimeException(path + " not found") - super.read(); + super.read() } override def close(): Unit = { - super.close(); - in = null; + super.close() + in = null } } } diff --git a/src/repl/scala/tools/nsc/interpreter/ILoop.scala b/src/repl/scala/tools/nsc/interpreter/ILoop.scala index ee1dea4dc1..4fd5768b79 100644 --- a/src/repl/scala/tools/nsc/interpreter/ILoop.scala +++ b/src/repl/scala/tools/nsc/interpreter/ILoop.scala @@ -618,8 +618,8 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) if (f.exists) { addedClasspath = ClassPath.join(addedClasspath, f.path) intp.addUrlsToClassPath(f.toURI.toURL) - echo("Added '%s' to classpath.".format(f.path, intp.global.classPath.asClasspathString)) - repldbg("Added '%s'. Your new classpath is:\n\"%s\"".format(f.path, intp.global.classPath.asClasspathString)) + echo("Added '%s' to classpath.".format(f.path, intp.global.classPath.asClassPathString)) + repldbg("Added '%s'. Your new classpath is:\n\"%s\"".format(f.path, intp.global.classPath.asClassPathString)) } else echo("The path '" + f + "' doesn't seem to exist.") } @@ -663,8 +663,8 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) else { addedClasspath = ClassPath.join(addedClasspath, f.path) intp.addUrlsToClassPath(f.toURI.toURL) - echo("Added '%s' to classpath.".format(f.path, intp.global.classPath.asClasspathString)) - repldbg("Added '%s'. Your new classpath is:\n\"%s\"".format(f.path, intp.global.classPath.asClasspathString)) + echo("Added '%s' to classpath.".format(f.path, intp.global.classPath.asClassPathString)) + repldbg("Added '%s'. Your new classpath is:\n\"%s\"".format(f.path, intp.global.classPath.asClassPathString)) } } diff --git a/src/repl/scala/tools/nsc/interpreter/IMain.scala b/src/repl/scala/tools/nsc/interpreter/IMain.scala index b990e401ec..f9f7388363 100644 --- a/src/repl/scala/tools/nsc/interpreter/IMain.scala +++ b/src/repl/scala/tools/nsc/interpreter/IMain.scala @@ -15,7 +15,7 @@ import scala.concurrent.{ Future, ExecutionContext } import scala.reflect.runtime.{ universe => ru } import scala.reflect.{ ClassTag, classTag } import scala.reflect.internal.util.{ BatchSourceFile, SourceFile } -import scala.tools.util.PathResolver +import scala.tools.util.PathResolverFactory import scala.tools.nsc.io.AbstractFile import scala.tools.nsc.typechecker.{ TypeStrings, StructuredTypeStrings } import scala.tools.nsc.util.{ ScalaClassLoader, stringFromReader, stringFromWriter, StackTraceOps, ClassPath, MergedClassPath } @@ -90,7 +90,7 @@ class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Set def compilerClasspath: Seq[java.net.URL] = ( if (isInitializeComplete) global.classPath.asURLs - else new PathResolver(settings).result.asURLs // the compiler's classpath + else PathResolverFactory.create(settings).resultAsURLs // the compiler's classpath ) def settings = initialSettings // Run the code body with the given boolean settings flipped to true. @@ -1282,9 +1282,11 @@ object IMain { def getProgram(statements: String*): String = null - def getScriptEngine: ScriptEngine = new IMain(this, new Settings() { - usemanifestcp.value = true - }) + def getScriptEngine: ScriptEngine = { + val settings = new Settings() + settings.usemanifestcp.value = true + new IMain(this, settings) + } } // The two name forms this is catching are the two sides of this assignment: diff --git a/src/scalap/scala/tools/scalap/Arguments.scala b/src/scalap/scala/tools/scalap/Arguments.scala index c375a5bac4..de9c30b8af 100644 --- a/src/scalap/scala/tools/scalap/Arguments.scala +++ b/src/scalap/scala/tools/scalap/Arguments.scala @@ -9,7 +9,7 @@ package scala.tools.scalap import scala.collection.mutable -import mutable.{ Buffer, ListBuffer } +import mutable.ListBuffer object Arguments { case class Parser(optionPrefix: Char) { @@ -47,7 +47,7 @@ object Arguments { } def parseBinding(str: String, separator: Char): (String, String) = (str indexOf separator) match { - case -1 => argumentError("missing '" + separator + "' in binding '" + str + "'") ; ("", "") + case -1 => argumentError(s"missing '$separator' in binding '$str'") ; ("", "") case idx => ((str take idx).trim, (str drop (idx + 1)).trim) } @@ -71,7 +71,7 @@ object Arguments { i += 1 } else if (optionalArgs contains args(i)) { if ((i + 1) == args.length) { - argumentError("missing argument for '" + args(i) + "'") + argumentError(s"missing argument for '${args(i)}'") i += 1 } else { res.addArgument(args(i), args(i + 1)) @@ -79,11 +79,11 @@ object Arguments { } } else if (optionalBindings contains args(i)) { if ((i + 1) == args.length) { - argumentError("missing argument for '" + args(i) + "'") + argumentError(s"missing argument for '${args(i)}'") i += 1 } else { res.addBinding(args(i), - parseBinding(args(i + 1), optionalBindings(args(i)))); + parseBinding(args(i + 1), optionalBindings(args(i)))) i += 2 } } else { @@ -92,23 +92,23 @@ object Arguments { while ((i == j) && iter.hasNext) { val prefix = iter.next if (args(i) startsWith prefix) { - res.addPrefixed(prefix, args(i).substring(prefix.length()).trim()); + res.addPrefixed(prefix, args(i).substring(prefix.length()).trim()) i += 1 } } if (i == j) { - val iter = prefixedBindings.keysIterator; + val iter = prefixedBindings.keysIterator while ((i == j) && iter.hasNext) { val prefix = iter.next if (args(i) startsWith prefix) { val arg = args(i).substring(prefix.length()).trim() i = i + 1 res.addBinding(prefix, - parseBinding(arg, prefixedBindings(prefix))); + parseBinding(arg, prefixedBindings(prefix))) } } if (i == j) { - argumentError("unknown option '" + args(i) + "'") + argumentError(s"unknown option '${args(i)}'") i = i + 1 } } @@ -119,7 +119,7 @@ object Arguments { def parse(options: String*)(args: Array[String]): Arguments = { val parser = new Parser('-') - options foreach (parser withOption _) + options foreach parser.withOption parser parse args } } @@ -142,7 +142,7 @@ class Arguments { if (key.length > 0) bindings.getOrElseUpdate(tag, new mutable.HashMap)(key) = value - def addBinding(tag: String, binding: Tuple2[String, String]): Unit = + def addBinding(tag: String, binding: (String, String)): Unit = addBinding(tag, binding._1, binding._2) def addOther(arg: String): Unit = others += arg diff --git a/src/scalap/scala/tools/scalap/Main.scala b/src/scalap/scala/tools/scalap/Main.scala index c72f416a89..7c554d196c 100644 --- a/src/scalap/scala/tools/scalap/Main.scala +++ b/src/scalap/scala/tools/scalap/Main.scala @@ -10,11 +10,16 @@ package tools.scalap import java.io.{ PrintStream, OutputStreamWriter, ByteArrayOutputStream } import scala.reflect.NameTransformer -import scalax.rules.scalasig._ -import scala.tools.nsc.util.{ ClassPath, JavaClassPath } -import scala.tools.util.PathResolver -import ClassPath.DefaultJavaContext +import scala.tools.nsc.Settings +import scala.tools.nsc.classpath.AggregateFlatClassPath +import scala.tools.nsc.classpath.FlatClassPathFactory import scala.tools.nsc.io.AbstractFile +import scala.tools.nsc.settings.ClassPathRepresentationType +import scala.tools.nsc.util.ClassFileLookup +import scala.tools.nsc.util.ClassPath.DefaultJavaContext +import scala.tools.nsc.util.JavaClassPath +import scala.tools.util.PathResolverFactory +import scalax.rules.scalasig._ /**The main object used to execute scalap on the command-line. * @@ -42,12 +47,12 @@ class Main { * * @param clazz the class file to be processed. */ - def processJavaClassFile(clazz: Classfile) { + def processJavaClassFile(clazz: Classfile): Unit = { // construct a new output stream writer val out = new OutputStreamWriter(Console.out) val writer = new JavaWriter(clazz, out) // print the class - writer.printClass + writer.printClass() out.flush() } @@ -60,21 +65,20 @@ class Main { syms.head.parent match { // Partial match - case Some(p) if (p.name != "<empty>") => { + case Some(p) if p.name != "<empty>" => val path = p.path if (!isPackageObject) { - stream.print("package "); - stream.print(path); + stream.print("package ") + stream.print(path) stream.print("\n") } else { val i = path.lastIndexOf(".") if (i > 0) { - stream.print("package "); + stream.print("package ") stream.print(path.substring(0, i)) stream.print("\n") } } - } case _ => } // Print classes @@ -96,7 +100,7 @@ class Main { /** Executes scalap with the given arguments and classpath for the * class denoted by `classname`. */ - def process(args: Arguments, path: ClassPath[AbstractFile])(classname: String): Unit = { + def process(args: Arguments, path: ClassFileLookup[AbstractFile])(classname: String): Unit = { // find the classfile val encName = classname match { case "scala.AnyRef" => "java.lang.Object" @@ -106,92 +110,115 @@ class Main { // we can afford allocations because this is not a performance critical code classname.split('.').map(NameTransformer.encode).mkString(".") } - val cls = path.findClass(encName) - if (cls.isDefined && cls.get.binary.isDefined) { - val cfile = cls.get.binary.get - if (verbose) { - Console.println(Console.BOLD + "FILENAME" + Console.RESET + " = " + cfile.path) - } - val bytes = cfile.toByteArray - if (isScalaFile(bytes)) { - Console.println(decompileScala(bytes, isPackageObjectFile(encName))) - } else { - // construct a reader for the classfile content - val reader = new ByteArrayReader(cfile.toByteArray) - // parse the classfile - val clazz = new Classfile(reader) - processJavaClassFile(clazz) - } - // if the class corresponds to the artificial class scala.Any. - // (see member list in class scala.tool.nsc.symtab.Definitions) - } - else - Console.println("class/object " + classname + " not found.") - } - object EmptyClasspath extends ClassPath[AbstractFile] { - /** - * The short name of the package (without prefix) - */ - def name = "" - def asURLs = Nil - def asClasspathString = "" - - val context = DefaultJavaContext - val classes = IndexedSeq() - val packages = IndexedSeq() - val sourcepaths = IndexedSeq() + path.findClassFile(encName) match { + case Some(classFile) => + if (verbose) { + Console.println(Console.BOLD + "FILENAME" + Console.RESET + " = " + classFile.path) + } + val bytes = classFile.toByteArray + if (isScalaFile(bytes)) { + Console.println(decompileScala(bytes, isPackageObjectFile(encName))) + } else { + // construct a reader for the classfile content + val reader = new ByteArrayReader(classFile.toByteArray) + // parse the classfile + val clazz = new Classfile(reader) + processJavaClassFile(clazz) + } + // if the class corresponds to the artificial class scala.Any. + // (see member list in class scala.tool.nsc.symtab.Definitions) + case _ => + Console.println(s"class/object $classname not found.") + } } } object Main extends Main { + + private object opts { + val cp = "-cp" + val help = "-help" + val classpath = "-classpath" + val showPrivateDefs = "-private" + val verbose = "-verbose" + val version = "-version" + + val classPathImplType = "-YclasspathImpl" + val disableFlatClassPathCaching = "-YdisableFlatCpCaching" + val logClassPath = "-Ylog-classpath" + } + /** Prints usage information for scalap. */ - def usage() { - Console println """ + def usage(): Unit = { + Console println s""" |Usage: scalap {<option>} <name> |where <name> is fully-qualified class name or <package_name>.package for package objects |and <option> is - | -private print private definitions - | -verbose print out additional information - | -version print out the version number of scalap - | -help display this usage message - | -classpath <path> specify where to find user class files - | -cp <path> specify where to find user class files + | ${opts.showPrivateDefs} print private definitions + | ${opts.verbose} print out additional information + | ${opts.version} print out the version number of scalap + | ${opts.help} display this usage message + | ${opts.classpath} <path> specify where to find user class files + | ${opts.cp} <path> specify where to find user class files """.stripMargin.trim } - def main(args: Array[String]) { - // print usage information if there is no command-line argument - if (args.isEmpty) - return usage() - - val arguments = Arguments.Parser('-') - .withOption("-private") - .withOption("-verbose") - .withOption("-version") - .withOption("-help") - .withOptionalArg("-classpath") - .withOptionalArg("-cp") - .parse(args); - - if (arguments contains "-version") - Console.println(versionMsg) - if (arguments contains "-help") - usage() - - verbose = arguments contains "-verbose" - printPrivates = arguments contains "-private" - // construct a custom class path - val cparg = List("-classpath", "-cp") map (arguments getArgument _) reduceLeft (_ orElse _) - val path = cparg match { - case Some(cp) => new JavaClassPath(DefaultJavaContext.classesInExpandedPath(cp), DefaultJavaContext) - case _ => PathResolver.fromPathString(".") // include '.' in the default classpath SI-6669 + def main(args: Array[String]): Unit = + // print usage information if there is no command-line argument + if (args.isEmpty) usage() + else { + val arguments = parseArguments(args) + + if (arguments contains opts.version) + Console.println(versionMsg) + if (arguments contains opts.help) + usage() + + verbose = arguments contains opts.verbose + printPrivates = arguments contains opts.showPrivateDefs + // construct a custom class path + val cpArg = List(opts.classpath, opts.cp) map arguments.getArgument reduceLeft (_ orElse _) + + val settings = new Settings() + + arguments getArgument opts.classPathImplType foreach settings.YclasspathImpl.tryToSetFromPropertyValue + settings.YdisableFlatCpCaching.value = arguments contains opts.disableFlatClassPathCaching + settings.Ylogcp.value = arguments contains opts.logClassPath + + val path = createClassPath(cpArg, settings) + + // print the classpath if output is verbose + if (verbose) + Console.println(Console.BOLD + "CLASSPATH" + Console.RESET + " = " + path.asClassPathString) + + // process all given classes + arguments.getOthers foreach process(arguments, path) } - // print the classpath if output is verbose - if (verbose) - Console.println(Console.BOLD + "CLASSPATH" + Console.RESET + " = " + path) - // process all given classes - arguments.getOthers foreach process(arguments, path) + private def parseArguments(args: Array[String]) = + Arguments.Parser('-') + .withOption(opts.showPrivateDefs) + .withOption(opts.verbose) + .withOption(opts.version) + .withOption(opts.help) + .withOptionalArg(opts.classpath) + .withOptionalArg(opts.cp) + // TODO three temporary, hidden options to be able to test different classpath representations + .withOptionalArg(opts.classPathImplType) + .withOption(opts.disableFlatClassPathCaching) + .withOption(opts.logClassPath) + .parse(args) + + private def createClassPath(cpArg: Option[String], settings: Settings) = cpArg match { + case Some(cp) => settings.YclasspathImpl.value match { + case ClassPathRepresentationType.Flat => + AggregateFlatClassPath(new FlatClassPathFactory(settings).classesInExpandedPath(cp)) + case ClassPathRepresentationType.Recursive => + new JavaClassPath(DefaultJavaContext.classesInExpandedPath(cp), DefaultJavaContext) + } + case _ => + settings.classpath.value = "." // include '.' in the default classpath SI-6669 + PathResolverFactory.create(settings).result } } diff --git a/test/files/run/t6502.scala b/test/files/run/t6502.scala index ced1b5812d..4ce034a482 100644 --- a/test/files/run/t6502.scala +++ b/test/files/run/t6502.scala @@ -1,6 +1,7 @@ -import scala.tools.partest._ -import java.io.File +import scala.tools.nsc.Settings import scala.tools.nsc.interpreter.ILoop +import scala.tools.nsc.settings.ClassPathRepresentationType +import scala.tools.partest._ object Test extends StoreReporterDirectTest { def code = ??? @@ -10,6 +11,14 @@ object Test extends StoreReporterDirectTest { compileString(newCompiler("-cp", classpath, "-d", s"${testOutput.path}/$jarFileName"))(code) } + // TODO flat classpath doesn't support the classpath invalidation yet so we force using the recursive one + // it's the only test which needed such a workaround + override def settings = { + val settings = new Settings + settings.YclasspathImpl.value = ClassPathRepresentationType.Recursive + settings + } + def app1 = """ package test @@ -41,7 +50,8 @@ object Test extends StoreReporterDirectTest { val jar = "test1.jar" compileCode(app1, jar) - val output = ILoop.run(List(s":require ${testOutput.path}/$jar", "test.Test.test()")) + val codeToRun = toCodeInSeparateLines(s":require ${testOutput.path}/$jar", "test.Test.test()") + val output = ILoop.run(codeToRun, settings) val lines = output.split("\n") val res1 = lines(4).contains("Added") && lines(4).contains("test1.jar") val res2 = lines(lines.length-3).contains("testing...") @@ -56,7 +66,8 @@ object Test extends StoreReporterDirectTest { val jar2 = "test2.jar" compileCode(app2, jar2) - val output = ILoop.run(List(s":require ${testOutput.path}/$jar1", s":require ${testOutput.path}/$jar2")) + val codeToRun = toCodeInSeparateLines(s":require ${testOutput.path}/$jar1", s":require ${testOutput.path}/$jar2") + val output = ILoop.run(codeToRun, settings) val lines = output.split("\n") val res1 = lines(4).contains("Added") && lines(4).contains("test1.jar") val res2 = lines(lines.length-3).contains("test2.jar") && lines(lines.length-3).contains("existing classpath entries conflict") @@ -71,7 +82,8 @@ object Test extends StoreReporterDirectTest { val jar3 = "test3.jar" compileCode(app3, jar3) - val output = ILoop.run(List(s":require ${testOutput.path}/$jar1", s":require ${testOutput.path}/$jar3", "test.Test3.test()")) + val codeToRun = toCodeInSeparateLines(s":require ${testOutput.path}/$jar1", s":require ${testOutput.path}/$jar3", "test.Test3.test()") + val output = ILoop.run(codeToRun, settings) val lines = output.split("\n") val res1 = lines(4).contains("Added") && lines(4).contains("test1.jar") val res2 = lines(lines.length-3).contains("new object in existing package") @@ -83,7 +95,8 @@ object Test extends StoreReporterDirectTest { def test4(): Unit = { // twice the same jar should be rejected val jar1 = "test1.jar" - val output = ILoop.run(List(s":require ${testOutput.path}/$jar1", s":require ${testOutput.path}/$jar1")) + val codeToRun = toCodeInSeparateLines(s":require ${testOutput.path}/$jar1", s":require ${testOutput.path}/$jar1") + val output = ILoop.run(codeToRun, settings) val lines = output.split("\n") val res1 = lines(4).contains("Added") && lines(4).contains("test1.jar") val res2 = lines(lines.length-3).contains("test1.jar") && lines(lines.length-3).contains("existing classpath entries conflict") @@ -98,4 +111,6 @@ object Test extends StoreReporterDirectTest { test3() test4() } + + def toCodeInSeparateLines(lines: String*): String = lines.map(_ + "\n").mkString } diff --git a/test/files/run/t6669.scala b/test/files/run/t6669.scala index e18f2514a9..27c4970d60 100644 --- a/test/files/run/t6669.scala +++ b/test/files/run/t6669.scala @@ -1,4 +1,5 @@ import java.io.{ByteArrayOutputStream, PrintStream} +import scala.reflect.io.File object Test extends App { val baos = new ByteArrayOutputStream() @@ -9,9 +10,11 @@ object Test extends App { scala.tools.scalap.Main.main(Array("-verbose", "java.lang.Object")) } + val currentLocationCpFragment = File.pathSeparator + "." + // now make sure we saw the '.' in the classpath val msg1 = baos.toString() - assert(msg1 contains "directory classpath: .", s"Did not see '.' in the default class path. Full results were:\n$msg1") + assert(msg1 contains currentLocationCpFragment, s"Did not see '.' in the default class path. Full results were:\n$msg1") // then test again with a user specified classpath baos.reset @@ -22,5 +25,5 @@ object Test extends App { // now make sure we did not see the '.' in the classpath val msg2 = baos.toString() - assert(!(msg2 contains "directory classpath: ."), s"Did saw '.' in the user specified class path. Full results were:\n$msg2") + assert(!(msg2 contains currentLocationCpFragment), s"Did saw '.' in the user specified class path. Full results were:\n$msg2") } diff --git a/test/files/run/various-flat-classpath-types.check b/test/files/run/various-flat-classpath-types.check new file mode 100644 index 0000000000..401f707d0e --- /dev/null +++ b/test/files/run/various-flat-classpath-types.check @@ -0,0 +1,12 @@ +ZipBin() +JarBin() +DirBin() +ZipSrc() +JarSrc() +DirSrc() +NestedZipBin() +NestedJarBin() +NestedDirBin() +NestedZipSrc() +NestedJarSrc() +NestedDirSrc()
\ No newline at end of file diff --git a/test/files/run/various-flat-classpath-types.scala b/test/files/run/various-flat-classpath-types.scala new file mode 100644 index 0000000000..d39019e885 --- /dev/null +++ b/test/files/run/various-flat-classpath-types.scala @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2014 Contributor. All rights reserved. + */ + +import java.io.{File => JFile, FileInputStream, FileOutputStream} +import java.util.zip.{ZipEntry, ZipOutputStream} +import scala.reflect.io.{Directory, File} +import scala.tools.nsc.classpath.FlatClassPath.RootPackage +import scala.tools.nsc.classpath.PackageNameUtils +import scala.tools.nsc.io.Jar + +/** + * Generates directories, jars and zip files containing sources and classes + * (the result of a compilation which is executed here) + * and use them as a class- and sourcepath during compilation and running + * created application. At the end everything is cleaned up. + * + * It can test also current, recursive classpath. Just right now we force + * flat classpath to test it also when the recursive one would be set as a default. + */ +object Test { + + private implicit class JFileOps(file: JFile) { + + def createDir(newDirName: String) = { + val newDir = new JFile(file, newDirName) + newDir.mkdir() + newDir + } + + def createSrcFile(newFileName: String) = createFile(newFileName + ".scala") + + def createFile(fullFileName: String) = { + val newFile = new JFile(file, fullFileName) + newFile.createNewFile() + newFile + } + + def writeAll(text: String): Unit = File(file) writeAll text + + def moveContentToZip(zipName: String): Unit = { + val newZip = zipsDir createFile s"$zipName.zip" + val outputStream = new ZipOutputStream(new FileOutputStream(newZip)) + + def addFileToZip(dirPrefix: String = "")(fileToAdd: JFile): Unit = + if (fileToAdd.isDirectory) { + val dirEntryName = fileToAdd.getName + "/" + outputStream.putNextEntry(new ZipEntry(dirEntryName)) + fileToAdd.listFiles() foreach addFileToZip(dirEntryName) + } else { + val inputStream = new FileInputStream(fileToAdd) + outputStream.putNextEntry(new ZipEntry(dirPrefix + fileToAdd.getName)) + + val buffer = new Array[Byte](1024) + var count = inputStream.read(buffer) + while (count > 0) { + outputStream.write(buffer, 0, count) + count = inputStream.read(buffer) + } + + inputStream.close() + } + + file.listFiles() foreach addFileToZip() + outputStream.close() + + cleanDir(file) + } + + def moveContentToJar(jarName: String): Unit = { + val newJar = jarsDir createFile s"$jarName.jar" + Jar.create(file = File(newJar), sourceDir = Directory(file), mainClass = "won't be used") + cleanDir(file) + } + + def path: String = file.getAbsolutePath + } + + private case class DirRep(name: String, nestedDirs: Seq[DirRep] = Nil, sourceFiles: Seq[String] = Nil) + + private val compiler = new scala.tools.nsc.MainClass + private val appRunner = new scala.tools.nsc.MainGenericRunner + private val classPathImplFlag = "-YclasspathImpl:flat" + private val javaClassPath = sys.props("java.class.path") + + // creates a test dir in a temporary dir containing compiled files of this test + // root dir will be automatically deleted after the end of test + private val rootDir = new JFile(sys.props("partest.output")) + private val testDir = rootDir createDir s"cp-tests-${System.currentTimeMillis()}" + + private val jarsDir = testDir createDir "jars" + private val zipsDir = testDir createDir "zips" + private val srcDir = testDir createDir "src" + private val binDir = testDir createDir "bin" + private val outDir = testDir createDir "out" + + def main(args: Array[String]): Unit = { + createClassesZipInZipsDir() + createClassesJarInJarsDir() + createClassesInBinDir() + createSourcesZipInZipsDir() + createSourcesJarInJarsDir() + createSourcesInSrcDir() + compileFinalApp() + runApp() + // at the end all created files will be deleted automatically + } + + private def createClassesZipInZipsDir(): Unit = { + val baseFileName = "ZipBin" + createStandardSrcHierarchy(baseFileName) + compileSrc(baseFileName) + outDir moveContentToZip "Bin" + cleanDir(srcDir) + } + + private def createClassesJarInJarsDir(): Unit = { + val baseFileName = "JarBin" + createStandardSrcHierarchy(baseFileName) + compileSrc(baseFileName) + outDir moveContentToJar "Bin" + cleanDir(srcDir) + } + + private def createClassesInBinDir(): Unit = { + val baseFileName = "DirBin" + createStandardSrcHierarchy(baseFileName) + compileSrc(baseFileName, destination = binDir) + cleanDir(srcDir) + } + + private def createSourcesZipInZipsDir(): Unit = { + createStandardSrcHierarchy(baseFileName = "ZipSrc") + srcDir moveContentToZip "Src" + } + + private def createSourcesJarInJarsDir(): Unit = { + createStandardSrcHierarchy(baseFileName = "JarSrc") + srcDir moveContentToJar "Src" + } + + private def createSourcesInSrcDir(): Unit = { + createStandardSrcHierarchy(baseFileName = "DirSrc") + + val appFile = srcDir createSrcFile "Main" + appFile writeAll s"""import nested._ + | object Main extends App { + | println(new ZipBin) + | println(new JarBin) + | println(new DirBin) + | println(new ZipSrc) + | println(new JarSrc) + | println(new DirSrc) + | + | println(new NestedZipBin) + | println(new NestedJarBin) + | println(new NestedDirBin) + | println(new NestedZipSrc) + | println(new NestedJarSrc) + | println(new NestedDirSrc) + | } + """.stripMargin + } + + private def compileFinalApp(): Unit = { + val classPath = mkPath(javaClassPath, binDir.path, zipsDir.path + "/Bin.zip", jarsDir.path + "/Bin.jar") + val sourcePath = mkPath(srcDir.path, zipsDir.path + "/Src.zip", jarsDir.path + "/Src.jar") + + compiler.process(Array(classPathImplFlag, "-cp", classPath, "-sourcepath", sourcePath, + "-d", outDir.path, s"${srcDir.path}/Main.scala")) + } + + private def runApp(): Unit = { + val classPath = mkPath(javaClassPath, outDir.path, binDir.path, zipsDir.path + "/Bin.zip", jarsDir.path + "/Bin.jar") + appRunner.process(Array(classPathImplFlag, "-cp", classPath, "Main")) + } + + private def createStandardSrcHierarchy(baseFileName: String): Unit = + createSources(RootPackage, srcDir, + DirRep("", + nestedDirs = Seq(DirRep("nested", sourceFiles = Seq("Nested" + baseFileName))), + sourceFiles = Seq(baseFileName) + ) + ) + + private def createSources(pkg: String, dirFile: JFile, dirRep: DirRep): Unit = { + dirRep.nestedDirs foreach { rep => + val nestedDir = dirFile createDir rep.name + val nestedPkg = PackageNameUtils.packagePrefix(pkg) + rep.name + createSources(nestedPkg, nestedDir, rep) + } + + val pkgHeader = if (pkg == RootPackage) "" else s"package $pkg\n\n" + dirRep.sourceFiles foreach { srcName => + val text = s"""${pkgHeader}case class $srcName(x: String = "")""" + val srcFile = dirFile createSrcFile srcName + srcFile writeAll text + } + } + + private def compileSrc(baseFileName: String, destination: JFile = outDir): Unit = { + val srcDirPath = srcDir.path + compiler.process(Array(classPathImplFlag, "-cp", javaClassPath, "-d", destination.path, + s"$srcDirPath/$baseFileName.scala", s"$srcDirPath/nested/Nested$baseFileName.scala")) + } + + private def cleanDir(dir: JFile): Unit = + dir.listFiles().foreach { file => + if (file.isDirectory) cleanDir(file) + file.delete() + } + + private def mkPath(pathEntries: String*) = pathEntries.mkString(File.pathSeparator) +} diff --git a/test/junit/scala/tools/nsc/SampleTest.scala b/test/junit/scala/tools/nsc/SampleTest.scala index 810c88ef9d..60bb09e98f 100644 --- a/test/junit/scala/tools/nsc/SampleTest.scala +++ b/test/junit/scala/tools/nsc/SampleTest.scala @@ -11,6 +11,6 @@ import org.junit.runners.JUnit4 class SampleTest { @Test def testMath: Unit = { - assert(2+2 == 4, "you didn't get the math right fellow") + assertTrue("you didn't get the math right fellow", 2 + 2 == 4) } } diff --git a/test/junit/scala/tools/nsc/classpath/AggregateFlatClassPathTest.scala b/test/junit/scala/tools/nsc/classpath/AggregateFlatClassPathTest.scala new file mode 100644 index 0000000000..9a004d5e0e --- /dev/null +++ b/test/junit/scala/tools/nsc/classpath/AggregateFlatClassPathTest.scala @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2014 Contributor. All rights reserved. + */ +package scala.tools.nsc.classpath + +import java.net.URL +import org.junit.Assert._ +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import scala.reflect.io.VirtualFile +import scala.tools.nsc.io.AbstractFile + +/** + * Tests whether AggregateFlatClassPath returns correct entries taken from + * cp instances used during creating it and whether it preserves the ordering + * (in the case of the repeated entry for a class or a source it returns the first one). + */ +@RunWith(classOf[JUnit4]) +class AggregateFlatClassPathTest { + + private class TestFlatClassPath extends FlatClassPath { + override def packages(inPackage: String): Seq[PackageEntry] = unsupported + override def sources(inPackage: String): Seq[SourceFileEntry] = unsupported + override def classes(inPackage: String): Seq[ClassFileEntry] = unsupported + + override def list(inPackage: String): FlatClassPathEntries = unsupported + override def findClassFile(name: String): Option[AbstractFile] = unsupported + + override def asClassPathStrings: Seq[String] = unsupported + override def asSourcePathString: String = unsupported + override def asURLs: Seq[URL] = unsupported + } + + private case class TestClassPath(virtualPath: String, classesInPackage: EntryNamesInPackage*) extends TestFlatClassPath { + + override def classes(inPackage: String): Seq[ClassFileEntry] = + for { + entriesWrapper <- classesInPackage if entriesWrapper.inPackage == inPackage + name <- entriesWrapper.names + } yield classFileEntry(virtualPath, inPackage, name) + + override def sources(inPackage: String): Seq[SourceFileEntry] = Nil + + // we'll ignore packages + override def list(inPackage: String): FlatClassPathEntries = FlatClassPathEntries(Nil, classes(inPackage)) + } + + private case class TestSourcePath(virtualPath: String, sourcesInPackage: EntryNamesInPackage*) extends TestFlatClassPath { + + override def sources(inPackage: String): Seq[SourceFileEntry] = + for { + entriesWrapper <- sourcesInPackage if entriesWrapper.inPackage == inPackage + name <- entriesWrapper.names + } yield sourceFileEntry(virtualPath, inPackage, name) + + override def classes(inPackage: String): Seq[ClassFileEntry] = Nil + + // we'll ignore packages + override def list(inPackage: String): FlatClassPathEntries = FlatClassPathEntries(Nil, sources(inPackage)) + } + + private case class EntryNamesInPackage(inPackage: String)(val names: String*) + + private val dir1 = "./dir1" + private val dir2 = "./dir2" + private val dir3 = "./dir3" + private val dir4 = "" + + private val pkg1 = "pkg1" + private val pkg2 = "pkg2" + private val pkg3 = "pkg1.nested" + private val nonexistingPkg = "nonexisting" + + private def unsupported = throw new UnsupportedOperationException + + private def classFileEntry(pathPrefix: String, inPackage: String, fileName: String) = + ClassFileEntryImpl(classFile(pathPrefix, inPackage, fileName)) + + private def sourceFileEntry(pathPrefix: String, inPackage: String, fileName: String) = + SourceFileEntryImpl(sourceFile(pathPrefix, inPackage, fileName)) + + private def classFile(pathPrefix: String, inPackage: String, fileName: String) = + virtualFile(pathPrefix, inPackage, fileName, ".class") + + private def sourceFile(pathPrefix: String, inPackage: String, fileName: String) = + virtualFile(pathPrefix, inPackage, fileName, ".scala") + + private def virtualFile(pathPrefix: String, inPackage: String, fileName: String, extension: String) = { + val packageDirs = + if (inPackage == FlatClassPath.RootPackage) "" + else inPackage.split('.').mkString("/", "/", "") + new VirtualFile(fileName + extension, s"$pathPrefix$packageDirs/$fileName$extension") + } + + private def createDefaultTestClasspath() = { + val partialClassPaths = Seq(TestSourcePath(dir1, EntryNamesInPackage(pkg1)("F", "A", "G")), + TestClassPath(dir2, EntryNamesInPackage(pkg1)("C", "B", "A"), EntryNamesInPackage(pkg2)("D", "A", "E")), + TestClassPath(dir3, EntryNamesInPackage(pkg1)("A", "D", "F")), + TestSourcePath(dir4, EntryNamesInPackage(pkg2)("A", "H", "I"), EntryNamesInPackage(pkg1)("A")), + TestSourcePath(dir2, EntryNamesInPackage(pkg3)("J", "K", "L")) + ) + + AggregateFlatClassPath(partialClassPaths) + } + + @Test + def testGettingPackages: Unit = { + case class ClassPathWithPackages(packagesInPackage: EntryNamesInPackage*) extends TestFlatClassPath { + override def packages(inPackage: String): Seq[PackageEntry] = + packagesInPackage.find(_.inPackage == inPackage).map(_.names).getOrElse(Nil) map PackageEntryImpl + } + + val partialClassPaths = Seq(ClassPathWithPackages(EntryNamesInPackage(pkg1)("pkg1.a", "pkg1.d", "pkg1.f")), + ClassPathWithPackages(EntryNamesInPackage(pkg1)("pkg1.c", "pkg1.b", "pkg1.a"), + EntryNamesInPackage(pkg2)("pkg2.d", "pkg2.a", "pkg2.e")) + ) + val cp = AggregateFlatClassPath(partialClassPaths) + + val packagesInPkg1 = Seq("pkg1.a", "pkg1.d", "pkg1.f", "pkg1.c", "pkg1.b") + assertEquals(packagesInPkg1, cp.packages(pkg1).map(_.name)) + + val packagesInPkg2 = Seq("pkg2.d", "pkg2.a", "pkg2.e") + assertEquals(packagesInPkg2, cp.packages(pkg2).map(_.name)) + + assertEquals(Seq.empty, cp.packages(nonexistingPkg)) + } + + @Test + def testGettingClasses: Unit = { + val cp = createDefaultTestClasspath() + + val classesInPkg1 = Seq(classFileEntry(dir2, pkg1, "C"), + classFileEntry(dir2, pkg1, "B"), + classFileEntry(dir2, pkg1, "A"), + classFileEntry(dir3, pkg1, "D"), + classFileEntry(dir3, pkg1, "F") + ) + assertEquals(classesInPkg1, cp.classes(pkg1)) + + val classesInPkg2 = Seq(classFileEntry(dir2, pkg2, "D"), + classFileEntry(dir2, pkg2, "A"), + classFileEntry(dir2, pkg2, "E") + ) + assertEquals(classesInPkg2, cp.classes(pkg2)) + + assertEquals(Seq.empty, cp.classes(pkg3)) + assertEquals(Seq.empty, cp.classes(nonexistingPkg)) + } + + @Test + def testGettingSources: Unit = { + val partialClassPaths = Seq(TestClassPath(dir1, EntryNamesInPackage(pkg1)("F", "A", "G")), + TestSourcePath(dir2, EntryNamesInPackage(pkg1)("C", "B", "A"), EntryNamesInPackage(pkg2)("D", "A", "E")), + TestSourcePath(dir3, EntryNamesInPackage(pkg1)("A", "D", "F")), + TestClassPath(dir4, EntryNamesInPackage(pkg2)("A", "H", "I")), + TestClassPath(dir2, EntryNamesInPackage(pkg3)("J", "K", "L")) + ) + val cp = AggregateFlatClassPath(partialClassPaths) + + val sourcesInPkg1 = Seq(sourceFileEntry(dir2, pkg1, "C"), + sourceFileEntry(dir2, pkg1, "B"), + sourceFileEntry(dir2, pkg1, "A"), + sourceFileEntry(dir3, pkg1, "D"), + sourceFileEntry(dir3, pkg1, "F") + ) + assertEquals(sourcesInPkg1, cp.sources(pkg1)) + + val sourcesInPkg2 = Seq(sourceFileEntry(dir2, pkg2, "D"), + sourceFileEntry(dir2, pkg2, "A"), + sourceFileEntry(dir2, pkg2, "E") + ) + assertEquals(sourcesInPkg2, cp.sources(pkg2)) + + assertEquals(Seq.empty, cp.sources(pkg3)) + assertEquals(Seq.empty, cp.sources(nonexistingPkg)) + } + + @Test + def testList: Unit = { + val cp = createDefaultTestClasspath() + + val classesAndSourcesInPkg1 = Seq( + ClassAndSourceFilesEntry(classFile(dir3, pkg1, "F"), sourceFile(dir1, pkg1, "F")), + ClassAndSourceFilesEntry(classFile(dir2, pkg1, "A"), sourceFile(dir1, pkg1, "A")), + sourceFileEntry(dir1, pkg1, "G"), + classFileEntry(dir2, pkg1, "C"), + classFileEntry(dir2, pkg1, "B"), + classFileEntry(dir3, pkg1, "D") + ) + assertEquals(classesAndSourcesInPkg1, cp.list(pkg1).classesAndSources) + + assertEquals(FlatClassPathEntries(Nil, Nil), cp.list(nonexistingPkg)) + } + + @Test + def testFindClass: Unit = { + val cp = createDefaultTestClasspath() + + assertEquals( + Some(ClassAndSourceFilesEntry(classFile(dir2, pkg1, "A"), sourceFile(dir1, pkg1, "A"))), + cp.findClass(s"$pkg1.A") + ) + assertEquals(Some(classFileEntry(dir3, pkg1, "D")), cp.findClass(s"$pkg1.D")) + assertEquals(Some(sourceFileEntry(dir2, pkg3, "L")), cp.findClass(s"$pkg3.L")) + assertEquals(None, cp.findClass("Nonexisting")) + } +} diff --git a/test/junit/scala/tools/nsc/classpath/FlatClassPathResolverTest.scala b/test/junit/scala/tools/nsc/classpath/FlatClassPathResolverTest.scala new file mode 100644 index 0000000000..a37ba31b31 --- /dev/null +++ b/test/junit/scala/tools/nsc/classpath/FlatClassPathResolverTest.scala @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2014 Contributor. All rights reserved. + */ +package scala.tools.nsc.classpath + +import java.io.File +import org.junit.Assert._ +import org.junit._ +import org.junit.rules.TemporaryFolder +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import scala.annotation.tailrec +import scala.tools.nsc.io.AbstractFile +import scala.tools.nsc.util.ClassPath +import scala.tools.nsc.Settings +import scala.tools.util.FlatClassPathResolver +import scala.tools.util.PathResolver + +@RunWith(classOf[JUnit4]) +class FlatClassPathResolverTest { + + val tempDir = new TemporaryFolder() + + private val packagesToTest = List(FlatClassPath.RootPackage, "scala", "scala.reflect", "scala.reflect.io") + private val classFilesToFind = List("scala.tools.util.FlatClassPathResolver", + "scala.reflect.io.AbstractFile", + "scala.collection.immutable.List", + "scala.Option", + "scala.collection.immutable.Vector", + "scala.util.hashing.MurmurHash3", + "java.lang.Object", + "java.util.Date") + + private val classesToFind = classFilesToFind ++ List("TestSourceInRootPackage", + "scala.reflect.io.TestScalaSource", + "scala.reflect.io.TestJavaSource") + + private val settings = new Settings + + @Before + def initTempDirAndSourcePath: Unit = { + // In Java TemporaryFolder in JUnit is managed automatically using @Rule. + // It would work also in Scala after adding and extending a class like + // TestWithTempFolder.java containing it. But in this case it doesn't work when running tests + // from the command line - java class is not compiled due to some, misterious reasons. + // That's why such dirs are here created and deleted manually. + tempDir.create() + tempDir.newFile("TestSourceInRootPackage.scala") + val ioDir = tempDir.newFolder("scala", "reflect", "io") + new File(ioDir, "AbstractFile.scala").createNewFile() + new File(ioDir, "ZipArchive.java").createNewFile() + new File(ioDir, "TestScalaSource.scala").createNewFile() + new File(ioDir, "TestJavaSource.java").createNewFile() + + settings.usejavacp.value = true + settings.sourcepath.value = tempDir.getRoot.getAbsolutePath + } + + @After + def deleteTempDir: Unit = tempDir.delete() + + private def createFlatClassPath(settings: Settings) = + new FlatClassPathResolver(settings).result + + @Test + def testEntriesFromListOperationAgainstSeparateMethods: Unit = { + val classPath = createFlatClassPath(settings) + + def compareEntriesInPackage(inPackage: String): Unit = { + val packages = classPath.packages(inPackage) + val classes = classPath.classes(inPackage) + val sources = classPath.sources(inPackage) + val FlatClassPathEntries(packagesFromList, classesAndSourcesFromList) = classPath.list(inPackage) + + val packageNames = packages.map(_.name).sorted + val packageNamesFromList = packagesFromList.map(_.name).sorted + assertEquals(s"Methods list and packages for package '$inPackage' should return the same packages", + packageNames, packageNamesFromList) + + val classFileNames = classes.map(_.name).sorted + val classFileNamesFromList = classesAndSourcesFromList.filter(_.binary.isDefined).map(_.name).sorted + assertEquals(s"Methods list and classes for package '$inPackage' should return entries for the same class files", + classFileNames, classFileNamesFromList) + + val sourceFileNames = sources.map(_.name).sorted + val sourceFileNamesFromList = classesAndSourcesFromList.filter(_.source.isDefined).map(_.name).sorted + assertEquals(s"Methods list and sources for package '$inPackage' should return entries for the same source files", + sourceFileNames, sourceFileNamesFromList) + + val uniqueNamesOfClassAndSourceFiles = (classFileNames ++ sourceFileNames).toSet + assertEquals(s"Class and source entries with the same name obtained via list for package '$inPackage' should be merged into one containing both files", + uniqueNamesOfClassAndSourceFiles.size, classesAndSourcesFromList.length) + } + + packagesToTest foreach compareEntriesInPackage + } + + @Test + def testCreatedEntriesAgainstRecursiveClassPath: Unit = { + val flatClassPath = createFlatClassPath(settings) + val recursiveClassPath = new PathResolver(settings).result + + def compareEntriesInPackage(inPackage: String): Unit = { + + @tailrec + def traverseToPackage(packageNameParts: Seq[String], cp: ClassPath[AbstractFile]): ClassPath[AbstractFile] = { + packageNameParts match { + case Nil => cp + case h :: t => + cp.packages.find(_.name == h) match { + case Some(nestedCp) => traverseToPackage(t, nestedCp) + case _ => throw new Exception(s"There's no package $inPackage in recursive classpath - error when searching for '$h'") + } + } + } + + val packageNameParts = if (inPackage == FlatClassPath.RootPackage) Nil else inPackage.split('.').toList + val recursiveClassPathInPackage = traverseToPackage(packageNameParts, recursiveClassPath) + + val flatCpPackages = flatClassPath.packages(inPackage).map(_.name) + val pkgPrefix = PackageNameUtils.packagePrefix(inPackage) + val recursiveCpPackages = recursiveClassPathInPackage.packages.map(pkgPrefix + _.name) + assertEquals(s"Packages in package '$inPackage' on flat cp should be the same as on the recursive cp", + recursiveCpPackages, flatCpPackages) + + val flatCpSources = flatClassPath.sources(inPackage).map(_.name).sorted + val recursiveCpSources = recursiveClassPathInPackage.classes + .filter(_.source.nonEmpty) + .map(_.name).sorted + assertEquals(s"Source entries in package '$inPackage' on flat cp should be the same as on the recursive cp", + recursiveCpSources, flatCpSources) + + val flatCpClasses = flatClassPath.classes(inPackage).map(_.name).sorted + val recursiveCpClasses = recursiveClassPathInPackage.classes + .filter(_.binary.nonEmpty) + .map(_.name).sorted + assertEquals(s"Class entries in package '$inPackage' on flat cp should be the same as on the recursive cp", + recursiveCpClasses, flatCpClasses) + } + + packagesToTest foreach compareEntriesInPackage + } + + @Test + def testFindClassFile: Unit = { + val classPath = createFlatClassPath(settings) + classFilesToFind foreach { className => + assertTrue(s"File for $className should be found", classPath.findClassFile(className).isDefined) + } + } + + @Test + def testFindClass: Unit = { + val classPath = createFlatClassPath(settings) + classesToFind foreach { className => + assertTrue(s"File for $className should be found", classPath.findClass(className).isDefined) + } + } +} diff --git a/test/junit/scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala b/test/junit/scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala index e4be42ac96..f0f20acf07 100644 --- a/test/junit/scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala +++ b/test/junit/scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala @@ -3,6 +3,9 @@ package symtab import scala.reflect.ClassTag import scala.reflect.internal.{Phase, NoPhase, SomePhase} +import scala.tools.nsc.classpath.FlatClassPath +import scala.tools.nsc.settings.ClassPathRepresentationType +import scala.tools.util.FlatClassPathResolver import scala.tools.util.PathResolver import util.ClassPath import io.AbstractFile @@ -26,13 +29,28 @@ class SymbolTableForUnitTesting extends SymbolTable { class LazyTreeCopier extends super.LazyTreeCopier with TreeCopier override def isCompilerUniverse: Boolean = true - def classPath = new PathResolver(settings).result + + def classPath = platform.classPath + def flatClassPath: FlatClassPath = platform.flatClassPath object platform extends backend.Platform { val symbolTable: SymbolTableForUnitTesting.this.type = SymbolTableForUnitTesting.this lazy val loaders: SymbolTableForUnitTesting.this.loaders.type = SymbolTableForUnitTesting.this.loaders + def platformPhases: List[SubComponent] = Nil - val classPath: ClassPath[AbstractFile] = new PathResolver(settings).result + + lazy val classPath: ClassPath[AbstractFile] = { + assert(settings.YclasspathImpl.value == ClassPathRepresentationType.Recursive, + "It's not possible to use the recursive classpath representation, when it's not the chosen classpath scanning method") + new PathResolver(settings).result + } + + private[nsc] lazy val flatClassPath: FlatClassPath = { + assert(settings.YclasspathImpl.value == ClassPathRepresentationType.Flat, + "It's not possible to use the flat classpath representation, when it's not the chosen classpath scanning method") + new FlatClassPathResolver(settings).result + } + def isMaybeBoxed(sym: Symbol): Boolean = ??? def needCompile(bin: AbstractFile, src: AbstractFile): Boolean = ??? def externalEquals: Symbol = ??? @@ -50,7 +68,12 @@ class SymbolTableForUnitTesting extends SymbolTable { class GlobalMirror extends Roots(NoSymbol) { val universe: SymbolTableForUnitTesting.this.type = SymbolTableForUnitTesting.this - def rootLoader: LazyType = new loaders.PackageLoader(classPath) + + def rootLoader: LazyType = settings.YclasspathImpl.value match { + case ClassPathRepresentationType.Flat => new loaders.PackageLoaderUsingFlatClassPath(FlatClassPath.RootPackage, flatClassPath) + case ClassPathRepresentationType.Recursive => new loaders.PackageLoader(classPath) + } + override def toString = "compiler mirror" } @@ -60,7 +83,7 @@ class SymbolTableForUnitTesting extends SymbolTable { rm.asInstanceOf[Mirror] } - def settings: Settings = { + lazy val settings: Settings = { val s = new Settings // initialize classpath using java classpath s.usejavacp.value = true diff --git a/test/junit/scala/tools/nsc/util/ClassPathImplComparator.scala b/test/junit/scala/tools/nsc/util/ClassPathImplComparator.scala new file mode 100644 index 0000000000..f2926e3e17 --- /dev/null +++ b/test/junit/scala/tools/nsc/util/ClassPathImplComparator.scala @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2014 Contributor. All rights reserved. + */ +package scala.tools.nsc.util + +import scala.reflect.io.AbstractFile +import scala.tools.nsc.Settings +import scala.tools.nsc.settings.ClassPathRepresentationType +import scala.tools.util.PathResolverFactory + +/** + * Simple application to compare efficiency of the recursive and the flat classpath representations + */ +object ClassPathImplComparator { + + private class TestSettings extends Settings { + val checkClasses = PathSetting("-checkClasses", "Specify names of classes which should be found separated with ;", "") + val requiredIterations = IntSetting("-requiredIterations", + "Repeat tests specified number of times (to check e.g. impact of caches)", 1, Some((1, Int.MaxValue)), (_: String) => None) + val cpCreationRepetitions = IntSetting("-cpCreationRepetitions", + "Repeat tests specified number of times (to check e.g. impact of caches)", 1, Some((1, Int.MaxValue)), (_: String) => None) + val cpLookupRepetitions = IntSetting("-cpLookupRepetitions", + "Repeat tests specified number of times (to check e.g. impact of caches)", 1, Some((1, Int.MaxValue)), (_: String) => None) + } + + private class DurationStats(name: String) { + private var sum = 0L + private var iterations = 0 + + def noteMeasuredTime(millis: Long): Unit = { + sum += millis + iterations += 1 + } + + def printResults(): Unit = { + val avg = if (iterations == 0) 0 else sum.toDouble / iterations + println(s"$name - total duration: $sum ms; iterations: $iterations; avg: $avg ms") + } + } + + private lazy val defaultClassesToFind = List( + "scala.collection.immutable.List", + "scala.Option", + "scala.Int", + "scala.collection.immutable.Vector", + "scala.util.hashing.MurmurHash3" + ) + + private val oldCpCreationStats = new DurationStats("Old classpath - create") + private val oldCpSearchingStats = new DurationStats("Old classpath - search") + + private val flatCpCreationStats = new DurationStats("Flat classpath - create") + private val flatCpSearchingStats = new DurationStats("Flat classpath - search") + + def main(args: Array[String]): Unit = { + + if (args contains "-help") + usage() + else { + val oldCpSettings = loadSettings(args.toList, ClassPathRepresentationType.Recursive) + val flatCpSettings = loadSettings(args.toList, ClassPathRepresentationType.Flat) + + val classesToCheck = oldCpSettings.checkClasses.value + val classesToFind = + if (classesToCheck.isEmpty) defaultClassesToFind + else classesToCheck.split(";").toList + + def doTest(classPath: => ClassFileLookup[AbstractFile], cpCreationStats: DurationStats, cpSearchingStats: DurationStats, + cpCreationRepetitions: Int, cpLookupRepetitions: Int)= { + + def createClassPaths() = (1 to cpCreationRepetitions).map(_ => classPath).last + def testClassLookup(cp: ClassFileLookup[AbstractFile]): Boolean = (1 to cpCreationRepetitions).foldLeft(true) { + case (a, _) => a && checkExistenceOfClasses(classesToFind)(cp) + } + + val cp = withMeasuredTime("Creating classpath", createClassPaths(), cpCreationStats) + val result = withMeasuredTime("Searching for specified classes", testClassLookup(cp), cpSearchingStats) + println(s"The end of the test case. All expected classes found = $result \n") + } + + (1 to oldCpSettings.requiredIterations.value) foreach { iteration => + if (oldCpSettings.requiredIterations.value > 1) + println(s"Iteration no $iteration") + + println("Recursive (old) classpath representation:") + doTest(PathResolverFactory.create(oldCpSettings).result, oldCpCreationStats, oldCpSearchingStats, + oldCpSettings.cpCreationRepetitions.value, oldCpSettings.cpLookupRepetitions.value) + + println("Flat classpath representation:") + doTest(PathResolverFactory.create(flatCpSettings).result, flatCpCreationStats, flatCpSearchingStats, + flatCpSettings.cpCreationRepetitions.value, flatCpSettings.cpLookupRepetitions.value) + } + + if (oldCpSettings.requiredIterations.value > 1) { + println("\nOld classpath - summary") + oldCpCreationStats.printResults() + oldCpSearchingStats.printResults() + + println("\nFlat classpath - summary") + flatCpCreationStats.printResults() + flatCpSearchingStats.printResults() + } + } + } + + /** + * Prints usage information + */ + private def usage(): Unit = + println("""Use classpath and sourcepath options like in the case of e.g. 'scala' command. + | There are also two additional options: + | -checkClasses <semicolon separated class names> Specify names of classes which should be found + | -requiredIterations <int value> Repeat tests specified count of times (to check e.g. impact of caches) + | Note: Option -YclasspathImpl will be set automatically for each case. + """.stripMargin.trim) + + private def loadSettings(args: List[String], implType: String) = { + val settings = new TestSettings() + settings.processArguments(args, processAll = true) + settings.YclasspathImpl.value = implType + if (settings.classpath.isDefault) + settings.classpath.value = sys.props("java.class.path") + settings + } + + private def withMeasuredTime[T](operationName: String, f: => T, durationStats: DurationStats): T = { + val startTime = System.currentTimeMillis() + val res = f + val elapsed = System.currentTimeMillis() - startTime + durationStats.noteMeasuredTime(elapsed) + println(s"$operationName - elapsed $elapsed ms") + res + } + + private def checkExistenceOfClasses(classesToCheck: Seq[String])(classPath: ClassFileLookup[AbstractFile]): Boolean = + classesToCheck.foldLeft(true) { + case (res, classToCheck) => + val found = classPath.findClass(classToCheck).isDefined + if (!found) + println(s"Class $classToCheck not found") // of course in this case the measured time will be affected by IO operation + found + } +} diff --git a/test/script-tests/README b/test/script-tests/README index 3f5c2ce19c..7b3291c407 100755 --- a/test/script-tests/README +++ b/test/script-tests/README @@ -5,4 +5,9 @@ putting self-contained script tests in here to run some way that doesn't depend on all the platform stars aligning all the time. Feel free to join me. --- extempore, Nov 21 2011
\ No newline at end of file +-- extempore, Nov 21 2011 + +But there's a problem that probably nobody would run such tests so they would become outdated quite quickly. +And therefore they wouldn't work (and even compile) after some time - like this one existing currently. + +-- mpociecha, Oct 9 2014
\ No newline at end of file |