mirror of https://github.com/sbt/sbt.git
discovery, persistence, frontend, and various fixes to incremental
This commit is contained in:
parent
9ad9df42b6
commit
37185c0fb6
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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() }
|
||||
}
|
||||
}
|
||||
|
|
@ -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]
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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.")
|
||||
}
|
||||
|
|
@ -2,8 +2,7 @@
|
|||
* Copyright 2010 Mark Harrah
|
||||
*/
|
||||
package sbt
|
||||
|
||||
import inc._
|
||||
package inc
|
||||
|
||||
import xsbti.api.Source
|
||||
import xsbt.api.APIFormat
|
||||
|
|
@ -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 )
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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) } )
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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) {}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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 [", ", ", "]")
|
||||
}
|
||||
|
|
@ -280,6 +280,7 @@ sealed abstract class PathFinder extends NotNull
|
|||
*/
|
||||
def ### : PathFinder = new BasePathFinder(this)
|
||||
|
||||
def x_: 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.*/
|
||||
|
|
|
|||
Loading…
Reference in New Issue