support incremental recompilation when using exportJars. fixes #108

This commit is contained in:
Mark Harrah 2011-07-18 17:14:22 -04:00
parent b3bcd82a21
commit 0b3ec05a81
16 changed files with 81 additions and 37 deletions

View File

@ -21,7 +21,7 @@ trait Analysis
def addSource(src: File, api: Source, stamp: Stamp, internalDeps: Iterable[File]): Analysis
def addBinaryDep(src: File, dep: File, className: String, stamp: Stamp): Analysis
def addExternalDep(src: File, dep: String, api: Source): Analysis
def addProduct(src: File, product: File, stamp: Stamp): Analysis
def addProduct(src: File, product: File, stamp: Stamp, name: String): Analysis
override lazy val toString = Analysis.summary(this)
}
@ -67,6 +67,6 @@ private class MAnalysis(val stamps: Stamps, val apis: APIs, val relations: Relat
def addExternalDep(src: File, dep: String, depAPI: Source): Analysis =
copy( stamps, apis.markExternalAPI(dep, depAPI), relations.addExternalDep(src, dep) )
def addProduct(src: File, product: File, stamp: Stamp): Analysis =
copy( stamps.markProduct(product, stamp), apis, relations.addProduct(src, product) )
def addProduct(src: File, product: File, stamp: Stamp, name: String): Analysis =
copy( stamps.markProduct(product, stamp), apis, relations.addProduct(src, product, name) )
}

View File

@ -28,7 +28,7 @@ object IncrementalCompile
None
else
forEntry(defines) flatMap { analysis =>
analysis.relations.produced(file).headOption flatMap { src =>
analysis.relations.definesClass(className).headOption flatMap { src =>
analysis.apis.internal get src
}
}
@ -45,7 +45,7 @@ private final class AnalysisCallback(internalMap: File => Option[File], external
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 classes = new HashMap[File, Set[(File, String)]]
private val sourceDeps = new HashMap[File, Set[File]]
private val extSrcDeps = new ListBuffer[(File, String, Source)]
private val binaryClassName = new HashMap[File, String]
@ -62,7 +62,6 @@ private final class AnalysisCallback(internalMap: File => Option[File], external
def externalSourceDependency(triple: (File, String, Source)) = extSrcDeps += triple
def binaryDependency(classFile: File, name: String, source: File) =
{
internalMap(classFile) match
{
case Some(dependsOn) =>
@ -79,16 +78,15 @@ private final class AnalysisCallback(internalMap: File => Option[File], external
externalBinaryDependency(classFile, name, source)
}
}
}
def generatedClass(source: File, module: File) = add(classes, source, module)
def generatedClass(source: File, module: File, name: String) = add(classes, source, (module, name))
def api(sourceFile: File, source: SourceAPI) { apis(sourceFile) = source }
def endSource(sourcePath: File): Unit =
assert(apis.contains(sourcePath))
def get: Analysis = addExternals( addBinaries( addProducts( addSources(Analysis.Empty) ) ) )
def addProducts(base: Analysis): Analysis = addAll(base, classes)( (a, src, prod) => a.addProduct(src, prod, current product prod ) )
def addProducts(base: Analysis): Analysis = addAll(base, classes) { case (a, src, (prod, name)) => a.addProduct(src, prod, current product prod, name ) }
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) ) =>

View File

@ -70,8 +70,8 @@ object Incremental
val srcChanges = changes(previous.allInternalSources.toSet, sources, f => !equivS.equiv( previous.internalSource(f), current.internalSource(f) ) )
val removedProducts = previous.allProducts.filter( p => !equivS.equiv( previous.product(p), current.product(p) ) ).toSet
val binaryDepChanges = previous.allBinaries.filter( externalBinaryModified(entry, previous.className _, previous, current)).toSet
val extChanges = changedIncremental(previousAPIs.allExternals, previousAPIs.externalAPI, currentExternalAPI(entry, forEntry))
val binaryDepChanges = previous.allBinaries.filter( externalBinaryModified(entry, forEntry, previous, current)).toSet
val extChanges = changedIncremental(previousAPIs.allExternals, previousAPIs.externalAPI _, currentExternalAPI(entry, forEntry))
InitialChanges(srcChanges, removedProducts, binaryDepChanges, extChanges )
}
@ -133,11 +133,12 @@ object Incremental
previous -- invalidatedSrcs
}
def externalBinaryModified(entry: String => Option[File], className: File => Option[String], previous: Stamps, current: ReadStamps)(implicit equivS: Equiv[Stamp]): File => Boolean =
def externalBinaryModified(entry: String => Option[File], analysis: File => Option[Analysis], previous: Stamps, current: ReadStamps)(implicit equivS: Equiv[Stamp]): File => Boolean =
dependsOn =>
analysis(dependsOn).isEmpty &&
orTrue(
for {
name <- className(dependsOn)
name <- previous.className(dependsOn)
e <- entry(name)
} yield {
val resolved = Locate.resolve(e, name)
@ -151,7 +152,7 @@ object Incremental
for {
e <- entry(className)
analysis <- forEntry(e)
src <- analysis.relations.produced(Locate.resolve(e, className)).headOption
src <- analysis.relations.definesClass(className).headOption
} yield
analysis.apis.internalAPI(src)
)

View File

@ -16,6 +16,9 @@ trait Relations
def allBinaryDeps: collection.Set[File]
def allInternalSrcDeps: collection.Set[File]
def allExternalDeps: collection.Set[String]
def classNames(src: File): Set[String]
def definesClass(name: String): Set[File]
def products(src: File): Set[File]
def produced(prod: File): Set[File]
@ -29,7 +32,7 @@ trait Relations
def externalDeps(src: File): Set[String]
def usesExternal(dep: String): Set[File]
def addProduct(src: File, prod: File): Relations
def addProduct(src: File, prod: File, name: String): Relations
def addExternalDep(src: File, dependsOn: String): Relations
def addInternalSrcDeps(src: File, dependsOn: Iterable[File]): Relations
def addBinaryDep(src: File, dependsOn: File): Relations
@ -41,14 +44,16 @@ trait Relations
def binaryDep: Relation[File, File]
def internalSrcDep: Relation[File, File]
def externalDep: Relation[File, String]
def classes: Relation[File, String]
}
object Relations
{
lazy val e = Relation.empty[File, File]
def empty: Relations = new MRelations(e, e, e, Relation.empty[File, String])
def make(srcProd: Relation[File, File], binaryDep: Relation[File, File], internalSrcDep: Relation[File, File], externalDep: Relation[File, String]): Relations =
new MRelations(srcProd, binaryDep, internalSrcDep, externalDep)
private[this] lazy val e = Relation.empty[File, File]
private[this] lazy val es = Relation.empty[File, String]
def empty: Relations = new MRelations(e, e, e, es, es)
def make(srcProd: Relation[File, File], binaryDep: Relation[File, File], internalSrcDep: Relation[File, File], externalDep: Relation[File, String], classes: Relation[File, String]): Relations =
new MRelations(srcProd, binaryDep, internalSrcDep, externalDep, classes)
}
/**
* `srcProd` is a relation between a source file and a product: (source, product).
@ -63,9 +68,11 @@ object Relations
*
* `externalSrcDeps` is a relation between a source file and a source dependency in another compilation group.
* Dependencies on sources in the same group belong in internal source dependencies.
*
* `classes` is a relation between a source file and its generated class names.
*/
private class MRelations(val srcProd: Relation[File, File], val binaryDep: Relation[File, File],
val internalSrcDep: Relation[File, File], val externalDep: Relation[File, String]) extends Relations
val internalSrcDep: Relation[File, File], val externalDep: Relation[File, String], val classes: Relation[File, String]) extends Relations
{
def allSources: collection.Set[File] = srcProd._1s
@ -73,6 +80,9 @@ private class MRelations(val srcProd: Relation[File, File], val binaryDep: Relat
def allBinaryDeps: collection.Set[File] = binaryDep._2s
def allInternalSrcDeps: collection.Set[File] = internalSrcDep._2s
def allExternalDeps: collection.Set[String] = externalDep._2s
def classNames(src: File): Set[String] = classes.forward(src)
def definesClass(name: String): Set[File] = classes.reverse(name)
def products(src: File): Set[File] = srcProd.forward(src)
def produced(prod: File): Set[File] = srcProd.reverse(prod)
@ -86,22 +96,22 @@ private class MRelations(val srcProd: Relation[File, File], val binaryDep: Relat
def externalDeps(src: File): Set[String] = externalDep.forward(src)
def usesExternal(dep: String): Set[File] = externalDep.reverse(dep)
def addProduct(src: File, prod: File): Relations =
new MRelations( srcProd + (src, prod), binaryDep, internalSrcDep, externalDep )
def addProduct(src: File, prod: File, name: String): Relations =
new MRelations( srcProd + (src, prod), binaryDep, internalSrcDep, externalDep, classes + (src, name) )
def addExternalDep(src: File, dependsOn: String): Relations =
new MRelations( srcProd, binaryDep, internalSrcDep, externalDep + (src, dependsOn) )
new MRelations( srcProd, binaryDep, internalSrcDep, externalDep + (src, dependsOn), classes )
def addInternalSrcDeps(src: File, dependsOn: Iterable[File]): Relations =
new MRelations( srcProd, binaryDep, internalSrcDep + (src, dependsOn ), externalDep )
new MRelations( srcProd, binaryDep, internalSrcDep + (src, dependsOn ), externalDep, classes )
def addBinaryDep(src: File, dependsOn: File): Relations =
new MRelations( srcProd, binaryDep + (src, dependsOn), internalSrcDep, externalDep )
new MRelations( srcProd, binaryDep + (src, dependsOn), internalSrcDep, externalDep, classes )
def ++ (o: Relations): Relations =
new MRelations(srcProd ++ o.srcProd, binaryDep ++ o.binaryDep, internalSrcDep ++ o.internalSrcDep, externalDep ++ o.externalDep)
new MRelations(srcProd ++ o.srcProd, binaryDep ++ o.binaryDep, internalSrcDep ++ o.internalSrcDep, externalDep ++ o.externalDep, classes ++ o.classes)
def -- (sources: Iterable[File]) =
new MRelations(srcProd -- sources, binaryDep -- sources, internalSrcDep -- sources, externalDep -- sources)
new MRelations(srcProd -- sources, binaryDep -- sources, internalSrcDep -- sources, externalDep -- sources, classes -- sources)
override def toString = "Relations:\n products: " + srcProd + "\n bin deps: " + binaryDep + "\n src deps: " + internalSrcDep + "\n ext deps: " + externalDep + "\n"
override def toString = "Relations:\n products: " + srcProd + "\n bin deps: " + binaryDep + "\n src deps: " + internalSrcDep + "\n ext deps: " + externalDep + "\n class names: " + classes + "\n"
}

View File

@ -65,7 +65,7 @@ final class Analyzer(val global: Global, val callback: AnalysisCallback) extends
{
val classFile = fileForClass(outputDirectory, sym, separatorRequired)
if(classFile.exists)
callback.generatedClass(sourceFile, classFile)
callback.generatedClass(sourceFile, classFile, className(sym, '.', separatorRequired))
}
if(sym.isModuleClass && !sym.isImplClass)
{
@ -108,8 +108,10 @@ final class Analyzer(val global: Global, val callback: AnalysisCallback) extends
atPhase (currentRun.picklerPhase.next) {
sym.isModuleClass && !sym.isImplClass && !sym.isNestedClass
}
private def className(s: Symbol, sep: Char, dollarRequired: Boolean): String =
flatname(s, sep) + (if(dollarRequired) "$" else "")
private def fileForClass(outputDirectory: File, s: Symbol, separatorRequired: Boolean): File =
new File(outputDirectory, flatname(s, File.separatorChar) + (if(separatorRequired) "$" else "") + ".class")
new File(outputDirectory, className(s, File.separatorChar, separatorRequired) + ".class")
// required because the 2.8 way to find a class is:
// classPath.findClass(name).flatMap(_.binary)

View File

@ -55,8 +55,8 @@ object AnalysisFormats
implicit def apisFormat(implicit internalF: Format[Map[File, Source]], externalF: Format[Map[String, Source]]): Format[APIs] =
asProduct2( APIs.apply _)( as => (as.internal, as.external) )(internalF, externalF)
implicit def relationsFormat(implicit prodF: Format[RFF], binF: Format[RFF], intF: Format[RFF], extF: Format[RFS]): Format[Relations] =
asProduct4[Relations, RFF, RFF, RFF, RFS]( (a,b,c,d) => Relations.make(a,b,c,d) )( rs => (rs.srcProd, rs.binaryDep, rs.internalSrcDep, rs.externalDep) )(prodF, binF, intF, extF)
implicit def relationsFormat(implicit prodF: Format[RFF], binF: Format[RFF], intF: Format[RFF], extF: Format[RFS], csF: Format[RFS]): Format[Relations] =
asProduct5[Relations, RFF, RFF, RFF, RFS, RFS]( (a,b,c,d,e) => Relations.make(a,b,c,d,e) )( rs => (rs.srcProd, rs.binaryDep, rs.internalSrcDep, rs.externalDep, rs.classes) )(prodF, binF, intF, extF, csF)
implicit def relationFormat[A,B](implicit af: Format[Map[A, Set[B]]], bf: Format[Map[B, Set[A]]]): Format[Relation[A,B]] =
asProduct2[Relation[A,B], Map[A, Set[B]], Map[B, Set[A]]]( Relation.make _ )( r => (r.forwardMap, r.reverseMap) )(af, bf)

View File

@ -18,8 +18,8 @@ public interface AnalysisCallback
* class named <code>name</code> from class or jar file <code>binary</code>. */
public void binaryDependency(File binary, String name, File source);
/** Called to indicate that the source file <code>source</code> produces a class file at
* <code>module</code>.*/
public void generatedClass(File source, File module);
* <code>module</code> contain class <code>name</code>.*/
public void generatedClass(File source, File module, String name);
/** 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. */

View File

@ -9,14 +9,14 @@ class TestCallback extends AnalysisCallback
val endedSources = new ArrayBuffer[File]
val sourceDependencies = new ArrayBuffer[(File, File)]
val binaryDependencies = new ArrayBuffer[(File, String, File)]
val products = new ArrayBuffer[(File, File)]
val products = new ArrayBuffer[(File, File, String)]
val apis = new ArrayBuffer[(File, xsbti.api.SourceAPI)]
def beginSource(source: File) { beganSources += source }
def sourceDependency(dependsOn: File, source: File) { sourceDependencies += ((dependsOn, source)) }
def binaryDependency(binary: File, name: String, source: File) { binaryDependencies += ((binary, name, source)) }
def generatedClass(source: File, module: File) { products += ((source, module)) }
def generatedClass(source: File, module: File, name: String) { products += ((source, module, name)) }
def endSource(source: File) { endedSources += source }
def api(source: File, sourceAPI: xsbti.api.SourceAPI) { apis += ((source, sourceAPI)) }

View File

@ -0,0 +1 @@
object A { final val x = 1 }

View File

@ -0,0 +1 @@
object A { final val x = 2 }

View File

@ -0,0 +1 @@
object A { def x = 3 }

View File

@ -0,0 +1,4 @@
object B
{
def main(args: Array[String]) = assert(args(0).toInt == A.x )
}

View File

@ -0,0 +1 @@
exportJars := true

View File

@ -0,0 +1,7 @@
import sbt._
object Build extends Build
{
lazy val root = Project("root", file(".")) dependsOn(a)
lazy val a = Project("a", file("a"))
}

View File

@ -0,0 +1,18 @@
$ copy-file changes/B.scala B.scala
$ copy-file changes/A1.scala a/A.scala
> run 1
$ copy-file changes/A2.scala a/A.scala
> run 2
$ copy-file changes/A3.scala a/A.scala
> run 3
$ copy-file changes/build2.sbt build.sbt
> reload
$ copy-file changes/A1.scala a/A.scala
> run 1
$ copy-file changes/A2.scala a/A.scala
> run 2
$ copy-file changes/A3.scala a/A.scala
> run 3

View File

@ -41,7 +41,7 @@ private[sbt] object Analyze
source <- guessSourcePath(sourceMap, classFile, log))
{
analysis.beginSource(source)
analysis.generatedClass(source, newClass)
analysis.generatedClass(source, newClass, classFile.className)
productToSource(newClass) = source
sourceToClassFiles.getOrElseUpdate(source, new ArrayBuffer[ClassFile]) += classFile
}