three-stage incremental compilation

This commit is contained in:
Mark Harrah 2012-08-25 06:08:59 -04:00
parent 61dee253dd
commit 089b4e284c
7 changed files with 95 additions and 11 deletions

View File

@ -22,14 +22,14 @@ object Incremental
}
val initialInv = invalidateInitial(previous.relations, initialChanges, log)
log.debug("Initially invalidated: " + initialInv)
val analysis = cycle(initialInv, binaryChanges, previous, doCompile, log)
val analysis = cycle(initialInv, binaryChanges, previous, doCompile, 1, log)
(!initialInv.isEmpty, analysis)
}
val incDebugProp = "xsbt.inc.debug"
// TODO: the Analysis for the last successful compilation should get returned + Boolean indicating success
// TODO: full external name changes, scopeInvalidations
def cycle(invalidated: Set[File], binaryChanges: DependencyChanges, previous: Analysis, doCompile: (Set[File], DependencyChanges) => Analysis, log: Logger): Analysis =
def cycle(invalidated: Set[File], binaryChanges: DependencyChanges, previous: Analysis, doCompile: (Set[File], DependencyChanges) => Analysis, cycleNum: Int, log: Logger): Analysis =
if(invalidated.isEmpty)
previous
else
@ -43,9 +43,8 @@ object Incremental
debug("********* Merged: \n" + merged.relations + "\n*********")
val incChanges = changedIncremental(invalidated, previous.apis.internalAPI _, merged.apis.internalAPI _)
debug("Changes:\n" + incChanges)
val incInv = invalidateIncremental(merged.relations, incChanges, invalidated, log)
log.debug("Incrementally invalidated: " + incInv)
cycle(incInv, emptyChanges, merged, doCompile, log)
val incInv = invalidateIncremental(merged.relations, incChanges, invalidated, cycleNum >= 2, log)
cycle(incInv, emptyChanges, merged, doCompile, cycleNum+1, log)
}
private[this] def emptyChanges: DependencyChanges = new DependencyChanges {
val modifiedBinaries = new Array[File](0)
@ -104,13 +103,22 @@ object Incremental
val (changed, unmodified) = inBoth.partition(existingModified)
}
def invalidateIncremental(previous: Relations, changes: APIChanges[File], recompiledSources: Set[File], log: Logger): Set[File] =
def invalidateIncremental(previous: Relations, changes: APIChanges[File], recompiledSources: Set[File], transitive: Boolean, log: Logger): Set[File] =
{
val inv =
invalidateTransitive(previous.usesInternalSrc _, changes.modified, log) ++
invalidateDuplicates(previous)
// ++ scopeInvalidations(previous.extAPI _, changes.modified, changes.names)
if((inv -- recompiledSources).isEmpty) Set.empty else inv
val dependsOnSrc = previous.usesInternalSrc _
val propagated =
if(transitive)
invalidateTransitive(dependsOnSrc, changes.modified, log)
else
invalidateStage2(dependsOnSrc, changes.modified, log)
val dups = invalidateDuplicates(previous)
if(dups.nonEmpty)
log.debug("Invalidated due to generated class file collision: " + dups)
val inv = propagated ++ dups // ++ scopeInvalidations(previous.extAPI _, changes.modified, changes.names)
val newlyInvalidated = inv -- recompiledSources
if(newlyInvalidated.isEmpty) Set.empty else inv
}
/** Invalidate all sources that claim to produce the same class file as another source file. */
@ -132,6 +140,17 @@ object Incremental
if(newInv.isEmpty) modified else invalidateTransitive(dependsOnSrc, modified ++ newInv, log)
}
def invalidateStage2(dependsOnSrc: File => Set[File], initial: Set[File], log: Logger): Set[File] =
{
val initAndImmediate = initial ++ initial.flatMap(dependsOnSrc)
log.debug("Step 2 changed sources and immdediate dependencies:\n\t" + initAndImmediate)
val components = sbt.inc.StronglyConnected(initAndImmediate)(dependsOnSrc)
log.debug("Non-trivial strongly connected components: " + components.filter(_.size > 1).mkString("\n\t", "\n\t", ""))
val inv = components.filter(initAndImmediate.exists).flatten
log.debug("Step 2 invalidated sources:\n\t" + inv)
inv
}
/** Invalidates sources based on initially detected 'changes' to the sources, products, and dependencies.*/
def invalidateInitial(previous: Relations, changes: InitialChanges, log: Logger): Set[File] =
{

View File

@ -0,0 +1,51 @@
package sbt.inc
// stolen from Josh
object StronglyConnected
{
def apply[N](nodes: Iterable[N])(dependencies: N => Iterable[N]): Set[Set[N]] =
{
val stack = new collection.mutable.Stack[N]
val onStack = new collection.mutable.HashSet[N]
val scc = new collection.mutable.ArrayBuffer[Set[N]]
val index = new collection.mutable.ArrayBuffer[N]
val lowLink = new collection.mutable.HashMap[N, Int]
def tarjanImpl(v: N)
{
index += v
lowLink(v) = index.size-1
stack.push(v)
onStack += v
for(n <- dependencies(v))
{
if( !index.contains(n) )
{
tarjanImpl(n)
lowLink(v) = math.min(lowLink(v), lowLink(n))
}
else if(onStack(n))
lowLink(v) = math.min(lowLink(v), index.indexOf(n))
}
if(lowLink(v) == index.indexOf(v))
{
val components = new collection.mutable.ArrayBuffer[N]
def popLoop()
{
val popped = stack.pop()
onStack -= popped
components.append(popped)
if(popped != v) popLoop()
}
popLoop()
scc.append(components.toSet)
}
}
for(node <- nodes)
if( !index.contains(node) )
tarjanImpl(node)
scc.toSet
}
}

View File

@ -0,0 +1 @@
trait A

View File

@ -0,0 +1 @@
trait B extends A

View File

@ -0,0 +1,4 @@
trait C extends B
{
def x = 3
}

View File

@ -0,0 +1,4 @@
trait A
{
def x = 5
}

View File

@ -0,0 +1,4 @@
> compile
$ copy-file changes/A2.scala A.scala
-> compile