mirror of https://github.com/sbt/sbt.git
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:
parent
820a2b6851
commit
0d5814e2b3
|
|
@ -18,7 +18,7 @@ trait 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 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 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 =
|
||||
copy( stamps.markInternalSource(src, stamp), apis.markInternalSource(src, api), relations.addInternalSrcDeps(src, internalDeps) )
|
||||
|
||||
def addBinaryDep(src: File, dep: File, stamp: Stamp): Analysis =
|
||||
copy( stamps.markBinary(dep, stamp), apis, relations.addBinaryDep(src, dep) )
|
||||
def addBinaryDep(src: File, dep: File, className: String, stamp: Stamp): Analysis =
|
||||
copy( stamps.markBinary(dep, className, stamp), apis, relations.addBinaryDep(src, dep) )
|
||||
|
||||
def addExternalDep(src: File, dep: String, depAPI: Source): Analysis =
|
||||
copy( stamps, apis.markExternalAPI(dep, depAPI), relations.addExternalDep(src, dep) )
|
||||
|
|
|
|||
|
|
@ -9,42 +9,74 @@ import java.io.File
|
|||
|
||||
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 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]) => {
|
||||
val callback = new AnalysisCallback(internalMap, current)
|
||||
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, externalAPI, current, outputPath)
|
||||
compile(srcs, callback)
|
||||
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")
|
||||
|
||||
import collection.mutable.{HashMap, HashSet, Map, Set}
|
||||
import collection.mutable.{HashMap, HashSet, ListBuffer, Map, Set}
|
||||
|
||||
private val apis = new HashMap[File, Source]
|
||||
private val binaryDeps = new HashMap[File, Set[File]]
|
||||
private val classes = 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 =
|
||||
map.getOrElseUpdate(a, new HashSet[B]) += b
|
||||
|
||||
def sourceDependency(dependsOn: File, source: File) = add(sourceDeps, source, dependsOn)
|
||||
|
||||
def jarDependency(jar: File, source: File) = add(binaryDeps, source, jar)
|
||||
def classDependency(clazz: File, source: File) = add(binaryDeps, source, clazz)
|
||||
|
||||
def productDependency(classFile: File, sourcePath: File) =
|
||||
internalMap(classFile) match {
|
||||
case Some(dependsOn) => sourceDependency(dependsOn, sourcePath)
|
||||
case None => classDependency(classFile, sourcePath)
|
||||
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 binaryDependency(classFile: File, name: String, source: File) =
|
||||
{
|
||||
internalMap(classFile) match
|
||||
{
|
||||
case Some(dependsOn) =>
|
||||
// dependency is a product of a source not included in this compilation
|
||||
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)
|
||||
|
||||
|
|
@ -52,26 +84,20 @@ private final class AnalysisCallback(internalMap: File => Option[File], current:
|
|||
def endSource(sourcePath: File): Unit =
|
||||
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 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 =
|
||||
(base /: apis) { case (a, (src, api) ) =>
|
||||
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 =
|
||||
(base /: m) { case (outer, (a, bs)) =>
|
||||
(outer /: bs) { (inner, 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) {}
|
||||
}
|
||||
|
|
@ -12,9 +12,9 @@ import java.io.File
|
|||
object Incremental
|
||||
{
|
||||
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)
|
||||
println("Initially invalidated: " + initialInv)
|
||||
cycle(initialInv, previous, doCompile)
|
||||
|
|
@ -60,12 +60,15 @@ object Incremental
|
|||
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 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 extChanges = changedIncremental(previousAPIs.allExternals, previousAPIs.externalAPI, externalAPI)
|
||||
val binaryDepChanges = previous.allBinaries.filter( externalBinaryModified(entry, previous.className _, previous, current)).toSet
|
||||
val extChanges = changedIncremental(previousAPIs.allExternals, previousAPIs.externalAPI, currentExternalAPI(entry, forEntry))
|
||||
|
||||
InitialChanges(srcChanges, removedProducts, binaryDepChanges, extChanges )
|
||||
}
|
||||
|
|
@ -121,6 +124,31 @@ object Incremental
|
|||
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
|
||||
// (this may unnecessarily invalidate them otherwise)
|
||||
/*def scopeInvalidation(previous: Analysis, otherSources: Set[File], names: NameChanges): Set[File] =
|
||||
|
|
|
|||
|
|
@ -29,6 +29,15 @@ object Locate
|
|||
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] =
|
||||
{
|
||||
val defClass = definesClass(entry)
|
||||
|
|
|
|||
|
|
@ -27,9 +27,12 @@ trait Stamps extends ReadStamps
|
|||
def sources: Map[File, Stamp]
|
||||
def binaries: 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 markBinary(bin: File, s: Stamp): Stamps
|
||||
def markBinary(bin: File, className: String, s: Stamp): Stamps
|
||||
def markProduct(prod: File, s: Stamp): Stamps
|
||||
|
||||
def filter(prod: File => Boolean, removeSources: Iterable[File], bin: File => Boolean): Stamps
|
||||
|
|
@ -77,36 +80,37 @@ object Stamps
|
|||
def empty: Stamps =
|
||||
{
|
||||
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 =
|
||||
new MStamps(products, sources, binaries)
|
||||
def apply(products: Map[File, Stamp], sources: Map[File, Stamp], binaries: Map[File, Stamp], binaryClassNames: Map[File, String]): Stamps =
|
||||
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 allBinaries: collection.Set[File] = binaries.keySet
|
||||
def allProducts: collection.Set[File] = products.keySet
|
||||
|
||||
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 =
|
||||
new MStamps(products, sources.updated(src, s), binaries)
|
||||
new MStamps(products, sources.updated(src, s), binaries, classNames)
|
||||
|
||||
def markBinary(bin: File, s: Stamp): Stamps =
|
||||
new MStamps(products, sources, binaries.updated(bin, s))
|
||||
def markBinary(bin: File, className: String, s: Stamp): Stamps =
|
||||
new MStamps(products, sources, binaries.updated(bin, s), classNames.updated(bin, className))
|
||||
|
||||
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 =
|
||||
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 internalSource(src: File) = getStamp(sources, src)
|
||||
def binary(bin: File) = getStamp(binaries, bin)
|
||||
def className(bin: File) = classNames get bin
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,20 +37,19 @@ final class Analyzer(val global: Global, val callback: AnalysisCallback) extends
|
|||
callback.beginSource(sourceFile)
|
||||
for(on <- unit.depends)
|
||||
{
|
||||
def binaryDependency(file: File, className: String) = callback.binaryDependency(file, className, sourceFile)
|
||||
val onSource = on.sourceFile
|
||||
if(onSource == null)
|
||||
{
|
||||
classFile(on) match
|
||||
{
|
||||
case Some(f) =>
|
||||
{
|
||||
case Some((f,className)) =>
|
||||
f match
|
||||
{
|
||||
case ze: ZipArchive#Entry => callback.jarDependency(new File(archive(ze).getName), sourceFile)
|
||||
case pf: PlainFile => callback.classDependency(pf.file, sourceFile)
|
||||
case ze: ZipArchive#Entry => binaryDependency(new File(archive(ze).getName), className)
|
||||
case pf: PlainFile => binaryDependency(pf.file, className)
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
case None => ()
|
||||
}
|
||||
}
|
||||
|
|
@ -82,25 +81,11 @@ final class Analyzer(val global: Global, val callback: AnalysisCallback) extends
|
|||
}
|
||||
}
|
||||
|
||||
private def classForName(name: 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] =
|
||||
private def classFile(sym: Symbol): Option[(AbstractFile, String)] =
|
||||
{
|
||||
import scala.tools.nsc.symtab.Flags
|
||||
val name = flatname(sym, finder.classSeparator) + moduleSuffix(sym)
|
||||
finder.findClass(name) orElse {
|
||||
finder.findClass(name).map(file => (file, name)) orElse {
|
||||
if(isTopLevelModule(sym))
|
||||
{
|
||||
val linked = linkedClass(sym)
|
||||
|
|
|
|||
|
|
@ -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] =
|
||||
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] =
|
||||
asProduct3( Stamps.apply _ )( s => (s.products, s.sources, s.binaries) )(prodF, srcF, binF)
|
||||
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] =
|
||||
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] =
|
||||
asUnion(hashF, modF, existsF)
|
||||
|
|
|
|||
|
|
@ -7,43 +7,21 @@ import java.io.File;
|
|||
|
||||
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. */
|
||||
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
|
||||
* <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
|
||||
* be passed as class dependencies to the classDependency method.*/
|
||||
public void sourceDependency(File dependsOn, File source);
|
||||
/** Called to indicate that the source file <code>source</code> depends on the jar
|
||||
* <code>jar</code>.*/
|
||||
public void jarDependency(File jar, 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> depends on the top-level
|
||||
* 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);
|
||||
/** Called after the source at the given location has been processed. */
|
||||
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. */
|
||||
public void api(File sourceFile, xsbti.api.Source source);
|
||||
}
|
||||
|
|
@ -1,24 +1,44 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2010 Mark Harrah
|
||||
*/
|
||||
package sbt
|
||||
|
||||
import std._
|
||||
import inc.Analysis
|
||||
import TaskExtra._
|
||||
import ClasspathProject._
|
||||
import java.io.File
|
||||
import Path._
|
||||
import Types._
|
||||
import scala.xml.NodeSeq
|
||||
|
||||
trait ClasspathProject
|
||||
{
|
||||
def configurations: Seq[Configuration]
|
||||
def products(configuration: Configuration, intermediate: Boolean): Task[Seq[File]]
|
||||
def unmanagedClasspath(configuration: Configuration): Task[Seq[File]]
|
||||
def managedClasspath(configuration: Configuration): Task[Seq[File]]
|
||||
val products: Classpath
|
||||
val unmanagedClasspath: Classpath
|
||||
val managedClasspath: Classpath
|
||||
val internalDependencyClasspath: Classpath
|
||||
|
||||
def dependencyClasspath(configuration: Configuration): Task[Seq[File]] =
|
||||
(unmanagedClasspath(configuration), managedClasspath(configuration)) map concat[File]
|
||||
lazy val externalDependencyClasspath: Classpath =
|
||||
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]] =
|
||||
(dependencyClasspath(configuration), products(configuration, intermediate) ) map concat[File]
|
||||
lazy val dependencyClasspath: Classpath =
|
||||
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
|
||||
|
|
@ -42,18 +62,25 @@ trait BasicClasspathProject extends ClasspathProject
|
|||
def classpathFilter: FileFilter = GlobFilter("*.jar")
|
||||
def defaultExcludeFilter: FileFilter = MultiProject.defaultExcludes
|
||||
|
||||
override def managedClasspath(configuration: Configuration) =
|
||||
update map { _.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
|
||||
override val managedClasspath: Classpath =
|
||||
TaskMap { configuration =>
|
||||
update map { x => attributed(x.getOrElse(configuration, error("No such configuration '" + configuration.toString + "'")) ) }
|
||||
}
|
||||
|
||||
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._
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -61,7 +88,7 @@ trait BasicClasspathProject extends ClasspathProject
|
|||
trait DefaultClasspathProject extends BasicClasspathProject with Project
|
||||
{
|
||||
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 }
|
||||
|
||||
def otherResolvers: Seq[Resolver] = Nil
|
||||
|
|
@ -84,6 +111,20 @@ trait DefaultClasspathProject extends BasicClasspathProject with Project
|
|||
def ivyScala: Option[IvyScala] = None
|
||||
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 moduleSettings: Task[ModuleSettings] = task {
|
||||
|
|
@ -98,10 +139,10 @@ trait MultiClasspathProject extends DefaultClasspathProject
|
|||
def version: String
|
||||
|
||||
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 =
|
||||
ClasspathProject.depMap(this) map { m =>
|
||||
depMap(this) map { 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 }
|
||||
}
|
||||
|
||||
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 module.id.ModuleRevisionId
|
||||
import module.descriptor.ModuleDescriptor
|
||||
|
||||
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 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]] =
|
||||
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])] =
|
||||
{
|
||||
import ProjectDependency.Classpath
|
||||
p.dependencies map {
|
||||
case Classpath(Left(extPath), conf) => (p.info.externals(extPath), conf)
|
||||
case Classpath(Right(proj), conf) => (proj, conf)
|
||||
p.dependencies map { cp =>
|
||||
(resolveProject(cp.project, p), cp.configuration)
|
||||
}
|
||||
}
|
||||
|
||||
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] =
|
||||
confString.split(";").map( conf =>
|
||||
conf.split("->",2).toList.map(_.trim) match {
|
||||
case x :: Nil => (x,x)
|
||||
case x :: y :: Nil => (x,y)
|
||||
confString.split(";").flatMap( conf =>
|
||||
trim(conf.split("->",2)) match {
|
||||
case x :: Nil => (x,x) :: Nil
|
||||
case x :: y :: Nil => trim(x.split(",")) map { a => (a,y) }
|
||||
case _ => error("Invalid configuration '" + conf + "'") // shouldn't get here
|
||||
}
|
||||
).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 + "'")
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ object Compile
|
|||
|
||||
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 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)
|
||||
|
||||
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 cacheDirectory = outputDirectory / "cache"
|
||||
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(
|
||||
compilers,
|
||||
new Options(classpath, sources, classesDirectory, options, javacOptions, maxErrors),
|
||||
new IncSetup(javaSrcBases, cacheDirectory),
|
||||
new IncSetup(javaSrcBases, analysisMap, cacheDirectory),
|
||||
log
|
||||
)
|
||||
|
||||
|
|
@ -74,6 +74,6 @@ object Compile
|
|||
import in.incSetup._
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -21,14 +21,14 @@ final class CompileConfiguration(val sources: Seq[File], val classpath: Seq[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)
|
||||
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] =
|
||||
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 =
|
||||
{
|
||||
|
|
@ -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] }
|
||||
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 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) => {
|
||||
IO.createDirectory(outputDirectory)
|
||||
|
|
@ -72,7 +73,7 @@ class AggressiveCompile(cacheDirectory: File)
|
|||
case Some(previous) if equiv.equiv(previous, currentSetup) => 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]) =
|
||||
previous match
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ private[sbt] object Analyze
|
|||
val loaded = load(tpe, Some("Problem processing dependencies of source " + source))
|
||||
for(clazz <- loaded; file <- ErrorHandling.convert(IO.classLocationFile(clazz)).right)
|
||||
{
|
||||
val name = clazz.getName
|
||||
if(file.isDirectory)
|
||||
{
|
||||
val resolved = resolveClassFile(file, tpe)
|
||||
|
|
@ -66,14 +67,14 @@ private[sbt] object Analyze
|
|||
productToSource.get(resolvedPath) match
|
||||
{
|
||||
case Some(dependsOn) => analysis.sourceDependency(dependsOn, source)
|
||||
case None => analysis.productDependency(resolvedPath, source)
|
||||
case None => analysis.binaryDependency(resolved, clazz.getName, source)
|
||||
}
|
||||
}
|
||||
else
|
||||
analysis.classDependency(resolved, source)
|
||||
analysis.binaryDependency(resolved, name, source)
|
||||
}
|
||||
else
|
||||
analysis.jarDependency(file, source)
|
||||
analysis.binaryDependency(file, name, source)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue