mirror of https://github.com/sbt/sbt.git
implement shortcut for API equality checking, fixes #18
This commit is contained in:
parent
d54a992c23
commit
c0a21c1524
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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] =
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -1,7 +1,16 @@
|
|||
Source
|
||||
compilation: Compilation
|
||||
hash: Byte*
|
||||
api: SourceAPI
|
||||
|
||||
SourceAPI
|
||||
packages : Package*
|
||||
definitions: Definition*
|
||||
|
||||
Compilation
|
||||
startTime: Long
|
||||
target: String
|
||||
|
||||
Package
|
||||
name: String
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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)) }
|
||||
}
|
||||
|
|
@ -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) };
|
||||
|
|
|
|||
Loading…
Reference in New Issue