From f7c00ab5810623de96abe99b446913bcf05d0117 Mon Sep 17 00:00:00 2001 From: Grzegorz Kossakowski Date: Wed, 14 May 2014 00:09:26 +0200 Subject: [PATCH 1/4] Sketch of abstracting over dependency details in incremental compiler This represents a sketch of the idea that we can abstract over details of a specific dependency kind. The goal would that only a few implementations of methods in incremental would be sensitive to specific dependency kind: 1. Dependency extraction logic 2. Implementation of Relations which adds dependencies to specific relations (or abstract over specific relations) 3. Invalidation algorithm (Incremental.scala) which has different of each kind of dependency 4. TextAnalysisFormat In particular, adding a new dependency kind would not affect signatures of existing methods. What needs to be done: - finish refactoring so the code compiles again and previous semantics are preserved - introduce deprecated overloads that preserve old method signatures (this is required for preserving binary compatibility) --- .../inc/src/main/scala/sbt/inc/Analysis.scala | 22 ++++++--- .../src/main/scala/sbt/inc/Dependency.scala | 48 +++++++++++++++++++ .../src/main/scala/sbt/inc/Relations.scala | 24 +++++++--- 3 files changed, 81 insertions(+), 13 deletions(-) create mode 100644 compile/inc/src/main/scala/sbt/inc/Dependency.scala diff --git a/compile/inc/src/main/scala/sbt/inc/Analysis.scala b/compile/inc/src/main/scala/sbt/inc/Analysis.scala index 29855ee71..eaf4cd0b3 100644 --- a/compile/inc/src/main/scala/sbt/inc/Analysis.scala +++ b/compile/inc/src/main/scala/sbt/inc/Analysis.scala @@ -51,9 +51,9 @@ trait Analysis { def copy(stamps: Stamps = stamps, apis: APIs = apis, relations: Relations = relations, infos: SourceInfos = infos, compilations: Compilations = compilations): Analysis - def addSource(src: File, api: Source, stamp: Stamp, directInternal: Iterable[File], inheritedInternal: Iterable[File], info: SourceInfo): Analysis + def addSource(src: File, api: Source, stamp: Stamp, dependencies: Iterable[Dependency], 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 + def addExternalDep(dep: Dependency): Analysis def addProduct(src: File, product: File, stamp: Stamp, name: String): Analysis /** Partitions this Analysis using the discriminator function. Externalizes internal deps that cross partitions. */ @@ -155,14 +155,24 @@ private class MAnalysis(val stamps: Stamps, val apis: APIs, val relations: Relat def copy(stamps: Stamps, apis: APIs, relations: Relations, infos: SourceInfos, compilations: Compilations = compilations): Analysis = new MAnalysis(stamps, apis, relations, infos, compilations) - def addSource(src: File, api: Source, stamp: Stamp, directInternal: Iterable[File], inheritedInternal: Iterable[File], info: SourceInfo): Analysis = - copy(stamps.markInternalSource(src, stamp), apis.markInternalSource(src, api), relations.addInternalSrcDeps(src, directInternal, inheritedInternal), infos.add(src, info)) + def addSource(src: File, api: Source, stamp: Stamp, dependencies: Iterable[Dependency], info: SourceInfo): Analysis = { + copy(stamps.markInternalSource(src, stamp), apis.markInternalSource(src, api), relations.addInternalSrcDeps(src, dependencies), infos.add(src, info)) + } def addBinaryDep(src: File, dep: File, className: String, stamp: Stamp): Analysis = copy(stamps.markBinary(dep, className, stamp), apis, relations.addBinaryDep(src, dep), infos) - def addExternalDep(src: File, dep: String, depAPI: Source, inherited: Boolean): Analysis = - copy(stamps, apis.markExternalAPI(dep, depAPI), relations.addExternalDep(src, dep, inherited), infos) + def addExternalDep(dep: Dependency): Analysis = { + // TODO: there's no static enforcment that Dependency passed as parameter has ExternalDependencyEdge + // as its edge. We risk MathError below; We should have just one method for adding dependencies + // where all cases are handled + val ExternalDepndencyEdge(fromSrc, toClassName, classApi) = dep.edge + val inherited = dep.context match { + case DependencyByInheritance => true + case _ => false + } + copy(stamps, apis.markExternalAPI(toClassName, classApi), relations.addExternalDep(fromSrc, toClassName, inherited), infos) + } def addProduct(src: File, product: File, stamp: Stamp, name: String): Analysis = copy(stamps.markProduct(product, stamp), apis, relations.addProduct(src, product, name), infos) diff --git a/compile/inc/src/main/scala/sbt/inc/Dependency.scala b/compile/inc/src/main/scala/sbt/inc/Dependency.scala new file mode 100644 index 000000000..a595d246c --- /dev/null +++ b/compile/inc/src/main/scala/sbt/inc/Dependency.scala @@ -0,0 +1,48 @@ +package sbt.inc + +import java.io.File +import xsbti.api.Source + +/** + * Dependency tracked by incremental compiler consists of two parts: + * - `edge` which describes an edge in dependency graph + * - `context` which stores context information from a src file where the dependency got introduced + * + * The context is needed for incremental compiler to decide what kind of invalidation strategy to use. + * It might also store some additional information useful for debugging. + */ +private[inc] final case class Dependency(edge: DependencyEdge, context: DependencyContext) + +private[inc] sealed abstract class DependencyEdge +/** + * External dependency from src file to class name. External means "outside of enclosing Analysis". + * Typically that means a dependency coming from another subproject in sbt. + * + * The classApi contains snapshot of information about the class (that `toClassName` points at) + * as observed from point of view of compilation that produced this dependency edge. + * + * The classApi is stored so we can detect changes to external apis by comparing snapshots + * of the api from two Analysis instances. + */ +private[inc] final case class ExternalDepndencyEdge(fromSrc: File, toClassName: String, classApi: Source) + extends DependencyEdge + +/** + * Represents a dependency edge between two source files in the same sbt subproject. + */ +private[inc] final case class InternalDependencyEdge(fromSrc: File, toSrc: File) extends DependencyEdge + +/** + * Represents contextual information about particular depedency edge. See comments in + * subtypes for examples of particular contexts. + */ +private[inc] sealed abstract class DependencyContext +/** + * Marks dependency edge introduced by referring to a class through inheritance as in + * + * class A extends B + * + * Each dependency by inheritance introduces corresponding dependency by member reference. + */ +private[inc] final case object DependencyByInheritance extends DependencyContext +private[inc] final case object DependencyByMemberRef extends DependencyContext diff --git a/compile/inc/src/main/scala/sbt/inc/Relations.scala b/compile/inc/src/main/scala/sbt/inc/Relations.scala index 6a60ca418..6bb230322 100644 --- a/compile/inc/src/main/scala/sbt/inc/Relations.scala +++ b/compile/inc/src/main/scala/sbt/inc/Relations.scala @@ -74,11 +74,12 @@ trait Relations { def addBinaryDep(src: File, dependsOn: File): Relations /** + * TODO: Update comment below. * Records internal source file `src` as having direct dependencies on internal source files `directDependsOn` * and inheritance dependencies on `inheritedDependsOn`. Everything in `inheritedDependsOn` must be included in `directDependsOn`; * this method does not automatically record direct dependencies like `addExternalDep` does. */ - def addInternalSrcDeps(src: File, directDependsOn: Iterable[File], inheritedDependsOn: Iterable[File]): Relations + def addInternalSrcDeps(src: File, dependencies: Iterable[Dependency]): Relations private[inc] def addUsedName(src: File, name: String): Relations @@ -398,12 +399,21 @@ private class MRelationsDefaultImpl(srcProd: Relation[File, File], binaryDep: Re new MRelationsDefaultImpl(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 MRelationsDefaultImpl(srcProd, binaryDep, direct = newD, publicInherited = newI, classes) - } + def addInternalSrcDeps(src: File, dependencies: Iterable[Dependency]): Relations = { + val depsByInheritance = dependencies.collect { + case Dependency(InternalDependencyEdge(fromSrc, toSrc), DependencyByInheritance) => + assert(src == fromSrc) + toSrc + } + val depsByMemberRef = dependencies.collect { + case Dependency(InternalDependencyEdge(fromSrc, toSrc), DependencyByMemberRef) => + assert(src == fromSrc) + toSrc + } + val newI = publicInherited.addInternal(src, depsByInheritance) + val newD = direct.addInternal(src, depsByMemberRef) + new MRelationsDefaultImpl(srcProd, binaryDep, direct = newD, publicInherited = newI, classes) + } def names: Relation[File, String] = throw new UnsupportedOperationException("Tracking of used names is not supported " + From c09a3912d1d5390d84c93607d261cb5dd5c23748 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Fri, 12 Sep 2014 23:45:45 +0200 Subject: [PATCH 2/4] Implement abstraction over dependency kinds This commit implements the abstraction over the dependency kinds and deprecates the methods that were used to register files and their dependencies. Dependencies are now divided into two categories : Internal and External dependencies. Moreover, each internal or external dependency has a Context, which describes what introduced the dependency (for instance, a dependency may be introduced by member reference or by inheritance). Dependencies must now be registered using the method `addSource` in `Analysis` and in `Relations`. This method has the advantage of being independent from the existing dependency contexts. That is, its signature does not need to be modified whenever a new dependency context is introduced. --- .../inc/src/main/scala/sbt/inc/Analysis.scala | 64 +++-- .../src/main/scala/sbt/inc/Dependency.scala | 40 +-- .../src/main/scala/sbt/inc/Relations.scala | 247 ++++++++++++++---- .../scala/sbt/inc/TestCaseGenerators.scala | 4 +- .../main/scala/sbt/inc/AnalysisFormats.scala | 4 +- 5 files changed, 259 insertions(+), 100 deletions(-) diff --git a/compile/inc/src/main/scala/sbt/inc/Analysis.scala b/compile/inc/src/main/scala/sbt/inc/Analysis.scala index eaf4cd0b3..90e8c73fa 100644 --- a/compile/inc/src/main/scala/sbt/inc/Analysis.scala +++ b/compile/inc/src/main/scala/sbt/inc/Analysis.scala @@ -51,9 +51,19 @@ 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, dependencies: Iterable[Dependency], info: SourceInfo): Analysis + def addSource(src: File, api: Source, stamp: Stamp, info: SourceInfo, + products: Iterable[(File, String, Stamp)], + internalDeps: Iterable[InternalDependency], + externalDeps: Iterable[ExternalDependency], + binaryDeps: Iterable[(File, String, Stamp)]): Analysis + + @deprecated("Register all products and dependencies in addSource.", "0.13.8") + def addSource(src: File, api: Source, stamp: Stamp, directInternal: Iterable[File], inheritedInternal: Iterable[File], info: SourceInfo): Analysis + @deprecated("Register all products and dependencies in addSource.", "0.13.8") def addBinaryDep(src: File, dep: File, className: String, stamp: Stamp): Analysis - def addExternalDep(dep: Dependency): Analysis + @deprecated("Register all products and dependencies in addSource.", "0.13.8") + def addExternalDep(src: File, dep: String, api: Source, inherited: Boolean): Analysis + @deprecated("Register all products and dependencies in addSource.", "0.13.8") def addProduct(src: File, product: File, stamp: Stamp, name: String): Analysis /** Partitions this Analysis using the discriminator function. Externalizes internal deps that cross partitions. */ @@ -155,27 +165,49 @@ private class MAnalysis(val stamps: Stamps, val apis: APIs, val relations: Relat def copy(stamps: Stamps, apis: APIs, relations: Relations, infos: SourceInfos, compilations: Compilations = compilations): Analysis = new MAnalysis(stamps, apis, relations, infos, compilations) - def addSource(src: File, api: Source, stamp: Stamp, dependencies: Iterable[Dependency], info: SourceInfo): Analysis = { - copy(stamps.markInternalSource(src, stamp), apis.markInternalSource(src, api), relations.addInternalSrcDeps(src, dependencies), infos.add(src, info)) + def addSource(src: File, api: Source, stamp: Stamp, info: SourceInfo, + products: Iterable[(File, String, Stamp)], + internalDeps: Iterable[InternalDependency], + externalDeps: Iterable[ExternalDependency], + binaryDeps: Iterable[(File, String, Stamp)]): Analysis = { + + val newStamps = { + val productStamps = products.foldLeft(stamps.markInternalSource(src, stamp)) { + case (tmpStamps, (toProduct, _, prodStamp)) => tmpStamps.markProduct(toProduct, prodStamp) + } + + binaryDeps.foldLeft(productStamps) { + case (tmpStamps, (toBinary, className, binStamp)) => tmpStamps.markBinary(toBinary, className, binStamp) + } + } + + val newAPIs = externalDeps.foldLeft(apis.markInternalSource(src, api)) { + case (tmpApis, ExternalDependency(_, toClassName, classApi, _)) => tmpApis.markExternalAPI(toClassName, classApi) + } + + val newRelations = relations.addSource(src, products map (p => (p._1, p._2)), internalDeps, externalDeps, binaryDeps) + + copy(newStamps, newAPIs, newRelations, infos.add(src, info)) + } + + def addSource(src: File, api: Source, stamp: Stamp, directInternal: Iterable[File], inheritedInternal: Iterable[File], info: SourceInfo): Analysis = { + + val directDeps = directInternal.map(InternalDependency(src, _, DependencyByMemberRef)) + val inheritedDeps = inheritedInternal.map(InternalDependency(src, _, DependencyByInheritance)) + + addSource(src, api, stamp, info, products = Nil, directDeps ++ inheritedDeps, Nil, Nil) } def addBinaryDep(src: File, dep: File, className: String, stamp: Stamp): Analysis = - copy(stamps.markBinary(dep, className, stamp), apis, relations.addBinaryDep(src, dep), infos) + copy(stamps.markBinary(dep, className, stamp), apis, relations.addBinaryDeps(src, (dep, className, stamp) :: Nil), infos) - def addExternalDep(dep: Dependency): Analysis = { - // TODO: there's no static enforcment that Dependency passed as parameter has ExternalDependencyEdge - // as its edge. We risk MathError below; We should have just one method for adding dependencies - // where all cases are handled - val ExternalDepndencyEdge(fromSrc, toClassName, classApi) = dep.edge - val inherited = dep.context match { - case DependencyByInheritance => true - case _ => false - } - copy(stamps, apis.markExternalAPI(toClassName, classApi), relations.addExternalDep(fromSrc, toClassName, inherited), infos) + def addExternalDep(src: File, dep: String, depAPI: Source, inherited: Boolean): Analysis = { + val context = if (inherited) DependencyByInheritance else DependencyByMemberRef + copy(stamps, apis.markExternalAPI(dep, depAPI), relations.addExternalDeps(src, ExternalDependency(src, dep, depAPI, context) :: Nil), infos) } def addProduct(src: File, product: File, stamp: Stamp, name: String): Analysis = - copy(stamps.markProduct(product, stamp), apis, relations.addProduct(src, product, name), infos) + copy(stamps.markProduct(product, stamp), apis, relations.addProducts(src, (product, name) :: Nil), infos) def groupBy[K](discriminator: File => K): Map[K, Analysis] = { if (relations.nameHashing) diff --git a/compile/inc/src/main/scala/sbt/inc/Dependency.scala b/compile/inc/src/main/scala/sbt/inc/Dependency.scala index a595d246c..a10d33d00 100644 --- a/compile/inc/src/main/scala/sbt/inc/Dependency.scala +++ b/compile/inc/src/main/scala/sbt/inc/Dependency.scala @@ -3,40 +3,12 @@ package sbt.inc import java.io.File import xsbti.api.Source -/** - * Dependency tracked by incremental compiler consists of two parts: - * - `edge` which describes an edge in dependency graph - * - `context` which stores context information from a src file where the dependency got introduced - * - * The context is needed for incremental compiler to decide what kind of invalidation strategy to use. - * It might also store some additional information useful for debugging. - */ -private[inc] final case class Dependency(edge: DependencyEdge, context: DependencyContext) - -private[inc] sealed abstract class DependencyEdge -/** - * External dependency from src file to class name. External means "outside of enclosing Analysis". - * Typically that means a dependency coming from another subproject in sbt. - * - * The classApi contains snapshot of information about the class (that `toClassName` points at) - * as observed from point of view of compilation that produced this dependency edge. - * - * The classApi is stored so we can detect changes to external apis by comparing snapshots - * of the api from two Analysis instances. - */ -private[inc] final case class ExternalDepndencyEdge(fromSrc: File, toClassName: String, classApi: Source) - extends DependencyEdge - -/** - * Represents a dependency edge between two source files in the same sbt subproject. - */ -private[inc] final case class InternalDependencyEdge(fromSrc: File, toSrc: File) extends DependencyEdge - /** * Represents contextual information about particular depedency edge. See comments in * subtypes for examples of particular contexts. */ private[inc] sealed abstract class DependencyContext + /** * Marks dependency edge introduced by referring to a class through inheritance as in * @@ -46,3 +18,13 @@ private[inc] sealed abstract class DependencyContext */ private[inc] final case object DependencyByInheritance extends DependencyContext private[inc] final case object DependencyByMemberRef extends DependencyContext + +/** + * Represents the kind of dependency that exists between `sourceFile` and either `targetFile` + * or `targetClassName`. + * + * `InternalDependency` represent dependencies that exist between the files of a same project, + * while `ExternalDependency` represent cross-project dependencies. + */ +private[inc] final case class InternalDependency(sourceFile: File, targetFile: File, context: DependencyContext) +private[inc] final case class ExternalDependency(sourceFile: File, targetClassName: String, targetSource: Source, context: DependencyContext) diff --git a/compile/inc/src/main/scala/sbt/inc/Relations.scala b/compile/inc/src/main/scala/sbt/inc/Relations.scala index 6bb230322..5db719311 100644 --- a/compile/inc/src/main/scala/sbt/inc/Relations.scala +++ b/compile/inc/src/main/scala/sbt/inc/Relations.scala @@ -7,6 +7,7 @@ package inc import java.io.File import Relations.Source import Relations.SourceDependencies +import xsbti.api.{ Source => APISource } /** * Provides mappings between source files, generated classes (products), and binaries. @@ -61,25 +62,59 @@ trait Relations { private[inc] def usedNames(src: File): Set[String] /** Records internal source file `src` as generating class file `prod` with top-level class `name`. */ + @deprecated("Record all products using `addProducts`.", "0.13.8") def addProduct(src: File, prod: File, name: String): Relations /** - * Records internal source file `src` as depending on class `dependsOn` in an external source file. - * If `inherited` is true, this dependency is recorded as coming from a public template in `src` extending something in `dependsOn` (an inheritance dependency). - * Whatever the value of `inherited`, the dependency is also recorded as a direct dependency. + * Records internal source file `src` as dependending on `dependsOn`. If this dependency is introduced + * by an inheritance relation, `inherited` is set to true. Note that in this case, the dependency is + * also registered as a direct dependency. */ + @deprecated("Record all external dependencies using `addExternalDeps`.", "0.13.8") def addExternalDep(src: File, dependsOn: String, inherited: Boolean): Relations /** Records internal source file `src` depending on a dependency binary dependency `dependsOn`.*/ + @deprecated("Record all binary dependencies using `addBinaryDeps`.", "0.13.8") def addBinaryDep(src: File, dependsOn: File): Relations /** - * TODO: Update comment below. * Records internal source file `src` as having direct dependencies on internal source files `directDependsOn` * and inheritance dependencies on `inheritedDependsOn`. Everything in `inheritedDependsOn` must be included in `directDependsOn`; * this method does not automatically record direct dependencies like `addExternalDep` does. */ - def addInternalSrcDeps(src: File, dependencies: Iterable[Dependency]): Relations + @deprecated("Record all internal dependencies using `addInternalSrcDeps(File, Iterable[InternalDependencies])`.", "0.13.8") + def addInternalSrcDeps(src: File, directDependsOn: Iterable[File], inheritedDependsOn: Iterable[File]): Relations + + /** + * Records that the file `src` generates products `products`, has internal dependencies `internalDeps`, + * has external dependencies `externalDeps` and binary dependencies `binaryDeps`. + */ + def addSource(src: File, + products: Iterable[(File, String)], + internalDeps: Iterable[InternalDependency], + externalDeps: Iterable[ExternalDependency], + binaryDeps: Iterable[(File, String, Stamp)]): Relations = + addProducts(src, products).addInternalSrcDeps(src, internalDeps).addExternalDeps(src, externalDeps).addBinaryDeps(src, binaryDeps) + + /** + * Records all the products `prods` generated by `src` + */ + private[inc] def addProducts(src: File, prods: Iterable[(File, String)]): Relations + + /** + * Records all the internal source dependencies `deps` of `src` + */ + private[inc] def addInternalSrcDeps(src: File, deps: Iterable[InternalDependency]): Relations + + /** + * Records all the external dependencies `deps` of `src` + */ + private[inc] def addExternalDeps(src: File, deps: Iterable[ExternalDependency]): Relations + + /** + * Records all the binary dependencies `deps` of `src` + */ + private[inc] def addBinaryDeps(src: File, deps: Iterable[(File, String, Stamp)]): Relations private[inc] def addUsedName(src: File, name: String): Relations @@ -104,6 +139,12 @@ trait Relations { /** The dependency relation between internal and external sources. This includes both direct and inherited dependencies.*/ def externalDep: Relation[File, String] + /** All the internal dependencies */ + private[inc] def internalDependencies: InternalDependencies + + /** All the external dependencies */ + private[inc] def externalDependencies: ExternalDependencies + /** * The source dependency relation between source files introduced by member reference. * @@ -228,9 +269,11 @@ object Relations { assert(nameHashing || (memberRefSrcDeps == emptySourceDependencies), "When name hashing is disabled the `memberRef` relation should be empty.") assert(!nameHashing || (directSrcDeps == emptySource), "When name hashing is enabled the `direct` relation should be empty.") - if (nameHashing) - Relations.make(srcProd, binaryDep, memberRefSrcDeps, inheritanceSrcDeps, classes, names) - else { + if (nameHashing) { + val internal = InternalDependencies(Map(DependencyByMemberRef -> mri.asInstanceOf[Relation[File, File]], DependencyByInheritance -> ii.asInstanceOf[Relation[File, File]])) + val external = ExternalDependencies(Map(DependencyByMemberRef -> mre.asInstanceOf[Relation[File, String]], DependencyByInheritance -> ie.asInstanceOf[Relation[File, String]])) + Relations.make(srcProd, binaryDep, internal, external, classes, names) + } else { assert(names.all.isEmpty, s"When `nameHashing` is disabled `names` relation should be empty: $names") Relations.make(srcProd, binaryDep, directSrcDeps, publicInheritedSrcDeps, classes) } @@ -240,7 +283,9 @@ object Relations { /** Tracks internal and external source dependencies for a specific dependency type, such as direct or inherited.*/ final class Source private[sbt] (val internal: Relation[File, File], val external: Relation[File, String]) { def addInternal(source: File, dependsOn: Iterable[File]): Source = new Source(internal + (source, dependsOn), external) + @deprecated("Use addExternal(File, Iterable[String])", "0.13.8") def addExternal(source: File, dependsOn: String): Source = new Source(internal, external + (source, dependsOn)) + def addExternal(source: File, dependsOn: Iterable[String]): Source = new Source(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]): Source = new Source(internal -- sources, external -- sources) def ++(o: Source): Source = new Source(internal ++ o.internal, external ++ o.external) @@ -265,7 +310,9 @@ object Relations { /** 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) + @deprecated("Use addExternal(File, Iterable[String])", "0.13.8") def addExternal(source: File, dependsOn: String): SourceDependencies = new SourceDependencies(internal, external + (source, dependsOn)) + def addExternal(source: File, dependsOn: Iterable[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) @@ -289,7 +336,7 @@ object Relations { def empty: Relations = empty(nameHashing = IncOptions.nameHashingDefault) private[inc] def empty(nameHashing: Boolean): Relations = if (nameHashing) - new MRelationsNameHashing(e, e, emptySourceDependencies, emptySourceDependencies, estr, estr) + new MRelationsNameHashing(e, e, InternalDependencies.empty, ExternalDependencies.empty, estr, estr) else new MRelationsDefaultImpl(e, e, es, es, estr) @@ -297,14 +344,73 @@ object Relations { new MRelationsDefaultImpl(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], - names: Relation[File, String]): Relations = - new MRelationsNameHashing(srcProd, binaryDep, memberRef = memberRef, inheritance = inheritance, - classes, names) + internalDependencies: InternalDependencies, externalDependencies: ExternalDependencies, + classes: Relation[File, String], names: Relation[File, String]): Relations = + new MRelationsNameHashing(srcProd, binaryDep, internalDependencies = internalDependencies, externalDependencies = externalDependencies, classes, names) 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) } +private object DependencyCollection { + /** + * Combine `m1` and `m2` such that the result contains all the dependencies they represent. + * `m1` is expected to be smaller than `m2`. + */ + def joinMaps[T](m1: Map[DependencyContext, Relation[File, T]], m2: Map[DependencyContext, Relation[File, T]]) = + m1.foldLeft(m2) { case (tmp, (key, values)) => tmp.updated(key, tmp.getOrElse(key, Relation.empty) ++ values) } +} + +private object InternalDependencies { + /** + * Constructs an empty `InteralDependencies` + */ + def empty = InternalDependencies(Map.empty) +} + +private case class InternalDependencies(dependencies: Map[DependencyContext, Relation[File, File]]) { + /** + * Adds `dep` to the dependencies + */ + def +(dep: InternalDependency): InternalDependencies = + InternalDependencies(dependencies.updated(dep.context, dependencies.getOrElse(dep.context, Relation.empty) + (dep.sourceFile, dep.targetFile))) + + /** + * Adds all `deps` to the dependencies + */ + def ++(deps: Iterable[InternalDependency]): InternalDependencies = deps.foldLeft(this)(_ + _) + def ++(deps: InternalDependencies): InternalDependencies = InternalDependencies(DependencyCollection.joinMaps(dependencies, deps.dependencies)) + + /** + * Removes all dependencies from `sources` to another file from the dependencies + */ + def --(sources: Iterable[File]): InternalDependencies = InternalDependencies(dependencies.mapValues(_ -- sources).filter(_._2.size > 0)) +} + +private object ExternalDependencies { + /** + * Constructs an empty `ExternalDependencies` + */ + def empty = ExternalDependencies(Map.empty) +} + +private case class ExternalDependencies(dependencies: Map[DependencyContext, Relation[File, String]]) { + /** + * Adds `dep` to the dependencies + */ + def +(dep: ExternalDependency): ExternalDependencies = ExternalDependencies(dependencies.updated(dep.context, dependencies.getOrElse(dep.context, Relation.empty) + (dep.sourceFile, dep.targetClassName))) + + /** + * Adds all `deps` to the dependencies + */ + def ++(deps: Iterable[ExternalDependency]): ExternalDependencies = deps.foldLeft(this)(_ + _) + def ++(deps: ExternalDependencies): ExternalDependencies = ExternalDependencies(DependencyCollection.joinMaps(dependencies, deps.dependencies)) + + /** + * Removes all dependencies from `sources` to another file from the dependencies + */ + def --(sources: Iterable[File]): ExternalDependencies = ExternalDependencies(dependencies.mapValues(_ -- sources).filter(_._2.size > 0)) +} + /** * An abstract class that contains common functionality inherited by two implementations of Relations trait. * @@ -393,28 +499,47 @@ private class MRelationsDefaultImpl(srcProd: Relation[File, File], binaryDep: Re new MRelationsDefaultImpl(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) + def addProducts(src: File, products: Iterable[(File, String)]): Relations = + new MRelationsDefaultImpl(srcProd ++ products.map(p => (src, p._1)), binaryDep, direct = direct, + publicInherited = publicInherited, classes ++ products.map(p => (src, p._2))) + + def addInternalSrcDeps(src: File, deps: Iterable[InternalDependency]) = { + val depsByInheritance = deps.collect { case InternalDependency(_, targetFile, DependencyByInheritance) => targetFile } + + val newD = direct.addInternal(src, deps.map(_.targetFile)) + val newI = publicInherited.addInternal(src, depsByInheritance) + new MRelationsDefaultImpl(srcProd, binaryDep, direct = newD, publicInherited = newI, classes) } - def addInternalSrcDeps(src: File, dependencies: Iterable[Dependency]): Relations = { - val depsByInheritance = dependencies.collect { - case Dependency(InternalDependencyEdge(fromSrc, toSrc), DependencyByInheritance) => - assert(src == fromSrc) - toSrc - } - val depsByMemberRef = dependencies.collect { - case Dependency(InternalDependencyEdge(fromSrc, toSrc), DependencyByMemberRef) => - assert(src == fromSrc) - toSrc - } - val newI = publicInherited.addInternal(src, depsByInheritance) - val newD = direct.addInternal(src, depsByMemberRef) + def addInternalSrcDeps(src: File, directDependsOn: Iterable[File], inheritedDependsOn: Iterable[File]): Relations = { + val directDeps = directDependsOn.map(d => InternalDependency(src, d, DependencyByMemberRef)) + val inheritedDeps = inheritedDependsOn.map(d => InternalDependency(src, d, DependencyByInheritance)) + addInternalSrcDeps(src, directDeps ++ inheritedDeps) + } + + def addExternalDeps(src: File, deps: Iterable[ExternalDependency]) = { + val depsByInheritance = deps.collect { case ExternalDependency(_, targetClassName, _, DependencyByInheritance) => targetClassName } + + val newD = direct.addExternal(src, deps.map(_.targetClassName)) + val newI = publicInherited.addExternal(src, depsByInheritance) + new MRelationsDefaultImpl(srcProd, binaryDep, direct = newD, publicInherited = newI, classes) } + def addExternalDep(src: File, dependsOn: String, inherited: Boolean): Relations = { + val newI = if (inherited) publicInherited.addExternal(src, dependsOn :: Nil) else publicInherited + val newD = direct.addExternal(src, dependsOn :: Nil) + new MRelationsDefaultImpl(srcProd, binaryDep, direct = newD, publicInherited = newI, classes) + } + + def addBinaryDeps(src: File, deps: Iterable[(File, String, Stamp)]) = + new MRelationsDefaultImpl(srcProd, binaryDep + (src, deps.map(_._1)), direct, publicInherited, classes) + + def addBinaryDep(src: File, dependsOn: File): Relations = + new MRelationsDefaultImpl(srcProd, binaryDep + (src, dependsOn), direct = direct, + publicInherited = publicInherited, classes) + def names: Relation[File, String] = throw new UnsupportedOperationException("Tracking of used names is not supported " + "when `nameHashing` is disabled.") @@ -423,9 +548,8 @@ private class MRelationsDefaultImpl(srcProd: Relation[File, File], binaryDep: Re throw new UnsupportedOperationException("Tracking of used names is not supported " + "when `nameHashing` is disabled.") - def addBinaryDep(src: File, dependsOn: File): Relations = - new MRelationsDefaultImpl(srcProd, binaryDep + (src, dependsOn), direct = direct, - publicInherited = publicInherited, classes) + override def externalDependencies: ExternalDependencies = ExternalDependencies(Map(DependencyByMemberRef -> direct.external, DependencyByInheritance -> publicInherited.external)) + override def internalDependencies: InternalDependencies = InternalDependencies(Map(DependencyByMemberRef -> direct.internal, DependencyByInheritance -> publicInherited.internal)) def ++(o: Relations): Relations = { if (nameHashing != o.nameHashing) @@ -508,8 +632,8 @@ private class MRelationsDefaultImpl(srcProd: Relation[File, File], binaryDep: Re * needed by the name hashing invalidation algorithm. */ private class MRelationsNameHashing(srcProd: Relation[File, File], binaryDep: Relation[File, File], - // memberRef should include everything in inherited - val memberRef: SourceDependencies, val inheritance: SourceDependencies, + val internalDependencies: InternalDependencies, + val externalDependencies: ExternalDependencies, classes: Relation[File, String], val names: Relation[File, String]) extends MRelationsCommon(srcProd, binaryDep, classes) { def direct: Source = @@ -525,42 +649,59 @@ private class MRelationsNameHashing(srcProd: Relation[File, File], binaryDep: Re def externalDep: Relation[File, String] = memberRef.external def addProduct(src: File, prod: File, name: String): Relations = - new MRelationsNameHashing(srcProd + (src, prod), binaryDep, memberRef = memberRef, - inheritance = inheritance, classes + (src, name), names = names) + new MRelationsNameHashing(srcProd + (src, prod), binaryDep, internalDependencies = internalDependencies, + externalDependencies = externalDependencies, classes + (src, name), names = names) - 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 MRelationsNameHashing(srcProd, binaryDep, memberRef = newMR, inheritance = newIH, classes, - names = names) - } + def addProducts(src: File, products: Iterable[(File, String)]): Relations = + new MRelationsNameHashing(srcProd ++ products.map(p => (src, p._1)), binaryDep, + internalDependencies = internalDependencies, externalDependencies = externalDependencies, + classes ++ products.map(p => (src, p._2)), names = names) + + def addInternalSrcDeps(src: File, deps: Iterable[InternalDependency]) = + new MRelationsNameHashing(srcProd, binaryDep, internalDependencies = internalDependencies ++ deps, + externalDependencies = externalDependencies, classes, names) def addInternalSrcDeps(src: File, dependsOn: Iterable[File], inherited: Iterable[File]): Relations = { - val newIH = inheritance.addInternal(src, inherited) - val newMR = memberRef.addInternal(src, dependsOn) - new MRelationsNameHashing(srcProd, binaryDep, memberRef = newMR, inheritance = newIH, classes, - names = names) + val memberRefDeps = dependsOn.map(InternalDependency(src, _, DependencyByMemberRef)) + val inheritedDeps = inherited.map(InternalDependency(src, _, DependencyByInheritance)) + addInternalSrcDeps(src, memberRefDeps ++ inheritedDeps) } - def addUsedName(src: File, name: String): Relations = - new MRelationsNameHashing(srcProd, binaryDep, memberRef = memberRef, - inheritance = inheritance, classes, names = names + (src, name)) + def addExternalDeps(src: File, deps: Iterable[ExternalDependency]) = + new MRelationsNameHashing(srcProd, binaryDep, internalDependencies = internalDependencies, + externalDependencies = externalDependencies ++ deps, classes, names) + + def addExternalDep(src: File, dependsOn: String, inherited: Boolean): Relations = + throw new UnsupportedOperationException("This method is not supported when `nameHashing` flag is enabled.") + + def addBinaryDeps(src: File, deps: Iterable[(File, String, Stamp)]) = + new MRelationsNameHashing(srcProd, binaryDep + (src, deps.map(_._1)), internalDependencies = internalDependencies, + externalDependencies = externalDependencies, classes, names) def addBinaryDep(src: File, dependsOn: File): Relations = - new MRelationsNameHashing(srcProd, binaryDep + (src, dependsOn), memberRef = memberRef, - inheritance = inheritance, classes, names = names) + new MRelationsNameHashing(srcProd, binaryDep + (src, dependsOn), internalDependencies = internalDependencies, + externalDependencies = externalDependencies, classes, names = names) + + def addUsedName(src: File, name: String): Relations = + new MRelationsNameHashing(srcProd, binaryDep, internalDependencies = internalDependencies, + externalDependencies = externalDependencies, classes, names = names + (src, name)) + + override def inheritance: SourceDependencies = + new SourceDependencies(internalDependencies.dependencies.getOrElse(DependencyByInheritance, Relation.empty), externalDependencies.dependencies.getOrElse(DependencyByInheritance, Relation.empty)) + override def memberRef: SourceDependencies = + new SourceDependencies(internalDependencies.dependencies.getOrElse(DependencyByMemberRef, Relation.empty), externalDependencies.dependencies.getOrElse(DependencyByMemberRef, Relation.empty)) def ++(o: Relations): Relations = { if (!o.nameHashing) throw new UnsupportedOperationException("The `++` operation is not supported for relations " + "with different values of `nameHashing` flag.") new MRelationsNameHashing(srcProd ++ o.srcProd, binaryDep ++ o.binaryDep, - memberRef = memberRef ++ o.memberRef, inheritance = inheritance ++ o.inheritance, + internalDependencies = internalDependencies ++ o.internalDependencies, externalDependencies = externalDependencies ++ o.externalDependencies, classes ++ o.classes, names = names ++ o.names) } def --(sources: Iterable[File]) = new MRelationsNameHashing(srcProd -- sources, binaryDep -- sources, - memberRef = memberRef -- sources, inheritance = inheritance -- sources, classes -- sources, + internalDependencies = internalDependencies -- sources, externalDependencies = externalDependencies -- sources, classes -- sources, names = names -- sources) def groupBy[K](f: File => K): Map[K, Relations] = { diff --git a/compile/inc/src/test/scala/sbt/inc/TestCaseGenerators.scala b/compile/inc/src/test/scala/sbt/inc/TestCaseGenerators.scala index 2c8d7a505..d0628a7e4 100644 --- a/compile/inc/src/test/scala/sbt/inc/TestCaseGenerators.scala +++ b/compile/inc/src/test/scala/sbt/inc/TestCaseGenerators.scala @@ -175,7 +175,9 @@ object TestCaseGenerators { inheritance <- genSubRSourceDependencies(memberRef) classes <- genStringRelation(srcs) names <- genStringRelation(srcs) - } yield Relations.make(srcProd, binaryDep, memberRef, inheritance, classes, names) + internal <- InternalDependencies(Map(DependencyByMemberRef -> memberRef.internal, DependencyByInheritance -> inheritance.internal)) + external <- ExternalDependencies(Map(DependencyByMemberRef -> memberRef.external, DependencyByInheritance -> inheritance.external)) + } yield Relations.make(srcProd, binaryDep, internal, external, classes, names) def genAnalysis(nameHashing: Boolean): Gen[Analysis] = for { rels <- if (nameHashing) genRelationsNameHashing else genRelations diff --git a/compile/persist/src/main/scala/sbt/inc/AnalysisFormats.scala b/compile/persist/src/main/scala/sbt/inc/AnalysisFormats.scala index 53c7d8cdb..1af8278aa 100644 --- a/compile/persist/src/main/scala/sbt/inc/AnalysisFormats.scala +++ b/compile/persist/src/main/scala/sbt/inc/AnalysisFormats.scala @@ -103,7 +103,9 @@ object AnalysisFormats { 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 `nameHashing` flag is enabled.") - Relations.make(srcProd, binaryDep, memberRef, inheritance, classes, names) + val internalDependencies = InternalDependencies(Map(DependencyByMemberRef -> memberRef.internal, DependencyByInheritance -> inheritance.internal)) + val externalDependencies = ExternalDependencies(Map(DependencyByMemberRef -> memberRef.external, DependencyByInheritance -> inheritance.external)) + Relations.make(srcProd, binaryDep, internalDependencies, externalDependencies, classes, names) } else { def isEmpty(sourceDependencies: SourceDependencies): Boolean = sourceDependencies.internal.all.isEmpty && sourceDependencies.external.all.isEmpty From b1a608cf1d2708d27a37d938695b2836d67656c1 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Sat, 13 Sep 2014 00:21:22 +0200 Subject: [PATCH 3/4] Use new infrastructure in `AnalysisCallback` A new infrastructure to register sources and their dependencies has been introduced in `c09a391`. This commit brings the required modifications to `AnalysisCallback`, so that it uses it. --- .../inc/src/main/scala/sbt/inc/Compile.scala | 60 ++++++++++++------- 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/compile/inc/src/main/scala/sbt/inc/Compile.scala b/compile/inc/src/main/scala/sbt/inc/Compile.scala index f9b43edfa..dd2dc4223 100644 --- a/compile/inc/src/main/scala/sbt/inc/Compile.scala +++ b/compile/inc/src/main/scala/sbt/inc/Compile.scala @@ -79,9 +79,8 @@ private final class AnalysisCallback(internalMap: File => Option[File], external private[this] val sourceDeps = new HashMap[File, Set[File]] // inherited internal source dependencies private[this] val inheritedSourceDeps = new HashMap[File, Set[File]] - // external source dependencies: - // (internal source, external source depended on, API of external dependency, true if an inheritance dependency) - private[this] val extSrcDeps = new ListBuffer[(File, String, Source, Boolean)] + // external source dependencies + private[this] val extSrcDeps = new HashMap[File, Iterable[ExternalDependency]] private[this] val binaryClassName = new HashMap[File, String] // source files containing a macro def. private[this] val macroSources = Set[File]() @@ -106,7 +105,12 @@ private final class AnalysisCallback(internalMap: File => Option[File], external binaryClassName.put(binary, className) add(binaryDeps, source, binary) } - def externalSourceDependency(t4: (File, String, Source, Boolean)) = extSrcDeps += t4 + + // The type corresponds to : (internal source, external source depended on, API of external dependency, true if an inheritance dependency) + def externalSourceDependency(t4: (File, String, Source, Boolean)) = { + val dependency = ExternalDependency(t4._1, t4._2, t4._3, if (t4._4) DependencyByInheritance else DependencyByMemberRef) + extSrcDeps += t4._1 -> (extSrcDeps.getOrElse(t4._1, Nil) ++ List(dependency)) + } def binaryDependency(classFile: File, name: String, source: File, inherited: Boolean) = internalMap(classFile) match { @@ -161,10 +165,26 @@ private final class AnalysisCallback(internalMap: File => Option[File], external def nameHashing: Boolean = options.nameHashing - def get: Analysis = addUsedNames(addCompilation(addExternals(addBinaries(addProducts(addSources(Analysis.empty(nameHashing = nameHashing))))))) - 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)) - def addSources(base: Analysis): Analysis = + def get: Analysis = addUsedNames(addCompilation(addProductsAndDeps(Analysis.empty(nameHashing = nameHashing)))) + + def getOrNil[A, B](m: collection.Map[A, Seq[B]], a: A): Seq[B] = m.get(a).toList.flatten + def addCompilation(base: Analysis): Analysis = base.copy(compilations = base.compilations.add(compilation)) + def addUsedNames(base: Analysis): Analysis = (base /: usedNames) { + case (a, (src, names)) => + (a /: names) { case (a, name) => a.copy(relations = a.relations.addUsedName(src, name)) } + } + + // This is no longer used by the new implementation relative to Dependency contexts + // See https://github.com/sbt/sbt/issues/1340 + def addAll[A, B](base: Analysis, m: Map[A, Set[B]])(f: (Analysis, A, B) => Analysis): Analysis = + (base /: m) { + case (outer, (a, bs)) => + (outer /: bs) { (inner, b) => + f(inner, a, b) + } + } + + def addProductsAndDeps(base: Analysis): Analysis = (base /: apis) { case (a, (src, api)) => val stamp = current.internalSource(src) @@ -175,21 +195,15 @@ private final class AnalysisCallback(internalMap: File => Option[File], external val info = SourceInfos.makeInfo(getOrNil(reporteds, src), getOrNil(unreporteds, src)) val direct = sourceDeps.getOrElse(src, Nil: Iterable[File]) val publicInherited = inheritedSourceDeps.getOrElse(src, Nil: Iterable[File]) - a.addSource(src, s, stamp, direct, publicInherited, info) - } - def getOrNil[A, B](m: collection.Map[A, Seq[B]], a: A): Seq[B] = m.get(a).toList.flatten - def addExternals(base: Analysis): Analysis = (base /: extSrcDeps) { case (a, (source, name, api, inherited)) => a.addExternalDep(source, name, api, inherited) } - def addCompilation(base: Analysis): Analysis = base.copy(compilations = base.compilations.add(compilation)) - def addUsedNames(base: Analysis): Analysis = (base /: usedNames) { - case (a, (src, names)) => - (a /: names) { case (a, name) => a.copy(relations = a.relations.addUsedName(src, name)) } - } + val binaries = binaryDeps.getOrElse(src, Nil: Iterable[File]) + val prods = classes.getOrElse(src, Nil: Iterable[(File, String)]) + + val products = prods.map { case (prod, name) => (prod, name, current product prod) } + val internalDeps = direct.map(InternalDependency(src, _, DependencyByMemberRef)) ++ publicInherited.map(InternalDependency(src, _, DependencyByInheritance)) + val externalDeps = extSrcDeps.getOrElse(src, Nil: Iterable[ExternalDependency]) + val binDeps = binaries.map(d => (d, binaryClassName(d), current binary d)) + + a.addSource(src, s, stamp, info, products, internalDeps, externalDeps, binDeps) - def addAll[A, B](base: Analysis, m: Map[A, Set[B]])(f: (Analysis, A, B) => Analysis): Analysis = - (base /: m) { - case (outer, (a, bs)) => - (outer /: bs) { (inner, b) => - f(inner, a, b) - } } } From 191c3ed675bfb38f3c7d52d9123e7b36b84affd1 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Tue, 18 Nov 2014 13:06:37 +0100 Subject: [PATCH 4/4] Use new infrastructure in `AnalysisTest` A new infrastructure to register sources and their dependencies has been introduced in `c09a391`. This commit brings the required modifications to `AnalysisTest`, so that it uses it. --- .../src/test/scala/sbt/inc/AnalysisTest.scala | 49 ++++++++++--------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/compile/inc/src/test/scala/sbt/inc/AnalysisTest.scala b/compile/inc/src/test/scala/sbt/inc/AnalysisTest.scala index 437176881..9df812685 100644 --- a/compile/inc/src/test/scala/sbt/inc/AnalysisTest.scala +++ b/compile/inc/src/test/scala/sbt/inc/AnalysisTest.scala @@ -21,34 +21,35 @@ object AnalysisTest extends Properties("Analysis") { val sourceInfos = SourceInfos.makeInfo(Nil, Nil) // a - var a = Analysis.empty(false) - a = a.addProduct(aScala, f("A.class"), exists, "A") - a = a.addProduct(aScala, f("A$.class"), exists, "A$") - a = a.addSource(aScala, aSource, exists, Nil, Nil, sourceInfos) - a = a.addBinaryDep(aScala, f("x.jar"), "x", exists) - a = a.addExternalDep(aScala, "C", cSource, inherited = false) + val aProducts = (f("A.class"), "A", exists) :: (f("A$.class"), "A$", exists) :: Nil + val aInternal = Nil + val aExternal = ExternalDependency(aScala, "C", cSource, DependencyByMemberRef) :: Nil + val aBinary = (f("x.jar"), "x", exists) :: Nil + + val a = Analysis.empty(false).addSource(aScala, aSource, exists, sourceInfos, aProducts, aInternal, aExternal, aBinary) // b - var b = Analysis.empty(false) - b = b.addProduct(bScala, f("B.class"), exists, "B") - b = b.addProduct(bScala, f("B$.class"), exists, "B$") - b = b.addSource(bScala, bSource, exists, Nil, Nil, sourceInfos) - b = b.addBinaryDep(bScala, f("x.jar"), "x", exists) - b = b.addBinaryDep(bScala, f("y.jar"), "y", exists) - b = b.addExternalDep(bScala, "A", aSource, inherited = true) + val bProducts = (f("B.class"), "B", exists) :: (f("B$.class"), "B$", exists) :: Nil + val bInternal = Nil + val bExternal = ExternalDependency(bScala, "A", aSource, DependencyByInheritance) :: Nil + val bBinary = (f("x.jar"), "x", exists) :: (f("y.jar"), "y", exists) :: Nil + + val b = Analysis.empty(false).addSource(bScala, bSource, exists, sourceInfos, bProducts, bInternal, bExternal, bBinary) // ab - var ab = Analysis.empty(false) - ab = ab.addProduct(aScala, f("A.class"), exists, "A") - ab = ab.addProduct(aScala, f("A$.class"), exists, "A$") - ab = ab.addProduct(bScala, f("B.class"), exists, "B") - ab = ab.addProduct(bScala, f("B$.class"), exists, "B$") - ab = ab.addSource(aScala, aSource, exists, Nil, Nil, sourceInfos) - ab = ab.addSource(bScala, bSource, exists, aScala :: Nil, aScala :: Nil, sourceInfos) - ab = ab.addBinaryDep(aScala, f("x.jar"), "x", exists) - ab = ab.addBinaryDep(bScala, f("x.jar"), "x", exists) - ab = ab.addBinaryDep(bScala, f("y.jar"), "y", exists) - ab = ab.addExternalDep(aScala, "C", cSource, inherited = false) + // `b` has an external dependency on `a` that will be internalized + val abAProducts = (f("A.class"), "A", exists) :: (f("A$.class"), "A$", exists) :: Nil + val abAInternal = Nil + val abAExternal = ExternalDependency(aScala, "C", cSource, DependencyByMemberRef) :: Nil + val abABinary = (f("x.jar"), "x", exists) :: Nil + + val abBProducts = (f("B.class"), "B", exists) :: (f("B$.class"), "B$", exists) :: Nil + val abBInternal = InternalDependency(bScala, aScala, DependencyByMemberRef) :: InternalDependency(bScala, aScala, DependencyByInheritance) :: Nil + val abBExternal = Nil + val abBBinary = (f("x.jar"), "x", exists) :: (f("y.jar"), "y", exists) :: Nil + + val ab = Analysis.empty(false).addSource(aScala, aSource, exists, sourceInfos, abAProducts, abAInternal, abAExternal, abABinary) + .addSource(bScala, bSource, exists, sourceInfos, abBProducts, abBInternal, abBExternal, abBBinary) val split: Map[String, Analysis] = ab.groupBy({ f: File => f.getName.substring(0, 1) })