mirror of https://github.com/sbt/sbt.git
136 lines
6.7 KiB
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 |