From 089b4e284c26601fda72b4b4cc0403865bcf3dae Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Sat, 25 Aug 2012 06:08:59 -0400 Subject: [PATCH] three-stage incremental compilation --- compile/inc/Incremental.scala | 41 +++++++++++---- compile/inc/StronglyConnected.scala | 51 +++++++++++++++++++ .../transitive-inherit/A.scala | 1 + .../transitive-inherit/B.scala | 1 + .../transitive-inherit/C.scala | 4 ++ .../transitive-inherit/changes/A2.scala | 4 ++ .../transitive-inherit/test | 4 ++ 7 files changed, 95 insertions(+), 11 deletions(-) create mode 100644 compile/inc/StronglyConnected.scala create mode 100644 sbt/src/sbt-test/source-dependencies/transitive-inherit/A.scala create mode 100644 sbt/src/sbt-test/source-dependencies/transitive-inherit/B.scala create mode 100644 sbt/src/sbt-test/source-dependencies/transitive-inherit/C.scala create mode 100644 sbt/src/sbt-test/source-dependencies/transitive-inherit/changes/A2.scala create mode 100644 sbt/src/sbt-test/source-dependencies/transitive-inherit/test diff --git a/compile/inc/Incremental.scala b/compile/inc/Incremental.scala index f913aaafe..38da6200c 100644 --- a/compile/inc/Incremental.scala +++ b/compile/inc/Incremental.scala @@ -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] = { diff --git a/compile/inc/StronglyConnected.scala b/compile/inc/StronglyConnected.scala new file mode 100644 index 000000000..89d8ac4a0 --- /dev/null +++ b/compile/inc/StronglyConnected.scala @@ -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 + } +} diff --git a/sbt/src/sbt-test/source-dependencies/transitive-inherit/A.scala b/sbt/src/sbt-test/source-dependencies/transitive-inherit/A.scala new file mode 100644 index 000000000..0eab80adc --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/transitive-inherit/A.scala @@ -0,0 +1 @@ +trait A diff --git a/sbt/src/sbt-test/source-dependencies/transitive-inherit/B.scala b/sbt/src/sbt-test/source-dependencies/transitive-inherit/B.scala new file mode 100644 index 000000000..310eb5b60 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/transitive-inherit/B.scala @@ -0,0 +1 @@ +trait B extends A \ No newline at end of file diff --git a/sbt/src/sbt-test/source-dependencies/transitive-inherit/C.scala b/sbt/src/sbt-test/source-dependencies/transitive-inherit/C.scala new file mode 100644 index 000000000..9e7708627 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/transitive-inherit/C.scala @@ -0,0 +1,4 @@ +trait C extends B +{ + def x = 3 +} \ No newline at end of file diff --git a/sbt/src/sbt-test/source-dependencies/transitive-inherit/changes/A2.scala b/sbt/src/sbt-test/source-dependencies/transitive-inherit/changes/A2.scala new file mode 100644 index 000000000..609031a44 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/transitive-inherit/changes/A2.scala @@ -0,0 +1,4 @@ +trait A +{ + def x = 5 +} \ No newline at end of file diff --git a/sbt/src/sbt-test/source-dependencies/transitive-inherit/test b/sbt/src/sbt-test/source-dependencies/transitive-inherit/test new file mode 100644 index 000000000..74fa79ef9 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/transitive-inherit/test @@ -0,0 +1,4 @@ +> compile + +$ copy-file changes/A2.scala A.scala +-> compile \ No newline at end of file