Merge branch 'feature/inc-track-inherit' into 0.13

This commit is contained in:
Mark Harrah 2013-05-01 19:25:01 -04:00
commit 626038bece
33 changed files with 406 additions and 195 deletions

View File

@ -10,11 +10,18 @@ import collection.mutable
object ClassToAPI
{
def apply(c: Seq[Class[_]]): api.SourceAPI =
def apply(c: Seq[Class[_]]): api.SourceAPI = process(c)._1
// (api, public inherited classes)
def process(c: Seq[Class[_]]): (api.SourceAPI, Set[Class[_]]) =
{
val pkgs = packages(c).map(p => new api.Package(p))
val defs = c.filter(isTopLevel).flatMap(toDefinitions(new mutable.HashMap))
new api.SourceAPI(pkgs.toArray, defs.toArray)
val cmap = emptyClassMap
val defs = c.filter(isTopLevel).flatMap(toDefinitions(cmap))
val source = new api.SourceAPI(pkgs.toArray, defs.toArray)
cmap.lz.foreach(_.get()) // force thunks to ensure all inherited dependencies are recorded
cmap.clear()
(source, cmap.inherited.toSet)
}
// Avoiding implicit allocation.
@ -35,9 +42,13 @@ object ClassToAPI
def isTopLevel(c: Class[_]): Boolean =
c.getEnclosingClass eq null
type ClassMap = mutable.Map[String, Seq[api.ClassLike]]
final class ClassMap private[sbt](private[sbt] val memo: mutable.Map[String, Seq[api.ClassLike]], private[sbt] val inherited: mutable.Set[Class[_]], private[sbt] val lz: mutable.Buffer[xsbti.api.Lazy[_]]) {
def clear() { memo.clear(); inherited.clear(); lz.clear() }
}
def emptyClassMap: ClassMap = new ClassMap(new mutable.HashMap, new mutable.HashSet, new mutable.ListBuffer)
def toDefinitions(cmap: ClassMap)(c: Class[_]): Seq[api.ClassLike] =
cmap.getOrElseUpdate(c.getName, toDefinitions0(c, cmap))
cmap.memo.getOrElseUpdate(c.getName, toDefinitions0(c, cmap))
def toDefinitions0(c: Class[_], cmap: ClassMap): Seq[api.ClassLike] =
{
import api.DefinitionType.{ClassDef, Module, Trait}
@ -48,13 +59,14 @@ object ClassToAPI
val name = c.getName
val tpe = if(Modifier.isInterface(c.getModifiers)) Trait else ClassDef
lazy val (static, instance) = structure(c, enclPkg, cmap)
val cls = new api.ClassLike(tpe, strict(Empty), lzy(instance), emptyStringArray, typeParameters(c.getTypeParameters), name, acc, mods, annots)
val stat = new api.ClassLike(Module, strict(Empty), lzy(static), emptyStringArray, emptyTypeParameterArray, name, acc, mods, annots)
val cls = new api.ClassLike(tpe, strict(Empty), lzy(instance, cmap), emptyStringArray, typeParameters(c.getTypeParameters), name, acc, mods, annots)
val stat = new api.ClassLike(Module, strict(Empty), lzy(static, cmap), emptyStringArray, emptyTypeParameterArray, name, acc, mods, annots)
val defs = cls :: stat :: Nil
cmap(c.getName) = defs
cmap.memo(c.getName) = defs
defs
}
/** Returns the (static structure, instance structure, inherited classes) for `c`. */
def structure(c: Class[_], enclPkg: Option[String], cmap: ClassMap): (api.Structure, api.Structure) =
{
val methods = mergeMap(c, c.getDeclaredMethods, c.getMethods, methodToDef(enclPkg))
@ -62,20 +74,29 @@ object ClassToAPI
val constructors = mergeMap(c, c.getDeclaredConstructors, c.getConstructors, constructorToDef(enclPkg))
val classes = merge[Class[_]](c, c.getDeclaredClasses, c.getClasses, toDefinitions(cmap), (_: Seq[Class[_]]).partition(isStatic), _.getEnclosingClass != c)
val all = (methods ++ fields ++ constructors ++ classes)
val parentTypes = parents(c)
val instanceStructure = new api.Structure(lzy(parentTypes.toArray), lzy(all.declared.toArray), lzy(all.inherited.toArray))
val staticStructure = new api.Structure(lzyEmptyTpeArray, lzy(all.staticDeclared.toArray), lzy(all.staticInherited.toArray))
val parentJavaTypes = allSuperTypes(c)
if(!Modifier.isPrivate(c.getModifiers))
cmap.inherited ++= parentJavaTypes.collect { case c: Class[_] => c }
val parentTypes = types(parentJavaTypes)
val instanceStructure = new api.Structure(lzyS(parentTypes.toArray), lzyS(all.declared.toArray), lzyS(all.inherited.toArray))
val staticStructure = new api.Structure(lzyEmptyTpeArray, lzyS(all.staticDeclared.toArray), lzyS(all.staticInherited.toArray))
(staticStructure, instanceStructure)
}
private[this] def lzyS[T <: AnyRef](t: T): xsbti.api.Lazy[T] = lzy(t)
def lzy[T <: AnyRef](t: => T): xsbti.api.Lazy[T] = xsbti.SafeLazy(t)
private[this] def lzy[T <: AnyRef](t: => T, cmap: ClassMap): xsbti.api.Lazy[T] = {
val s = lzy(t)
cmap.lz += s
s
}
private val emptyStringArray = new Array[String](0)
private val emptyTypeArray = new Array[xsbti.api.Type](0)
private val emptyAnnotationArray = new Array[xsbti.api.Annotation](0)
private val emptyTypeParameterArray = new Array[xsbti.api.TypeParameter](0)
private val emptySimpleTypeArray = new Array[xsbti.api.SimpleType](0)
private val lzyEmptyTpeArray = lzy(emptyTypeArray)
private val lzyEmptyDefArray = lzy(new Array[xsbti.api.Definition](0))
private val lzyEmptyTpeArray = lzyS(emptyTypeArray)
private val lzyEmptyDefArray = lzyS(new Array[xsbti.api.Definition](0))
private def allSuperTypes(t: Type): Seq[Type] =
{
@ -101,6 +122,7 @@ object ClassToAPI
accumulate(t).filterNot(_ == null).distinct
}
@deprecated("No longer used", "0.13.0")
def parents(c: Class[_]): Seq[api.Type] = types(allSuperTypes(c))
def types(ts: Seq[Type]): Array[api.Type] = ts filter (_ ne null) map reference toArray;
def upperBounds(ts: Array[Type]): api.Type =

View File

@ -11,6 +11,7 @@ trait Analysis
{
val stamps: Stamps
val apis: APIs
/** Mappings between sources, classes, and binaries. */
val relations: Relations
val infos: SourceInfos
/** Information about compiler runs accumulated since `clean` command has been run. */
@ -21,9 +22,9 @@ trait Analysis
def copy(stamps: Stamps = stamps, apis: APIs = apis, relations: Relations = relations, infos: SourceInfos = infos,
compilations: Compilations = compilations): Analysis
def addSource(src: File, api: Source, stamp: Stamp, internalDeps: Iterable[File], info: SourceInfo): Analysis
def addSource(src: File, api: Source, stamp: Stamp, directInternal: Iterable[File], inheritedInternal: Iterable[File], info: SourceInfo): Analysis
def addBinaryDep(src: File, dep: File, className: String, stamp: Stamp): Analysis
def addExternalDep(src: File, dep: String, api: Source): Analysis
def addExternalDep(src: File, dep: String, api: Source, inherited: Boolean): Analysis
def addProduct(src: File, product: File, stamp: Stamp, name: String): Analysis
def groupBy[K](f: (File => K)): Map[K, Analysis]
@ -60,8 +61,7 @@ object Analysis
}
}
private class MAnalysis(val stamps: Stamps, val apis: APIs, val relations: Relations, val infos: SourceInfos,
val compilations: Compilations) extends Analysis
private class MAnalysis(val stamps: Stamps, val apis: APIs, val relations: Relations, val infos: SourceInfos, val compilations: Compilations) extends Analysis
{
def ++ (o: Analysis): Analysis = new MAnalysis(stamps ++ o.stamps, apis ++ o.apis, relations ++ o.relations,
infos ++ o.infos, compilations ++ o.compilations)
@ -78,14 +78,14 @@ private class MAnalysis(val stamps: Stamps, val apis: APIs, val relations: Relat
def copy(stamps: Stamps, apis: APIs, relations: Relations, infos: SourceInfos, compilations: Compilations = compilations): Analysis =
new MAnalysis(stamps, apis, relations, infos, compilations)
def addSource(src: File, api: Source, stamp: Stamp, internalDeps: Iterable[File], info: SourceInfo): Analysis =
copy( stamps.markInternalSource(src, stamp), apis.markInternalSource(src, api), relations.addInternalSrcDeps(src, internalDeps), infos.add(src, info) )
def addSource(src: File, api: Source, stamp: Stamp, directInternal: Iterable[File], inheritedInternal: Iterable[File], info: SourceInfo): Analysis =
copy( stamps.markInternalSource(src, stamp), apis.markInternalSource(src, api), relations.addInternalSrcDeps(src, directInternal, inheritedInternal), infos.add(src, info) )
def addBinaryDep(src: File, dep: File, className: String, stamp: Stamp): Analysis =
copy( stamps.markBinary(dep, className, stamp), apis, relations.addBinaryDep(src, dep), infos )
def addExternalDep(src: File, dep: String, depAPI: Source): Analysis =
copy( stamps, apis.markExternalAPI(dep, depAPI), relations.addExternalDep(src, dep), infos )
def addExternalDep(src: File, dep: String, depAPI: Source, inherited: Boolean): Analysis =
copy( stamps, apis.markExternalAPI(dep, depAPI), relations.addExternalDep(src, dep, inherited), infos )
def addProduct(src: File, product: File, stamp: Stamp, name: String): Analysis =
copy( stamps.markProduct(product, stamp), apis, relations.addProduct(src, product, name), infos )

View File

@ -62,12 +62,17 @@ private final class AnalysisCallback(internalMap: File => Option[File], external
private[this] val unreporteds = new HashMap[File, ListBuffer[Problem]]
private[this] val reporteds = new HashMap[File, ListBuffer[Problem]]
private[this] val binaryDeps = new HashMap[File, Set[File]]
// source file to set of generated (class file, class name)
// source file to set of generated (class file, class name)
private[this] val classes = new HashMap[File, Set[(File, String)]]
// generated class file to its source file
// 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]]
private[this] val extSrcDeps = new ListBuffer[(File, String, Source)]
// inherited internal source dependencies
private[this] val inheritedSourceDeps = new HashMap[File, Set[File]]
// external source dependencies:
// (internal source, external source depended on, API of external dependency, true if an inheritance dependency)
private[this] val extSrcDeps = new ListBuffer[(File, String, Source, Boolean)]
private[this] val binaryClassName = new HashMap[File, String]
// source files containing a macro def.
private[this] val macroSources = Set[File]()
@ -83,41 +88,45 @@ private final class AnalysisCallback(internalMap: File => Option[File], external
}
}
def sourceDependency(dependsOn: File, source: File) = if(source != dependsOn) add(sourceDeps, source, dependsOn)
def externalBinaryDependency(binary: File, className: String, source: File)
def sourceDependency(dependsOn: File, source: File, inherited: Boolean) =
if(source != dependsOn) {
add(sourceDeps, source, dependsOn)
if(inherited) add(inheritedSourceDeps, source, dependsOn)
}
def externalBinaryDependency(binary: File, className: String, source: File, inherited: Boolean)
{
binaryClassName.put(binary, className)
add(binaryDeps, source, binary)
}
def externalSourceDependency(triple: (File, String, Source)) = extSrcDeps += triple
def externalSourceDependency(t4: (File, String, Source, Boolean)) = extSrcDeps += t4
def binaryDependency(classFile: File, name: String, source: File) =
def binaryDependency(classFile: File, name: String, source: File, inherited: Boolean) =
internalMap(classFile) match
{
case Some(dependsOn) =>
// dependency is a product of a source not included in this compilation
sourceDependency(dependsOn, source)
sourceDependency(dependsOn, source, inherited)
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)
sourceDependency(dependsOn, source, inherited)
case None =>
externalDependency(classFile, name, source)
externalDependency(classFile, name, source, inherited)
}
}
private[this] def externalDependency(classFile: File, name: String, source: File): Unit =
private[this] def externalDependency(classFile: File, name: String, source: File, inherited: Boolean): Unit =
externalAPI(classFile, name) match
{
case Some(api) =>
// dependency is a product of a source in another project
externalSourceDependency( (source, name, api) )
externalSourceDependency( (source, name, api, inherited) )
case None =>
// dependency is some other binary on the classpath
externalBinaryDependency(classFile, name, source)
externalBinaryDependency(classFile, name, source, inherited)
}
def generatedClass(source: File, module: File, name: String) =
@ -148,10 +157,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, hasMacro)
val info = SourceInfos.makeInfo(getOrNil(reporteds, src), getOrNil(unreporteds, src))
a.addSource(src, s, stamp, sourceDeps.getOrElse(src, Nil: Iterable[File]), info)
val direct = sourceDeps.getOrElse(src, Nil: Iterable[File])
val publicInherited = inheritedSourceDeps.getOrElse(src, Nil: Iterable[File])
a.addSource(src, s, stamp, direct, publicInherited, info)
}
def getOrNil[A,B](m: collection.Map[A, Seq[B]], a: A): Seq[B] = m.get(a).toList.flatten
def addExternals(base: Analysis): Analysis = (base /: extSrcDeps) { case (a, (source, name, api)) => a.addExternalDep(source, name, api) }
def addExternals(base: Analysis): Analysis = (base /: extSrcDeps) { case (a, (source, name, api, inherited)) => a.addExternalDep(source, name, api, inherited) }
def addCompilation(base: Analysis): Analysis = base.copy(compilations = base.compilations.add(compilation))
def addAll[A,B](base: Analysis, m: Map[A, Set[B]])( f: (Analysis, A, B) => Analysis): Analysis =

View File

@ -38,7 +38,10 @@ final case class IncOptions(
object IncOptions {
val Default = IncOptions(
transitiveStep = 2,
// 1. recompile changed sources
// 2(3). recompile direct dependencies and transitive public inheritance dependencies of sources with API changes in 1(2).
// 4. further changes invalidate all dependencies transitively to avoid too many steps
transitiveStep = 3,
recompileAllFraction = 0.5,
relationsDebug = false,
apiDebug = false,

View File

@ -77,7 +77,7 @@ object Incremental
debug("********* Merged: \n" + merged.relations + "\n*********")
val incChanges = changedIncremental(invalidated, previous.apis.internalAPI _, merged.apis.internalAPI _, options)
debug("Changes:\n" + incChanges)
debug("\nChanges:\n" + incChanges)
val transitiveStep = options.transitiveStep
val incInv = invalidateIncremental(merged.relations, incChanges, invalidated, cycleNum >= transitiveStep, log)
cycle(incInv, allSources, emptyChanges, merged, doCompile, classfileManager, cycleNum+1, log, options)
@ -96,10 +96,10 @@ object Incremental
else invalidated
}
// Package objects are fragile: if they depend on an invalidated source, get "class file needed by package is missing" error
// Package objects are fragile: if they inherit from an invalidated source, get "class file needed by package is missing" error
// This might be too conservative: we probably only need package objects for packages of invalidated sources.
private[this] def invalidatedPackageObjects(invalidated: Set[File], relations: Relations): Set[File] =
invalidated flatMap relations.usesInternalSrc filter { _.getName == "package.scala" }
invalidated flatMap relations.publicInherited.internal.reverse filter { _.getName == "package.scala" }
/**
* Accepts the sources that were recompiled during the last step and functions
@ -159,7 +159,7 @@ object Incremental
if(transitive)
transitiveDependencies(dependsOnSrc, changes.modified, log)
else
invalidateStage2(dependsOnSrc, changes.modified, log)
invalidateIntermediate(previous, changes.modified, log)
val dups = invalidateDuplicates(previous)
if(dups.nonEmpty)
@ -176,53 +176,15 @@ object Incremental
if(sources.size > 1) sources else Nil
} toSet;
/** Only invalidates direct source dependencies. It excludes any sources that were recompiled during the previous run.
* Callers may want to augment the returned set with 'modified' or all sources recompiled up to this point. */
def invalidateDirect(dependsOnSrc: File => Set[File], modified: Set[File]): Set[File] =
(modified flatMap dependsOnSrc) -- modified
/** Invalidates transitive source dependencies including `modified`.*/
@tailrec def invalidateTransitive(dependsOnSrc: File => Set[File], modified: Set[File], log: Logger): Set[File] =
{
val newInv = invalidateDirect(dependsOnSrc, modified)
log.debug("\tInvalidated direct: " + newInv)
if(newInv.isEmpty) modified else invalidateTransitive(dependsOnSrc, modified ++ newInv, log)
}
/** Returns the transitive source dependencies of `initial`, excluding the files in `initial` in most cases.
* In three-stage incremental compilation, the `initial` files are the sources from step 2 that had API changes.
* Because strongly connected components (cycles) are included in step 2, the files with API changes shouldn't
* need to be compiled in step 3 if their dependencies haven't changed. If there are new cycles introduced after
* step 2, these can require step 2 sources to be included in step 3 recompilation.
*/
/** Returns the transitive source dependencies of `initial`.
* Because the intermediate steps do not pull in cycles, this result includes the initial files
* if they are part of a cycle containing newly invalidated files . */
def transitiveDependencies(dependsOnSrc: File => Set[File], initial: Set[File], log: Logger): Set[File] =
{
// include any file that depends on included files
def recheck(included: Set[File], process: Set[File], excluded: Set[File]): Set[File] =
{
val newIncludes = (process flatMap dependsOnSrc) intersect excluded
if(newIncludes.isEmpty)
included
else
recheck(included ++ newIncludes, newIncludes, excluded -- newIncludes)
}
val transitiveOnly = transitiveDepsOnly(initial)(dependsOnSrc)
log.debug("Step 3 transitive dependencies:\n\t" + transitiveOnly)
val stage3 = recheck(transitiveOnly, transitiveOnly, initial)
log.debug("Step 3 sources from new step 2 source dependencies:\n\t" + (stage3 -- transitiveOnly))
stage3
}
def invalidateStage2(dependsOnSrc: File => Set[File], initial: Set[File], log: Logger): Set[File] =
{
val initAndImmediate = initial ++ initial.flatMap(dependsOnSrc)
log.debug("Step 2 changed sources and immdediate dependencies:\n\t" + initAndImmediate)
val components = sbt.inc.StronglyConnected(initAndImmediate)(dependsOnSrc)
log.debug("Non-trivial strongly connected components: " + components.filter(_.size > 1).mkString("\n\t", "\n\t", ""))
val inv = components.filter(initAndImmediate.exists).flatten
log.debug("Step 2 invalidated sources:\n\t" + inv)
inv
val transitiveWithInitial = transitiveDeps(initial)(dependsOnSrc)
val transitivePartial = includeInitialCond(initial, transitiveWithInitial, dependsOnSrc, log)
log.debug("Final step, transitive dependencies:\n\t" + transitivePartial)
transitivePartial
}
/** Invalidates sources based on initially detected 'changes' to the sources, products, and dependencies.*/
@ -232,7 +194,7 @@ object Incremental
val srcDirect = srcChanges.removed ++ srcChanges.removed.flatMap(previous.usesInternalSrc) ++ srcChanges.added ++ srcChanges.changed
val byProduct = changes.removedProducts.flatMap(previous.produced)
val byBinaryDep = changes.binaryDeps.flatMap(previous.usesBinary)
val byExtSrcDep = changes.external.modified.flatMap(previous.usesExternal) // ++ scopeInvalidations
val byExtSrcDep = invalidateByExternal(previous, changes.external.modified, log) //changes.external.modified.flatMap(previous.usesExternal) // ++ scopeInvalidations
log.debug(
"\nInitial source changes: \n\tremoved:" + srcChanges.removed + "\n\tadded: " + srcChanges.added + "\n\tmodified: " + srcChanges.changed +
"\nRemoved products: " + changes.removedProducts +
@ -248,6 +210,51 @@ object Incremental
srcDirect ++ byProduct ++ byBinaryDep ++ byExtSrcDep
}
/** Sources invalidated by `external` sources in other projects according to the previous `relations`. */
def invalidateByExternal(relations: Relations, external: Set[String], log: Logger): Set[File] =
{
// Propagate public inheritance dependencies transitively.
// This differs from normal because we need the initial crossing from externals to sources in this project.
val externalInheritedR = relations.publicInherited.external
val byExternalInherited = external flatMap externalInheritedR.reverse
val internalInheritedR = relations.publicInherited.internal
val transitiveInherited = transitiveDeps(byExternalInherited)(internalInheritedR.reverse _)
// Get the direct dependencies of all sources transitively invalidated by inheritance
val directA = transitiveInherited flatMap relations.direct.internal.reverse
// Get the sources that directly depend on externals. This includes non-inheritance dependencies and is not transitive.
val directB = external flatMap relations.direct.external.reverse
transitiveInherited ++ directA ++ directB
}
/** Intermediate invalidation step: steps after the initial invalidation, but before the final transitive invalidation. */
def invalidateIntermediate(relations: Relations, modified: Set[File], log: Logger): Set[File] =
{
def reverse(r: Relations.Source) = r.internal.reverse _
invalidateSources(reverse(relations.direct), reverse(relations.publicInherited), modified, log)
}
/** Invalidates inheritance dependencies, transitively. Then, invalidates direct dependencies. Finally, excludes initial dependencies not
* included in a cycle with newly invalidated sources. */
private[this] def invalidateSources(directDeps: File => Set[File], publicInherited: File => Set[File], initial: Set[File], log: Logger): Set[File] =
{
val transitiveInherited = transitiveDeps(initial)(publicInherited)
log.debug("Invalidated by transitive public inheritance: " + transitiveInherited)
val direct = transitiveInherited flatMap directDeps
log.debug("Invalidated by direct dependency: " + direct)
val all = transitiveInherited ++ direct
includeInitialCond(initial, all, f => directDeps(f) ++ publicInherited(f), log)
}
/** Conditionally include initial sources that are dependencies of newly invalidated sources.
** Initial sources included in this step can be because of a cycle, but not always. */
private[this] def includeInitialCond(initial: Set[File], currentInvalidations: Set[File], allDeps: File => Set[File], log: Logger): Set[File] =
{
val newInv = currentInvalidations -- initial
log.debug("New invalidations:\n\t" + newInv)
val transitiveOfNew = transitiveDeps(newInv)(allDeps)
val initialDependsOnNew = transitiveOfNew & initial
log.debug("Previously invalidated, but (transitively) depend on new invalidations:\n\t" + initialDependsOnNew)
newInv ++ initialDependsOnNew
}
def prune(invalidatedSrcs: Set[File], previous: Analysis): Analysis =
prune(invalidatedSrcs, previous, ClassfileManager.deleteImmediately())
@ -309,7 +316,7 @@ object Incremental
def orEmpty(o: Option[Source]): Source = o getOrElse APIs.emptySource
def orTrue(o: Option[Boolean]): Boolean = o getOrElse true
private[this] def transitiveDepsOnly[T](nodes: Iterable[T])(dependencies: T => Iterable[T]): Set[T] =
private[this] def transitiveDeps[T](nodes: Iterable[T])(dependencies: T => Iterable[T]): Set[T] =
{
val xs = new collection.mutable.HashSet[T]
def all(ns: Iterable[T]): Unit = ns.foreach(visit)
@ -319,7 +326,6 @@ object Incremental
all(dependencies(n))
}
all(nodes)
xs --= nodes
xs.toSet
}

View File

@ -6,55 +6,133 @@ package inc
import xsbti.api.Source
import java.io.File
import Relations.{Source => RSource}
/** Provides mappings between source files, generated classes (products), and binaries.
* Dependencies that are tracked include internal: a dependency on a source in the same compilation group (project),
* external: a dependency on a source in another compilation group (tracked as the name of the class),
* binary: a dependency on a class or jar file not generated by a source file in any tracked compilation group,
* inherited: a dependency that resulted from a public template inheriting,
* direct: any type of dependency, including inheritance. */
trait Relations
{
/** All sources _with at least one product_ . */
def allSources: collection.Set[File]
/** All products associates with sources. */
def allProducts: collection.Set[File]
/** All files that are recorded as a binary dependency of a source file.*/
def allBinaryDeps: collection.Set[File]
/** All files in this compilation group (project) that are recorded as a source dependency of a source file in this group.*/
def allInternalSrcDeps: collection.Set[File]
/** All files in another compilation group (project) that are recorded as a source dependency of a source file in this group.*/
def allExternalDeps: collection.Set[String]
/** Fully qualified names of classes defined in source file `src`. */
def classNames(src: File): Set[String]
/** Names of classes defined in source file `src`. */
def definesClass(name: String): Set[File]
/** The classes that were generated for source file `src`. */
def products(src: File): Set[File]
/** The source files that generated class file `prod`. This is typically a set containing a single file. */
def produced(prod: File): Set[File]
/** The binary dependencies for the source file `src`. */
def binaryDeps(src: File): Set[File]
/** The source files that depend on binary file `dep`. */
def usesBinary(dep: File): Set[File]
/** Internal source dependencies for `src`. This includes both direct and inherited dependencies. */
def internalSrcDeps(src: File): Set[File]
/** Internal source files that depend on internal source `dep`. This includes both direct and inherited dependencies. */
def usesInternalSrc(dep: File): Set[File]
/** External source dependencies that internal source file `src` depends on. This includes both direct and inherited dependencies. */
def externalDeps(src: File): Set[String]
/** Internal source dependencies that depend on external source file `dep`. This includes both direct and inherited dependencies. */
def usesExternal(dep: String): Set[File]
/** Records internal source file `src` as generating class file `prod` with top-level class `name`. */
def addProduct(src: File, prod: File, name: String): Relations
def addExternalDep(src: File, dependsOn: String): Relations
def addInternalSrcDeps(src: File, dependsOn: Iterable[File]): Relations
/** Records internal source file `src` as depending on class `dependsOn` in an external source file.
* If `inherited` is true, this dependency is recorded as coming from a public template in `src` extending something in `dependsOn` (an inheritance dependency).
* Whatever the value of `inherited`, the dependency is also recorded as a direct dependency. */
def addExternalDep(src: File, dependsOn: String, inherited: Boolean): Relations
/** Records internal source file `src` depending on a dependency binary dependency `dependsOn`.*/
def addBinaryDep(src: File, dependsOn: File): Relations
/** Records internal source file `src` as having direct dependencies on internal source files `directDependsOn`
* and inheritance dependencies on `inheritedDependsOn`. Everything in `inheritedDependsOn` must be included in `directDependsOn`;
* this method does not automatically record direct dependencies like `addExternalDep` does.*/
def addInternalSrcDeps(src: File, directDependsOn: Iterable[File], inheritedDependsOn: Iterable[File]): Relations
def ++ (o: Relations): Relations
/** Drops all dependency mappings from `sources`. This will not remove mappings to them (that is, mappings where they are dependencies).*/
def -- (sources: Iterable[File]): Relations
def groupBy[K](f: (File => K)): Map[K, Relations]
/** The relation between internal sources and generated class files. */
def srcProd: Relation[File, File]
/** The dependency relation between internal sources and binaries. */
def binaryDep: Relation[File, File]
/** The dependency relation between internal sources. This includes both direct and inherited dependencies.*/
def internalSrcDep: Relation[File, File]
/** The dependency relation between internal and external sources. This includes both direct and inherited dependencies.*/
def externalDep: Relation[File, String]
/** The dependency relations between sources. These include both direct and inherited dependencies.*/
def direct: RSource
/** The inheritance dependency relations between sources.*/
def publicInherited: RSource
/** The relation between a source file and names of classes generated from it.*/
def classes: Relation[File, String]
}
object Relations
{
/** Tracks internal and external source dependencies for a specific dependency type, such as direct or inherited.*/
final class Source private[sbt](val internal: Relation[File,File], val external: Relation[File,String]) {
def addInternal(source: File, dependsOn: Iterable[File]): Source = new Source(internal + (source, dependsOn), external)
def addExternal(source: File, dependsOn: String): Source = new Source(internal, external + (source, dependsOn))
/** Drops all dependency mappings from `sources`. This will not remove mappings to them (that is, where they are dependencies).*/
def --(sources: Iterable[File]): Source = new Source(internal -- sources, external -- sources)
def ++(o: Source): Source = new Source(internal ++ o.internal, external ++ o.external)
def groupBySource[K](f: File => K): Map[K, Source] = {
val i = internal.groupBy { case (a,b) => f(a) }
val e = external.groupBy { case (a,b) => f(a) }
val pairs = for( k <- i.keySet ++ e.keySet ) yield
(k, new Source( getOrEmpty(i, k), getOrEmpty(e, k) ))
pairs.toMap
}
}
private[sbt] def getOrEmpty[A,B,K](m: Map[K, Relation[A,B]], k: K): Relation[A,B] = m.getOrElse(k, Relation.empty)
private[this] lazy val e = Relation.empty[File, File]
private[this] lazy val es = Relation.empty[File, String]
def empty: Relations = new MRelations(e, e, e, es, es)
def make(srcProd: Relation[File, File], binaryDep: Relation[File, File], internalSrcDep: Relation[File, File], externalDep: Relation[File, String], classes: Relation[File, String]): Relations =
new MRelations(srcProd, binaryDep, internalSrcDep, externalDep, classes)
private[this] lazy val estr = Relation.empty[File, String]
private[this] lazy val es = new Source(e, estr)
def emptySource: Source = es
def empty: Relations = new MRelations(e, e, es, es, estr)
def make(srcProd: Relation[File, File], binaryDep: Relation[File, File], direct: Source, publicInherited: Source, classes: Relation[File, String]): Relations =
new MRelations(srcProd, binaryDep, direct = direct, publicInherited = publicInherited, classes)
def makeSource(internal: Relation[File,File], external: Relation[File,String]): Source = new Source(internal, external)
}
/**
* `srcProd` is a relation between a source file and a product: (source, product).
@ -64,23 +142,27 @@ object Relations
* This only includes dependencies on classes and jars that do not have a corresponding source/API to track instead.
* A class or jar with a corresponding source should only be tracked in one of the source dependency relations.
*
* `internalSrcDeps` is a relation between a source file and a source dependency in the same compilation group.
* Dependencies on sources in other projects belong in external source dependencies.
* `direct` defines relations for dependencies between internal and external source dependencies. It includes all types of
* dependencies, including inheritance.
*
* `externalSrcDeps` is a relation between a source file and a source dependency in another compilation group.
* Dependencies on sources in the same group belong in internal source dependencies.
* `publicInherited` defines relations for internal and external source dependencies, only including dependencies
* introduced by inheritance.
*
* `classes` is a relation between a source file and its generated class names.
*/
private class MRelations(val srcProd: Relation[File, File], val binaryDep: Relation[File, File],
val internalSrcDep: Relation[File, File], val externalDep: Relation[File, String], val classes: Relation[File, String]) extends Relations
// direct should include everything in inherited
val direct: RSource, val publicInherited: RSource, val classes: Relation[File, String]) extends Relations
{
def internalSrcDep: Relation[File, File] = direct.internal
def externalDep: Relation[File, String] = direct.external
def allSources: collection.Set[File] = srcProd._1s
def allProducts: collection.Set[File] = srcProd._2s
def allBinaryDeps: collection.Set[File] = binaryDep._2s
def allInternalSrcDeps: collection.Set[File] = internalSrcDep._2s
def allExternalDeps: collection.Set[String] = externalDep._2s
def allInternalSrcDeps: collection.Set[File] = direct.internal._2s
def allExternalDeps: collection.Set[String] = direct.external._2s
def classNames(src: File): Set[String] = classes.forward(src)
def definesClass(name: String): Set[File] = classes.reverse(name)
@ -91,45 +173,52 @@ private class MRelations(val srcProd: Relation[File, File], val binaryDep: Relat
def binaryDeps(src: File): Set[File] = binaryDep.forward(src)
def usesBinary(dep: File): Set[File] = binaryDep.reverse(dep)
def internalSrcDeps(src: File): Set[File] = internalSrcDep.forward(src)
def usesInternalSrc(dep: File): Set[File] = internalSrcDep.reverse(dep)
def internalSrcDeps(src: File): Set[File] = direct.internal.forward(src)
def usesInternalSrc(dep: File): Set[File] = direct.internal.reverse(dep)
def externalDeps(src: File): Set[String] = externalDep.forward(src)
def usesExternal(dep: String): Set[File] = externalDep.reverse(dep)
def externalDeps(src: File): Set[String] = direct.external.forward(src)
def usesExternal(dep: String): Set[File] = direct.external.reverse(dep)
def addProduct(src: File, prod: File, name: String): Relations =
new MRelations( srcProd + (src, prod), binaryDep, internalSrcDep, externalDep, classes + (src, name) )
new MRelations( srcProd + (src, prod), binaryDep, direct = direct, publicInherited = publicInherited, classes + (src, name) )
def addExternalDep(src: File, dependsOn: String): Relations =
new MRelations( srcProd, binaryDep, internalSrcDep, externalDep + (src, dependsOn), classes )
def addExternalDep(src: File, dependsOn: String, inherited: Boolean): Relations = {
val newI = if(inherited) publicInherited.addExternal(src, dependsOn) else publicInherited
val newD = direct.addExternal(src, dependsOn)
new MRelations( srcProd, binaryDep, direct = newD, publicInherited = newI, classes )
}
def addInternalSrcDeps(src: File, dependsOn: Iterable[File]): Relations =
new MRelations( srcProd, binaryDep, internalSrcDep + (src, dependsOn ), externalDep, classes )
def addInternalSrcDeps(src: File, dependsOn: Iterable[File], inherited: Iterable[File]): Relations = {
val newI = publicInherited.addInternal(src, inherited)
val newD = direct.addInternal(src, dependsOn)
new MRelations( srcProd, binaryDep, direct = newD, publicInherited = newI, classes )
}
def addBinaryDep(src: File, dependsOn: File): Relations =
new MRelations( srcProd, binaryDep + (src, dependsOn), internalSrcDep, externalDep, classes )
new MRelations( srcProd, binaryDep + (src, dependsOn), direct = direct, publicInherited = publicInherited, classes )
def ++ (o: Relations): Relations =
new MRelations(srcProd ++ o.srcProd, binaryDep ++ o.binaryDep, internalSrcDep ++ o.internalSrcDep, externalDep ++ o.externalDep, classes ++ o.classes)
new MRelations(srcProd ++ o.srcProd, binaryDep ++ o.binaryDep, direct = direct ++ o.direct, publicInherited = publicInherited ++ o.publicInherited, classes ++ o.classes)
def -- (sources: Iterable[File]) =
new MRelations(srcProd -- sources, binaryDep -- sources, internalSrcDep -- sources, externalDep -- sources, classes -- sources)
new MRelations(srcProd -- sources, binaryDep -- sources, direct = direct -- sources, publicInherited = publicInherited -- sources, classes -- sources)
def groupBy[K](f: File => K): Map[K, Relations] =
{
type MapRel[T] = Map[K, Relation[File, T]]
def outerJoin(srcProdMap: MapRel[File], binaryDepMap: MapRel[File], internalSrcDepMap: MapRel[File],
externalDepMap: MapRel[String], classesMap: MapRel[String]): Map[K, Relations] =
def outerJoin(srcProdMap: MapRel[File], binaryDepMap: MapRel[File], direct: Map[K, RSource], inherited: Map[K, RSource],
classesMap: MapRel[String]): Map[K, Relations] =
{
def kRelations(k: K): Relations = {
def get[T](m: Map[K, Relation[File, T]]) = m.getOrElse(k, Relation.empty)
new MRelations( get(srcProdMap), get(binaryDepMap), get(internalSrcDepMap), get(externalDepMap), get(classesMap) )
def get[T](m: Map[K, Relation[File, T]]) = Relations.getOrEmpty(m, k)
def getSrc(m: Map[K, RSource]): RSource = m.getOrElse(k, Relations.emptySource)
new MRelations( get(srcProdMap), get(binaryDepMap), getSrc(direct), getSrc(inherited), get(classesMap) )
}
val keys = (srcProdMap.keySet ++ binaryDepMap.keySet ++ internalSrcDepMap.keySet ++ externalDepMap.keySet ++ classesMap.keySet).toList
val keys = (srcProdMap.keySet ++ binaryDepMap.keySet ++ direct.keySet ++ inherited.keySet ++ classesMap.keySet).toList
Map( keys.map( (k: K) => (k, kRelations(k)) ) : _*)
}
def f1[B](item: (File, B)): K = f(item._1)
outerJoin(srcProd.groupBy(f1), binaryDep.groupBy(f1), internalSrcDep.groupBy(f1), externalDep.groupBy(f1), classes.groupBy(f1))
outerJoin(srcProd.groupBy(f1), binaryDep.groupBy(f1), direct.groupBySource(f), publicInherited.groupBySource(f), classes.groupBy(f1))
}

View File

@ -1,51 +0,0 @@
package sbt.inc
// stolen from Josh
object StronglyConnected
{
def apply[N](nodes: Iterable[N])(dependencies: N => Iterable[N]): Set[Set[N]] =
{
val stack = new collection.mutable.Stack[N]
val onStack = new collection.mutable.HashSet[N]
val scc = new collection.mutable.ArrayBuffer[Set[N]]
val index = new collection.mutable.ArrayBuffer[N]
val lowLink = new collection.mutable.HashMap[N, Int]
def tarjanImpl(v: N)
{
index += v
lowLink(v) = index.size-1
stack.push(v)
onStack += v
for(n <- dependencies(v))
{
if( !index.contains(n) )
{
tarjanImpl(n)
lowLink(v) = math.min(lowLink(v), lowLink(n))
}
else if(onStack(n))
lowLink(v) = math.min(lowLink(v), index.indexOf(n))
}
if(lowLink(v) == index.indexOf(v))
{
val components = new collection.mutable.ArrayBuffer[N]
def popLoop()
{
val popped = stack.pop()
onStack -= popped
components.append(popped)
if(popped != v) popLoop()
}
popLoop()
scc.append(components.toSet)
}
}
for(node <- nodes)
if( !index.contains(node) )
tarjanImpl(node)
scc.toSet
}
}

View File

@ -126,7 +126,11 @@ class AggressiveCompile(cacheFile: File)
javac.compile(javaSrcs.toArray, absClasspath.toArray, output, options.javacOptions.toArray, log)
}
def readAPI(source: File, classes: Seq[Class[_]]) { callback.api(source, ClassToAPI(classes)) }
def readAPI(source: File, classes: Seq[Class[_]]): Set[String] = {
val (api, inherits) = ClassToAPI.process(classes)
callback.api(source, api)
inherits.map(_.getName)
}
timed("Java analysis", log) {
for ((classesFinder, oldClasses, srcs) <- memo) {

View File

@ -39,6 +39,7 @@ final class API(val global: CallbackGlobal) extends Compat
def processScalaUnit(unit: CompilationUnit)
{
val sourceFile = unit.source.file.file
currentSourceFile = sourceFile
debug("Traversing " + sourceFile)
val traverser = new TopLevelHandler(sourceFile)
traverser.apply(unit.body)
@ -50,6 +51,10 @@ final class API(val global: CallbackGlobal) extends Compat
}
}
// Tracks the source file associated with the CompilationUnit currently being processed by the API phase.
// This is used when recording inheritance dependencies.
private[this] var currentSourceFile: File = _
// this cache reduces duplicate work both here and when persisting
// caches on other structures had minimal effect on time and cache size
// (tried: Definition, Modifier, Path, Id, String)
@ -237,8 +242,18 @@ final class API(val global: CallbackGlobal) extends Compat
mkStructure(s, baseTypes, ds, is)
}
private def mkStructure(s: Symbol, bases: List[Type], declared: List[Symbol], inherited: List[Symbol]): xsbti.api.Structure =
// If true, this template is publicly visible and should be processed as a public inheritance dependency.
// Local classes and local refinements will never be traversed by the api phase, so we don't need to check for that.
private[this] def isPublicStructure(s: Symbol): Boolean =
s.isStructuralRefinement ||
// do not consider templates that are private[this] or private
!(s.isPrivate && (s.privateWithin == NoSymbol || s.isLocal))
private def mkStructure(s: Symbol, bases: List[Type], declared: List[Symbol], inherited: List[Symbol]): xsbti.api.Structure = {
if(isPublicStructure(s))
addInheritedDependencies(currentSourceFile, bases.map(_.dealias.typeSymbol))
new xsbti.api.Structure(lzy(types(s, bases)), lzy(processDefinitions(s, declared)), lzy(processDefinitions(s, inherited)))
}
private def processDefinitions(in: Symbol, defs: List[Symbol]): Array[xsbti.api.Definition] =
sort(defs.toArray).flatMap( (d: Symbol) => definition(in, d))
private[this] def sort(defs: Array[Symbol]): Array[Symbol] = {

View File

@ -33,9 +33,11 @@ final class Analyzer(val global: CallbackGlobal) extends Compat
// build dependencies structure
val sourceFile = unit.source.file.file
callback.beginSource(sourceFile)
for(on <- unit.depends)
for(on <- unit.depends) processDependency(on, inherited=false)
for(on <- inheritedDependencies.getOrElse(sourceFile, Nil: Iterable[Symbol])) processDependency(on, inherited=true)
def processDependency(on: Symbol, inherited: Boolean)
{
def binaryDependency(file: File, className: String) = callback.binaryDependency(file, className, sourceFile)
def binaryDependency(file: File, className: String) = callback.binaryDependency(file, className, sourceFile, inherited)
val onSource = on.sourceFile
if(onSource == null)
{
@ -53,7 +55,7 @@ final class Analyzer(val global: CallbackGlobal) extends Compat
}
}
else
callback.sourceDependency(onSource.file, sourceFile)
callback.sourceDependency(onSource.file, sourceFile, inherited)
}
// build list of generated classes

View File

@ -42,6 +42,11 @@ sealed abstract class CallbackGlobal(settings: Settings, reporter: reporters.Rep
case multi: MultipleOutput => multi.outputGroups.toStream map (_.outputDirectory)
}
}
// Map source files to public inherited dependencies. These dependencies are tracked as the symbol for the dealiased base class.
val inheritedDependencies = new mutable.HashMap[File, mutable.Set[Symbol]]
def addInheritedDependencies(file: File, deps: Iterable[Symbol]) {
inheritedDependencies.getOrElseUpdate(file, new mutable.HashSet) ++= deps
}
}
class InterfaceCompileFailed(val arguments: Array[String], val problems: Array[Problem], override val toString: String) extends xsbti.CompileFailed

View File

@ -13,6 +13,7 @@ package inc
import DefaultProtocol._
import DefaultProtocol.tuple2Format
import Logger.{m2o, position, problem}
import Relations.{Source => RSource}
object AnalysisFormats
{
@ -97,8 +98,11 @@ object AnalysisFormats
implicit def apisFormat(implicit internalF: Format[Map[File, Source]], externalF: Format[Map[String, Source]]): Format[APIs] =
asProduct2( APIs.apply _)( as => (as.internal, as.external) )(internalF, externalF)
implicit def relationsFormat(implicit prodF: Format[RFF], binF: Format[RFF], intF: Format[RFF], extF: Format[RFS], csF: Format[RFS]): Format[Relations] =
asProduct5[Relations, RFF, RFF, RFF, RFS, RFS]( (a,b,c,d,e) => Relations.make(a,b,c,d,e) )( rs => (rs.srcProd, rs.binaryDep, rs.internalSrcDep, rs.externalDep, rs.classes) )(prodF, binF, intF, extF, csF)
implicit def relationsFormat(implicit prodF: Format[RFF], binF: Format[RFF], directF: Format[RSource], inheritedF: Format[RSource], csF: Format[RFS]): Format[Relations] =
asProduct5[Relations, RFF, RFF, RSource, RSource, RFS]( (a,b,c,d,e) => Relations.make(a,b,c,d,e) )( rs => (rs.srcProd, rs.binaryDep, rs.direct, rs.publicInherited, rs.classes) )(prodF, binF, directF, inheritedF, csF)
implicit def relationsSourceFormat(implicit internalFormat: Format[Relation[File, File]], externalFormat: Format[Relation[File,String]]): Format[RSource] =
asProduct2[RSource, RFF, RFS]( (a, b) => Relations.makeSource(a,b))( rs => (rs.internal, rs.external))
implicit def relationFormat[A,B](implicit af: Format[Map[A, Set[B]]], bf: Format[Map[B, Set[A]]]): Format[Relation[A,B]] =
asProduct2[Relation[A,B], Map[A, Set[B]], Map[B, Set[A]]]( Relation.make _ )( r => (r.forwardMap, r.reverseMap) )(af, bf)

View File

@ -12,11 +12,15 @@ public interface AnalysisCallback
/** 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.*/
public void sourceDependency(File dependsOn, File source);
* 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. */
public void sourceDependency(File dependsOn, 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>. */
public void binaryDependency(File binary, String name, File source);
* 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. */
public void binaryDependency(File binary, String name, File source, boolean publicInherited);
/** 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

@ -7,15 +7,15 @@ class TestCallback extends AnalysisCallback
{
val beganSources = new ArrayBuffer[File]
val endedSources = new ArrayBuffer[File]
val sourceDependencies = new ArrayBuffer[(File, File)]
val binaryDependencies = new ArrayBuffer[(File, String, File)]
val sourceDependencies = new ArrayBuffer[(File, File, Boolean)]
val binaryDependencies = new ArrayBuffer[(File, String, File, Boolean)]
val products = new ArrayBuffer[(File, File, String)]
val apis = new ArrayBuffer[(File, xsbti.api.SourceAPI)]
def beginSource(source: File) { beganSources += source }
def sourceDependency(dependsOn: File, source: File) { sourceDependencies += ((dependsOn, source)) }
def binaryDependency(binary: File, name: String, source: File) { binaryDependencies += ((binary, name, source)) }
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 generatedClass(source: File, module: File, name: String) { products += ((source, module, name)) }
def endSource(source: File) { endedSources += source }

View File

@ -0,0 +1,4 @@
// T is a type constructor [x]C
// C extends D
// E verifies the core type gets pulled out
trait A extends B.T[Int] with (E[Int] @unchecked)

View File

@ -0,0 +1,19 @@
object B {
type T[x] = C
}
class B {
// not public, so this shouldn't be tracked as an inherited dependency
private[this] class X extends D with E[Int]
def x(i: Int) {
// not public, not an inherited dependency
trait Y extends D
}
def y(j: Int) {
// not public
val w: D { def length: Int } = ???
()
}
}

View File

@ -0,0 +1 @@
trait C extends D

View File

@ -0,0 +1 @@
trait D extends G.P

View File

@ -0,0 +1 @@
trait E[T]

View File

@ -0,0 +1,3 @@
class F {
def q: C { def length: Int } = ???
}

View File

@ -0,0 +1 @@
object G { trait P extends J }

View File

@ -0,0 +1 @@
class J

View File

@ -0,0 +1,27 @@
lazy val verifyDeps = taskKey[Unit]("verify inherited dependencies are properly extracted")
verifyDeps := {
val a = compile.in(Compile).value
same(a.relations.publicInherited.internal.forwardMap, expectedDeps.forwardMap)
}
lazy val expected = Seq(
"A" -> Seq("C", "D", "E", "G", "J"),
"B" -> Seq(),
"C" -> Seq("D", "G", "J"),
"D" -> Seq("G", "J"),
"E" -> Seq(),
"F" -> Seq("C", "D", "G", "J"),
"G" -> Seq("J"),
"J" -> Seq()
)
lazy val pairs =
expected.map { case (from,tos) =>
(toFile(from), tos.map(toFile))
}
lazy val expectedDeps = (Relation.empty[File,File] /: pairs) { case (r, (x,ys)) => r + (x,ys) }
def toFile(s: String) = file(s + ".scala").getAbsoluteFile
def same[T](x: T, y: T) {
assert(x == y, s"\nActual: $x, \nExpected: $y")
}

View File

@ -0,0 +1 @@
> verifyDeps

View File

@ -0,0 +1,3 @@
class A {
def x = 3
}

View File

@ -0,0 +1 @@
class B extends A

View File

@ -0,0 +1 @@
class C extends B

View File

@ -0,0 +1,4 @@
object D {
val c = new C
def x: String = c.x.toString
}

View File

@ -0,0 +1,3 @@
object E extends App {
assert(D.x == "3")
}

View File

@ -0,0 +1,10 @@
import complete.DefaultParsers._
val checkIterations = inputKey[Unit]("Verifies the accumlated number of iterations of incremental compilation.")
checkIterations := {
val expected: Int = (Space ~> NatBasic).parsed
val actual: Int = (compile in Compile).value.compilations.allCompilations.size
assert(expected == actual, s"Expected $expected compilations, got $actual")
}

View File

@ -0,0 +1,3 @@
class A {
def x = "3"
}

View File

@ -0,0 +1,9 @@
# 1 iteration from initial full compile
> run
$ copy-file changes/A2.scala A.scala
# 1 iteration for the initial changes
# 1 iteration to recompile all descendents and direct dependencies
# no further iteration, because APIs of directs don't change
> run
> checkIterations 3

View File

@ -15,7 +15,7 @@ import java.net.URL
private[sbt] object Analyze
{
def apply[T](newClasses: Seq[File], sources: Seq[File], log: Logger)(analysis: xsbti.AnalysisCallback, loader: ClassLoader, readAPI: (File,Seq[Class[_]]) => Unit)
def apply[T](newClasses: Seq[File], sources: Seq[File], log: Logger)(analysis: xsbti.AnalysisCallback, loader: ClassLoader, readAPI: (File,Seq[Class[_]]) => Set[String])
{
val sourceMap = sources.toSet[File].groupBy(_.getName)
@ -42,29 +42,33 @@ private[sbt] object Analyze
// get class to class dependencies and map back to source to class dependencies
for( (source, classFiles) <- sourceToClassFiles )
{
def processDependency(tpe: String)
val publicInherited = readAPI(source, classFiles.toSeq.flatMap(c => load(c.className, Some("Error reading API from class file") )))
def processDependency(tpe: String, inherited: Boolean)
{
trapAndLog(log)
{
for (url <- Option(loader.getResource(tpe.replace('.', '/') + ClassExt)); file <- urlAsFile(url, log))
{
if(url.getProtocol == "jar")
analysis.binaryDependency(file, tpe, source)
analysis.binaryDependency(file, tpe, source, inherited)
else
{
assume(url.getProtocol == "file")
productToSource.get(file) match
{
case Some(dependsOn) => analysis.sourceDependency(dependsOn, source)
case None => analysis.binaryDependency(file, tpe, source)
case Some(dependsOn) => analysis.sourceDependency(dependsOn, source, inherited)
case None => analysis.binaryDependency(file, tpe, source, inherited)
}
}
}
}
}
def processDependencies(tpes: Iterable[String], inherited: Boolean): Unit = tpes.foreach(tpe => processDependency(tpe, inherited))
classFiles.flatMap(_.types).toSet.foreach(processDependency)
readAPI(source, classFiles.toSeq.flatMap(c => load(c.className, Some("Error reading API from class file") )))
val notInherited = classFiles.flatMap(_.types).toSet -- publicInherited
processDependencies(notInherited, false)
processDependencies(publicInherited, true)
analysis.endSource(source)
}