Merge pull request #1736 from Duhemm/dependency-kind-compile

Abstract over dependency kind in Compile
This commit is contained in:
Grzegorz Kossakowski 2014-12-02 16:00:22 +01:00
commit 015c61ad69
13 changed files with 114 additions and 72 deletions

View File

@ -5,6 +5,7 @@ package sbt
package inc
import xsbti.api.Source
import xsbti.DependencyContext._
import java.io.File
/**

View File

@ -10,6 +10,8 @@ import xsbti.{ Position, Problem, Severity }
import Logger.{ m2o, problem }
import java.io.File
import xsbti.api.Definition
import xsbti.DependencyContext
import xsbti.DependencyContext.{ DependencyByInheritance, DependencyByMemberRef }
object IncrementalCompile {
def apply(sources: Set[File], entry: String => Option[File],
@ -61,7 +63,7 @@ private final class AnalysisCallback(internalMap: File => Option[File], external
new Compilation(System.currentTimeMillis, outputSettings)
}
override def toString = (List("APIs", "Binary deps", "Products", "Source deps") zip List(apis, binaryDeps, classes, sourceDeps)).map { case (label, map) => label + "\n\t" + map.mkString("\n\t") }.mkString("\n")
override def toString = (List("APIs", "Binary deps", "Products", "Source deps") zip List(apis, binaryDeps, classes, intSrcDeps)).map { case (label, map) => label + "\n\t" + map.mkString("\n\t") }.mkString("\n")
import collection.mutable.{ HashMap, HashSet, ListBuffer, Map, Set }
@ -75,12 +77,10 @@ private final class AnalysisCallback(internalMap: File => Option[File], external
private[this] val classes = new HashMap[File, Set[(File, String)]]
// generated class file to its source file
private[this] val classToSource = new HashMap[File, File]
// all internal source depenencies, including direct and inherited
private[this] val sourceDeps = new HashMap[File, Set[File]]
// inherited internal source dependencies
private[this] val inheritedSourceDeps = new HashMap[File, Set[File]]
// internal source dependencies
private[this] val intSrcDeps = new HashMap[File, Set[InternalDependency]]
// external source dependencies
private[this] val extSrcDeps = new HashMap[File, Iterable[ExternalDependency]]
private[this] val extSrcDeps = new HashMap[File, Set[ExternalDependency]]
private[this] val binaryClassName = new HashMap[File, String]
// source files containing a macro def.
private[this] val macroSources = Set[File]()
@ -96,46 +96,57 @@ private final class AnalysisCallback(internalMap: File => Option[File], external
}
}
def sourceDependency(dependsOn: File, source: File, context: DependencyContext) = {
add(intSrcDeps, source, InternalDependency(source, dependsOn, context))
}
@deprecated("Use `sourceDependency(File, File, DependencyContext)`.", "0.13.8")
def sourceDependency(dependsOn: File, source: File, inherited: Boolean) =
{
add(sourceDeps, source, dependsOn)
if (inherited) add(inheritedSourceDeps, source, dependsOn)
val context = if (inherited) DependencyByInheritance else DependencyByMemberRef
sourceDependency(dependsOn, source, context)
}
def externalBinaryDependency(binary: File, className: String, source: File, inherited: Boolean) {
private[this] def externalBinaryDependency(binary: File, className: String, source: File, context: DependencyContext) = {
binaryClassName.put(binary, className)
add(binaryDeps, source, binary)
}
// The type corresponds to : (internal source, external source depended on, API of external dependency, true if an inheritance dependency)
def externalSourceDependency(t4: (File, String, Source, Boolean)) = {
val dependency = ExternalDependency(t4._1, t4._2, t4._3, if (t4._4) DependencyByInheritance else DependencyByMemberRef)
extSrcDeps += t4._1 -> (extSrcDeps.getOrElse(t4._1, Nil) ++ List(dependency))
private[this] def externalSourceDependency(sourceFile: File, dependsOn: String, source: Source, context: DependencyContext) = {
val dependency = ExternalDependency(sourceFile, dependsOn, source, context)
add(extSrcDeps, sourceFile, dependency)
}
def binaryDependency(classFile: File, name: String, source: File, inherited: Boolean) =
def binaryDependency(classFile: File, name: String, source: File, context: DependencyContext) =
internalMap(classFile) match {
case Some(dependsOn) =>
// dependency is a product of a source not included in this compilation
sourceDependency(dependsOn, source, inherited)
sourceDependency(dependsOn, source, context)
case None =>
classToSource.get(classFile) match {
case Some(dependsOn) =>
// dependency is a product of a source in this compilation step,
// but not in the same compiler run (as in javac v. scalac)
sourceDependency(dependsOn, source, inherited)
sourceDependency(dependsOn, source, context)
case None =>
externalDependency(classFile, name, source, inherited)
externalDependency(classFile, name, source, context)
}
}
private[this] def externalDependency(classFile: File, name: String, source: File, inherited: Boolean): Unit =
@deprecated("Use `binaryDependency(File, String, File, DependencyContext)`.", "0.13.8")
def binaryDependency(classFile: File, name: String, source: File, inherited: Boolean) = {
val context = if (inherited) DependencyByInheritance else DependencyByMemberRef
binaryDependency(classFile, name, source, context)
}
private[this] def externalDependency(classFile: File, name: String, source: File, context: DependencyContext): Unit =
externalAPI(classFile, name) match {
case Some(api) =>
// dependency is a product of a source in another project
externalSourceDependency((source, name, api, inherited))
externalSourceDependency(source, name, api, context)
case None =>
// dependency is some other binary on the classpath
externalBinaryDependency(classFile, name, source, inherited)
externalBinaryDependency(classFile, name, source, context)
}
def generatedClass(source: File, module: File, name: String) =
@ -174,16 +185,6 @@ private final class AnalysisCallback(internalMap: File => Option[File], external
(a /: names) { case (a, name) => a.copy(relations = a.relations.addUsedName(src, name)) }
}
// This is no longer used by the new implementation relative to Dependency contexts
// See https://github.com/sbt/sbt/issues/1340
def addAll[A, B](base: Analysis, m: Map[A, Set[B]])(f: (Analysis, A, B) => Analysis): Analysis =
(base /: m) {
case (outer, (a, bs)) =>
(outer /: bs) { (inner, b) =>
f(inner, a, b)
}
}
def addProductsAndDeps(base: Analysis): Analysis =
(base /: apis) {
case (a, (src, api)) =>
@ -193,14 +194,12 @@ private final class AnalysisCallback(internalMap: File => Option[File], external
val hasMacro: Boolean = macroSources.contains(src)
val s = new xsbti.api.Source(compilation, hash, api._2, api._1, publicNameHashes(src), hasMacro)
val info = SourceInfos.makeInfo(getOrNil(reporteds, src), getOrNil(unreporteds, src))
val direct = sourceDeps.getOrElse(src, Nil: Iterable[File])
val publicInherited = inheritedSourceDeps.getOrElse(src, Nil: Iterable[File])
val binaries = binaryDeps.getOrElse(src, Nil: Iterable[File])
val prods = classes.getOrElse(src, Nil: Iterable[(File, String)])
val products = prods.map { case (prod, name) => (prod, name, current product prod) }
val internalDeps = direct.map(InternalDependency(src, _, DependencyByMemberRef)) ++ publicInherited.map(InternalDependency(src, _, DependencyByInheritance))
val externalDeps = extSrcDeps.getOrElse(src, Nil: Iterable[ExternalDependency])
val internalDeps = intSrcDeps.getOrElse(src, Set.empty)
val externalDeps = extSrcDeps.getOrElse(src, Set.empty)
val binDeps = binaries.map(d => (d, binaryClassName(d), current binary d))
a.addSource(src, s, stamp, info, products, internalDeps, externalDeps, binDeps)

View File

@ -2,22 +2,7 @@ package sbt.inc
import java.io.File
import xsbti.api.Source
/**
* Represents contextual information about particular depedency edge. See comments in
* subtypes for examples of particular contexts.
*/
private[inc] sealed abstract class DependencyContext
/**
* Marks dependency edge introduced by referring to a class through inheritance as in
*
* class A extends B
*
* Each dependency by inheritance introduces corresponding dependency by member reference.
*/
private[inc] final case object DependencyByInheritance extends DependencyContext
private[inc] final case object DependencyByMemberRef extends DependencyContext
import xsbti.DependencyContext
/**
* Represents the kind of dependency that exists between `sourceFile` and either `targetFile`

View File

@ -8,6 +8,8 @@ import java.io.File
import Relations.Source
import Relations.SourceDependencies
import xsbti.api.{ Source => APISource }
import xsbti.DependencyContext
import xsbti.DependencyContext._
/**
* Provides mappings between source files, generated classes (products), and binaries.

View File

@ -7,6 +7,7 @@ import sbt.inc.TestCaseGenerators._
import org.scalacheck._
import Gen._
import Prop._
import xsbti.DependencyContext._
object AnalysisTest extends Properties("Analysis") {
// Merge and split a hard-coded trivial example.

View File

@ -10,6 +10,7 @@ import Gen._
import sbt.Relation
import xsbti.api._
import xsbti.SafeLazy
import xsbti.DependencyContext._
/**
* Scalacheck generators for Analysis objects and their substructures.

View File

@ -6,6 +6,8 @@ package xsbt
import scala.tools.nsc.{ io, symtab, Phase }
import io.{ AbstractFile, PlainFile, ZipArchive }
import symtab.Flags
import xsbti.DependencyContext
import xsbti.DependencyContext._
import java.io.File
@ -41,22 +43,22 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile {
if (global.callback.nameHashing) {
val dependenciesByMemberRef = extractDependenciesByMemberRef(unit)
for (on <- dependenciesByMemberRef)
processDependency(on, inherited = false)
processDependency(on, context = DependencyByMemberRef)
val dependenciesByInheritance = extractDependenciesByInheritance(unit)
for (on <- dependenciesByInheritance)
processDependency(on, inherited = true)
processDependency(on, context = DependencyByInheritance)
} else {
for (on <- unit.depends) processDependency(on, inherited = false)
for (on <- inheritedDependencies.getOrElse(sourceFile, Nil: Iterable[Symbol])) processDependency(on, inherited = true)
for (on <- unit.depends) processDependency(on, context = DependencyByMemberRef)
for (on <- inheritedDependencies.getOrElse(sourceFile, Nil: Iterable[Symbol])) processDependency(on, context = DependencyByInheritance)
}
/**
* 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)
def processDependency(on: Symbol, context: DependencyContext) {
def binaryDependency(file: File, className: String) = callback.binaryDependency(file, className, sourceFile, context)
val onSource = on.sourceFile
if (onSource == null) {
classFile(on) match {
@ -70,7 +72,7 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile {
case None => ()
}
} else if (onSource.file != sourceFile)
callback.sourceDependency(onSource.file, sourceFile, inherited)
callback.sourceDependency(onSource.file, sourceFile, context)
}
}
}

View File

@ -12,6 +12,7 @@ import xsbti.api.Definition
import xsbti.api.Def
import xsbt.api.SameAPI
import sbt.ConsoleLogger
import xsbti.DependencyContext._
import ScalaCompilerForUnitTesting.ExtractedSourceDependencies
@ -68,11 +69,11 @@ class ScalaCompilerForUnitTesting(nameHashing: Boolean = false) {
val memberRefFileDeps = testCallback.sourceDependencies collect {
// false indicates that those dependencies are not introduced by inheritance
case (target, src, false) => (src, target)
case (target, src, DependencyByMemberRef) => (src, target)
}
val inheritanceFileDeps = testCallback.sourceDependencies collect {
// true indicates that those dependencies are introduced by inheritance
case (target, src, true) => (src, target)
case (target, src, DependencyByInheritance) => (src, target)
}
def toSymbols(src: File, target: File): (Symbol, Symbol) = (fileToSymbol(src), fileToSymbol(target))
val memberRefDeps = memberRefFileDeps map { case (src, target) => toSymbols(src, target) }

View File

@ -7,6 +7,7 @@ package inc
import xsbti.api.{ Source, Compilation }
import xsbti.{ Position, Problem, Severity }
import xsbti.compile.{ CompileOrder, Output => APIOutput, SingleOutput, MultipleOutput }
import xsbti.DependencyContext._
import MultipleOutput.OutputGroup
import java.io.File
import sbinary._

View File

@ -12,13 +12,29 @@ public interface AnalysisCallback
* passed to this method. Dependencies on classes generated by sources not in the current compilation will
* be passed as class dependencies to the classDependency method.
* If <code>publicInherited</code> is true, this dependency is a result of inheritance by a
* template accessible outside of the source file. */
* template accessible outside of the source file.
* @deprecated Use `sourceDependency(File dependsOn, File source, DependencyContext context)` instead. */
@Deprecated
public void sourceDependency(File dependsOn, File source, boolean publicInherited);
/** Called to indicate that the source file <code>source</code> depends on the source file
* <code>dependsOn</code>. Note that only source files included in the current compilation will
* passed to this method. Dependencies on classes generated by sources not in the current compilation will
* be passed as class dependencies to the classDependency method.
* <code>context</code> gives information about the context in which this dependency has been extracted.
* See xsbti.DependencyContext for the list of existing dependency contexts. */
public void sourceDependency(File dependsOn, File source, DependencyContext context);
/** Called to indicate that the source file <code>source</code> depends on the top-level
* class named <code>name</code> from class or jar file <code>binary</code>.
* If <code>publicInherited</code> is true, this dependency is a result of inheritance by a
* template accessible outside of the source file. */
* template accessible outside of the source file.
* @deprecated Use `binaryDependency(File binary, String name, File source, DependencyContext context)` instead. */
@Deprecated
public void binaryDependency(File binary, String name, File source, boolean publicInherited);
/** Called to indicate that the source file <code>source</code> depends on the top-level
* class named <code>name</code> from class or jar file <code>binary</code>.
* <code>context</code> gives information about the context in which this dependency has been extracted.
* See xsbti.DependencyContext for the list of existing dependency contexts. */
public void binaryDependency(File binary, String name, File source, DependencyContext context);
/** Called to indicate that the source file <code>source</code> produces a class file at
* <code>module</code> contain class <code>name</code>.*/
public void generatedClass(File source, File module, String name);

View File

@ -0,0 +1,22 @@
package xsbti;
/**
* Enumeration of existing dependency contexts.
* Dependency contexts represent the various kind of dependencies that
* can exist between symbols.
*/
public enum DependencyContext {
/**
* Represents a direct dependency between two symbols :
* object Foo
* object Bar { def foo = Foo }
*/
DependencyByMemberRef,
/**
* Represents an inheritance dependency between two symbols :
* class A
* class B extends A
*/
DependencyByInheritance
}

View File

@ -3,17 +3,26 @@ package xsbti
import java.io.File
import scala.collection.mutable.ArrayBuffer
import xsbti.api.SourceAPI
import xsbti.DependencyContext._
class TestCallback(override val nameHashing: Boolean = false) extends AnalysisCallback
{
val sourceDependencies = new ArrayBuffer[(File, File, Boolean)]
val binaryDependencies = new ArrayBuffer[(File, String, File, Boolean)]
val sourceDependencies = new ArrayBuffer[(File, File, DependencyContext)]
val binaryDependencies = new ArrayBuffer[(File, String, File, DependencyContext)]
val products = new ArrayBuffer[(File, File, String)]
val usedNames = scala.collection.mutable.Map.empty[File, Set[String]].withDefaultValue(Set.empty)
val apis: scala.collection.mutable.Map[File, SourceAPI] = scala.collection.mutable.Map.empty
def sourceDependency(dependsOn: File, source: File, inherited: Boolean) { sourceDependencies += ((dependsOn, source, inherited)) }
def binaryDependency(binary: File, name: String, source: File, inherited: Boolean) { binaryDependencies += ((binary, name, source, inherited)) }
def sourceDependency(dependsOn: File, source: File, inherited: Boolean) {
val context = if(inherited) DependencyByInheritance else DependencyByMemberRef
sourceDependency(dependsOn, source, context)
}
def sourceDependency(dependsOn: File, source: File, context: DependencyContext) { sourceDependencies += ((dependsOn, source, context)) }
def binaryDependency(binary: File, name: String, source: File, inherited: Boolean) {
val context = if(inherited) DependencyByInheritance else DependencyByMemberRef
binaryDependency(binary, name, source, context)
}
def binaryDependency(binary: File, name: String, source: File, context: DependencyContext) { binaryDependencies += ((binary, name, source, context)) }
def generatedClass(source: File, module: File, name: String) { products += ((source, module, name)) }
def usedName(source: File, name: String) { usedNames(source) += name }

View File

@ -12,6 +12,8 @@ import java.lang.annotation.Annotation
import java.lang.reflect.Method
import java.lang.reflect.Modifier.{ STATIC, PUBLIC, ABSTRACT }
import java.net.URL
import xsbti.DependencyContext
import xsbti.DependencyContext._
private[sbt] object Analyze {
def apply[T](newClasses: Seq[File], sources: Seq[File], log: Logger)(analysis: xsbti.AnalysisCallback, loader: ClassLoader, readAPI: (File, Seq[Class[_]]) => Set[String]) {
@ -41,26 +43,26 @@ private[sbt] object Analyze {
for ((source, classFiles) <- sourceToClassFiles) {
val publicInherited = readAPI(source, classFiles.toSeq.flatMap(c => load(c.className, Some("Error reading API from class file"))))
def processDependency(tpe: String, inherited: Boolean) {
def processDependency(tpe: String, context: DependencyContext) {
trapAndLog(log) {
for (url <- Option(loader.getResource(tpe.replace('.', '/') + ClassExt)); file <- urlAsFile(url, log)) {
if (url.getProtocol == "jar")
analysis.binaryDependency(file, tpe, source, inherited)
analysis.binaryDependency(file, tpe, source, context)
else {
assume(url.getProtocol == "file")
productToSource.get(file) match {
case Some(dependsOn) => analysis.sourceDependency(dependsOn, source, inherited)
case None => analysis.binaryDependency(file, tpe, source, inherited)
case Some(dependsOn) => analysis.sourceDependency(dependsOn, source, context)
case None => analysis.binaryDependency(file, tpe, source, context)
}
}
}
}
}
def processDependencies(tpes: Iterable[String], inherited: Boolean): Unit = tpes.foreach(tpe => processDependency(tpe, inherited))
def processDependencies(tpes: Iterable[String], context: DependencyContext): Unit = tpes.foreach(tpe => processDependency(tpe, context))
val notInherited = classFiles.flatMap(_.types).toSet -- publicInherited
processDependencies(notInherited, false)
processDependencies(publicInherited, true)
processDependencies(notInherited, DependencyByMemberRef)
processDependencies(publicInherited, DependencyByInheritance)
}
for (source <- sources filterNot sourceToClassFiles.keySet) {