From 59ba35ce902dda86ff206b3e86b95e1c84d33758 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Wed, 5 Nov 2014 16:02:17 -0500 Subject: [PATCH] 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) }