From 0b3ec05a815f34ce4df658f77f40335fc806c551 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Mon, 18 Jul 2011 17:14:22 -0400 Subject: [PATCH] support incremental recompilation when using exportJars. fixes #108 --- compile/inc/Analysis.scala | 6 +-- compile/inc/Compile.scala | 10 ++--- compile/inc/Incremental.scala | 11 +++--- compile/inc/Relations.scala | 38 ++++++++++++------- compile/interface/Analyzer.scala | 6 ++- compile/persist/AnalysisFormats.scala | 4 +- .../src/main/java/xsbti/AnalysisCallback.java | 4 +- interface/src/test/scala/TestCallback.scala | 4 +- .../export-jars/changes/A1.scala | 1 + .../export-jars/changes/A2.scala | 1 + .../export-jars/changes/A3.scala | 1 + .../export-jars/changes/B.scala | 4 ++ .../export-jars/changes/build2.sbt | 1 + .../export-jars/project/Build.scala | 7 ++++ .../source-dependencies/export-jars/test | 18 +++++++++ util/classfile/Analyze.scala | 2 +- 16 files changed, 81 insertions(+), 37 deletions(-) create mode 100644 sbt/src/sbt-test/source-dependencies/export-jars/changes/A1.scala create mode 100644 sbt/src/sbt-test/source-dependencies/export-jars/changes/A2.scala create mode 100644 sbt/src/sbt-test/source-dependencies/export-jars/changes/A3.scala create mode 100644 sbt/src/sbt-test/source-dependencies/export-jars/changes/B.scala create mode 100644 sbt/src/sbt-test/source-dependencies/export-jars/changes/build2.sbt create mode 100644 sbt/src/sbt-test/source-dependencies/export-jars/project/Build.scala create mode 100644 sbt/src/sbt-test/source-dependencies/export-jars/test diff --git a/compile/inc/Analysis.scala b/compile/inc/Analysis.scala index 8bf2f95e6..2fdcf59c1 100644 --- a/compile/inc/Analysis.scala +++ b/compile/inc/Analysis.scala @@ -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) ) } \ No newline at end of file diff --git a/compile/inc/Compile.scala b/compile/inc/Compile.scala index fce1a4de2..adb29dfba 100644 --- a/compile/inc/Compile.scala +++ b/compile/inc/Compile.scala @@ -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) ) => diff --git a/compile/inc/Incremental.scala b/compile/inc/Incremental.scala index 11464bee8..7a6d7c6e7 100644 --- a/compile/inc/Incremental.scala +++ b/compile/inc/Incremental.scala @@ -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) ) diff --git a/compile/inc/Relations.scala b/compile/inc/Relations.scala index 0915d67f7..ec06fbc69 100644 --- a/compile/inc/Relations.scala +++ b/compile/inc/Relations.scala @@ -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" } \ No newline at end of file diff --git a/compile/interface/Analyzer.scala b/compile/interface/Analyzer.scala index bf6143ab0..258857626 100644 --- a/compile/interface/Analyzer.scala +++ b/compile/interface/Analyzer.scala @@ -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) diff --git a/compile/persist/AnalysisFormats.scala b/compile/persist/AnalysisFormats.scala index bab09bc8b..4f817621f 100644 --- a/compile/persist/AnalysisFormats.scala +++ b/compile/persist/AnalysisFormats.scala @@ -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) diff --git a/interface/src/main/java/xsbti/AnalysisCallback.java b/interface/src/main/java/xsbti/AnalysisCallback.java index efca555c4..53a33253b 100644 --- a/interface/src/main/java/xsbti/AnalysisCallback.java +++ b/interface/src/main/java/xsbti/AnalysisCallback.java @@ -18,8 +18,8 @@ public interface AnalysisCallback * class named name from class or jar file binary. */ public void binaryDependency(File binary, String name, File source); /** Called to indicate that the source file source produces a class file at - * module.*/ - public void generatedClass(File source, File module); + * module contain class name.*/ + 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. */ diff --git a/interface/src/test/scala/TestCallback.scala b/interface/src/test/scala/TestCallback.scala index c49e11317..6621a40ef 100644 --- a/interface/src/test/scala/TestCallback.scala +++ b/interface/src/test/scala/TestCallback.scala @@ -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)) } diff --git a/sbt/src/sbt-test/source-dependencies/export-jars/changes/A1.scala b/sbt/src/sbt-test/source-dependencies/export-jars/changes/A1.scala new file mode 100644 index 000000000..bfab0f263 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/export-jars/changes/A1.scala @@ -0,0 +1 @@ +object A { final val x = 1 } \ No newline at end of file diff --git a/sbt/src/sbt-test/source-dependencies/export-jars/changes/A2.scala b/sbt/src/sbt-test/source-dependencies/export-jars/changes/A2.scala new file mode 100644 index 000000000..d18ff057c --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/export-jars/changes/A2.scala @@ -0,0 +1 @@ +object A { final val x = 2 } \ No newline at end of file diff --git a/sbt/src/sbt-test/source-dependencies/export-jars/changes/A3.scala b/sbt/src/sbt-test/source-dependencies/export-jars/changes/A3.scala new file mode 100644 index 000000000..f34adf32f --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/export-jars/changes/A3.scala @@ -0,0 +1 @@ +object A { def x = 3 } \ No newline at end of file diff --git a/sbt/src/sbt-test/source-dependencies/export-jars/changes/B.scala b/sbt/src/sbt-test/source-dependencies/export-jars/changes/B.scala new file mode 100644 index 000000000..058527993 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/export-jars/changes/B.scala @@ -0,0 +1,4 @@ +object B +{ + def main(args: Array[String]) = assert(args(0).toInt == A.x ) +} \ No newline at end of file diff --git a/sbt/src/sbt-test/source-dependencies/export-jars/changes/build2.sbt b/sbt/src/sbt-test/source-dependencies/export-jars/changes/build2.sbt new file mode 100644 index 000000000..0f5735bc8 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/export-jars/changes/build2.sbt @@ -0,0 +1 @@ +exportJars := true \ No newline at end of file diff --git a/sbt/src/sbt-test/source-dependencies/export-jars/project/Build.scala b/sbt/src/sbt-test/source-dependencies/export-jars/project/Build.scala new file mode 100644 index 000000000..4a783acbe --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/export-jars/project/Build.scala @@ -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")) +} \ No newline at end of file diff --git a/sbt/src/sbt-test/source-dependencies/export-jars/test b/sbt/src/sbt-test/source-dependencies/export-jars/test new file mode 100644 index 000000000..4955eccc5 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/export-jars/test @@ -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 \ No newline at end of file diff --git a/util/classfile/Analyze.scala b/util/classfile/Analyze.scala index 6f3ff8cb2..b8c35cee5 100644 --- a/util/classfile/Analyze.scala +++ b/util/classfile/Analyze.scala @@ -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 }