Skip to content

Commit

Permalink
Sketch of abstracting over dependency details in incremental compiler
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
gkossakowski committed May 13, 2014
1 parent 066a12c commit 62fddc2
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 13 deletions.
22 changes: 16 additions & 6 deletions compile/inc/src/main/scala/sbt/inc/Analysis.scala
Expand Up @@ -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. */
Expand Down Expand Up @@ -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)
Expand Down
48 changes: 48 additions & 0 deletions 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
24 changes: 17 additions & 7 deletions compile/inc/src/main/scala/sbt/inc/Relations.scala
Expand Up @@ -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

Expand Down Expand Up @@ -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 " +
Expand Down

0 comments on commit 62fddc2

Please sign in to comment.