implement shortcut for API equality checking, fixes #18

This commit is contained in:
Mark Harrah 2011-06-01 02:19:46 -04:00
parent d54a992c23
commit c0a21c1524
16 changed files with 60 additions and 36 deletions

View File

@ -15,7 +15,7 @@ object APIUtil
new Modifiers( x(0), x(1), x(2), x(3), x(4), x(5) )
}
def verifyTypeParameters(s: Source): Boolean =
def verifyTypeParameters(s: SourceAPI): Boolean =
{
val check = new CheckTypeParameters
val invalid = check(s)
@ -26,9 +26,9 @@ object APIUtil
{
private val defined = new HashSet[Int]
private val referenced = new HashSet[Int]
def apply(s: Source): List[Int] =
def apply(s: SourceAPI): List[Int] =
{
super.visit(s)
super.visitAPI(s)
(referenced filterNot defined).toList
}
override def visitTypeParameter(parameter: TypeParameter)

View File

@ -9,11 +9,11 @@ package sbt
object ClassToAPI
{
def apply(c: Seq[Class[_]]): api.Source =
def apply(c: Seq[Class[_]]): api.SourceAPI =
{
val pkgs = packages(c).map(p => new api.Package(p))
val defs = c.filter(isTopLevel).flatMap(toDefinitions(new mutable.HashMap))
new api.Source(pkgs.toArray, defs.toArray)
new api.SourceAPI(pkgs.toArray, defs.toArray)
}
def packages(c: Seq[Class[_]]): Set[String] =

View File

@ -19,8 +19,12 @@ class NameChanges(val newTypes: Set[String], val removedTypes: Set[String], val
object TopLevel
{
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. */
def nameChanges(a: Iterable[Source], b: Iterable[Source]): NameChanges =
def apiNameChanges(a: Iterable[SourceAPI], b: Iterable[SourceAPI]): NameChanges =
{
def changes(s: Set[String], t: Set[String]) = (s -- t, t -- s)
@ -32,14 +36,14 @@ object TopLevel
new NameChanges(newTypes, removedTypes, newTerms, removedTerms)
}
def definitions(i: Iterable[Source]) = SameAPI.separateDefinitions(i.toSeq.flatMap( _.definitions ))
def definitions(i: Iterable[SourceAPI]) = SameAPI.separateDefinitions(i.toSeq.flatMap( _.definitions ))
def names(s: Iterable[Definition]): Set[String] = Set() ++ s.map(_.name)
}
import TagTypeVariables.TypeVars
/** Checks the API of two source files for equality.*/
object SameAPI
{
def apply(a: Source, b: Source) =
def apply(a: SourceAPI, b: SourceAPI) =
{
val start = System.currentTimeMillis
@ -98,18 +102,18 @@ class SameAPI(tagsA: TypeVars, tagsB: TypeVars, includePrivate: Boolean, include
}
/** Returns true if source `a` has the same API as source `b`.*/
def check(a: Source, b: Source): Boolean =
def check(a: SourceAPI, b: SourceAPI): Boolean =
{
samePackages(a, b) &&
debug(sameDefinitions(a, b), "Definitions differed")
}
def samePackages(a: Source, b: Source): Boolean =
def samePackages(a: SourceAPI, b: SourceAPI): Boolean =
sameStrings(packages(a), packages(b))
def packages(s: Source): Set[String] =
def packages(s: SourceAPI): Set[String] =
Set() ++ s.packages.map(_.name)
def sameDefinitions(a: Source, b: Source): Boolean =
def sameDefinitions(a: SourceAPI, b: SourceAPI): Boolean =
sameDefinitions(a.definitions, b.definitions, true)
def sameDefinitions(a: Seq[Definition], b: Seq[Definition], topLevel: Boolean): Boolean =
{

View File

@ -54,8 +54,8 @@ trait ShowBase
implicit def showVariance: Show[Variance] =
new Show[Variance] { def show(v: Variance) = v match { case Invariant => ""; case Covariant => "+"; case Contravariant => "-" } }
implicit def showSource(implicit ps: Show[Package], ds: Show[Definition]): Show[Source] =
new Show[Source] { def show(a: Source) = lines(a.packages, ps) + "\n" + lines(a.definitions, ds) }
implicit def showSource(implicit ps: Show[Package], ds: Show[Definition]): Show[SourceAPI] =
new Show[SourceAPI] { def show(a: SourceAPI) = lines(a.packages, ps) + "\n" + lines(a.definitions, ds) }
implicit def showPackage: Show[Package] =
new Show[Package] { def show(pkg: Package) = "package " + pkg.name }

View File

@ -9,7 +9,7 @@ package xsbt.api
object TagTypeVariables
{
type TypeVars = collection.Map[Int, (Int, Int)]
def apply(s: Source): TypeVars = (new TagTypeVariables).tag(s)
def apply(s: SourceAPI): TypeVars = (new TagTypeVariables).tag(s)
}
import TagTypeVariables.TypeVars
private class TagTypeVariables
@ -21,7 +21,7 @@ private class TagTypeVariables
private var level = 0
private var index = 0
def tag(s: Source): TypeVars =
def tag(s: SourceAPI): TypeVars =
{
s.definitions.foreach(tagDefinition)
tags

View File

@ -11,7 +11,8 @@ class Visit
private[this] val visitedStructures = new mutable.HashSet[Structure]
private[this] val visitedClassLike = new mutable.HashSet[ClassLike]
def visit(s: Source): Unit =
def visit(s: Source): Unit = visitAPI(s.api)
def visitAPI(s: SourceAPI): Unit =
{
s.packages foreach visitPackage
s.definitions foreach visitDefinition

View File

@ -130,7 +130,7 @@ object ApplicationsTest extends Specification
x = println("\n" + file + ":\n" + (api.definitions.flatMap { case c: xsbti.api.ClassLike => c.structure.inherited.filter(_.name == "main"); case _ => Nil }).map(xsbt.api.DefaultShowAPI.apply).mkString("\n"));
application <- applications(api))
yield (file, application)
def applications(src: xsbti.api.Source): Seq[String] =
def applications(src: xsbti.api.SourceAPI): Seq[String] =
Discovery.applications(src.definitions) collect { case (definition, Discovered(_, _, true, _)) => definition.name }
private def testRun(loader: ClassLoader, className: String)

View File

@ -36,8 +36,10 @@ object APIs
def apply(internal: Map[File, Source], external: Map[String, Source]): APIs = new MAPIs(internal, external)
def empty: APIs = apply(Map.empty, Map.empty)
val emptyAPI = new xsbti.api.Source(Array(), Array())
def getAPI[T](map: Map[T, Source], src: T): Source = map.getOrElse(src, emptyAPI)
val emptyAPI = new xsbti.api.SourceAPI(Array(), Array())
val emptyCompilation = new xsbti.api.Compilation(-1, "")
val emptySource = new xsbti.api.Source(emptyCompilation, Array(), emptyAPI)
def getAPI[T](map: Map[T, Source], src: T): Source = map.getOrElse(src, emptySource)
}
private class MAPIs(val internal: Map[File, Source], val external: Map[String, Source]) extends APIs

View File

@ -4,7 +4,7 @@
package sbt
package inc
import xsbti.api.Source
import xsbti.api.{Source, SourceAPI}
import java.io.File
object IncrementalCompile
@ -36,11 +36,14 @@ object IncrementalCompile
}
private final class AnalysisCallback(internalMap: File => Option[File], externalAPI: (File, String) => Option[Source], current: ReadStamps, outputPath: File) extends xsbti.AnalysisCallback
{
val time = System.currentTimeMillis
val compilation = new xsbti.api.Compilation(time, outputPath.getAbsolutePath)
override def toString = ( List("APIs", "Binary deps", "Products", "Source deps") zip List(apis, binaryDeps, classes, sourceDeps)).map { case (label, map) => label + "\n\t" + map.mkString("\n\t") }.mkString("\n")
import collection.mutable.{HashMap, HashSet, ListBuffer, Map, Set}
private val apis = new HashMap[File, Source]
private val apis = new HashMap[File, SourceAPI]
private val binaryDeps = new HashMap[File, Set[File]]
private val classes = new HashMap[File, Set[File]]
private val sourceDeps = new HashMap[File, Set[File]]
@ -80,7 +83,7 @@ private final class AnalysisCallback(internalMap: File => Option[File], external
def generatedClass(source: File, module: File) = add(classes, source, module)
def api(sourceFile: File, source: Source) { apis(sourceFile) = source }
def api(sourceFile: File, source: SourceAPI) { apis(sourceFile) = source }
def endSource(sourcePath: File): Unit =
assert(apis.contains(sourcePath))
@ -89,7 +92,10 @@ private final class AnalysisCallback(internalMap: File => Option[File], external
def addBinaries(base: Analysis): Analysis = addAll(base, binaryDeps)( (a, src, bin) => a.addBinaryDep(src, bin, binaryClassName(bin), current binary bin) )
def addSources(base: Analysis): Analysis =
(base /: apis) { case (a, (src, api) ) =>
a.addSource(src, api, current.internalSource(src), sourceDeps.getOrElse(src, Nil: Iterable[File]))
val stamp = current.internalSource(src)
val hash = stamp match { case h: Hash => h.value; case _ => new Array[Byte](0) }
val s = new xsbti.api.Source(compilation, hash, api)
a.addSource(src, s, stamp, sourceDeps.getOrElse(src, Nil: Iterable[File]))
}
def addExternals(base: Analysis): Analysis = (base /: extSrcDeps) { case (a, (source, name, api)) => a.addExternalDep(source, name, api) }

View File

@ -6,7 +6,7 @@ package inc
import xsbt.api.{NameChanges, SameAPI, TopLevel}
import annotation.tailrec
import xsbti.api.Source
import xsbti.api.{Compilation, Source}
import java.io.File
object Incremental
@ -51,8 +51,7 @@ object Incremental
{
val oldApis = lastSources.toSeq map oldAPI
val newApis = lastSources.toSeq map newAPI
for(api <- newApis; definition <- api.definitions) { debug(xsbt.api.DefaultShowAPI(definition)) }
val changes = (lastSources, oldApis, newApis).zipped.filter { (src, oldApi, newApi) => !SameAPI(oldApi, newApi) }
val changes = (lastSources, oldApis, newApis).zipped.filter { (src, oldApi, newApi) => !sameSource(oldApi, newApi) }
val changedNames = TopLevel.nameChanges(changes._3, changes._2 )
@ -60,6 +59,9 @@ object Incremental
new APIChanges(modifiedAPIs, changedNames)
}
def sameSource(a: Source, b: Source): Boolean = shortcutSameSource(a, b) || SameAPI(a.api, b.api)
def shortcutSameSource(a: Source, b: Source): Boolean = !a.hash.isEmpty && !b.hash.isEmpty && sameCompilation(a.compilation, b.compilation) && (a.hash deepEquals b.hash)
def sameCompilation(a: Compilation, b: Compilation): Boolean = a.startTime == b.startTime && a.target == b.target
def changedInitial(entry: String => Option[File], sources: Set[File], previousAnalysis: Analysis, current: ReadStamps, forEntry: File => Option[Analysis])(implicit equivS: Equiv[Stamp]): InitialChanges =
{
@ -154,7 +156,7 @@ object Incremental
analysis.apis.internalAPI(src)
)
def orEmpty(o: Option[Source]): Source = o getOrElse APIs.emptyAPI
def orEmpty(o: Option[Source]): Source = o getOrElse APIs.emptySource
def orTrue(o: Option[Boolean]): Boolean = o getOrElse true
// unmodifiedSources should not contain any sources in the previous compilation run
// (this may unnecessarily invalidate them otherwise)

View File

@ -46,7 +46,7 @@ final class API(val global: Global, val callback: xsbti.AnalysisCallback) extend
val traverser = new TopLevelHandler(sourceFile)
traverser.apply(unit.body)
val packages = traverser.packages.toArray[String].map(p => new xsbti.api.Package(p))
val source = new xsbti.api.Source(packages, traverser.definitions.toArray[xsbti.api.Definition])
val source = new xsbti.api.SourceAPI(packages, traverser.definitions.toArray[xsbti.api.Definition])
forceStructures()
clearCaches()
callback.api(sourceFile, source)

View File

@ -47,8 +47,8 @@ trait APIFormats extends FormatExtra
wrap[Id, String](_.id, i => new Id(i))(s)
implicit val formatThis: Format[This] = asSingleton(new This)
implicit def formatSource(implicit pa: Format[Array[Package]], da: Format[Array[Definition]]): Format[Source] =
p2( (s: Source) => (s.packages, s.definitions))( (p, d) => new Source(p, d) )(pa, da)
implicit def formatSource(implicit pa: Format[Array[Package]], da: Format[Array[Definition]]): Format[SourceAPI] =
p2( (s: SourceAPI) => (s.packages, s.definitions))( (p, d) => new SourceAPI(p, d) )(pa, da)
implicit def formatAnnotated(implicit t: Format[SimpleType], as: Format[Array[Annotation]]): Format[Annotated] =
p2( (a: Annotated) => (a.baseType,a.annotations))(new Annotated(_,_))(t,as)
@ -218,7 +218,7 @@ class DefaultAPIFormats(implicit val references: References) extends APIFormats
implicit lazy val sf: Format[Super] = lazyFormat(formatSuper(pathf))
implicit lazy val pathf: Format[Path] = formatPath
implicit val srcFormat: Format[Source] = formatSource(??, array(df))
implicit val srcFormat: Format[SourceAPI] = formatSource(??, array(df))
private[this] def array[T](format: Format[T])(implicit mf: Manifest[T]): Format[Array[T]] = arrayFormat(format, mf)
}

View File

@ -1,7 +1,16 @@
Source
compilation: Compilation
hash: Byte*
api: SourceAPI
SourceAPI
packages : Package*
definitions: Definition*
Compilation
startTime: Long
target: String
Package
name: String

View File

@ -23,5 +23,5 @@ public interface AnalysisCallback
/** Called after the source at the given location has been processed. */
public void endSource(File sourcePath);
/** Called when the public API of a source file is extracted. */
public void api(File sourceFile, xsbti.api.Source source);
public void api(File sourceFile, xsbti.api.SourceAPI source);
}

View File

@ -10,7 +10,7 @@ class TestCallback extends AnalysisCallback
val sourceDependencies = new ArrayBuffer[(File, File)]
val binaryDependencies = new ArrayBuffer[(File, String, File)]
val products = new ArrayBuffer[(File, File)]
val apis = new ArrayBuffer[(File, xsbti.api.Source)]
val apis = new ArrayBuffer[(File, xsbti.api.SourceAPI)]
def beginSource(source: File) { beganSources += source }
@ -19,5 +19,5 @@ class TestCallback extends AnalysisCallback
def generatedClass(source: File, module: File) { products += ((source, module)) }
def endSource(source: File) { endedSources += source }
def api(source: File, sourceAPI: xsbti.api.Source) { apis += ((source, sourceAPI)) }
def api(source: File, sourceAPI: xsbti.api.SourceAPI) { apis += ((source, sourceAPI)) }
}

View File

@ -113,7 +113,7 @@ object Tests
def discover(frameworks: Seq[Framework], analysis: Analysis, log: Logger): (Seq[TestDefinition], Set[String]) =
discover(frameworks flatMap TestFramework.getTests, allDefs(analysis), log)
def allDefs(analysis: Analysis) = analysis.apis.internal.values.flatMap(_.definitions).toSeq
def allDefs(analysis: Analysis) = analysis.apis.internal.values.flatMap(_.api.definitions).toSeq
def discover(fingerprints: Seq[Fingerprint], definitions: Seq[Definition], log: Logger): (Seq[TestDefinition], Set[String]) =
{
val subclasses = fingerprints collect { case sub: SubclassFingerprint => (sub.superClassName, sub.isModule, sub) };