sbt/tasks/standard/Compile.scala

119 lines
5.7 KiB
Scala
Raw Normal View History

package xsbt
import java.io.File
trait Compile extends TrackedTaskDefinition[CompileReport]
{
val sources: Task[Set[File]]
val classpath: Task[Set[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._
new Changed(options.map(_.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, classpathChanges, sourceChanges) =>
invalidation( classpathChanges +++ sourceChanges ) { (report, tracking) => // invalidation based on changes
compile(sourceChanges, classpathChanges, options, report, tracking)
}
}
}
} dependsOn(sources, options)// raise these dependencies to the top for parallelism
def compile(sourceChanges: ChangeReport[File], classpathChanges: ChangeReport[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 options: Task[Seq[String]],
val superclassNames: Task[Set[String]], val compilerTask: Task[AnalyzeCompiler], val cacheDirectory: File, val log: xsbti.Logger) extends Compile
{
import Task._
import sbinary.{DefaultProtocol, Format, Operations}
import DefaultProtocol._
import Operations.{fromByteArray, toByteArray}
import scala.collection.mutable.{ArrayBuffer, Buffer, HashMap, HashSet, Map, Set => mSet}
private implicit val subclassFormat: Format[DetectedSubclass] =
asProduct4(DetectedSubclass.apply)( ds => Some(ds.source, ds.subclassName, ds.superclassName, ds.isModule))
override def create = super.create dependsOn(superclassNames, compilerTask) // raise these dependencies to the top for parallelism
def compile(sourceChanges: ChangeReport[File], classpathChanges: ChangeReport[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
(compilerTask, superclassNames) map { (compiler, superClasses) =>
val callback = new CompileAnalysisCallback(superClasses.toArray, tracking)
val arguments = Seq("-cp", abs(classpath).mkString(File.pathSeparator)) ++ options ++ abs(sources).toSeq
compiler(arguments, 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
}
}
}
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) = fromByteArray[(List[String], List[DetectedSubclass])](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[(File,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, toByteArray( (applications, subclasses) ) )
applications = Nil
subclasses = Nil
}
}
def foundApplication(source: File, className: String) { applications ::= ( (source, 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) }
}
}
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