Skip to content

Commit

Permalink
Fix scala#2186: Synchronize classpath handling with Scala 2.12
Browse files Browse the repository at this point in the history
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
smarter committed Apr 11, 2017
1 parent 92a9d05 commit 2b04d2a
Show file tree
Hide file tree
Showing 20 changed files with 1,254 additions and 392 deletions.
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

0 comments on commit 2b04d2a

Please sign in to comment.