mirror of https://github.com/sbt/sbt.git
support incremental recompilation when using exportJars. fixes #108
This commit is contained in:
parent
b3bcd82a21
commit
0b3ec05a81
|
|
@ -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) )
|
||||
}
|
||||
|
|
@ -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) ) =>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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. */
|
||||
|
|
|
|||
|
|
@ -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)) }
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
object A { final val x = 1 }
|
||||
|
|
@ -0,0 +1 @@
|
|||
object A { final val x = 2 }
|
||||
|
|
@ -0,0 +1 @@
|
|||
object A { def x = 3 }
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
object B
|
||||
{
|
||||
def main(args: Array[String]) = assert(args(0).toInt == A.x )
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
exportJars := true
|
||||
|
|
@ -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"))
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue