require a smaller set of sources in the third (transitive) step of inc. compilation

This commit is contained in:
Mark Harrah 2012-08-26 13:44:32 -04:00
parent b4e64d37db
commit 8c0a2fbe1c
1 changed files with 43 additions and 2 deletions

View File

@ -108,7 +108,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)
@ -132,7 +132,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)
@ -140,6 +140,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)
@ -206,6 +231,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] =