Skip to content

Commit 2b04d2a

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. 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`.
1 parent 92a9d05 commit 2b04d2a

20 files changed

+1254
-392
lines changed

AUTHORS.md

+14-9
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,28 @@
1-
The dotty compiler frontend has been developed since November 2012 by Martin Odersky. It is expected and hoped for
1+
The dotty compiler frontend has been developed since November 2012 by Martin Odersky. It is expected and hoped for
22
that the list of contributors to the codebase will grow quickly. Dotty draws inspiration and code from the original
3-
Scala compiler "nsc", which is developed at [scala/scala](https://github.com/scala/scala).
3+
Scala compiler "nsc", which is developed at [scala/scala](https://github.com/scala/scala).
44

55
The majority of the dotty codebase is new code, with the exception of the components mentioned below. We have for each component tried to come up with a list of the original authors in the [scala/scala](https://github.com/scala/scala) codebase. Apologies if some major authors were omitted by oversight.
66

77
`dotty.tools.dotc.ast`
88

9-
> The syntax tree handling is mostly new, but some elements, such as the idea of tree copiers and the `TreeInfo` module,
10-
> were adopted from [scala/scala](https://github.com/scala/scala).
9+
> The syntax tree handling is mostly new, but some elements, such as the idea of tree copiers and the `TreeInfo` module,
10+
> were adopted from [scala/scala](https://github.com/scala/scala).
1111
> The original authors of these parts include Martin Odersky, Paul Phillips, Adriaan Moors, and Matthias Zenger.
1212
13+
`dotty.tools.dotc.classpath`
14+
15+
> The classpath handling is taken mostly as is from [scala/scala](https://github.com/scala/scala).
16+
> The original authors were Grzegorz Kossakowski, Michał Pociecha, Lukas Rytz, Jason Zaugg and others.
17+
1318
`dotty.tools.dotc.config`
1419

15-
> The configuration components were adapted and extended from [scala/scala](https://github.com/scala/scala).
20+
> The configuration components were adapted and extended from [scala/scala](https://github.com/scala/scala).
1621
> The original sources were authored by Paul Phillips with contributions from Martin Odersky, Miguel Garcia and others.
17-
22+
1823
`dotty.tools.dotc.core`
1924

20-
> The core data structures and operations are mostly new. Some parts (e.g. those dealing with names) were adapted from [scala/scala](https://github.com/scala/scala).
25+
> The core data structures and operations are mostly new. Some parts (e.g. those dealing with names) were adapted from [scala/scala](https://github.com/scala/scala).
2126
> These were originally authored by Martin Odersky, Adriaan Moors, Jason Zaugg, Paul Phillips, Eugene Burmako and others.
2227
2328
`dotty.tools.dotc.core.pickling`
@@ -41,7 +46,7 @@ The majority of the dotty codebase is new code, with the exception of the compon
4146

4247
> The utilities package is a mix of new and adapted components. The files in [scala/scala](https://github.com/scala/scala) were originally authored by many people,
4348
> including Paul Phillips, Martin Odersky, Sean McDirmid, and others.
44-
49+
4550
`dotty.tools.io`
4651

4752
> The I/O support library was adapted from current Scala compiler. Original authors were Paul Phillips and others.
@@ -53,7 +58,7 @@ The majority of the dotty codebase is new code, with the exception of the compon
5358
> the needs of dotty. Original authors include: Adrian Moors, Lukas Rytz,
5459
> Grzegorz Kossakowski, Paul Phillips
5560
56-
`dotty.tools.dotc.sbt and everything in bridge/`
61+
`dotty.tools.dotc.sbt and everything in sbt-bridge/`
5762

5863
> The sbt compiler phases are based on
5964
> https://github.com/adriaanm/scala/tree/sbt-api-consolidate/src/compiler/scala/tools/sbt
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+
}
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+
}
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)