diff --git a/compile/AnalyzingCompiler.scala b/compile/AnalyzingCompiler.scala index 98239aa3b..ce67edbd3 100644 --- a/compile/AnalyzingCompiler.scala +++ b/compile/AnalyzingCompiler.scala @@ -12,7 +12,7 @@ package compiler * provided by scalaInstance. This class requires a ComponentManager in order to obtain the interface code to scalac and * the analysis plugin. Because these call Scala code for a different Scala version than the one used for this class, they must * be compiled for the version of Scala being used.*/ -class AnalyzingCompiler(val scalaInstance: ScalaInstance, val provider: CompilerInterfaceProvider, val cp: ClasspathOptions, log: Logger) +class AnalyzingCompiler(val scalaInstance: xsbti.compile.ScalaInstance, val provider: CompilerInterfaceProvider, val cp: xsbti.compile.ClasspathOptions, log: Logger) { def this(scalaInstance: ScalaInstance, provider: CompilerInterfaceProvider, log: Logger) = this(scalaInstance, provider, ClasspathOptions.auto, log) def apply(sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String], callback: AnalysisCallback, maximumErrors: Int, log: Logger) @@ -73,3 +73,34 @@ class AnalyzingCompiler(val scalaInstance: ScalaInstance, val provider: Compiler } override def toString = "Analyzing compiler (Scala " + scalaInstance.actualVersion + ")" } +object AnalyzingCompiler +{ + import sbt.IO.{copy, createDirectory, zip, jars, unzip, withTemporaryDirectory} + + /** Extract sources from source jars, compile them with the xsbti interfaces on the classpath, and package the compiled classes and + * any resources from the source jars into a final jar.*/ + def compileSources(sourceJars: Iterable[File], targetJar: File, xsbtiJars: Iterable[File], id: String, compiler: RawCompiler, log: Logger) + { + val isSource = (f: File) => isSourceName(f.getName) + def keepIfSource(files: Set[File]): Set[File] = if(files.exists(isSource)) files else Set() + + withTemporaryDirectory { dir => + val extractedSources = (Set[File]() /: sourceJars) { (extracted, sourceJar)=> extracted ++ keepIfSource(unzip(sourceJar, dir)) } + val (sourceFiles, resources) = extractedSources.partition(isSource) + withTemporaryDirectory { outputDirectory => + log.info("'" + id + "' not yet compiled for Scala " + compiler.scalaInstance.actualVersion + ". Compiling...") + val start = System.currentTimeMillis + try + { + compiler(sourceFiles.toSeq, xsbtiJars.toSeq ++ sourceJars, outputDirectory, "-nowarn" :: Nil) + log.info(" Compilation completed in " + (System.currentTimeMillis - start) / 1000.0 + " s") + } + catch { case e: xsbti.CompileFailed => throw new CompileFailed(e.arguments, "Error compiling sbt component '" + id + "'") } + import sbt.Path._ + copy(resources x rebase(dir, outputDirectory)) + zip((outputDirectory ***) x_! relativeTo(outputDirectory), targetJar) + } + } + } + private def isSourceName(name: String): Boolean = name.endsWith(".scala") || name.endsWith(".java") +} \ No newline at end of file diff --git a/compile/CompilerArguments.scala b/compile/CompilerArguments.scala index 888dc6437..b023d3727 100644 --- a/compile/CompilerArguments.scala +++ b/compile/CompilerArguments.scala @@ -14,7 +14,7 @@ package compiler * order to add these jars to the boot classpath. The 'scala.home' property must be unset because Scala * puts jars in that directory on the bootclasspath. Because we use multiple Scala versions, * this would lead to compiling against the wrong library jar.*/ -final class CompilerArguments(scalaInstance: ScalaInstance, cp: ClasspathOptions) +final class CompilerArguments(scalaInstance: xsbti.compile.ScalaInstance, cp: xsbti.compile.ClasspathOptions) { def apply(sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String]): Seq[String] = { @@ -28,7 +28,7 @@ final class CompilerArguments(scalaInstance: ScalaInstance, cp: ClasspathOptions options ++ outputOption ++ bootClasspathOption ++ classpathOption ++ abs(sources) } def finishClasspath(classpath: Seq[File]): Seq[File] = - filterLibrary(classpath) ++ include(cp.compiler, scalaInstance.compilerJar) ++ include(cp.extra, scalaInstance.extraJars : _*) + filterLibrary(classpath) ++ include(cp.compiler, scalaInstance.compilerJar) ++ include(cp.extra, scalaInstance.otherJars : _*) private def include(flag: Boolean, jars: File*) = if(flag) jars else Nil protected def abs(files: Seq[File]) = files.map(_.getAbsolutePath).sortWith(_ < _) protected def checkScalaHomeUnset() diff --git a/compile/CompilerInterfaceProvider.scala b/compile/CompilerInterfaceProvider.scala index d0c641681..02d0ccd65 100644 --- a/compile/CompilerInterfaceProvider.scala +++ b/compile/CompilerInterfaceProvider.scala @@ -5,11 +5,11 @@ package compiler trait CompilerInterfaceProvider { - def apply(scalaInstance: ScalaInstance, log: Logger): File + def apply(scalaInstance: xsbti.compile.ScalaInstance, log: Logger): File } object CompilerInterfaceProvider { def constant(file: File): CompilerInterfaceProvider = new CompilerInterfaceProvider { - def apply(scalaInstance: ScalaInstance, log: Logger): File = file + def apply(scalaInstance: xsbti.compile.ScalaInstance, log: Logger): File = file } } \ No newline at end of file diff --git a/compile/RawCompiler.scala b/compile/RawCompiler.scala index b685ee308..f3419ed55 100644 --- a/compile/RawCompiler.scala +++ b/compile/RawCompiler.scala @@ -10,7 +10,7 @@ package compiler * is used, for example, to compile the interface/plugin code. * If `explicitClasspath` is true, the bootclasspath and classpath are not augmented. If it is false, * the scala-library.jar from `scalaInstance` is put on bootclasspath and the scala-compiler jar goes on the classpath.*/ -class RawCompiler(val scalaInstance: ScalaInstance, cp: ClasspathOptions, log: Logger) +class RawCompiler(val scalaInstance: xsbti.compile.ScalaInstance, cp: ClasspathOptions, log: Logger) { def apply(sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String]) { diff --git a/compile/integration/AggressiveCompile.scala b/compile/integration/AggressiveCompile.scala index 0cc348da7..97d29d436 100644 --- a/compile/integration/AggressiveCompile.scala +++ b/compile/integration/AggressiveCompile.scala @@ -20,11 +20,11 @@ import inc._ final class CompileConfiguration(val sources: Seq[File], val classpath: Seq[File], val previousAnalysis: Analysis, val previousSetup: Option[CompileSetup], val currentSetup: CompileSetup, val getAnalysis: File => Option[Analysis], val definesClass: DefinesClass, - val maxErrors: Int, val compiler: AnalyzingCompiler, val javac: JavaCompiler) + val maxErrors: Int, val compiler: AnalyzingCompiler, val javac: xsbti.compile.JavaCompiler) class AggressiveCompile(cacheFile: File) { - def apply(compiler: AnalyzingCompiler, javac: JavaCompiler, sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String] = Nil, javacOptions: Seq[String] = Nil, analysisMap: File => Option[Analysis] = const(None), definesClass: DefinesClass = Locate.definesClass _, maxErrors: Int = 100, compileOrder: CompileOrder = Mixed, skip: Boolean = false)(implicit log: Logger): Analysis = + def apply(compiler: AnalyzingCompiler, javac: xsbti.compile.JavaCompiler, sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String] = Nil, javacOptions: Seq[String] = Nil, analysisMap: File => Option[Analysis] = const(None), definesClass: DefinesClass = Locate.definesClass _, maxErrors: Int = 100, compileOrder: CompileOrder = Mixed, skip: Boolean = false)(implicit log: Logger): Analysis = { val setup = new CompileSetup(outputDirectory, new CompileOptions(options, javacOptions), compiler.scalaInstance.actualVersion, compileOrder) compile1(sources, classpath, setup, store, analysisMap, definesClass, compiler, javac, maxErrors, skip) @@ -33,7 +33,7 @@ class AggressiveCompile(cacheFile: File) def withBootclasspath(args: CompilerArguments, classpath: Seq[File]): Seq[File] = args.bootClasspath ++ args.finishClasspath(classpath) - def compile1(sources: Seq[File], classpath: Seq[File], setup: CompileSetup, store: AnalysisStore, analysis: File => Option[Analysis], definesClass: DefinesClass, compiler: AnalyzingCompiler, javac: JavaCompiler, maxErrors: Int, skip: Boolean)(implicit log: Logger): Analysis = + def compile1(sources: Seq[File], classpath: Seq[File], setup: CompileSetup, store: AnalysisStore, analysis: File => Option[Analysis], definesClass: DefinesClass, compiler: AnalyzingCompiler, javac: xsbti.compile.JavaCompiler, maxErrors: Int, skip: Boolean)(implicit log: Logger): Analysis = { val (previousAnalysis, previousSetup) = extract(store.get()) if(skip) @@ -75,7 +75,7 @@ class AggressiveCompile(cacheFile: File) val loader = ClasspathUtilities.toLoader(searchClasspath) def readAPI(source: File, classes: Seq[Class[_]]) { callback.api(source, ClassToAPI(classes)) } Analyze(outputDirectory, javaSrcs, log)(callback, loader, readAPI) { - javac(javaSrcs, absClasspath, outputDirectory, options.javacOptions) + javac.compile(javaSrcs.toArray, absClasspath.toArray, outputDirectory, options.javacOptions.toArray, maxErrors, log) } } if(order == JavaThenScala) { compileJava(); compileScala() } else { compileScala(); compileJava() } diff --git a/compile/integration/IncrementalCompiler.scala b/compile/integration/IncrementalCompiler.scala new file mode 100644 index 000000000..b6bda5dee --- /dev/null +++ b/compile/integration/IncrementalCompiler.scala @@ -0,0 +1,32 @@ +package sbt.compiler + + import sbt.inc.Analysis + import xsbti.{Logger, Maybe} + import xsbti.compile._ + + import java.io.File + +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(setup.cacheFile) + val aMap = (f: File) => m2o(analysisMap(f)) + val defClass = (f: File) => { val dc = definesClass(f); (name: String) => dc.apply(name) } + agg(scalac, javac, sources, classpath, classesDirectory, scalacOptions, javacOptions, aMap, defClass, maxErrors, order, skip)(log) + } + + private[this] def m2o[S](opt: Maybe[S]): Option[S] = if(opt.isEmpty) None else Some(opt.get) + + def newScalaCompiler(instance: ScalaInstance, interfaceJar: File, options: ClasspathOptions, log: Logger): AnalyzingCompiler = + new AnalyzingCompiler(instance, CompilerInterfaceProvider.constant(interfaceJar), options, log) + + def compileInterfaceJar(label: String, sourceJar: File, targetJar: File, interfaceJar: File, instance: ScalaInstance, log: Logger) + { + val raw = new RawCompiler(instance, sbt.ClasspathOptions.auto, log) + AnalyzingCompiler.compileSources(sourceJar :: Nil, targetJar, interfaceJar :: Nil, label, raw, log) + } +} diff --git a/compile/ivy/ComponentCompiler.scala b/compile/ivy/ComponentCompiler.scala index e4431c127..f4732aabf 100644 --- a/compile/ivy/ComponentCompiler.scala +++ b/compile/ivy/ComponentCompiler.scala @@ -17,7 +17,7 @@ object ComponentCompiler def interfaceProvider(manager: ComponentManager): CompilerInterfaceProvider = new CompilerInterfaceProvider { - def apply(scalaInstance: ScalaInstance, log: Logger): File = + def apply(scalaInstance: xsbti.compile.ScalaInstance, log: Logger): File = { // this is the instance used to compile the interface component val componentCompiler = new ComponentCompiler(new RawCompiler(scalaInstance, ClasspathOptions.auto, log), manager) @@ -32,7 +32,6 @@ object ComponentCompiler class ComponentCompiler(compiler: RawCompiler, manager: ComponentManager) { import ComponentCompiler._ - import sbt.IO.{copy, createDirectory, zip, jars, unzip, withTemporaryDirectory} def apply(id: String): File = try { getPrecompiled(id) } catch { case _: InvalidComponent => getLocallyCompiled(id) } @@ -56,37 +55,11 @@ class ComponentCompiler(compiler: RawCompiler, manager: ComponentManager) protected def compileAndInstall(id: String, binID: String) { val srcID = id + srcExtension - withTemporaryDirectory { binaryDirectory => + IO.withTemporaryDirectory { binaryDirectory => val targetJar = new File(binaryDirectory, id + ".jar") - compileSources(manager.files(srcID)(IfMissing.Fail), targetJar, id) + val xsbtiJars = manager.files(xsbtiID)(IfMissing.Fail) + AnalyzingCompiler.compileSources(manager.files(srcID)(IfMissing.Fail), targetJar, xsbtiJars, id, compiler, manager.log) manager.define(binID, Seq(targetJar)) } } - /** Extract sources from source jars, compile them with the xsbti interfaces on the classpath, and package the compiled classes and - * any resources from the source jars into a final jar.*/ - private def compileSources(sourceJars: Iterable[File], targetJar: File, id: String) - { - val isSource = (f: File) => isSourceName(f.getName) - def keepIfSource(files: Set[File]): Set[File] = if(files.exists(isSource)) files else Set() - - withTemporaryDirectory { dir => - val extractedSources = (Set[File]() /: sourceJars) { (extracted, sourceJar)=> extracted ++ keepIfSource(unzip(sourceJar, dir)) } - val (sourceFiles, resources) = extractedSources.partition(isSource) - withTemporaryDirectory { outputDirectory => - val xsbtiJars = manager.files(xsbtiID)(IfMissing.Fail) - manager.log.info("'" + id + "' not yet compiled for Scala " + compiler.scalaInstance.actualVersion + ". Compiling...") - val start = System.currentTimeMillis - try - { - compiler(sourceFiles.toSeq, xsbtiJars.toSeq ++ sourceJars, outputDirectory, "-nowarn" :: Nil) - manager.log.info(" Compilation completed in " + (System.currentTimeMillis - start) / 1000.0 + " s") - } - catch { case e: xsbti.CompileFailed => throw new CompileFailed(e.arguments, "Error compiling sbt component '" + id + "'") } - import sbt.Path._ - copy(resources x rebase(dir, outputDirectory)) - zip((outputDirectory ***) x_! relativeTo(outputDirectory), targetJar) - } - } - } - private def isSourceName(name: String): Boolean = name.endsWith(".scala") || name.endsWith(".java") } \ No newline at end of file diff --git a/interface/src/main/java/xsbti/compile/IncrementalCompiler.java b/interface/src/main/java/xsbti/compile/IncrementalCompiler.java index 9fe301029..f2323111d 100644 --- a/interface/src/main/java/xsbti/compile/IncrementalCompiler.java +++ b/interface/src/main/java/xsbti/compile/IncrementalCompiler.java @@ -52,9 +52,10 @@ public interface IncrementalCompiler * to create a ScalaCompiler for incremental compilation. It is the client's responsibility to manage compiled jars for * different Scala versions. * + * @param label A brief name describing the source component for use in error messages * @param sourceJar The jar file containing the compiler interface sources. These are published as sbt's compiler-interface-src module. * @param targetJar Where to create the output jar file containing the compiled classes. * @param instance The ScalaInstance to compile the compiler interface for. * @param log The logger to use during compilation. */ - void compileInterfaceJar(File sourceJar, File targetJar, ScalaInstance instance, Logger log); + void compileInterfaceJar(String label, File sourceJar, File targetJar, File interfaceJar, ScalaInstance instance, Logger log); } diff --git a/interface/src/main/java/xsbti/compile/Setup.java b/interface/src/main/java/xsbti/compile/Setup.java index ca9999978..a1a9a1ad8 100644 --- a/interface/src/main/java/xsbti/compile/Setup.java +++ b/interface/src/main/java/xsbti/compile/Setup.java @@ -17,8 +17,8 @@ public interface Setup /** If true, no sources are actually compiled and the Analysis from the previous compilation is returned.*/ boolean skip(); - /** The directory used to cache information across compilations. - * This directory can be removed to force a full recompilation. - * The directory should be unique and not shared between compilations. */ - File cacheDirectory(); + /** The file used to cache information across compilations. + * This file can be removed to force a full recompilation. + * The file should be unique and not shared between compilations. */ + File cacheFile(); } diff --git a/main/Build.scala b/main/Build.scala index 366637d90..253e77dbf 100644 --- a/main/Build.scala +++ b/main/Build.scala @@ -285,5 +285,5 @@ object BuildPaths final val GlobalPluginsProperty = "sbt.global.plugins" final val GlobalSettingsProperty = "sbt.global.settings" - def crossPath(base: File, instance: ScalaInstance): File = base / ("scala_" + instance.version) + def crossPath(base: File, instance: xsbti.compile.ScalaInstance): File = base / ("scala_" + instance.version) }