From 37185c0fb6537ebf6b59be2614e69bd40aed0b70 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Fri, 2 Jul 2010 06:57:03 -0400 Subject: [PATCH] discovery, persistence, frontend, and various fixes to incremental --- compile/ComponentCompiler.scala | 2 +- compile/api/SameAPI.scala | 26 ++- compile/api/TagTypeVariables.scala | 6 +- compile/discover/Discovery.scala | 94 ++++++++++ compile/inc/AnalysisStore.scala | 10 +- compile/inc/Changes.scala | 5 +- compile/inc/Compile.scala | 2 + compile/inc/Incremental.scala | 67 ++++--- compile/inc/Relations.scala | 2 + compile/inc/Stamp.scala | 6 +- compile/interface/API.scala | 17 +- compile/interface/Analyzer.scala | 2 +- .../persist}/AnalysisFormats.scala | 3 +- .../persist}/FileBasedStore.scala | 9 +- main/AggressiveCompile.scala | 21 ++- main/AggressiveCompiler.scala | 30 ++- project/build/XSbt.scala | 33 ++-- tasks/standard/Compile.scala | 173 ------------------ tasks/standard/TransitiveCompile.scala | 120 ------------ util/collection/Relation.scala | 2 +- util/io/Path.scala | 1 + 21 files changed, 245 insertions(+), 386 deletions(-) create mode 100644 compile/discover/Discovery.scala rename {main => compile/persist}/AnalysisFormats.scala (99%) rename {main => compile/persist}/FileBasedStore.scala (73%) delete mode 100644 tasks/standard/Compile.scala delete mode 100644 tasks/standard/TransitiveCompile.scala diff --git a/compile/ComponentCompiler.scala b/compile/ComponentCompiler.scala index d6f14c4e7..069561d20 100644 --- a/compile/ComponentCompiler.scala +++ b/compile/ComponentCompiler.scala @@ -70,7 +70,7 @@ class ComponentCompiler(compiler: RawCompiler, manager: ComponentManager) catch { case e: xsbti.CompileFailed => throw new CompileFailed(e.arguments, "Error compiling sbt component '" + id + "'") } import sbt.Path._ copy(resources x rebase(dir, outputDirectory)) - zip((outputDirectory ***) x relativeTo(outputDirectory), targetJar) + zip((outputDirectory ***) x_! relativeTo(outputDirectory), targetJar) } } } diff --git a/compile/api/SameAPI.scala b/compile/api/SameAPI.scala index 9e8b7da82..88f76524a 100644 --- a/compile/api/SameAPI.scala +++ b/compile/api/SameAPI.scala @@ -4,11 +4,19 @@ package xsbt.api import xsbti.api._ +import TagTypeVariables.TypeVars import Function.tupled import scala.collection.{immutable, mutable} class NameChanges(val newTypes: Set[String], val removedTypes: Set[String], val newTerms: Set[String], val removedTerms: Set[String]) +{ + override def toString = + (("New types", newTypes) :: ("Removed types", removedTypes) :: ("New terms", newTerms) :: ("Removed terms", removedTerms) :: Nil).map { + case (label,set) => label + ":\n\t" + set.mkString("\n\t") + }.mkString("Name changes:\n ", "\n ", "\n") + +} object TopLevel { @@ -41,8 +49,12 @@ object SameAPI println(ShowAPI.show(a)) println("\n=========== API #2 ================") println(ShowAPI.show(b)) - - val result = (new SameAPI(a,b, false)).check + + /** de Bruijn levels for type parameters in source a and b*/ + val tagsA = TagTypeVariables(a) + val tagsB = TagTypeVariables(b) + + val result = (new SameAPI(tagsA,tagsB, false, true)).check(a,b) val end = System.currentTimeMillis println(" API comparison took: " + (end - start) / 1000.0 + " s") result @@ -74,13 +86,9 @@ object SameAPI * * If `includePrivate` is true, `private` and `private[this]` members are included in the comparison. Otherwise, those members are excluded. */ -private class SameAPI(a: Source, b: Source, includePrivate: Boolean) +class SameAPI(tagsA: TypeVars, tagsB: TypeVars, includePrivate: Boolean, includeParamNames: Boolean) { import SameAPI._ - /** de Bruijn levels for type parameters in source `a`*/ - private lazy val tagsA = TagTypeVariables(a) - /** de Bruijn levels for type parameters in source `b`*/ - private lazy val tagsB = TagTypeVariables(b) def debug(flag: Boolean, msg: => String): Boolean = { @@ -89,7 +97,7 @@ private class SameAPI(a: Source, b: Source, includePrivate: Boolean) } /** Returns true if source `a` has the same API as source `b`.*/ - def check: Boolean = + def check(a: Source, b: Source): Boolean = { samePackages(a, b) && debug(sameDefinitions(a, b), "Definitions differed") @@ -263,7 +271,7 @@ private class SameAPI(a: Source, b: Source, includePrivate: Boolean) def sameParameters(a: Seq[MethodParameter], b: Seq[MethodParameter]): Boolean = sameSeq(a, b)(sameMethodParameter) def sameMethodParameter(a: MethodParameter, b: MethodParameter): Boolean = - (a.name == b.name) && + (!includeParamNames || a.name == b.name) && sameType(a.tpe, b.tpe) && (a.hasDefault == b.hasDefault) && sameParameterModifier(a.modifier, b.modifier) diff --git a/compile/api/TagTypeVariables.scala b/compile/api/TagTypeVariables.scala index 23763a4f3..a2eca2e83 100644 --- a/compile/api/TagTypeVariables.scala +++ b/compile/api/TagTypeVariables.scala @@ -4,15 +4,17 @@ import xsbti.api._ object TagTypeVariables { - def apply(s: Source): scala.collection.Map[Int, (Int, Int)] = (new TagTypeVariables).tag(s) + type TypeVars = collection.Map[Int, (Int, Int)] + def apply(s: Source): TypeVars = (new TagTypeVariables).tag(s) } +import TagTypeVariables.TypeVars private class TagTypeVariables extends NotNull { private val tags = new scala.collection.mutable.HashMap[Int, (Int, Int)] private var level = 0 private var index = 0 - def tag(s: Source): scala.collection.Map[Int, (Int, Int)] = + def tag(s: Source): TypeVars = { s.definitions.foreach(tagDefinition) tags diff --git a/compile/discover/Discovery.scala b/compile/discover/Discovery.scala new file mode 100644 index 000000000..6f77712be --- /dev/null +++ b/compile/discover/Discovery.scala @@ -0,0 +1,94 @@ +/* sbt -- Simple Build Tool + * Copyright 2010 Mark Harrah + */ +package sbt +package inc + + import xsbti.api.{Path => APath, _} + +import Discovery._ + +class Discovery(baseClasses: Set[String], annotations: Set[String]) +{ + def apply(s: Seq[Definition]): Seq[(Definition, Discovered)] = + s.map { d => (d, apply(d)) } + def apply(d: Definition): Discovered = + d match + { + case c: ClassLike if isPublic(c) && isConcrete(c.modifiers) => discover(c) + case _ => Discovered.empty + } + def discover(c: ClassLike): Discovered = + { + val onClass = findAnnotations(c.annotations) + val onDefs = defAnnotations(c.structure.declared) ++ defAnnotations(c.structure.inherited) + val module = isModule(c) + new Discovered( bases(c.structure.parents), onClass ++ onDefs, module && hasMainMethod(c), module ) + } + def bases(c: Seq[Type]): Set[String] = + c.flatMap(simpleName).filter(baseClasses).toSet + def findAnnotations(as: Seq[Annotation]): Set[String] = + as.flatMap { a => simpleName(a.base).filter(annotations) }.toSet + def defAnnotations(defs: Seq[Definition]): Set[String] = + findAnnotations( defs.flatMap { case d: Def => d.annotations.toSeq; case _ => Nil } ) +} +object Discovery +{ + def isConcrete(a: Definition): Boolean = isConcrete(a.modifiers) + def isConcrete(m: Modifiers) = !m.isAbstract && !m.isDeferred + def isPublic(a: Definition): Boolean = isPublic(a.access) + def isPublic(a: Access): Boolean = a.isInstanceOf[Public] + def isModule(c: ClassLike) = c.definitionType == DefinitionType.Module + + def hasMainMethod(c: ClassLike): Boolean = + hasMainMethod(c.structure.declared) || hasMainMethod(c.structure.inherited) + def hasMainMethod(defs: Seq[Definition]): Boolean = + defs.exists(isMainMethod) + def isMainMethod(d: Definition): Boolean = + d match { + case d: Def => isPublic(d) && isConcrete(d) && isUnit(d.returnType) && isStringArray(d.valueParameters) + case _ => false + } + def isStringArray(vp: IndexedSeq[ParameterList]): Boolean = vp.length == 1 && isStringArray(vp(0).parameters) + def isStringArray(params: Seq[MethodParameter]): Boolean = params.length == 1 && isStringArray(params(0)) + def isStringArray(p: MethodParameter): Boolean = p.modifier == ParameterModifier.Plain && isStringArray(p.tpe) + def isStringArray(t: Type): Boolean = isParameterized(t, "scala.Array", "java.lang.String") // doesn't handle scala.this#Predef#String, should API phase dealias? + + def isParameterized(t: Type, base: String, args: String*): Boolean = t match { + case p: Parameterized => + named(p.baseType, base) && p.typeArguments.length == args.length && p.typeArguments.flatMap(simpleName).sameElements(args) + case _ => false + } + def named(t: Type, nme: String) = simpleName(t) == Some(nme) + + def simpleName(t: Type): Option[String] = t match { + case a: Annotated => simpleName(a.baseType) + case sing: Singleton => None + case p: Projection => + p.prefix match { + case s: Singleton => pathName(s.path, p.id) + case e: EmptyType => Some( p.id ) + case _ => None + } + case _ => None + } + + def pathName(p: APath, id: String): Option[String] = + { + val cs = p.components + cs.last match + { + case _: This => + val ids = cs.init.collect { case i: Id => i.id } + if(ids.length == cs.length - 1) Some( (ids ++ Seq(id)).mkString(".") ) else None + case _ => None + } + } + + def isUnit(t: Type): Boolean = named(t, "scala.Unit") +} +final case class Discovered(baseClasses: Set[String], annotations: Set[String], hasMain: Boolean, isModule: Boolean) +object Discovered +{ + def empty = new Discovered(Set.empty, Set.empty, false, false) +} \ No newline at end of file diff --git a/compile/inc/AnalysisStore.scala b/compile/inc/AnalysisStore.scala index 06d3c8c8c..f8dfe6f6b 100644 --- a/compile/inc/AnalysisStore.scala +++ b/compile/inc/AnalysisStore.scala @@ -7,7 +7,7 @@ package inc trait AnalysisStore { def set(analysis: Analysis, setup: CompileSetup): Unit - def get(): (Analysis, CompileSetup) + def get(): Option[(Analysis, CompileSetup)] } object AnalysisStore @@ -19,15 +19,15 @@ object AnalysisStore backing.set(analysis, setup) last = Some( (analysis, setup) ) } - def get(): (Analysis, CompileSetup) = + def get(): Option[(Analysis, CompileSetup)] = { if(last.isEmpty) - last = Some(backing.get()) - last.get + last = backing.get() + last } } def sync(backing: AnalysisStore): AnalysisStore = new AnalysisStore { def set(analysis: Analysis, setup: CompileSetup): Unit = synchronized { backing.set(analysis, setup) } - def get(): (Analysis, CompileSetup) = synchronized { backing.get() } + def get(): Option[(Analysis, CompileSetup)] = synchronized { backing.get() } } } \ No newline at end of file diff --git a/compile/inc/Changes.scala b/compile/inc/Changes.scala index 9870d560d..11ff23651 100644 --- a/compile/inc/Changes.scala +++ b/compile/inc/Changes.scala @@ -8,7 +8,10 @@ import xsbt.api.NameChanges import java.io.File final case class InitialChanges(internalSrc: Changes[File], removedProducts: Set[File], binaryDeps: Set[File], external: APIChanges[String]) -final case class APIChanges[T](modified: Set[T], names: NameChanges) +final class APIChanges[T](val modified: Set[T], val names: NameChanges) +{ + override def toString = "API Changes: " + modified + "\n" + names +} trait Changes[A] { diff --git a/compile/inc/Compile.scala b/compile/inc/Compile.scala index b47d09f9d..a2e626a8c 100644 --- a/compile/inc/Compile.scala +++ b/compile/inc/Compile.scala @@ -23,6 +23,8 @@ object IncrementalCompile } private final class AnalysisCallback(internalMap: File => Option[File], current: ReadStamps) 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} private val apis = new HashMap[File, Source] diff --git a/compile/inc/Incremental.scala b/compile/inc/Incremental.scala index 334131fdf..8f6b98fec 100644 --- a/compile/inc/Incremental.scala +++ b/compile/inc/Incremental.scala @@ -11,27 +11,33 @@ import java.io.File object Incremental { - // TODO: the Analysis for the last successful compilation should get returned + Boolean indicating success - // TODO: full external name changes, scopeInvalidations def compile(sources: Set[File], previous: Analysis, current: ReadStamps, externalAPI: String => Source, doCompile: Set[File] => Analysis)(implicit equivS: Equiv[Stamp]): Analysis = { - def cycle(invalidated: Set[File], previous: Analysis): Analysis = - if(invalidated.isEmpty) - previous - else - { - val pruned = prune(invalidated, previous) - val fresh = doCompile(invalidated) - val merged = pruned ++ fresh//.copy(relations = pruned.relations ++ fresh.relations, apis = pruned.apis ++ fresh.apis) - val incChanges = changedIncremental(invalidated, previous.apis.internalAPI _, merged.apis.internalAPI _) - val incInv = invalidateIncremental(merged.relations, incChanges) - cycle(incInv, merged) - } - val initialChanges = changedInitial(sources, previous.stamps, previous.apis, current, externalAPI) val initialInv = invalidateInitial(previous.relations, initialChanges) - cycle(initialInv, previous) + println("Initially invalidated: " + initialInv) + cycle(initialInv, previous, doCompile) } + + // TODO: the Analysis for the last successful compilation should get returned + Boolean indicating success + // TODO: full external name changes, scopeInvalidations + def cycle(invalidated: Set[File], previous: Analysis, doCompile: Set[File] => Analysis): Analysis = + if(invalidated.isEmpty) + previous + else + { + val pruned = prune(invalidated, previous) + println("********* Pruned: \n" + pruned.relations + "\n*********") + val fresh = doCompile(invalidated) + println("********* Fresh: \n" + fresh.relations + "\n*********") + val merged = pruned ++ fresh//.copy(relations = pruned.relations ++ fresh.relations, apis = pruned.apis ++ fresh.apis) + println("********* Merged: \n" + merged.relations + "\n*********") + val incChanges = changedIncremental(invalidated, previous.apis.internalAPI _, merged.apis.internalAPI _) + println("Changes:\n" + incChanges) + val incInv = invalidateIncremental(merged.relations, incChanges, invalidated) + println("Incrementally invalidated: " + incInv) + cycle(incInv, merged, doCompile) + } /** @@ -44,13 +50,13 @@ object Incremental val oldApis = lastSources map oldAPI val newApis = lastSources map newAPI - val changes = (lastSources, oldApis, newApis).zipped.filter { (src, oldApi, newApi) => SameAPI(oldApi, newApi) } + val changes = (lastSources, oldApis, newApis).zipped.filter { (src, oldApi, newApi) => !SameAPI(oldApi, newApi) } val changedNames = TopLevel.nameChanges(changes._3, changes._2 ) val modifiedAPIs = changes._1.toSet - 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 = @@ -72,29 +78,38 @@ object Incremental val (changed, unmodified) = inBoth.partition(existingModified) } - def invalidateIncremental(previous: Relations, changes: APIChanges[File]): Set[File] = - invalidateTransitive(previous.internalSrcDeps _, changes.modified )// ++ scopeInvalidations(previous.extAPI _, changes.modified, changes.names) + def invalidateIncremental(previous: Relations, changes: APIChanges[File], recompiledSources: Set[File]): Set[File] = + { + val inv = invalidateTransitive(previous.usesInternalSrc _, changes.modified )// ++ scopeInvalidations(previous.extAPI _, changes.modified, changes.names) + if((inv -- recompiledSources).isEmpty) Set.empty else inv + } /** Only invalidates direct source dependencies. It excludes any sources that were recompiled during the previous run. - * Callers may want to augment the returned set with 'modified' or even all sources recompiled up to this point. */ - def invalidateDirect(sourceDeps: File => Set[File], modified: Set[File]): Set[File] = - (modified flatMap sourceDeps) -- modified + * Callers may want to augment the returned set with 'modified' or all sources recompiled up to this point. */ + def invalidateDirect(dependsOnSrc: File => Set[File], modified: Set[File]): Set[File] = + (modified flatMap dependsOnSrc) -- modified /** Invalidates transitive source dependencies including `modified`. It excludes any sources that were recompiled during the previous run.*/ - @tailrec def invalidateTransitive(sourceDeps: File => Set[File], modified: Set[File]): Set[File] = + @tailrec def invalidateTransitive(dependsOnSrc: File => Set[File], modified: Set[File]): Set[File] = { - val newInv = invalidateDirect(sourceDeps, modified) - if(newInv.isEmpty) modified else invalidateTransitive(sourceDeps, modified ++ newInv) + val newInv = invalidateDirect(dependsOnSrc, modified) + println("\tInvalidated direct: " + newInv) + if(newInv.isEmpty) modified else invalidateTransitive(dependsOnSrc, modified ++ newInv) } /** Invalidates sources based on initially detected 'changes' to the sources, products, and dependencies.*/ def invalidateInitial(previous: Relations, changes: InitialChanges): Set[File] = { val srcChanges = changes.internalSrc + println("Initial source changes: \n\tremoved:" + srcChanges.removed + "\n\tadded: " + srcChanges.added + "\n\tmodified: " + srcChanges.changed) val srcDirect = srcChanges.removed.flatMap(previous.usesInternalSrc) ++ srcChanges.added ++ srcChanges.changed + println("Initial source direct: " + srcDirect) val byProduct = changes.removedProducts.flatMap(previous.produced) + println("Initial by product: " + byProduct) val byBinaryDep = changes.binaryDeps.flatMap(previous.usesBinary) + println("Initial by binary dep: " + byBinaryDep) val byExtSrcDep = changes.external.modified.flatMap(previous.usesExternal) // ++ scopeInvalidations + println("Initial by binary dep: " + byExtSrcDep) srcDirect ++ byProduct ++ byBinaryDep ++ byExtSrcDep } diff --git a/compile/inc/Relations.scala b/compile/inc/Relations.scala index f28d3bb3f..0915d67f7 100644 --- a/compile/inc/Relations.scala +++ b/compile/inc/Relations.scala @@ -102,4 +102,6 @@ private class MRelations(val srcProd: Relation[File, File], val binaryDep: Relat new MRelations(srcProd ++ o.srcProd, binaryDep ++ o.binaryDep, internalSrcDep ++ o.internalSrcDep, externalDep ++ o.externalDep) def -- (sources: Iterable[File]) = new MRelations(srcProd -- sources, binaryDep -- sources, internalSrcDep -- sources, externalDep -- sources) + + override def toString = "Relations:\n products: " + srcProd + "\n bin deps: " + binaryDep + "\n src deps: " + internalSrcDep + "\n ext deps: " + externalDep + "\n" } \ No newline at end of file diff --git a/compile/inc/Stamp.scala b/compile/inc/Stamp.scala index 549ff761a..4b1c02b0a 100644 --- a/compile/inc/Stamp.scala +++ b/compile/inc/Stamp.scala @@ -4,7 +4,6 @@ package sbt package inc -import xsbti.api.Source import java.io.{File, IOException} import Stamp.getStamp @@ -53,6 +52,11 @@ object Stamp case _ => false } } + def show(s: Stamp): String = s match { + case h: Hash => "hash(" + Hash.toHex(h.value) + ")" + case e: Exists => if(e.value) "exists" else "does not exist" + case lm: LastModified => "last modified(" + lm.value + ")" + } val hash = (f: File) => tryStamp(new Hash(Hash(f))) val lastModified = (f: File) => tryStamp(new LastModified(f.lastModified)) diff --git a/compile/interface/API.scala b/compile/interface/API.scala index 997ece0dc..ceecac514 100644 --- a/compile/interface/API.scala +++ b/compile/interface/API.scala @@ -30,13 +30,10 @@ final class API(val global: Global, val callback: xsbti.AnalysisCallback) extend def name = API.name def run: Unit = { - if(java.lang.Boolean.getBoolean("sbt.api.enable")) - { - val start = System.currentTimeMillis - //currentRun.units.foreach(processUnit) - val stop = System.currentTimeMillis - println("API phase took : " + ((stop - start)/1000.0) + " s") - } + val start = System.currentTimeMillis + currentRun.units.foreach(processUnit) + val stop = System.currentTimeMillis + println("API phase took : " + ((stop - start)/1000.0) + " s") } def processUnit(unit: CompilationUnit) { @@ -52,7 +49,7 @@ final class API(val global: Global, val callback: xsbti.AnalysisCallback) extend private def path(components: List[PathComponent]) = new xsbti.api.Path(components.toArray[PathComponent]) private def pathComponents(sym: Symbol, postfix: List[PathComponent]): List[PathComponent] = { - if(sym == NoSymbol || sym.isRoot || sym.isRootPackage) postfix + if(sym == NoSymbol || sym.isRoot || sym.isEmptyPackageClass || sym.isRootPackage) postfix else pathComponents(sym.owner, new xsbti.api.Id(simpleName(sym)) :: postfix) } private def simpleType(t: Type): SimpleType = @@ -135,7 +132,7 @@ final class API(val global: Global, val callback: xsbti.AnalysisCallback) extend s.hasFlag(Flags.DEFAULTPARAM) } private def fieldDef[T](s: Symbol, create: (xsbti.api.Type, String, xsbti.api.Access, xsbti.api.Modifiers, Array[xsbti.api.Annotation]) => T): T = - create(processType(s.tpe), simpleName(s), getAccess(s), getModifiers(s), annotations(s)) + create(processType(s.tpeHK), simpleName(s), getAccess(s), getModifiers(s), annotations(s)) private def typeDef(s: Symbol): xsbti.api.TypeMember = { @@ -209,7 +206,7 @@ final class API(val global: Global, val callback: xsbti.AnalysisCallback) extend private def processType(t: Type): xsbti.api.Type = { - t match + t.dealias match { case NoPrefix => Constants.emptyType case ThisType(sym) => new xsbti.api.Singleton(thisPath(sym)) diff --git a/compile/interface/Analyzer.scala b/compile/interface/Analyzer.scala index 7159d0624..a4030a9cf 100644 --- a/compile/interface/Analyzer.scala +++ b/compile/interface/Analyzer.scala @@ -252,5 +252,5 @@ abstract class Compat { def getArchive = z.archive; def archive = sourceCompatibilityOnly } - private def sourceCompatibilityOnly = error("For source compatibility only: should not get here.") + private def sourceCompatibilityOnly: Nothing = throw new RuntimeException("For source compatibility only: should not get here.") } \ No newline at end of file diff --git a/main/AnalysisFormats.scala b/compile/persist/AnalysisFormats.scala similarity index 99% rename from main/AnalysisFormats.scala rename to compile/persist/AnalysisFormats.scala index bcb94ac12..10246d518 100644 --- a/main/AnalysisFormats.scala +++ b/compile/persist/AnalysisFormats.scala @@ -2,8 +2,7 @@ * Copyright 2010 Mark Harrah */ package sbt - -import inc._ +package inc import xsbti.api.Source import xsbt.api.APIFormat diff --git a/main/FileBasedStore.scala b/compile/persist/FileBasedStore.scala similarity index 73% rename from main/FileBasedStore.scala rename to compile/persist/FileBasedStore.scala index 492040d99..43bf41ffb 100644 --- a/main/FileBasedStore.scala +++ b/compile/persist/FileBasedStore.scala @@ -2,10 +2,9 @@ * Copyright 2010 Mark Harrah */ package sbt +package inc -import inc.{Analysis, AnalysisStore, CompileSetup} - - import java.io.File + import java.io.{File, IOException} import sbinary._ import Operations.{read, write} import DefaultProtocol._ @@ -19,7 +18,9 @@ object FileBasedStore write[(Analysis, CompileSetup)](out, (analysis, setup) ) } - def get(): (Analysis, CompileSetup) = + def get(): Option[(Analysis, CompileSetup)] = + try { Some(getUncaught()) } catch { case io: IOException => None } + def getUncaught(): (Analysis, CompileSetup) = Using.fileInputStream(file) { in => read[(Analysis, CompileSetup)]( in ) } diff --git a/main/AggressiveCompile.scala b/main/AggressiveCompile.scala index aa5c3728a..9e47897e4 100644 --- a/main/AggressiveCompile.scala +++ b/main/AggressiveCompile.scala @@ -13,7 +13,7 @@ import inc._ import sbinary.DefaultProtocol.{ immutableMapFormat, immutableSetFormat, StringFormat } final class CompileConfiguration(val sources: Seq[File], val classpath: Seq[File], val previousAnalysis: Analysis, - val previousSetup: CompileSetup, val currentSetup: CompileSetup, val getAnalysis: File => Option[Analysis], + val previousSetup: Option[CompileSetup], val currentSetup: CompileSetup, val getAnalysis: File => Option[Analysis], val maxErrors: Int, val compiler: AnalyzingCompiler) class AggressiveCompile(cacheDirectory: File) @@ -29,7 +29,7 @@ class AggressiveCompile(cacheDirectory: File) def compile1(sources: Seq[File], classpath: Seq[File], setup: CompileSetup, store: AnalysisStore, analysis: Map[File, Analysis], compiler: AnalyzingCompiler, log: CompileLogger): Analysis = { - val (previousAnalysis, previousSetup) = store.get() + val (previousAnalysis, previousSetup) = extract(store.get()) val config = new CompileConfiguration(sources, classpath, previousAnalysis, previousSetup, setup, analysis.get _, 100, compiler) val result = compile2(config, log) store.set(result, setup) @@ -47,20 +47,29 @@ class AggressiveCompile(cacheDirectory: File) val cArgs = new CompilerArguments(compiler.scalaInstance, compiler.cp) val externalAPI = apiOrEmpty compose Locate.value(withBootclasspath(cArgs, classpath), getAPI) val compile0 = (include: Set[File], callback: AnalysisCallback) => { + IO.createDirectory(outputDirectory) val arguments = cArgs(sources.filter(include), classpath, outputDirectory, options.options) compiler.compile(arguments, callback, maxErrors, log) } val sourcesSet = sources.toSet - val analysis = if(equiv.equiv(previousSetup, currentSetup)) previousAnalysis else Incremental.prune(sourcesSet, previousAnalysis) + val analysis = previousSetup match { + case Some(previous) if equiv.equiv(previous, currentSetup) => previousAnalysis + case _ => Incremental.prune(sourcesSet, previousAnalysis) + } IncrementalCompile(sourcesSet, compile0, analysis, externalAPI) } + private def extract(previous: Option[(Analysis, CompileSetup)]): (Analysis, Option[CompileSetup]) = + previous match + { + case Some((an, setup)) => (an, Some(setup)) + case None => (Analysis.Empty, None) + } import AnalysisFormats._ // The following intermediate definitions are needed because of Scala's implicit parameter rules. - // implicit def a(implicit b: T[Int]): S = ... - // triggers a divierging expansion because T[Int] dominates S, even though they are unrelated + // implicit def a(implicit b: Format[T[Int]]): Format[S] = ... + // triggers a diverging expansion because Format[T[Int]] dominates Format[S] implicit val r = relationFormat[File,File] - implicit val map = immutableMapFormat[File, Stamp] implicit val rF = relationsFormat(r,r,r, relationFormat[File, String]) implicit val aF = analysisFormat(stampsFormat, apisFormat, rF) diff --git a/main/AggressiveCompiler.scala b/main/AggressiveCompiler.scala index 55b0a8301..aecc4f94e 100644 --- a/main/AggressiveCompiler.scala +++ b/main/AggressiveCompiler.scala @@ -14,13 +14,13 @@ class AggressiveCompiler extends xsbti.AppMain final def run(configuration: xsbti.AppConfiguration): xsbti.MainResult = { val args = configuration.arguments.map(_.trim).toList - readLine("Press enter to compile... ") + val command = readLine("Press enter to compile... ").trim() val start = now - val success = run(args, configuration.baseDirectory, configuration.provider) + val success = run(command, args, configuration.baseDirectory, configuration.provider) println("Compiled in " + ((now - start) / 1000.0) + " s") run(configuration) } - def run(args: List[String], cwd: Path, app: xsbti.AppProvider): Boolean = + def run(command: String, args: List[String], cwd: Path, app: xsbti.AppProvider): Boolean = { val launcher = app.scalaProvider.launcher val sources = cwd ** "*.scala" @@ -34,7 +34,12 @@ class AggressiveCompiler extends xsbti.AppMain val compiler = new AnalyzingCompiler(ScalaInstance(args.head, launcher), componentManager, log) val agg = new AggressiveCompile(cacheDirectory) - try { agg(sources.get.toSeq, classpath.get.toSeq, outputDirectory, options, compiler, log); true } + try + { + val analysis = agg(sources.get.toSeq, classpath.get.toSeq, outputDirectory, options, compiler, log) + processResult(analysis, command) + true + } catch { case e: Exception => handleException(e); false } } def handleException(e: Throwable) = @@ -45,4 +50,21 @@ class AggressiveCompiler extends xsbti.AppMain System.err.println(e.toString) } } + def processResult(analysis: inc.Analysis, command: String) + { + if(command.isEmpty) () + else + { + xsbt.api.ParseType.parseType(command) match + { + case Left(err) => println("Error parsing type: " + err) + case Right(tpe) => analysis.apis.internal.values.foreach(processAPI) + } + } + } + def processAPI(api: xsbti.api.Source) + { + val d = new inc.Discovery(Set("scala.Enumeration", "scala.AnyRef", "scala.ScalaObject"), Set("scala.deprecated", "scala.annotation.tailrec")) + println(d(api.definitions).map { case (a, b) => (a.name, b) } ) + } } \ No newline at end of file diff --git a/project/build/XSbt.scala b/project/build/XSbt.scala index 118a98dff..28e6323d8 100644 --- a/project/build/XSbt.scala +++ b/project/build/XSbt.scala @@ -13,31 +13,32 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) with NoCrossPaths val apiSub = baseProject(compilePath / "api", "API", interfaceSub) val controlSub = baseProject(utilPath / "control", "Control") - val collectionSub = project(utilPath / "collection", "Collections", new CollectionsProject(_)) - val ioSub = project(utilPath / "io", "IO", new IOProject(_), controlSub) + val collectionSub = testedBase(utilPath / "collection", "Collections") + val ioSub = testedBase(utilPath / "io", "IO", controlSub) val classpathSub = baseProject(utilPath / "classpath", "Classpath") - val classfileSub = project(utilPath / "classfile", "Classfile", new ClassfileProject(_), ioSub, interfaceSub) - val completeSub = project(utilPath / "complete", "Completion", new CompletionProject(_), ioSub) + val classfileSub = testedBase(utilPath / "classfile", "Classfile", ioSub, interfaceSub) + val completeSub = testedBase(utilPath / "complete", "Completion", ioSub) val ivySub = project("ivy", "Ivy", new IvyProject(_), interfaceSub, launchInterfaceSub) val logSub = project(utilPath / "log", "Logging", new LogProject(_), interfaceSub) val datatypeSub = baseProject("util" /"datatype", "Datatype Generator", ioSub) val compileInterfaceSub = project(compilePath / "interface", "Compiler Interface", new CompilerInterfaceProject(_), interfaceSub) - val compileIncrementalSub = project(compilePath / "inc", "Incremental Compiler", new IncrementalProject(_), collectionSub, apiSub, ioSub) + val compileIncrementalSub = testedBase(compilePath / "inc", "Incremental Compiler", collectionSub, apiSub, ioSub) + val discoverySub = testedBase(compilePath / "discover", "Discovery", compileIncrementalSub, apiSub) + val compilePersistSub = project(compilePath / "persist", "Persist", new PersistProject(_), compileIncrementalSub, apiSub) val compilerSub = project(compilePath, "Compile", new CompileProject(_), launchInterfaceSub, interfaceSub, ivySub, ioSub, classpathSub, compileInterfaceSub) - val taskSub = project(tasksPath, "Tasks", new TaskProject(_), controlSub, collectionSub) + val taskSub = testedBase(tasksPath, "Tasks", controlSub, collectionSub) val cacheSub = project(cachePath, "Cache", new CacheProject(_), ioSub, collectionSub) + val altCompilerSub = baseProject("main", "Alternate Compiler Test", compileIncrementalSub, compilerSub, ioSub, logSub, discoverySub, compilePersistSub) + /** following are not updated for 2.8 or 0.9 */ val testSub = project("scripted", "Test", new TestProject(_), ioSub) val trackingSub = baseProject(cachePath / "tracking", "Tracking", cacheSub) - val stdTaskSub = project(tasksPath / "standard", "Standard Tasks", new StandardTaskProject(_), trackingSub, taskSub, compilerSub, apiSub) - - val altCompilerSub = project("main", "Alternate Compiler Test", new AlternateProject(_), compileIncrementalSub, compilerSub, ioSub, logSub) val sbtSub = project(sbtPath, "Simple Build Tool", new SbtProject(_) {}, compilerSub, launchInterfaceSub) val installerSub = project(sbtPath / "install", "Installer", new InstallerProject(_) {}, sbtSub) @@ -45,6 +46,7 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) with NoCrossPaths lazy val dist = task { None } dependsOn(launchSub.proguard, sbtSub.publishLocal, installerSub.publishLocal) def baseProject(path: Path, name: String, deps: Project*) = project(path, name, new Base(_), deps : _*) + def testedBase(path: Path, name: String, deps: Project*) = project(path, name, new TestedBase(_), deps : _*) /* Multi-subproject paths */ def sbtPath = path("sbt") @@ -93,22 +95,12 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) with NoCrossPaths val sp = "org.scala-tools.testing" %% "specs" % "1.6.5-SNAPSHOT" % "test" val snaps = ScalaToolsSnapshots } - class StandardTaskProject(info: ProjectInfo) extends Base(info) - { - override def testClasspath = super.testClasspath +++ compilerSub.testClasspath --- compilerInterfaceClasspath - } class LogProject(info: ProjectInfo) extends Base(info) with TestDependencies { val jline = jlineDep } - class IncrementalProject(info: ProjectInfo) extends Base(info) with TestDependencies - class CollectionsProject(info: ProjectInfo) extends Base(info) with TestDependencies - class IOProject(info: ProjectInfo) extends Base(info) with TestDependencies - class TaskProject(info: ProjectInfo) extends Base(info) with TestDependencies - class ClassfileProject(info: ProjectInfo) extends Base(info) with TestDependencies - class CompletionProject(info: ProjectInfo) extends Base(info) with TestDependencies class CacheProject(info: ProjectInfo) extends Base(info) with SBinaryDep - class AlternateProject(info: ProjectInfo) extends Base(info) with SBinaryDep + class PersistProject(info: ProjectInfo) extends Base(info) with SBinaryDep trait SBinaryDep extends BasicManagedProject { // these compilation options are useful for debugging caches and task composition @@ -121,6 +113,7 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) with NoCrossPaths override def consoleClasspath = testClasspath override def compileOptions = super.compileOptions ++ compileOptions("-Xelide-below", "0") } + class TestedBase(info: ProjectInfo) extends Base(info) with TestDependencies trait Licensed extends BasicScalaProject { def notice = path("NOTICE") diff --git a/tasks/standard/Compile.scala b/tasks/standard/Compile.scala deleted file mode 100644 index 59c54292f..000000000 --- a/tasks/standard/Compile.scala +++ /dev/null @@ -1,173 +0,0 @@ -/* sbt -- Simple Build Tool - * Copyright 2009, 2010 Mark Harrah - */ -package xsbt - - import java.io.File - import xsbt.api.{APIFormat, SameAPI} - import xsbti.api.Source - -trait CompileImpl[R] -{ - def apply(sourceChanges: ChangeReport[File], classpathChanges: ChangeReport[File], outputDirectory: File, options: Seq[String]): Task[R] - def tracked: Seq[Tracked] -} -final class Compile[R](val cacheDirectory: File, val sources: Task[Set[File]], val classpath: Task[Set[File]], - val outputDirectory: Task[File], val options: Task[Seq[String]], compileImpl: CompileImpl[R]) extends TrackedTaskDefinition[R] -{ - val trackedClasspath = Difference.inputs(classpath, FilesInfo.lastModified, cacheFile("classpath")) - val trackedSource = Difference.inputs(sources, FilesInfo.hash, cacheFile("sources")) - val trackedOptions = - { - import Cache._ - import Task._ - new Changed((outputDirectory, options) map ( "-d" :: _.getAbsolutePath :: _.toList), cacheFile("options")) - } - - val task = - trackedClasspath { rawClasspathChanges => // detect changes to the classpath (last modified only) - trackedSource { rawSourceChanges => // detect changes to sources (hash only) - val newOpts = (opts: Seq[String]) => (opts, rawSourceChanges.markAllModified, rawClasspathChanges.markAllModified) // if options changed, mark everything changed - val sameOpts = (opts: Seq[String]) => (opts, rawSourceChanges, rawClasspathChanges) - trackedOptions(newOpts, sameOpts) bind { // detect changes to options - case (options, sourceChanges, classpathChanges) => - outputDirectory bind { outDir => - FileUtilities.createDirectory(outDir) - compileImpl(sourceChanges, classpathChanges, outDir, options) - } - } - } - } dependsOn(sources, classpath, options, outputDirectory)// raise these dependencies to the top for parallelism - - lazy val tracked = Seq(trackedClasspath, trackedSource, trackedOptions) ++ compileImpl.tracked -} - -object AggressiveCompile -{ - def apply(sources: Task[Set[File]], classpath: Task[Set[File]], outputDirectory: Task[File], options: Task[Seq[String]], - cacheDirectory: File, compilerTask: Task[AnalyzingCompiler], log: CompileLogger): Compile[Set[File]] = - { - val implCache = new File(cacheDirectory, "deps") - val baseCache = new File(cacheDirectory, "inputs") - val impl = new AggressiveCompile(implCache, compilerTask, log) - new Compile(baseCache, sources, classpath, outputDirectory, options, impl) - } -} - -class AggressiveCompile(val cacheDirectory: File, val compilerTask: Task[AnalyzingCompiler], val log: CompileLogger) extends CompileImpl[Set[File]] -{ - def apply(sourceChanges: ChangeReport[File], classpathChanges: ChangeReport[File], outputDirectory: File, options: Seq[String]): Task[Set[File]] = - compilerTask bind { compiler => - tracking { tracker => - timestamp { tstamp => - Task { - log.info("Removed sources: \n\t" + sourceChanges.removed.mkString("\n\t")) - log.info("Added sources: \n\t" + sourceChanges.added.mkString("\n\t")) - log.info("Modified sources: \n\t" + (sourceChanges.modified -- sourceChanges.added -- sourceChanges.removed).mkString("\n\t")) - - val classpath = classpathChanges.checked - val readTracker = tracker.read - // directories that are no longer on the classpath, not necessarily removed from the filesystem - val removedDirectories = classpathChanges.removed.filter(_.isDirectory) - log.info("Directories no longer on classpath:\n\t" + removedDirectories.mkString("\n\t")) - - def uptodate(time: Long, files: Iterable[File]) = files.forall(_.lastModified < time) - def isOutofdate(file: File, related: => Iterable[File]) = !file.exists || !uptodate(file.lastModified, related) - def invalidatesUses(file: File) = !file.exists || file.lastModified > tstamp - def isProductOutofdate(product: File) = isOutofdate(product, readTracker.sources(product)) - def inRemovedDirectory(file: File) = removedDirectories.exists(dir => FileUtilities.relativize(dir, file).isDefined) - def isUsedOutofdate(file: File) = classpathChanges.modified(file) || inRemovedDirectory(file) || invalidatesUses(file) - - // these are products that no longer exist or are older than the sources that produced them - val outofdateProducts = readTracker.allProducts.filter(isProductOutofdate) - log.info("Out of date products:\n\t" + outofdateProducts.mkString("\n\t")) - // used classes and jars that a) no longer exist b) are no longer on the classpath or c) are newer than the sources that use them - val outofdateUses = readTracker.allUsed.filter(isUsedOutofdate) - log.info("Out of date binaries:\n\t" + outofdateUses.mkString("\n\t")) - - val modifiedSources = sourceChanges.modified - val invalidatedByClasspath = outofdateUses.flatMap(readTracker.usedBy) - log.info("Invalidated by classpath changes:\n\t" + invalidatedByClasspath.mkString("\n\t")) - val invalidatedByRemovedSrc = sourceChanges.removed.flatMap(readTracker.dependsOn) - log.info("Invalidated by removed sources:\n\t" + invalidatedByRemovedSrc.mkString("\n\t")) - val productsOutofdate = outofdateProducts.flatMap(readTracker.sources) - log.info("Invalidated by out of date products:\n\t" + productsOutofdate.mkString("\n\t")) - - val rawInvalidatedSources = modifiedSources ++ invalidatedByClasspath ++ invalidatedByRemovedSrc ++ productsOutofdate - val invalidatedSources = scc(readTracker, rawInvalidatedSources) - val sources = invalidatedSources.filter(_.exists) - val previousAPIMap = Map() ++ sources.map { src => (src, APIFormat.read(readTracker.tag(src))) } - val invalidatedProducts = outofdateProducts ++ products(readTracker, invalidatedSources) - - val transitiveIfNeeded = InvalidateTransitive(tracker, sources) - tracker.removeAll(invalidatedProducts ++ classpathChanges.modified ++ (invalidatedSources -- sources)) - tracker.pending(sources) - FileUtilities.delete(invalidatedProducts) - - log.info("All initially invalidated sources:\n\t" + sources.mkString("\n\t")) - if(!sources.isEmpty) - { - val newAPIMap = doCompile(sources, classpath, outputDirectory, options, tracker, compiler, log) - val apiChanged = sources filter { src => !sameAPI(previousAPIMap, newAPIMap, src) } - log.info("Sources with API changes:\n\t" + apiChanged.mkString("\n\t")) - lazy val nextSources = transitiveIfNeeded.invalid ** sourceChanges.checked - def nextDone = nextSources.forall(sources contains _) - val finalAPIMap = - // if either nothing changed or everything was already recompiled, stop here - if(apiChanged.isEmpty || nextDone) newAPIMap - else - { - //val changedNames = TopLevel.nameChanges(newAPIMap.values, previousAPIMap.values) - InvalidateTransitive.clean(tracker, FileUtilities.delete, transitiveIfNeeded) - log.info("All sources invalidated by API changes:\n\t" + nextSources.mkString("\n\t")) - doCompile(nextSources, classpath, outputDirectory, options, tracker, compiler, log) - } - finalAPIMap.foreach { case (src, api) => tracker.tag(src, APIFormat.write(api)) } - } - Set() ++ tracker.read.allProducts - } - }} - } - def products(tracker: ReadTracking[File], srcs: Set[File]): Set[File] = srcs.flatMap(tracker.products) - - def doCompile(sources: Set[File], classpath: Set[File], outputDirectory: File, options: Seq[String], tracker: UpdateTracking[File], compiler: AnalyzingCompiler, log: CompileLogger): scala.collection.Map[File, Source] = - { - val callback = new APIAnalysisCallback(tracker) - log.debug("Compiling using compiler " + compiler) - compiler(sources, classpath, outputDirectory, options, callback, 100, log) - callback.apiMap - } - - import sbinary.DefaultProtocol.FileFormat - val tracking = new DependencyTracked(cacheDirectory, true, (files: File) => FileUtilities.delete(files)) - val timestamp = new Timestamp(new File(cacheDirectory,"timestamp")) - def tracked = Seq(tracking, timestamp) - - def sameAPI[T](a: scala.collection.Map[T, Source], b: scala.collection.Map[T, Source], t: T): Boolean = sameAPI(a.get(t), b.get(t)) - def sameAPI(a: Option[Source], b: Option[Source]): Boolean = - if(a.isEmpty) b.isEmpty else (b.isDefined && SameAPI(a.get, b.get)) - - // TODO: implement - def scc(readTracker: ReadTracking[File], sources: Set[File]) = sources -} - - -private final class APIAnalysisCallback(tracking: UpdateTracking[File]) extends xsbti.AnalysisCallback -{ - val apiMap = new scala.collection.mutable.HashMap[File, Source] - - def sourceDependency(dependsOn: File, source: File) { tracking.dependency(source, dependsOn) } - def jarDependency(jar: File, source: File) { tracking.use(source, jar) } - def classDependency(clazz: File, source: File) { tracking.dependency(source, clazz) } - def generatedClass(source: File, clazz: File) { tracking.product(source, clazz) } - def api(source: File, api: xsbti.api.Source) { apiMap(source) = api } - - def superclassNames = Array() - def annotationNames = Array() - def superclassNotFound(superclassName: String) {} - def beginSource(source: File) {} - def endSource(source: File) {} - def foundApplication(source: File, className: String) {} - def foundSubclass(source: File, subclassName: String, superclassName: String, isModule: Boolean) {} - def foundAnnotated(source: File, className: String, annotationName: String, isModule: Boolean) {} -} diff --git a/tasks/standard/TransitiveCompile.scala b/tasks/standard/TransitiveCompile.scala deleted file mode 100644 index 54d719224..000000000 --- a/tasks/standard/TransitiveCompile.scala +++ /dev/null @@ -1,120 +0,0 @@ -/* sbt -- Simple Build Tool - * Copyright 2009, 2010 Mark Harrah - */ -package xsbt - - import java.io.File - - -trait TransitiveCompile extends CompileImpl[CompileReport] with WithCache -{ - final val invalidation = InvalidateFiles(cacheFile("dependencies/")) - def tracked = Seq(invalidation) - - def apply(sourceChanges: ChangeReport[File], classpathChanges: ChangeReport[File], outputDirectory: File, options: Seq[String]): Task[CompileReport] = - // doesn't notice if classes are removed - invalidation( classpathChanges +++ sourceChanges ) { (report, tracking) => // invalidation based on changes - compile(sourceChanges, classpathChanges, outputDirectory, options, report, tracking) - } - def compile(sourceChanges: ChangeReport[File], classpathChanges: ChangeReport[File], outputDirectory: File, - options: Seq[String], report: InvalidationReport[File], tracking: UpdateTracking[File]): Task[CompileReport] -} - -class StandardCompile(val sources: Task[Set[File]], val classpath: Task[Set[File]], val outputDirectory: Task[File], val options: Task[Seq[String]], - val superclassNames: Task[Set[String]], val compilerTask: Task[AnalyzingCompiler], val cacheDirectory: File, val log: CompileLogger) extends TransitiveCompile -{ - import Task._ - import scala.collection.mutable.{ArrayBuffer, Buffer, HashMap, HashSet, Map, Set => mSet} - - def compile(sourceChanges: ChangeReport[File], classpathChanges: ChangeReport[File], outputDirectory: File, - options: Seq[String], report: InvalidationReport[File], tracking: UpdateTracking[File]): Task[CompileReport] = - { - val sources = report.invalid ** sourceChanges.checked // determine the sources that need recompiling (report.invalid also contains classes and libraries) - val classpath = classpathChanges.checked - compile(sources, classpath, outputDirectory, options, tracking) - } - def compile(sources: Set[File], classpath: Set[File], outputDirectory: File, options: Seq[String], tracking: UpdateTracking[File]): Task[CompileReport] = - { - (compilerTask, superclassNames) map { (compiler, superClasses) => - if(!sources.isEmpty) - { - val callback = new CompileAnalysisCallback(superClasses.toArray, tracking) - log.debug("Compile task calling compiler " + compiler) - compiler(sources, classpath, outputDirectory, options, callback, 100, log) - } - val readTracking = tracking.read - val applicationSet = new HashSet[String] - val subclassMap = new HashMap[String, Buffer[DetectedSubclass]] - readTags(applicationSet, subclassMap, readTracking) - new CompileReport - { - val superclasses = superClasses - def subclasses(superclass: String) = Set() ++ subclassMap.getOrElse(superclass, Nil) - val applications = Set() ++ applicationSet - val classes = Set() ++ readTracking.allProducts - override def toString = - { - val superStrings = superclasses.map(superC => superC + " >: \n\t\t" + subclasses(superC).mkString("\n\t\t")) - val applicationsPart = if(applications.isEmpty) Nil else Seq("Applications") ++ applications - val lines = Seq("Compilation Report:", sources.size + " sources", classes.size + " classes") ++ superStrings - lines.mkString("\n\t") - } - } - } - } - private def abs(f: Set[File]) = f.map(_.getAbsolutePath) - private def readTags(allApplications: mSet[String], subclassMap: Map[String, Buffer[DetectedSubclass]], readTracking: ReadTracking[File]) - { - for((source, tag) <- readTracking.allTags) if(tag.length > 0) - { - val (applications, subclasses) = Tag.fromBytes(tag) - allApplications ++= applications - subclasses.foreach(subclass => subclassMap.getOrElseUpdate(subclass.superclassName, new ArrayBuffer[DetectedSubclass]) += subclass) - } - } - private final class CompileAnalysisCallback(superClasses: Array[String], tracking: UpdateTracking[File]) extends xsbti.AnalysisCallback - { - private var applications = List[String]() - private var subclasses = List[DetectedSubclass]() - def superclassNames = superClasses - def annotationNames = error("TODO") - def superclassNotFound(superclassName: String) = error("Superclass not found: " + superclassName) - def beginSource(source: File) {} - def endSource(source: File) - { - if(!applications.isEmpty || !subclasses.isEmpty) - { - tracking.tag(source, Tag.toBytes(applications, subclasses) ) - applications = Nil - subclasses = Nil - } - } - def foundApplication(source: File, className: String) { applications ::= className } - def foundAnnotated(source: File, className: String, annotationName: String, isModule: Boolean) { error("TODO") } - def foundSubclass(source: File, subclassName: String, superclassName: String, isModule: Boolean): Unit = - subclasses ::= DetectedSubclass(source, subclassName, superclassName, isModule) - def sourceDependency(dependsOn: File, source: File) { tracking.dependency(source, dependsOn) } - def jarDependency(jar: File, source: File) { tracking.use(source, jar) } - def classDependency(clazz: File, source: File) { tracking.dependency(source, clazz) } - def generatedClass(source: File, clazz: File) { tracking.product(source, clazz) } - def api(source: File, api: xsbti.api.Source) = () - } -} - -object Tag -{ - import sbinary.{DefaultProtocol, Format, Operations} - import DefaultProtocol._ - private implicit val subclassFormat: Format[DetectedSubclass] = - asProduct4(DetectedSubclass.apply)( ds => (ds.source, ds.subclassName, ds.superclassName, ds.isModule)) - def toBytes(applications: List[String], subclasses: List[DetectedSubclass]) = CacheIO.toBytes((applications, subclasses)) - def fromBytes(bytes: Array[Byte]) = CacheIO.fromBytes( ( List[String](), List[DetectedSubclass]() ) )(bytes) -} -trait CompileReport extends NotNull -{ - def classes: Set[File] - def applications: Set[String] - def superclasses: Set[String] - def subclasses(superclass: String): Set[DetectedSubclass] -} -final case class DetectedSubclass(source: File, subclassName: String, superclassName: String, isModule: Boolean) extends NotNull \ No newline at end of file diff --git a/util/collection/Relation.scala b/util/collection/Relation.scala index ab31cdd06..ed6046f6e 100644 --- a/util/collection/Relation.scala +++ b/util/collection/Relation.scala @@ -96,5 +96,5 @@ private final class MRelation[A,B](fwd: Map[A, Set[B]], rev: Map[B, Set[A]]) ext private[this] def get[X,Y](map: M[X,Y], t: X): Set[Y] = map.getOrElse(t, Set.empty[Y]) - override def toString = all.mkString("Relation [", ", ", "]") + override def toString = all.map { case (a,b) => a + " -> " + b }.mkString("Relation [", ", ", "]") } \ No newline at end of file diff --git a/util/io/Path.scala b/util/io/Path.scala index 207ce1203..91917f6d8 100644 --- a/util/io/Path.scala +++ b/util/io/Path.scala @@ -280,6 +280,7 @@ sealed abstract class PathFinder extends NotNull */ def ### : PathFinder = new BasePathFinder(this) + def x_![T](mapper: File => Option[T]): Traversable[(File,T)] = x(mapper, false) /** Applies `mapper` to each path selected by this PathFinder and returns the path paired with the non-empty result. * If the result is empty (None) and `errorIfNone` is true, an exception is thrown. * If `errorIfNone` is false, the path is dropped from the returned Traversable.*/