From 0f784ab101eca6250cc840f6580753839c85e2ce Mon Sep 17 00:00:00 2001 From: James Roper Date: Tue, 27 May 2014 14:50:39 +1000 Subject: [PATCH 1/9] Allow bytecode enhancement to update analysis This breaks the loading/saving of the incremental compiler analysis out into separate task, thereby providing the necessary hooks for byte code enhancement tasks to enhance bytecode and update the analysis before the analysis gets stored to disk. --- .../sbt/compiler/AggressiveCompile.scala | 30 +++++++-------- .../sbt/compiler/IncrementalCompiler.scala | 13 +++++-- .../actions/src/main/scala/sbt/Compiler.scala | 21 ++++++++--- main/src/main/scala/sbt/Defaults.scala | 37 ++++++++++++++++--- main/src/main/scala/sbt/Keys.scala | 3 ++ 5 files changed, 73 insertions(+), 31 deletions(-) diff --git a/compile/integration/src/main/scala/sbt/compiler/AggressiveCompile.scala b/compile/integration/src/main/scala/sbt/compiler/AggressiveCompile.scala index c4ce04804..8241bd3ed 100644 --- a/compile/integration/src/main/scala/sbt/compiler/AggressiveCompile.scala +++ b/compile/integration/src/main/scala/sbt/compiler/AggressiveCompile.scala @@ -24,7 +24,7 @@ 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) -class AggressiveCompile(cacheFile: File) { +class AggressiveCompile { def apply(compiler: AnalyzingCompiler, javac: xsbti.compile.JavaCompiler, sources: Seq[File], classpath: Seq[File], @@ -33,17 +33,20 @@ class AggressiveCompile(cacheFile: File) { 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)(implicit log: Logger): Analysis = + incrementalCompilerOptions: IncOptions)(implicit log: Logger): (Analysis, CompileSetup, Boolean) = { val setup = new CompileSetup(output, new CompileOptions(options, javacOptions), compiler.scalaInstance.actualVersion, compileOrder, incrementalCompilerOptions.nameHashing) - compile1(sources, classpath, setup, progress, store, analysisMap, definesClass, + val (analysis, modified) = compile1(sources, classpath, setup, progress, previousAnalysis, previousSetup, analysisMap, definesClass, compiler, javac, reporter, skip, cache, incrementalCompilerOptions) + (analysis, setup, modified) } def withBootclasspath(args: CompilerArguments, classpath: Seq[File]): Seq[File] = @@ -52,25 +55,23 @@ class AggressiveCompile(cacheFile: File) { def compile1(sources: Seq[File], classpath: Seq[File], setup: CompileSetup, progress: Option[CompileProgress], - store: AnalysisStore, + 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)(implicit log: Logger): Analysis = + incrementalCompilerOptions: IncOptions)(implicit log: Logger): (Analysis, Boolean) = { - val (previousAnalysis, previousSetup) = extract(store.get(), incrementalCompilerOptions) if (skip) - previousAnalysis + (previousAnalysis, false) else { val config = new CompileConfiguration(sources, classpath, previousAnalysis, previousSetup, setup, progress, analysis, definesClass, reporter, compiler, javac, cache, incrementalCompilerOptions) val (modified, result) = compile2(config) - if (modified) - store.set(result, setup) - result + (result, modified) } } def compile2(config: CompileConfiguration)(implicit log: Logger, equiv: Equiv[CompileSetup]): (Boolean, Analysis) = @@ -176,17 +177,11 @@ class AggressiveCompile(cacheFile: File) { if (!combined.isEmpty) log.info(combined.mkString("Compiling ", " and ", " to " + outputDirs.map(_.getAbsolutePath).mkString(",") + "...")) } - private def extract(previous: Option[(Analysis, CompileSetup)], incOptions: IncOptions): (Analysis, Option[CompileSetup]) = - previous match { - case Some((an, setup)) => (an, Some(setup)) - case None => (Analysis.empty(nameHashing = incOptions.nameHashing), None) - } def javaOnly(f: File) = f.getName.endsWith(".java") private[this] def explicitBootClasspath(options: Seq[String]): Seq[File] = options.dropWhile(_ != CompilerArguments.BootClasspathOption).drop(1).take(1).headOption.toList.flatMap(IO.parseClasspath) - val store = AggressiveCompile.staticCache(cacheFile, AnalysisStore.sync(AnalysisStore.cached(FileBasedStore(cacheFile)))) } object AggressiveCompile { import collection.mutable @@ -200,6 +195,9 @@ object AggressiveCompile { b } } + + def staticCachedStore(cacheFile: File) = staticCache(cacheFile, AnalysisStore.sync(AnalysisStore.cached(FileBasedStore(cacheFile)))) + @deprecated("0.13.8", "Deprecated in favor of new sbt.compiler.javac package.") def directOrFork(instance: ScalaInstance, cpOptions: ClasspathOptions, javaHome: Option[File]): JavaTool = if (javaHome.isDefined) diff --git a/compile/integration/src/main/scala/sbt/compiler/IncrementalCompiler.scala b/compile/integration/src/main/scala/sbt/compiler/IncrementalCompiler.scala index b7a4b6c57..e2f7cd1d1 100644 --- a/compile/integration/src/main/scala/sbt/compiler/IncrementalCompiler.scala +++ b/compile/integration/src/main/scala/sbt/compiler/IncrementalCompiler.scala @@ -12,12 +12,19 @@ object IC extends IncrementalCompiler[Analysis, AnalyzingCompiler] { val setup = in.setup; import setup._ val options = in.options; import options.{ options => scalacOptions, _ } val compilers = in.compilers; import compilers._ - val agg = new AggressiveCompile(setup.cacheFile) + val agg = new AggressiveCompile val aMap = (f: File) => m2o(analysisMap(f)) val defClass = (f: File) => { val dc = definesClass(f); (name: String) => dc.apply(name) } val incOptions = IncOptions.fromStringMap(incrementalCompilerOptions) - agg(scalac, javac, sources, classpath, output, cache, m2o(progress), scalacOptions, javacOptions, aMap, - defClass, reporter, order, skip, incOptions)(log) + val (previousAnalysis, previousSetup) = { + AggressiveCompile.staticCachedStore(setup.cacheFile()).get().map { + case (a, s) => (a, Some(s)) + } getOrElse { + (Analysis.empty(nameHashing = incOptions.nameHashing), None) + } + } + agg(scalac, javac, sources, classpath, output, cache, m2o(progress), scalacOptions, javacOptions, previousAnalysis, + previousSetup, aMap, defClass, reporter, order, skip, incOptions)(log)._1 } private[this] def m2o[S](opt: Maybe[S]): Option[S] = if (opt.isEmpty) None else Some(opt.get) diff --git a/main/actions/src/main/scala/sbt/Compiler.scala b/main/actions/src/main/scala/sbt/Compiler.scala index 672e4105e..0d2ed5ad7 100644 --- a/main/actions/src/main/scala/sbt/Compiler.scala +++ b/main/actions/src/main/scala/sbt/Compiler.scala @@ -15,9 +15,10 @@ import java.io.File object Compiler { val DefaultMaxErrors = 100 - final case class Inputs(compilers: Compilers, config: Options, incSetup: IncSetup) + final case class Inputs(compilers: Compilers, config: Options, incSetup: IncSetup, 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 IncSetup(analysisMap: File => Option[Analysis], definesClass: DefinesClass, skip: Boolean, cacheFile: File, cache: GlobalsCache, incOptions: IncOptions) +<<<<<<< HEAD private[sbt] trait JavaToolWithNewInterface extends JavaTool { def newJavac: IncrementalCompilerJavaTools } @@ -29,12 +30,20 @@ object Compiler { } } final case class NewCompilers(scalac: AnalyzingCompiler, javac: JavaTools) +======= + final case class Compilers(scalac: AnalyzingCompiler, javac: JavaTool) + final case class PreviousAnalysis(analysis: Analysis, setup: Option[CompileSetup]) + final case class AnalysisResult(analysis: Analysis, setup: CompileSetup, modified: Boolean) +>>>>>>> Allow bytecode enhancement to update analysis - 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, previousAnalysis: PreviousAnalysis)(implicit compilers: Compilers, incSetup: IncSetup, log: Logger): Inputs = new Inputs( compilers, new Options(classpath, sources, classesDirectory, options, javacOptions, maxErrors, foldMappers(sourcePositionMappers), order), - incSetup + incSetup, + previousAnalysis ) def compilers(cpOptions: ClasspathOptions)(implicit app: AppConfiguration, log: Logger): Compilers = @@ -79,14 +88,14 @@ object Compiler { val provider = ComponentCompiler.interfaceProvider(componentManager) new AnalyzingCompiler(instance, provider, cpOptions, log) } - def apply(in: Inputs, log: Logger): Analysis = + def apply(in: Inputs, log: Logger): AnalysisResult = { import in.compilers._ import in.config._ import in.incSetup._ apply(in, log, new LoggerReporter(maxErrors, log, sourcePositionMapper)) } - def apply(in: Inputs, log: Logger, reporter: xsbti.Reporter): Analysis = + def apply(in: Inputs, log: Logger, reporter: xsbti.Reporter): AnalysisResult = { import in.compilers._ import in.config._ @@ -98,7 +107,7 @@ object Compiler { val javacChosen: xsbti.compile.JavaCompiler = in.compilers.newJavac.map(_.xsbtiCompiler).getOrElse(in.compilers.javac) agg(scalac, javacChosen, sources, classpath, CompileOutput(classesDirectory), cache, None, options, javacOptions, - analysisMap, definesClass, reporter, order, skip, incOptions)(log) + in.previousAnalysis.analysis, in.previousAnalysis.setup, analysisMap, definesClass, reporter, order, skip, incOptions)(log) } private[sbt] def foldMappers[A](mappers: Seq[A => Option[A]]) = diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 40645e4fe..210db6b3a 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -14,7 +14,8 @@ import Configurations.{ Compile, CompilerPlugin, IntegrationTest, names, Provide import CrossVersion.{ binarySbtVersion, binaryScalaVersion, partialVersion } import complete._ import std.TaskExtra._ -import inc.{ FileValueCache, IncOptions, Locate } +import sbt.inc.{ Analysis, FileValueCache, IncOptions, Locate } +import sbt.compiler.AggressiveCompile import testing.{ Framework, Runner, AnnotatedFingerprint, SubclassFingerprint } import sys.error @@ -246,8 +247,9 @@ object Defaults extends BuildCommon { 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( - compile <<= compileTask tag (Tags.Compile, Tags.CPU), + lazy val configTasks = docTaskSettings(doc) ++ inTask(compile)(compileInputsSettings) ++ configGlobal ++ compileAnalysisSettings ++ Seq( + compile <<= compileTask, + compileIncremental <<= compileIncrementalTask tag (Tags.Compile, Tags.CPU), printWarnings <<= printWarningsTask, compileAnalysisFilename := { // Here, if the user wants cross-scala-versioning, we also append it @@ -778,8 +780,11 @@ object Defaults extends BuildCommon { @deprecated("Use inTask(compile)(compileInputsSettings)", "0.13.0") 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) } - private[this] def compileTaskImpl(s: TaskStreams, ci: Compiler.Inputs, reporter: Option[xsbti.Reporter]): inc.Analysis = + def compileTask: Initialize[Task[inc.Analysis]] = Def.task { saveAnalysis.value } + def compileIncrementalTask = Def.task { + compileIncrementalTaskImpl(streams.value, (compileInputs in compile).value, (compilerReporter in compile).value) + } + private[this] def compileIncrementalTaskImpl(s: TaskStreams, ci: Compiler.Inputs, reporter: Option[xsbti.Reporter]): Compiler.AnalysisResult = { 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"))) @@ -803,9 +808,29 @@ object Defaults extends BuildCommon { def compileInputsSettings: Seq[Setting[_]] = Seq(compileInputs := { 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, readAnalysis.value)(compilers.value, compileIncSetup.value, streams.value.log) }, compilerReporter := None) + def compileAnalysisSettings: Seq[Setting[_]] = Seq( + readAnalysis := { + val setup: Compiler.IncSetup = compileIncSetup.value + val store = AggressiveCompile.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) + } + }, + saveAnalysis := { + val setup: Compiler.IncSetup = compileIncSetup.value + val analysisResult: Compiler.AnalysisResult = compileIncremental.value + if (analysisResult.modified) { + val store = AggressiveCompile.staticCachedStore(setup.cacheFile) + store.set(analysisResult.analysis, analysisResult.setup) + } + analysisResult.analysis + } + ) def printWarningsTask: Initialize[Task[Unit]] = (streams, compile, maxErrors, sourcePositionMappers) map { (s, analysis, max, spms) => diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 8795d0795..cbc27853f 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -141,6 +141,9 @@ object Keys { 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 compile = TaskKey[Analysis]("compile", "Compiles sources.", APlusTask) + val compileIncremental = TaskKey[Compiler.AnalysisResult]("compileIncremental", "Actually runs the incremental compliation", DTask) + val readAnalysis = TaskKey[Compiler.PreviousAnalysis]("readAnalysis", "Read the incremental compiler analysis from disk", DTask) + val saveAnalysis = TaskKey[Analysis]("saveAnalysis", "Save the incremental compiler analysis to disk", 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 compileIncSetup = TaskKey[Compiler.IncSetup]("inc-compile-setup", "Configures aspects of incremental compilation.", DTask) From 40bf599f4a4ac9fac07869c87ac5901cbb8d14f9 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Tue, 4 Nov 2014 11:08:50 -0500 Subject: [PATCH 2/9] Deprecating old APIs and attempting to document behavior correctly. * Removed as many binary incompatibilities as I could find. * Deprecating old APIs * Attempt to construct new nomenclature that fits the design of Incremental API. * Add as much documentation as I was comfortable writing (from my understanding of things). --- .../sbt/compiler/AggressiveCompile.scala | 35 +-- .../sbt/compiler/CompileConfiguration.scala | 41 ++++ .../sbt/compiler/IncrementalCompiler.scala | 16 +- .../sbt/compiler/MixedAnalyzingCompiler.scala | 229 ++++++++++++++++++ .../main/java/xsbti/compile/GlobalsCache.java | 3 + .../actions/src/main/scala/sbt/Compiler.scala | 29 ++- main/src/main/scala/sbt/Defaults.scala | 20 +- main/src/main/scala/sbt/Keys.scala | 3 +- 8 files changed, 334 insertions(+), 42 deletions(-) create mode 100644 compile/integration/src/main/scala/sbt/compiler/CompileConfiguration.scala create mode 100644 compile/integration/src/main/scala/sbt/compiler/MixedAnalyzingCompiler.scala diff --git a/compile/integration/src/main/scala/sbt/compiler/AggressiveCompile.scala b/compile/integration/src/main/scala/sbt/compiler/AggressiveCompile.scala index 8241bd3ed..4aa7a2a22 100644 --- a/compile/integration/src/main/scala/sbt/compiler/AggressiveCompile.scala +++ b/compile/integration/src/main/scala/sbt/compiler/AggressiveCompile.scala @@ -20,11 +20,9 @@ import xsbti.api.Source import xsbti.compile.{ CompileOrder, DependencyChanges, GlobalsCache, Output, SingleOutput, MultipleOutput, CompileProgress } 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 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 { +@deprecated("0.13.8", "Use MixedAnalyzingCompiler instead.") +class AggressiveCompile(cacheFile: File) { + @deprecated("0.13.8", "Use MixedAnalyzingCompiler.analyzingCompile instead.") def apply(compiler: AnalyzingCompiler, javac: xsbti.compile.JavaCompiler, sources: Seq[File], classpath: Seq[File], @@ -33,20 +31,17 @@ class AggressiveCompile { 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)(implicit log: Logger): (Analysis, CompileSetup, Boolean) = + incrementalCompilerOptions: IncOptions)(implicit log: Logger): Analysis = { val setup = new CompileSetup(output, new CompileOptions(options, javacOptions), compiler.scalaInstance.actualVersion, compileOrder, incrementalCompilerOptions.nameHashing) - val (analysis, modified) = compile1(sources, classpath, setup, progress, previousAnalysis, previousSetup, analysisMap, definesClass, + compile1(sources, classpath, setup, progress, store, analysisMap, definesClass, compiler, javac, reporter, skip, cache, incrementalCompilerOptions) - (analysis, setup, modified) } def withBootclasspath(args: CompilerArguments, classpath: Seq[File]): Seq[File] = @@ -55,23 +50,25 @@ class AggressiveCompile { def compile1(sources: Seq[File], classpath: Seq[File], setup: CompileSetup, progress: Option[CompileProgress], - previousAnalysis: Analysis, - previousSetup: Option[CompileSetup], + store: AnalysisStore, analysis: File => Option[Analysis], definesClass: DefinesClass, compiler: AnalyzingCompiler, javac: xsbti.compile.JavaCompiler, reporter: Reporter, skip: Boolean, cache: GlobalsCache, - incrementalCompilerOptions: IncOptions)(implicit log: Logger): (Analysis, Boolean) = + incrementalCompilerOptions: IncOptions)(implicit log: Logger): Analysis = { + val (previousAnalysis, previousSetup) = extract(store.get(), incrementalCompilerOptions) if (skip) - (previousAnalysis, false) + previousAnalysis else { val config = new CompileConfiguration(sources, classpath, previousAnalysis, previousSetup, setup, progress, analysis, definesClass, reporter, compiler, javac, cache, incrementalCompilerOptions) val (modified, result) = compile2(config) - (result, modified) + if (modified) + store.set(result, setup) + result } } def compile2(config: CompileConfiguration)(implicit log: Logger, equiv: Equiv[CompileSetup]): (Boolean, Analysis) = @@ -177,12 +174,20 @@ class AggressiveCompile { if (!combined.isEmpty) log.info(combined.mkString("Compiling ", " and ", " to " + outputDirs.map(_.getAbsolutePath).mkString(",") + "...")) } + private def extract(previous: Option[(Analysis, CompileSetup)], incOptions: IncOptions): (Analysis, Option[CompileSetup]) = + previous match { + case Some((an, setup)) => (an, Some(setup)) + case None => (Analysis.empty(nameHashing = incOptions.nameHashing), None) + } def javaOnly(f: File) = f.getName.endsWith(".java") private[this] def explicitBootClasspath(options: Seq[String]): Seq[File] = options.dropWhile(_ != CompilerArguments.BootClasspathOption).drop(1).take(1).headOption.toList.flatMap(IO.parseClasspath) + val store = AggressiveCompile.staticCache(cacheFile, AnalysisStore.sync(AnalysisStore.cached(FileBasedStore(cacheFile)))) + } +@deprecated("0.13.8", "Use MixedAnalyzingCompiler instead.") object AggressiveCompile { import collection.mutable import java.lang.ref.{ Reference, SoftReference } diff --git a/compile/integration/src/main/scala/sbt/compiler/CompileConfiguration.scala b/compile/integration/src/main/scala/sbt/compiler/CompileConfiguration.scala new file mode 100644 index 000000000..d5e6b0a91 --- /dev/null +++ b/compile/integration/src/main/scala/sbt/compiler/CompileConfiguration.scala @@ -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) diff --git a/compile/integration/src/main/scala/sbt/compiler/IncrementalCompiler.scala b/compile/integration/src/main/scala/sbt/compiler/IncrementalCompiler.scala index e2f7cd1d1..7c13a821f 100644 --- a/compile/integration/src/main/scala/sbt/compiler/IncrementalCompiler.scala +++ b/compile/integration/src/main/scala/sbt/compiler/IncrementalCompiler.scala @@ -6,31 +6,37 @@ import sbt.inc.{ Analysis, IncOptions, TextAnalysisFormat } import xsbti.{ Logger, Maybe } import xsbti.compile._ +/** + * An implementation of the incremetnal compiler that can compile inputs and dump out source dependency analysis. + */ object IC extends IncrementalCompiler[Analysis, AnalyzingCompiler] { def compile(in: Inputs[Analysis, AnalyzingCompiler], log: Logger): Analysis = { val setup = in.setup; import setup._ val options = in.options; import options.{ options => scalacOptions, _ } val compilers = in.compilers; import compilers._ - val agg = new AggressiveCompile val aMap = (f: File) => m2o(analysisMap(f)) val defClass = (f: File) => { val dc = definesClass(f); (name: String) => dc.apply(name) } val incOptions = IncOptions.fromStringMap(incrementalCompilerOptions) val (previousAnalysis, previousSetup) = { - AggressiveCompile.staticCachedStore(setup.cacheFile()).get().map { + MixedAnalyzingCompiler.staticCachedStore(setup.cacheFile()).get().map { case (a, s) => (a, Some(s)) } getOrElse { (Analysis.empty(nameHashing = incOptions.nameHashing), None) } } - agg(scalac, javac, sources, classpath, output, cache, m2o(progress), scalacOptions, javacOptions, previousAnalysis, - previousSetup, aMap, defClass, reporter, order, skip, incOptions)(log)._1 + MixedAnalyzingCompiler.analyzingCompile(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) + @deprecated("0.13.8", "A constructor is no longer needed.") def newScalaCompiler(instance: ScalaInstance, interfaceJar: File, options: ClasspathOptions, log: Logger): AnalyzingCompiler = - new AnalyzingCompiler(instance, CompilerInterfaceProvider.constant(interfaceJar), options, log) + new AnalyzingCompiler(instance, CompilerInterfaceProvider.constant(interfaceJar), options) + + 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) { val raw = new RawCompiler(instance, sbt.ClasspathOptions.auto, log) diff --git a/compile/integration/src/main/scala/sbt/compiler/MixedAnalyzingCompiler.scala b/compile/integration/src/main/scala/sbt/compiler/MixedAnalyzingCompiler.scala new file mode 100644 index 000000000..4fe5729b5 --- /dev/null +++ b/compile/integration/src/main/scala/sbt/compiler/MixedAnalyzingCompiler.scala @@ -0,0 +1,229 @@ +package sbt.compiler + +import java.io.File +import java.lang.ref.{ SoftReference, Reference } + +import sbt.classfile.Analyze +import sbt.classpath.ClasspathUtilities +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._ + +/** + * 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. + */ +object MixedAnalyzingCompiler { + /** 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 + * @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 analyzingCompile(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)(implicit log: Logger): Result = + { + val setup = new CompileSetup(output, new CompileOptions(options, javacOptions), + scalac.scalaInstance.actualVersion, compileOrder, incrementalCompilerOptions.nameHashing) + val (analysis, modified) = compile1(sources, classpath, setup, progress, previousAnalysis, previousSetup, analysisMap, definesClass, + scalac, javac, reporter, skip, cache, incrementalCompilerOptions) + Result(analysis, setup, modified) + } + + def withBootclasspath(args: CompilerArguments, classpath: Seq[File]): Seq[File] = + args.bootClasspathFor(classpath) ++ args.extClasspath ++ args.finishClasspath(classpath) + + /** + * The first level of compilation. This checks to see if we need to skip compiling because of the skip flag, + * IF we're not skipping, this setups up the `CompileConfiguration` and delegates to `compile2` + */ + private def compile1(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)(implicit log: Logger): (Analysis, Boolean) = + { + if (skip) + (previousAnalysis, false) + else { + val config = new CompileConfiguration(sources, classpath, previousAnalysis, previousSetup, setup, + progress, analysis, definesClass, reporter, compiler, javac, cache, incrementalCompilerOptions) + val (modified, result) = compile2(config) + (result, modified) + } + } + /** + * This runs the actual compilation of Java + Scala. There are two helper methods which + * actually perform the compilation of Java or Scala. This method uses the compile order to determine + * which files to pass to which compilers. + */ + private def compile2(config: CompileConfiguration)(implicit log: Logger, equiv: Equiv[CompileSetup]): (Boolean, Analysis) = + { + 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) + val entry = Locate.entry(searchClasspath, definesClass) + + val compile0 = (include: Set[File], changes: DependencyChanges, callback: AnalysisCallback) => { + 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) + def compileScala() = + 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) + } + } + def compileJava() = + if (!javaSrcs.isEmpty) { + import Path._ + @annotation.tailrec def ancestor(f1: File, f2: File): Boolean = + if (f2 eq null) false else if (f1 == f2) true else ancestor(f1, f2.getParentFile) + + val chunks: Map[Option[File], Seq[File]] = output match { + case single: SingleOutput => Map(Some(single.outputDirectory) -> javaSrcs) + case multi: MultipleOutput => + javaSrcs groupBy { src => + multi.outputGroups find { out => ancestor(out.sourceDirectory, src) } map (_.outputDirectory) + } + } + chunks.get(None) foreach { srcs => + log.error("No output directory mapped for: " + srcs.map(_.getAbsolutePath).mkString(",")) + } + val memo = for ((Some(outputDirectory), srcs) <- chunks) yield { + val classesFinder = PathFinder(outputDirectory) ** "*.class" + (classesFinder, classesFinder.get, srcs) + } + + val loader = ClasspathUtilities.toLoader(searchClasspath) + timed("Java compilation", log) { + try javac.compileWithReporter(javaSrcs.toArray, absClasspath.toArray, output, options.javacOptions.toArray, reporter, log) + catch { + // Handle older APIs + case _: NoSuchMethodError => + javac.compile(javaSrcs.toArray, absClasspath.toArray, output, options.javacOptions.toArray, log) + } + } + + def readAPI(source: File, classes: Seq[Class[_]]): Set[String] = { + val (api, inherits) = ClassToAPI.process(classes) + callback.api(source, api) + inherits.map(_.getName) + } + + timed("Java analysis", log) { + for ((classesFinder, oldClasses, srcs) <- memo) { + val newClasses = Set(classesFinder.get: _*) -- oldClasses + Analyze(newClasses.toSeq, srcs, log)(callback, loader, readAPI) + } + } + } + if (order == JavaThenScala) { compileJava(); compileScala() } else { compileScala(); compileJava() } + } + + 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) + } + IncrementalCompile(sourcesSet, entry, compile0, analysis, getAnalysis, output, log, incOptions) + } + private[this] def outputDirectories(output: Output): Seq[File] = output match { + case single: SingleOutput => List(single.outputDirectory) + case mult: MultipleOutput => mult.outputGroups map (_.outputDirectory) + } + 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. */ + def javaOnly(f: File) = f.getName.endsWith(".java") + + 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)))) + +} diff --git a/interface/src/main/java/xsbti/compile/GlobalsCache.java b/interface/src/main/java/xsbti/compile/GlobalsCache.java index a3a412836..d9aa1c017 100644 --- a/interface/src/main/java/xsbti/compile/GlobalsCache.java +++ b/interface/src/main/java/xsbti/compile/GlobalsCache.java @@ -3,6 +3,9 @@ package xsbti.compile; import xsbti.Logger; import xsbti.Reporter; +/** + * An interface which lets us know how to retrieve cached compiler instances form the current JVM. + */ public interface GlobalsCache { public CachedCompiler apply(String[] args, Output output, boolean forceNew, CachedCompilerProvider provider, Logger log, Reporter reporter); diff --git a/main/actions/src/main/scala/sbt/Compiler.scala b/main/actions/src/main/scala/sbt/Compiler.scala index 0d2ed5ad7..ff3f28f65 100644 --- a/main/actions/src/main/scala/sbt/Compiler.scala +++ b/main/actions/src/main/scala/sbt/Compiler.scala @@ -18,10 +18,10 @@ object Compiler { final case class Inputs(compilers: Compilers, config: Options, incSetup: IncSetup, 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 IncSetup(analysisMap: File => Option[Analysis], definesClass: DefinesClass, skip: Boolean, cacheFile: File, cache: GlobalsCache, incOptions: IncOptions) -<<<<<<< HEAD private[sbt] trait JavaToolWithNewInterface extends JavaTool { def newJavac: IncrementalCompilerJavaTools } + /** The instances of Scalac/Javac used to compile the current project. */ final case class Compilers(scalac: AnalyzingCompiler, javac: JavaTool) { final def newJavac: Option[IncrementalCompilerJavaTools] = javac match { @@ -29,12 +29,9 @@ object Compiler { case _ => None } } - final case class NewCompilers(scalac: AnalyzingCompiler, javac: JavaTools) -======= - final case class Compilers(scalac: AnalyzingCompiler, javac: JavaTool) + /** The previous source dependency analysis result from compilation. */ final case class PreviousAnalysis(analysis: Analysis, setup: Option[CompileSetup]) - final case class AnalysisResult(analysis: Analysis, setup: CompileSetup, modified: Boolean) ->>>>>>> Allow bytecode enhancement to update analysis + type CompileResult = MixedAnalyzingCompiler.Result def inputs(classpath: Seq[File], sources: Seq[File], classesDirectory: File, options: Seq[String], javacOptions: Seq[String], maxErrors: Int, sourcePositionMappers: Seq[Position => Option[Position]], @@ -86,27 +83,37 @@ object Compiler { val launcher = app.provider.scalaProvider.launcher val componentManager = new ComponentManager(launcher.globalLock, app.provider.components, Option(launcher.ivyHome), log) val provider = ComponentCompiler.interfaceProvider(componentManager) - new AnalyzingCompiler(instance, provider, cpOptions, log) + new AnalyzingCompiler(instance, provider, cpOptions) } - def apply(in: Inputs, log: Logger): AnalysisResult = + + @deprecated("0.13.8", "Use the `compile` method instead.") + def apply(in: Inputs, log: Logger): Analysis = { import in.compilers._ import in.config._ import in.incSetup._ apply(in, log, new LoggerReporter(maxErrors, log, sourcePositionMapper)) } - def apply(in: Inputs, log: Logger, reporter: xsbti.Reporter): AnalysisResult = + @deprecated("0.13.8", "Use the `compile` method instead.") + def apply(in: Inputs, log: Logger, reporter: xsbti.Reporter): Analysis = compile(in, log, reporter).analysis + def compile(in: Inputs, log: Logger): CompileResult = + { + import in.compilers._ + import in.config._ + import in.incSetup._ + compile(in, log, new LoggerReporter(maxErrors, log, sourcePositionMapper)) + } + def compile(in: Inputs, log: Logger, reporter: xsbti.Reporter): CompileResult = { import in.compilers._ import in.config._ import in.incSetup._ - val agg = new AggressiveCompile(cacheFile) // Here is some trickery to choose the more recent (reporter-using) java compiler rather // than the previously defined versions. // TODO - Remove this hackery in sbt 1.0. val javacChosen: xsbti.compile.JavaCompiler = in.compilers.newJavac.map(_.xsbtiCompiler).getOrElse(in.compilers.javac) - agg(scalac, javacChosen, sources, classpath, CompileOutput(classesDirectory), cache, None, options, javacOptions, + MixedAnalyzingCompiler.analyzingCompile(scalac, javacChosen, sources, classpath, CompileOutput(classesDirectory), cache, None, options, javacOptions, in.previousAnalysis.analysis, in.previousAnalysis.setup, analysisMap, definesClass, reporter, order, skip, incOptions)(log) } diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 210db6b3a..8f0b2b7e0 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -15,7 +15,7 @@ import CrossVersion.{ binarySbtVersion, binaryScalaVersion, partialVersion } import complete._ import std.TaskExtra._ import sbt.inc.{ Analysis, FileValueCache, IncOptions, Locate } -import sbt.compiler.AggressiveCompile +import sbt.compiler.{ MixedAnalyzingCompiler, AggressiveCompile } import testing.{ Framework, Runner, AnnotatedFingerprint, SubclassFingerprint } import sys.error @@ -650,7 +650,7 @@ object Defaults extends BuildCommon { key in TaskGlobal <<= packageTask, packageConfiguration <<= packageConfigurationTask, mappings <<= mappingsTask, - packagedArtifact := (artifact.value, key.value), + packagedArtifact := (artifact.value -> key.value), artifact <<= artifactSetting, artifactPath <<= artifactPathSetting(artifact) )) @@ -784,14 +784,14 @@ object Defaults extends BuildCommon { def compileIncrementalTask = Def.task { compileIncrementalTaskImpl(streams.value, (compileInputs in compile).value, (compilerReporter in compile).value) } - private[this] def compileIncrementalTaskImpl(s: TaskStreams, ci: Compiler.Inputs, reporter: Option[xsbti.Reporter]): Compiler.AnalysisResult = + private[this] def compileIncrementalTaskImpl(s: TaskStreams, ci: Compiler.Inputs, reporter: Option[xsbti.Reporter]): Compiler.CompileResult = { 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"))) val i = ci.copy(compilers = onArgs(ci.compilers)) try reporter match { - case Some(reporter) => Compiler(i, s.log, reporter) - case None => Compiler(i, s.log) + case Some(reporter) => Compiler.compile(i, s.log, reporter) + case None => Compiler.compile(i, s.log) } finally x.close() // workaround for #937 } @@ -823,9 +823,9 @@ object Defaults extends BuildCommon { }, saveAnalysis := { val setup: Compiler.IncSetup = compileIncSetup.value - val analysisResult: Compiler.AnalysisResult = compileIncremental.value - if (analysisResult.modified) { - val store = AggressiveCompile.staticCachedStore(setup.cacheFile) + val analysisResult: Compiler.CompileResult = compileIncremental.value + if (analysisResult.hasModified) { + val store = MixedAnalyzingCompiler.staticCachedStore(setup.cacheFile) store.set(analysisResult.analysis, analysisResult.setup) } analysisResult.analysis @@ -1031,7 +1031,7 @@ object Classpaths { packagedArtifacts :== Map.empty, crossTarget := target.value, 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), deliverLocal <<= deliverTask(deliverLocalConfiguration), publish <<= publishTask(publishConfiguration, deliver), @@ -1510,7 +1510,7 @@ object Classpaths { def visit(p: ProjectRef, c: Configuration) { val applicableConfigs = allConfigs(c) 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)) for (ResolvedClasspathDependency(dep, confMapping) <- deps.classpath(p)) { diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index cbc27853f..53ce20f87 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -9,6 +9,7 @@ import Def.ScopedKey import complete._ import inc.Analysis import inc.Locate.DefinesClass +import sbt.compiler.MixedAnalyzingCompiler import std.TaskExtra._ import xsbti.compile.{ CompileOrder, GlobalsCache } import scala.xml.{ Node => XNode, NodeSeq } @@ -141,7 +142,7 @@ object Keys { 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 compile = TaskKey[Analysis]("compile", "Compiles sources.", APlusTask) - val compileIncremental = TaskKey[Compiler.AnalysisResult]("compileIncremental", "Actually runs the incremental compliation", DTask) + val compileIncremental = TaskKey[Compiler.CompileResult]("compileIncremental", "Actually runs the incremental compliation", DTask) val readAnalysis = TaskKey[Compiler.PreviousAnalysis]("readAnalysis", "Read the incremental compiler analysis from disk", DTask) val saveAnalysis = TaskKey[Analysis]("saveAnalysis", "Save the incremental compiler analysis to disk", DTask) val compilers = TaskKey[Compiler.Compilers]("compilers", "Defines the Scala and Java compilers to use for compilation.", DTask) From 045ac1d9840f7acfc173d7d7eba1fa3009cfb024 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Tue, 4 Nov 2014 12:38:09 -0500 Subject: [PATCH 3/9] Tweak remaining binary compatibility issues with design. --- .../sbt/compiler/AggressiveCompile.scala | 17 ++---- .../sbt/compiler/IncrementalCompiler.scala | 2 +- .../actions/src/main/scala/sbt/Compiler.scala | 53 +++++++++++-------- main/src/main/scala/sbt/Defaults.scala | 11 ++-- 4 files changed, 42 insertions(+), 41 deletions(-) diff --git a/compile/integration/src/main/scala/sbt/compiler/AggressiveCompile.scala b/compile/integration/src/main/scala/sbt/compiler/AggressiveCompile.scala index 4aa7a2a22..17d584320 100644 --- a/compile/integration/src/main/scala/sbt/compiler/AggressiveCompile.scala +++ b/compile/integration/src/main/scala/sbt/compiler/AggressiveCompile.scala @@ -184,24 +184,13 @@ class AggressiveCompile(cacheFile: File) { private[this] def explicitBootClasspath(options: Seq[String]): Seq[File] = 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 { - import collection.mutable - import java.lang.ref.{ Reference, SoftReference } - private[this] val cache = new collection.mutable.HashMap[File, Reference[AnalysisStore]] - private def staticCache(file: File, backing: => AnalysisStore): AnalysisStore = - synchronized { - cache get file flatMap { ref => Option(ref.get) } getOrElse { - val b = backing - cache.put(file, new SoftReference(b)) - b - } - } - - def staticCachedStore(cacheFile: File) = staticCache(cacheFile, AnalysisStore.sync(AnalysisStore.cached(FileBasedStore(cacheFile)))) + @deprecated("0.13.8", "Use MixedAnalyzingCompiler.staticCachedStore instead.") + def staticCachedStore(cacheFile: File) = MixedAnalyzingCompiler.staticCachedStore(cacheFile) @deprecated("0.13.8", "Deprecated in favor of new sbt.compiler.javac package.") def directOrFork(instance: ScalaInstance, cpOptions: ClasspathOptions, javaHome: Option[File]): JavaTool = diff --git a/compile/integration/src/main/scala/sbt/compiler/IncrementalCompiler.scala b/compile/integration/src/main/scala/sbt/compiler/IncrementalCompiler.scala index 7c13a821f..845b0f45e 100644 --- a/compile/integration/src/main/scala/sbt/compiler/IncrementalCompiler.scala +++ b/compile/integration/src/main/scala/sbt/compiler/IncrementalCompiler.scala @@ -31,7 +31,7 @@ object IC extends IncrementalCompiler[Analysis, AnalyzingCompiler] { private[this] def m2o[S](opt: Maybe[S]): Option[S] = if (opt.isEmpty) None else Some(opt.get) - @deprecated("0.13.8", "A constructor is no longer needed.") + @deprecated("0.13.8", "A logger is no longer needed.") def newScalaCompiler(instance: ScalaInstance, interfaceJar: File, options: ClasspathOptions, log: Logger): AnalyzingCompiler = new AnalyzingCompiler(instance, CompilerInterfaceProvider.constant(interfaceJar), options) diff --git a/main/actions/src/main/scala/sbt/Compiler.scala b/main/actions/src/main/scala/sbt/Compiler.scala index ff3f28f65..3d10bf688 100644 --- a/main/actions/src/main/scala/sbt/Compiler.scala +++ b/main/actions/src/main/scala/sbt/Compiler.scala @@ -15,7 +15,10 @@ import java.io.File object Compiler { val DefaultMaxErrors = 100 - final case class Inputs(compilers: Compilers, config: Options, incSetup: IncSetup, previousAnalysis: PreviousAnalysis) + /** Inputs necessary to run the incremental compiler. */ + 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 IncSetup(analysisMap: File => Option[Analysis], definesClass: DefinesClass, skip: Boolean, cacheFile: File, cache: GlobalsCache, incOptions: IncOptions) private[sbt] trait JavaToolWithNewInterface extends JavaTool { @@ -35,12 +38,11 @@ object Compiler { 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, previousAnalysis: PreviousAnalysis)(implicit compilers: Compilers, incSetup: IncSetup, log: Logger): Inputs = + order: CompileOrder)(implicit compilers: Compilers, incSetup: IncSetup, log: Logger): Inputs = new Inputs( compilers, new Options(classpath, sources, classesDirectory, options, javacOptions, maxErrors, foldMappers(sourcePositionMappers), order), - incSetup, - previousAnalysis + incSetup ) def compilers(cpOptions: ClasspathOptions)(implicit app: AppConfiguration, log: Logger): Compilers = @@ -87,32 +89,41 @@ object Compiler { } @deprecated("0.13.8", "Use the `compile` method instead.") - def apply(in: Inputs, log: Logger): Analysis = - { - import in.compilers._ - import in.config._ - import in.incSetup._ - apply(in, log, new LoggerReporter(maxErrors, log, sourcePositionMapper)) - } + def apply(in: Inputs, log: Logger): Analysis = { + import in.config._ + 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 = compile(in, log, reporter).analysis - def compile(in: Inputs, log: Logger): CompileResult = + 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) + } + } + compile(InputsWithPrevious(in, PreviousAnalysis(previousAnalysis, previousSetup)), log, reporter).analysis + } + def compile(in: InputsWithPrevious, log: Logger): CompileResult = { - import in.compilers._ - import in.config._ - import in.incSetup._ + import in.inputs.config._ compile(in, log, new LoggerReporter(maxErrors, log, sourcePositionMapper)) } - def compile(in: Inputs, log: Logger, reporter: xsbti.Reporter): CompileResult = + def compile(in: InputsWithPrevious, log: Logger, reporter: xsbti.Reporter): CompileResult = { - import in.compilers._ - import in.config._ - import in.incSetup._ + 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 // than the previously defined versions. // TODO - Remove this hackery in sbt 1.0. 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) + // TODO - Why are we not using the IC interface??? MixedAnalyzingCompiler.analyzingCompile(scalac, javacChosen, sources, classpath, CompileOutput(classesDirectory), cache, None, options, javacOptions, in.previousAnalysis.analysis, in.previousAnalysis.setup, analysisMap, definesClass, reporter, order, skip, incOptions)(log) } diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 8f0b2b7e0..47cf18d6e 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -5,6 +5,7 @@ package sbt import Attributed.data import Scope.{ fillTaskAxis, GlobalScope, ThisScope } +import sbt.Compiler.InputsWithPrevious import xsbt.api.Discovery import xsbti.compile.CompileOrder import Project.{ inConfig, inScope, inTask, richInitialize, richInitializeTask, richTaskSessionVar } @@ -782,13 +783,13 @@ object Defaults extends BuildCommon { def compileTask: Initialize[Task[inc.Analysis]] = Def.task { saveAnalysis.value } def compileIncrementalTask = Def.task { - compileIncrementalTaskImpl(streams.value, (compileInputs in compile).value, (compilerReporter in compile).value) + compileIncrementalTaskImpl(streams.value, (compileInputs in compile).value, (readAnalysis in compile).value, (compilerReporter in compile).value) } - private[this] def compileIncrementalTaskImpl(s: TaskStreams, ci: Compiler.Inputs, reporter: Option[xsbti.Reporter]): Compiler.CompileResult = + private[this] def compileIncrementalTaskImpl(s: TaskStreams, ci: Compiler.Inputs, previous: Compiler.PreviousAnalysis, reporter: Option[xsbti.Reporter]): Compiler.CompileResult = { 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"))) - val i = ci.copy(compilers = onArgs(ci.compilers)) + val i = InputsWithPrevious(ci.copy(compilers = onArgs(ci.compilers)), previous) try reporter match { case Some(reporter) => Compiler.compile(i, s.log, reporter) case None => Compiler.compile(i, s.log) @@ -809,13 +810,13 @@ object Defaults extends BuildCommon { Seq(compileInputs := { val cp = classDirectory.value +: data(dependencyClasspath.value) Compiler.inputs(cp, sources.value, classDirectory.value, scalacOptions.value, javacOptions.value, - maxErrors.value, sourcePositionMappers.value, compileOrder.value, readAnalysis.value)(compilers.value, compileIncSetup.value, streams.value.log) + maxErrors.value, sourcePositionMappers.value, compileOrder.value)(compilers.value, compileIncSetup.value, streams.value.log) }, compilerReporter := None) def compileAnalysisSettings: Seq[Setting[_]] = Seq( readAnalysis := { val setup: Compiler.IncSetup = compileIncSetup.value - val store = AggressiveCompile.staticCachedStore(setup.cacheFile) + 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) From 067479f59e141a82bcb5be0763b248f934807b64 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Tue, 4 Nov 2014 15:07:52 -0500 Subject: [PATCH 4/9] Debug issues with implicit usage for CompileSetup. * Force CompileSetup Equiv typeclass to use Equiv relations defined locally. * Add toString methods on many of the incremental compiler datatypes. * Remove remaining binary compatibility issues in Defaults.scala. --- .../inc/src/main/scala/sbt/CompileSetup.scala | 40 ++++++++++++++----- .../sbt/compiler/MixedAnalyzingCompiler.scala | 4 ++ .../xsbt/ScalaCompilerForUnitTesting.scala | 1 + .../main/scala/sbt/inc/AnalysisFormats.scala | 8 +++- .../scala/sbt/inc/TextAnalysisFormat.scala | 2 + .../scala/sbt/compiler/CompilerOutput.scala | 4 ++ main/src/main/scala/sbt/Defaults.scala | 3 +- 7 files changed, 50 insertions(+), 12 deletions(-) diff --git a/compile/inc/src/main/scala/sbt/CompileSetup.scala b/compile/inc/src/main/scala/sbt/CompileSetup.scala index 338f7d810..5d5a0823b 100644 --- a/compile/inc/src/main/scala/sbt/CompileSetup.scala +++ b/compile/inc/src/main/scala/sbt/CompileSetup.scala @@ -10,24 +10,41 @@ import java.io.File // We cannot require an implicit parameter Equiv[Seq[String]] to construct Equiv[CompileSetup] // because complexity(Equiv[Seq[String]]) > complexity(Equiv[CompileSetup]) // (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, val order: CompileOrder, val nameHashing: Boolean) { @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) = { this(output, options, compilerVersion, order, false) } + override def toString = s"""CompileSetup( + | options = $options + | compilerVersion = $compilerVersion + | order = $order + | nameHashing = $nameHashing + | output = $output + |)""".stripMargin } object 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] { - def equiv(a: CompileSetup, b: CompileSetup) = - equivOutput.equiv(a.output, b.output) && - equivOpts.equiv(a.options, b.options) && - equivComp.equiv(a.compilerVersion, b.compilerVersion) && - a.order == b.order && // equivOrder.equiv(a.order, b.order) - a.nameHashing == b.nameHashing + def equiv(a: CompileSetup, b: CompileSetup) = { + // For some reason, an Equiv[Nothing] or some such is getting injected into here now, and borking all our results. + // We hardcode these to use the Equiv defined in this class. + def sameOutput = CompileSetup.equivOutput.equiv(a.output, b.output) + def sameOptions = CompileSetup.equivOpts.equiv(a.options, b.options) + 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] { def equiv(a: File, b: File) = a.getAbsoluteFile == b.getAbsoluteFile @@ -41,14 +58,17 @@ object CompileSetup { case (a, b) => equivFile.equiv(a.sourceDirectory, b.sourceDirectory) && equivFile.equiv(a.outputDirectory, b.outputDirectory) }) - case (s1: SingleOutput, s2: SingleOutput) => equivFile.equiv(s1.outputDirectory, s2.outputDirectory) - case _ => false + case (s1: SingleOutput, s2: SingleOutput) => + equivFile.equiv(s1.outputDirectory, s2.outputDirectory) + case _ => + false } } 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.javacOptions sameElements b.javacOptions) + } } implicit val equivCompilerVersion: Equiv[String] = new Equiv[String] { def equiv(a: String, b: String) = a == b diff --git a/compile/integration/src/main/scala/sbt/compiler/MixedAnalyzingCompiler.scala b/compile/integration/src/main/scala/sbt/compiler/MixedAnalyzingCompiler.scala index 4fe5729b5..83b160600 100644 --- a/compile/integration/src/main/scala/sbt/compiler/MixedAnalyzingCompiler.scala +++ b/compile/integration/src/main/scala/sbt/compiler/MixedAnalyzingCompiler.scala @@ -17,6 +17,9 @@ import xsbti.compile._ /** * 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 is actually the core logic of the incremental compiler for sbt. */ object MixedAnalyzingCompiler { /** The result of running the compilation. */ @@ -90,6 +93,7 @@ object MixedAnalyzingCompiler { cache: GlobalsCache, incrementalCompilerOptions: IncOptions)(implicit log: Logger): (Analysis, Boolean) = { + import CompileSetup._ if (skip) (previousAnalysis, false) else { diff --git a/compile/interface/src/test/scala/xsbt/ScalaCompilerForUnitTesting.scala b/compile/interface/src/test/scala/xsbt/ScalaCompilerForUnitTesting.scala index 926be962f..33ce99a8f 100644 --- a/compile/interface/src/test/scala/xsbt/ScalaCompilerForUnitTesting.scala +++ b/compile/interface/src/test/scala/xsbt/ScalaCompilerForUnitTesting.scala @@ -149,6 +149,7 @@ class ScalaCompilerForUnitTesting(nameHashing: Boolean = false) { val args = Array.empty[String] object output extends SingleOutput { def outputDirectory: File = outputDir + override def toString = s"SingleOutput($outputDirectory)" } val weakLog = new WeakLog(ConsoleLogger(), ConsoleReporter) val cachedCompiler = new CachedCompiler0(args, output, weakLog, false) diff --git a/compile/persist/src/main/scala/sbt/inc/AnalysisFormats.scala b/compile/persist/src/main/scala/sbt/inc/AnalysisFormats.scala index 1af8278aa..b1add862c 100644 --- a/compile/persist/src/main/scala/sbt/inc/AnalysisFormats.scala +++ b/compile/persist/src/main/scala/sbt/inc/AnalysisFormats.scala @@ -76,7 +76,13 @@ object AnalysisFormats { implicit val multipleOutputFormat: Format[MultipleOutput] = wrap[MultipleOutput, Array[OutputGroup]]( (_.outputGroups), - { groups => new MultipleOutput { def outputGroups = groups } } + { + groups => + new MultipleOutput { + def outputGroups = groups + override def toString = s"MultipleOutput($outputGroups)" + } + } ) implicit val singleOutputFormat: Format[SingleOutput] = wrap[SingleOutput, File]( diff --git a/compile/persist/src/main/scala/sbt/inc/TextAnalysisFormat.scala b/compile/persist/src/main/scala/sbt/inc/TextAnalysisFormat.scala index 3749298ea..57da4fabc 100644 --- a/compile/persist/src/main/scala/sbt/inc/TextAnalysisFormat.scala +++ b/compile/persist/src/main/scala/sbt/inc/TextAnalysisFormat.scala @@ -308,8 +308,10 @@ object TextAnalysisFormat { case (src: File, out: File) => new MultipleOutput.OutputGroup { val sourceDirectory = src 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) } diff --git a/compile/src/main/scala/sbt/compiler/CompilerOutput.scala b/compile/src/main/scala/sbt/compiler/CompilerOutput.scala index 66aee7367..a69164450 100755 --- a/compile/src/main/scala/sbt/compiler/CompilerOutput.scala +++ b/compile/src/main/scala/sbt/compiler/CompilerOutput.scala @@ -8,9 +8,11 @@ package compiler import xsbti.compile.{ Output, SingleOutput, MultipleOutput } import java.io.File +/** Constructor for the `Output` ADT for incremental compiler. Can either take groups (src -> out) or a single output. */ object CompileOutput { def apply(dir: File): Output = new SingleOutput { def outputDirectory = dir + override def toString = s"SingleOutput($outputDirectory)" } def apply(groups: (File, File)*): Output = new MultipleOutput { @@ -18,7 +20,9 @@ object CompileOutput { case (src, out) => new MultipleOutput.OutputGroup { def sourceDirectory = src def outputDirectory = out + override def toString = s"OutputGroup($src -> $out)" } } + override def toString = s"MultiOutput($outputGroups)" } } diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 47cf18d6e..1d2ba94df 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -783,7 +783,8 @@ object Defaults extends BuildCommon { def compileTask: Initialize[Task[inc.Analysis]] = Def.task { saveAnalysis.value } def compileIncrementalTask = Def.task { - compileIncrementalTaskImpl(streams.value, (compileInputs in compile).value, (readAnalysis in compile).value, (compilerReporter in compile).value) + // TODO - Should readAnalysis + saveAnalysis be scoped by the compile task too? + compileIncrementalTaskImpl(streams.value, (compileInputs in compile).value, readAnalysis.value, (compilerReporter in compile).value) } private[this] def compileIncrementalTaskImpl(s: TaskStreams, ci: Compiler.Inputs, previous: Compiler.PreviousAnalysis, reporter: Option[xsbti.Reporter]): Compiler.CompileResult = { From fa57346a6630bfea1f200b1eb9dedfcaae9c9051 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Wed, 5 Nov 2014 09:24:42 -0500 Subject: [PATCH 5/9] Fix scripted test to understand new compile/compileIncremental task switch. --- sbt/src/sbt-test/actions/compile-time-only/build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sbt/src/sbt-test/actions/compile-time-only/build.sbt b/sbt/src/sbt-test/actions/compile-time-only/build.sbt index a81a081e4..5baf27554 100644 --- a/sbt/src/sbt-test/actions/compile-time-only/build.sbt +++ b/sbt/src/sbt-test/actions/compile-time-only/build.sbt @@ -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.") expectErrorNotCrash := { - val fail = (compile in Compile).failure.value + val fail = (compileIncremental in Compile).failure.value fail.directCause match { case Some(x: xsbti.CompileFailed) => () case _ => sys.error("Compiler crashed instead of providing a compile-time-only exception.") From ae4721aba0e32db46ba115ce3193da263f676239 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Wed, 5 Nov 2014 10:03:05 -0500 Subject: [PATCH 6/9] Adding more scaladoc to incremental compiler library. --- .../inc/src/main/scala/sbt/inc/Compile.scala | 26 +++++++++++++ .../src/main/scala/sbt/inc/Incremental.scala | 21 ++++++++++ .../sbt/compiler/MixedAnalyzingCompiler.scala | 38 +++++++++++++++---- 3 files changed, 78 insertions(+), 7 deletions(-) diff --git a/compile/inc/src/main/scala/sbt/inc/Compile.scala b/compile/inc/src/main/scala/sbt/inc/Compile.scala index dd2dc4223..532698f20 100644 --- a/compile/inc/src/main/scala/sbt/inc/Compile.scala +++ b/compile/inc/src/main/scala/sbt/inc/Compile.scala @@ -11,7 +11,33 @@ import Logger.{ m2o, problem } import java.io.File import xsbti.api.Definition +/** + * 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 { + /** + * 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], compile: (Set[File], DependencyChanges, xsbti.AnalysisCallback) => Unit, previous: Analysis, diff --git a/compile/inc/src/main/scala/sbt/inc/Incremental.scala b/compile/inc/src/main/scala/sbt/inc/Incremental.scala index 7973cbf7a..9dfa04a64 100644 --- a/compile/inc/src/main/scala/sbt/inc/Incremental.scala +++ b/compile/inc/src/main/scala/sbt/inc/Incremental.scala @@ -10,7 +10,28 @@ import xsbti.api.{ Compilation, Source } import xsbti.compile.DependencyChanges import java.io.File +/** Helper class to run incremental compilation algorithm. + * + * + * This class delegates down to + * - IncrementalNameHashing + * - IncrementalDefault + * - IncrementalAnyStyle + */ 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], entry: String => Option[File], previous: Analysis, diff --git a/compile/integration/src/main/scala/sbt/compiler/MixedAnalyzingCompiler.scala b/compile/integration/src/main/scala/sbt/compiler/MixedAnalyzingCompiler.scala index 83b160600..cfe98b6c7 100644 --- a/compile/integration/src/main/scala/sbt/compiler/MixedAnalyzingCompiler.scala +++ b/compile/integration/src/main/scala/sbt/compiler/MixedAnalyzingCompiler.scala @@ -19,7 +19,8 @@ import xsbti.compile._ * with a `xsbti.JavaCompiler`, allowing cross-compilation of mixed Java/Scala projects with analysis output. * * - * NOTE: this is actually the core logic of the incremental compiler for sbt. + * 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 { /** The result of running the compilation. */ @@ -118,13 +119,24 @@ object MixedAnalyzingCompiler { val searchClasspath = explicitBootClasspath(options.options) ++ withBootclasspath(cArgs, absClasspath) val entry = Locate.entry(searchClasspath, definesClass) + /** Here we set up a function that can run *one* iteration of the incremental compiler. + * + * Given: + * A set of files to compile + * A set of dependency changes + * A callback for analysis results + * + * Run: + * Scalac + Javac on the set of files, the order of which is determined by the current CompileConfiguration. + */ val compile0 = (include: Set[File], changes: DependencyChanges, callback: AnalysisCallback) => { 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) - def compileScala() = + /** 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) @@ -132,12 +144,17 @@ object MixedAnalyzingCompiler { compiler.compile(sources, changes, arguments, output, callback, reporter, config.cache, log, progress) } } - def compileJava() = + /** Compiles the Java code necessary. All analysis code is included in this method. + * + * TODO - much of this logic should be extracted into an "AnalyzingJavaCompiler" class. + */ + def compileJava(): Unit = if (!javaSrcs.isEmpty) { import Path._ @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) -> javaSrcs) case multi: MultipleOutput => @@ -145,14 +162,16 @@ object MixedAnalyzingCompiler { 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) timed("Java compilation", log) { try javac.compileWithReporter(javaSrcs.toArray, absClasspath.toArray, output, options.javacOptions.toArray, reporter, log) @@ -162,13 +181,13 @@ object MixedAnalyzingCompiler { javac.compile(javaSrcs.toArray, absClasspath.toArray, output, options.javacOptions.toArray, log) } } - + /** 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 @@ -176,9 +195,12 @@ object MixedAnalyzingCompiler { } } } + // TODO - Maybe on "Mixed" we should try to compile both Scala + Java. if (order == JavaThenScala) { compileJava(); compileScala() } else { compileScala(); compileJava() } } + // Here we check to see if any previous compilation results are invalid, based on altered + // javac/scalac options. val sourcesSet = sources.toSet val analysis = previousSetup match { case Some(previous) if previous.nameHashing != currentSetup.nameHashing => @@ -190,12 +212,14 @@ object MixedAnalyzingCompiler { case Some(previous) if equiv.equiv(previous, currentSetup) => previousAnalysis case _ => Incremental.prune(sourcesSet, previousAnalysis) } + // Run the incremental compiler using the large compile0 "compile step" we've defined. IncrementalCompile(sourcesSet, entry, compile0, analysis, getAnalysis, output, log, incOptions) } 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 From 3c4bc23cdb491b79abbebc4ebc9294a55e949648 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Wed, 5 Nov 2014 14:31:26 -0500 Subject: [PATCH 7/9] First set of refactorings from review. * Split Java analyzing compile into its own class. * MixedAnalyzingCompiler now only does the mixing * Start moving methods around to more-final locations * Static analyzingCompile method now constructs a MixedAnalyzingCOmpiler and delegates to incremental compile. --- .../src/main/scala/sbt/inc/Incremental.scala | 44 ++-- .../sbt/compiler/IncrementalCompiler.scala | 15 +- .../sbt/compiler/MixedAnalyzingCompiler.scala | 190 ++++++++---------- .../javac/AnalyzingJavaCompiler.scala | 108 ++++++++++ .../java/xsbti/compile/CompileProgress.java | 5 + .../xsbti/compile/IncrementalCompiler.java | 10 + project/Sbt.scala | 4 +- 7 files changed, 247 insertions(+), 129 deletions(-) create mode 100644 compile/src/main/scala/sbt/compiler/javac/AnalyzingJavaCompiler.scala diff --git a/compile/inc/src/main/scala/sbt/inc/Incremental.scala b/compile/inc/src/main/scala/sbt/inc/Incremental.scala index 9dfa04a64..fa0484206 100644 --- a/compile/inc/src/main/scala/sbt/inc/Incremental.scala +++ b/compile/inc/src/main/scala/sbt/inc/Incremental.scala @@ -10,28 +10,30 @@ import xsbti.api.{ Compilation, Source } import xsbti.compile.DependencyChanges import java.io.File -/** Helper class to run incremental compilation algorithm. - * - * - * This class delegates down to - * - IncrementalNameHashing - * - IncrementalDefault - * - IncrementalAnyStyle - */ +/** + * Helper class to run incremental compilation algorithm. + * + * + * This class delegates down to + * - IncrementalNameHashing + * - IncrementalDefault + * - IncrementalAnyStyle + */ 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. - */ + /** + * 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], entry: String => Option[File], previous: Analysis, diff --git a/compile/integration/src/main/scala/sbt/compiler/IncrementalCompiler.scala b/compile/integration/src/main/scala/sbt/compiler/IncrementalCompiler.scala index 845b0f45e..2100c4333 100644 --- a/compile/integration/src/main/scala/sbt/compiler/IncrementalCompiler.scala +++ b/compile/integration/src/main/scala/sbt/compiler/IncrementalCompiler.scala @@ -6,11 +6,18 @@ import sbt.inc.{ Analysis, IncOptions, TextAnalysisFormat } import xsbti.{ Logger, Maybe } 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 incremetnal compiler that can compile inputs and dump out source dependency analysis. + * An implementation of the incremental compiler that can compile inputs and dump out source dependency analysis. */ 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 options = in.options; import options.{ options => scalacOptions, _ } @@ -32,10 +39,10 @@ object IC extends IncrementalCompiler[Analysis, AnalyzingCompiler] { private[this] def m2o[S](opt: Maybe[S]): Option[S] = if (opt.isEmpty) None else Some(opt.get) @deprecated("0.13.8", "A logger is no longer needed.") - def newScalaCompiler(instance: ScalaInstance, interfaceJar: File, options: ClasspathOptions, log: Logger): AnalyzingCompiler = + override def newScalaCompiler(instance: ScalaInstance, interfaceJar: File, options: ClasspathOptions, log: Logger): AnalyzingCompiler = new AnalyzingCompiler(instance, CompilerInterfaceProvider.constant(interfaceJar), options) - def newScalaCompiler(instance: ScalaInstance, interfaceJar: File, options: ClasspathOptions): AnalyzingCompiler = + 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) { diff --git a/compile/integration/src/main/scala/sbt/compiler/MixedAnalyzingCompiler.scala b/compile/integration/src/main/scala/sbt/compiler/MixedAnalyzingCompiler.scala index cfe98b6c7..9b7aeb20a 100644 --- a/compile/integration/src/main/scala/sbt/compiler/MixedAnalyzingCompiler.scala +++ b/compile/integration/src/main/scala/sbt/compiler/MixedAnalyzingCompiler.scala @@ -5,6 +5,7 @@ import java.lang.ref.{ SoftReference, Reference } import sbt.classfile.Analyze import sbt.classpath.ClasspathUtilities +import sbt.compiler.javac.AnalyzingJavaCompiler import sbt.inc.Locate.DefinesClass import sbt._ import sbt.inc._ @@ -14,6 +15,80 @@ 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. @@ -113,91 +188,21 @@ object MixedAnalyzingCompiler { { import config._ import currentSetup._ + // TODO - most of this around classpath-ness sohuld move to AnalyzingJavaCompiler constructor 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) val entry = Locate.entry(searchClasspath, definesClass) - - /** Here we set up a function that can run *one* iteration of the incremental compiler. - * - * Given: - * A set of files to compile - * A set of dependency changes - * A callback for analysis results - * - * Run: - * Scalac + Javac on the set of files, the order of which is determined by the current CompileConfiguration. - */ - val compile0 = (include: Set[File], changes: DependencyChanges, callback: AnalysisCallback) => { - 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. - * - * TODO - much of this logic should be extracted into an "AnalyzingJavaCompiler" class. - */ - def compileJava(): Unit = - if (!javaSrcs.isEmpty) { - import Path._ - @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) -> javaSrcs) - case multi: MultipleOutput => - javaSrcs 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) - timed("Java compilation", log) { - try javac.compileWithReporter(javaSrcs.toArray, absClasspath.toArray, output, options.javacOptions.toArray, reporter, log) - catch { - // Handle older APIs - case _: NoSuchMethodError => - javac.compile(javaSrcs.toArray, absClasspath.toArray, output, options.javacOptions.toArray, log) - } - } - /** 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 - Maybe on "Mixed" we should try to compile both Scala + Java. - if (order == JavaThenScala) { compileJava(); compileScala() } else { compileScala(); compileJava() } - } + // Construct a compiler which can handle both java and scala sources. + val mixedCompiler = + new MixedAnalyzingCompiler( + compiler, + // TODO - Construction of analyzing Java compiler MAYBE should be earlier... + new AnalyzingJavaCompiler(javac, classpath, compiler.scalaInstance, entry, searchClasspath), + config, + log + ) // Here we check to see if any previous compilation results are invalid, based on altered // javac/scalac options. @@ -212,29 +217,10 @@ object MixedAnalyzingCompiler { case Some(previous) if equiv.equiv(previous, currentSetup) => previousAnalysis case _ => Incremental.prune(sourcesSet, previousAnalysis) } - // Run the incremental compiler using the large compile0 "compile step" we've defined. - IncrementalCompile(sourcesSet, entry, compile0, analysis, getAnalysis, output, log, incOptions) + // Run the incremental compiler using the mixed compiler we've defined. + IncrementalCompile(sourcesSet, entry, mixedCompiler.compile, analysis, getAnalysis, output, log, incOptions) } - 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. */ def javaOnly(f: File) = f.getName.endsWith(".java") diff --git a/compile/src/main/scala/sbt/compiler/javac/AnalyzingJavaCompiler.scala b/compile/src/main/scala/sbt/compiler/javac/AnalyzingJavaCompiler.scala new file mode 100644 index 000000000..4bcce6e7c --- /dev/null +++ b/compile/src/main/scala/sbt/compiler/javac/AnalyzingJavaCompiler.scala @@ -0,0 +1,108 @@ +package sbt.compiler.javac + +import java.io.File + +import sbt.classfile.Analyze +import sbt.classpath.ClasspathUtilities +import sbt.compiler.CompilerArguments +import sbt._ +import xsbti.api.Source +import xsbti.{ Reporter, AnalysisCallback } +import xsbti.compile._ + +object AnalyzingJavaCompiler { + type ClasspathLookup = String => Option[File] +} + +/** + * 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: AnalyzingJavaCompiler.ClasspathLookup, + 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) + import Path._ + @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 + } + } + + // TODO - This code is duplciated later in MixedAnalyzing compiler. It should probably just live in this class. + private[this] def explicitBootClasspath(options: Seq[String]): Seq[File] = options.dropWhile(_ != CompilerArguments.BootClasspathOption).drop(1).take(1).headOption.toList.flatMap(IO.parseClasspath) + private[this] def withBootclasspath(args: CompilerArguments, classpath: Seq[File]): Seq[File] = + args.bootClasspathFor(classpath) ++ args.extClasspath ++ args.finishClasspath(classpath) + /** 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 + } +} diff --git a/interface/src/main/java/xsbti/compile/CompileProgress.java b/interface/src/main/java/xsbti/compile/CompileProgress.java index 902a50018..17174ff6a 100755 --- a/interface/src/main/java/xsbti/compile/CompileProgress.java +++ b/interface/src/main/java/xsbti/compile/CompileProgress.java @@ -1,5 +1,10 @@ package xsbti.compile; +/** + * An API for reporting when files are being compiled. + * + * Note; This is tied VERY SPECIFICALLY to scala. + */ public interface CompileProgress { void startUnit(String phase, String unitPath); diff --git a/interface/src/main/java/xsbti/compile/IncrementalCompiler.java b/interface/src/main/java/xsbti/compile/IncrementalCompiler.java index f2323111d..c98263e7f 100644 --- a/interface/src/main/java/xsbti/compile/IncrementalCompiler.java +++ b/interface/src/main/java/xsbti/compile/IncrementalCompiler.java @@ -44,8 +44,18 @@ public interface IncrementalCompiler * @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. + * */ + @Deprecated 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 diff --git a/project/Sbt.scala b/project/Sbt.scala index d5c1f0539..77712268a 100644 --- a/project/Sbt.scala +++ b/project/Sbt.scala @@ -156,8 +156,8 @@ object Sbt extends Build { // Persists the incremental data structures using 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 - lazy val compilerSub = testedBaseProject(compilePath, "Compile") dependsOn (launchInterfaceSub, interfaceSub % "compile;test->test", logSub, ioSub, classpathSub, - logSub % "test->test", launchSub % "test->test", apiSub % "test") settings (compilerSettings: _*) + lazy val compilerSub = testedBaseProject(compilePath, "Compile") dependsOn (launchInterfaceSub, interfaceSub % "compile;test->test", logSub, ioSub, classpathSub, apiSub, classfileSub, + logSub % "test->test", launchSub % "test->test") settings (compilerSettings: _*) lazy val compilerIntegrationSub = baseProject(compilePath / "integration", "Compiler Integration") dependsOn ( compileIncrementalSub, compilerSub, compilePersistSub, apiSub, classfileSub) lazy val compilerIvySub = baseProject(compilePath / "ivy", "Compiler Ivy Integration") dependsOn (ivySub, compilerSub) From 59ba35ce902dda86ff206b3e86b95e1c84d33758 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Wed, 5 Nov 2014 16:02:17 -0500 Subject: [PATCH 8/9] Finish rearchitecting. * IncrementalCompiler IC object now holds the actual logic to start incremental compilation (rather than AggresiveCompiler or other) * MixedAnalyzingCompiler ONLY does anlaysis of Java/Scala code * Moved the AnalyzingJavaCompiler into the integration library so that necessary dependencies are visible. --- .../sbt/compiler/AggressiveCompile.scala | 4 +- .../sbt/compiler/IncrementalCompiler.scala | 96 ++++++++++- .../sbt/compiler/MixedAnalyzingCompiler.scala | 153 +++++++----------- .../javac/AnalyzingJavaCompiler.scala | 18 +-- .../actions/src/main/scala/sbt/Compiler.scala | 6 +- 5 files changed, 159 insertions(+), 118 deletions(-) rename compile/{ => integration}/src/main/scala/sbt/compiler/javac/AnalyzingJavaCompiler.scala (86%) diff --git a/compile/integration/src/main/scala/sbt/compiler/AggressiveCompile.scala b/compile/integration/src/main/scala/sbt/compiler/AggressiveCompile.scala index 17d584320..ffad4d61e 100644 --- a/compile/integration/src/main/scala/sbt/compiler/AggressiveCompile.scala +++ b/compile/integration/src/main/scala/sbt/compiler/AggressiveCompile.scala @@ -20,9 +20,9 @@ import xsbti.api.Source import xsbti.compile.{ CompileOrder, DependencyChanges, GlobalsCache, Output, SingleOutput, MultipleOutput, CompileProgress } import CompileOrder.{ JavaThenScala, Mixed, ScalaThenJava } -@deprecated("0.13.8", "Use MixedAnalyzingCompiler instead.") +@deprecated("0.13.8", "Use MixedAnalyzingCompiler or IC instead.") class AggressiveCompile(cacheFile: File) { - @deprecated("0.13.8", "Use MixedAnalyzingCompiler.analyzingCompile instead.") + @deprecated("0.13.8", "Use IC.compile instead.") def apply(compiler: AnalyzingCompiler, javac: xsbti.compile.JavaCompiler, sources: Seq[File], classpath: Seq[File], diff --git a/compile/integration/src/main/scala/sbt/compiler/IncrementalCompiler.scala b/compile/integration/src/main/scala/sbt/compiler/IncrementalCompiler.scala index 2100c4333..e1a0b747e 100644 --- a/compile/integration/src/main/scala/sbt/compiler/IncrementalCompiler.scala +++ b/compile/integration/src/main/scala/sbt/compiler/IncrementalCompiler.scala @@ -1,9 +1,17 @@ package sbt.compiler import java.io.File -import sbt.{ CompileSetup, IO, Using } -import sbt.inc.{ Analysis, IncOptions, TextAnalysisFormat } -import xsbti.{ Logger, Maybe } +import sbt.compiler.javac.AnalyzingJavaCompiler +import sbt.inc.Locate._ +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._ // TODO - @@ -32,7 +40,7 @@ object IC extends IncrementalCompiler[Analysis, AnalyzingCompiler] { (Analysis.empty(nameHashing = incOptions.nameHashing), None) } } - MixedAnalyzingCompiler.analyzingCompile(scalac, javac, sources, classpath, output, cache, m2o(progress), scalacOptions, javacOptions, previousAnalysis, + incrementalCompile(scalac, javac, sources, classpath, output, cache, m2o(progress), scalacOptions, javacOptions, previousAnalysis, previousSetup, aMap, defClass, reporter, order, skip, incOptions)(log).analysis } @@ -71,4 +79,84 @@ object IC extends IncrementalCompiler[Analysis, AnalyzingCompiler] { 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 + } } diff --git a/compile/integration/src/main/scala/sbt/compiler/MixedAnalyzingCompiler.scala b/compile/integration/src/main/scala/sbt/compiler/MixedAnalyzingCompiler.scala index 9b7aeb20a..914c16bd5 100644 --- a/compile/integration/src/main/scala/sbt/compiler/MixedAnalyzingCompiler.scala +++ b/compile/integration/src/main/scala/sbt/compiler/MixedAnalyzingCompiler.scala @@ -5,6 +5,7 @@ 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._ @@ -98,33 +99,8 @@ final class MixedAnalyzingCompiler( * down to the incremental compiler for the rest. */ object MixedAnalyzingCompiler { - /** 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 - * @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 analyzingCompile(scalac: AnalyzingCompiler, + def makeConfig(scalac: AnalyzingCompiler, javac: xsbti.compile.JavaCompiler, sources: Seq[File], classpath: Seq[File], @@ -140,90 +116,77 @@ object MixedAnalyzingCompiler { reporter: Reporter, compileOrder: CompileOrder = Mixed, skip: Boolean = false, - incrementalCompilerOptions: IncOptions)(implicit log: Logger): Result = + incrementalCompilerOptions: IncOptions): CompileConfiguration = { - val setup = new CompileSetup(output, new CompileOptions(options, javacOptions), + val compileSetup = new CompileSetup(output, new CompileOptions(options, javacOptions), scalac.scalaInstance.actualVersion, compileOrder, incrementalCompilerOptions.nameHashing) - val (analysis, modified) = compile1(sources, classpath, setup, progress, previousAnalysis, previousSetup, analysisMap, definesClass, - scalac, javac, reporter, skip, cache, incrementalCompilerOptions) - Result(analysis, setup, modified) + config( + sources, + classpath, + compileSetup, + progress, + previousAnalysis, + previousSetup, + analysisMap, + definesClass, + scalac, + javac, + reporter, + skip, + cache, + incrementalCompilerOptions) } - def withBootclasspath(args: CompilerArguments, classpath: Seq[File]): Seq[File] = - args.bootClasspathFor(classpath) ++ args.extClasspath ++ args.finishClasspath(classpath) - - /** - * The first level of compilation. This checks to see if we need to skip compiling because of the skip flag, - * IF we're not skipping, this setups up the `CompileConfiguration` and delegates to `compile2` - */ - private def compile1(sources: Seq[File], + def config( + sources: Seq[File], classpath: Seq[File], - setup: CompileSetup, progress: Option[CompileProgress], + 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, + reporter: Reporter, + skip: Boolean, cache: GlobalsCache, - incrementalCompilerOptions: IncOptions)(implicit log: Logger): (Analysis, Boolean) = - { - import CompileSetup._ - if (skip) - (previousAnalysis, false) - else { - val config = new CompileConfiguration(sources, classpath, previousAnalysis, previousSetup, setup, - progress, analysis, definesClass, reporter, compiler, javac, cache, incrementalCompilerOptions) - val (modified, result) = compile2(config) - (result, modified) - } - } - /** - * This runs the actual compilation of Java + Scala. There are two helper methods which - * actually perform the compilation of Java or Scala. This method uses the compile order to determine - * which files to pass to which compilers. - */ - private def compile2(config: CompileConfiguration)(implicit log: Logger, equiv: Equiv[CompileSetup]): (Boolean, Analysis) = - { - import config._ - import currentSetup._ - // TODO - most of this around classpath-ness sohuld move to AnalyzingJavaCompiler constructor - 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) - val entry = Locate.entry(searchClasspath, definesClass) - // Construct a compiler which can handle both java and scala sources. - val mixedCompiler = - new MixedAnalyzingCompiler( - compiler, - // TODO - Construction of analyzing Java compiler MAYBE should be earlier... - new AnalyzingJavaCompiler(javac, classpath, compiler.scalaInstance, entry, searchClasspath), - config, - log - ) + incrementalCompilerOptions: IncOptions): CompileConfiguration = { + import CompileSetup._ + new CompileConfiguration(sources, classpath, previousAnalysis, previousSetup, setup, + progress, analysis, definesClass, reporter, compiler, javac, cache, incrementalCompilerOptions) + } - // Here we check to see if any previous compilation results are invalid, based on altered - // javac/scalac options. - 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) - } + /** 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 true if the file is java. */ - def javaOnly(f: File) = f.getName.endsWith(".java") + /** 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) diff --git a/compile/src/main/scala/sbt/compiler/javac/AnalyzingJavaCompiler.scala b/compile/integration/src/main/scala/sbt/compiler/javac/AnalyzingJavaCompiler.scala similarity index 86% rename from compile/src/main/scala/sbt/compiler/javac/AnalyzingJavaCompiler.scala rename to compile/integration/src/main/scala/sbt/compiler/javac/AnalyzingJavaCompiler.scala index 4bcce6e7c..d49bf9515 100644 --- a/compile/src/main/scala/sbt/compiler/javac/AnalyzingJavaCompiler.scala +++ b/compile/integration/src/main/scala/sbt/compiler/javac/AnalyzingJavaCompiler.scala @@ -2,17 +2,14 @@ package sbt.compiler.javac import java.io.File +import sbt._ import sbt.classfile.Analyze import sbt.classpath.ClasspathUtilities import sbt.compiler.CompilerArguments -import sbt._ +import sbt.inc.Locate import xsbti.api.Source -import xsbti.{ Reporter, AnalysisCallback } import xsbti.compile._ - -object AnalyzingJavaCompiler { - type ClasspathLookup = String => Option[File] -} +import xsbti.{ AnalysisCallback, Reporter } /** * This is a java compiler which will also report any discovered source dependencies/apis out via @@ -25,9 +22,8 @@ final class AnalyzingJavaCompiler private[sbt] ( val javac: xsbti.compile.JavaCompiler, val classpath: Seq[File], val scalaInstance: xsbti.compile.ScalaInstance, - val classLookup: AnalyzingJavaCompiler.ClasspathLookup, + val classLookup: (String => Option[File]), val searchClasspath: Seq[File]) { - /** * Compile some java code using the current configured compiler. * @@ -42,7 +38,6 @@ final class AnalyzingJavaCompiler private[sbt] ( 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) - import Path._ @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 @@ -92,11 +87,6 @@ final class AnalyzingJavaCompiler private[sbt] ( // TODO - Perhaps we just record task 2/2 here } } - - // TODO - This code is duplciated later in MixedAnalyzing compiler. It should probably just live in this class. - private[this] def explicitBootClasspath(options: Seq[String]): Seq[File] = options.dropWhile(_ != CompilerArguments.BootClasspathOption).drop(1).take(1).headOption.toList.flatMap(IO.parseClasspath) - private[this] def withBootclasspath(args: CompilerArguments, classpath: Seq[File]): Seq[File] = - args.bootClasspathFor(classpath) ++ args.extClasspath ++ args.finishClasspath(classpath) /** 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 diff --git a/main/actions/src/main/scala/sbt/Compiler.scala b/main/actions/src/main/scala/sbt/Compiler.scala index 3d10bf688..b05123e44 100644 --- a/main/actions/src/main/scala/sbt/Compiler.scala +++ b/main/actions/src/main/scala/sbt/Compiler.scala @@ -3,7 +3,7 @@ */ package sbt -import sbt.compiler.javac.{ IncrementalCompilerJavaTools, JavaCompiler, JavaTools } +import sbt.compiler.javac.{ IncrementalCompilerJavaTools, JavaTools } import xsbti.{ Logger => _, _ } import xsbti.compile.{ CompileOrder, GlobalsCache } import CompileOrder.{ JavaThenScala, Mixed, ScalaThenJava } @@ -34,7 +34,7 @@ object Compiler { } /** The previous source dependency analysis result from compilation. */ final case class PreviousAnalysis(analysis: Analysis, setup: Option[CompileSetup]) - type CompileResult = MixedAnalyzingCompiler.Result + 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]], @@ -124,7 +124,7 @@ object Compiler { val javacChosen: xsbti.compile.JavaCompiler = in.inputs.compilers.newJavac.map(_.xsbtiCompiler).getOrElse(in.inputs.compilers.javac) // TODO - Why are we not using the IC interface??? - MixedAnalyzingCompiler.analyzingCompile(scalac, javacChosen, sources, classpath, CompileOutput(classesDirectory), cache, None, options, javacOptions, + 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) } From f0adf92345443799354faf32a96524b321232669 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Mon, 1 Dec 2014 15:20:17 -0500 Subject: [PATCH 9/9] Fixes @eed3s1gn's comments * Use verbs. * compile actually saves the analysis * previousCompile loads previous anlaysis * explicit task to override for bytecode manipulation folkel. --- main/src/main/scala/sbt/Defaults.scala | 25 +++++++++++++------------ main/src/main/scala/sbt/Keys.scala | 4 ++-- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 1d2ba94df..16fb80673 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -250,6 +250,7 @@ object Defaults extends BuildCommon { lazy val configTasks = docTaskSettings(doc) ++ inTask(compile)(compileInputsSettings) ++ configGlobal ++ compileAnalysisSettings ++ Seq( compile <<= compileTask, + manipulateBytecode := compileIncremental.value, compileIncremental <<= compileIncrementalTask tag (Tags.Compile, Tags.CPU), printWarnings <<= printWarningsTask, compileAnalysisFilename := { @@ -781,10 +782,19 @@ object Defaults extends BuildCommon { @deprecated("Use inTask(compile)(compileInputsSettings)", "0.13.0") def compileTaskSettings: Seq[Setting[_]] = inTask(compile)(compileInputsSettings) - def compileTask: Initialize[Task[inc.Analysis]] = Def.task { saveAnalysis.value } + def compileTask: Initialize[Task[inc.Analysis]] = Def.task { + 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, readAnalysis.value, (compilerReporter in compile).value) + 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 = { @@ -815,22 +825,13 @@ object Defaults extends BuildCommon { }, compilerReporter := None) def compileAnalysisSettings: Seq[Setting[_]] = Seq( - readAnalysis := { + 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) } - }, - saveAnalysis := { - val setup: Compiler.IncSetup = compileIncSetup.value - val analysisResult: Compiler.CompileResult = compileIncremental.value - if (analysisResult.hasModified) { - val store = MixedAnalyzingCompiler.staticCachedStore(setup.cacheFile) - store.set(analysisResult.analysis, analysisResult.setup) - } - analysisResult.analysis } ) diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 53ce20f87..66171fdcd 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -142,9 +142,9 @@ object Keys { 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 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 readAnalysis = TaskKey[Compiler.PreviousAnalysis]("readAnalysis", "Read the incremental compiler analysis from disk", DTask) - val saveAnalysis = TaskKey[Analysis]("saveAnalysis", "Save the incremental compiler analysis to disk", 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 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)