Rework external dependency tracking and multi-projects

Reduce AnalysisCallback interface:
  remove discovery
  simplify dependency notification methods
Use map of classpath entry to Analysis for locating
  source API for external dependencies
Handle classpath changes by locating class
  on classpath and either locating Analysis/Source
  as above or comparing Stamp.  This requires storing
  the class name of a binary dependency now.
Make this process aware of full classpath, including
  boot classpath
This commit is contained in:
Mark Harrah 2010-09-17 21:38:40 -04:00
parent 820a2b6851
commit 0d5814e2b3
12 changed files with 242 additions and 134 deletions

View File

@ -18,7 +18,7 @@ trait Analysis
def copy(stamps: Stamps = stamps, apis: APIs = apis, relations: Relations = relations): Analysis def copy(stamps: Stamps = stamps, apis: APIs = apis, relations: Relations = relations): Analysis
def addSource(src: File, api: Source, stamp: Stamp, internalDeps: Iterable[File]): Analysis def addSource(src: File, api: Source, stamp: Stamp, internalDeps: Iterable[File]): Analysis
def addBinaryDep(src: File, dep: File, stamp: Stamp): Analysis def addBinaryDep(src: File, dep: File, className: String, stamp: Stamp): Analysis
def addExternalDep(src: File, dep: String, api: Source): 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): Analysis
} }
@ -44,8 +44,8 @@ private class MAnalysis(val stamps: Stamps, val apis: APIs, val relations: Relat
def addSource(src: File, api: Source, stamp: Stamp, internalDeps: Iterable[File]): Analysis = def addSource(src: File, api: Source, stamp: Stamp, internalDeps: Iterable[File]): Analysis =
copy( stamps.markInternalSource(src, stamp), apis.markInternalSource(src, api), relations.addInternalSrcDeps(src, internalDeps) ) copy( stamps.markInternalSource(src, stamp), apis.markInternalSource(src, api), relations.addInternalSrcDeps(src, internalDeps) )
def addBinaryDep(src: File, dep: File, stamp: Stamp): Analysis = def addBinaryDep(src: File, dep: File, className: String, stamp: Stamp): Analysis =
copy( stamps.markBinary(dep, stamp), apis, relations.addBinaryDep(src, dep) ) copy( stamps.markBinary(dep, className, stamp), apis, relations.addBinaryDep(src, dep) )
def addExternalDep(src: File, dep: String, depAPI: Source): Analysis = def addExternalDep(src: File, dep: String, depAPI: Source): Analysis =
copy( stamps, apis.markExternalAPI(dep, depAPI), relations.addExternalDep(src, dep) ) copy( stamps, apis.markExternalAPI(dep, depAPI), relations.addExternalDep(src, dep) )

View File

@ -9,42 +9,74 @@ import java.io.File
object IncrementalCompile object IncrementalCompile
{ {
def apply(sources: Set[File], compile: (Set[File], xsbti.AnalysisCallback) => Unit, previous: Analysis, externalAPI: String => Source): Analysis = def apply(sources: Set[File], entry: String => Option[File], compile: (Set[File], xsbti.AnalysisCallback) => Unit, previous: Analysis, forEntry: File => Option[Analysis], outputPath: File): Analysis =
{ {
val current = Stamps.initial(Stamp.exists, Stamp.hash, Stamp.lastModified) val current = Stamps.initial(Stamp.exists, Stamp.hash, Stamp.lastModified)
val internalMap = (f: File) => previous.relations.produced(f).headOption val internalMap = (f: File) => previous.relations.produced(f).headOption
Incremental.compile(sources, previous, current, externalAPI, doCompile(compile, internalMap, current)) val externalAPI = getExternalAPI(entry, forEntry)
Incremental.compile(sources, entry, previous, current, forEntry, doCompile(compile, internalMap, externalAPI, current, outputPath))
} }
def doCompile(compile: (Set[File], xsbti.AnalysisCallback) => Unit, internalMap: File => Option[File], current: ReadStamps) = (srcs: Set[File]) => { def doCompile(compile: (Set[File], xsbti.AnalysisCallback) => Unit, internalMap: File => Option[File], externalAPI: (File, String) => Option[Source], current: ReadStamps, outputPath: File) = (srcs: Set[File]) => {
val callback = new AnalysisCallback(internalMap, current) val callback = new AnalysisCallback(internalMap, externalAPI, current, outputPath)
compile(srcs, callback) compile(srcs, callback)
callback.get callback.get
} }
def getExternalAPI(entry: String => Option[File], forEntry: File => Option[Analysis]): (File, String) => Option[Source] =
(file: File,className: String) =>
entry(className) flatMap { defines =>
if(file != Locate.resolve(defines, className) )
None
else
forEntry(defines) flatMap { analysis =>
analysis.relations.produced(file).headOption flatMap { src =>
analysis.apis.internal get src
}
}
}
} }
private final class AnalysisCallback(internalMap: File => Option[File], current: ReadStamps) extends xsbti.AnalysisCallback private final class AnalysisCallback(internalMap: File => Option[File], externalAPI: (File, String) => Option[Source], current: ReadStamps, outputPath: File) extends xsbti.AnalysisCallback
{ {
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") 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, Map, Set} import collection.mutable.{HashMap, HashSet, ListBuffer, Map, Set}
private val apis = new HashMap[File, Source] private val apis = new HashMap[File, Source]
private val binaryDeps = new HashMap[File, Set[File]] private val binaryDeps = new HashMap[File, Set[File]]
private val classes = new HashMap[File, Set[File]] private val classes = new HashMap[File, Set[File]]
private val sourceDeps = new HashMap[File, Set[File]] private val sourceDeps = new HashMap[File, Set[File]]
private val extSrcDeps = new ListBuffer[(File, String, Source)]
private val binaryClassName = new HashMap[File, String]
private def add[A,B](map: Map[A,Set[B]], a: A, b: B): Unit = private def add[A,B](map: Map[A,Set[B]], a: A, b: B): Unit =
map.getOrElseUpdate(a, new HashSet[B]) += b map.getOrElseUpdate(a, new HashSet[B]) += b
def sourceDependency(dependsOn: File, source: File) = add(sourceDeps, source, dependsOn) def sourceDependency(dependsOn: File, source: File) = if(source != dependsOn) add(sourceDeps, source, dependsOn)
def externalBinaryDependency(binary: File, className: String, source: File)
{
binaryClassName.put(binary, className)
add(binaryDeps, source, binary)
}
def externalSourceDependency(triple: (File, String, Source)) = extSrcDeps += triple
def jarDependency(jar: File, source: File) = add(binaryDeps, source, jar) def binaryDependency(classFile: File, name: String, source: File) =
def classDependency(clazz: File, source: File) = add(binaryDeps, source, clazz) {
internalMap(classFile) match
def productDependency(classFile: File, sourcePath: File) = {
internalMap(classFile) match { case Some(dependsOn) =>
case Some(dependsOn) => sourceDependency(dependsOn, sourcePath) // dependency is a product of a source not included in this compilation
case None => classDependency(classFile, sourcePath) sourceDependency(dependsOn, source)
case None =>
externalAPI(classFile, name) match
{
case Some(api) =>
// dependency is a product of a source in another project
externalSourceDependency( (source, name, api) )
case None =>
// dependency is some other binary on the classpath
externalBinaryDependency(classFile, name, source)
}
} }
}
def generatedClass(source: File, module: File) = add(classes, source, module) def generatedClass(source: File, module: File) = add(classes, source, module)
@ -52,13 +84,14 @@ private final class AnalysisCallback(internalMap: File => Option[File], current:
def endSource(sourcePath: File): Unit = def endSource(sourcePath: File): Unit =
assert(apis.contains(sourcePath)) assert(apis.contains(sourcePath))
def get: Analysis = addBinaries( addProducts( addSources(Analysis.Empty) ) ) 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)( (a, src, prod) => a.addProduct(src, prod, current product prod ) )
def addBinaries(base: Analysis): Analysis = addAll(base, binaryDeps)( (a, src, bin) => a.addBinaryDep(src, bin, current binary bin) ) 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 = def addSources(base: Analysis): Analysis =
(base /: apis) { case (a, (src, api) ) => (base /: apis) { case (a, (src, api) ) =>
a.addSource(src, api, current.internalSource(src), sourceDeps.getOrElse(src, Nil: Iterable[File])) a.addSource(src, api, current.internalSource(src), sourceDeps.getOrElse(src, Nil: Iterable[File]))
} }
def addExternals(base: Analysis): Analysis = (base /: extSrcDeps) { case (a, (source, name, api)) => a.addExternalDep(source, name, api) }
def addAll[A,B](base: Analysis, m: Map[A, Set[B]])( f: (Analysis, A, B) => Analysis): Analysis = def addAll[A,B](base: Analysis, m: Map[A, Set[B]])( f: (Analysis, A, B) => Analysis): Analysis =
(base /: m) { case (outer, (a, bs)) => (base /: m) { case (outer, (a, bs)) =>
@ -66,12 +99,5 @@ private final class AnalysisCallback(internalMap: File => Option[File], current:
f(inner, a, b) f(inner, a, b)
} } } }
private def emptyS = new Array[String](0)
def superclassNames = emptyS
def annotationNames = emptyS
def superclassNotFound(superclassName: String) {}
def foundSubclass(source: File, subclassName: String, superclassName: String, isModule: Boolean) {}
def foundAnnotated(source: File, className: String, annotationName: String, isModule: Boolean) {}
def foundApplication(source: File, className: String) {}
def beginSource(source: File) {} def beginSource(source: File) {}
} }

View File

@ -12,9 +12,9 @@ import java.io.File
object Incremental object Incremental
{ {
def println(s: String) = if(java.lang.Boolean.getBoolean("xsbt.inc.debug")) System.out.println(s) else () def println(s: String) = if(java.lang.Boolean.getBoolean("xsbt.inc.debug")) System.out.println(s) else ()
def compile(sources: Set[File], previous: Analysis, current: ReadStamps, externalAPI: String => Source, doCompile: Set[File] => Analysis)(implicit equivS: Equiv[Stamp]): Analysis = def compile(sources: Set[File], entry: String => Option[File], previous: Analysis, current: ReadStamps, forEntry: File => Option[Analysis], doCompile: Set[File] => Analysis)(implicit equivS: Equiv[Stamp]): Analysis =
{ {
val initialChanges = changedInitial(sources, previous.stamps, previous.apis, current, externalAPI) val initialChanges = changedInitial(entry, sources, previous, current, forEntry)
val initialInv = invalidateInitial(previous.relations, initialChanges) val initialInv = invalidateInitial(previous.relations, initialChanges)
println("Initially invalidated: " + initialInv) println("Initially invalidated: " + initialInv)
cycle(initialInv, previous, doCompile) cycle(initialInv, previous, doCompile)
@ -60,12 +60,15 @@ object Incremental
new APIChanges(modifiedAPIs, changedNames) new APIChanges(modifiedAPIs, changedNames)
} }
def changedInitial(sources: Set[File], previous: Stamps, previousAPIs: APIs, current: ReadStamps, externalAPI: String => Source)(implicit equivS: Equiv[Stamp]): InitialChanges = def changedInitial(entry: String => Option[File], sources: Set[File], previousAnalysis: Analysis, current: ReadStamps, forEntry: File => Option[Analysis])(implicit equivS: Equiv[Stamp]): InitialChanges =
{ {
val previous = previousAnalysis.stamps
val previousAPIs = previousAnalysis.apis
val srcChanges = changes(previous.allInternalSources.toSet, sources, f => !equivS.equiv( previous.internalSource(f), current.internalSource(f) ) ) 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 removedProducts = previous.allProducts.filter( p => !equivS.equiv( previous.product(p), current.product(p) ) ).toSet
val binaryDepChanges = previous.allBinaries.filter( f => !equivS.equiv( previous.binary(f), current.binary(f) ) ).toSet val binaryDepChanges = previous.allBinaries.filter( externalBinaryModified(entry, previous.className _, previous, current)).toSet
val extChanges = changedIncremental(previousAPIs.allExternals, previousAPIs.externalAPI, externalAPI) val extChanges = changedIncremental(previousAPIs.allExternals, previousAPIs.externalAPI, currentExternalAPI(entry, forEntry))
InitialChanges(srcChanges, removedProducts, binaryDepChanges, extChanges ) InitialChanges(srcChanges, removedProducts, binaryDepChanges, extChanges )
} }
@ -121,6 +124,31 @@ object Incremental
previous -- invalidatedSrcs previous -- invalidatedSrcs
} }
def externalBinaryModified(entry: String => Option[File], className: File => Option[String], previous: Stamps, current: ReadStamps)(implicit equivS: Equiv[Stamp]): File => Boolean =
dependsOn =>
orTrue(
for {
name <- className(dependsOn)
e <- entry(name)
} yield {
val resolved = Locate.resolve(e, name)
(resolved != dependsOn) || !equivS.equiv(previous.binary(dependsOn), current.binary(resolved))
}
)
def currentExternalAPI(entry: String => Option[File], forEntry: File => Option[Analysis]): String => Source =
className =>
orEmpty(
for {
e <- entry(className)
analysis <- forEntry(e)
src <- analysis.relations.produced(Locate.resolve(e, className)).headOption
} yield
analysis.apis.internalAPI(src)
)
def orEmpty(o: Option[Source]): Source = o getOrElse APIs.emptyAPI
def orTrue(o: Option[Boolean]): Boolean = o getOrElse true
// unmodifiedSources should not contain any sources in the previous compilation run // unmodifiedSources should not contain any sources in the previous compilation run
// (this may unnecessarily invalidate them otherwise) // (this may unnecessarily invalidate them otherwise)
/*def scopeInvalidation(previous: Analysis, otherSources: Set[File], names: NameChanges): Set[File] = /*def scopeInvalidation(previous: Analysis, otherSources: Set[File], names: NameChanges): Set[File] =

View File

@ -29,6 +29,15 @@ object Locate
case x => x case x => x
} }
/** Returns a function that searches the provided class path for
* a class name and returns the entry that defines that class.*/
def entry(classpath: Seq[File]): String => Option[File] =
{
val entries = classpath.toStream.map { entry => (entry, definesClass(entry)) }
className => entries collect { case (entry, defines) if defines(className) => entry } headOption;
}
def resolve(f: File, className: String): File = if(f.isDirectory) classFile(f, className) else f
def getValue[S](get: File => String => Option[S])(entry: File): String => Either[Boolean, S] = def getValue[S](get: File => String => Option[S])(entry: File): String => Either[Boolean, S] =
{ {
val defClass = definesClass(entry) val defClass = definesClass(entry)

View File

@ -27,9 +27,12 @@ trait Stamps extends ReadStamps
def sources: Map[File, Stamp] def sources: Map[File, Stamp]
def binaries: Map[File, Stamp] def binaries: Map[File, Stamp]
def products: Map[File, Stamp] def products: Map[File, Stamp]
def classNames: Map[File, String]
def className(bin: File): Option[String]
def markInternalSource(src: File, s: Stamp): Stamps def markInternalSource(src: File, s: Stamp): Stamps
def markBinary(bin: File, s: Stamp): Stamps def markBinary(bin: File, className: String, s: Stamp): Stamps
def markProduct(prod: File, s: Stamp): Stamps def markProduct(prod: File, s: Stamp): Stamps
def filter(prod: File => Boolean, removeSources: Iterable[File], bin: File => Boolean): Stamps def filter(prod: File => Boolean, removeSources: Iterable[File], bin: File => Boolean): Stamps
@ -77,36 +80,37 @@ object Stamps
def empty: Stamps = def empty: Stamps =
{ {
val eSt = Map.empty[File, Stamp] val eSt = Map.empty[File, Stamp]
apply(eSt, eSt, eSt) apply(eSt, eSt, eSt, Map.empty[File, String])
} }
def apply(products: Map[File, Stamp], sources: Map[File, Stamp], binaries: Map[File, Stamp]): Stamps = def apply(products: Map[File, Stamp], sources: Map[File, Stamp], binaries: Map[File, Stamp], binaryClassNames: Map[File, String]): Stamps =
new MStamps(products, sources, binaries) new MStamps(products, sources, binaries, binaryClassNames)
} }
private class MStamps(val products: Map[File, Stamp], val sources: Map[File, Stamp], val binaries: Map[File, Stamp]) extends Stamps private class MStamps(val products: Map[File, Stamp], val sources: Map[File, Stamp], val binaries: Map[File, Stamp], val classNames: Map[File, String]) extends Stamps
{ {
def allInternalSources: collection.Set[File] = sources.keySet def allInternalSources: collection.Set[File] = sources.keySet
def allBinaries: collection.Set[File] = binaries.keySet def allBinaries: collection.Set[File] = binaries.keySet
def allProducts: collection.Set[File] = products.keySet def allProducts: collection.Set[File] = products.keySet
def ++ (o: Stamps): Stamps = def ++ (o: Stamps): Stamps =
new MStamps(products ++ o.products, sources ++ o.sources, binaries ++ o.binaries) new MStamps(products ++ o.products, sources ++ o.sources, binaries ++ o.binaries, classNames ++ o.classNames)
def markInternalSource(src: File, s: Stamp): Stamps = def markInternalSource(src: File, s: Stamp): Stamps =
new MStamps(products, sources.updated(src, s), binaries) new MStamps(products, sources.updated(src, s), binaries, classNames)
def markBinary(bin: File, s: Stamp): Stamps = def markBinary(bin: File, className: String, s: Stamp): Stamps =
new MStamps(products, sources, binaries.updated(bin, s)) new MStamps(products, sources, binaries.updated(bin, s), classNames.updated(bin, className))
def markProduct(prod: File, s: Stamp): Stamps = def markProduct(prod: File, s: Stamp): Stamps =
new MStamps(products.updated(prod, s), sources, binaries) new MStamps(products.updated(prod, s), sources, binaries, classNames)
def filter(prod: File => Boolean, removeSources: Iterable[File], bin: File => Boolean): Stamps = def filter(prod: File => Boolean, removeSources: Iterable[File], bin: File => Boolean): Stamps =
new MStamps(products.filterKeys(prod), sources -- removeSources, binaries.filterKeys(bin)) new MStamps(products.filterKeys(prod), sources -- removeSources, binaries.filterKeys(bin), classNames.filterKeys(bin))
def product(prod: File) = getStamp(products, prod) def product(prod: File) = getStamp(products, prod)
def internalSource(src: File) = getStamp(sources, src) def internalSource(src: File) = getStamp(sources, src)
def binary(bin: File) = getStamp(binaries, bin) def binary(bin: File) = getStamp(binaries, bin)
def className(bin: File) = classNames get bin
} }

View File

@ -37,20 +37,19 @@ final class Analyzer(val global: Global, val callback: AnalysisCallback) extends
callback.beginSource(sourceFile) callback.beginSource(sourceFile)
for(on <- unit.depends) for(on <- unit.depends)
{ {
def binaryDependency(file: File, className: String) = callback.binaryDependency(file, className, sourceFile)
val onSource = on.sourceFile val onSource = on.sourceFile
if(onSource == null) if(onSource == null)
{ {
classFile(on) match classFile(on) match
{ {
case Some(f) => case Some((f,className)) =>
{
f match f match
{ {
case ze: ZipArchive#Entry => callback.jarDependency(new File(archive(ze).getName), sourceFile) case ze: ZipArchive#Entry => binaryDependency(new File(archive(ze).getName), className)
case pf: PlainFile => callback.classDependency(pf.file, sourceFile) case pf: PlainFile => binaryDependency(pf.file, className)
case _ => () case _ => ()
} }
}
case None => () case None => ()
} }
} }
@ -82,25 +81,11 @@ final class Analyzer(val global: Global, val callback: AnalysisCallback) extends
} }
} }
private def classForName(name: String) = private def classFile(sym: Symbol): Option[(AbstractFile, String)] =
{
try
{
if(name.indexOf('.') < 0)
{
val sym = definitions.EmptyPackageClass.info.member(newTypeName(name))
if(sym != NoSymbol) Some( sym ) else { callback.superclassNotFound(name); None }
}
else
Some( global.definitions.getClass(newTermName(name)) )
}
catch { case fe: scala.tools.nsc.FatalError => callback.superclassNotFound(name); None }
}
private def classFile(sym: Symbol): Option[AbstractFile] =
{ {
import scala.tools.nsc.symtab.Flags import scala.tools.nsc.symtab.Flags
val name = flatname(sym, finder.classSeparator) + moduleSuffix(sym) val name = flatname(sym, finder.classSeparator) + moduleSuffix(sym)
finder.findClass(name) orElse { finder.findClass(name).map(file => (file, name)) orElse {
if(isTopLevelModule(sym)) if(isTopLevelModule(sym))
{ {
val linked = linkedClass(sym) val linked = linkedClass(sym)

View File

@ -21,8 +21,8 @@ object AnalysisFormats
implicit def setupFormat(implicit outDirF: Format[File], optionF: Format[CompileOptions], compilerVersion: Format[String], orderF: Format[CompileOrder.Value]): Format[CompileSetup] = implicit def setupFormat(implicit outDirF: Format[File], optionF: Format[CompileOptions], compilerVersion: Format[String], orderF: Format[CompileOrder.Value]): Format[CompileSetup] =
asProduct4[CompileSetup, File, CompileOptions, String, CompileOrder.Value]( (a,b,c,d) => new CompileSetup(a,b,c,d) )(s => (s.outputDirectory, s.options, s.compilerVersion, s.order))(outDirF, optionF, compilerVersion, orderF) asProduct4[CompileSetup, File, CompileOptions, String, CompileOrder.Value]( (a,b,c,d) => new CompileSetup(a,b,c,d) )(s => (s.outputDirectory, s.options, s.compilerVersion, s.order))(outDirF, optionF, compilerVersion, orderF)
implicit def stampsFormat(implicit prodF: Format[Map[File, Stamp]], srcF: Format[Map[File, Stamp]], binF: Format[Map[File, Stamp]]): Format[Stamps] = implicit def stampsFormat(implicit prodF: Format[Map[File, Stamp]], srcF: Format[Map[File, Stamp]], binF: Format[Map[File, Stamp]], nameF: Format[Map[File, String]]): Format[Stamps] =
asProduct3( Stamps.apply _ )( s => (s.products, s.sources, s.binaries) )(prodF, srcF, binF) asProduct4( Stamps.apply _ )( s => (s.products, s.sources, s.binaries, s.classNames) )(prodF, srcF, binF, nameF)
implicit def stampFormat(implicit hashF: Format[Hash], modF: Format[LastModified], existsF: Format[Exists]): Format[Stamp] = implicit def stampFormat(implicit hashF: Format[Hash], modF: Format[LastModified], existsF: Format[Exists]): Format[Stamp] =
asUnion(hashF, modF, existsF) asUnion(hashF, modF, existsF)

View File

@ -7,43 +7,21 @@ import java.io.File;
public interface AnalysisCallback public interface AnalysisCallback
{ {
/** The names of classes that the analyzer should find subclasses of.*/
public String[] superclassNames();
/** The names of annotations that the analyzer should look for on methods and classes.*/
public String[] annotationNames();
/** Called when the the given superclass could not be found on the classpath by the compiler.*/
public void superclassNotFound(String superclassName);
/** Called before the source at the given location is processed. */ /** Called before the source at the given location is processed. */
public void beginSource(File source); public void beginSource(File source);
/** Called when the a subclass of one of the classes given in <code>superclassNames</code> is
* discovered.*/
public void foundSubclass(File source, String subclassName, String superclassName, boolean isModule);
/** Called when an annotation with name <code>annotationName</code> is found on a class or one of its methods.*/
public void foundAnnotated(File source, String className, String annotationName, boolean isModule);
/** Called to indicate that the source file <code>source</code> depends on the source file /** Called to indicate that the source file <code>source</code> depends on the source file
* <code>dependsOn</code>. Note that only source files included in the current compilation will * <code>dependsOn</code>. Note that only source files included in the current compilation will
* passed to this method. Dependencies on classes generated by sources not in the current compilation will * passed to this method. Dependencies on classes generated by sources not in the current compilation will
* be passed as class dependencies to the classDependency method.*/ * be passed as class dependencies to the classDependency method.*/
public void sourceDependency(File dependsOn, File source); public void sourceDependency(File dependsOn, File source);
/** Called to indicate that the source file <code>source</code> depends on the jar /** Called to indicate that the source file <code>source</code> depends on the top-level
* <code>jar</code>.*/ * class named <code>name</code> from class or jar file <code>binary</code>. */
public void jarDependency(File jar, File source); public void binaryDependency(File binary, String name, File source);
/** Called to indicate that the source file <code>source</code> depends on the class file
* <code>clazz</code>.*/
public void classDependency(File clazz, File source);
/** Called to indicate that the source file <code>sourcePath</code> depends on the class file
* <code>classFile</code> that is a product of some source. This differs from classDependency
* because it is really a sourceDependency. The source corresponding to <code>classFile</code>
* was not incuded in the compilation so the plugin doesn't know what the source is though. It
* only knows that the class file came from the output directory.*/
public void productDependency(File classFile, File sourcePath);
/** Called to indicate that the source file <code>source</code> produces a class file at /** Called to indicate that the source file <code>source</code> produces a class file at
* <code>module</code>.*/ * <code>module</code>.*/
public void generatedClass(File source, File module); public void generatedClass(File source, File module);
/** Called after the source at the given location has been processed. */ /** Called after the source at the given location has been processed. */
public void endSource(File sourcePath); public void endSource(File sourcePath);
/** Called when a module with a public 'main' method with the right signature is found.*/
public void foundApplication(File source, String className);
/** Called when the public API of a source file is extracted. */ /** 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.Source source);
} }

View File

@ -1,24 +1,44 @@
/* sbt -- Simple Build Tool
* Copyright 2010 Mark Harrah
*/
package sbt package sbt
import std._ import std._
import inc.Analysis
import TaskExtra._ import TaskExtra._
import ClasspathProject._ import ClasspathProject._
import java.io.File import java.io.File
import Path._ import Path._
import Types._
import scala.xml.NodeSeq import scala.xml.NodeSeq
trait ClasspathProject trait ClasspathProject
{ {
def configurations: Seq[Configuration] def configurations: Seq[Configuration]
def products(configuration: Configuration, intermediate: Boolean): Task[Seq[File]] val products: Classpath
def unmanagedClasspath(configuration: Configuration): Task[Seq[File]] val unmanagedClasspath: Classpath
def managedClasspath(configuration: Configuration): Task[Seq[File]] val managedClasspath: Classpath
val internalDependencyClasspath: Classpath
def dependencyClasspath(configuration: Configuration): Task[Seq[File]] = lazy val externalDependencyClasspath: Classpath =
(unmanagedClasspath(configuration), managedClasspath(configuration)) map concat[File] TaskMap { (configuration: Configuration) =>
val un = unmanagedClasspath(configuration)
val m = managedClasspath(configuration)
(un, m) map concat[Attributed[File]]
}
def fullClasspath(configuration: Configuration, intermediate: Boolean): Task[Seq[File]] = lazy val dependencyClasspath: Classpath =
(dependencyClasspath(configuration), products(configuration, intermediate) ) map concat[File] TaskMap { (configuration: Configuration) =>
val external = externalDependencyClasspath(configuration)
val internal = internalDependencyClasspath(configuration)
(external, internal) map concat[Attributed[File]]
}
lazy val fullClasspath: Classpath =
TaskMap { case (configuration: Configuration) =>
val dep = dependencyClasspath(configuration)
val prod = products(configuration)
(dep, prod) map concat[Attributed[File]]
}
} }
trait BasicClasspathProject extends ClasspathProject trait BasicClasspathProject extends ClasspathProject
@ -42,18 +62,25 @@ trait BasicClasspathProject extends ClasspathProject
def classpathFilter: FileFilter = GlobFilter("*.jar") def classpathFilter: FileFilter = GlobFilter("*.jar")
def defaultExcludeFilter: FileFilter = MultiProject.defaultExcludes def defaultExcludeFilter: FileFilter = MultiProject.defaultExcludes
override def managedClasspath(configuration: Configuration) = override val managedClasspath: Classpath =
update map { _.getOrElse(configuration, error("No such configuration '" + configuration.toString + "'")) } TaskMap { configuration =>
update map { x => attributed(x.getOrElse(configuration, error("No such configuration '" + configuration.toString + "'")) ) }
def unmanagedClasspath(configuration: Configuration): Task[Seq[File]] =
unmanagedBase map { base =>
(base * (classpathFilter -- defaultExcludeFilter) +++
(base / configuration.toString).descendentsExcept(classpathFilter, defaultExcludeFilter)).getFiles.toSeq
} }
val unmanagedClasspath: Classpath =
TaskMap { configuration =>
unmanagedBase map { base =>
attributed( (base * (classpathFilter -- defaultExcludeFilter) +++
(base / configuration.toString).descendentsExcept(classpathFilter, defaultExcludeFilter)).getFiles.toSeq )
}
}
lazy val configurationMap: Map[String, Configuration] =
configurations map { conf => (conf.name, conf) } toMap;
import Types._ import Types._
lazy val update = (ivyModule, updateConfig) map { case module :+: config :+: HNil => lazy val update = (ivyModule, updateConfig) map { case module :+: config :+: HNil =>
val confMap = configurations map { conf => (conf.name, conf) } toMap; val confMap = configurationMap
IvyActions.update(module, config) map { case (key, value) => (confMap(key), value) } toMap; IvyActions.update(module, config) map { case (key, value) => (confMap(key), value) } toMap;
} }
} }
@ -61,7 +88,7 @@ trait BasicClasspathProject extends ClasspathProject
trait DefaultClasspathProject extends BasicClasspathProject with Project trait DefaultClasspathProject extends BasicClasspathProject with Project
{ {
def projectID: ModuleID def projectID: ModuleID
def baseResolvers: Seq[Resolver] = Resolver.withDefaultResolvers(ReflectUtilities.allVals[Resolver](this).toSeq.map(_._2) ) def baseResolvers: Seq[Resolver]
lazy val resolvers: Task[Seq[Resolver]] = task { baseResolvers } lazy val resolvers: Task[Seq[Resolver]] = task { baseResolvers }
def otherResolvers: Seq[Resolver] = Nil def otherResolvers: Seq[Resolver] = Nil
@ -84,6 +111,20 @@ trait DefaultClasspathProject extends BasicClasspathProject with Project
def ivyScala: Option[IvyScala] = None def ivyScala: Option[IvyScala] = None
def ivyValidate: Boolean = false def ivyValidate: Boolean = false
//TODO: transitive dependencies
lazy val internalDependencyClasspath: Classpath =
TaskMap { (conf: Configuration) =>
val confMap = configurationMap
val productsTasks =
for( (p: ClasspathProject, Some(confString)) <- ClasspathProject.resolvedDependencies(this)) yield
{
println("Project " + p.name + ", conf: " + confString)
val to = parseSimpleConfigurations(confString).getOrElse(conf.toString, missingMapping(this.name, p.name, conf.toString))
p.products(confMap(to))
}
(productsTasks.toSeq.join) named(name + "/join") map(_.flatten) named(name + "/int")
}
lazy val unmanagedBase = task { dependencyPath.asFile } lazy val unmanagedBase = task { dependencyPath.asFile }
lazy val moduleSettings: Task[ModuleSettings] = task { lazy val moduleSettings: Task[ModuleSettings] = task {
@ -98,10 +139,10 @@ trait MultiClasspathProject extends DefaultClasspathProject
def version: String def version: String
def projectDependencies: Iterable[ModuleID] = def projectDependencies: Iterable[ModuleID] =
ClasspathProject.resolvedDependencies(this) collect { case (p: DefaultClasspathProject, conf) => p.projectID.copy(configurations = conf) } resolvedDependencies(this) collect { case (p: DefaultClasspathProject, conf) => p.projectID.copy(configurations = conf) }
lazy val projectResolver = lazy val projectResolver =
ClasspathProject.depMap(this) map { m => depMap(this) map { m =>
new RawRepository(new ProjectResolver("inter-project", m)) new RawRepository(new ProjectResolver("inter-project", m))
} }
@ -111,14 +152,42 @@ trait MultiClasspathProject extends DefaultClasspathProject
override lazy val resolvers: Task[Seq[Resolver]] = projectResolver map { _ +: baseResolvers } override lazy val resolvers: Task[Seq[Resolver]] = projectResolver map { _ +: baseResolvers }
} }
trait ReflectiveClasspathProject extends DefaultClasspathProject
{
private[this] def vals[T: Manifest] = ReflectUtilities.allVals[T](this).toSeq.map(_._2)
def configurations: Seq[Configuration] = vals[Configuration]
def baseResolvers: Seq[Resolver] = Resolver.withDefaultResolvers(vals[Resolver] )
}
import org.apache.ivy.core.module import org.apache.ivy.core.module
import module.id.ModuleRevisionId import module.id.ModuleRevisionId
import module.descriptor.ModuleDescriptor import module.descriptor.ModuleDescriptor
object ClasspathProject object ClasspathProject
{ {
type Classpath = Configuration => Task[Seq[Attributed[File]]]
val Analyzed = AttributeKey[Analysis]("analysis")
def attributed[T](in: Seq[T]): Seq[Attributed[T]] = in map Attributed.blank
def analyzed[T](data: T, analysis: Analysis) = Attributed.blank(data).put(Analyzed, analysis)
def analyzed(compile: Task[Analysis], inputs: Task[Compile.Inputs]): Task[Attributed[File]] =
(compile, inputs) map { case analysis :+: i :+: HNil =>
analyzed(i.config.classesDirectory, analysis)
}
def concat[A]: (Seq[A], Seq[A]) => Seq[A] = _ ++ _ def concat[A]: (Seq[A], Seq[A]) => Seq[A] = _ ++ _
def extractAnalysis[T](a: Attributed[T]): (T, Analysis) =
(a.data, a.metadata get Analyzed getOrElse Analysis.Empty)
def analysisMap[T](cp: Seq[Attributed[T]]): Map[T, Analysis] =
(cp map extractAnalysis).toMap
def data[T](in: Seq[Attributed[T]]): Seq[T] = in.map(_.data)
def depMap(root: Project): Task[Map[ModuleRevisionId, ModuleDescriptor]] = def depMap(root: Project): Task[Map[ModuleRevisionId, ModuleDescriptor]] =
depMap(MultiProject.topologicalSort(root).dropRight(1) collect { case cp: DefaultClasspathProject => cp }) depMap(MultiProject.topologicalSort(root).dropRight(1) collect { case cp: DefaultClasspathProject => cp })
@ -131,20 +200,27 @@ object ClasspathProject
} }
def resolvedDependencies(p: Project): Iterable[(Project, Option[String])] = def resolvedDependencies(p: Project): Iterable[(Project, Option[String])] =
{ p.dependencies map { cp =>
import ProjectDependency.Classpath (resolveProject(cp.project, p), cp.configuration)
p.dependencies map {
case Classpath(Left(extPath), conf) => (p.info.externals(extPath), conf)
case Classpath(Right(proj), conf) => (proj, conf)
} }
}
def resolveProject(e: Either[File, Project], context: Project): Project =
e match {
case Left(extPath) => context.info.externals(extPath)
case Right(proj) => proj
}
def parseSimpleConfigurations(confString: String): Map[String, String] = def parseSimpleConfigurations(confString: String): Map[String, String] =
confString.split(";").map( conf => confString.split(";").flatMap( conf =>
conf.split("->",2).toList.map(_.trim) match { trim(conf.split("->",2)) match {
case x :: Nil => (x,x) case x :: Nil => (x,x) :: Nil
case x :: y :: Nil => (x,y) case x :: y :: Nil => trim(x.split(",")) map { a => (a,y) }
case _ => error("Invalid configuration '" + conf + "'") // shouldn't get here case _ => error("Invalid configuration '" + conf + "'") // shouldn't get here
} }
).toMap ).toMap
private def trim(a: Array[String]): List[String] = a.toList.map(_.trim)
def missingMapping(from: String, to: String, conf: String) =
error("No configuration mapping defined from '" + from + "' to '" + to + "' for '" + conf + "'")
} }

View File

@ -14,7 +14,7 @@ object Compile
final class Inputs(val compilers: Compilers, val config: Options, val incSetup: IncSetup, val log: Logger) final class Inputs(val compilers: Compilers, val config: Options, val incSetup: IncSetup, val log: Logger)
final class Options(val classpath: Seq[File], val sources: Seq[File], val classesDirectory: File, val options: Seq[String], val javacOptions: Seq[String], val maxErrors: Int) final class Options(val classpath: Seq[File], val sources: Seq[File], val classesDirectory: File, val options: Seq[String], val javacOptions: Seq[String], val maxErrors: Int)
final class IncSetup(val javaSrcBases: Seq[File], val cacheDirectory: File) final class IncSetup(val javaSrcBases: Seq[File], val analysisMap: Map[File, Analysis], val cacheDirectory: File)
final class Compilers(val scalac: AnalyzingCompiler, val javac: JavaCompiler) final class Compilers(val scalac: AnalyzingCompiler, val javac: JavaCompiler)
def inputs(classpath: Seq[File], sources: Seq[File], outputDirectory: File, options: Seq[String], javacOptions: Seq[String], javaSrcBases: Seq[File], maxErrors: Int)(implicit compilers: Compilers, log: Logger): Inputs = def inputs(classpath: Seq[File], sources: Seq[File], outputDirectory: File, options: Seq[String], javacOptions: Seq[String], javaSrcBases: Seq[File], maxErrors: Int)(implicit compilers: Compilers, log: Logger): Inputs =
@ -23,13 +23,13 @@ object Compile
val classesDirectory = outputDirectory / "classes" val classesDirectory = outputDirectory / "classes"
val cacheDirectory = outputDirectory / "cache" val cacheDirectory = outputDirectory / "cache"
val augClasspath = classesDirectory.asFile +: classpath val augClasspath = classesDirectory.asFile +: classpath
inputs(augClasspath, sources, classesDirectory, options, javacOptions, javaSrcBases, cacheDirectory, maxErrors) inputs(augClasspath, sources, classesDirectory, options, javacOptions, javaSrcBases, Map.empty, cacheDirectory, maxErrors)
} }
def inputs(classpath: Seq[File], sources: Seq[File], classesDirectory: File, options: Seq[String], javacOptions: Seq[String], javaSrcBases: Seq[File], cacheDirectory: File, maxErrors: Int)(implicit compilers: Compilers, log: Logger): Inputs = def inputs(classpath: Seq[File], sources: Seq[File], classesDirectory: File, options: Seq[String], javacOptions: Seq[String], javaSrcBases: Seq[File], analysisMap: Map[File, Analysis], cacheDirectory: File, maxErrors: Int)(implicit compilers: Compilers, log: Logger): Inputs =
new Inputs( new Inputs(
compilers, compilers,
new Options(classpath, sources, classesDirectory, options, javacOptions, maxErrors), new Options(classpath, sources, classesDirectory, options, javacOptions, maxErrors),
new IncSetup(javaSrcBases, cacheDirectory), new IncSetup(javaSrcBases, analysisMap, cacheDirectory),
log log
) )
@ -74,6 +74,6 @@ object Compile
import in.incSetup._ import in.incSetup._
val agg = new build.AggressiveCompile(cacheDirectory) val agg = new build.AggressiveCompile(cacheDirectory)
agg(scalac, javac, sources, classpath, classesDirectory, javaSrcBases, options, javacOptions, maxErrors)(in.log) agg(scalac, javac, sources, classpath, classesDirectory, javaSrcBases, options, javacOptions, analysisMap, maxErrors)(in.log)
} }
} }

View File

@ -21,14 +21,14 @@ final class CompileConfiguration(val sources: Seq[File], val classpath: Seq[File
class AggressiveCompile(cacheDirectory: File) class AggressiveCompile(cacheDirectory: File)
{ {
def apply(compiler: AnalyzingCompiler, javac: JavaCompiler, sources: Seq[File], classpath: Seq[File], outputDirectory: File, javaSrcBases: Seq[File] = Nil, options: Seq[String] = Nil, javacOptions: Seq[String] = Nil, maxErrors: Int = 100)(implicit log: Logger): Analysis = def apply(compiler: AnalyzingCompiler, javac: JavaCompiler, sources: Seq[File], classpath: Seq[File], outputDirectory: File, javaSrcBases: Seq[File] = Nil, options: Seq[String] = Nil, javacOptions: Seq[String] = Nil, analysisMap: Map[File, Analysis] = Map.empty, maxErrors: Int = 100)(implicit log: Logger): Analysis =
{ {
val setup = new CompileSetup(outputDirectory, new CompileOptions(options, javacOptions), compiler.scalaInstance.actualVersion, CompileOrder.Mixed) val setup = new CompileSetup(outputDirectory, new CompileOptions(options, javacOptions), compiler.scalaInstance.actualVersion, CompileOrder.Mixed)
compile1(sources, classpath, javaSrcBases, setup, store, Map.empty, compiler, javac, maxErrors) compile1(sources, classpath, javaSrcBases, setup, store, analysisMap, compiler, javac, maxErrors)
} }
def withBootclasspath(args: CompilerArguments, classpath: Seq[File]): Seq[File] = def withBootclasspath(args: CompilerArguments, classpath: Seq[File]): Seq[File] =
args.bootClasspath ++ classpath args.bootClasspath ++ args.finishClasspath(classpath)
def compile1(sources: Seq[File], classpath: Seq[File], javaSrcBases: Seq[File], setup: CompileSetup, store: AnalysisStore, analysis: Map[File, Analysis], compiler: AnalyzingCompiler, javac: JavaCompiler, maxErrors: Int)(implicit log: Logger): Analysis = def compile1(sources: Seq[File], classpath: Seq[File], javaSrcBases: Seq[File], setup: CompileSetup, store: AnalysisStore, analysis: Map[File, Analysis], compiler: AnalyzingCompiler, javac: JavaCompiler, maxErrors: Int)(implicit log: Logger): Analysis =
{ {
@ -46,9 +46,10 @@ class AggressiveCompile(cacheDirectory: File)
val extApis = getAnalysis(f) match { case Some(a) => a.apis.external; case None => Map.empty[String, Source] } val extApis = getAnalysis(f) match { case Some(a) => a.apis.external; case None => Map.empty[String, Source] }
extApis.get _ extApis.get _
} }
val apiOrEmpty = (api: Either[Boolean, Source]) => api.right.toOption.getOrElse( APIs.emptyAPI ) val apiOption= (api: Either[Boolean, Source]) => api.right.toOption
val cArgs = new CompilerArguments(compiler.scalaInstance, compiler.cp) val cArgs = new CompilerArguments(compiler.scalaInstance, compiler.cp)
val externalAPI = apiOrEmpty compose Locate.value(withBootclasspath(cArgs, classpath), getAPI) val searchClasspath = withBootclasspath(cArgs, classpath)
val entry = Locate.entry(searchClasspath)
val compile0 = (include: Set[File], callback: AnalysisCallback) => { val compile0 = (include: Set[File], callback: AnalysisCallback) => {
IO.createDirectory(outputDirectory) IO.createDirectory(outputDirectory)
@ -72,7 +73,7 @@ class AggressiveCompile(cacheDirectory: File)
case Some(previous) if equiv.equiv(previous, currentSetup) => previousAnalysis case Some(previous) if equiv.equiv(previous, currentSetup) => previousAnalysis
case _ => Incremental.prune(sourcesSet, previousAnalysis) case _ => Incremental.prune(sourcesSet, previousAnalysis)
} }
IncrementalCompile(sourcesSet, compile0, analysis, externalAPI) IncrementalCompile(sourcesSet, entry, compile0, analysis, getAnalysis, outputDirectory)
} }
private def extract(previous: Option[(Analysis, CompileSetup)]): (Analysis, Option[CompileSetup]) = private def extract(previous: Option[(Analysis, CompileSetup)]): (Analysis, Option[CompileSetup]) =
previous match previous match

View File

@ -56,6 +56,7 @@ private[sbt] object Analyze
val loaded = load(tpe, Some("Problem processing dependencies of source " + source)) val loaded = load(tpe, Some("Problem processing dependencies of source " + source))
for(clazz <- loaded; file <- ErrorHandling.convert(IO.classLocationFile(clazz)).right) for(clazz <- loaded; file <- ErrorHandling.convert(IO.classLocationFile(clazz)).right)
{ {
val name = clazz.getName
if(file.isDirectory) if(file.isDirectory)
{ {
val resolved = resolveClassFile(file, tpe) val resolved = resolveClassFile(file, tpe)
@ -66,14 +67,14 @@ private[sbt] object Analyze
productToSource.get(resolvedPath) match productToSource.get(resolvedPath) match
{ {
case Some(dependsOn) => analysis.sourceDependency(dependsOn, source) case Some(dependsOn) => analysis.sourceDependency(dependsOn, source)
case None => analysis.productDependency(resolvedPath, source) case None => analysis.binaryDependency(resolved, clazz.getName, source)
} }
} }
else else
analysis.classDependency(resolved, source) analysis.binaryDependency(resolved, name, source)
} }
else else
analysis.jarDependency(file, source) analysis.binaryDependency(file, name, source)
} }
} }
} }