From d6a45e82c306e8a8b8d4e85ed34e37ad6227d321 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Sun, 26 Aug 2012 13:44:32 -0400 Subject: [PATCH] require a smaller set of sources in the third (transitive) step of inc. compilation --- compile/inc/Incremental.scala | 45 +++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/compile/inc/Incremental.scala b/compile/inc/Incremental.scala index 917223f78..97e23b785 100644 --- a/compile/inc/Incremental.scala +++ b/compile/inc/Incremental.scala @@ -106,7 +106,7 @@ object Incremental val dependsOnSrc = previous.usesInternalSrc _ val propagated = if(transitive) - invalidateTransitive(dependsOnSrc, changes.modified, log) + transitiveDependencies(dependsOnSrc, changes.modified, log) else invalidateStage2(dependsOnSrc, changes.modified, log) @@ -130,7 +130,7 @@ object Incremental def invalidateDirect(dependsOnSrc: File => Set[File], modified: Set[File]): Set[File] = (modified flatMap dependsOnSrc) -- modified - /** Invalidates transitive source dependencies including `modified`. It excludes any sources that were recompiled during the previous run.*/ + /** Invalidates transitive source dependencies including `modified`.*/ @tailrec def invalidateTransitive(dependsOnSrc: File => Set[File], modified: Set[File], log: Logger): Set[File] = { val newInv = invalidateDirect(dependsOnSrc, modified) @@ -138,6 +138,31 @@ object Incremental if(newInv.isEmpty) modified else invalidateTransitive(dependsOnSrc, modified ++ newInv, log) } + /** Returns the transitive source dependencies of `initial`, excluding the files in `initial` in most cases. + * In three-stage incremental compilation, the `initial` files are the sources from step 2 that had API changes. + * Because strongly connected components (cycles) are included in step 2, the files with API changes shouldn't + * need to be compiled in step 3 if their dependencies haven't changed. If there are new cycles introduced after + * step 2, these can require step 2 sources to be included in step 3 recompilation. + */ + def transitiveDependencies(dependsOnSrc: File => Set[File], initial: Set[File], log: Logger): Set[File] = + { + // include any file that depends on included files + def recheck(included: Set[File], process: Set[File], excluded: Set[File]): Set[File] = + { + val newIncludes = (process flatMap dependsOnSrc) ** excluded + if(newIncludes.isEmpty) + included + else + recheck(included ++ newIncludes, newIncludes, excluded -- newIncludes) + } + val transitiveOnly = transitiveDepsOnly(initial)(dependsOnSrc) + log.debug("Step 3 transitive dependencies:\n\t" + transitiveOnly) + val stage3 = recheck(transitiveOnly, transitiveOnly, initial) + log.debug("Step 3 sources from new step 2 source dependencies:\n\t" + (stage3 -- transitiveOnly)) + stage3 + } + + def invalidateStage2(dependsOnSrc: File => Set[File], initial: Set[File], log: Logger): Set[File] = { val initAndImmediate = initial ++ initial.flatMap(dependsOnSrc) @@ -204,6 +229,22 @@ object Incremental def orEmpty(o: Option[Source]): Source = o getOrElse APIs.emptySource def orTrue(o: Option[Boolean]): Boolean = o getOrElse true + + private[this] def transitiveDepsOnly[T](nodes: Iterable[T])(dependencies: T => Iterable[T]): Set[T] = + { + val xs = new collection.mutable.HashSet[T] + def all(ns: Iterable[T]): Unit = ns.foreach(visit) + def visit(n: T): Unit = + if (!xs.contains(n)) { + xs += n + all(dependencies(n)) + } + all(nodes) + xs --= nodes + xs.toSet + } + + // unmodifiedSources should not contain any sources in the previous compilation run // (this may unnecessarily invalidate them otherwise) /*def scopeInvalidation(previous: Analysis, otherSources: Set[File], names: NameChanges): Set[File] =