Skip to content

Fix #2186: Synchronize classpath handling with Scala 2.12 #2191

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 11, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 14 additions & 9 deletions AUTHORS.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
The dotty compiler frontend has been developed since November 2012 by Martin Odersky. It is expected and hoped for
The dotty compiler frontend has been developed since November 2012 by Martin Odersky. It is expected and hoped for
that the list of contributors to the codebase will grow quickly. Dotty draws inspiration and code from the original
Scala compiler "nsc", which is developed at [scala/scala](https://github.com/scala/scala).
Scala compiler "nsc", which is developed at [scala/scala](https://github.com/scala/scala).

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.

`dotty.tools.dotc.ast`

> The syntax tree handling is mostly new, but some elements, such as the idea of tree copiers and the `TreeInfo` module,
> were adopted from [scala/scala](https://github.com/scala/scala).
> The syntax tree handling is mostly new, but some elements, such as the idea of tree copiers and the `TreeInfo` module,
> were adopted from [scala/scala](https://github.com/scala/scala).
> The original authors of these parts include Martin Odersky, Paul Phillips, Adriaan Moors, and Matthias Zenger.

`dotty.tools.dotc.classpath`

> The classpath handling is taken mostly as is from [scala/scala](https://github.com/scala/scala).
> The original authors were Grzegorz Kossakowski, Michał Pociecha, Lukas Rytz, Jason Zaugg and others.

`dotty.tools.dotc.config`

> The configuration components were adapted and extended from [scala/scala](https://github.com/scala/scala).
> The configuration components were adapted and extended from [scala/scala](https://github.com/scala/scala).
> The original sources were authored by Paul Phillips with contributions from Martin Odersky, Miguel Garcia and others.

`dotty.tools.dotc.core`

> 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).
> 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).
> These were originally authored by Martin Odersky, Adriaan Moors, Jason Zaugg, Paul Phillips, Eugene Burmako and others.

`dotty.tools.dotc.core.pickling`
Expand All @@ -41,7 +46,7 @@ The majority of the dotty codebase is new code, with the exception of the compon

> 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,
> including Paul Phillips, Martin Odersky, Sean McDirmid, and others.

`dotty.tools.io`

> The I/O support library was adapted from current Scala compiler. Original authors were Paul Phillips and others.
Expand All @@ -53,7 +58,7 @@ The majority of the dotty codebase is new code, with the exception of the compon
> the needs of dotty. Original authors include: Adrian Moors, Lukas Rytz,
> Grzegorz Kossakowski, Paul Phillips

`dotty.tools.dotc.sbt and everything in bridge/`
`dotty.tools.dotc.sbt and everything in sbt-bridge/`

> The sbt compiler phases are based on
> https://github.com/adriaanm/scala/tree/sbt-api-consolidate/src/compiler/scala/tools/sbt
Expand Down
149 changes: 149 additions & 0 deletions compiler/src/dotty/tools/dotc/classpath/AggregateClassPath.scala
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)
}
}
60 changes: 60 additions & 0 deletions compiler/src/dotty/tools/dotc/classpath/ClassPath.scala
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 compiler/src/dotty/tools/dotc/classpath/ClassPathFactory.scala
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")
}
}
Loading