mirror of https://github.com/sbt/sbt.git
195 lines
8.0 KiB
Scala
195 lines
8.0 KiB
Scala
/* sbt -- Simple Build Tool
|
|
* Copyright 2008, 2009 Mark Harrah
|
|
*/
|
|
package xsbt
|
|
|
|
import scala.tools.nsc.{ io, symtab, Phase }
|
|
import io.{ AbstractFile, PlainFile, ZipArchive }
|
|
import symtab.Flags
|
|
|
|
import java.io.File
|
|
|
|
object Dependency {
|
|
def name = "xsbt-dependency"
|
|
}
|
|
/**
|
|
* Extracts dependency information from each compilation unit.
|
|
*
|
|
* This phase uses CompilationUnit.depends and CallbackGlobal.inheritedDependencies
|
|
* to collect all symbols that given compilation unit depends on. Those symbols are
|
|
* guaranteed to represent Class-like structures.
|
|
*
|
|
* The CallbackGlobal.inheritedDependencies is populated by the API phase. See,
|
|
* ExtractAPI class.
|
|
*
|
|
* When dependency symbol is processed, it is mapped back to either source file where
|
|
* it's defined in (if it's available in current compilation run) or classpath entry
|
|
* where it originates from. The Symbol->Classfile mapping is implemented by
|
|
* LocateClassFile that we inherit from.
|
|
*/
|
|
final class Dependency(val global: CallbackGlobal) extends LocateClassFile {
|
|
import global._
|
|
|
|
def newPhase(prev: Phase): Phase = new DependencyPhase(prev)
|
|
private class DependencyPhase(prev: Phase) extends Phase(prev) {
|
|
override def description = "Extracts dependency information"
|
|
def name = Dependency.name
|
|
def run {
|
|
for (unit <- currentRun.units if !unit.isJava) {
|
|
// build dependencies structure
|
|
val sourceFile = unit.source.file.file
|
|
if (global.callback.nameHashing) {
|
|
val dependenciesByMemberRef = extractDependenciesByMemberRef(unit)
|
|
for (on <- dependenciesByMemberRef)
|
|
processDependency(on, inherited = false)
|
|
|
|
val dependenciesByInheritance = extractDependenciesByInheritance(unit)
|
|
for (on <- dependenciesByInheritance)
|
|
processDependency(on, inherited = true)
|
|
} else {
|
|
for (on <- unit.depends) processDependency(on, inherited = false)
|
|
for (on <- inheritedDependencies.getOrElse(sourceFile, Nil: Iterable[Symbol])) processDependency(on, inherited = true)
|
|
}
|
|
/**
|
|
* Handles dependency on given symbol by trying to figure out if represents a term
|
|
* that is coming from either source code (not necessarily compiled in this compilation
|
|
* run) or from class file and calls respective callback method.
|
|
*/
|
|
def processDependency(on: Symbol, inherited: Boolean) {
|
|
def binaryDependency(file: File, className: String) = callback.binaryDependency(file, className, sourceFile, inherited)
|
|
val onSource = on.sourceFile
|
|
if (onSource == null) {
|
|
classFile(on) match {
|
|
case Some((f, className, inOutDir)) =>
|
|
if (inOutDir && on.isJavaDefined) registerTopLevelSym(on)
|
|
f match {
|
|
case ze: ZipArchive#Entry => for (zip <- ze.underlyingSource; zipFile <- Option(zip.file)) binaryDependency(zipFile, className)
|
|
case pf: PlainFile => binaryDependency(pf.file, className)
|
|
case _ => ()
|
|
}
|
|
case None => ()
|
|
}
|
|
} else if (onSource.file != sourceFile)
|
|
callback.sourceDependency(onSource.file, sourceFile, inherited)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Traverses given type and collects result of applying a partial function `pf`.
|
|
*
|
|
* NOTE: This class exists in Scala 2.10 as CollectTypeCollector but does not in earlier
|
|
* versions (like 2.9) of Scala compiler that incremental cmpiler supports so we had to
|
|
* reimplement that class here.
|
|
*/
|
|
private final class CollectTypeTraverser[T](pf: PartialFunction[Type, T]) extends TypeTraverser {
|
|
var collected: List[T] = Nil
|
|
def traverse(tpe: Type): Unit = {
|
|
if (pf.isDefinedAt(tpe))
|
|
collected = pf(tpe) :: collected
|
|
mapOver(tpe)
|
|
}
|
|
}
|
|
|
|
private abstract class ExtractDependenciesTraverser extends Traverser {
|
|
protected val depBuf = collection.mutable.ArrayBuffer.empty[Symbol]
|
|
protected def addDependency(dep: Symbol): Unit = depBuf += dep
|
|
def dependencies: collection.immutable.Set[Symbol] = {
|
|
// convert to immutable set and remove NoSymbol if we have one
|
|
depBuf.toSet - NoSymbol
|
|
}
|
|
}
|
|
|
|
private class ExtractDependenciesByMemberRefTraverser extends ExtractDependenciesTraverser {
|
|
override def traverse(tree: Tree): Unit = {
|
|
tree match {
|
|
case Import(expr, selectors) =>
|
|
selectors.foreach {
|
|
case ImportSelector(nme.WILDCARD, _, null, _) =>
|
|
// in case of wildcard import we do not rely on any particular name being defined
|
|
// on `expr`; all symbols that are being used will get caught through selections
|
|
case ImportSelector(name: Name, _, _, _) =>
|
|
def lookupImported(name: Name) = expr.symbol.info.member(name)
|
|
// importing a name means importing both a term and a type (if they exist)
|
|
addDependency(lookupImported(name.toTermName))
|
|
addDependency(lookupImported(name.toTypeName))
|
|
}
|
|
case select: Select =>
|
|
addDependency(select.symbol)
|
|
/*
|
|
* Idents are used in number of situations:
|
|
* - to refer to local variable
|
|
* - to refer to a top-level package (other packages are nested selections)
|
|
* - to refer to a term defined in the same package as an enclosing class;
|
|
* this looks fishy, see this thread:
|
|
* https://groups.google.com/d/topic/scala-internals/Ms9WUAtokLo/discussion
|
|
*/
|
|
case ident: Ident =>
|
|
addDependency(ident.symbol)
|
|
case typeTree: TypeTree =>
|
|
val typeSymbolCollector = new CollectTypeTraverser({
|
|
case tpe if !tpe.typeSymbol.isPackage => tpe.typeSymbol
|
|
})
|
|
typeSymbolCollector.traverse(typeTree.tpe)
|
|
val deps = typeSymbolCollector.collected.toSet
|
|
deps.foreach(addDependency)
|
|
case Template(parents, self, body) =>
|
|
traverseTrees(body)
|
|
/*
|
|
* Some macros appear to contain themselves as original tree
|
|
* In this case, we don't need to inspect the original tree because
|
|
* we already inspected its expansion, which is equal.
|
|
* See https://issues.scala-lang.org/browse/SI-8486
|
|
*/
|
|
case MacroExpansionOf(original) if original != tree =>
|
|
this.traverse(original)
|
|
case other => ()
|
|
}
|
|
super.traverse(tree)
|
|
}
|
|
}
|
|
|
|
private def extractDependenciesByMemberRef(unit: CompilationUnit): collection.immutable.Set[Symbol] = {
|
|
val traverser = new ExtractDependenciesByMemberRefTraverser
|
|
traverser.traverse(unit.body)
|
|
val dependencies = traverser.dependencies
|
|
dependencies.map(enclosingTopLevelClass)
|
|
}
|
|
|
|
/** Copied straight from Scala 2.10 as it does not exist in Scala 2.9 compiler */
|
|
private final def debuglog(msg: => String) {
|
|
if (settings.debug.value)
|
|
log(msg)
|
|
}
|
|
|
|
private final class ExtractDependenciesByInheritanceTraverser extends ExtractDependenciesTraverser {
|
|
override def traverse(tree: Tree): Unit = tree match {
|
|
case Template(parents, self, body) =>
|
|
// we are using typeSymbol and not typeSymbolDirect because we want
|
|
// type aliases to be expanded
|
|
val parentTypeSymbols = parents.map(parent => parent.tpe.typeSymbol).toSet
|
|
debuglog("Parent type symbols for " + tree.pos + ": " + parentTypeSymbols.map(_.fullName))
|
|
parentTypeSymbols.foreach(addDependency)
|
|
traverseTrees(body)
|
|
case tree => super.traverse(tree)
|
|
}
|
|
}
|
|
|
|
private def extractDependenciesByInheritance(unit: CompilationUnit): collection.immutable.Set[Symbol] = {
|
|
val traverser = new ExtractDependenciesByInheritanceTraverser
|
|
traverser.traverse(unit.body)
|
|
val dependencies = traverser.dependencies
|
|
dependencies.map(enclosingTopLevelClass)
|
|
}
|
|
|
|
/**
|
|
* We capture enclosing classes only because that's what CompilationUnit.depends does and we don't want
|
|
* to deviate from old behaviour too much for now.
|
|
*/
|
|
private def enclosingTopLevelClass(sym: Symbol): Symbol =
|
|
// for Scala 2.8 and 2.9 this method is provided through SymbolCompat
|
|
sym.enclosingTopLevelClass
|
|
|
|
}
|