Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

basis for a resident compiler

unstable, but can be tested with -Dsbt.resident.limit=n
 n is the maximum Globals kept around
  • Loading branch information...
commit 6769c942083d37c7729adcd26f43d441ab89ecf1 1 parent 0566a61
@harrah authored
Showing with 423 additions and 130 deletions.
  1. +30 −14 compile/AnalyzingCompiler.scala
  2. +52 −0 compile/CompilerCache.scala
  3. +4 −3 compile/inc/Compile.scala
  4. +16 −5 compile/inc/Incremental.scala
  5. +13 −12 compile/integration/AggressiveCompile.scala
  6. +1 −1  compile/integration/IncrementalCompiler.scala
  7. +1 −1  compile/interface/API.scala
  8. +3 −2 compile/interface/Analyzer.scala
  9. +205 −79 compile/interface/CompilerInterface.scala
  10. +2 −1  compile/interface/DelegatingReporter.scala
  11. +11 −0 interface/src/main/java/xsbti/compile/CachedCompiler.java
  12. +10 −0 interface/src/main/java/xsbti/compile/CachedCompilerProvider.java
  13. +13 −0 interface/src/main/java/xsbti/compile/DependencyChanges.java
  14. +10 −0 interface/src/main/java/xsbti/compile/GlobalsCache.java
  15. +2 −0  interface/src/main/java/xsbti/compile/Setup.java
  16. +3 −2 main/Defaults.scala
  17. +3 −1 main/Keys.scala
  18. +18 −2 main/Main.scala
  19. +4 −4 main/actions/Compiler.scala
  20. +6 −3 sbt/package.scala
  21. +2 −0  sbt/src/sbt-test/source-dependencies/added/changes/A1.scala
  22. +2 −0  sbt/src/sbt-test/source-dependencies/added/changes/A2.scala
  23. +2 −0  sbt/src/sbt-test/source-dependencies/added/changes/A3.scala
  24. +2 −0  sbt/src/sbt-test/source-dependencies/added/changes/B1.scala
  25. +2 −0  sbt/src/sbt-test/source-dependencies/added/changes/B2.scala
  26. +2 −0  sbt/src/sbt-test/source-dependencies/dup-class/changes/A.scala
  27. +2 −0  sbt/src/sbt-test/source-dependencies/dup-class/changes/A2.scala
  28. +2 −0  sbt/src/sbt-test/source-dependencies/dup-class/changes/B.scala
View
44 compile/AnalyzingCompiler.scala
@@ -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(sources: Seq[File], changes: DependencyChanges, options: Seq[String], callback: AnalysisCallback, maximumErrors: Int, cache: GlobalsCache, log: Logger): Unit =
+ {
+ 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 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 newCachedCompiler(arguments: Seq[String], log: xLogger, reporter: Reporter): CachedCompiler =
{
- call("xsbt.CompilerInterface", log)(
- classOf[Array[String]], classOf[AnalysisCallback], classOf[xLogger], classOf[Reporter] ) (
- arguments.toArray[String] : Array[String], callback, log, reporter )
+ 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
52 compile/CompilerCache.scala
@@ -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
7 compile/inc/Compile.scala
@@ -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
21 compile/inc/Incremental.scala
@@ -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
25 compile/integration/AggressiveCompile.scala
@@ -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
2  compile/integration/IncrementalCompiler.scala
@@ -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
2  compile/interface/API.scala
@@ -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
5 compile/interface/Analyzer.scala
@@ -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
284 compile/interface/CompilerInterface.scala
@@ -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)
- {
- import scala.tools.nsc.{Global, Settings}
-
- debug(log, "Interfacing (CompilerInterface) with Scala compiler " + scala.tools.nsc.Properties.versionString)
+ 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
- val settings = new Settings(Log.settingsError(log))
- val command = Command(args.toList, settings)
- val dreporter = DelegatingReporter(settings, delegate)
- def noErrors = !dreporter.hasErrors && command.ok
+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
+ }
+}
- 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)
+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
+ {
+ println("Running cached compiler " + hashCode.toHexString)
+ debug(log, "Interfacing (CompilerInterface) with Scala compiler " + scala.tools.nsc.Properties.versionString)
+ val dreporter = DelegatingReporter(settings, delegate)
+ 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)
+ if(!noErrors(dreporter)) handleErrors(dreporter, log)
+ }
+ 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()
{
- debug(log, "Compilation failed (CompilerInterface)")
- throw new InterfaceCompileFailed(args, dreporter.problems, "Compilation failed")
+ 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
}
-}
-class InterfaceCompileFailed(val arguments: Array[String], val problems: Array[Problem], override val toString: String) extends xsbti.CompileFailed
+}
View
3  compile/interface/DelegatingReporter.scala
@@ -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
11 interface/src/main/java/xsbti/compile/CachedCompiler.java
@@ -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
10 interface/src/main/java/xsbti/compile/CachedCompilerProvider.java
@@ -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
13 interface/src/main/java/xsbti/compile/DependencyChanges.java
@@ -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
10 interface/src/main/java/xsbti/compile/GlobalsCache.java
@@ -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
2  interface/src/main/java/xsbti/compile/Setup.java
@@ -21,4 +21,6 @@
* 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
5 main/Defaults.scala
@@ -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
4 main/Keys.scala
@@ -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
20 main/Main.scala
@@ -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
8 main/actions/Compiler.scala
@@ -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
9 sbt/package.scala
@@ -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
2  sbt/src/sbt-test/source-dependencies/added/changes/A1.scala
@@ -1,3 +1,5 @@
+package example
+
object A
{
val x: Int = 3
View
2  sbt/src/sbt-test/source-dependencies/added/changes/A2.scala
@@ -1,3 +1,5 @@
+package example
+
object A
{
val x: Int = B.y
View
2  sbt/src/sbt-test/source-dependencies/added/changes/A3.scala
@@ -1,3 +1,5 @@
+package example
+
object A
{
val x: String = B.y
View
2  sbt/src/sbt-test/source-dependencies/added/changes/B1.scala
@@ -1,3 +1,5 @@
+package example
+
object B
{
val y: String = "4"
View
2  sbt/src/sbt-test/source-dependencies/added/changes/B2.scala
@@ -1,3 +1,5 @@
+package example
+
object B
{
val y: Int = 5
View
2  sbt/src/sbt-test/source-dependencies/dup-class/changes/A.scala
@@ -1 +1,3 @@
+package clear
+
object A
View
2  sbt/src/sbt-test/source-dependencies/dup-class/changes/A2.scala
@@ -1 +1,3 @@
+package clear
+
object A
View
2  sbt/src/sbt-test/source-dependencies/dup-class/changes/B.scala
@@ -1 +1,3 @@
+package clear
+
object B
Please sign in to comment.
Something went wrong with that request. Please try again.