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 " +