Introduce `memberRef` and `inheritance` relations.

Introduce an alternative source dependency tracking mechanism that is
needed by upcoming name hashing algorithm. This new mechanism is
implemented by introducing two new source dependency relations called
`memberRef` and `inheritance`.

Those relations are very similar to existing `direct` and
`publicInherited` relations in some subtle ways. Those differences
will be highlighted in the description below.

Dependencies between source files are tracked in two distinct
categories:

  * dependencies introduced by inheriting from a class/trait
    defined in other source file
  * dependencies introduced by referring (selecting) a member
    defined in other source file (that covers all other
    kinds of dependencies)

Due to invalidation algorithm implementation details sbt would need to
track inheritance dependencies of public classes only. Thus, we had
relation called `publicInherited`. The name hashing algorithm which
improves invalidation logic will need more precise information about
dependencies introduced by inheritance including dependencies of non-public
classes. That's one difference between `inheritance` and `publicInherited`
relations.

One surprising (to me) thing about `publicInherited` is that it includes
all base classes of a given class and not just parents. In that sense
`publicInherited` is transitive. This is a bit irregular because
everything else in Relations doesn't include transitive dependencies.

Since we are introducing new relations we have an excellent chance to
make things more regular. Therefore `inheritance` relation is
non-transitive and includes only extracted parent classes.

The access to `direct`, `publicInherited`, `memberRef` and `inheritance`
relations is dependent upon the value of `memberRefAndInheritanceDeps`
flag. Check documentation of that flag for details.

The two alternatives for source dependency tracking are implemented by
introduction of two subclasses that implement Relations trait and one
abstract class that contains some common logic shared between those two
subclasses. The two new subclasses are needed for the time being when we
are slowly migrating to the name hashing algorithm which requires
subtle changes to dependency tracking as explained above. 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.
This commit is contained in:
Grzegorz Kossakowski 2013-11-26 18:39:13 +01:00
parent a9a15748cc
commit b8371691f2
4 changed files with 377 additions and 77 deletions

View File

@ -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)

View File

@ -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
}

View File

@ -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)

View File

@ -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)
}
}