2010-06-27 15:18:35 +02:00
|
|
|
/* sbt -- Simple Build Tool
|
|
|
|
|
* Copyright 2010 Mark Harrah
|
|
|
|
|
*/
|
|
|
|
|
package sbt
|
2012-04-18 14:07:53 +02:00
|
|
|
package compiler
|
2010-06-27 15:18:35 +02:00
|
|
|
|
|
|
|
|
import inc._
|
|
|
|
|
|
|
|
|
|
import java.io.File
|
2010-07-15 01:24:50 +02:00
|
|
|
import classpath.ClasspathUtilities
|
|
|
|
|
import classfile.Analyze
|
2010-06-27 15:18:35 +02:00
|
|
|
import xsbti.api.Source
|
|
|
|
|
import xsbti.AnalysisCallback
|
2011-06-23 01:17:10 +02:00
|
|
|
import inc.Locate.DefinesClass
|
2010-06-27 15:18:35 +02:00
|
|
|
import CompileSetup._
|
2011-04-04 03:15:35 +02:00
|
|
|
import CompileOrder.{JavaThenScala, Mixed, ScalaThenJava}
|
2010-06-27 15:18:35 +02:00
|
|
|
import sbinary.DefaultProtocol.{ immutableMapFormat, immutableSetFormat, StringFormat }
|
|
|
|
|
|
2011-03-10 00:04:53 +01:00
|
|
|
final class CompileConfiguration(val sources: Seq[File], val classpath: Seq[File],
|
2011-06-23 01:17:10 +02:00
|
|
|
val previousAnalysis: Analysis, val previousSetup: Option[CompileSetup], val currentSetup: CompileSetup, val getAnalysis: File => Option[Analysis], val definesClass: DefinesClass,
|
2010-07-15 01:24:50 +02:00
|
|
|
val maxErrors: Int, val compiler: AnalyzingCompiler, val javac: JavaCompiler)
|
2010-06-27 15:18:35 +02:00
|
|
|
|
2012-01-15 18:29:53 +01:00
|
|
|
class AggressiveCompile(cacheFile: File)
|
2010-06-27 15:18:35 +02:00
|
|
|
{
|
2011-07-26 16:57:20 +02:00
|
|
|
def apply(compiler: AnalyzingCompiler, javac: JavaCompiler, sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String] = Nil, javacOptions: Seq[String] = Nil, analysisMap: Map[File, Analysis] = Map.empty, definesClass: DefinesClass = Locate.definesClass _, maxErrors: Int = 100, compileOrder: CompileOrder.Value = Mixed, skip: Boolean = false)(implicit log: Logger): Analysis =
|
2010-06-27 15:18:35 +02:00
|
|
|
{
|
2011-04-04 03:15:35 +02:00
|
|
|
val setup = new CompileSetup(outputDirectory, new CompileOptions(options, javacOptions), compiler.scalaInstance.actualVersion, compileOrder)
|
2011-07-26 16:57:20 +02:00
|
|
|
compile1(sources, classpath, setup, store, analysisMap, definesClass, compiler, javac, maxErrors, skip)
|
2010-06-27 15:18:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def withBootclasspath(args: CompilerArguments, classpath: Seq[File]): Seq[File] =
|
2010-09-18 03:38:40 +02:00
|
|
|
args.bootClasspath ++ args.finishClasspath(classpath)
|
2010-06-27 15:18:35 +02:00
|
|
|
|
2011-07-26 16:57:20 +02:00
|
|
|
def compile1(sources: Seq[File], classpath: Seq[File], setup: CompileSetup, store: AnalysisStore, analysis: Map[File, Analysis], definesClass: DefinesClass, compiler: AnalyzingCompiler, javac: JavaCompiler, maxErrors: Int, skip: Boolean)(implicit log: Logger): Analysis =
|
2010-06-27 15:18:35 +02:00
|
|
|
{
|
2010-07-02 12:57:03 +02:00
|
|
|
val (previousAnalysis, previousSetup) = extract(store.get())
|
2011-07-26 16:57:20 +02:00
|
|
|
if(skip)
|
|
|
|
|
previousAnalysis
|
|
|
|
|
else {
|
|
|
|
|
val config = new CompileConfiguration(sources, classpath, previousAnalysis, previousSetup, setup, analysis.get _, definesClass, maxErrors, compiler, javac)
|
|
|
|
|
val (modified, result) = compile2(config)
|
|
|
|
|
if(modified)
|
|
|
|
|
store.set(result, setup)
|
|
|
|
|
result
|
|
|
|
|
}
|
2010-06-27 15:18:35 +02:00
|
|
|
}
|
2010-10-30 21:44:36 +02:00
|
|
|
def compile2(config: CompileConfiguration)(implicit log: Logger, equiv: Equiv[CompileSetup]): (Boolean, Analysis) =
|
2010-06-27 15:18:35 +02:00
|
|
|
{
|
|
|
|
|
import config._
|
|
|
|
|
import currentSetup._
|
2011-03-04 18:02:46 +01:00
|
|
|
val absClasspath = classpath.map(_.getCanonicalFile)
|
2010-09-18 03:38:40 +02:00
|
|
|
val apiOption= (api: Either[Boolean, Source]) => api.right.toOption
|
2010-06-27 15:18:35 +02:00
|
|
|
val cArgs = new CompilerArguments(compiler.scalaInstance, compiler.cp)
|
2011-08-04 13:20:25 +02:00
|
|
|
val searchClasspath = explicitBootClasspath(options.options) ++ withBootclasspath(cArgs, absClasspath)
|
2011-06-23 01:17:10 +02:00
|
|
|
val entry = Locate.entry(searchClasspath, definesClass)
|
2010-07-15 01:24:50 +02:00
|
|
|
|
2010-06-27 15:18:35 +02:00
|
|
|
val compile0 = (include: Set[File], callback: AnalysisCallback) => {
|
2010-07-02 12:57:03 +02:00
|
|
|
IO.createDirectory(outputDirectory)
|
2010-07-15 01:24:50 +02:00
|
|
|
val incSrc = sources.filter(include)
|
2011-02-24 01:19:44 +01:00
|
|
|
val (javaSrcs, scalaSrcs) = incSrc partition javaOnly
|
2011-06-01 00:37:07 +02:00
|
|
|
logInputs(log, javaSrcs.size, scalaSrcs.size, outputDirectory)
|
2011-04-04 03:15:35 +02:00
|
|
|
def compileScala() =
|
|
|
|
|
if(!scalaSrcs.isEmpty)
|
|
|
|
|
{
|
|
|
|
|
val sources = if(order == Mixed) incSrc else scalaSrcs
|
|
|
|
|
val arguments = cArgs(sources, absClasspath, outputDirectory, options.options)
|
|
|
|
|
compiler.compile(arguments, callback, maxErrors, log)
|
2010-07-15 01:24:50 +02:00
|
|
|
}
|
2011-04-04 03:15:35 +02:00
|
|
|
def compileJava() =
|
|
|
|
|
if(!javaSrcs.isEmpty)
|
|
|
|
|
{
|
|
|
|
|
import Path._
|
2011-05-15 02:16:57 +02:00
|
|
|
val loader = ClasspathUtilities.toLoader(searchClasspath)
|
2011-04-04 03:15:35 +02:00
|
|
|
def readAPI(source: File, classes: Seq[Class[_]]) { callback.api(source, ClassToAPI(classes)) }
|
|
|
|
|
Analyze(outputDirectory, javaSrcs, log)(callback, loader, readAPI) {
|
|
|
|
|
javac(javaSrcs, absClasspath, outputDirectory, options.javacOptions)
|
|
|
|
|
}
|
|
|
|
|
}
|
2011-05-08 04:02:05 +02:00
|
|
|
if(order == JavaThenScala) { compileJava(); compileScala() } else { compileScala(); compileJava() }
|
2010-06-27 15:18:35 +02:00
|
|
|
}
|
2010-07-15 01:24:50 +02:00
|
|
|
|
2010-06-27 15:18:35 +02:00
|
|
|
val sourcesSet = sources.toSet
|
2010-07-02 12:57:03 +02:00
|
|
|
val analysis = previousSetup match {
|
|
|
|
|
case Some(previous) if equiv.equiv(previous, currentSetup) => previousAnalysis
|
|
|
|
|
case _ => Incremental.prune(sourcesSet, previousAnalysis)
|
|
|
|
|
}
|
2011-05-30 01:17:31 +02:00
|
|
|
IncrementalCompile(sourcesSet, entry, compile0, analysis, getAnalysis, outputDirectory, log)
|
2010-06-27 15:18:35 +02:00
|
|
|
}
|
2011-06-01 00:37:07 +02:00
|
|
|
private[this] def logInputs(log: Logger, javaCount: Int, scalaCount: Int, out: File)
|
2011-05-30 01:17:31 +02:00
|
|
|
{
|
|
|
|
|
val scalaMsg = Util.counted("Scala source", "", "s", scalaCount)
|
|
|
|
|
val javaMsg = Util.counted("Java source", "", "s", javaCount)
|
|
|
|
|
val combined = scalaMsg ++ javaMsg
|
|
|
|
|
if(!combined.isEmpty)
|
2011-06-01 00:37:07 +02:00
|
|
|
log.info(combined.mkString("Compiling ", " and ", " to " + out.getAbsolutePath + "..."))
|
2011-05-30 01:17:31 +02:00
|
|
|
}
|
2010-07-02 12:57:03 +02:00
|
|
|
private def extract(previous: Option[(Analysis, CompileSetup)]): (Analysis, Option[CompileSetup]) =
|
|
|
|
|
previous match
|
|
|
|
|
{
|
|
|
|
|
case Some((an, setup)) => (an, Some(setup))
|
|
|
|
|
case None => (Analysis.Empty, None)
|
|
|
|
|
}
|
2010-07-15 01:24:50 +02:00
|
|
|
def javaOnly(f: File) = f.getName.endsWith(".java")
|
2010-06-27 15:18:35 +02:00
|
|
|
|
2011-08-04 13:20:25 +02:00
|
|
|
private[this] def explicitBootClasspath(options: Seq[String]): Seq[File] =
|
|
|
|
|
options.dropWhile(_ != CompilerArguments.BootClasspathOption).drop(1).take(1).headOption.toList.flatMap(IO.parseClasspath)
|
|
|
|
|
|
2010-06-27 15:18:35 +02:00
|
|
|
import AnalysisFormats._
|
2012-01-15 18:29:53 +01:00
|
|
|
val store = AggressiveCompile.staticCache(cacheFile, AnalysisStore.sync(AnalysisStore.cached(FileBasedStore(cacheFile))))
|
2011-03-26 02:37:17 +01:00
|
|
|
}
|
2012-04-18 14:07:53 +02:00
|
|
|
object AggressiveCompile
|
2011-03-26 02:37:17 +01:00
|
|
|
{
|
|
|
|
|
import collection.mutable
|
|
|
|
|
import java.lang.ref.{Reference,SoftReference}
|
|
|
|
|
private[this] val cache = new collection.mutable.HashMap[File, Reference[AnalysisStore]]
|
|
|
|
|
private def staticCache(file: File, backing: => AnalysisStore): AnalysisStore =
|
|
|
|
|
synchronized {
|
|
|
|
|
cache get file flatMap { ref => Option(ref.get) } getOrElse {
|
|
|
|
|
val b = backing
|
|
|
|
|
cache.put(file, new SoftReference(b))
|
|
|
|
|
b
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-04-18 14:07:53 +02:00
|
|
|
|
|
|
|
|
def directOrFork(instance: ScalaInstance, cpOptions: ClasspathOptions, javaHome: Option[File]): JavaCompiler =
|
|
|
|
|
if(javaHome.isDefined)
|
|
|
|
|
JavaCompiler.fork(cpOptions, instance)(forkJavac(javaHome))
|
|
|
|
|
else
|
|
|
|
|
JavaCompiler.directOrFork(cpOptions, instance)(forkJavac(None))
|
|
|
|
|
|
|
|
|
|
def forkJavac(javaHome: Option[File]): JavaCompiler.Fork =
|
|
|
|
|
{
|
|
|
|
|
import Path._
|
|
|
|
|
def exec(jc: JavacContract) = javaHome match { case None => jc.name; case Some(jh) => (jh / "bin" / jc.name).absolutePath }
|
|
|
|
|
(contract: JavacContract, args: Seq[String], log: Logger) => {
|
|
|
|
|
log.debug("Forking " + contract.name + ": " + exec(contract) + " " + args.mkString(" "))
|
|
|
|
|
val javacLogger = new JavacLogger(log)
|
|
|
|
|
var exitCode = -1
|
|
|
|
|
try {
|
|
|
|
|
exitCode = Process(exec(contract), args) ! javacLogger
|
|
|
|
|
} finally {
|
|
|
|
|
javacLogger.flush(exitCode)
|
|
|
|
|
}
|
|
|
|
|
exitCode
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private[sbt] class JavacLogger(log: Logger) extends ProcessLogger {
|
|
|
|
|
import scala.collection.mutable.ListBuffer
|
|
|
|
|
import Level.{Info, Warn, Error, Value => LogLevel}
|
|
|
|
|
|
|
|
|
|
private val msgs: ListBuffer[(LogLevel, String)] = new ListBuffer()
|
|
|
|
|
|
|
|
|
|
def info(s: => String): Unit =
|
|
|
|
|
synchronized { msgs += ((Info, s)) }
|
|
|
|
|
|
|
|
|
|
def error(s: => String): Unit =
|
|
|
|
|
synchronized { msgs += ((Error, s)) }
|
|
|
|
|
|
|
|
|
|
def buffer[T](f: => T): T = f
|
|
|
|
|
|
|
|
|
|
private def print(desiredLevel: LogLevel)(t: (LogLevel, String)) = t match {
|
|
|
|
|
case (Info, msg) => log.info(msg)
|
|
|
|
|
case (Error, msg) => log.log(desiredLevel, msg)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def flush(exitCode: Int): Unit = {
|
|
|
|
|
val level = if (exitCode == 0) Warn else Error
|
|
|
|
|
msgs foreach print(level)
|
|
|
|
|
msgs.clear()
|
|
|
|
|
}
|
|
|
|
|
}
|