From 62fddc2c95ac64253ce4d9a0eb3f4852770ffaef Mon Sep 17 00:00:00 2001 From: Grzegorz Kossakowski Date: Wed, 14 May 2014 00:09:26 +0200 Subject: [PATCH] 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 29855ee718..eaf4cd0b3f 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 0000000000..a595d246c7 --- /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 a42794eb67..685aac84d7 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 @@ -340,12 +341,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 " +