sbt/tasks/standard/Compile.scala

136 lines
6.7 KiB
Scala

package xsbt
import java.io.File
trait Compile extends TrackedTaskDefinition[CompileReport]
{
val sources: Task[Set[File]]
val classpath: Task[Set[File]]
val outputDirectory: Task[File]
val options: Task[Seq[String]]
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 invalidation = InvalidateFiles(cacheFile("dependencies/"))
lazy val task = create
def create =
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) =>
invalidation( classpathChanges +++ sourceChanges ) { (report, tracking) => // invalidation based on changes
outputDirectory bind { outDir => compile(sourceChanges, classpathChanges, outDir, options, report, tracking) }
}
}
}
} dependsOn(sources, options, outputDirectory)// raise these dependencies to the top for parallelism
def compile(sourceChanges: ChangeReport[File], classpathChanges: ChangeReport[File], outputDirectory: File, options: Seq[String], report: InvalidationReport[File], tracking: UpdateTracking[File]): Task[CompileReport]
lazy val tracked = getTracked
protected def getTracked = Seq(trackedClasspath, trackedSource, trackedOptions, invalidation)
}
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 Compile
{
import Task._
import scala.collection.mutable.{ArrayBuffer, Buffer, HashMap, HashSet, Map, Set => mSet}
override def create = super.create dependsOn(superclassNames, compilerTask) // raise these dependencies to the top for parallelism
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 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 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) }
}
}
object Tag
{
import sbinary.{DefaultProtocol, Format, Operations}
import DefaultProtocol._
private implicit val subclassFormat: Format[DetectedSubclass] =
asProduct4(DetectedSubclass.apply)( ds => Some(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