discovery, persistence, frontend, and various fixes to incremental

This commit is contained in:
Mark Harrah 2010-07-02 06:57:03 -04:00
parent 9ad9df42b6
commit 37185c0fb6
21 changed files with 245 additions and 386 deletions

View File

@ -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)
}
}
}

View File

@ -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)

View File

@ -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

View File

@ -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)
}

View File

@ -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() }
}
}

View File

@ -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]
{

View File

@ -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]

View File

@ -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
}

View File

@ -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"
}

View File

@ -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))

View File

@ -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))

View File

@ -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.")
}

View File

@ -2,8 +2,7 @@
* Copyright 2010 Mark Harrah
*/
package sbt
import inc._
package inc
import xsbti.api.Source
import xsbt.api.APIFormat

View File

@ -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 )
}

View File

@ -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)

View File

@ -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) } )
}
}

View File

@ -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")

View File

@ -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) {}
}

View File

@ -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

View File

@ -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 [", ", ", "]")
}

View File

@ -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.*/