diff --git a/compile/inc/Analysis.scala b/compile/inc/Analysis.scala index d53d4ccdc..795358888 100644 --- a/compile/inc/Analysis.scala +++ b/compile/inc/Analysis.scala @@ -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) ) diff --git a/compile/inc/Compile.scala b/compile/inc/Compile.scala index a2e626a8c..42f3239e9 100644 --- a/compile/inc/Compile.scala +++ b/compile/inc/Compile.scala @@ -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) {} } \ No newline at end of file diff --git a/compile/inc/Incremental.scala b/compile/inc/Incremental.scala index 5e3a7ecb4..e84deddea 100644 --- a/compile/inc/Incremental.scala +++ b/compile/inc/Incremental.scala @@ -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] = diff --git a/compile/inc/Locate.scala b/compile/inc/Locate.scala index 4a12425af..5a2396086 100644 --- a/compile/inc/Locate.scala +++ b/compile/inc/Locate.scala @@ -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) diff --git a/compile/inc/Stamp.scala b/compile/inc/Stamp.scala index 4b1c02b0a..b078c2318 100644 --- a/compile/inc/Stamp.scala +++ b/compile/inc/Stamp.scala @@ -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 } diff --git a/compile/interface/Analyzer.scala b/compile/interface/Analyzer.scala index 809881b92..048a7c75d 100644 --- a/compile/interface/Analyzer.scala +++ b/compile/interface/Analyzer.scala @@ -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) diff --git a/compile/persist/AnalysisFormats.scala b/compile/persist/AnalysisFormats.scala index 13ac559ec..57cd5521a 100644 --- a/compile/persist/AnalysisFormats.scala +++ b/compile/persist/AnalysisFormats.scala @@ -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) diff --git a/interface/src/main/java/xsbti/AnalysisCallback.java b/interface/src/main/java/xsbti/AnalysisCallback.java index 03c4798c9..d3eb2ab54 100644 --- a/interface/src/main/java/xsbti/AnalysisCallback.java +++ b/interface/src/main/java/xsbti/AnalysisCallback.java @@ -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 superclassNames is - * discovered.*/ - public void foundSubclass(File source, String subclassName, String superclassName, boolean isModule); - /** Called when an annotation with name annotationName 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 source depends on the source file * dependsOn. 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 source depends on the jar - * jar.*/ - public void jarDependency(File jar, File source); - /** Called to indicate that the source file source depends on the class file - * clazz.*/ - public void classDependency(File clazz, File source); - /** Called to indicate that the source file sourcePath depends on the class file - * classFile that is a product of some source. This differs from classDependency - * because it is really a sourceDependency. The source corresponding to classFile - * 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 source depends on the top-level + * class named name from class or jar file binary. */ + public void binaryDependency(File binary, String name, File source); /** Called to indicate that the source file source produces a class file at * module.*/ public void generatedClass(File source, File module); /** 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); } \ No newline at end of file diff --git a/main/ClasspathProject.scala b/main/ClasspathProject.scala index 8d8ac08e4..ae8e90185 100644 --- a/main/ClasspathProject.scala +++ b/main/ClasspathProject.scala @@ -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 + "'") } \ No newline at end of file diff --git a/main/Compile.scala b/main/Compile.scala index 018257fc3..8bbb7752c 100644 --- a/main/Compile.scala +++ b/main/Compile.scala @@ -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) } } \ No newline at end of file diff --git a/main/build/AggressiveCompile.scala b/main/build/AggressiveCompile.scala index 4d8fbc19a..ed684b29c 100644 --- a/main/build/AggressiveCompile.scala +++ b/main/build/AggressiveCompile.scala @@ -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 diff --git a/util/classfile/Analyze.scala b/util/classfile/Analyze.scala index d9d803010..9a03361e0 100644 --- a/util/classfile/Analyze.scala +++ b/util/classfile/Analyze.scala @@ -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) } } }