diff --git a/compile/inc/src/main/scala/sbt/inc/Analysis.scala b/compile/inc/src/main/scala/sbt/inc/Analysis.scala index 29855ee71..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, 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 + @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,17 +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, 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, 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(src: File, dep: String, depAPI: Source, inherited: Boolean): Analysis = - copy(stamps, apis.markExternalAPI(dep, depAPI), relations.addExternalDep(src, dep, 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/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) - } } } 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..a10d33d00 --- /dev/null +++ b/compile/inc/src/main/scala/sbt/inc/Dependency.scala @@ -0,0 +1,30 @@ +package sbt.inc + +import java.io.File +import xsbti.api.Source + +/** + * Represents contextual information about particular depedency edge. See comments in + * subtypes for examples of particular contexts. + */ +private[inc] sealed abstract class DependencyContext + +/** + * Marks dependency edge introduced by referring to a class through inheritance as in + * + * class A extends B + * + * Each dependency by inheritance introduces corresponding dependency by member reference. + */ +private[inc] final case object DependencyByInheritance extends DependencyContext +private[inc] final case object DependencyByMemberRef extends DependencyContext + +/** + * 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 6a60ca418..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,16 +62,19 @@ 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 /** @@ -78,8 +82,40 @@ trait Relations { * and inheritance dependencies on `inheritedDependsOn`. Everything in `inheritedDependsOn` must be included in `directDependsOn`; * this method does not automatically record direct dependencies like `addExternalDep` does. */ + @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 /** Concatenates the two relations. Acts naively, i.e., doesn't internalize external deps on added files. */ @@ -103,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. * @@ -227,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) } @@ -239,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) @@ -264,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) @@ -288,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) @@ -296,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. * @@ -392,18 +499,46 @@ 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, 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, 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 " + @@ -413,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) @@ -498,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 = @@ -515,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/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) }) 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