basis for a resident compiler

unstable, but can be tested with -Dsbt.resident.limit=n
 n is the maximum Globals kept around
This commit is contained in:
Mark Harrah 2012-04-28 18:58:38 -04:00
parent 0566a61689
commit 6769c94208
28 changed files with 425 additions and 132 deletions

View File

@ -5,6 +5,7 @@ package sbt
package compiler
import xsbti.{AnalysisCallback, Logger => xLogger, Reporter}
import xsbti.compile.{CachedCompiler, CachedCompilerProvider, DependencyChanges, GlobalsCache}
import java.io.File
import java.net.{URL, URLClassLoader}
@ -12,22 +13,37 @@ package compiler
* provided by scalaInstance. This class requires a ComponentManager in order to obtain the interface code to scalac and
* the analysis plugin. Because these call Scala code for a different Scala version than the one used for this class, they must
* be compiled for the version of Scala being used.*/
class AnalyzingCompiler(val scalaInstance: xsbti.compile.ScalaInstance, val provider: CompilerInterfaceProvider, val cp: xsbti.compile.ClasspathOptions, log: Logger)
final class AnalyzingCompiler(val scalaInstance: xsbti.compile.ScalaInstance, val provider: CompilerInterfaceProvider, val cp: xsbti.compile.ClasspathOptions, log: Logger) extends CachedCompilerProvider
{
def this(scalaInstance: ScalaInstance, provider: CompilerInterfaceProvider, log: Logger) = this(scalaInstance, provider, ClasspathOptions.auto, log)
def apply(sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String], callback: AnalysisCallback, maximumErrors: Int, log: Logger)
def apply(sources: Seq[File], changes: DependencyChanges, classpath: Seq[File], outputDirectory: File, options: Seq[String], callback: AnalysisCallback, maximumErrors: Int, cache: GlobalsCache, log: Logger)
{
val arguments = (new CompilerArguments(scalaInstance, cp))(sources, classpath, outputDirectory, options)
compile(arguments, callback, maximumErrors, log)
val arguments = (new CompilerArguments(scalaInstance, cp))(Nil, classpath, outputDirectory, options)
compile(sources, changes, arguments, callback, maximumErrors, cache, log)
}
def compile(arguments: Seq[String], callback: AnalysisCallback, maximumErrors: Int, log: Logger): Unit =
compile(arguments, callback, log, new LoggerReporter(maximumErrors, log))
def compile(arguments: Seq[String], callback: AnalysisCallback, log: Logger, reporter: Reporter)
def compile(sources: Seq[File], changes: DependencyChanges, options: Seq[String], callback: AnalysisCallback, maximumErrors: Int, cache: GlobalsCache, log: Logger): Unit =
{
call("xsbt.CompilerInterface", log)(
classOf[Array[String]], classOf[AnalysisCallback], classOf[xLogger], classOf[Reporter] ) (
arguments.toArray[String] : Array[String], callback, log, reporter )
val reporter = new LoggerReporter(maximumErrors, log)
val cached = cache(options.toArray, !changes.isEmpty, this, log, reporter)
compile(sources, changes, callback, log, reporter, cached)
}
def compile(sources: Seq[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger, reporter: Reporter, compiler: CachedCompiler)
{
call("xsbt.CompilerInterface", "run", log)(
classOf[Array[File]], classOf[DependencyChanges], classOf[AnalysisCallback], classOf[xLogger], classOf[Reporter], classOf[CachedCompiler]) (
sources.toArray, changes, callback, log, reporter, compiler )
}
def newCachedCompiler(arguments: Array[String], log: xLogger, reporter: Reporter): CachedCompiler =
newCachedCompiler(arguments: Seq[String], log, reporter)
def newCachedCompiler(arguments: Seq[String], log: xLogger, reporter: Reporter): CachedCompiler =
{
call("xsbt.CompilerInterface", "newCompiler", log)(
classOf[Array[String]], classOf[xLogger], classOf[Reporter] ) (
arguments.toArray[String] : Array[String], log, reporter ).
asInstanceOf[CachedCompiler]
}
def doc(sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String], maximumErrors: Int, log: Logger): Unit =
@ -35,7 +51,7 @@ class AnalyzingCompiler(val scalaInstance: xsbti.compile.ScalaInstance, val prov
def doc(sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String], log: Logger, reporter: Reporter): Unit =
{
val arguments = (new CompilerArguments(scalaInstance, cp))(sources, classpath, outputDirectory, options)
call("xsbt.ScaladocInterface", log) (classOf[Array[String]], classOf[xLogger], classOf[Reporter]) (
call("xsbt.ScaladocInterface", "run", log) (classOf[Array[String]], classOf[xLogger], classOf[Reporter]) (
arguments.toArray[String] : Array[String], log, reporter)
}
def console(classpath: Seq[File], options: Seq[String], initialCommands: String, cleanupCommands: String, log: Logger)(loader: Option[ClassLoader] = None, bindings: Seq[(String, Any)] = Nil): Unit =
@ -44,16 +60,16 @@ class AnalyzingCompiler(val scalaInstance: xsbti.compile.ScalaInstance, val prov
val classpathString = CompilerArguments.absString(arguments.finishClasspath(classpath))
val bootClasspath = if(cp.autoBoot) arguments.createBootClasspath else ""
val (names, values) = bindings.unzip
call("xsbt.ConsoleInterface", log)(
call("xsbt.ConsoleInterface", "run", log)(
classOf[Array[String]], classOf[String], classOf[String], classOf[String], classOf[String], classOf[ClassLoader], classOf[Array[String]], classOf[Array[Any]], classOf[xLogger])(
options.toArray[String]: Array[String], bootClasspath, classpathString, initialCommands, cleanupCommands, loader.orNull, names.toArray[String], values.toArray[Any], log)
}
def force(log: Logger): Unit = provider(scalaInstance, log)
private def call(interfaceClassName: String, log: Logger)(argTypes: Class[_]*)(args: AnyRef*)
private def call(interfaceClassName: String, methodName: String, log: Logger)(argTypes: Class[_]*)(args: AnyRef*): AnyRef =
{
val interfaceClass = getInterfaceClass(interfaceClassName, log)
val interface = interfaceClass.newInstance.asInstanceOf[AnyRef]
val method = interfaceClass.getMethod("run", argTypes : _*)
val method = interfaceClass.getMethod(methodName, argTypes : _*)
try { method.invoke(interface, args: _*) }
catch { case e: java.lang.reflect.InvocationTargetException => throw e.getCause }
}

View File

@ -0,0 +1,52 @@
package sbt
package compiler
import xsbti.{Logger => xLogger, Reporter}
import xsbti.compile.{CachedCompiler, CachedCompilerProvider, GlobalsCache}
import Logger.f0
import java.io.File
import java.util.{LinkedHashMap,Map}
private final class CompilerCache(val maxInstances: Int) extends GlobalsCache
{
private[this] val cache = lru[CompilerKey, CachedCompiler](maxInstances)
private[this] def lru[A,B](max: Int) = new LinkedHashMap[A,B](8, 0.75f, true) {
override def removeEldestEntry(eldest: Map.Entry[A,B]): Boolean = size > max
}
def apply(args: Array[String], forceNew: Boolean, c: CachedCompilerProvider, log: xLogger, reporter: Reporter): CachedCompiler = synchronized
{
val key = CompilerKey(dropSources(args.toList), c.scalaInstance.actualVersion)
if(forceNew) cache.remove(key)
cache.get(key) match {
case null =>
log.debug(f0("Compiler cache miss. " + key.toString))
put(key, c.newCachedCompiler(args, log, reporter))
case cc =>
log.debug(f0("Compiler cache hit (" + cc.hashCode.toHexString + "). " + key.toString))
cc
}
}
def clear(): Unit = synchronized { cache.clear() }
private[this] def dropSources(args: Seq[String]): Seq[String] =
args.filterNot(arg => arg.endsWith(".scala") || arg.endsWith(".java"))
private[this] def put(key: CompilerKey, cc: CachedCompiler): CachedCompiler =
{
cache.put(key, cc)
cc
}
private[this] final case class CompilerKey(args: Seq[String], scalaVersion: String) {
override def toString = "scala " + scalaVersion + ", args: " + args.mkString(" ")
}
}
object CompilerCache
{
def apply(maxInstances: Int): GlobalsCache = new CompilerCache(maxInstances)
val fresh: GlobalsCache = new GlobalsCache {
def clear() {}
def apply(args: Array[String], forceNew: Boolean, c: CachedCompilerProvider, log: xLogger, reporter: Reporter): CachedCompiler =
c.newCachedCompiler(args, log, reporter)
}
}

View File

@ -5,22 +5,23 @@ package sbt
package inc
import xsbti.api.{Source, SourceAPI}
import xsbti.compile.DependencyChanges
import xsbti.{Position,Problem,Severity}
import Logger.{m2o, problem}
import java.io.File
object IncrementalCompile
{
def apply(sources: Set[File], entry: String => Option[File], compile: (Set[File], xsbti.AnalysisCallback) => Unit, previous: Analysis, forEntry: File => Option[Analysis], outputPath: File, log: Logger): (Boolean, Analysis) =
def apply(sources: Set[File], entry: String => Option[File], compile: (Set[File], DependencyChanges, xsbti.AnalysisCallback) => Unit, previous: Analysis, forEntry: File => Option[Analysis], outputPath: File, log: Logger): (Boolean, Analysis) =
{
val current = Stamps.initial(Stamp.exists, Stamp.hash, Stamp.lastModified)
val internalMap = (f: File) => previous.relations.produced(f).headOption
val externalAPI = getExternalAPI(entry, forEntry)
Incremental.compile(sources, entry, previous, current, forEntry, doCompile(compile, internalMap, externalAPI, current, outputPath), log)
}
def doCompile(compile: (Set[File], xsbti.AnalysisCallback) => Unit, internalMap: File => Option[File], externalAPI: (File, String) => Option[Source], current: ReadStamps, outputPath: File) = (srcs: Set[File]) => {
def doCompile(compile: (Set[File], DependencyChanges, xsbti.AnalysisCallback) => Unit, internalMap: File => Option[File], externalAPI: (File, String) => Option[Source], current: ReadStamps, outputPath: File) = (srcs: Set[File], changes: DependencyChanges) => {
val callback = new AnalysisCallback(internalMap, externalAPI, current, outputPath)
compile(srcs, callback)
compile(srcs, changes, callback)
callback.get
}
def getExternalAPI(entry: String => Option[File], forEntry: File => Option[Analysis]): (File, String) => Option[Source] =

View File

@ -7,30 +7,36 @@ package inc
import xsbt.api.{NameChanges, SameAPI, TopLevel}
import annotation.tailrec
import xsbti.api.{Compilation, Source}
import xsbti.compile.DependencyChanges
import java.io.File
object Incremental
{
def debug(s: => String) = if(java.lang.Boolean.getBoolean("xsbt.inc.debug")) println(s) else ()
def compile(sources: Set[File], entry: String => Option[File], previous: Analysis, current: ReadStamps, forEntry: File => Option[Analysis], doCompile: Set[File] => Analysis, log: Logger)(implicit equivS: Equiv[Stamp]): (Boolean, Analysis) =
def compile(sources: Set[File], entry: String => Option[File], previous: Analysis, current: ReadStamps, forEntry: File => Option[Analysis], doCompile: (Set[File], DependencyChanges) => Analysis, log: Logger)(implicit equivS: Equiv[Stamp]): (Boolean, Analysis) =
{
val initialChanges = changedInitial(entry, sources, previous, current, forEntry)
val binaryChanges = new DependencyChanges {
val modifiedBinaries = initialChanges.binaryDeps.toArray
val modifiedClasses = initialChanges.external.modified.toArray
def isEmpty = modifiedBinaries.isEmpty && modifiedClasses.isEmpty
}
val initialInv = invalidateInitial(previous.relations, initialChanges, log)
log.debug("Initially invalidated: " + initialInv)
val analysis = cycle(initialInv, previous, doCompile, log)
val analysis = cycle(initialInv, binaryChanges, previous, doCompile, log)
(!initialInv.isEmpty, analysis)
}
// 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, log: Logger): Analysis =
def cycle(invalidated: Set[File], binaryChanges: DependencyChanges, previous: Analysis, doCompile: (Set[File], DependencyChanges) => Analysis, log: Logger): Analysis =
if(invalidated.isEmpty)
previous
else
{
val pruned = prune(invalidated, previous)
debug("********* Pruned: \n" + pruned.relations + "\n*********")
val fresh = doCompile(invalidated)
val fresh = doCompile(invalidated, binaryChanges)
debug("********* Fresh: \n" + fresh.relations + "\n*********")
val merged = pruned ++ fresh//.copy(relations = pruned.relations ++ fresh.relations, apis = pruned.apis ++ fresh.apis)
debug("********* Merged: \n" + merged.relations + "\n*********")
@ -38,8 +44,13 @@ object Incremental
debug("Changes:\n" + incChanges)
val incInv = invalidateIncremental(merged.relations, incChanges, invalidated, log)
log.debug("Incrementally invalidated: " + incInv)
cycle(incInv, merged, doCompile, log)
cycle(incInv, emptyChanges, merged, doCompile, log)
}
private[this] def emptyChanges: DependencyChanges = new DependencyChanges {
val modifiedBinaries = new Array[File](0)
val modifiedClasses = new Array[String](0)
def isEmpty = true
}
/**

View File

@ -9,37 +9,38 @@ import inc._
import java.io.File
import classpath.ClasspathUtilities
import classfile.Analyze
import xsbti.api.Source
import xsbti.compile.CompileOrder
import CompileOrder.{JavaThenScala, Mixed, ScalaThenJava}
import xsbti.AnalysisCallback
import inc.Locate.DefinesClass
import CompileSetup._
import sbinary.DefaultProtocol.{ immutableMapFormat, immutableSetFormat, StringFormat }
import Types.const
import xsbti.AnalysisCallback
import xsbti.api.Source
import xsbti.compile.{CompileOrder, DependencyChanges, GlobalsCache}
import CompileOrder.{JavaThenScala, Mixed, ScalaThenJava}
final class CompileConfiguration(val sources: Seq[File], val classpath: Seq[File],
val previousAnalysis: Analysis, val previousSetup: Option[CompileSetup], val currentSetup: CompileSetup, val getAnalysis: File => Option[Analysis], val definesClass: DefinesClass,
val maxErrors: Int, val compiler: AnalyzingCompiler, val javac: xsbti.compile.JavaCompiler)
val maxErrors: Int, val compiler: AnalyzingCompiler, val javac: xsbti.compile.JavaCompiler, val cache: GlobalsCache)
class AggressiveCompile(cacheFile: File)
{
def apply(compiler: AnalyzingCompiler, javac: xsbti.compile.JavaCompiler, sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String] = Nil, javacOptions: Seq[String] = Nil, analysisMap: File => Option[Analysis] = const(None), definesClass: DefinesClass = Locate.definesClass _, maxErrors: Int = 100, compileOrder: CompileOrder = Mixed, skip: Boolean = false)(implicit log: Logger): Analysis =
def apply(compiler: AnalyzingCompiler, javac: xsbti.compile.JavaCompiler, sources: Seq[File], classpath: Seq[File], outputDirectory: File, cache: GlobalsCache, options: Seq[String] = Nil, javacOptions: Seq[String] = Nil, analysisMap: File => Option[Analysis] = const(None), definesClass: DefinesClass = Locate.definesClass _, maxErrors: Int = 100, compileOrder: CompileOrder = Mixed, skip: Boolean = false)(implicit log: Logger): Analysis =
{
val setup = new CompileSetup(outputDirectory, new CompileOptions(options, javacOptions), compiler.scalaInstance.actualVersion, compileOrder)
compile1(sources, classpath, setup, store, analysisMap, definesClass, compiler, javac, maxErrors, skip)
compile1(sources, classpath, setup, store, analysisMap, definesClass, compiler, javac, maxErrors, skip, cache)
}
def withBootclasspath(args: CompilerArguments, classpath: Seq[File]): Seq[File] =
args.bootClasspath ++ args.finishClasspath(classpath)
def compile1(sources: Seq[File], classpath: Seq[File], setup: CompileSetup, store: AnalysisStore, analysis: File => Option[Analysis], definesClass: DefinesClass, compiler: AnalyzingCompiler, javac: xsbti.compile.JavaCompiler, maxErrors: Int, skip: Boolean)(implicit log: Logger): Analysis =
def compile1(sources: Seq[File], classpath: Seq[File], setup: CompileSetup, store: AnalysisStore, analysis: File => Option[Analysis], definesClass: DefinesClass, compiler: AnalyzingCompiler, javac: xsbti.compile.JavaCompiler, maxErrors: Int, skip: Boolean, cache: GlobalsCache)(implicit log: Logger): Analysis =
{
val (previousAnalysis, previousSetup) = extract(store.get())
if(skip)
previousAnalysis
else {
val config = new CompileConfiguration(sources, classpath, previousAnalysis, previousSetup, setup, analysis, definesClass, maxErrors, compiler, javac)
val config = new CompileConfiguration(sources, classpath, previousAnalysis, previousSetup, setup, analysis, definesClass, maxErrors, compiler, javac, cache)
val (modified, result) = compile2(config)
if(modified)
store.set(result, setup)
@ -56,7 +57,7 @@ class AggressiveCompile(cacheFile: File)
val searchClasspath = explicitBootClasspath(options.options) ++ withBootclasspath(cArgs, absClasspath)
val entry = Locate.entry(searchClasspath, definesClass)
val compile0 = (include: Set[File], callback: AnalysisCallback) => {
val compile0 = (include: Set[File], changes: DependencyChanges, callback: AnalysisCallback) => {
IO.createDirectory(outputDirectory)
val incSrc = sources.filter(include)
val (javaSrcs, scalaSrcs) = incSrc partition javaOnly
@ -65,8 +66,8 @@ class AggressiveCompile(cacheFile: File)
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)
val arguments = cArgs(Nil, absClasspath, outputDirectory, options.options)
compiler.compile(sources, changes, arguments, callback, maxErrors, cache, log)
}
def compileJava() =
if(!javaSrcs.isEmpty)

View File

@ -16,7 +16,7 @@ object IC extends IncrementalCompiler[Analysis, AnalyzingCompiler]
val agg = new AggressiveCompile(setup.cacheFile)
val aMap = (f: File) => m2o(analysisMap(f))
val defClass = (f: File) => { val dc = definesClass(f); (name: String) => dc.apply(name) }
agg(scalac, javac, sources, classpath, classesDirectory, scalacOptions, javacOptions, aMap, defClass, maxErrors, order, skip)(log)
agg(scalac, javac, sources, classpath, classesDirectory, cache, scalacOptions, javacOptions, aMap, defClass, maxErrors, order, skip)(log)
}
private[this] def m2o[S](opt: Maybe[S]): Option[S] = if(opt.isEmpty) None else Some(opt.get)

View File

@ -16,7 +16,7 @@ object API
val name = "xsbt-api"
}
final class API(val global: Global, val callback: xsbti.AnalysisCallback) extends Compat
final class API(val global: CallbackGlobal) extends Compat
{
import global._
def error(msg: String) = throw new RuntimeException(msg)

View File

@ -17,7 +17,7 @@ object Analyzer
{
def name = "xsbt-analyzer"
}
final class Analyzer(val global: Global, val callback: AnalysisCallback) extends Compat
final class Analyzer(val global: CallbackGlobal) extends Compat
{
import global._
@ -35,10 +35,12 @@ final class Analyzer(val global: Global, val callback: AnalysisCallback) extends
// build dependencies structure
val sourceFile = unit.source.file.file
callback.beginSource(sourceFile)
println("Dependencies of " + sourceFile)
for(on <- unit.depends)
{
def binaryDependency(file: File, className: String) = callback.binaryDependency(file, className, sourceFile)
val onSource = on.sourceFile
println("\t" + on + ", src: " + onSource + ", class: " + classFile(on))
if(onSource == null)
{
classFile(on) match
@ -82,7 +84,6 @@ final class Analyzer(val global: Global, val callback: AnalysisCallback) extends
}
private[this] final val classSeparator = '.'
private[this] def findClass(name: String): Option[AbstractFile] = classPath.findClass(name).flatMap(_.binary.asInstanceOf[Option[AbstractFile]])
private[this] def classFile(sym: Symbol): Option[(AbstractFile, String)] =
{
import scala.tools.nsc.symtab.Flags

View File

@ -4,101 +4,227 @@
package xsbt
import xsbti.{AnalysisCallback,Logger,Problem,Reporter,Severity}
import scala.tools.nsc.{Phase, SubComponent}
import xsbti.compile.{CachedCompiler, DependencyChanges}
import scala.tools.nsc.{io, reporters, util, Phase, Global, Settings, SubComponent}
import util.{ClassPath,DirectoryClassPath,MergedClassPath,JavaClassPath}
import ClassPath.{ClassPathContext,JavaContext}
import io.AbstractFile
import scala.annotation.tailrec
import scala.collection.mutable
import Log.debug
import java.io.File
class CompilerInterface
final class CompilerInterface
{
def run(args: Array[String], callback: AnalysisCallback, log: Logger, delegate: Reporter)
def newCompiler(options: Array[String], initialLog: Logger, initialDelegate: Reporter): CachedCompiler =
new CachedCompiler0(options, new WeakLog(initialLog, initialDelegate))
def run(sources: Array[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger, delegate: Reporter, cached: CachedCompiler): Unit =
cached.run(sources, changes, callback, log, delegate)
}
sealed abstract class CallbackGlobal(settings: Settings, reporter: reporters.Reporter) extends Global(settings, reporter) {
def callback: AnalysisCallback
def findClass(name: String): Option[AbstractFile]
}
class InterfaceCompileFailed(val arguments: Array[String], val problems: Array[Problem], override val toString: String) extends xsbti.CompileFailed
private final class WeakLog(private[this] var log: Logger, private[this] var delegate: Reporter)
{
def apply(message: String) {
assert(log ne null, "Stale reference to logger")
log.error(Message(message))
}
def logger: Logger = log
def reporter: Reporter = delegate
def clear() {
log = null
delegate = null
}
}
private final class CachedCompiler0(args: Array[String], initialLog: WeakLog) extends CachedCompiler
{
val settings = new Settings(s => initialLog(s))
val command = Command(args.toList, settings)
private[this] val dreporter = DelegatingReporter(settings, initialLog.reporter)
try {
compiler // force compiler internal structures
if(!noErrors(dreporter)) {
dreporter.printSummary()
handleErrors(dreporter, initialLog.logger)
}
} finally
initialLog.clear()
def noErrors(dreporter: DelegatingReporter) = !dreporter.hasErrors && command.ok
def run(sources: Array[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger, delegate: Reporter): Unit = synchronized
{
import scala.tools.nsc.{Global, Settings}
println("Running cached compiler " + hashCode.toHexString)
debug(log, "Interfacing (CompilerInterface) with Scala compiler " + scala.tools.nsc.Properties.versionString)
val settings = new Settings(Log.settingsError(log))
val command = Command(args.toList, settings)
val dreporter = DelegatingReporter(settings, delegate)
def noErrors = !dreporter.hasErrors && command.ok
object compiler extends Global(command.settings, dreporter)
{
object dummy // temporary fix for #4426
object sbtAnalyzer extends
{
val global: compiler.type = compiler
val phaseName = Analyzer.name
val runsAfter = List("jvm")
override val runsBefore = List("terminal")
val runsRightAfter = None
}
with SubComponent
{
val analyzer = new Analyzer(global, callback)
def newPhase(prev: Phase) = analyzer.newPhase(prev)
def name = phaseName
}
object apiExtractor extends
{
val global: compiler.type = compiler
val phaseName = API.name
val runsAfter = List("typer")
override val runsBefore = List("erasure")
val runsRightAfter = Some("typer")
}
with SubComponent
{
val api = new API(global, callback)
def newPhase(prev: Phase) = api.newPhase(prev)
def name = phaseName
}
override lazy val phaseDescriptors =
{
phasesSet += sbtAnalyzer
phasesSet += apiExtractor
superComputePhaseDescriptors
}
// Required because computePhaseDescriptors is private in 2.8 (changed to protected sometime later).
private def superComputePhaseDescriptors() =
{
val meth = classOf[Global].getDeclaredMethod("computePhaseDescriptors")
meth.setAccessible(true)
meth.invoke(this).asInstanceOf[List[SubComponent]]
}
def logUnreportedWarnings(seq: List[(Position,String)]): Unit = // Scala 2.10.x and later
{
for( (pos, msg) <- seq) yield
callback.problem(dreporter.convert(pos), msg, Severity.Warn, false)
}
def logUnreportedWarnings(count: Boolean): Unit = () // for source compatibility with Scala 2.8.x
def logUnreportedWarnings(count: Int): Unit = () // for source compatibility with Scala 2.9.x
}
def processUnreportedWarnings(run: compiler.Run)
{
implicit def listToBoolean[T](l: List[T]): Boolean = error("source compatibility only, should never be called")
implicit def listToInt[T](l: List[T]): Int = error("source compatibility only, should never be called")
compiler.logUnreportedWarnings(run.deprecationWarnings)
compiler.logUnreportedWarnings(run.uncheckedWarnings)
}
try { run(sources.toList, changes, callback, log, dreporter) }
finally { dreporter.dropDelegate() }
}
private[this] def run(sources: List[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger, dreporter: DelegatingReporter)
{
if(command.shouldStopWithInfo)
{
dreporter.info(null, command.getInfoMessage(compiler), true)
throw new InterfaceCompileFailed(args, Array(), "Compiler option supplied that disabled actual compilation.")
}
if(noErrors)
if(noErrors(dreporter))
{
val run = new compiler.Run
debug(log, args.mkString("Calling Scala compiler with arguments (CompilerInterface):\n\t", "\n\t", ""))
run compile command.files
processUnreportedWarnings(run)
compiler.set(callback, dreporter)
try {
val run = new compiler.Run
compiler.reload(changes)
val sortedSourceFiles = sources.map(_.getAbsolutePath).sortWith(_ < _)
run compile sortedSourceFiles
processUnreportedWarnings(run)
} finally {
compiler.clear()
}
dreporter.problems foreach { p => callback.problem(p.position, p.message, p.severity, true) }
}
dreporter.printSummary()
if(!noErrors)
{
debug(log, "Compilation failed (CompilerInterface)")
throw new InterfaceCompileFailed(args, dreporter.problems, "Compilation failed")
}
if(!noErrors(dreporter)) handleErrors(dreporter, log)
}
}
class InterfaceCompileFailed(val arguments: Array[String], val problems: Array[Problem], override val toString: String) extends xsbti.CompileFailed
def handleErrors(dreporter: DelegatingReporter, log: Logger): Nothing =
{
debug(log, "Compilation failed (CompilerInterface)")
throw new InterfaceCompileFailed(args, dreporter.problems, "Compilation failed")
}
def processUnreportedWarnings(run: compiler.Run)
{
implicit def listToBoolean[T](l: List[T]): Boolean = error("source compatibility only, should never be called")
implicit def listToInt[T](l: List[T]): Int = error("source compatibility only, should never be called")
compiler.logUnreportedWarnings(run.deprecationWarnings)
compiler.logUnreportedWarnings(run.uncheckedWarnings)
}
object compiler extends CallbackGlobal(command.settings, dreporter)
{
object dummy // temporary fix for #4426
object sbtAnalyzer extends
{
val global: compiler.type = compiler
val phaseName = Analyzer.name
val runsAfter = List("jvm")
override val runsBefore = List("terminal")
val runsRightAfter = None
}
with SubComponent
{
val analyzer = new Analyzer(global)
def newPhase(prev: Phase) = analyzer.newPhase(prev)
def name = phaseName
}
object apiExtractor extends
{
val global: compiler.type = compiler
val phaseName = API.name
val runsAfter = List("typer")
override val runsBefore = List("erasure")
val runsRightAfter = Some("typer")
}
with SubComponent
{
val api = new API(global)
def newPhase(prev: Phase) = api.newPhase(prev)
def name = phaseName
}
val out = new File(settings.outdir.value)
override lazy val phaseDescriptors =
{
phasesSet += sbtAnalyzer
phasesSet += apiExtractor
superComputePhaseDescriptors
}
// Required because computePhaseDescriptors is private in 2.8 (changed to protected sometime later).
private[this] def superComputePhaseDescriptors() = superCall("computePhaseDescriptors").asInstanceOf[List[SubComponent]]
private[this] def superDropRun(): Unit = superCall("dropRun")
private[this] def superCall(methodName: String): AnyRef =
{
val meth = classOf[Global].getDeclaredMethod(methodName)
meth.setAccessible(true)
meth.invoke(this)
}
def logUnreportedWarnings(seq: List[(Position,String)]): Unit = // Scala 2.10.x and later
{
for( (pos, msg) <- seq) yield
callback.problem(reporter.asInstanceOf[DelegatingReporter].convert(pos), msg, Severity.Warn, false)
}
def logUnreportedWarnings(count: Boolean): Unit = () // for source compatibility with Scala 2.8.x
def logUnreportedWarnings(count: Int): Unit = () // for source compatibility with Scala 2.9.x
def set(callback: AnalysisCallback, dreporter: DelegatingReporter)
{
this.callback0 = callback
reporter = dreporter
}
def clear()
{
callback0 = null
atPhase(currentRun.namerPhase) { forgetAll() }
superDropRun()
reporter = null
}
override def registerTopLevelSym(sym: Symbol) = toForget += sym
def findClass(name: String): Option[AbstractFile] =
getOutputClass(name) orElse findOnClassPath(name)
def getOutputClass(name: String): Option[AbstractFile] =
{
val f = new File(out, name.replace('.', '/') + ".class")
if(f.exists) Some(AbstractFile.getFile(f)) else None
}
def findOnClassPath(name: String): Option[AbstractFile] = classPath.findClass(name).flatMap(_.binary.asInstanceOf[Option[AbstractFile]])
final def unlinkAll(m: Symbol) {
val scope = m.owner.info.decls
scope unlink m
scope unlink m.companionSymbol
// if(scope.isEmpty && m.owner != definitions.EmptyPackageClass && m.owner != definitions.RootClass)
// emptyPackages += m.owner
}
def reloadClass(pkg: Symbol, simpleName: String, bin: AbstractFile)
{
val loader = new loaders.ClassfileLoader(bin)
toForget += loaders.enterClass(pkg, simpleName, loader)
toForget += loaders.enterModule(pkg, simpleName, loader)
}
def forgetAll()
{
for(sym <- toForget) {
unlinkAll(sym)
toReload.put(sym.fullName, (sym.owner, sym.name.toString))
}
toForget = mutable.Set()
}
// fine-control over external changes is unimplemented:
// must drop whole CachedCompiler when !changes.isEmpty
def reload(changes: DependencyChanges)
{
for {
(fullName,(pkg,simpleName)) <- toReload
classFile <- getOutputClass(fullName)
}
reloadClass(pkg, simpleName, classFile)
toReload = newReloadMap()
}
private [this] def newReloadMap() = mutable.Map[String,(Symbol,String)]()
private[this] var emptyPackages = mutable.Set[Symbol]()
private[this] var toReload = newReloadMap()
private[this] var toForget = mutable.Set[Symbol]()
private[this] var callback0: AnalysisCallback = null
def callback: AnalysisCallback = callback0
}
}

View File

@ -15,10 +15,11 @@ private object DelegatingReporter
// The following code is based on scala.tools.nsc.reporters.{AbstractReporter, ConsoleReporter}
// Copyright 2002-2009 LAMP/EPFL
// Original author: Martin Odersky
private final class DelegatingReporter(warnFatal: Boolean, delegate: xsbti.Reporter) extends scala.tools.nsc.reporters.Reporter
private final class DelegatingReporter(warnFatal: Boolean, private[this] var delegate: xsbti.Reporter) extends scala.tools.nsc.reporters.Reporter
{
import scala.tools.nsc.util.{FakePos,NoPosition,Position}
def dropDelegate() { delegate = null }
def error(msg: String) { error(FakePos("scalac"), msg) }
def printSummary() = delegate.printSummary()

View File

@ -0,0 +1,11 @@
package xsbti.compile;
import xsbti.AnalysisCallback;
import xsbti.Logger;
import xsbti.Reporter;
import java.io.File;
public interface CachedCompiler
{
public void run(File[] sources, DependencyChanges cpChanges, AnalysisCallback callback, Logger log, Reporter delegate);
}

View File

@ -0,0 +1,10 @@
package xsbti.compile;
import xsbti.Logger;
import xsbti.Reporter;
public interface CachedCompilerProvider
{
ScalaInstance scalaInstance();
CachedCompiler newCachedCompiler(String[] arguments, Logger log, Reporter reporter);
}

View File

@ -0,0 +1,13 @@
package xsbti.compile;
import java.io.File;
// only includes changes to dependencies outside of the project
public interface DependencyChanges
{
boolean isEmpty();
// class files or jar files
File[] modifiedBinaries();
// class names
String[] modifiedClasses();
}

View File

@ -0,0 +1,10 @@
package xsbti.compile;
import xsbti.Logger;
import xsbti.Reporter;
public interface GlobalsCache
{
public CachedCompiler apply(String[] args, boolean forceNew, CachedCompilerProvider provider, Logger log, Reporter reporter);
public void clear();
}

View File

@ -21,4 +21,6 @@ public interface Setup<Analysis>
* This file can be removed to force a full recompilation.
* The file should be unique and not shared between compilations. */
File cacheFile();
GlobalsCache cache();
}

View File

@ -55,6 +55,7 @@ object Defaults extends BuildCommon
managedDirectory <<= baseDirectory(_ / "lib_managed")
))
def globalCore: Seq[Setting[_]] = inScope(GlobalScope)(defaultTestTasks(test) ++ defaultTestTasks(testOnly) ++ defaultTestTasks(testQuick) ++ Seq(
compilerCache <<= state map { _ get Keys.stateCompilerCache getOrElse compiler.CompilerCache.fresh },
crossVersion :== CrossVersion.Disabled,
scalaOrganization :== ScalaArtifacts.Organization,
buildDependencies <<= buildDependencies or Classpaths.constructBuildDependencies,
@ -574,8 +575,8 @@ object Defaults extends BuildCommon
def compileTask = (compileInputs in compile, streams) map { (i,s) => Compiler(i,s.log) }
def compileIncSetupTask =
(dependencyClasspath, cacheDirectory, skip in compile, definesClass) map { (cp, cacheDir, skip, definesC) =>
Compiler.IncSetup(analysisMap(cp), definesC, skip, cacheDir / "inc_compile")
(dependencyClasspath, cacheDirectory, skip in compile, definesClass, compilerCache) map { (cp, cacheDir, skip, definesC, cache) =>
Compiler.IncSetup(analysisMap(cp), definesC, skip, cacheDir / "inc_compile", cache)
}
def compileInputsSettings: Seq[Setting[_]] = {
val optionsPair = TaskKey.local[(Seq[String], Seq[String])]

View File

@ -10,7 +10,7 @@ package sbt
import inc.Analysis
import inc.Locate.DefinesClass
import std.TaskExtra._
import xsbti.compile.CompileOrder
import xsbti.compile.{CompileOrder, GlobalsCache}
import scala.xml.{Node => XNode, NodeSeq}
import org.apache.ivy.core.module.{descriptor, id}
import descriptor.ModuleDescriptor, id.ModuleRevisionId
@ -153,6 +153,8 @@ object Keys
val compile = TaskKey[Analysis]("compile", "Compiles sources.", APlusTask)
val compilers = TaskKey[Compiler.Compilers]("compilers", "Defines the Scala and Java compilers to use for compilation.", DTask)
val compileIncSetup = TaskKey[Compiler.IncSetup]("inc-compile-setup", "Configures aspects of incremental compilation.", DTask)
val compilerCache = TaskKey[GlobalsCache]("compiler-cache", "Cache of scala.tools.nsc.Global instances. This should typically be cached so that it isn't recreated every task run.", DTask)
val stateCompilerCache = AttributeKey[GlobalsCache]("compiler-cache", "Internal use: Global cache.")
val definesClass = TaskKey[DefinesClass]("defines-class", "Internal use: provides a function that determines whether the provided file contains a given class.", Invisible)
val doc = TaskKey[File]("doc", "Generates API documentation.", AMinusTask)
val copyResources = TaskKey[Seq[(File,File)]]("copy-resources", "Copies resources to the output directory.", AMinusTask)

View File

@ -4,7 +4,7 @@
package sbt
import complete.{DefaultParsers, Parser}
import compiler.EvalImports
import compiler.{CompilerCache,EvalImports}
import Types.{const,idFun}
import Aggregation.AnyKeys
import Project.LoadAction
@ -394,11 +394,27 @@ object BuiltinCommands
def loadProjectImpl = Command(LoadProjectImpl)(_ => Project.loadActionParser)( doLoadProject )
def doLoadProject(s0: State, action: LoadAction.Value): State =
{
val (s, base) = Project.loadAction(SessionVar.clear(s0), action)
val (s1, base) = Project.loadAction(SessionVar.clear(s0), action)
IO.createDirectory(base)
val s = if(s1 has Keys.stateCompilerCache) s1 else registerCompilerCache(s1)
val (eval, structure) = Load.defaultLoad(s, base, s.log, Project.inPluginProject(s), Project.extraBuilds(s))
val session = Load.initialSession(structure, eval, s0)
SessionSettings.checkSession(session, s)
Project.setProject(session, structure, s)
}
def registerCompilerCache(s: State): State =
{
val maxCompilers = System.getProperty("sbt.resident.limit")
val cache =
if(maxCompilers == null)
CompilerCache.fresh
else
{
val num = try maxCompilers.toInt catch {
case e: NumberFormatException => throw new RuntimeException("Resident compiler limit must be an integer.", e)
}
if(num <= 0) CompilerCache.fresh else CompilerCache(num)
}
s.put(Keys.stateCompilerCache, cache)
}
}

View File

@ -4,7 +4,7 @@
package sbt
import xsbti.{Logger => _,_}
import xsbti.compile.CompileOrder
import xsbti.compile.{CompileOrder,GlobalsCache}
import CompileOrder.{JavaThenScala, Mixed, ScalaThenJava}
import compiler._
import inc._
@ -17,7 +17,7 @@ object Compiler
final case class Inputs(compilers: Compilers, config: Options, incSetup: IncSetup)
final case class Options(classpath: Seq[File], sources: Seq[File], classesDirectory: File, options: Seq[String], javacOptions: Seq[String], maxErrors: Int, order: CompileOrder)
final case class IncSetup(analysisMap: File => Option[Analysis], definesClass: DefinesClass, skip: Boolean, cacheFile: File)
final case class IncSetup(analysisMap: File => Option[Analysis], definesClass: DefinesClass, skip: Boolean, cacheFile: File, cache: GlobalsCache)
final case class Compilers(scalac: AnalyzingCompiler, javac: JavaTool)
@deprecated("Use the other inputs variant.", "0.12.0")
@ -27,7 +27,7 @@ object Compiler
val classesDirectory = outputDirectory / "classes"
val cacheFile = outputDirectory / "cache_old_style"
val augClasspath = classesDirectory.asFile +: classpath
val incSetup = IncSetup(Map.empty, definesClass, false, cacheFile)
val incSetup = IncSetup(Map.empty, definesClass, false, cacheFile, CompilerCache.fresh)
inputs(augClasspath, sources, classesDirectory, options, javacOptions, maxErrors, order)(compilers, incSetup, log)
}
def inputs(classpath: Seq[File], sources: Seq[File], classesDirectory: File, options: Seq[String], javacOptions: Seq[String], maxErrors: Int, order: CompileOrder)(implicit compilers: Compilers, incSetup: IncSetup, log: Logger): Inputs =
@ -76,6 +76,6 @@ object Compiler
import in.incSetup._
val agg = new AggressiveCompile(cacheFile)
agg(scalac, javac, sources, classpath, classesDirectory, options, javacOptions, analysisMap, definesClass, maxErrors, order, skip)(log)
agg(scalac, javac, sources, classpath, classesDirectory, cache, options, javacOptions, analysisMap, definesClass, maxErrors, order, skip)(log)
}
}

View File

@ -20,10 +20,13 @@ package object sbt extends sbt.std.TaskExtra with sbt.Types with sbt.ProcessExtr
type File = java.io.File
type URI = java.net.URI
type URL = java.net.URL
object CompileOrder {
val JavaThenScala = xsbti.compile.CompileOrder.JavaThenScala
val ScalaThenJava = xsbti.compile.CompileOrder.ScalaThenJava
val Mixed = xsbti.compile.CompileOrder.Mixed
}
type CompileOrder = xsbti.compile.CompileOrder
val JavaThenScala = xsbti.compile.CompileOrder.JavaThenScala
val ScalaThenJava = xsbti.compile.CompileOrder.ScalaThenJava
val Mixed = xsbti.compile.CompileOrder.Mixed
implicit def maybeToOption[S](m: xsbti.Maybe[S]): Option[S] =
if(m.isDefined) Some(m.get) else None

View File

@ -1,3 +1,5 @@
package example
object A
{
val x: Int = 3

View File

@ -1,3 +1,5 @@
package example
object A
{
val x: Int = B.y

View File

@ -1,3 +1,5 @@
package example
object A
{
val x: String = B.y

View File

@ -1,3 +1,5 @@
package example
object B
{
val y: String = "4"

View File

@ -1,3 +1,5 @@
package example
object B
{
val y: Int = 5

View File

@ -1 +1,3 @@
package clear
object A

View File

@ -1 +1,3 @@
package clear
object A

View File

@ -1 +1,3 @@
package clear
object B