mirror of https://github.com/sbt/sbt.git
Merge pull request #941 from gkossakowski/apichanges-refactor
More detailed logging and APIChanges refactoring
This commit is contained in:
commit
f2bdeff486
|
|
@ -8,6 +8,7 @@ import xsbti.api._
|
|||
import Function.tupled
|
||||
import scala.collection.{immutable, mutable}
|
||||
|
||||
@deprecated("This class is not used in incremental compiler and will be removed in next major version.", "0.13.2")
|
||||
class NameChanges(val newTypes: Set[String], val removedTypes: Set[String], val newTerms: Set[String], val removedTerms: Set[String])
|
||||
{
|
||||
override def toString =
|
||||
|
|
@ -19,11 +20,13 @@ class NameChanges(val newTypes: Set[String], val removedTypes: Set[String], val
|
|||
|
||||
object TopLevel
|
||||
{
|
||||
@deprecated("The NameChanges class is deprecated and will be removed in next major version.", "0.13.2")
|
||||
def nameChanges(a: Iterable[Source], b: Iterable[Source]): NameChanges = {
|
||||
val api = (_: Source).api
|
||||
apiNameChanges(a map api, b map api)
|
||||
}
|
||||
/** Identifies removed and new top-level definitions by name. */
|
||||
@deprecated("The NameChanges class is deprecated and will be removed in next major version.", "0.13.2")
|
||||
def apiNameChanges(a: Iterable[SourceAPI], b: Iterable[SourceAPI]): NameChanges =
|
||||
{
|
||||
def changes(s: Set[String], t: Set[String]) = (s -- t, t -- s)
|
||||
|
|
|
|||
|
|
@ -8,11 +8,21 @@ import xsbt.api.NameChanges
|
|||
import java.io.File
|
||||
|
||||
final case class InitialChanges(internalSrc: Changes[File], removedProducts: Set[File], binaryDeps: Set[File], external: APIChanges[String])
|
||||
final class APIChanges[T](val modified: Set[T], val names: NameChanges)
|
||||
final class APIChanges[T](val apiChanges: Iterable[APIChange[T]])
|
||||
{
|
||||
override def toString = "API Changes: " + modified + "\n" + names
|
||||
override def toString = "API Changes: " + apiChanges
|
||||
def allModified: Iterable[T] = apiChanges.map(_.modified)
|
||||
}
|
||||
|
||||
sealed abstract class APIChange[T](val modified: T)
|
||||
/**
|
||||
* If we recompile a source file that contains a macro definition then we always assume that it's
|
||||
* api has changed. The reason is that there's no way to determine if changes to macros implementation
|
||||
* are affecting its users or not. Therefore we err on the side of caution.
|
||||
*/
|
||||
case class APIChangeDueToMacroDefinition[T](modified0: T) extends APIChange(modified0)
|
||||
case class SourceAPIChange[T](modified0: T) extends APIChange(modified0)
|
||||
|
||||
trait Changes[A]
|
||||
{
|
||||
def added: Set[A]
|
||||
|
|
@ -24,4 +34,4 @@ trait Changes[A]
|
|||
sealed abstract class Change(val file: File)
|
||||
final class Removed(f: File) extends Change(f)
|
||||
final class Added(f: File, newStamp: Stamp) extends Change(f)
|
||||
final class Modified(f: File, oldStamp: Stamp, newStamp: Stamp) extends Change(f)
|
||||
final class Modified(f: File, oldStamp: Stamp, newStamp: Stamp) extends Change(f)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ object Incremental
|
|||
val initialChanges = changedInitial(entry, sources, previous, current, forEntry, options, log)
|
||||
val binaryChanges = new DependencyChanges {
|
||||
val modifiedBinaries = initialChanges.binaryDeps.toArray
|
||||
val modifiedClasses = initialChanges.external.modified.toArray
|
||||
val modifiedClasses = initialChanges.external.allModified.toArray
|
||||
def isEmpty = modifiedBinaries.isEmpty && modifiedClasses.isEmpty
|
||||
}
|
||||
val initialInv = invalidateInitial(previous.relations, initialChanges, log)
|
||||
|
|
@ -79,7 +79,7 @@ object Incremental
|
|||
val incChanges = changedIncremental(invalidated, previous.apis.internalAPI _, merged.apis.internalAPI _, log, options)
|
||||
debug("\nChanges:\n" + incChanges)
|
||||
val transitiveStep = options.transitiveStep
|
||||
val incInv = invalidateIncremental(merged.relations, incChanges, invalidated, cycleNum >= transitiveStep, log)
|
||||
val incInv = invalidateIncremental(merged.relations, merged.apis, incChanges, invalidated, cycleNum >= transitiveStep, log)
|
||||
cycle(incInv, allSources, emptyChanges, merged, doCompile, classfileManager, cycleNum+1, log, options)
|
||||
}
|
||||
private[this] def emptyChanges: DependencyChanges = new DependencyChanges {
|
||||
|
|
@ -106,15 +106,20 @@ object Incremental
|
|||
*
|
||||
* NOTE: This method creates a new APIDiff instance on every invocation.
|
||||
*/
|
||||
private def logApiChanges[T](changes: (collection.Set[T], Seq[Source], Seq[Source]), log: Logger,
|
||||
options: IncOptions): Unit = {
|
||||
private def logApiChanges[T](apiChanges: Iterable[APIChange[T]], oldAPIMapping: T => Source,
|
||||
newAPIMapping: T => Source, log: Logger, options: IncOptions): Unit = {
|
||||
val contextSize = options.apiDiffContextSize
|
||||
try {
|
||||
val apiDiff = new APIDiff
|
||||
changes.zipped foreach {
|
||||
case (src, oldApi, newApi) =>
|
||||
apiChanges foreach {
|
||||
case APIChangeDueToMacroDefinition(src) =>
|
||||
log.debug(s"Public API is considered to be changed because $src contains a macro definition.")
|
||||
case SourceAPIChange(src) =>
|
||||
val oldApi = oldAPIMapping(src)
|
||||
val newApi = newAPIMapping(src)
|
||||
val apiUnifiedPatch = apiDiff.generateApiDiff(src.toString, oldApi.api, newApi.api, contextSize)
|
||||
log.debug("Detected a change in a public API:\n" + apiUnifiedPatch)
|
||||
log.debug(s"Detected a change in a public API (${src.toString}):\n"
|
||||
+ apiUnifiedPatch)
|
||||
}
|
||||
} catch {
|
||||
case e: ClassNotFoundException =>
|
||||
|
|
@ -137,22 +142,33 @@ object Incremental
|
|||
{
|
||||
val oldApis = lastSources.toSeq map oldAPI
|
||||
val newApis = lastSources.toSeq map newAPI
|
||||
val changes = (lastSources, oldApis, newApis).zipped.filter { (src, oldApi, newApi) => !sameSource(oldApi, newApi) }
|
||||
val apiChanges = (lastSources, oldApis, newApis).zipped.flatMap { (src, oldApi, newApi) => sameSource(src, oldApi, newApi, log) }
|
||||
|
||||
if (apiDebug(options) && changes.zipped.nonEmpty) {
|
||||
logApiChanges(changes, log, options)
|
||||
if (apiDebug(options) && apiChanges.nonEmpty) {
|
||||
logApiChanges(apiChanges, oldAPI, newAPI, log, options)
|
||||
}
|
||||
|
||||
val changedNames = TopLevel.nameChanges(changes._3, changes._2 )
|
||||
|
||||
val modifiedAPIs = changes._1.toSet
|
||||
|
||||
new APIChanges(modifiedAPIs, changedNames)
|
||||
new APIChanges(apiChanges)
|
||||
}
|
||||
def sameSource(a: Source, b: Source): Boolean = {
|
||||
def sameSource[T](src: T, a: Source, b: Source, log: Logger): Option[APIChange[T]] = {
|
||||
// Clients of a modified source file (ie, one that doesn't satisfy `shortcutSameSource`) containing macros must be recompiled.
|
||||
val hasMacro = a.hasMacro || b.hasMacro
|
||||
shortcutSameSource(a, b) || (!hasMacro && SameAPI(a,b))
|
||||
if (shortcutSameSource(a, b)) {
|
||||
None
|
||||
} else {
|
||||
if (hasMacro) {
|
||||
Some(APIChangeDueToMacroDefinition(src))
|
||||
} else sameAPI(src, a, b, log)
|
||||
}
|
||||
}
|
||||
|
||||
def sameAPI[T](src: T, a: Source, b: Source, log: Logger): Option[SourceAPIChange[T]] = {
|
||||
if (SameAPI(a,b))
|
||||
None
|
||||
else {
|
||||
val sourceApiChange = SourceAPIChange(src)
|
||||
Some(sourceApiChange)
|
||||
}
|
||||
}
|
||||
|
||||
def shortcutSameSource(a: Source, b: Source): Boolean = !a.hash.isEmpty && !b.hash.isEmpty && sameCompilation(a.compilation, b.compilation) && (a.hash.deep equals b.hash.deep)
|
||||
|
|
@ -183,14 +199,14 @@ object Incremental
|
|||
val (changed, unmodified) = inBoth.partition(existingModified)
|
||||
}
|
||||
|
||||
def invalidateIncremental(previous: Relations, changes: APIChanges[File], recompiledSources: Set[File], transitive: Boolean, log: Logger): Set[File] =
|
||||
def invalidateIncremental(previous: Relations, apis: APIs, changes: APIChanges[File], recompiledSources: Set[File], transitive: Boolean, log: Logger): Set[File] =
|
||||
{
|
||||
val dependsOnSrc = previous.usesInternalSrc _
|
||||
val propagated =
|
||||
if(transitive)
|
||||
transitiveDependencies(dependsOnSrc, changes.modified, log)
|
||||
transitiveDependencies(dependsOnSrc, changes.allModified.toSet, log)
|
||||
else
|
||||
invalidateIntermediate(previous, changes.modified, log)
|
||||
invalidateIntermediate(previous, changes, log)
|
||||
|
||||
val dups = invalidateDuplicates(previous)
|
||||
if(dups.nonEmpty)
|
||||
|
|
@ -198,6 +214,7 @@ object Incremental
|
|||
|
||||
val inv = propagated ++ dups // ++ scopeInvalidations(previous.extAPI _, changes.modified, changes.names)
|
||||
val newlyInvalidated = inv -- recompiledSources
|
||||
log.debug("All newly invalidated sources after taking into account (previously) recompiled sources:" + newlyInvalidated)
|
||||
if(newlyInvalidated.isEmpty) Set.empty else inv
|
||||
}
|
||||
|
||||
|
|
@ -212,7 +229,7 @@ object Incremental
|
|||
* if they are part of a cycle containing newly invalidated files . */
|
||||
def transitiveDependencies(dependsOnSrc: File => Set[File], initial: Set[File], log: Logger): Set[File] =
|
||||
{
|
||||
val transitiveWithInitial = transitiveDeps(initial)(dependsOnSrc)
|
||||
val transitiveWithInitial = transitiveDeps(initial, log)(dependsOnSrc)
|
||||
val transitivePartial = includeInitialCond(initial, transitiveWithInitial, dependsOnSrc, log)
|
||||
log.debug("Final step, transitive dependencies:\n\t" + transitivePartial)
|
||||
transitivePartial
|
||||
|
|
@ -225,12 +242,13 @@ object Incremental
|
|||
val srcDirect = srcChanges.removed ++ srcChanges.removed.flatMap(previous.usesInternalSrc) ++ srcChanges.added ++ srcChanges.changed
|
||||
val byProduct = changes.removedProducts.flatMap(previous.produced)
|
||||
val byBinaryDep = changes.binaryDeps.flatMap(previous.usesBinary)
|
||||
val byExtSrcDep = invalidateByExternal(previous, changes.external.modified, log) //changes.external.modified.flatMap(previous.usesExternal) // ++ scopeInvalidations
|
||||
val externalModifiedSources = changes.external.allModified.toSet
|
||||
val byExtSrcDep = invalidateByExternal(previous, externalModifiedSources, log) //changes.external.modified.flatMap(previous.usesExternal) // ++ scopeInvalidations
|
||||
checkAbsolute(srcChanges.added.toList)
|
||||
log.debug(
|
||||
"\nInitial source changes: \n\tremoved:" + srcChanges.removed + "\n\tadded: " + srcChanges.added + "\n\tmodified: " + srcChanges.changed +
|
||||
"\nRemoved products: " + changes.removedProducts +
|
||||
"\nModified external sources: " + changes.external.modified +
|
||||
"\nModified external sources: " + externalModifiedSources +
|
||||
"\nModified binary dependencies: " + changes.binaryDeps +
|
||||
"\nInitial directly invalidated sources: " + srcDirect +
|
||||
"\n\nSources indirectly invalidated by:" +
|
||||
|
|
@ -263,7 +281,7 @@ object Incremental
|
|||
val externalInheritedR = relations.publicInherited.external
|
||||
val byExternalInherited = external flatMap externalInheritedR.reverse
|
||||
val internalInheritedR = relations.publicInherited.internal
|
||||
val transitiveInherited = transitiveDeps(byExternalInherited)(internalInheritedR.reverse _)
|
||||
val transitiveInherited = transitiveDeps(byExternalInherited, log)(internalInheritedR.reverse _)
|
||||
|
||||
// Get the direct dependencies of all sources transitively invalidated by inheritance
|
||||
val directA = transitiveInherited flatMap relations.direct.internal.reverse
|
||||
|
|
@ -272,29 +290,32 @@ object Incremental
|
|||
transitiveInherited ++ directA ++ directB
|
||||
}
|
||||
/** Intermediate invalidation step: steps after the initial invalidation, but before the final transitive invalidation. */
|
||||
def invalidateIntermediate(relations: Relations, modified: Set[File], log: Logger): Set[File] =
|
||||
def invalidateIntermediate(relations: Relations, changes: APIChanges[File], log: Logger): Set[File] =
|
||||
{
|
||||
def reverse(r: Relations.Source) = r.internal.reverse _
|
||||
invalidateSources(reverse(relations.direct), reverse(relations.publicInherited), modified, log)
|
||||
invalidateSources(reverse(relations.direct), reverse(relations.publicInherited), changes, log)
|
||||
}
|
||||
/** Invalidates inheritance dependencies, transitively. Then, invalidates direct dependencies. Finally, excludes initial dependencies not
|
||||
* included in a cycle with newly invalidated sources. */
|
||||
private[this] def invalidateSources(directDeps: File => Set[File], publicInherited: File => Set[File], initial: Set[File], log: Logger): Set[File] =
|
||||
private[this] def invalidateSources(directDeps: File => Set[File], publicInherited: File => Set[File], changes: APIChanges[File], log: Logger): Set[File] =
|
||||
{
|
||||
val transitiveInherited = transitiveDeps(initial)(publicInherited)
|
||||
val initial = changes.allModified.toSet
|
||||
log.debug("Invalidating by inheritance (transitively)...")
|
||||
val transitiveInherited = transitiveDeps(initial, log)(publicInherited)
|
||||
log.debug("Invalidated by transitive public inheritance: " + transitiveInherited)
|
||||
val direct = transitiveInherited flatMap directDeps
|
||||
log.debug("Invalidated by direct dependency: " + direct)
|
||||
val all = transitiveInherited ++ direct
|
||||
includeInitialCond(initial, all, f => directDeps(f) ++ publicInherited(f), log)
|
||||
}
|
||||
|
||||
/** Conditionally include initial sources that are dependencies of newly invalidated sources.
|
||||
** Initial sources included in this step can be because of a cycle, but not always. */
|
||||
private[this] def includeInitialCond(initial: Set[File], currentInvalidations: Set[File], allDeps: File => Set[File], log: Logger): Set[File] =
|
||||
{
|
||||
val newInv = currentInvalidations -- initial
|
||||
log.debug("New invalidations:\n\t" + newInv)
|
||||
val transitiveOfNew = transitiveDeps(newInv)(allDeps)
|
||||
val transitiveOfNew = transitiveDeps(newInv, log)(allDeps)
|
||||
val initialDependsOnNew = transitiveOfNew & initial
|
||||
log.debug("Previously invalidated, but (transitively) depend on new invalidations:\n\t" + initialDependsOnNew)
|
||||
newInv ++ initialDependsOnNew
|
||||
|
|
@ -361,16 +382,21 @@ object Incremental
|
|||
def orEmpty(o: Option[Source]): Source = o getOrElse APIs.emptySource
|
||||
def orTrue(o: Option[Boolean]): Boolean = o getOrElse true
|
||||
|
||||
private[this] def transitiveDeps[T](nodes: Iterable[T])(dependencies: T => Iterable[T]): Set[T] =
|
||||
private[this] def transitiveDeps[T](nodes: Iterable[T], log: Logger)(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))
|
||||
def all(from: T, tos: Iterable[T]): Unit = tos.foreach(to => visit(from, to))
|
||||
def visit(from: T, to: T): Unit =
|
||||
if (!xs.contains(to)) {
|
||||
log.debug(s"Including $to by $from")
|
||||
xs += to
|
||||
all(to, dependencies(to))
|
||||
}
|
||||
all(nodes)
|
||||
log.debug("Initial set of included nodes: " + nodes)
|
||||
nodes foreach { start =>
|
||||
xs += start
|
||||
all(start, dependencies(start))
|
||||
}
|
||||
xs.toSet
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue