forked from scala/scala3
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix scala#2186: Synchronize classpath handling with Scala 2.12
This commit is a very crude port of the classpath handling as it exists in the 2.12.x branch of scalac (hash: 232d95a198c94da0c6c8393624e83e9b9ac84e81), this replaces the existing Classpath code that was adapted from scalac years ago. This code was written by Grzegorz Kossakowski, Michał Pociecha, Lukas Rytz, Jason Zaugg and other scalac contributors, many thanks to them! For more information on this implementation, see the description of the PR that originally added it to scalac: scala/scala#4060 Changes made to the copied code to get it to compile with dotty: - Rename scala.tools.nsc.util.ClassPath to dotty.tools.io.ClassPath - Rename scala.tools.nsc.classpath.* to dotty.tools.dotc.classpath.* - Replace "private[nsc]" by "private[dotty]" - Changed `isClass` methods in FileUtils to skip Scala 2.11 implementation classes (needed until we stop being retro-compatible with Scala 2.11) I also copied PlainFile.scala from scalac to get access to `PlainNioFile`.
- Loading branch information
Showing
20 changed files
with
1,254 additions
and
392 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
149 changes: 149 additions & 0 deletions
149
compiler/src/dotty/tools/dotc/classpath/AggregateClassPath.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
/* | ||
* Copyright (c) 2014 Contributor. All rights reserved. | ||
*/ | ||
package dotty.tools.dotc.classpath | ||
|
||
import java.net.URL | ||
import scala.annotation.tailrec | ||
import scala.collection.mutable.ArrayBuffer | ||
import scala.reflect.internal.FatalError | ||
import scala.reflect.io.AbstractFile | ||
import dotty.tools.io.ClassPath | ||
import dotty.tools.io.ClassRepresentation | ||
|
||
/** | ||
* A classpath unifying multiple class- and sourcepath entries. | ||
* The 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 AggregateClassPath(aggregates: Seq[ClassPath]) extends ClassPath { | ||
override def findClassFile(className: String): Option[AbstractFile] = { | ||
@tailrec | ||
def find(aggregates: Seq[ClassPath]): 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] = { | ||
@tailrec | ||
def findEntry(aggregates: Seq[ClassPath], isSource: Boolean): Option[ClassRepresentation] = | ||
if (aggregates.nonEmpty) { | ||
val entry = aggregates.head.findClass(className) match { | ||
case s @ Some(_: SourceFileEntry) if isSource => s | ||
case s @ Some(_: ClassFileEntry) if !isSource => s | ||
case _ => None | ||
} | ||
if (entry.isDefined) entry | ||
else findEntry(aggregates.tail, isSource) | ||
} else None | ||
|
||
val classEntry = findEntry(aggregates, isSource = false) | ||
val sourceEntry = findEntry(aggregates, isSource = true) | ||
|
||
(classEntry, sourceEntry) match { | ||
case (Some(c: ClassFileEntry), Some(s: SourceFileEntry)) => Some(ClassAndSourceFilesEntry(c.file, s.file)) | ||
case (c @ Some(_), _) => c | ||
case (_, s) => s | ||
} | ||
} | ||
|
||
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[dotty] def packages(inPackage: String): Seq[PackageEntry] = { | ||
val aggregatedPackages = aggregates.flatMap(_.packages(inPackage)).distinct | ||
aggregatedPackages | ||
} | ||
|
||
override private[dotty] def classes(inPackage: String): Seq[ClassFileEntry] = | ||
getDistinctEntries(_.classes(inPackage)) | ||
|
||
override private[dotty] def sources(inPackage: String): Seq[SourceFileEntry] = | ||
getDistinctEntries(_.sources(inPackage)) | ||
|
||
override private[dotty] def list(inPackage: String): ClassPathEntries = { | ||
val (packages, classesAndSources) = aggregates.map { cp => | ||
try { | ||
cp.list(inPackage) | ||
} catch { | ||
case ex: java.io.IOException => | ||
val e = new FatalError(ex.getMessage) | ||
e.initCause(ex) | ||
throw e | ||
} | ||
}.unzip | ||
val distinctPackages = packages.flatten.distinct | ||
val distinctClassesAndSources = mergeClassesAndSources(classesAndSources: _*) | ||
ClassPathEntries(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[ClassRepresentation]*): Seq[ClassRepresentation] = { | ||
// based on the implementation from MergedClassPath | ||
var count = 0 | ||
val indices = collection.mutable.HashMap[String, Int]() | ||
val mergedEntries = new ArrayBuffer[ClassRepresentation](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 <: ClassRepresentation](getEntries: ClassPath => 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 | ||
} | ||
} | ||
|
||
object AggregateClassPath { | ||
def createAggregate(parts: ClassPath*): ClassPath = { | ||
val elems = new ArrayBuffer[ClassPath]() | ||
parts foreach { | ||
case AggregateClassPath(ps) => elems ++= ps | ||
case p => elems += p | ||
} | ||
if (elems.size == 1) elems.head | ||
else AggregateClassPath(elems.toIndexedSeq) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
/* | ||
* Copyright (c) 2014 Contributor. All rights reserved. | ||
*/ | ||
package dotty.tools.dotc.classpath | ||
|
||
import scala.reflect.io.AbstractFile | ||
import dotty.tools.io.ClassRepresentation | ||
|
||
case class ClassPathEntries(packages: Seq[PackageEntry], classesAndSources: Seq[ClassRepresentation]) | ||
|
||
object ClassPathEntries { | ||
import scala.language.implicitConversions | ||
// to have working unzip method | ||
implicit def entry2Tuple(entry: ClassPathEntries): (Seq[PackageEntry], Seq[ClassRepresentation]) = (entry.packages, entry.classesAndSources) | ||
} | ||
|
||
trait ClassFileEntry extends ClassRepresentation { | ||
def file: AbstractFile | ||
} | ||
|
||
trait SourceFileEntry extends ClassRepresentation { | ||
def file: AbstractFile | ||
} | ||
|
||
trait PackageEntry { | ||
def name: String | ||
} | ||
|
||
private[dotty] 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[dotty] 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[dotty] case class ClassAndSourceFilesEntry(classFile: AbstractFile, srcFile: AbstractFile) extends ClassRepresentation { | ||
override def name = FileUtils.stripClassExtension(classFile.name) | ||
|
||
override def binary: Option[AbstractFile] = Some(classFile) | ||
override def source: Option[AbstractFile] = Some(srcFile) | ||
} | ||
|
||
private[dotty] case class PackageEntryImpl(name: String) extends PackageEntry | ||
|
||
private[dotty] trait NoSourcePaths { | ||
def asSourcePathString: String = "" | ||
private[dotty] def sources(inPackage: String): Seq[SourceFileEntry] = Seq.empty | ||
} | ||
|
||
private[dotty] trait NoClassPaths { | ||
def findClassFile(className: String): Option[AbstractFile] = None | ||
private[dotty] def classes(inPackage: String): Seq[ClassFileEntry] = Seq.empty | ||
} |
83 changes: 83 additions & 0 deletions
83
compiler/src/dotty/tools/dotc/classpath/ClassPathFactory.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
/* | ||
* Copyright (c) 2014 Contributor. All rights reserved. | ||
*/ | ||
package dotty.tools.dotc.classpath | ||
|
||
import scala.reflect.io.{AbstractFile, VirtualDirectory} | ||
import scala.reflect.io.Path.string2path | ||
import dotty.tools.dotc.config.Settings | ||
import FileUtils.AbstractFileOps | ||
import dotty.tools.io.ClassPath | ||
import dotty.tools.dotc.core.Contexts.Context | ||
|
||
/** | ||
* Provides factory methods for 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 ClassPathFactory { | ||
/** | ||
* Create a new classpath based on the abstract file. | ||
*/ | ||
def newClassPath(file: AbstractFile)(implicit ctx: Context): ClassPath = ClassPathFactory.newClassPath(file) | ||
|
||
/** | ||
* Creators for sub classpaths which preserve this context. | ||
*/ | ||
def sourcesInPath(path: String)(implicit ctx: Context): List[ClassPath] = | ||
for { | ||
file <- expandPath(path, expandStar = false) | ||
dir <- Option(AbstractFile getDirectory file) | ||
} yield createSourcePath(dir) | ||
|
||
|
||
def expandPath(path: String, expandStar: Boolean = true): List[String] = dotty.tools.io.ClassPath.expandPath(path, expandStar) | ||
|
||
def expandDir(extdir: String): List[String] = dotty.tools.io.ClassPath.expandDir(extdir) | ||
|
||
def contentsOfDirsInPath(path: String)(implicit ctx: Context): List[ClassPath] = | ||
for { | ||
dir <- expandPath(path, expandStar = false) | ||
name <- expandDir(dir) | ||
entry <- Option(AbstractFile.getDirectory(name)) | ||
} yield newClassPath(entry) | ||
|
||
def classesInExpandedPath(path: String)(implicit ctx: Context): IndexedSeq[ClassPath] = | ||
classesInPathImpl(path, expand = true).toIndexedSeq | ||
|
||
def classesInPath(path: String)(implicit ctx: Context) = classesInPathImpl(path, expand = false) | ||
|
||
def classesInManifest(useManifestClassPath: Boolean)(implicit ctx: Context) = | ||
if (useManifestClassPath) dotty.tools.io.ClassPath.manifests.map(url => newClassPath(AbstractFile getResources url)) | ||
else Nil | ||
|
||
// Internal | ||
protected def classesInPathImpl(path: String, expand: Boolean)(implicit ctx: Context) = | ||
for { | ||
file <- expandPath(path, expand) | ||
dir <- { | ||
def asImage = if (file.endsWith(".jimage")) Some(AbstractFile.getFile(file)) else None | ||
Option(AbstractFile.getDirectory(file)).orElse(asImage) | ||
} | ||
} yield newClassPath(dir) | ||
|
||
private def createSourcePath(file: AbstractFile)(implicit ctx: Context): ClassPath = | ||
if (file.isJarOrZip) | ||
ZipAndJarSourcePathFactory.create(file) | ||
else if (file.isDirectory) | ||
new DirectorySourcePath(file.file) | ||
else | ||
sys.error(s"Unsupported sourcepath element: $file") | ||
} | ||
|
||
object ClassPathFactory { | ||
def newClassPath(file: AbstractFile)(implicit ctx: Context): ClassPath = file match { | ||
case vd: VirtualDirectory => VirtualDirectoryClassPath(vd) | ||
case _ => | ||
if (file.isJarOrZip) | ||
ZipAndJarClassPathFactory.create(file) | ||
else if (file.isDirectory) | ||
new DirectoryClassPath(file.file) | ||
else | ||
sys.error(s"Unsupported classpath element: $file") | ||
} | ||
} |
Oops, something went wrong.