mirror of https://github.com/sbt/sbt.git
Merge pull request #1002 from gkossakowski/deps-by-treewalking
Extract dependencies by tree walking
This commit is contained in:
commit
4b0123f9ec
|
|
@ -41,7 +41,7 @@ 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, 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, inherited: Boolean): Analysis
|
||||
|
|
@ -59,6 +59,10 @@ object Analysis
|
|||
|
||||
/** Merge multiple analysis objects into one. Deps will be internalized as needed. */
|
||||
def merge(analyses: Traversable[Analysis]): Analysis = {
|
||||
if (analyses.exists(_.relations.memberRefAndInheritanceDeps))
|
||||
throw new IllegalArgumentException("Merging of Analyses that have" +
|
||||
"`relations.memberRefAndInheritanceDeps` set to `true` is not supported.")
|
||||
|
||||
// Merge the Relations, internalizing deps as needed.
|
||||
val mergedSrcProd = Relation.merge(analyses map { _.relations.srcProd })
|
||||
val mergedBinaryDep = Relation.merge(analyses map { _.relations.binaryDep })
|
||||
|
|
@ -156,6 +160,10 @@ private class MAnalysis(val stamps: Stamps, val apis: APIs, val relations: Relat
|
|||
copy( stamps.markProduct(product, stamp), apis, relations.addProduct(src, product, name), infos )
|
||||
|
||||
def groupBy[K](discriminator: File => K): Map[K, Analysis] = {
|
||||
if (relations.memberRefAndInheritanceDeps)
|
||||
throw new UnsupportedOperationException("Grouping of Analyses that have" +
|
||||
"`relations.memberRefAndInheritanceDeps` set to `true` is not supported.")
|
||||
|
||||
def discriminator1(x: (File, _)) = discriminator(x._1) // Apply the discriminator to the first coordinate.
|
||||
|
||||
val kSrcProd = relations.srcProd.groupBy(discriminator1)
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ private final class AnalysisCallback(internalMap: File => Option[File], external
|
|||
}
|
||||
|
||||
def sourceDependency(dependsOn: File, source: File, inherited: Boolean) =
|
||||
if(source != dependsOn) {
|
||||
{
|
||||
add(sourceDeps, source, dependsOn)
|
||||
if(inherited) add(inheritedSourceDeps, source, dependsOn)
|
||||
}
|
||||
|
|
@ -151,6 +151,8 @@ private final class AnalysisCallback(internalMap: File => Option[File], external
|
|||
apis(sourceFile) = (HashAPI(source), savedSource)
|
||||
}
|
||||
|
||||
def memberRefAndInheritanceDeps: Boolean = false // TODO: define the flag in IncOptions which controls this
|
||||
|
||||
def get: Analysis = addCompilation( addExternals( addBinaries( addProducts( addSources(Analysis.Empty) ) ) ) )
|
||||
def addProducts(base: Analysis): Analysis = addAll(base, classes) { case (a, src, (prod, name)) => a.addProduct(src, prod, current product prod, name ) }
|
||||
def addBinaries(base: Analysis): Analysis = addAll(base, binaryDeps)( (a, src, bin) => a.addBinaryDep(src, bin, binaryClassName(bin), current binary bin) )
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ package inc
|
|||
|
||||
import java.io.File
|
||||
import Relations.Source
|
||||
import Relations.SourceDependencies
|
||||
|
||||
|
||||
/** Provides mappings between source files, generated classes (products), and binaries.
|
||||
|
|
@ -18,7 +19,7 @@ trait Relations
|
|||
{
|
||||
/** All sources _with at least one product_ . */
|
||||
def allSources: collection.Set[File]
|
||||
|
||||
|
||||
/** All products associated with sources. */
|
||||
def allProducts: collection.Set[File]
|
||||
|
||||
|
|
@ -36,12 +37,12 @@ trait Relations
|
|||
|
||||
/** Source files that generated a class with the given fully qualified `name`. This is typically a set containing a single file. */
|
||||
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`. */
|
||||
|
|
@ -51,12 +52,12 @@ trait Relations
|
|||
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
|
||||
|
||||
|
|
@ -82,10 +83,10 @@ trait Relations
|
|||
@deprecated("OK to remove in 0.14", "0.13.1")
|
||||
def groupBy[K](f: (File => K)): Map[K, Relations]
|
||||
|
||||
/** The relation between internal sources and generated class files. */
|
||||
/** The relation between internal sources and generated class files. */
|
||||
def srcProd: Relation[File, File]
|
||||
|
||||
/** The dependency relation between internal sources and binaries. */
|
||||
/** 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.*/
|
||||
|
|
@ -94,6 +95,48 @@ trait Relations
|
|||
/** The dependency relation between internal and external sources. This includes both direct and inherited dependencies.*/
|
||||
def externalDep: Relation[File, String]
|
||||
|
||||
/**
|
||||
* The source dependency relation between source files introduced by member reference.
|
||||
*
|
||||
* NOTE: All inheritance dependencies are included in this relation because in order to
|
||||
* inherit from a member you have to refer to it. If you check documentation of `inheritance`
|
||||
* you'll see that there's small oddity related to traits being the first parent of a
|
||||
* class/trait that results in additional parents being introduced due to normalization.
|
||||
* This relation properly accounts for that so the invariant that `memberRef` is a superset
|
||||
* of `inheritance` is preserved.
|
||||
*/
|
||||
def memberRef: SourceDependencies
|
||||
|
||||
/**
|
||||
* The source dependency relation between source files introduced by inheritance.
|
||||
* The dependency by inheritance is introduced when a template (class or trait) mentions
|
||||
* a given type in a parent position.
|
||||
*
|
||||
* NOTE: Due to an oddity in how Scala's type checker works there's one unexpected dependency
|
||||
* on a class being introduced. An example illustrates the best the problem. Let's consider
|
||||
* the following structure:
|
||||
*
|
||||
* trait A extends B
|
||||
* trait B extends C
|
||||
* trait C extends D
|
||||
* class D
|
||||
*
|
||||
* We are interested in dependencies by inheritance of `A`. One would expect it to be just `B`
|
||||
* but the answer is `B` and `D`. The reason is because Scala's type checker performs a certain
|
||||
* normalization so the first parent of a type is a class. Therefore the example above is normalized
|
||||
* to the following form:
|
||||
*
|
||||
* trait A extends D with B
|
||||
* trait B extends D with C
|
||||
* trait C extends D
|
||||
* class D
|
||||
*
|
||||
* Therefore if you inherit from a trait you'll get an additional dependency on a class that is
|
||||
* resolved transitively. You should not rely on this behavior, though.
|
||||
*
|
||||
*/
|
||||
def inheritance: SourceDependencies
|
||||
|
||||
/** The dependency relations between sources. These include both direct and inherited dependencies.*/
|
||||
def direct: Source
|
||||
|
||||
|
|
@ -102,6 +145,19 @@ trait Relations
|
|||
|
||||
/** The relation between a source file and the fully qualified names of classes generated from it.*/
|
||||
def classes: Relation[File, String]
|
||||
|
||||
/**
|
||||
* Flag which indicates whether the new style (based on `memberRef` and `inheritance` source dependencies)
|
||||
* of dependency tracking is enabled. When this flag is enabled access to `direct` and `publicInherited`
|
||||
* relations is illegal and will cause runtime exception being thrown.
|
||||
*
|
||||
* Conversely, when `memberRefAndInheritanceDeps` flag is disabled access to `memberRef` and `inheritance`
|
||||
* relations is illegal and will cause runtime exception being thrown.
|
||||
*
|
||||
* The name of this flag is ugly but it's private to incremental compiler and it's temporary measure during
|
||||
* our migration to the new dependency tracking.
|
||||
*/
|
||||
private[inc] def memberRefAndInheritanceDeps: Boolean
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -133,6 +189,22 @@ object Relations
|
|||
override def hashCode = (internal, external).hashCode
|
||||
}
|
||||
|
||||
/** Tracks internal and external source dependencies for a specific dependency type, such as direct or inherited.*/
|
||||
private[inc] final class SourceDependencies(val internal: Relation[File,File], val external: Relation[File,String]) {
|
||||
def addInternal(source: File, dependsOn: Iterable[File]): SourceDependencies = new SourceDependencies(internal + (source, dependsOn), external)
|
||||
def addExternal(source: File, dependsOn: String): SourceDependencies = new SourceDependencies(internal, external + (source, dependsOn))
|
||||
/** Drops all dependency mappings from `sources`. Acts naively, i.e., doesn't externalize internal deps on removed files.*/
|
||||
def --(sources: Iterable[File]): SourceDependencies = new SourceDependencies(internal -- sources, external -- sources)
|
||||
def ++(o: SourceDependencies): SourceDependencies = new SourceDependencies(internal ++ o.internal, external ++ o.external)
|
||||
|
||||
override def equals(other: Any) = other match {
|
||||
case o: SourceDependencies => internal == o.internal && external == o.external
|
||||
case _ => false
|
||||
}
|
||||
|
||||
override def hashCode = (internal, external).hashCode
|
||||
}
|
||||
|
||||
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]
|
||||
|
|
@ -140,101 +212,181 @@ object Relations
|
|||
private[this] lazy val es = new Source(e, estr)
|
||||
|
||||
def emptySource: Source = es
|
||||
def empty: Relations = new MRelations(e, e, es, es, estr)
|
||||
private[inc] lazy val emptySourceDependencies: SourceDependencies = new SourceDependencies(e, estr)
|
||||
def empty: Relations = empty(memberRefAndInheritanceDeps = false)
|
||||
def empty(memberRefAndInheritanceDeps: Boolean): Relations =
|
||||
if (memberRefAndInheritanceDeps)
|
||||
new MRelationsMemberRefAndInheritance(e, e, emptySourceDependencies, emptySourceDependencies, estr)
|
||||
else
|
||||
new MRelationsDirectAndPublicInherited(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).
|
||||
* Note that some source files may not have a product and will not be included in this relation.
|
||||
*
|
||||
* `binaryDeps` is a relation between a source file and a binary dependency: (source, binary dependency).
|
||||
* 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.
|
||||
*
|
||||
* `direct` defines relations for dependencies between internal and external source dependencies. It includes all types of
|
||||
* dependencies, including inheritance.
|
||||
*
|
||||
* `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 fully-qualified class names.
|
||||
*/
|
||||
private class MRelations(val srcProd: Relation[File, File], val binaryDep: Relation[File, File],
|
||||
// direct should include everything in inherited
|
||||
val direct: Source, val publicInherited: Source, val classes: Relation[File, String]) extends Relations
|
||||
{
|
||||
def internalSrcDep: Relation[File, File] = direct.internal
|
||||
def externalDep: Relation[File, String] = direct.external
|
||||
new MRelationsDirectAndPublicInherited(srcProd, binaryDep, direct = direct, publicInherited = publicInherited, classes)
|
||||
|
||||
private[inc] def make(srcProd: Relation[File, File], binaryDep: Relation[File, File],
|
||||
memberRef: SourceDependencies, inheritance: SourceDependencies, classes: Relation[File, String]): Relations =
|
||||
new MRelationsMemberRefAndInheritance(srcProd, binaryDep, memberRef = memberRef, inheritance = inheritance, classes)
|
||||
def makeSource(internal: Relation[File,File], external: Relation[File,String]): Source = new Source(internal, external)
|
||||
private[inc] def makeSourceDependencies(internal: Relation[File,File], external: Relation[File,String]): SourceDependencies = new SourceDependencies(internal, external)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* An abstract class that contains common functionality inherited by two implementations of Relations trait.
|
||||
*
|
||||
* A little note why we have two different implementations of Relations trait. This is needed for the time
|
||||
* being when we are slowly migrating to the new invalidation algorithm called "name hashing" which requires
|
||||
* some subtle changes to dependency tracking. For some time we plan to keep both algorithms side-by-side
|
||||
* and have a runtime switch which allows to pick one. So we need logic for both old and new dependency
|
||||
* tracking to be available. That's exactly what two subclasses of MRelationsCommon implement. Once name
|
||||
* hashing is proven to be stable and reliable we'll phase out the old algorithm and the old dependency tracking
|
||||
* logic.
|
||||
*
|
||||
* `srcProd` is a relation between a source file and a product: (source, product).
|
||||
* Note that some source files may not have a product and will not be included in this relation.
|
||||
*
|
||||
* `binaryDeps` is a relation between a source file and a binary dependency: (source, binary dependency).
|
||||
* 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.
|
||||
*
|
||||
* `classes` is a relation between a source file and its generated fully-qualified class names.
|
||||
*/
|
||||
private abstract class MRelationsCommon(val srcProd: Relation[File, File], val binaryDep: Relation[File, File],
|
||||
val classes: Relation[File, String]) extends Relations
|
||||
{
|
||||
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] = direct.internal._2s
|
||||
def allExternalDeps: collection.Set[String] = direct.external._2s
|
||||
def allInternalSrcDeps: collection.Set[File] = internalSrcDep._2s
|
||||
def allExternalDeps: collection.Set[String] = externalDep._2s
|
||||
|
||||
def classNames(src: File): Set[String] = classes.forward(src)
|
||||
def definesClass(name: String): Set[File] = classes.reverse(name)
|
||||
|
||||
|
||||
def products(src: File): Set[File] = srcProd.forward(src)
|
||||
def produced(prod: File): Set[File] = srcProd.reverse(prod)
|
||||
|
||||
|
||||
def binaryDeps(src: File): Set[File] = binaryDep.forward(src)
|
||||
def usesBinary(dep: File): Set[File] = binaryDep.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] = direct.external.forward(src)
|
||||
def usesExternal(dep: String): Set[File] = direct.external.reverse(dep)
|
||||
def internalSrcDeps(src: File): Set[File] = internalSrcDep.forward(src)
|
||||
def usesInternalSrc(dep: File): Set[File] = internalSrcDep.reverse(dep)
|
||||
|
||||
def externalDeps(src: File): Set[String] = externalDep.forward(src)
|
||||
def usesExternal(dep: String): Set[File] = externalDep.reverse(dep)
|
||||
|
||||
/** Making large Relations a little readable. */
|
||||
private val userDir = sys.props("user.dir").stripSuffix("/") + "/"
|
||||
private def nocwd(s: String) = s stripPrefix userDir
|
||||
private def line_s(kv: (Any, Any)) = " " + nocwd("" + kv._1) + " -> " + nocwd("" + kv._2) + "\n"
|
||||
private def relation_s(r: Relation[_, _]) = (
|
||||
if (r.forwardMap.isEmpty) "Relation [ ]"
|
||||
else (r.all.toSeq map line_s sorted) mkString ("Relation [\n", "", "]")
|
||||
)
|
||||
override def toString = (
|
||||
"""
|
||||
|Relations:
|
||||
| products: %s
|
||||
| bin deps: %s
|
||||
| src deps: %s
|
||||
| ext deps: %s
|
||||
| class names: %s
|
||||
""".trim.stripMargin.format(List(srcProd, binaryDep, internalSrcDep, externalDep, classes) map relation_s : _*)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This class implements Relations trait with support for tracking of `direct` and `publicInherited` source
|
||||
* dependencies. Therefore this class preserves the "old" (from sbt 0.13.0) dependency tracking logic and it's
|
||||
* a default implementation.
|
||||
*
|
||||
* `direct` defines relations for dependencies between internal and external source dependencies. It includes all types of
|
||||
* dependencies, including inheritance.
|
||||
*
|
||||
* `publicInherited` defines relations for internal and external source dependencies, only including dependencies
|
||||
* introduced by inheritance.
|
||||
*
|
||||
*/
|
||||
private class MRelationsDirectAndPublicInherited(srcProd: Relation[File, File], binaryDep: Relation[File, File],
|
||||
// direct should include everything in inherited
|
||||
val direct: Source, val publicInherited: Source,
|
||||
classes: Relation[File, String]) extends MRelationsCommon(srcProd, binaryDep, classes)
|
||||
{
|
||||
def internalSrcDep: Relation[File, File] = direct.internal
|
||||
def externalDep: Relation[File, String] = direct.external
|
||||
|
||||
def memberRefAndInheritanceDeps: Boolean = false
|
||||
|
||||
def memberRef: SourceDependencies =
|
||||
throw new UnsupportedOperationException("The `memberRef` source dependencies relation is not supported " +
|
||||
"when `memberRefAndInheritanceDeps` is disabled. Do you have name hashing algorithm disabled?")
|
||||
def inheritance: SourceDependencies =
|
||||
throw new UnsupportedOperationException("The `memberRef` source dependencies relation is not supported " +
|
||||
"when `memberRefAndInheritanceDeps` is disabled. Do you have name hashing algorithm disabled?")
|
||||
|
||||
def addProduct(src: File, prod: File, name: String): Relations =
|
||||
new MRelations( srcProd + (src, prod), binaryDep, direct = direct, publicInherited = publicInherited, classes + (src, name) )
|
||||
new MRelationsDirectAndPublicInherited(srcProd + (src, prod), binaryDep, direct = direct,
|
||||
publicInherited = publicInherited, classes + (src, name))
|
||||
|
||||
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 )
|
||||
new MRelationsDirectAndPublicInherited( srcProd, binaryDep, direct = newD, publicInherited = newI, 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 addInternalSrcDeps(src: File, dependsOn: Iterable[File], inherited: Iterable[File]): Relations =
|
||||
{
|
||||
val newI = publicInherited.addInternal(src, inherited)
|
||||
val newD = direct.addInternal(src, dependsOn)
|
||||
new MRelationsDirectAndPublicInherited( srcProd, binaryDep, direct = newD, publicInherited = newI, classes)
|
||||
}
|
||||
|
||||
def addBinaryDep(src: File, dependsOn: File): Relations =
|
||||
new MRelations( srcProd, binaryDep + (src, dependsOn), direct = direct, publicInherited = publicInherited, classes )
|
||||
def ++ (o: Relations): Relations =
|
||||
new MRelations(srcProd ++ o.srcProd, binaryDep ++ o.binaryDep, direct = direct ++ o.direct, publicInherited = publicInherited ++ o.publicInherited, classes ++ o.classes)
|
||||
new MRelationsDirectAndPublicInherited( srcProd, binaryDep + (src, dependsOn), direct = direct,
|
||||
publicInherited = publicInherited, classes)
|
||||
|
||||
def ++ (o: Relations): Relations = {
|
||||
if (memberRefAndInheritanceDeps != o.memberRefAndInheritanceDeps)
|
||||
throw new UnsupportedOperationException("The `++` operation is not supported for relations " +
|
||||
"with different values of `memberRefAndInheritanceDeps` flag.")
|
||||
new MRelationsDirectAndPublicInherited(srcProd ++ o.srcProd, binaryDep ++ o.binaryDep, direct ++ o.direct,
|
||||
publicInherited ++ o.publicInherited, classes ++ o.classes)
|
||||
}
|
||||
def -- (sources: Iterable[File]) =
|
||||
new MRelations(srcProd -- sources, binaryDep -- sources, direct = direct -- sources, publicInherited = publicInherited -- sources, classes -- sources)
|
||||
new MRelationsDirectAndPublicInherited(srcProd -- sources, binaryDep -- sources, direct = direct -- sources,
|
||||
publicInherited = publicInherited -- sources, classes -- sources)
|
||||
|
||||
@deprecated("Broken implementation. OK to remove in 0.14", "0.13.1")
|
||||
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], direct: Map[K, Source], inherited: Map[K, Source],
|
||||
classesMap: MapRel[String]): Map[K, Relations] =
|
||||
def outerJoin(srcProdMap: MapRel[File], binaryDepMap: MapRel[File], direct: Map[K, Source],
|
||||
inherited: Map[K, Source], classesMap: MapRel[String]): Map[K, Relations] =
|
||||
{
|
||||
def kRelations(k: K): Relations = {
|
||||
def get[T](m: Map[K, Relation[File, T]]) = Relations.getOrEmpty(m, k)
|
||||
def getSrc(m: Map[K, Source]): Source = m.getOrElse(k, Relations.emptySource)
|
||||
new MRelations( get(srcProdMap), get(binaryDepMap), getSrc(direct), getSrc(inherited), get(classesMap) )
|
||||
def getSrcDeps(m: Map[K, SourceDependencies]): SourceDependencies =
|
||||
m.getOrElse(k, Relations.emptySourceDependencies)
|
||||
new MRelationsDirectAndPublicInherited( get(srcProdMap), get(binaryDepMap), getSrc(direct), getSrc(inherited),
|
||||
get(classesMap))
|
||||
}
|
||||
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), direct.groupBySource(f), publicInherited.groupBySource(f), classes.groupBy(f1))
|
||||
}
|
||||
|
||||
outerJoin(srcProd.groupBy(f1), binaryDep.groupBy(f1), direct.groupBySource(f),
|
||||
publicInherited.groupBySource(f), classes.groupBy(f1))
|
||||
}
|
||||
|
||||
override def equals(other: Any) = other match {
|
||||
case o: MRelations => srcProd == o.srcProd && binaryDep == o.binaryDep && direct == o.direct && publicInherited == o.publicInherited && classes == o.classes
|
||||
case o: MRelationsDirectAndPublicInherited =>
|
||||
srcProd == o.srcProd && binaryDep == o.binaryDep && direct == o.direct &&
|
||||
publicInherited == o.publicInherited && classes == o.classes
|
||||
case _ => false
|
||||
}
|
||||
|
||||
|
|
@ -258,4 +410,74 @@ private class MRelations(val srcProd: Relation[File, File], val binaryDep: Relat
|
|||
| class names: %s
|
||||
""".trim.stripMargin.format(List(srcProd, binaryDep, internalSrcDep, externalDep, classes) map relation_s : _*)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class implements Relations trait with support for tracking of `memberRef` and `inheritance` source
|
||||
* dependencies. Therefore this class implements the new (compared to sbt 0.13.0) dependency tracking logic
|
||||
* needed by the name hashing invalidation algorithm.
|
||||
*/
|
||||
private class MRelationsMemberRefAndInheritance(srcProd: Relation[File, File], binaryDep: Relation[File, File],
|
||||
// memberRef should include everything in inherited
|
||||
val memberRef: SourceDependencies, val inheritance: SourceDependencies,
|
||||
classes: Relation[File, String]) extends MRelationsCommon(srcProd, binaryDep, classes)
|
||||
{
|
||||
def direct: Source =
|
||||
throw new UnsupportedOperationException("The `direct` source dependencies relation is not supported " +
|
||||
"when `memberRefAndInheritanceDeps` is disabled. Do you have name hashing algorithm disabled?")
|
||||
def publicInherited: Source =
|
||||
throw new UnsupportedOperationException("The `publicInherited` source dependencies relation is not supported " +
|
||||
"when `memberRefAndInheritanceDeps` is disabled. Do you have name hashing algorithm disabled?")
|
||||
|
||||
val memberRefAndInheritanceDeps: Boolean = true
|
||||
|
||||
def internalSrcDep: Relation[File, File] = memberRef.internal
|
||||
def externalDep: Relation[File, String] = memberRef.external
|
||||
|
||||
def addProduct(src: File, prod: File, name: String): Relations =
|
||||
new MRelationsMemberRefAndInheritance(srcProd + (src, prod), binaryDep, memberRef = memberRef,
|
||||
inheritance = inheritance, classes + (src, name))
|
||||
|
||||
def addExternalDep(src: File, dependsOn: String, inherited: Boolean): Relations = {
|
||||
val newIH = if(inherited) inheritance.addExternal(src, dependsOn) else inheritance
|
||||
val newMR = memberRef.addExternal(src, dependsOn)
|
||||
new MRelationsMemberRefAndInheritance( srcProd, binaryDep, memberRef = newMR, inheritance = newIH, classes)
|
||||
}
|
||||
|
||||
def addInternalSrcDeps(src: File, dependsOn: Iterable[File], inherited: Iterable[File]): Relations = {
|
||||
val newIH = inheritance.addInternal(src, inherited)
|
||||
val newMR = memberRef.addInternal(src, dependsOn)
|
||||
new MRelationsMemberRefAndInheritance( srcProd, binaryDep, memberRef = newMR, inheritance = newIH, classes)
|
||||
}
|
||||
|
||||
def addBinaryDep(src: File, dependsOn: File): Relations =
|
||||
new MRelationsMemberRefAndInheritance(srcProd, binaryDep + (src, dependsOn), memberRef = memberRef,
|
||||
inheritance = inheritance, classes)
|
||||
|
||||
def ++ (o: Relations): Relations = {
|
||||
if (!o.memberRefAndInheritanceDeps)
|
||||
throw new UnsupportedOperationException("The `++` operation is not supported for relations " +
|
||||
"with different values of `memberRefAndInheritanceDeps` flag.")
|
||||
new MRelationsMemberRefAndInheritance(srcProd ++ o.srcProd, binaryDep ++ o.binaryDep,
|
||||
memberRef = memberRef ++ o.memberRef, inheritance = inheritance ++ o.inheritance,
|
||||
classes ++ o.classes)
|
||||
}
|
||||
def -- (sources: Iterable[File]) =
|
||||
new MRelationsMemberRefAndInheritance(srcProd -- sources, binaryDep -- sources,
|
||||
memberRef = memberRef -- sources, inheritance = inheritance -- sources, classes -- sources)
|
||||
|
||||
def groupBy[K](f: File => K): Map[K, Relations] = {
|
||||
throw new UnsupportedOperationException("Merging of Analyses that have" +
|
||||
"`relations.memberRefAndInheritanceDeps` set to `true` is not supported.")
|
||||
}
|
||||
|
||||
override def equals(other: Any) = other match {
|
||||
case o: MRelationsMemberRefAndInheritance =>
|
||||
srcProd == o.srcProd && binaryDep == o.binaryDep && memberRef == o.memberRef &&
|
||||
inheritance == o.inheritance && classes == o.classes
|
||||
case _ => false
|
||||
}
|
||||
|
||||
override def hashCode = (srcProd :: binaryDep :: memberRef :: inheritance :: classes :: Nil).hashCode
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,8 +43,23 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile
|
|||
{
|
||||
// build dependencies structure
|
||||
val sourceFile = unit.source.file.file
|
||||
for(on <- unit.depends) processDependency(on, inherited=false)
|
||||
for(on <- inheritedDependencies.getOrElse(sourceFile, Nil: Iterable[Symbol])) processDependency(on, inherited=true)
|
||||
if (global.callback.memberRefAndInheritanceDeps) {
|
||||
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)
|
||||
|
|
@ -64,11 +79,112 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile
|
|||
case None => ()
|
||||
}
|
||||
}
|
||||
else
|
||||
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)
|
||||
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
|
||||
// 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
|
||||
dependencies.map(_.toplevelClass)
|
||||
}
|
||||
|
||||
/** 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(_.toplevelClass)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,112 @@
|
|||
package xsbt
|
||||
|
||||
import org.junit.runner.RunWith
|
||||
import xsbti.api.ClassLike
|
||||
import xsbti.api.Def
|
||||
import xsbt.api.SameAPI
|
||||
import org.specs2.mutable.Specification
|
||||
import org.specs2.runner.JUnitRunner
|
||||
|
||||
import ScalaCompilerForUnitTesting.ExtractedSourceDependencies
|
||||
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class DependencySpecification extends Specification {
|
||||
|
||||
"Extracted source dependencies from public members" in {
|
||||
val sourceDependencies = extractSourceDependenciesPublic
|
||||
val memberRef = sourceDependencies.memberRef
|
||||
val inheritance = sourceDependencies.inheritance
|
||||
memberRef('A) === Set.empty
|
||||
inheritance('A) === Set.empty
|
||||
memberRef('B) === Set('A, 'D)
|
||||
inheritance('B) === Set('D)
|
||||
memberRef('C) === Set('A)
|
||||
inheritance('C) === Set.empty
|
||||
memberRef('D) === Set.empty
|
||||
inheritance('D) === Set.empty
|
||||
memberRef('E) === Set.empty
|
||||
inheritance('E) === Set.empty
|
||||
memberRef('F) === Set('A, 'B, 'C, 'D, 'E)
|
||||
inheritance('F) === Set('A, 'E)
|
||||
memberRef('H) === Set('B, 'E, 'G)
|
||||
// aliases and applied type constructors are expanded so we have inheritance dependency on B
|
||||
inheritance('H) === Set('B, 'E)
|
||||
}
|
||||
|
||||
"Extracted source dependencies from private members" in {
|
||||
val sourceDependencies = extractSourceDependenciesPrivate
|
||||
val memberRef = sourceDependencies.memberRef
|
||||
val inheritance = sourceDependencies.inheritance
|
||||
memberRef('A) === Set.empty
|
||||
inheritance('A) === Set.empty
|
||||
memberRef('B) === Set.empty
|
||||
inheritance('B) === Set.empty
|
||||
memberRef('C) === Set('A)
|
||||
inheritance('C) === Set('A)
|
||||
memberRef('D) === Set('B)
|
||||
inheritance('D) === Set('B)
|
||||
}
|
||||
|
||||
"Extracted source dependencies with trait as first parent" in {
|
||||
val sourceDependencies = extractSourceDependenciesTraitAsFirstPatent
|
||||
val memberRef = sourceDependencies.memberRef
|
||||
val inheritance = sourceDependencies.inheritance
|
||||
memberRef('A) === Set.empty
|
||||
inheritance('A) === Set.empty
|
||||
memberRef('B) === Set('A)
|
||||
inheritance('B) === Set('A)
|
||||
// verify that memberRef captures the oddity described in documentation of `Relations.inheritance`
|
||||
// we are mainly interested whether dependency on A is captured in `memberRef` relation so
|
||||
// the invariant that says that memberRef is superset of inheritance relation is preserved
|
||||
memberRef('C) === Set('A, 'B)
|
||||
inheritance('C) === Set('A, 'B)
|
||||
// same as above but indirect (C -> B -> A), note that only A is visible here
|
||||
memberRef('D) === Set('A, 'C)
|
||||
inheritance('D) === Set('A, 'C)
|
||||
}
|
||||
|
||||
private def extractSourceDependenciesPublic: ExtractedSourceDependencies = {
|
||||
val srcA = "class A"
|
||||
val srcB = "class B extends D[A]"
|
||||
val srcC = """|class C {
|
||||
| def a: A = null
|
||||
|}""".stripMargin
|
||||
val srcD = "class D[T]"
|
||||
val srcE = "trait E[T]"
|
||||
val srcF = "trait F extends A with E[D[B]] { self: C => }"
|
||||
val srcG = "object G { type T[x] = B }"
|
||||
// T is a type constructor [x]B
|
||||
// B extends D
|
||||
// E verifies the core type gets pulled out
|
||||
val srcH = "trait H extends G.T[Int] with (E[Int] @unchecked)"
|
||||
|
||||
val compilerForTesting = new ScalaCompilerForUnitTesting(memberRefAndInheritanceDeps = true)
|
||||
val sourceDependencies = compilerForTesting.extractDependenciesFromSrcs('A -> srcA, 'B -> srcB, 'C -> srcC,
|
||||
'D -> srcD, 'E -> srcE, 'F -> srcF, 'G -> srcG, 'H -> srcH)
|
||||
sourceDependencies
|
||||
}
|
||||
|
||||
private def extractSourceDependenciesPrivate: ExtractedSourceDependencies = {
|
||||
val srcA = "class A"
|
||||
val srcB = "class B"
|
||||
val srcC = "class C { private class Inner1 extends A }"
|
||||
val srcD = "class D { def foo: Unit = { class Inner2 extends B } }"
|
||||
|
||||
val compilerForTesting = new ScalaCompilerForUnitTesting(memberRefAndInheritanceDeps = true)
|
||||
val sourceDependencies =
|
||||
compilerForTesting.extractDependenciesFromSrcs('A -> srcA, 'B -> srcB, 'C -> srcC, 'D -> srcD)
|
||||
sourceDependencies
|
||||
}
|
||||
|
||||
private def extractSourceDependenciesTraitAsFirstPatent: ExtractedSourceDependencies = {
|
||||
val srcA = "class A"
|
||||
val srcB = "trait B extends A"
|
||||
val srcC = "trait C extends B"
|
||||
val srcD = "class D extends C"
|
||||
|
||||
val compilerForTesting = new ScalaCompilerForUnitTesting(memberRefAndInheritanceDeps = true)
|
||||
val sourceDependencies =
|
||||
compilerForTesting.extractDependenciesFromSrcs('A -> srcA, 'B -> srcB, 'C -> srcC, 'D -> srcD)
|
||||
sourceDependencies
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@ class ExtractAPISpecification extends Specification {
|
|||
def stableExistentialNames: Boolean = {
|
||||
def compileAndGetFooMethodApi(src: String): Def = {
|
||||
val compilerForTesting = new ScalaCompilerForUnitTesting
|
||||
val sourceApi = compilerForTesting.compileSrc(src)
|
||||
val sourceApi = compilerForTesting.extractApiFromSrc(src)
|
||||
val FooApi = sourceApi.definitions().find(_.name() == "Foo").get.asInstanceOf[ClassLike]
|
||||
val fooMethodApi = FooApi.structure().declared().find(_.name == "foo").get
|
||||
fooMethodApi.asInstanceOf[Def]
|
||||
|
|
|
|||
|
|
@ -13,34 +13,90 @@ import xsbti.api.Def
|
|||
import xsbt.api.SameAPI
|
||||
import sbt.ConsoleLogger
|
||||
|
||||
import ScalaCompilerForUnitTesting.ExtractedSourceDependencies
|
||||
|
||||
/**
|
||||
* Provides common functionality needed for unit tests that require compiling
|
||||
* source code using Scala compiler.
|
||||
*/
|
||||
class ScalaCompilerForUnitTesting {
|
||||
class ScalaCompilerForUnitTesting(memberRefAndInheritanceDeps: Boolean = false) {
|
||||
|
||||
/**
|
||||
* Compiles given source code using Scala compiler and returns API representation
|
||||
* extracted by ExtractAPI class.
|
||||
*/
|
||||
def compileSrc(src: String): SourceAPI = {
|
||||
import java.io.FileWriter
|
||||
def extractApiFromSrc(src: String): SourceAPI = {
|
||||
val (Seq(tempSrcFile), analysisCallback) = compileSrcs(src)
|
||||
analysisCallback.apis(tempSrcFile)
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles given source code snippets (passed as Strings) using Scala compiler and returns extracted
|
||||
* dependencies between snippets. Source code snippets are identified by symbols. Each symbol should
|
||||
* be associated with one snippet only.
|
||||
*
|
||||
* Symbols are used to express extracted dependencies between source code snippets. This way we have
|
||||
* file system-independent way of testing dependencies between source code "files".
|
||||
*/
|
||||
def extractDependenciesFromSrcs(srcs: (Symbol, String)*): ExtractedSourceDependencies = {
|
||||
val (symbolsForSrcs, rawSrcs) = srcs.unzip
|
||||
assert(symbolsForSrcs.distinct.size == symbolsForSrcs.size,
|
||||
s"Duplicate symbols for srcs detected: $symbolsForSrcs")
|
||||
val (tempSrcFiles, testCallback) = compileSrcs(rawSrcs: _*)
|
||||
val fileToSymbol = (tempSrcFiles zip symbolsForSrcs).toMap
|
||||
val memberRefFileDeps = testCallback.sourceDependencies collect {
|
||||
// false indicates that those dependencies are not introduced by inheritance
|
||||
case (target, src, false) => (src, target)
|
||||
}
|
||||
val inheritanceFileDeps = testCallback.sourceDependencies collect {
|
||||
// true indicates that those dependencies are introduced by inheritance
|
||||
case (target, src, true) => (src, target)
|
||||
}
|
||||
def toSymbols(src: File, target: File): (Symbol, Symbol) = (fileToSymbol(src), fileToSymbol(target))
|
||||
val memberRefDeps = memberRefFileDeps map { case (src, target) => toSymbols(src, target) }
|
||||
val inheritanceDeps = inheritanceFileDeps map { case (src, target) => toSymbols(src, target) }
|
||||
def pairsToMultiMap[A, B](pairs: Seq[(A, B)]): Map[A, Set[B]] = {
|
||||
import scala.collection.mutable.{HashMap, MultiMap}
|
||||
val emptyMultiMap = new HashMap[A, scala.collection.mutable.Set[B]] with MultiMap[A, B]
|
||||
val multiMap = pairs.foldLeft(emptyMultiMap) { case (acc, (key, value)) =>
|
||||
acc.addBinding(key, value)
|
||||
}
|
||||
// convert all collections to immutable variants
|
||||
multiMap.toMap.mapValues(_.toSet).withDefaultValue(Set.empty)
|
||||
}
|
||||
ExtractedSourceDependencies(pairsToMultiMap(memberRefDeps), pairsToMultiMap(inheritanceDeps))
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles given source code snippets written to a temporary files. Each snippet is
|
||||
* written to a separate temporary file.
|
||||
*
|
||||
* The sequence of temporary files corresponding to passed snippets and analysis
|
||||
* callback is returned as a result.
|
||||
*/
|
||||
private def compileSrcs(srcs: String*): (Seq[File], TestCallback) = {
|
||||
withTemporaryDirectory { temp =>
|
||||
val analysisCallback = new TestCallback
|
||||
val analysisCallback = new TestCallback(memberRefAndInheritanceDeps)
|
||||
val classesDir = new File(temp, "classes")
|
||||
classesDir.mkdir()
|
||||
val compiler = prepareCompiler(classesDir, analysisCallback)
|
||||
val run = new compiler.Run
|
||||
val srcFile = new File(temp, "Test.scala")
|
||||
srcFile.createNewFile()
|
||||
val fw = new FileWriter(srcFile)
|
||||
fw.write(src)
|
||||
fw.close()
|
||||
run.compile(List(srcFile.getAbsolutePath()))
|
||||
analysisCallback.apis(srcFile)
|
||||
val srcFiles = srcs.toSeq.zipWithIndex map { case (src, i) =>
|
||||
val fileName = s"Test_$i.scala"
|
||||
prepareSrcFile(temp, fileName, src)
|
||||
}
|
||||
val srcFilePaths = srcFiles.map(srcFile => srcFile.getAbsolutePath).toList
|
||||
run.compile(srcFilePaths)
|
||||
(srcFiles, analysisCallback)
|
||||
}
|
||||
}
|
||||
|
||||
private def prepareSrcFile(baseDir: File, fileName: String, src: String): File = {
|
||||
val srcFile = new File(baseDir, fileName)
|
||||
sbt.IO.write(srcFile, src)
|
||||
srcFile
|
||||
}
|
||||
|
||||
private def prepareCompiler(outputDir: File, analysisCallback: AnalysisCallback): CachedCompiler0#Compiler = {
|
||||
val args = Array.empty[String]
|
||||
object output extends SingleOutput {
|
||||
|
|
@ -69,3 +125,7 @@ class ScalaCompilerForUnitTesting {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
object ScalaCompilerForUnitTesting {
|
||||
case class ExtractedSourceDependencies(memberRef: Map[Symbol, Set[Symbol]], inheritance: Map[Symbol, Set[Symbol]])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ package inc
|
|||
import DefaultProtocol._
|
||||
import DefaultProtocol.tuple2Format
|
||||
import Logger.{m2o, position, problem}
|
||||
import Relations.{Source => RSource}
|
||||
import Relations.{Source => RSource, SourceDependencies}
|
||||
|
||||
@deprecated("Replaced by TextAnalysisFormat. OK to remove in 0.14.", since="0.13.1")
|
||||
object AnalysisFormats
|
||||
|
|
@ -99,12 +99,34 @@ 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], 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 relationsFormat(implicit prodF: Format[RFF], binF: Format[RFF], directF: Format[RSource], inheritedF: Format[RSource], memberRefF: Format[SourceDependencies], inheritanceF: Format[SourceDependencies], csF: Format[RFS]): Format[Relations] =
|
||||
{
|
||||
def makeRelation(srcProd: RFF, binaryDep: RFF, direct: RSource, publicInherited: RSource,
|
||||
memberRef: SourceDependencies, inheritance: SourceDependencies, classes: RFS,
|
||||
memberRefAndInheritanceDeps: Boolean): Relations = if (memberRefAndInheritanceDeps) {
|
||||
def isEmpty(sourceDependencies: RSource): Boolean =
|
||||
sourceDependencies.internal.all.isEmpty && sourceDependencies.external.all.isEmpty
|
||||
// we check direct dependencies only because publicInherited dependencies are subset of direct
|
||||
assert(isEmpty(direct), "Direct dependencies are not empty but `memberRefAndInheritanceDeps` flag is enabled.")
|
||||
Relations.make(srcProd, binaryDep, memberRef, inheritance, classes)
|
||||
} else {
|
||||
def isEmpty(sourceDependencies: SourceDependencies): Boolean =
|
||||
sourceDependencies.internal.all.isEmpty && sourceDependencies.external.all.isEmpty
|
||||
// we check memberRef dependencies only because inheritance dependencies are subset of memberRef
|
||||
assert(isEmpty(memberRef), "Direct dependencies are not empty but `memberRefAndInheritanceDeps` flag is enabled.")
|
||||
Relations.make(srcProd, binaryDep, direct, publicInherited, classes)
|
||||
}
|
||||
asProduct8[Relations, RFF, RFF, RSource, RSource, SourceDependencies, SourceDependencies, RFS, Boolean]( (a,b,c,d,e,f,g,h) =>makeRelation(a,b,c,d,e,f,g,h) )(
|
||||
rs => (rs.srcProd, rs.binaryDep, rs.direct, rs.publicInherited, rs.memberRef, rs.inheritance, rs.classes, rs.memberRefAndInheritanceDeps) )(
|
||||
prodF, binF, directF, inheritedF, memberRefF, inheritanceF, csF, implicitly[Format[Boolean]])
|
||||
}
|
||||
|
||||
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 relationsSourceDependenciesFormat(implicit internalFormat: Format[Relation[File, File]], externalFormat: Format[Relation[File,String]]): Format[SourceDependencies] =
|
||||
asProduct2[SourceDependencies, RFF, RFS]( (a, b) => Relations.makeSourceDependencies(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)
|
||||
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ object TextAnalysisFormat {
|
|||
}
|
||||
|
||||
private[this] object VersionF {
|
||||
val currentVersion = "1"
|
||||
val currentVersion = "2"
|
||||
|
||||
def write(out: Writer) {
|
||||
out.write("format version: %s\n".format(currentVersion))
|
||||
|
|
@ -107,11 +107,16 @@ object TextAnalysisFormat {
|
|||
object Headers {
|
||||
val srcProd = "products"
|
||||
val binaryDep = "binary dependencies"
|
||||
val internalSrcDep = "source dependencies"
|
||||
val externalDep = "external dependencies"
|
||||
val directSrcDep = "direct source dependencies"
|
||||
val directExternalDep = "direct external dependencies"
|
||||
val internalSrcDepPI = "public inherited source dependencies"
|
||||
val externalDepPI = "public inherited external dependencies"
|
||||
val classes = "class names"
|
||||
|
||||
val memberRefInternalDep = "member reference internal dependencies"
|
||||
val memberRefExternalDep = "member reference external dependencies"
|
||||
val inheritanceInternalDep = "inheritance internal dependencies"
|
||||
val inheritanceExternalDep = "inheritance external dependencies"
|
||||
}
|
||||
|
||||
def write(out: Writer, relations: Relations) {
|
||||
|
|
@ -129,12 +134,29 @@ object TextAnalysisFormat {
|
|||
}
|
||||
}
|
||||
|
||||
val memberRefAndInheritanceDeps = relations.memberRefAndInheritanceDeps
|
||||
writeRelation(Headers.srcProd, relations.srcProd)
|
||||
writeRelation(Headers.binaryDep, relations.binaryDep)
|
||||
writeRelation(Headers.internalSrcDep, relations.internalSrcDep)
|
||||
writeRelation(Headers.externalDep, relations.externalDep)
|
||||
writeRelation(Headers.internalSrcDepPI, relations.publicInherited.internal)
|
||||
writeRelation(Headers.externalDepPI, relations.publicInherited.external)
|
||||
|
||||
val direct = if (memberRefAndInheritanceDeps) Relations.emptySource else relations.direct
|
||||
val publicInherited = if (memberRefAndInheritanceDeps)
|
||||
Relations.emptySource else relations.publicInherited
|
||||
|
||||
val memberRef = if (memberRefAndInheritanceDeps)
|
||||
relations.memberRef else Relations.emptySourceDependencies
|
||||
val inheritance = if (memberRefAndInheritanceDeps)
|
||||
relations.inheritance else Relations.emptySourceDependencies
|
||||
|
||||
writeRelation(Headers.directSrcDep, direct.internal)
|
||||
writeRelation(Headers.directExternalDep, direct.external)
|
||||
writeRelation(Headers.internalSrcDepPI, publicInherited.internal)
|
||||
writeRelation(Headers.externalDepPI, publicInherited.external)
|
||||
|
||||
writeRelation(Headers.memberRefInternalDep, memberRef.internal)
|
||||
writeRelation(Headers.memberRefExternalDep, memberRef.external)
|
||||
writeRelation(Headers.inheritanceInternalDep, inheritance.internal)
|
||||
writeRelation(Headers.inheritanceExternalDep, inheritance.external)
|
||||
|
||||
writeRelation(Headers.classes, relations.classes)
|
||||
}
|
||||
|
||||
|
|
@ -164,14 +186,40 @@ object TextAnalysisFormat {
|
|||
|
||||
val srcProd = readFileRelation(Headers.srcProd)
|
||||
val binaryDep = readFileRelation(Headers.binaryDep)
|
||||
val internalSrcDep = readFileRelation(Headers.internalSrcDep)
|
||||
val externalDep = readStringRelation(Headers.externalDep)
|
||||
val internalSrcDepPI = readFileRelation(Headers.internalSrcDepPI)
|
||||
val externalDepPI = readStringRelation(Headers.externalDepPI)
|
||||
|
||||
import sbt.inc.Relations.{Source, SourceDependencies, makeSourceDependencies, emptySource,
|
||||
makeSource, emptySourceDependencies}
|
||||
val directSrcDeps: Source = {
|
||||
val internalSrcDep = readFileRelation(Headers.directSrcDep)
|
||||
val externalDep = readStringRelation(Headers.directExternalDep)
|
||||
makeSource(internalSrcDep, externalDep)
|
||||
}
|
||||
val publicInheritedSrcDeps: Source = {
|
||||
val internalSrcDepPI = readFileRelation(Headers.internalSrcDepPI)
|
||||
val externalDepPI = readStringRelation(Headers.externalDepPI)
|
||||
makeSource(internalSrcDepPI, externalDepPI)
|
||||
}
|
||||
val memberRefSrcDeps: SourceDependencies = {
|
||||
val internalMemberRefDep = readFileRelation(Headers.memberRefInternalDep)
|
||||
val externalMemberRefDep = readStringRelation(Headers.memberRefExternalDep)
|
||||
makeSourceDependencies(internalMemberRefDep, externalMemberRefDep)
|
||||
}
|
||||
val inheritanceSrcDeps: SourceDependencies = {
|
||||
val internalInheritanceDep = readFileRelation(Headers.inheritanceInternalDep)
|
||||
val externalInheritanceDep = readStringRelation(Headers.inheritanceExternalDep)
|
||||
makeSourceDependencies(internalInheritanceDep, externalInheritanceDep)
|
||||
}
|
||||
// we don't check for emptiness of publicInherited/inheritance relations because
|
||||
// we assume that invariant that says they are subsets of direct/memberRef holds
|
||||
assert((directSrcDeps == emptySource) || (memberRefSrcDeps == emptySourceDependencies),
|
||||
"One mechanism is supported for tracking source dependencies at the time")
|
||||
val memberRefAndInheritanceDeps = memberRefSrcDeps != emptySourceDependencies
|
||||
val classes = readStringRelation(Headers.classes)
|
||||
|
||||
Relations.make(srcProd, binaryDep, Relations.makeSource(internalSrcDep, externalDep),
|
||||
Relations.makeSource(internalSrcDepPI, externalDepPI), classes)
|
||||
if (memberRefAndInheritanceDeps)
|
||||
Relations.make(srcProd, binaryDep, memberRefSrcDeps, inheritanceSrcDeps, classes)
|
||||
else
|
||||
Relations.make(srcProd, binaryDep, directSrcDeps, publicInheritedSrcDeps, classes)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,4 +27,16 @@ public interface AnalysisCallback
|
|||
/** Provides problems discovered during compilation. These may be reported (logged) or unreported.
|
||||
* Unreported problems are usually unreported because reporting was not enabled via a command line switch. */
|
||||
public void problem(String what, Position pos, String msg, Severity severity, boolean reported);
|
||||
/**
|
||||
* Determines whether member reference and inheritance dependencies should be extracted in given compiler
|
||||
* run.
|
||||
*
|
||||
* As the signature suggests, this method's implementation is meant to be side-effect free. It's added
|
||||
* to AnalysisCallback because it indicates how other callback calls should be interpreted by both
|
||||
* implementation of AnalysisCallback and it's clients.
|
||||
*
|
||||
* NOTE: This method is an implementation detail and can be removed at any point without deprecation.
|
||||
* Do not depend on it, please.
|
||||
*/
|
||||
public boolean memberRefAndInheritanceDeps();
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import java.io.File
|
|||
import scala.collection.mutable.ArrayBuffer
|
||||
import xsbti.api.SourceAPI
|
||||
|
||||
class TestCallback extends AnalysisCallback
|
||||
class TestCallback(override val memberRefAndInheritanceDeps: Boolean = false) extends AnalysisCallback
|
||||
{
|
||||
val sourceDependencies = new ArrayBuffer[(File, File, Boolean)]
|
||||
val binaryDependencies = new ArrayBuffer[(File, String, File, Boolean)]
|
||||
|
|
|
|||
|
|
@ -2,25 +2,31 @@ lazy val verifyDeps = taskKey[Unit]("verify inherited dependencies are properly
|
|||
|
||||
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))
|
||||
val baseDir = baseDirectory.value
|
||||
def relative(f: java.io.File): java.io.File = f.relativeTo(baseDir) getOrElse f
|
||||
def toFile(s: String) = relative(baseDir / (s + ".scala"))
|
||||
def inheritedDeps(name: String): Set[File] = {
|
||||
val file = (baseDir / (name + ".scala")).getAbsoluteFile
|
||||
val absoluteFiles = a.relations.publicInherited.internal.forward(file)
|
||||
absoluteFiles.map(relative)
|
||||
}
|
||||
lazy val expectedDeps = (Relation.empty[File,File] /: pairs) { case (r, (x,ys)) => r + (x,ys) }
|
||||
def toFile(s: String) = file(s + ".scala").getAbsoluteFile
|
||||
val ADeps = Set("C", "D", "E", "G", "J").map(toFile)
|
||||
same(inheritedDeps("A"), ADeps)
|
||||
val BDeps = Set.empty[File]
|
||||
same(inheritedDeps("B"), BDeps)
|
||||
val CDeps = Set("D", "G", "J").map(toFile)
|
||||
same(inheritedDeps("C"), CDeps)
|
||||
val DDeps = Set("G", "J").map(toFile)
|
||||
same(inheritedDeps("D"), DDeps)
|
||||
val EDeps = Set.empty[File]
|
||||
same(inheritedDeps("E"), EDeps)
|
||||
val FDeps = Set("C", "D", "G", "J").map(toFile)
|
||||
same(inheritedDeps("F"), FDeps)
|
||||
val GDeps = Set("J").map(toFile)
|
||||
same(inheritedDeps("G"), GDeps)
|
||||
val JDeps = Set.empty[File]
|
||||
same(inheritedDeps("J"), JDeps)
|
||||
}
|
||||
|
||||
def same[T](x: T, y: T) {
|
||||
assert(x == y, s"\nActual: $x, \nExpected: $y")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
class A[T]
|
||||
|
||||
abstract class C {
|
||||
def foo: A[B]
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
class B
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
logLevel := Level.Debug
|
||||
|
||||
// disable recompile all which causes full recompile which
|
||||
// makes it more difficult to test dependency tracking
|
||||
incOptions ~= { _.copy(recompileAllFraction = 1.0) }
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# test case for dependency tracking in case given type (`B` in our case)
|
||||
# mentioned only in type ref (as a type argument)
|
||||
> compile
|
||||
|
||||
$ delete B.scala
|
||||
|
||||
-> compile
|
||||
Loading…
Reference in New Issue