From c0a21c15242ed4fe741fc7429697655b22dc534f Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Wed, 1 Jun 2011 02:19:46 -0400 Subject: [PATCH] implement shortcut for API equality checking, fixes #18 --- compile/api/APIUtil.scala | 6 +++--- compile/api/ClassToAPI.scala | 4 ++-- compile/api/SameAPI.scala | 18 +++++++++++------- compile/api/ShowAPI.scala | 4 ++-- compile/api/TagTypeVariables.scala | 4 ++-- compile/api/Visit.scala | 3 ++- .../src/test/scala/ApplicationsTest.scala | 2 +- compile/inc/APIs.scala | 6 ++++-- compile/inc/Compile.scala | 14 ++++++++++---- compile/inc/Incremental.scala | 10 ++++++---- compile/interface/API.scala | 2 +- compile/persist/APIFormats.scala | 6 +++--- interface/other | 9 +++++++++ .../src/main/java/xsbti/AnalysisCallback.java | 2 +- interface/src/test/scala/TestCallback.scala | 4 ++-- main/actions/Tests.scala | 2 +- 16 files changed, 60 insertions(+), 36 deletions(-) diff --git a/compile/api/APIUtil.scala b/compile/api/APIUtil.scala index 98bec00b0..ac7e50626 100644 --- a/compile/api/APIUtil.scala +++ b/compile/api/APIUtil.scala @@ -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) diff --git a/compile/api/ClassToAPI.scala b/compile/api/ClassToAPI.scala index 7c82fc0ac..1629928fc 100644 --- a/compile/api/ClassToAPI.scala +++ b/compile/api/ClassToAPI.scala @@ -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] = diff --git a/compile/api/SameAPI.scala b/compile/api/SameAPI.scala index 721adb46a..97b6e81d4 100644 --- a/compile/api/SameAPI.scala +++ b/compile/api/SameAPI.scala @@ -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 = { diff --git a/compile/api/ShowAPI.scala b/compile/api/ShowAPI.scala index cb8f4382c..ab7e41fb7 100644 --- a/compile/api/ShowAPI.scala +++ b/compile/api/ShowAPI.scala @@ -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 } diff --git a/compile/api/TagTypeVariables.scala b/compile/api/TagTypeVariables.scala index 759486b9d..ee1992bd7 100644 --- a/compile/api/TagTypeVariables.scala +++ b/compile/api/TagTypeVariables.scala @@ -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 diff --git a/compile/api/Visit.scala b/compile/api/Visit.scala index ded478380..85dd46db1 100644 --- a/compile/api/Visit.scala +++ b/compile/api/Visit.scala @@ -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 diff --git a/compile/discover/src/test/scala/ApplicationsTest.scala b/compile/discover/src/test/scala/ApplicationsTest.scala index ac2c89ab7..3d1aedbe9 100644 --- a/compile/discover/src/test/scala/ApplicationsTest.scala +++ b/compile/discover/src/test/scala/ApplicationsTest.scala @@ -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) diff --git a/compile/inc/APIs.scala b/compile/inc/APIs.scala index 8c23232fa..54d155897 100644 --- a/compile/inc/APIs.scala +++ b/compile/inc/APIs.scala @@ -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 diff --git a/compile/inc/Compile.scala b/compile/inc/Compile.scala index 5f73ebed4..fce1a4de2 100644 --- a/compile/inc/Compile.scala +++ b/compile/inc/Compile.scala @@ -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) } diff --git a/compile/inc/Incremental.scala b/compile/inc/Incremental.scala index f73394ca3..11464bee8 100644 --- a/compile/inc/Incremental.scala +++ b/compile/inc/Incremental.scala @@ -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) diff --git a/compile/interface/API.scala b/compile/interface/API.scala index 8226cb064..86e75b769 100644 --- a/compile/interface/API.scala +++ b/compile/interface/API.scala @@ -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) diff --git a/compile/persist/APIFormats.scala b/compile/persist/APIFormats.scala index 171ad7396..dfd276500 100644 --- a/compile/persist/APIFormats.scala +++ b/compile/persist/APIFormats.scala @@ -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) } \ No newline at end of file diff --git a/interface/other b/interface/other index 78ebbc3c8..64b09422d 100644 --- a/interface/other +++ b/interface/other @@ -1,7 +1,16 @@ Source + compilation: Compilation + hash: Byte* + api: SourceAPI + +SourceAPI packages : Package* definitions: Definition* +Compilation + startTime: Long + target: String + Package name: String diff --git a/interface/src/main/java/xsbti/AnalysisCallback.java b/interface/src/main/java/xsbti/AnalysisCallback.java index d3eb2ab54..efca555c4 100644 --- a/interface/src/main/java/xsbti/AnalysisCallback.java +++ b/interface/src/main/java/xsbti/AnalysisCallback.java @@ -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); } \ No newline at end of file diff --git a/interface/src/test/scala/TestCallback.scala b/interface/src/test/scala/TestCallback.scala index d554a9a08..c49e11317 100644 --- a/interface/src/test/scala/TestCallback.scala +++ b/interface/src/test/scala/TestCallback.scala @@ -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)) } } \ No newline at end of file diff --git a/main/actions/Tests.scala b/main/actions/Tests.scala index 74245326a..0c9947d3f 100644 --- a/main/actions/Tests.scala +++ b/main/actions/Tests.scala @@ -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) };