Merge pull request #1714 from sbt/wip/bytecode-enhancement

Expose mechanism whereby bytecode enhancement can be run *before* saving incremental compiler hashes.
This commit is contained in:
eugene yokota 2014-12-03 08:46:52 -05:00
commit 7df9802f0c
20 changed files with 673 additions and 69 deletions

View File

@ -10,24 +10,41 @@ import java.io.File
// We cannot require an implicit parameter Equiv[Seq[String]] to construct Equiv[CompileSetup] // We cannot require an implicit parameter Equiv[Seq[String]] to construct Equiv[CompileSetup]
// because complexity(Equiv[Seq[String]]) > complexity(Equiv[CompileSetup]) // because complexity(Equiv[Seq[String]]) > complexity(Equiv[CompileSetup])
// (6 > 4) // (6 > 4)
final class CompileOptions(val options: Seq[String], val javacOptions: Seq[String]) final class CompileOptions(val options: Seq[String], val javacOptions: Seq[String]) {
override def toString = s"CompileOptions(scalac=$options, javac=$javacOptions)"
}
final class CompileSetup(val output: APIOutput, val options: CompileOptions, val compilerVersion: String, final class CompileSetup(val output: APIOutput, val options: CompileOptions, val compilerVersion: String,
val order: CompileOrder, val nameHashing: Boolean) { val order: CompileOrder, val nameHashing: Boolean) {
@deprecated("Use the other overloaded variant of the constructor that takes `nameHashing` value, instead.", "0.13.2") @deprecated("Use the other overloaded variant of the constructor that takes `nameHashing` value, instead.", "0.13.2")
def this(output: APIOutput, options: CompileOptions, compilerVersion: String, order: CompileOrder) = { def this(output: APIOutput, options: CompileOptions, compilerVersion: String, order: CompileOrder) = {
this(output, options, compilerVersion, order, false) this(output, options, compilerVersion, order, false)
} }
override def toString = s"""CompileSetup(
| options = $options
| compilerVersion = $compilerVersion
| order = $order
| nameHashing = $nameHashing
| output = $output
|)""".stripMargin
} }
object CompileSetup { object CompileSetup {
// Equiv[CompileOrder.Value] dominates Equiv[CompileSetup] // Equiv[CompileOrder.Value] dominates Equiv[CompileSetup]
implicit def equivCompileSetup(implicit equivOutput: Equiv[APIOutput], equivOpts: Equiv[CompileOptions], equivComp: Equiv[String] /*, equivOrder: Equiv[CompileOrder]*/ ): Equiv[CompileSetup] = new Equiv[CompileSetup] { implicit def equivCompileSetup(implicit equivOutput: Equiv[APIOutput], equivOpts: Equiv[CompileOptions], equivComp: Equiv[String] /*, equivOrder: Equiv[CompileOrder]*/ ): Equiv[CompileSetup] = new Equiv[CompileSetup] {
def equiv(a: CompileSetup, b: CompileSetup) = def equiv(a: CompileSetup, b: CompileSetup) = {
equivOutput.equiv(a.output, b.output) && // For some reason, an Equiv[Nothing] or some such is getting injected into here now, and borking all our results.
equivOpts.equiv(a.options, b.options) && // We hardcode these to use the Equiv defined in this class.
equivComp.equiv(a.compilerVersion, b.compilerVersion) && def sameOutput = CompileSetup.equivOutput.equiv(a.output, b.output)
a.order == b.order && // equivOrder.equiv(a.order, b.order) def sameOptions = CompileSetup.equivOpts.equiv(a.options, b.options)
a.nameHashing == b.nameHashing def sameCompiler = equivComp.equiv(a.compilerVersion, b.compilerVersion)
def sameOrder = a.order == b.order
def sameNameHasher = a.nameHashing == b.nameHashing
sameOutput &&
sameOptions &&
sameCompiler &&
sameOrder && // equivOrder.equiv(a.order, b.order)
sameNameHasher
}
} }
implicit val equivFile: Equiv[File] = new Equiv[File] { implicit val equivFile: Equiv[File] = new Equiv[File] {
def equiv(a: File, b: File) = a.getAbsoluteFile == b.getAbsoluteFile def equiv(a: File, b: File) = a.getAbsoluteFile == b.getAbsoluteFile
@ -41,14 +58,17 @@ object CompileSetup {
case (a, b) => case (a, b) =>
equivFile.equiv(a.sourceDirectory, b.sourceDirectory) && equivFile.equiv(a.outputDirectory, b.outputDirectory) equivFile.equiv(a.sourceDirectory, b.sourceDirectory) && equivFile.equiv(a.outputDirectory, b.outputDirectory)
}) })
case (s1: SingleOutput, s2: SingleOutput) => equivFile.equiv(s1.outputDirectory, s2.outputDirectory) case (s1: SingleOutput, s2: SingleOutput) =>
case _ => false equivFile.equiv(s1.outputDirectory, s2.outputDirectory)
case _ =>
false
} }
} }
implicit val equivOpts: Equiv[CompileOptions] = new Equiv[CompileOptions] { implicit val equivOpts: Equiv[CompileOptions] = new Equiv[CompileOptions] {
def equiv(a: CompileOptions, b: CompileOptions) = def equiv(a: CompileOptions, b: CompileOptions) = {
(a.options sameElements b.options) && (a.options sameElements b.options) &&
(a.javacOptions sameElements b.javacOptions) (a.javacOptions sameElements b.javacOptions)
}
} }
implicit val equivCompilerVersion: Equiv[String] = new Equiv[String] { implicit val equivCompilerVersion: Equiv[String] = new Equiv[String] {
def equiv(a: String, b: String) = a == b def equiv(a: String, b: String) = a == b

View File

@ -13,7 +13,33 @@ import xsbti.api.Definition
import xsbti.DependencyContext import xsbti.DependencyContext
import xsbti.DependencyContext.{ DependencyByInheritance, DependencyByMemberRef } import xsbti.DependencyContext.{ DependencyByInheritance, DependencyByMemberRef }
/**
* Helper methods for running incremental compilation. All this is responsible for is
* adapting any xsbti.AnalysisCallback into one compatible with the [[sbt.inc.Incremental]] class.
*/
object IncrementalCompile { object IncrementalCompile {
/**
* Runs the incremental compilation algorithm.
* @param sources
* The full set of input sources
* @param entry
* A className -> source file lookup function.
* @param compile
* The mechanism to run a single 'step' of compile, for ALL source files involved.
* @param previous
* The previous dependency Analysis (or an empty one).
* @param forEntry
* The dependency Analysis associated with a given file
* @param output
* The configured output directory/directory mapping for source files.
* @param log
* Where all log messages should go
* @param options
* Incremental compiler options (like name hashing vs. not).
* @return
* A flag of whether or not compilation completed succesfully, and the resulting dependency analysis object.
*
*/
def apply(sources: Set[File], entry: String => Option[File], def apply(sources: Set[File], entry: String => Option[File],
compile: (Set[File], DependencyChanges, xsbti.AnalysisCallback) => Unit, compile: (Set[File], DependencyChanges, xsbti.AnalysisCallback) => Unit,
previous: Analysis, previous: Analysis,

View File

@ -10,7 +10,30 @@ import xsbti.api.{ Compilation, Source }
import xsbti.compile.DependencyChanges import xsbti.compile.DependencyChanges
import java.io.File import java.io.File
/**
* Helper class to run incremental compilation algorithm.
*
*
* This class delegates down to
* - IncrementalNameHashing
* - IncrementalDefault
* - IncrementalAnyStyle
*/
object Incremental { object Incremental {
/**
* Runs the incremental compiler algorithm.
*
* @param sources The sources to compile
* @param entry The means of looking up a class on the classpath.
* @param previous The previously detected source dependencies.
* @param current A mechanism for generating stamps (timestamps, hashes, etc).
* @param doCompile The function which can run one level of compile.
* @param log The log where we write debugging information
* @param options Incremental compilation options
* @param equivS The means of testing whether two "Stamps" are the same.
* @return
* A flag of whether or not compilation completed succesfully, and the resulting dependency analysis object.
*/
def compile(sources: Set[File], def compile(sources: Set[File],
entry: String => Option[File], entry: String => Option[File],
previous: Analysis, previous: Analysis,

View File

@ -20,11 +20,9 @@ import xsbti.api.Source
import xsbti.compile.{ CompileOrder, DependencyChanges, GlobalsCache, Output, SingleOutput, MultipleOutput, CompileProgress } import xsbti.compile.{ CompileOrder, DependencyChanges, GlobalsCache, Output, SingleOutput, MultipleOutput, CompileProgress }
import CompileOrder.{ JavaThenScala, Mixed, ScalaThenJava } import CompileOrder.{ JavaThenScala, Mixed, ScalaThenJava }
final class CompileConfiguration(val sources: Seq[File], val classpath: Seq[File], @deprecated("0.13.8", "Use MixedAnalyzingCompiler or IC instead.")
val previousAnalysis: Analysis, val previousSetup: Option[CompileSetup], val currentSetup: CompileSetup, val progress: Option[CompileProgress], val getAnalysis: File => Option[Analysis], val definesClass: DefinesClass,
val reporter: Reporter, val compiler: AnalyzingCompiler, val javac: xsbti.compile.JavaCompiler, val cache: GlobalsCache, val incOptions: IncOptions)
class AggressiveCompile(cacheFile: File) { class AggressiveCompile(cacheFile: File) {
@deprecated("0.13.8", "Use IC.compile instead.")
def apply(compiler: AnalyzingCompiler, def apply(compiler: AnalyzingCompiler,
javac: xsbti.compile.JavaCompiler, javac: xsbti.compile.JavaCompiler,
sources: Seq[File], classpath: Seq[File], sources: Seq[File], classpath: Seq[File],
@ -186,20 +184,14 @@ class AggressiveCompile(cacheFile: File) {
private[this] def explicitBootClasspath(options: Seq[String]): Seq[File] = private[this] def explicitBootClasspath(options: Seq[String]): Seq[File] =
options.dropWhile(_ != CompilerArguments.BootClasspathOption).drop(1).take(1).headOption.toList.flatMap(IO.parseClasspath) options.dropWhile(_ != CompilerArguments.BootClasspathOption).drop(1).take(1).headOption.toList.flatMap(IO.parseClasspath)
val store = AggressiveCompile.staticCache(cacheFile, AnalysisStore.sync(AnalysisStore.cached(FileBasedStore(cacheFile)))) val store = MixedAnalyzingCompiler.staticCachedStore(cacheFile)
} }
@deprecated("0.13.8", "Use MixedAnalyzingCompiler instead.")
object AggressiveCompile { object AggressiveCompile {
import collection.mutable @deprecated("0.13.8", "Use MixedAnalyzingCompiler.staticCachedStore instead.")
import java.lang.ref.{ Reference, SoftReference } def staticCachedStore(cacheFile: File) = MixedAnalyzingCompiler.staticCachedStore(cacheFile)
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
}
}
@deprecated("0.13.8", "Deprecated in favor of new sbt.compiler.javac package.") @deprecated("0.13.8", "Deprecated in favor of new sbt.compiler.javac package.")
def directOrFork(instance: ScalaInstance, cpOptions: ClasspathOptions, javaHome: Option[File]): JavaTool = def directOrFork(instance: ScalaInstance, cpOptions: ClasspathOptions, javaHome: Option[File]): JavaTool =
if (javaHome.isDefined) if (javaHome.isDefined)

View File

@ -0,0 +1,41 @@
package sbt.compiler
import java.io.File
import sbt.CompileSetup
import sbt.inc.{ IncOptions, Analysis }
import sbt.inc.Locate._
import xsbti.Reporter
import xsbti.compile.{ GlobalsCache, CompileProgress }
/**
* Configuration used for running an analyzing compiler (a compiler which can extract dependencies between source files and JARs).
*
* @param sources
* @param classpath
* @param previousAnalysis
* @param previousSetup
* @param currentSetup
* @param progress
* @param getAnalysis
* @param definesClass
* @param reporter
* @param compiler
* @param javac
* @param cache
* @param incOptions
*/
final class CompileConfiguration(
val sources: Seq[File],
val classpath: Seq[File],
val previousAnalysis: Analysis,
val previousSetup: Option[CompileSetup],
val currentSetup: CompileSetup,
val progress: Option[CompileProgress],
val getAnalysis: File => Option[Analysis],
val definesClass: DefinesClass,
val reporter: Reporter,
val compiler: AnalyzingCompiler,
val javac: xsbti.compile.JavaCompiler,
val cache: GlobalsCache,
val incOptions: IncOptions)

View File

@ -1,29 +1,57 @@
package sbt.compiler package sbt.compiler
import java.io.File import java.io.File
import sbt.{ CompileSetup, IO, Using } import sbt.compiler.javac.AnalyzingJavaCompiler
import sbt.inc.{ Analysis, IncOptions, TextAnalysisFormat } import sbt.inc.Locate._
import xsbti.{ Logger, Maybe } import sbt._
import sbt.inc._
import xsbti.Logger
import xsbti.api.Source
import xsbti.compile.ClasspathOptions
import xsbti.compile.CompileOrder._
import xsbti.compile.DefinesClass
import xsbti.compile.ScalaInstance
import xsbti.{ Reporter, Logger, Maybe }
import xsbti.compile._ import xsbti.compile._
// TODO -
// 1. Move analyzingCompile from MixedAnalyzingCompiler into here
// 2. Create AnalyzingJavaComiler class
// 3. MixedAnalyzingCompiler should just provide the raw 'compile' method used in incremental compiler (and
// by this class.
/**
* An implementation of the incremental compiler that can compile inputs and dump out source dependency analysis.
*/
object IC extends IncrementalCompiler[Analysis, AnalyzingCompiler] { object IC extends IncrementalCompiler[Analysis, AnalyzingCompiler] {
def compile(in: Inputs[Analysis, AnalyzingCompiler], log: Logger): Analysis =
override def compile(in: Inputs[Analysis, AnalyzingCompiler], log: Logger): Analysis =
{ {
val setup = in.setup; import setup._ val setup = in.setup; import setup._
val options = in.options; import options.{ options => scalacOptions, _ } val options = in.options; import options.{ options => scalacOptions, _ }
val compilers = in.compilers; import compilers._ val compilers = in.compilers; import compilers._
val agg = new AggressiveCompile(setup.cacheFile)
val aMap = (f: File) => m2o(analysisMap(f)) val aMap = (f: File) => m2o(analysisMap(f))
val defClass = (f: File) => { val dc = definesClass(f); (name: String) => dc.apply(name) } val defClass = (f: File) => { val dc = definesClass(f); (name: String) => dc.apply(name) }
val incOptions = IncOptions.fromStringMap(incrementalCompilerOptions) val incOptions = IncOptions.fromStringMap(incrementalCompilerOptions)
agg(scalac, javac, sources, classpath, output, cache, m2o(progress), scalacOptions, javacOptions, aMap, val (previousAnalysis, previousSetup) = {
defClass, reporter, order, skip, incOptions)(log) MixedAnalyzingCompiler.staticCachedStore(setup.cacheFile()).get().map {
case (a, s) => (a, Some(s))
} getOrElse {
(Analysis.empty(nameHashing = incOptions.nameHashing), None)
}
}
incrementalCompile(scalac, javac, sources, classpath, output, cache, m2o(progress), scalacOptions, javacOptions, previousAnalysis,
previousSetup, aMap, defClass, reporter, order, skip, incOptions)(log).analysis
} }
private[this] def m2o[S](opt: Maybe[S]): Option[S] = if (opt.isEmpty) None else Some(opt.get) private[this] def m2o[S](opt: Maybe[S]): Option[S] = if (opt.isEmpty) None else Some(opt.get)
def newScalaCompiler(instance: ScalaInstance, interfaceJar: File, options: ClasspathOptions, log: Logger): AnalyzingCompiler = @deprecated("0.13.8", "A logger is no longer needed.")
new AnalyzingCompiler(instance, CompilerInterfaceProvider.constant(interfaceJar), options, log) override def newScalaCompiler(instance: ScalaInstance, interfaceJar: File, options: ClasspathOptions, log: Logger): AnalyzingCompiler =
new AnalyzingCompiler(instance, CompilerInterfaceProvider.constant(interfaceJar), options)
override def newScalaCompiler(instance: ScalaInstance, interfaceJar: File, options: ClasspathOptions): AnalyzingCompiler =
new AnalyzingCompiler(instance, CompilerInterfaceProvider.constant(interfaceJar), options)
def compileInterfaceJar(label: String, sourceJar: File, targetJar: File, interfaceJar: File, instance: ScalaInstance, log: Logger) { def compileInterfaceJar(label: String, sourceJar: File, targetJar: File, interfaceJar: File, instance: ScalaInstance, log: Logger) {
val raw = new RawCompiler(instance, sbt.ClasspathOptions.auto, log) val raw = new RawCompiler(instance, sbt.ClasspathOptions.auto, log)
@ -51,4 +79,84 @@ object IC extends IncrementalCompiler[Analysis, AnalyzingCompiler] {
throw new java.io.IOException(s"Error while reading $file", ex) throw new java.io.IOException(s"Error while reading $file", ex)
} }
} }
/** The result of running the compilation. */
final case class Result(analysis: Analysis, setup: CompileSetup, hasModified: Boolean)
/**
* This will run a mixed-compilation of Java/Scala sources
*
*
* TODO - this is the interface sbt uses. Somehow this needs to be exposed further.
*
* @param scalac An instances of the Scalac compiler which can also extract "Analysis" (dependencies)
* @param javac An instance of the Javac compiler.
* @param sources The set of sources to compile
* @param classpath The classpath to use when compiling.
* @param output Configuration for where to output .class files.
* @param cache The caching mechanism to use instead of insantiating new compiler instances.
* @param progress Progress listening for the compilation process. TODO - Feed this through the Javac Compiler!
* @param options Options for the Scala compiler
* @param javacOptions Options for the Java compiler
* @param previousAnalysis The previous dependency Analysis object/
* @param previousSetup The previous compilation setup (if any)
* @param analysisMap A map of file to the dependency analysis of that file.
* @param definesClass A mehcnaism of looking up whether or not a JAR defines a particular Class.
* @param reporter Where we sent all compilation error/warning events
* @param compileOrder The order we'd like to mix compilation. JavaThenScala, ScalaThenJava or Mixed.
* @param skip IF true, we skip compilation and just return the previous analysis file.
* @param incrementalCompilerOptions Options specific to incremental compilation.
* @param log The location where we write log messages.
* @return The full configuration used to instantiate this mixed-analyzing compiler, the set of extracted dependencies and
* whether or not any file were modified.
*/
def incrementalCompile(scalac: AnalyzingCompiler,
javac: xsbti.compile.JavaCompiler,
sources: Seq[File],
classpath: Seq[File],
output: Output,
cache: GlobalsCache,
progress: Option[CompileProgress] = None,
options: Seq[String] = Nil,
javacOptions: Seq[String] = Nil,
previousAnalysis: Analysis,
previousSetup: Option[CompileSetup],
analysisMap: File => Option[Analysis] = { _ => None },
definesClass: Locate.DefinesClass = Locate.definesClass _,
reporter: Reporter,
compileOrder: CompileOrder = Mixed,
skip: Boolean = false,
incrementalCompilerOptions: IncOptions)(implicit log: Logger): Result = {
val config = MixedAnalyzingCompiler.makeConfig(scalac, javac, sources, classpath, output, cache,
progress, options, javacOptions, previousAnalysis, previousSetup, analysisMap, definesClass, reporter,
compileOrder, skip, incrementalCompilerOptions
)
import config.{ currentSetup => setup }
if (skip) Result(previousAnalysis, setup, false)
else {
val (analysis, changed) = compileInternal(MixedAnalyzingCompiler(config)(log))
Result(analysis, setup, changed)
}
}
/** Actually runs the incremental compiler using the given mixed compiler. This will prune the inputs based on the CompileSetup. */
private def compileInternal(mixedCompiler: MixedAnalyzingCompiler)(implicit log: Logger, equiv: Equiv[CompileSetup]): (Analysis, Boolean) = {
val entry = MixedAnalyzingCompiler.classPathLookup(mixedCompiler.config)
import mixedCompiler.config._
import mixedCompiler.config.currentSetup.output
val sourcesSet = sources.toSet
val analysis = previousSetup match {
case Some(previous) if previous.nameHashing != currentSetup.nameHashing =>
// if the value of `nameHashing` flag has changed we have to throw away
// previous Analysis completely and start with empty Analysis object
// that supports the particular value of the `nameHashing` flag.
// Otherwise we'll be getting UnsupportedOperationExceptions
Analysis.empty(currentSetup.nameHashing)
case Some(previous) if equiv.equiv(previous, currentSetup) => previousAnalysis
case _ => Incremental.prune(sourcesSet, previousAnalysis)
}
// Run the incremental compiler using the mixed compiler we've defined.
IncrementalCompile(sourcesSet, entry, mixedCompiler.compile, analysis, getAnalysis, output, log, incOptions).swap
}
} }

View File

@ -0,0 +1,206 @@
package sbt.compiler
import java.io.File
import java.lang.ref.{ SoftReference, Reference }
import sbt.classfile.Analyze
import sbt.classpath.ClasspathUtilities
import sbt.compiler.CompileConfiguration
import sbt.compiler.javac.AnalyzingJavaCompiler
import sbt.inc.Locate.DefinesClass
import sbt._
import sbt.inc._
import sbt.inc.Locate
import xsbti.{ AnalysisCallback, Reporter }
import xsbti.api.Source
import xsbti.compile.CompileOrder._
import xsbti.compile._
/** An instance of an analyzing compiler that can run both javac + scalac. */
final class MixedAnalyzingCompiler(
val scalac: AnalyzingCompiler,
val javac: AnalyzingJavaCompiler,
val config: CompileConfiguration,
val log: Logger) {
import config._
import currentSetup._
private[this] val absClasspath = classpath.map(_.getAbsoluteFile)
/** Mechanism to work with compiler arguments. */
private[this] val cArgs = new CompilerArguments(compiler.scalaInstance, compiler.cp)
/**
* Compiles the given Java/Scala files.
*
* @param include The files to compile right now
* @param changes A list of dependency changes.
* @param callback The callback where we report dependency issues.
*/
def compile(include: Set[File], changes: DependencyChanges, callback: AnalysisCallback): Unit = {
val outputDirs = outputDirectories(output)
outputDirs foreach (IO.createDirectory)
val incSrc = sources.filter(include)
val (javaSrcs, scalaSrcs) = incSrc partition javaOnly
logInputs(log, javaSrcs.size, scalaSrcs.size, outputDirs)
/** compiles the scala code necessary using the analyzing compiler. */
def compileScala(): Unit =
if (!scalaSrcs.isEmpty) {
val sources = if (order == Mixed) incSrc else scalaSrcs
val arguments = cArgs(Nil, absClasspath, None, options.options)
timed("Scala compilation", log) {
compiler.compile(sources, changes, arguments, output, callback, reporter, config.cache, log, progress)
}
}
/**
* Compiles the Java code necessary. All analysis code is included in this method.
*/
def compileJava(): Unit =
if (!javaSrcs.isEmpty) {
// Runs the analysis portion of Javac.
timed("Java compile + analysis", log) {
javac.compile(javaSrcs, options.javacOptions.toArray[String], output, callback, reporter, log, progress)
}
}
// TODO - Maybe on "Mixed" we should try to compile both Scala + Java.
if (order == JavaThenScala) { compileJava(); compileScala() } else { compileScala(); compileJava() }
}
private[this] def outputDirectories(output: Output): Seq[File] = output match {
case single: SingleOutput => List(single.outputDirectory)
case mult: MultipleOutput => mult.outputGroups map (_.outputDirectory)
}
/** Debugging method to time how long it takes to run various compilation tasks. */
private[this] def timed[T](label: String, log: Logger)(t: => T): T = {
val start = System.nanoTime
val result = t
val elapsed = System.nanoTime - start
log.debug(label + " took " + (elapsed / 1e9) + " s")
result
}
private[this] def logInputs(log: Logger, javaCount: Int, scalaCount: Int, outputDirs: Seq[File]) {
val scalaMsg = Analysis.counted("Scala source", "", "s", scalaCount)
val javaMsg = Analysis.counted("Java source", "", "s", javaCount)
val combined = scalaMsg ++ javaMsg
if (!combined.isEmpty)
log.info(combined.mkString("Compiling ", " and ", " to " + outputDirs.map(_.getAbsolutePath).mkString(",") + "..."))
}
/** Returns true if the file is java. */
private[this] def javaOnly(f: File) = f.getName.endsWith(".java")
}
/**
* This is a compiler that mixes the `sbt.compiler.AnalyzingCompiler` for Scala incremental compilation
* with a `xsbti.JavaCompiler`, allowing cross-compilation of mixed Java/Scala projects with analysis output.
*
*
* NOTE: this class *defines* how to run one step of cross-Java-Scala compilation and then delegates
* down to the incremental compiler for the rest.
*/
object MixedAnalyzingCompiler {
def makeConfig(scalac: AnalyzingCompiler,
javac: xsbti.compile.JavaCompiler,
sources: Seq[File],
classpath: Seq[File],
output: Output,
cache: GlobalsCache,
progress: Option[CompileProgress] = None,
options: Seq[String] = Nil,
javacOptions: Seq[String] = Nil,
previousAnalysis: Analysis,
previousSetup: Option[CompileSetup],
analysisMap: File => Option[Analysis] = { _ => None },
definesClass: DefinesClass = Locate.definesClass _,
reporter: Reporter,
compileOrder: CompileOrder = Mixed,
skip: Boolean = false,
incrementalCompilerOptions: IncOptions): CompileConfiguration =
{
val compileSetup = new CompileSetup(output, new CompileOptions(options, javacOptions),
scalac.scalaInstance.actualVersion, compileOrder, incrementalCompilerOptions.nameHashing)
config(
sources,
classpath,
compileSetup,
progress,
previousAnalysis,
previousSetup,
analysisMap,
definesClass,
scalac,
javac,
reporter,
skip,
cache,
incrementalCompilerOptions)
}
def config(
sources: Seq[File],
classpath: Seq[File],
setup: CompileSetup,
progress: Option[CompileProgress],
previousAnalysis: Analysis,
previousSetup: Option[CompileSetup],
analysis: File => Option[Analysis],
definesClass: DefinesClass,
compiler: AnalyzingCompiler,
javac: xsbti.compile.JavaCompiler,
reporter: Reporter,
skip: Boolean,
cache: GlobalsCache,
incrementalCompilerOptions: IncOptions): CompileConfiguration = {
import CompileSetup._
new CompileConfiguration(sources, classpath, previousAnalysis, previousSetup, setup,
progress, analysis, definesClass, reporter, compiler, javac, cache, incrementalCompilerOptions)
}
/** Returns the search classpath (for dependencies) and a function which can also do so. */
def searchClasspathAndLookup(config: CompileConfiguration): (Seq[File], String => Option[File]) = {
import config._
import currentSetup._
val absClasspath = classpath.map(_.getAbsoluteFile)
val apiOption = (api: Either[Boolean, Source]) => api.right.toOption
val cArgs = new CompilerArguments(compiler.scalaInstance, compiler.cp)
val searchClasspath = explicitBootClasspath(options.options) ++ withBootclasspath(cArgs, absClasspath)
(searchClasspath, Locate.entry(searchClasspath, definesClass))
}
/** Returns a "lookup file for a given class name" function. */
def classPathLookup(config: CompileConfiguration): String => Option[File] =
searchClasspathAndLookup(config)._2
def apply(config: CompileConfiguration)(implicit log: Logger): MixedAnalyzingCompiler = {
import config._
val (searchClasspath, entry) = searchClasspathAndLookup(config)
// Construct a compiler which can handle both java and scala sources.
new MixedAnalyzingCompiler(
compiler,
// TODO - Construction of analyzing Java compiler MAYBE should be earlier...
new AnalyzingJavaCompiler(javac, classpath, compiler.scalaInstance, entry, searchClasspath),
config,
log
)
}
def withBootclasspath(args: CompilerArguments, classpath: Seq[File]): Seq[File] =
args.bootClasspathFor(classpath) ++ args.extClasspath ++ args.finishClasspath(classpath)
private[this] def explicitBootClasspath(options: Seq[String]): Seq[File] =
options.dropWhile(_ != CompilerArguments.BootClasspathOption).drop(1).take(1).headOption.toList.flatMap(IO.parseClasspath)
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
}
}
/** Create a an analysis store cache at the desired location. */
def staticCachedStore(cacheFile: File) = staticCache(cacheFile, AnalysisStore.sync(AnalysisStore.cached(FileBasedStore(cacheFile))))
}

View File

@ -0,0 +1,98 @@
package sbt.compiler.javac
import java.io.File
import sbt._
import sbt.classfile.Analyze
import sbt.classpath.ClasspathUtilities
import sbt.compiler.CompilerArguments
import sbt.inc.Locate
import xsbti.api.Source
import xsbti.compile._
import xsbti.{ AnalysisCallback, Reporter }
/**
* This is a java compiler which will also report any discovered source dependencies/apis out via
* an analysis callback.
*
* @param searchClasspath Differes from classpath in that we look up binary dependencies via this classpath.
* @param classLookup A mechanism by which we can figure out if a JAR contains a classfile.
*/
final class AnalyzingJavaCompiler private[sbt] (
val javac: xsbti.compile.JavaCompiler,
val classpath: Seq[File],
val scalaInstance: xsbti.compile.ScalaInstance,
val classLookup: (String => Option[File]),
val searchClasspath: Seq[File]) {
/**
* Compile some java code using the current configured compiler.
*
* @param sources The sources to compile
* @param options The options for the Java compiler
* @param output The output configuration for this compiler
* @param callback A callback to report discovered source/binary dependencies on.
* @param reporter A reporter where semantic compiler failures can be reported.
* @param log A place where we can log debugging/error messages.
* @param progressOpt An optional compilation progress reporter. Where we can report back what files we're currently compiling.
*/
def compile(sources: Seq[File], options: Seq[String], output: Output, callback: AnalysisCallback, reporter: Reporter, log: Logger, progressOpt: Option[CompileProgress]): Unit = {
if (!sources.isEmpty) {
val absClasspath = classpath.map(_.getAbsoluteFile)
@annotation.tailrec def ancestor(f1: File, f2: File): Boolean =
if (f2 eq null) false else if (f1 == f2) true else ancestor(f1, f2.getParentFile)
// Here we outline "chunks" of compiles we need to run so that the .class files end up in the right
// location for Java.
val chunks: Map[Option[File], Seq[File]] = output match {
case single: SingleOutput => Map(Some(single.outputDirectory) -> sources)
case multi: MultipleOutput =>
sources groupBy { src =>
multi.outputGroups find { out => ancestor(out.sourceDirectory, src) } map (_.outputDirectory)
}
}
// Report warnings about source files that have no output directory.
chunks.get(None) foreach { srcs =>
log.error("No output directory mapped for: " + srcs.map(_.getAbsolutePath).mkString(","))
}
// Here we try to memoize (cache) the known class files in the output directory.
val memo = for ((Some(outputDirectory), srcs) <- chunks) yield {
val classesFinder = PathFinder(outputDirectory) ** "*.class"
(classesFinder, classesFinder.get, srcs)
}
// Here we construct a class-loader we'll use to load + analyze the
val loader = ClasspathUtilities.toLoader(searchClasspath)
// TODO - Perhaps we just record task 0/2 here
timed("Java compilation", log) {
try javac.compileWithReporter(sources.toArray, absClasspath.toArray, output, options.toArray, reporter, log)
catch {
// Handle older APIs
case _: NoSuchMethodError =>
javac.compile(sources.toArray, absClasspath.toArray, output, options.toArray, log)
}
}
// TODO - Perhaps we just record task 1/2 here
/** Reads the API information directly from the Class[_] object. Used when Analyzing dependencies. */
def readAPI(source: File, classes: Seq[Class[_]]): Set[String] = {
val (api, inherits) = ClassToAPI.process(classes)
callback.api(source, api)
inherits.map(_.getName)
}
// Runs the analysis portion of Javac.
timed("Java analysis", log) {
for ((classesFinder, oldClasses, srcs) <- memo) {
val newClasses = Set(classesFinder.get: _*) -- oldClasses
Analyze(newClasses.toSeq, srcs, log)(callback, loader, readAPI)
}
}
// TODO - Perhaps we just record task 2/2 here
}
}
/** Debugging method to time how long it takes to run various compilation tasks. */
private[this] def timed[T](label: String, log: Logger)(t: => T): T = {
val start = System.nanoTime
val result = t
val elapsed = System.nanoTime - start
log.debug(label + " took " + (elapsed / 1e9) + " s")
result
}
}

View File

@ -150,6 +150,7 @@ class ScalaCompilerForUnitTesting(nameHashing: Boolean = false) {
val args = Array.empty[String] val args = Array.empty[String]
object output extends SingleOutput { object output extends SingleOutput {
def outputDirectory: File = outputDir def outputDirectory: File = outputDir
override def toString = s"SingleOutput($outputDirectory)"
} }
val weakLog = new WeakLog(ConsoleLogger(), ConsoleReporter) val weakLog = new WeakLog(ConsoleLogger(), ConsoleReporter)
val cachedCompiler = new CachedCompiler0(args, output, weakLog, false) val cachedCompiler = new CachedCompiler0(args, output, weakLog, false)

View File

@ -77,7 +77,13 @@ object AnalysisFormats {
implicit val multipleOutputFormat: Format[MultipleOutput] = implicit val multipleOutputFormat: Format[MultipleOutput] =
wrap[MultipleOutput, Array[OutputGroup]]( wrap[MultipleOutput, Array[OutputGroup]](
(_.outputGroups), (_.outputGroups),
{ groups => new MultipleOutput { def outputGroups = groups } } {
groups =>
new MultipleOutput {
def outputGroups = groups
override def toString = s"MultipleOutput($outputGroups)"
}
}
) )
implicit val singleOutputFormat: Format[SingleOutput] = implicit val singleOutputFormat: Format[SingleOutput] =
wrap[SingleOutput, File]( wrap[SingleOutput, File](

View File

@ -308,8 +308,10 @@ object TextAnalysisFormat {
case (src: File, out: File) => new MultipleOutput.OutputGroup { case (src: File, out: File) => new MultipleOutput.OutputGroup {
val sourceDirectory = src val sourceDirectory = src
val outputDirectory = out val outputDirectory = out
override def toString = s"OutputGroup($src -> $out)"
} }
} }
override def toString = s"MultipleOuput($outputGroups)"
} }
case str: String => throw new ReadException("Unrecognized output mode: " + str) case str: String => throw new ReadException("Unrecognized output mode: " + str)
} }

View File

@ -8,9 +8,11 @@ package compiler
import xsbti.compile.{ Output, SingleOutput, MultipleOutput } import xsbti.compile.{ Output, SingleOutput, MultipleOutput }
import java.io.File import java.io.File
/** Constructor for the `Output` ADT for incremental compiler. Can either take groups (src -> out) or a single output. */
object CompileOutput { object CompileOutput {
def apply(dir: File): Output = new SingleOutput { def apply(dir: File): Output = new SingleOutput {
def outputDirectory = dir def outputDirectory = dir
override def toString = s"SingleOutput($outputDirectory)"
} }
def apply(groups: (File, File)*): Output = new MultipleOutput { def apply(groups: (File, File)*): Output = new MultipleOutput {
@ -18,7 +20,9 @@ object CompileOutput {
case (src, out) => new MultipleOutput.OutputGroup { case (src, out) => new MultipleOutput.OutputGroup {
def sourceDirectory = src def sourceDirectory = src
def outputDirectory = out def outputDirectory = out
override def toString = s"OutputGroup($src -> $out)"
} }
} }
override def toString = s"MultiOutput($outputGroups)"
} }
} }

View File

@ -1,5 +1,10 @@
package xsbti.compile; package xsbti.compile;
/**
* An API for reporting when files are being compiled.
*
* Note; This is tied VERY SPECIFICALLY to scala.
*/
public interface CompileProgress { public interface CompileProgress {
void startUnit(String phase, String unitPath); void startUnit(String phase, String unitPath);

View File

@ -3,6 +3,9 @@ package xsbti.compile;
import xsbti.Logger; import xsbti.Logger;
import xsbti.Reporter; import xsbti.Reporter;
/**
* An interface which lets us know how to retrieve cached compiler instances form the current JVM.
*/
public interface GlobalsCache public interface GlobalsCache
{ {
public CachedCompiler apply(String[] args, Output output, boolean forceNew, CachedCompilerProvider provider, Logger log, Reporter reporter); public CachedCompiler apply(String[] args, Output output, boolean forceNew, CachedCompilerProvider provider, Logger log, Reporter reporter);

View File

@ -44,8 +44,18 @@ public interface IncrementalCompiler<Analysis, ScalaCompiler>
* @param instance The Scala version to use * @param instance The Scala version to use
* @param interfaceJar The compiler interface jar compiled for the Scala version being used * @param interfaceJar The compiler interface jar compiled for the Scala version being used
* @param options Configures how arguments to the underlying Scala compiler will be built. * @param options Configures how arguments to the underlying Scala compiler will be built.
*
*/ */
@Deprecated
ScalaCompiler newScalaCompiler(ScalaInstance instance, File interfaceJar, ClasspathOptions options, Logger log); ScalaCompiler newScalaCompiler(ScalaInstance instance, File interfaceJar, ClasspathOptions options, Logger log);
/**
* Creates a compiler instance that can be used by the `compile` method.
*
* @param instance The Scala version to use
* @param interfaceJar The compiler interface jar compiled for the Scala version being used
* @param options Configures how arguments to the underlying Scala compiler will be built.
*/
ScalaCompiler newScalaCompiler(ScalaInstance instance, File interfaceJar, ClasspathOptions options);
/** /**
* Compiles the source interface for a Scala version. The resulting jar can then be used by the `newScalaCompiler` method * Compiles the source interface for a Scala version. The resulting jar can then be used by the `newScalaCompiler` method

View File

@ -3,7 +3,7 @@
*/ */
package sbt package sbt
import sbt.compiler.javac.{ IncrementalCompilerJavaTools, JavaCompiler, JavaTools } import sbt.compiler.javac.{ IncrementalCompilerJavaTools, JavaTools }
import xsbti.{ Logger => _, _ } import xsbti.{ Logger => _, _ }
import xsbti.compile.{ CompileOrder, GlobalsCache } import xsbti.compile.{ CompileOrder, GlobalsCache }
import CompileOrder.{ JavaThenScala, Mixed, ScalaThenJava } import CompileOrder.{ JavaThenScala, Mixed, ScalaThenJava }
@ -15,12 +15,16 @@ import java.io.File
object Compiler { object Compiler {
val DefaultMaxErrors = 100 val DefaultMaxErrors = 100
/** Inputs necessary to run the incremental compiler. */
final case class Inputs(compilers: Compilers, config: Options, incSetup: IncSetup) final case class Inputs(compilers: Compilers, config: Options, incSetup: IncSetup)
/** The inputs for the copiler *and* the previous analysis of source dependecnies. */
final case class InputsWithPrevious(inputs: Inputs, previousAnalysis: PreviousAnalysis)
final case class Options(classpath: Seq[File], sources: Seq[File], classesDirectory: File, options: Seq[String], javacOptions: Seq[String], maxErrors: Int, sourcePositionMapper: Position => Position, order: CompileOrder) final case class Options(classpath: Seq[File], sources: Seq[File], classesDirectory: File, options: Seq[String], javacOptions: Seq[String], maxErrors: Int, sourcePositionMapper: Position => Position, order: CompileOrder)
final case class IncSetup(analysisMap: File => Option[Analysis], definesClass: DefinesClass, skip: Boolean, cacheFile: File, cache: GlobalsCache, incOptions: IncOptions) final case class IncSetup(analysisMap: File => Option[Analysis], definesClass: DefinesClass, skip: Boolean, cacheFile: File, cache: GlobalsCache, incOptions: IncOptions)
private[sbt] trait JavaToolWithNewInterface extends JavaTool { private[sbt] trait JavaToolWithNewInterface extends JavaTool {
def newJavac: IncrementalCompilerJavaTools def newJavac: IncrementalCompilerJavaTools
} }
/** The instances of Scalac/Javac used to compile the current project. */
final case class Compilers(scalac: AnalyzingCompiler, javac: JavaTool) { final case class Compilers(scalac: AnalyzingCompiler, javac: JavaTool) {
final def newJavac: Option[IncrementalCompilerJavaTools] = final def newJavac: Option[IncrementalCompilerJavaTools] =
javac match { javac match {
@ -28,9 +32,13 @@ object Compiler {
case _ => None case _ => None
} }
} }
final case class NewCompilers(scalac: AnalyzingCompiler, javac: JavaTools) /** The previous source dependency analysis result from compilation. */
final case class PreviousAnalysis(analysis: Analysis, setup: Option[CompileSetup])
type CompileResult = IC.Result
def inputs(classpath: Seq[File], sources: Seq[File], classesDirectory: File, options: Seq[String], javacOptions: Seq[String], maxErrors: Int, sourcePositionMappers: Seq[Position => Option[Position]], order: CompileOrder)(implicit compilers: Compilers, incSetup: IncSetup, log: Logger): Inputs = def inputs(classpath: Seq[File], sources: Seq[File], classesDirectory: File, options: Seq[String],
javacOptions: Seq[String], maxErrors: Int, sourcePositionMappers: Seq[Position => Option[Position]],
order: CompileOrder)(implicit compilers: Compilers, incSetup: IncSetup, log: Logger): Inputs =
new Inputs( new Inputs(
compilers, compilers,
new Options(classpath, sources, classesDirectory, options, javacOptions, maxErrors, foldMappers(sourcePositionMappers), order), new Options(classpath, sources, classesDirectory, options, javacOptions, maxErrors, foldMappers(sourcePositionMappers), order),
@ -77,28 +85,47 @@ object Compiler {
val launcher = app.provider.scalaProvider.launcher val launcher = app.provider.scalaProvider.launcher
val componentManager = new ComponentManager(launcher.globalLock, app.provider.components, Option(launcher.ivyHome), log) val componentManager = new ComponentManager(launcher.globalLock, app.provider.components, Option(launcher.ivyHome), log)
val provider = ComponentCompiler.interfaceProvider(componentManager) val provider = ComponentCompiler.interfaceProvider(componentManager)
new AnalyzingCompiler(instance, provider, cpOptions, log) new AnalyzingCompiler(instance, provider, cpOptions)
} }
def apply(in: Inputs, log: Logger): Analysis =
{ @deprecated("0.13.8", "Use the `compile` method instead.")
import in.compilers._ def apply(in: Inputs, log: Logger): Analysis = {
import in.config._ import in.config._
import in.incSetup._ apply(in, log, new LoggerReporter(maxErrors, log, sourcePositionMapper))
apply(in, log, new LoggerReporter(maxErrors, log, sourcePositionMapper)) }
@deprecated("0.13.8", "Use the `compile` method instead.")
def apply(in: Inputs, log: Logger, reporter: xsbti.Reporter): Analysis = {
import in.compilers._
import in.config._
import in.incSetup._
// Here we load the previous analysis since the new paths don't.
val (previousAnalysis, previousSetup) = {
MixedAnalyzingCompiler.staticCachedStore(cacheFile).get().map {
case (a, s) => (a, Some(s))
} getOrElse {
(Analysis.empty(nameHashing = incOptions.nameHashing), None)
}
} }
def apply(in: Inputs, log: Logger, reporter: xsbti.Reporter): Analysis = compile(InputsWithPrevious(in, PreviousAnalysis(previousAnalysis, previousSetup)), log, reporter).analysis
}
def compile(in: InputsWithPrevious, log: Logger): CompileResult =
{ {
import in.compilers._ import in.inputs.config._
import in.config._ compile(in, log, new LoggerReporter(maxErrors, log, sourcePositionMapper))
import in.incSetup._ }
val agg = new AggressiveCompile(cacheFile) def compile(in: InputsWithPrevious, log: Logger, reporter: xsbti.Reporter): CompileResult =
{
import in.inputs.compilers._
import in.inputs.config._
import in.inputs.incSetup._
// Here is some trickery to choose the more recent (reporter-using) java compiler rather // Here is some trickery to choose the more recent (reporter-using) java compiler rather
// than the previously defined versions. // than the previously defined versions.
// TODO - Remove this hackery in sbt 1.0. // TODO - Remove this hackery in sbt 1.0.
val javacChosen: xsbti.compile.JavaCompiler = val javacChosen: xsbti.compile.JavaCompiler =
in.compilers.newJavac.map(_.xsbtiCompiler).getOrElse(in.compilers.javac) in.inputs.compilers.newJavac.map(_.xsbtiCompiler).getOrElse(in.inputs.compilers.javac)
agg(scalac, javacChosen, sources, classpath, CompileOutput(classesDirectory), cache, None, options, javacOptions, // TODO - Why are we not using the IC interface???
analysisMap, definesClass, reporter, order, skip, incOptions)(log) IC.incrementalCompile(scalac, javacChosen, sources, classpath, CompileOutput(classesDirectory), cache, None, options, javacOptions,
in.previousAnalysis.analysis, in.previousAnalysis.setup, analysisMap, definesClass, reporter, order, skip, incOptions)(log)
} }
private[sbt] def foldMappers[A](mappers: Seq[A => Option[A]]) = private[sbt] def foldMappers[A](mappers: Seq[A => Option[A]]) =

View File

@ -5,6 +5,7 @@ package sbt
import Attributed.data import Attributed.data
import Scope.{ fillTaskAxis, GlobalScope, ThisScope } import Scope.{ fillTaskAxis, GlobalScope, ThisScope }
import sbt.Compiler.InputsWithPrevious
import xsbt.api.Discovery import xsbt.api.Discovery
import xsbti.compile.CompileOrder import xsbti.compile.CompileOrder
import Project.{ inConfig, inScope, inTask, richInitialize, richInitializeTask, richTaskSessionVar } import Project.{ inConfig, inScope, inTask, richInitialize, richInitializeTask, richTaskSessionVar }
@ -14,7 +15,8 @@ import Configurations.{ Compile, CompilerPlugin, IntegrationTest, names, Provide
import CrossVersion.{ binarySbtVersion, binaryScalaVersion, partialVersion } import CrossVersion.{ binarySbtVersion, binaryScalaVersion, partialVersion }
import complete._ import complete._
import std.TaskExtra._ import std.TaskExtra._
import inc.{ FileValueCache, IncOptions, Locate } import sbt.inc.{ Analysis, FileValueCache, IncOptions, Locate }
import sbt.compiler.{ MixedAnalyzingCompiler, AggressiveCompile }
import testing.{ Framework, Runner, AnnotatedFingerprint, SubclassFingerprint } import testing.{ Framework, Runner, AnnotatedFingerprint, SubclassFingerprint }
import sys.error import sys.error
@ -246,8 +248,10 @@ object Defaults extends BuildCommon {
def compilersSetting = compilers := Compiler.compilers(scalaInstance.value, classpathOptions.value, javaHome.value)(appConfiguration.value, streams.value.log) def compilersSetting = compilers := Compiler.compilers(scalaInstance.value, classpathOptions.value, javaHome.value)(appConfiguration.value, streams.value.log)
lazy val configTasks = docTaskSettings(doc) ++ inTask(compile)(compileInputsSettings) ++ configGlobal ++ Seq( lazy val configTasks = docTaskSettings(doc) ++ inTask(compile)(compileInputsSettings) ++ configGlobal ++ compileAnalysisSettings ++ Seq(
compile <<= compileTask tag (Tags.Compile, Tags.CPU), compile <<= compileTask,
manipulateBytecode := compileIncremental.value,
compileIncremental <<= compileIncrementalTask tag (Tags.Compile, Tags.CPU),
printWarnings <<= printWarningsTask, printWarnings <<= printWarningsTask,
compileAnalysisFilename := { compileAnalysisFilename := {
// Here, if the user wants cross-scala-versioning, we also append it // Here, if the user wants cross-scala-versioning, we also append it
@ -648,7 +652,7 @@ object Defaults extends BuildCommon {
key in TaskGlobal <<= packageTask, key in TaskGlobal <<= packageTask,
packageConfiguration <<= packageConfigurationTask, packageConfiguration <<= packageConfigurationTask,
mappings <<= mappingsTask, mappings <<= mappingsTask,
packagedArtifact := (artifact.value, key.value), packagedArtifact := (artifact.value -> key.value),
artifact <<= artifactSetting, artifact <<= artifactSetting,
artifactPath <<= artifactPathSetting(artifact) artifactPath <<= artifactPathSetting(artifact)
)) ))
@ -778,15 +782,28 @@ object Defaults extends BuildCommon {
@deprecated("Use inTask(compile)(compileInputsSettings)", "0.13.0") @deprecated("Use inTask(compile)(compileInputsSettings)", "0.13.0")
def compileTaskSettings: Seq[Setting[_]] = inTask(compile)(compileInputsSettings) def compileTaskSettings: Seq[Setting[_]] = inTask(compile)(compileInputsSettings)
def compileTask: Initialize[Task[inc.Analysis]] = Def.task { compileTaskImpl(streams.value, (compileInputs in compile).value, (compilerReporter in compile).value) } def compileTask: Initialize[Task[inc.Analysis]] = Def.task {
private[this] def compileTaskImpl(s: TaskStreams, ci: Compiler.Inputs, reporter: Option[xsbti.Reporter]): inc.Analysis = val setup: Compiler.IncSetup = compileIncSetup.value
// TODO - expose bytecode manipulation phase.
val analysisResult: Compiler.CompileResult = manipulateBytecode.value
if (analysisResult.hasModified) {
val store = MixedAnalyzingCompiler.staticCachedStore(setup.cacheFile)
store.set(analysisResult.analysis, analysisResult.setup)
}
analysisResult.analysis
}
def compileIncrementalTask = Def.task {
// TODO - Should readAnalysis + saveAnalysis be scoped by the compile task too?
compileIncrementalTaskImpl(streams.value, (compileInputs in compile).value, previousCompile.value, (compilerReporter in compile).value)
}
private[this] def compileIncrementalTaskImpl(s: TaskStreams, ci: Compiler.Inputs, previous: Compiler.PreviousAnalysis, reporter: Option[xsbti.Reporter]): Compiler.CompileResult =
{ {
lazy val x = s.text(ExportStream) lazy val x = s.text(ExportStream)
def onArgs(cs: Compiler.Compilers) = cs.copy(scalac = cs.scalac.onArgs(exported(x, "scalac")), javac = cs.javac.onArgs(exported(x, "javac"))) def onArgs(cs: Compiler.Compilers) = cs.copy(scalac = cs.scalac.onArgs(exported(x, "scalac")), javac = cs.javac.onArgs(exported(x, "javac")))
val i = ci.copy(compilers = onArgs(ci.compilers)) val i = InputsWithPrevious(ci.copy(compilers = onArgs(ci.compilers)), previous)
try reporter match { try reporter match {
case Some(reporter) => Compiler(i, s.log, reporter) case Some(reporter) => Compiler.compile(i, s.log, reporter)
case None => Compiler(i, s.log) case None => Compiler.compile(i, s.log)
} }
finally x.close() // workaround for #937 finally x.close() // workaround for #937
} }
@ -803,9 +820,20 @@ object Defaults extends BuildCommon {
def compileInputsSettings: Seq[Setting[_]] = def compileInputsSettings: Seq[Setting[_]] =
Seq(compileInputs := { Seq(compileInputs := {
val cp = classDirectory.value +: data(dependencyClasspath.value) val cp = classDirectory.value +: data(dependencyClasspath.value)
Compiler.inputs(cp, sources.value, classDirectory.value, scalacOptions.value, javacOptions.value, maxErrors.value, sourcePositionMappers.value, compileOrder.value)(compilers.value, compileIncSetup.value, streams.value.log) Compiler.inputs(cp, sources.value, classDirectory.value, scalacOptions.value, javacOptions.value,
maxErrors.value, sourcePositionMappers.value, compileOrder.value)(compilers.value, compileIncSetup.value, streams.value.log)
}, },
compilerReporter := None) compilerReporter := None)
def compileAnalysisSettings: Seq[Setting[_]] = Seq(
previousCompile := {
val setup: Compiler.IncSetup = compileIncSetup.value
val store = MixedAnalyzingCompiler.staticCachedStore(setup.cacheFile)
store.get() match {
case Some((an, setup)) => Compiler.PreviousAnalysis(an, Some(setup))
case None => Compiler.PreviousAnalysis(Analysis.empty(nameHashing = setup.incOptions.nameHashing), None)
}
}
)
def printWarningsTask: Initialize[Task[Unit]] = def printWarningsTask: Initialize[Task[Unit]] =
(streams, compile, maxErrors, sourcePositionMappers) map { (s, analysis, max, spms) => (streams, compile, maxErrors, sourcePositionMappers) map { (s, analysis, max, spms) =>
@ -1006,7 +1034,7 @@ object Classpaths {
packagedArtifacts :== Map.empty, packagedArtifacts :== Map.empty,
crossTarget := target.value, crossTarget := target.value,
makePom := { val config = makePomConfiguration.value; IvyActions.makePom(ivyModule.value, config, streams.value.log); config.file }, makePom := { val config = makePomConfiguration.value; IvyActions.makePom(ivyModule.value, config, streams.value.log); config.file },
packagedArtifact in makePom := (artifact in makePom value, makePom value), packagedArtifact in makePom := ((artifact in makePom).value -> makePom.value),
deliver <<= deliverTask(deliverConfiguration), deliver <<= deliverTask(deliverConfiguration),
deliverLocal <<= deliverTask(deliverLocalConfiguration), deliverLocal <<= deliverTask(deliverLocalConfiguration),
publish <<= publishTask(publishConfiguration, deliver), publish <<= publishTask(publishConfiguration, deliver),
@ -1485,7 +1513,7 @@ object Classpaths {
def visit(p: ProjectRef, c: Configuration) { def visit(p: ProjectRef, c: Configuration) {
val applicableConfigs = allConfigs(c) val applicableConfigs = allConfigs(c)
for (ac <- applicableConfigs) // add all configurations in this project for (ac <- applicableConfigs) // add all configurations in this project
visited add (p, ac.name) visited add (p -> ac.name)
val masterConfs = names(getConfigurations(projectRef, data)) val masterConfs = names(getConfigurations(projectRef, data))
for (ResolvedClasspathDependency(dep, confMapping) <- deps.classpath(p)) { for (ResolvedClasspathDependency(dep, confMapping) <- deps.classpath(p)) {

View File

@ -9,6 +9,7 @@ import Def.ScopedKey
import complete._ import complete._
import inc.Analysis import inc.Analysis
import inc.Locate.DefinesClass import inc.Locate.DefinesClass
import sbt.compiler.MixedAnalyzingCompiler
import std.TaskExtra._ import std.TaskExtra._
import xsbti.compile.{ CompileOrder, GlobalsCache } import xsbti.compile.{ CompileOrder, GlobalsCache }
import scala.xml.{ Node => XNode, NodeSeq } import scala.xml.{ Node => XNode, NodeSeq }
@ -141,6 +142,9 @@ object Keys {
val consoleQuick = TaskKey[Unit]("console-quick", "Starts the Scala interpreter with the project dependencies on the classpath.", ATask, console) val consoleQuick = TaskKey[Unit]("console-quick", "Starts the Scala interpreter with the project dependencies on the classpath.", ATask, console)
val consoleProject = TaskKey[Unit]("console-project", "Starts the Scala interpreter with the sbt and the build definition on the classpath and useful imports.", AMinusTask) val consoleProject = TaskKey[Unit]("console-project", "Starts the Scala interpreter with the sbt and the build definition on the classpath and useful imports.", AMinusTask)
val compile = TaskKey[Analysis]("compile", "Compiles sources.", APlusTask) val compile = TaskKey[Analysis]("compile", "Compiles sources.", APlusTask)
val manipulateBytecode = TaskKey[Compiler.CompileResult]("manipulateBytecode", "Manipulates generated bytecode", BTask)
val compileIncremental = TaskKey[Compiler.CompileResult]("compileIncremental", "Actually runs the incremental compliation", DTask)
val previousCompile = TaskKey[Compiler.PreviousAnalysis]("readAnalysis", "Read the incremental compiler analysis from disk", DTask)
val compilers = TaskKey[Compiler.Compilers]("compilers", "Defines the Scala and Java compilers to use for compilation.", DTask) val compilers = TaskKey[Compiler.Compilers]("compilers", "Defines the Scala and Java compilers to use for compilation.", DTask)
val compileAnalysisFilename = TaskKey[String]("compileAnalysisFilename", "Defines the filename used to store the incremental compiler analysis file (inside the streams cacheDirectory).", DTask) val compileAnalysisFilename = TaskKey[String]("compileAnalysisFilename", "Defines the filename used to store the incremental compiler analysis file (inside the streams cacheDirectory).", DTask)
val compileIncSetup = TaskKey[Compiler.IncSetup]("inc-compile-setup", "Configures aspects of incremental compilation.", DTask) val compileIncSetup = TaskKey[Compiler.IncSetup]("inc-compile-setup", "Configures aspects of incremental compilation.", DTask)

View File

@ -156,8 +156,8 @@ object Sbt extends Build {
// Persists the incremental data structures using SBinary // Persists the incremental data structures using SBinary
lazy val compilePersistSub = testedBaseProject(compilePath / "persist", "Persist") dependsOn (compileIncrementalSub, apiSub, compileIncrementalSub % "test->test") settings (sbinary) lazy val compilePersistSub = testedBaseProject(compilePath / "persist", "Persist") dependsOn (compileIncrementalSub, apiSub, compileIncrementalSub % "test->test") settings (sbinary)
// sbt-side interface to compiler. Calls compiler-side interface reflectively // sbt-side interface to compiler. Calls compiler-side interface reflectively
lazy val compilerSub = testedBaseProject(compilePath, "Compile") dependsOn (launchInterfaceSub, interfaceSub % "compile;test->test", logSub, ioSub, classpathSub, lazy val compilerSub = testedBaseProject(compilePath, "Compile") dependsOn (launchInterfaceSub, interfaceSub % "compile;test->test", logSub, ioSub, classpathSub, apiSub, classfileSub,
logSub % "test->test", launchSub % "test->test", apiSub % "test") settings (compilerSettings: _*) logSub % "test->test", launchSub % "test->test") settings (compilerSettings: _*)
lazy val compilerIntegrationSub = baseProject(compilePath / "integration", "Compiler Integration") dependsOn ( lazy val compilerIntegrationSub = baseProject(compilePath / "integration", "Compiler Integration") dependsOn (
compileIncrementalSub, compilerSub, compilePersistSub, apiSub, classfileSub) compileIncrementalSub, compilerSub, compilePersistSub, apiSub, classfileSub)
lazy val compilerIvySub = baseProject(compilePath / "ivy", "Compiler Ivy Integration") dependsOn (ivySub, compilerSub) lazy val compilerIvySub = baseProject(compilePath / "ivy", "Compiler Ivy Integration") dependsOn (ivySub, compilerSub)

View File

@ -4,7 +4,7 @@ libraryDependencies += "org.scala-sbt" % "sbt" % sbtVersion.value
lazy val expectErrorNotCrash = taskKey[Unit]("Ensures that sbt properly set types on Trees so that the compiler doesn't crash on a bad reference to .value, but gives a proper error instead.") lazy val expectErrorNotCrash = taskKey[Unit]("Ensures that sbt properly set types on Trees so that the compiler doesn't crash on a bad reference to .value, but gives a proper error instead.")
expectErrorNotCrash := { expectErrorNotCrash := {
val fail = (compile in Compile).failure.value val fail = (compileIncremental in Compile).failure.value
fail.directCause match { fail.directCause match {
case Some(x: xsbti.CompileFailed) => () case Some(x: xsbti.CompileFailed) => ()
case _ => sys.error("Compiler crashed instead of providing a compile-time-only exception.") case _ => sys.error("Compiler crashed instead of providing a compile-time-only exception.")