Skip to content

Commit 2423ec9

Browse files
committed
Fix #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. I'll update this commit with more details and proper attribution later, for now I just want to run this on the CI.
1 parent 586113d commit 2423ec9

19 files changed

+1234
-382
lines changed
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/*
2+
* Copyright (c) 2014 Contributor. All rights reserved.
3+
*/
4+
package dotty.tools.dotc.classpath
5+
6+
import java.net.URL
7+
import scala.annotation.tailrec
8+
import scala.collection.mutable.ArrayBuffer
9+
import scala.reflect.internal.FatalError
10+
import scala.reflect.io.AbstractFile
11+
import dotty.tools.io.ClassPath
12+
import dotty.tools.io.ClassRepresentation
13+
14+
/**
15+
* A classpath unifying multiple class- and sourcepath entries.
16+
* The Classpath can obtain entries for classes and sources independently
17+
* so it tries to do operations quite optimally - iterating only these collections
18+
* which are needed in the given moment and only as far as it's necessary.
19+
*
20+
* @param aggregates classpath instances containing entries which this class processes
21+
*/
22+
case class AggregateClassPath(aggregates: Seq[ClassPath]) extends ClassPath {
23+
override def findClassFile(className: String): Option[AbstractFile] = {
24+
@tailrec
25+
def find(aggregates: Seq[ClassPath]): Option[AbstractFile] =
26+
if (aggregates.nonEmpty) {
27+
val classFile = aggregates.head.findClassFile(className)
28+
if (classFile.isDefined) classFile
29+
else find(aggregates.tail)
30+
} else None
31+
32+
find(aggregates)
33+
}
34+
35+
override def findClass(className: String): Option[ClassRepresentation] = {
36+
@tailrec
37+
def findEntry(aggregates: Seq[ClassPath], isSource: Boolean): Option[ClassRepresentation] =
38+
if (aggregates.nonEmpty) {
39+
val entry = aggregates.head.findClass(className) match {
40+
case s @ Some(_: SourceFileEntry) if isSource => s
41+
case s @ Some(_: ClassFileEntry) if !isSource => s
42+
case _ => None
43+
}
44+
if (entry.isDefined) entry
45+
else findEntry(aggregates.tail, isSource)
46+
} else None
47+
48+
val classEntry = findEntry(aggregates, isSource = false)
49+
val sourceEntry = findEntry(aggregates, isSource = true)
50+
51+
(classEntry, sourceEntry) match {
52+
case (Some(c: ClassFileEntry), Some(s: SourceFileEntry)) => Some(ClassAndSourceFilesEntry(c.file, s.file))
53+
case (c @ Some(_), _) => c
54+
case (_, s) => s
55+
}
56+
}
57+
58+
override def asURLs: Seq[URL] = aggregates.flatMap(_.asURLs)
59+
60+
override def asClassPathStrings: Seq[String] = aggregates.map(_.asClassPathString).distinct
61+
62+
override def asSourcePathString: String = ClassPath.join(aggregates map (_.asSourcePathString): _*)
63+
64+
override private[dotty] def packages(inPackage: String): Seq[PackageEntry] = {
65+
val aggregatedPackages = aggregates.flatMap(_.packages(inPackage)).distinct
66+
aggregatedPackages
67+
}
68+
69+
override private[dotty] def classes(inPackage: String): Seq[ClassFileEntry] =
70+
getDistinctEntries(_.classes(inPackage))
71+
72+
override private[dotty] def sources(inPackage: String): Seq[SourceFileEntry] =
73+
getDistinctEntries(_.sources(inPackage))
74+
75+
override private[dotty] def list(inPackage: String): ClassPathEntries = {
76+
val (packages, classesAndSources) = aggregates.map { cp =>
77+
try {
78+
cp.list(inPackage)
79+
} catch {
80+
case ex: java.io.IOException =>
81+
val e = new FatalError(ex.getMessage)
82+
e.initCause(ex)
83+
throw e
84+
}
85+
}.unzip
86+
val distinctPackages = packages.flatten.distinct
87+
val distinctClassesAndSources = mergeClassesAndSources(classesAndSources: _*)
88+
ClassPathEntries(distinctPackages, distinctClassesAndSources)
89+
}
90+
91+
/**
92+
* Returns only one entry for each name. If there's both a source and a class entry, it
93+
* creates an entry containing both of them. If there would be more than one class or source
94+
* entries for the same class it always would use the first entry of each type found on a classpath.
95+
*/
96+
private def mergeClassesAndSources(entries: Seq[ClassRepresentation]*): Seq[ClassRepresentation] = {
97+
// based on the implementation from MergedClassPath
98+
var count = 0
99+
val indices = collection.mutable.HashMap[String, Int]()
100+
val mergedEntries = new ArrayBuffer[ClassRepresentation](1024)
101+
102+
for {
103+
partOfEntries <- entries
104+
entry <- partOfEntries
105+
} {
106+
val name = entry.name
107+
if (indices contains name) {
108+
val index = indices(name)
109+
val existing = mergedEntries(index)
110+
111+
if (existing.binary.isEmpty && entry.binary.isDefined)
112+
mergedEntries(index) = ClassAndSourceFilesEntry(entry.binary.get, existing.source.get)
113+
if (existing.source.isEmpty && entry.source.isDefined)
114+
mergedEntries(index) = ClassAndSourceFilesEntry(existing.binary.get, entry.source.get)
115+
}
116+
else {
117+
indices(name) = count
118+
mergedEntries += entry
119+
count += 1
120+
}
121+
}
122+
mergedEntries.toIndexedSeq
123+
}
124+
125+
private def getDistinctEntries[EntryType <: ClassRepresentation](getEntries: ClassPath => Seq[EntryType]): Seq[EntryType] = {
126+
val seenNames = collection.mutable.HashSet[String]()
127+
val entriesBuffer = new ArrayBuffer[EntryType](1024)
128+
for {
129+
cp <- aggregates
130+
entry <- getEntries(cp) if !seenNames.contains(entry.name)
131+
} {
132+
entriesBuffer += entry
133+
seenNames += entry.name
134+
}
135+
entriesBuffer.toIndexedSeq
136+
}
137+
}
138+
139+
object AggregateClassPath {
140+
def createAggregate(parts: ClassPath*): ClassPath = {
141+
val elems = new ArrayBuffer[ClassPath]()
142+
parts foreach {
143+
case AggregateClassPath(ps) => elems ++= ps
144+
case p => elems += p
145+
}
146+
if (elems.size == 1) elems.head
147+
else AggregateClassPath(elems.toIndexedSeq)
148+
}
149+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright (c) 2014 Contributor. All rights reserved.
3+
*/
4+
package dotty.tools.dotc.classpath
5+
6+
import scala.reflect.io.AbstractFile
7+
import dotty.tools.io.ClassRepresentation
8+
9+
case class ClassPathEntries(packages: Seq[PackageEntry], classesAndSources: Seq[ClassRepresentation])
10+
11+
object ClassPathEntries {
12+
import scala.language.implicitConversions
13+
// to have working unzip method
14+
implicit def entry2Tuple(entry: ClassPathEntries): (Seq[PackageEntry], Seq[ClassRepresentation]) = (entry.packages, entry.classesAndSources)
15+
}
16+
17+
trait ClassFileEntry extends ClassRepresentation {
18+
def file: AbstractFile
19+
}
20+
21+
trait SourceFileEntry extends ClassRepresentation {
22+
def file: AbstractFile
23+
}
24+
25+
trait PackageEntry {
26+
def name: String
27+
}
28+
29+
private[dotty] case class ClassFileEntryImpl(file: AbstractFile) extends ClassFileEntry {
30+
override def name = FileUtils.stripClassExtension(file.name) // class name
31+
32+
override def binary: Option[AbstractFile] = Some(file)
33+
override def source: Option[AbstractFile] = None
34+
}
35+
36+
private[dotty] case class SourceFileEntryImpl(file: AbstractFile) extends SourceFileEntry {
37+
override def name = FileUtils.stripSourceExtension(file.name)
38+
39+
override def binary: Option[AbstractFile] = None
40+
override def source: Option[AbstractFile] = Some(file)
41+
}
42+
43+
private[dotty] case class ClassAndSourceFilesEntry(classFile: AbstractFile, srcFile: AbstractFile) extends ClassRepresentation {
44+
override def name = FileUtils.stripClassExtension(classFile.name)
45+
46+
override def binary: Option[AbstractFile] = Some(classFile)
47+
override def source: Option[AbstractFile] = Some(srcFile)
48+
}
49+
50+
private[dotty] case class PackageEntryImpl(name: String) extends PackageEntry
51+
52+
private[dotty] trait NoSourcePaths {
53+
def asSourcePathString: String = ""
54+
private[dotty] def sources(inPackage: String): Seq[SourceFileEntry] = Seq.empty
55+
}
56+
57+
private[dotty] trait NoClassPaths {
58+
def findClassFile(className: String): Option[AbstractFile] = None
59+
private[dotty] def classes(inPackage: String): Seq[ClassFileEntry] = Seq.empty
60+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright (c) 2014 Contributor. All rights reserved.
3+
*/
4+
package dotty.tools.dotc.classpath
5+
6+
import scala.reflect.io.{AbstractFile, VirtualDirectory}
7+
import scala.reflect.io.Path.string2path
8+
import dotty.tools.dotc.config.Settings
9+
import FileUtils.AbstractFileOps
10+
import dotty.tools.io.ClassPath
11+
import dotty.tools.dotc.core.Contexts.Context
12+
13+
/**
14+
* Provides factory methods for classpath. When creating classpath instances for a given path,
15+
* it uses proper type of classpath depending on a types of particular files containing sources or classes.
16+
*/
17+
class ClassPathFactory {
18+
/**
19+
* Create a new classpath based on the abstract file.
20+
*/
21+
def newClassPath(file: AbstractFile)(implicit ctx: Context): ClassPath = ClassPathFactory.newClassPath(file)
22+
23+
/**
24+
* Creators for sub classpaths which preserve this context.
25+
*/
26+
def sourcesInPath(path: String)(implicit ctx: Context): List[ClassPath] =
27+
for {
28+
file <- expandPath(path, expandStar = false)
29+
dir <- Option(AbstractFile getDirectory file)
30+
} yield createSourcePath(dir)
31+
32+
33+
def expandPath(path: String, expandStar: Boolean = true): List[String] = dotty.tools.io.ClassPath.expandPath(path, expandStar)
34+
35+
def expandDir(extdir: String): List[String] = dotty.tools.io.ClassPath.expandDir(extdir)
36+
37+
def contentsOfDirsInPath(path: String)(implicit ctx: Context): List[ClassPath] =
38+
for {
39+
dir <- expandPath(path, expandStar = false)
40+
name <- expandDir(dir)
41+
entry <- Option(AbstractFile.getDirectory(name))
42+
} yield newClassPath(entry)
43+
44+
def classesInExpandedPath(path: String)(implicit ctx: Context): IndexedSeq[ClassPath] =
45+
classesInPathImpl(path, expand = true).toIndexedSeq
46+
47+
def classesInPath(path: String)(implicit ctx: Context) = classesInPathImpl(path, expand = false)
48+
49+
def classesInManifest(useManifestClassPath: Boolean)(implicit ctx: Context) =
50+
if (useManifestClassPath) dotty.tools.io.ClassPath.manifests.map(url => newClassPath(AbstractFile getResources url))
51+
else Nil
52+
53+
// Internal
54+
protected def classesInPathImpl(path: String, expand: Boolean)(implicit ctx: Context) =
55+
for {
56+
file <- expandPath(path, expand)
57+
dir <- {
58+
def asImage = if (file.endsWith(".jimage")) Some(AbstractFile.getFile(file)) else None
59+
Option(AbstractFile.getDirectory(file)).orElse(asImage)
60+
}
61+
} yield newClassPath(dir)
62+
63+
private def createSourcePath(file: AbstractFile)(implicit ctx: Context): ClassPath =
64+
if (file.isJarOrZip)
65+
ZipAndJarSourcePathFactory.create(file)
66+
else if (file.isDirectory)
67+
new DirectorySourcePath(file.file)
68+
else
69+
sys.error(s"Unsupported sourcepath element: $file")
70+
}
71+
72+
object ClassPathFactory {
73+
def newClassPath(file: AbstractFile)(implicit ctx: Context): ClassPath = file match {
74+
case vd: VirtualDirectory => VirtualDirectoryClassPath(vd)
75+
case _ =>
76+
if (file.isJarOrZip)
77+
ZipAndJarClassPathFactory.create(file)
78+
else if (file.isDirectory)
79+
new DirectoryClassPath(file.file)
80+
else
81+
sys.error(s"Unsupported classpath element: $file")
82+
}
83+
}

0 commit comments

Comments
 (0)