From b5a29987e63bf6a9d07f5f4ae492d2078f8fe641 Mon Sep 17 00:00:00 2001 From: Eugene Vigdorchik Date: Tue, 10 Jul 2012 21:12:39 +0400 Subject: [PATCH] Changes required to use sbt as-is from Scala-IDE. --- compile/AnalyzingCompiler.scala | 41 +++++---- compile/CompilerArguments.scala | 4 +- compile/CompilerCache.scala | 12 +-- compile/CompilerOutput.scala | 24 +++++ compile/JavaCompiler.scala | 11 ++- compile/RawCompiler.scala | 2 +- compile/inc/APIs.scala | 4 +- compile/inc/Compile.scala | 26 +++--- compile/inc/CompileSetup.scala | 23 +++-- compile/inc/Incremental.scala | 4 +- compile/integration/AggressiveCompile.scala | 71 ++++++++++----- compile/integration/IncrementalCompiler.scala | 2 +- compile/interface/Analyzer.scala | 7 +- compile/interface/CompilerInterface.scala | 58 ++++++++---- compile/persist/AnalysisFormats.scala | 22 ++++- interface/other | 6 +- .../java/xsbti/compile/CachedCompiler.java | 2 +- .../xsbti/compile/CachedCompilerProvider.java | 2 +- .../java/xsbti/compile/CompileProgress.java | 7 ++ .../main/java/xsbti/compile/GlobalsCache.java | 2 +- .../main/java/xsbti/compile/JavaCompiler.java | 2 +- .../java/xsbti/compile/MultipleOutput.java | 20 +++++ .../src/main/java/xsbti/compile/Options.java | 7 +- .../src/main/java/xsbti/compile/Output.java | 9 ++ .../src/main/java/xsbti/compile/Setup.java | 3 + .../main/java/xsbti/compile/SingleOutput.java | 12 +++ main/actions/Compiler.scala | 2 +- util/classfile/Analyze.scala | 88 ++++++++----------- 28 files changed, 316 insertions(+), 157 deletions(-) create mode 100755 compile/CompilerOutput.scala create mode 100755 interface/src/main/java/xsbti/compile/CompileProgress.java create mode 100755 interface/src/main/java/xsbti/compile/MultipleOutput.java create mode 100755 interface/src/main/java/xsbti/compile/Output.java create mode 100755 interface/src/main/java/xsbti/compile/SingleOutput.java diff --git a/compile/AnalyzingCompiler.scala b/compile/AnalyzingCompiler.scala index ce6b9e4dc..eab09fb8c 100644 --- a/compile/AnalyzingCompiler.scala +++ b/compile/AnalyzingCompiler.scala @@ -5,7 +5,7 @@ package sbt package compiler import xsbti.{AnalysisCallback, Logger => xLogger, Reporter} - import xsbti.compile.{CachedCompiler, CachedCompilerProvider, DependencyChanges, GlobalsCache} + import xsbti.compile.{CachedCompiler, CachedCompilerProvider, DependencyChanges, GlobalsCache, CompileProgress, Output} import java.io.File import java.net.{URL, URLClassLoader} @@ -16,33 +16,35 @@ package compiler final class AnalyzingCompiler(val scalaInstance: xsbti.compile.ScalaInstance, val provider: CompilerInterfaceProvider, val cp: xsbti.compile.ClasspathOptions, log: Logger) extends CachedCompilerProvider { def this(scalaInstance: ScalaInstance, provider: CompilerInterfaceProvider, log: Logger) = this(scalaInstance, provider, ClasspathOptions.auto, log) - def apply(sources: Seq[File], changes: DependencyChanges, classpath: Seq[File], outputDirectory: File, options: Seq[String], callback: AnalysisCallback, maximumErrors: Int, cache: GlobalsCache, log: Logger) + def apply(sources: Seq[File], changes: DependencyChanges, classpath: Seq[File], singleOutput: File, options: Seq[String], callback: AnalysisCallback, maximumErrors: Int, cache: GlobalsCache, log: Logger) { - val arguments = (new CompilerArguments(scalaInstance, cp))(Nil, classpath, outputDirectory, options) - compile(sources, changes, arguments, callback, maximumErrors, cache, log) + val arguments = (new CompilerArguments(scalaInstance, cp))(Nil, classpath, None, options) + val output = CompileOutput(singleOutput) + compile(sources, changes, arguments, output, callback, maximumErrors, cache, log, None) } - def compile(sources: Seq[File], changes: DependencyChanges, options: Seq[String], callback: AnalysisCallback, maximumErrors: Int, cache: GlobalsCache, log: Logger): Unit = + def compile(sources: Seq[File], changes: DependencyChanges, options: Seq[String], output: Output, callback: AnalysisCallback, maximumErrors: Int, cache: GlobalsCache, log: Logger, progressOpt: Option[CompileProgress]): Unit = { val reporter = new LoggerReporter(maximumErrors, log) - val cached = cache(options.toArray, !changes.isEmpty, this, log, reporter) - compile(sources, changes, callback, log, reporter, cached) + val cached = cache(options.toArray, output, !changes.isEmpty, this, log, reporter) + val progress = progressOpt getOrElse IgnoreProgress + compile(sources, changes, callback, log, reporter, progress, cached) } - def compile(sources: Seq[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger, reporter: Reporter, compiler: CachedCompiler) + def compile(sources: Seq[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger, reporter: Reporter, progress: CompileProgress, compiler: CachedCompiler) { call("xsbt.CompilerInterface", "run", log)( - classOf[Array[File]], classOf[DependencyChanges], classOf[AnalysisCallback], classOf[xLogger], classOf[Reporter], classOf[CachedCompiler]) ( - sources.toArray, changes, callback, log, reporter, compiler ) + classOf[Array[File]], classOf[DependencyChanges], classOf[AnalysisCallback], classOf[xLogger], classOf[Reporter], classOf[CompileProgress], classOf[CachedCompiler]) ( + sources.toArray, changes, callback, log, reporter, progress, compiler ) } - def newCachedCompiler(arguments: Array[String], log: xLogger, reporter: Reporter, resident: Boolean): CachedCompiler = - newCachedCompiler(arguments: Seq[String], log, reporter, resident) + def newCachedCompiler(arguments: Array[String], output: Output, log: xLogger, reporter: Reporter, resident: Boolean): CachedCompiler = + newCachedCompiler(arguments: Seq[String], output, log, reporter, resident) - def newCachedCompiler(arguments: Seq[String], log: xLogger, reporter: Reporter, resident: Boolean): CachedCompiler = + def newCachedCompiler(arguments: Seq[String], output: Output, log: xLogger, reporter: Reporter, resident: Boolean): CachedCompiler = { call("xsbt.CompilerInterface", "newCompiler", log)( - classOf[Array[String]], classOf[xLogger], classOf[Reporter], classOf[Boolean] ) ( - arguments.toArray[String] : Array[String], log, reporter, resident: java.lang.Boolean ). + classOf[Array[String]], classOf[Output], classOf[xLogger], classOf[Reporter], classOf[Boolean] ) ( + arguments.toArray[String] : Array[String], output, log, reporter, resident: java.lang.Boolean ). asInstanceOf[CachedCompiler] } @@ -50,7 +52,7 @@ final class AnalyzingCompiler(val scalaInstance: xsbti.compile.ScalaInstance, va doc(sources, classpath, outputDirectory, options, log, new LoggerReporter(maximumErrors, log)) def doc(sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String], log: Logger, reporter: Reporter): Unit = { - val arguments = (new CompilerArguments(scalaInstance, cp))(sources, classpath, outputDirectory, options) + val arguments = (new CompilerArguments(scalaInstance, cp))(sources, classpath, Some(outputDirectory), options) call("xsbt.ScaladocInterface", "run", log) (classOf[Array[String]], classOf[xLogger], classOf[Reporter]) ( arguments.toArray[String] : Array[String], log, reporter) } @@ -124,4 +126,9 @@ object AnalyzingCompiler } } private def isSourceName(name: String): Boolean = name.endsWith(".scala") || name.endsWith(".java") -} \ No newline at end of file +} + +private[this] object IgnoreProgress extends CompileProgress { + def startUnit(phase: String, unitPath: String) {} + def advance(current: Int, total: Int) = true +} diff --git a/compile/CompilerArguments.scala b/compile/CompilerArguments.scala index ea9703dd8..f7445dfef 100644 --- a/compile/CompilerArguments.scala +++ b/compile/CompilerArguments.scala @@ -16,7 +16,7 @@ package compiler * this would lead to compiling against the wrong library jar.*/ 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] = + def apply(sources: Seq[File], classpath: Seq[File], outputDirectory: Option[File], options: Seq[String]): Seq[String] = { checkScalaHomeUnset() val cpWithCompiler = finishClasspath(classpath) @@ -24,7 +24,7 @@ final class CompilerArguments(scalaInstance: xsbti.compile.ScalaInstance, cp: xs // We append a random dummy element as workaround. val dummy = "dummy_" + Integer.toHexString(util.Random.nextInt) val classpathOption = Seq("-classpath", if(cpWithCompiler.isEmpty) dummy else absString(cpWithCompiler)) - val outputOption = Seq("-d", outputDirectory.getAbsolutePath) + val outputOption = outputDirectory map {out => Seq("-d", out.getAbsolutePath)} getOrElse Seq() options ++ outputOption ++ bootClasspathOption(hasLibrary(classpath)) ++ classpathOption ++ abs(sources) } def finishClasspath(classpath: Seq[File]): Seq[File] = diff --git a/compile/CompilerCache.scala b/compile/CompilerCache.scala index c807b70c3..43d4d0b69 100644 --- a/compile/CompilerCache.scala +++ b/compile/CompilerCache.scala @@ -2,7 +2,7 @@ package sbt package compiler import xsbti.{Logger => xLogger, Reporter} - import xsbti.compile.{CachedCompiler, CachedCompilerProvider, GlobalsCache} + import xsbti.compile.{CachedCompiler, CachedCompilerProvider, GlobalsCache, Output} import Logger.f0 import java.io.File import java.util.{LinkedHashMap,Map} @@ -13,14 +13,14 @@ private final class CompilerCache(val maxInstances: Int) extends GlobalsCache private[this] def lru[A,B](max: Int) = new LinkedHashMap[A,B](8, 0.75f, true) { override def removeEldestEntry(eldest: Map.Entry[A,B]): Boolean = size > max } - def apply(args: Array[String], forceNew: Boolean, c: CachedCompilerProvider, log: xLogger, reporter: Reporter): CachedCompiler = synchronized + def apply(args: Array[String], output: Output, forceNew: Boolean, c: CachedCompilerProvider, log: xLogger, reporter: Reporter): CachedCompiler = synchronized { val key = CompilerKey(dropSources(args.toList), c.scalaInstance.actualVersion) if(forceNew) cache.remove(key) cache.get(key) match { case null => log.debug(f0("Compiler cache miss. " + key.toString)) - put(key, c.newCachedCompiler(args, log, reporter, /* resident = */ !forceNew)) + put(key, c.newCachedCompiler(args, output, log, reporter, /* resident = */ !forceNew)) case cc => log.debug(f0("Compiler cache hit (" + cc.hashCode.toHexString + "). " + key.toString)) cc @@ -46,7 +46,7 @@ object CompilerCache val fresh: GlobalsCache = new GlobalsCache { def clear() {} - def apply(args: Array[String], forceNew: Boolean, c: CachedCompilerProvider, log: xLogger, reporter: Reporter): CachedCompiler = - c.newCachedCompiler(args, log, reporter, /*resident = */ false) + def apply(args: Array[String], output: Output, forceNew: Boolean, c: CachedCompilerProvider, log: xLogger, reporter: Reporter): CachedCompiler = + c.newCachedCompiler(args, output, log, reporter, /*resident = */ false) } -} \ No newline at end of file +} diff --git a/compile/CompilerOutput.scala b/compile/CompilerOutput.scala new file mode 100755 index 000000000..daa8209d8 --- /dev/null +++ b/compile/CompilerOutput.scala @@ -0,0 +1,24 @@ +/* sbt -- Simple Build Tool + * Copyright 2012 Eugene Vigdorchik + */ + +package sbt +package compiler + + import xsbti.compile.{Output, SingleOutput, MultipleOutput} + import java.io.File + +object CompileOutput { + def apply(dir: File): Output = new SingleOutput { + def outputDirectory = dir + } + + def apply(groups: (File, File)*): Output = new MultipleOutput { + def outputGroups = groups.toArray map { + case (src, out) => new MultipleOutput.OutputGroup { + def sourceDirectory = src + def outputDirectory = out + } + } + } +} diff --git a/compile/JavaCompiler.scala b/compile/JavaCompiler.scala index d782f7cbb..86cc7cb8b 100644 --- a/compile/JavaCompiler.scala +++ b/compile/JavaCompiler.scala @@ -13,8 +13,13 @@ trait JavaCompiler extends xsbti.compile.JavaCompiler { def apply(sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String])(implicit log: Logger) - def compile(sources: Array[File], classpath: Array[File], outputDirectory: File, options: Array[String], maxErrors: Int, log: xsbti.Logger): Unit = + def compile(sources: Array[File], classpath: Array[File], output: xsbti.compile.Output, options: Array[String], maxErrors: Int, log: xsbti.Logger): Unit = { + val outputDirectory = output match { + case single: xsbti.compile.SingleOutput => single.outputDirectory + case _ => throw new RuntimeException("Javac doesn't support multiple output directories") + } apply(sources, classpath, outputDirectory, options)(log) + } } trait Javadoc { @@ -52,7 +57,7 @@ object JavaCompiler def compile(contract: JavacContract, sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String])(implicit log: Logger) { val augmentedClasspath = if(cp.autoBoot) classpath ++ Seq(scalaInstance.libraryJar) else classpath val javaCp = ClasspathOptions.javac(cp.compiler) - val arguments = (new CompilerArguments(scalaInstance, javaCp))(sources, augmentedClasspath, outputDirectory, options) + val arguments = (new CompilerArguments(scalaInstance, javaCp))(sources, augmentedClasspath, Some(outputDirectory), options) log.debug("Calling " + contract.name.capitalize + " with arguments:\n\t" + arguments.mkString("\n\t")) val code: Int = f(contract, arguments, log) log.debug(contract.name + " returned exit code: " + code) @@ -106,4 +111,4 @@ object JavaCompiler // javac's argument file seems to allow naive space escaping with quotes. escaping a quote with a backslash does not work def escapeSpaces(s: String): String = '\"' + normalizeSlash(s) + '\"' def normalizeSlash(s: String) = s.replace(File.separatorChar, '/') -} \ No newline at end of file +} diff --git a/compile/RawCompiler.scala b/compile/RawCompiler.scala index 43bda6f1b..f7ae16468 100644 --- a/compile/RawCompiler.scala +++ b/compile/RawCompiler.scala @@ -19,7 +19,7 @@ class RawCompiler(val scalaInstance: xsbti.compile.ScalaInstance, cp: ClasspathO // but should not be otherwise directly referenced import scala.tools.nsc.Main.{process => _} - val arguments = compilerArguments(sources, classpath, outputDirectory, options) + val arguments = compilerArguments(sources, classpath, Some(outputDirectory), options) log.debug("Plain interface to Scala compiler " + scalaInstance.actualVersion + " with arguments: " + arguments.mkString("\n\t", "\n\t", "")) val mainClass = Class.forName("scala.tools.nsc.Main", true, scalaInstance.loader) val process = mainClass.getMethod("process", classOf[Array[String]]) diff --git a/compile/inc/APIs.scala b/compile/inc/APIs.scala index 451aaf2ec..892fc62aa 100644 --- a/compile/inc/APIs.scala +++ b/compile/inc/APIs.scala @@ -37,7 +37,7 @@ object APIs def empty: APIs = apply(Map.empty, Map.empty) val emptyAPI = new xsbti.api.SourceAPI(Array(), Array()) - val emptyCompilation = new xsbti.api.Compilation(-1, "") + val emptyCompilation = new xsbti.api.Compilation(-1, Array()) val emptySource = new xsbti.api.Source(emptyCompilation, Array(), emptyAPI, 0, false) def getAPI[T](map: Map[T, Source], src: T): Source = map.getOrElse(src, emptySource) } @@ -61,4 +61,4 @@ private class MAPIs(val internal: Map[File, Source], val external: Map[String, S def internalAPI(src: File) = getAPI(internal, src) def externalAPI(ext: String) = getAPI(external, ext) -} \ No newline at end of file +} diff --git a/compile/inc/Compile.scala b/compile/inc/Compile.scala index 2187ae740..8572af8d9 100644 --- a/compile/inc/Compile.scala +++ b/compile/inc/Compile.scala @@ -4,23 +4,23 @@ package sbt package inc -import xsbti.api.{Source, SourceAPI} -import xsbti.compile.DependencyChanges +import xsbti.api.{Source, SourceAPI, Compilation, OutputSetting} +import xsbti.compile.{DependencyChanges, Output, SingleOutput, MultipleOutput} import xsbti.{Position,Problem,Severity} import Logger.{m2o, problem} import java.io.File object IncrementalCompile { - def apply(sources: Set[File], entry: String => Option[File], compile: (Set[File], DependencyChanges, xsbti.AnalysisCallback) => Unit, previous: Analysis, forEntry: File => Option[Analysis], outputPath: File, log: Logger): (Boolean, Analysis) = + def apply(sources: Set[File], entry: String => Option[File], compile: (Set[File], DependencyChanges, xsbti.AnalysisCallback) => Unit, previous: Analysis, forEntry: File => Option[Analysis], output: Output, log: Logger): (Boolean, Analysis) = { val current = Stamps.initial(Stamp.exists, Stamp.hash, Stamp.lastModified) val internalMap = (f: File) => previous.relations.produced(f).headOption val externalAPI = getExternalAPI(entry, forEntry) - Incremental.compile(sources, entry, previous, current, forEntry, doCompile(compile, internalMap, externalAPI, current, outputPath), log) + Incremental.compile(sources, entry, previous, current, forEntry, doCompile(compile, internalMap, externalAPI, current, output), log) } - def doCompile(compile: (Set[File], DependencyChanges, xsbti.AnalysisCallback) => Unit, internalMap: File => Option[File], externalAPI: (File, String) => Option[Source], current: ReadStamps, outputPath: File) = (srcs: Set[File], changes: DependencyChanges) => { - val callback = new AnalysisCallback(internalMap, externalAPI, current, outputPath) + def doCompile(compile: (Set[File], DependencyChanges, xsbti.AnalysisCallback) => Unit, internalMap: File => Option[File], externalAPI: (File, String) => Option[Source], current: ReadStamps, output: Output) = (srcs: Set[File], changes: DependencyChanges) => { + val callback = new AnalysisCallback(internalMap, externalAPI, current, output) compile(srcs, changes, callback) callback.get } @@ -37,10 +37,16 @@ object IncrementalCompile } } } -private final class AnalysisCallback(internalMap: File => Option[File], externalAPI: (File, String) => Option[Source], current: ReadStamps, outputPath: File) extends xsbti.AnalysisCallback +private final class AnalysisCallback(internalMap: File => Option[File], externalAPI: (File, String) => Option[Source], current: ReadStamps, output: Output) extends xsbti.AnalysisCallback { - val time = System.currentTimeMillis - val compilation = new xsbti.api.Compilation(time, outputPath.getAbsolutePath) + val compilation = { + val outputSettings = output match { + case single: SingleOutput => Array(new OutputSetting("/", single.outputDirectory.getAbsolutePath)) + case multi: MultipleOutput => + multi.outputGroups.map(out => new OutputSetting(out.sourceDirectory.getAbsolutePath, out.outputDirectory.getAbsolutePath)).toArray + } + new Compilation(System.currentTimeMillis, outputSettings) + } override def toString = ( List("APIs", "Binary deps", "Products", "Source deps") zip List(apis, binaryDeps, classes, sourceDeps)).map { case (label, map) => label + "\n\t" + map.mkString("\n\t") }.mkString("\n") @@ -146,4 +152,4 @@ private final class AnalysisCallback(internalMap: File => Option[File], external } } def beginSource(source: File) {} -} \ No newline at end of file +} diff --git a/compile/inc/CompileSetup.scala b/compile/inc/CompileSetup.scala index 8426f85bf..18367287e 100644 --- a/compile/inc/CompileSetup.scala +++ b/compile/inc/CompileSetup.scala @@ -3,7 +3,7 @@ */ package sbt - import xsbti.compile.CompileOrder + import xsbti.compile.{ CompileOrder, Output, SingleOutput, MultipleOutput } import java.io.File // this class exists because of Scala's restriction on implicit parameter search. @@ -11,21 +11,32 @@ package sbt // because complexity(Equiv[Seq[String]]) > complexity(Equiv[CompileSetup]) // (6 > 4) final class CompileOptions(val options: Seq[String], val javacOptions: Seq[String]) -final class CompileSetup(val outputDirectory: File, val options: CompileOptions, val compilerVersion: String, val order: CompileOrder) +final class CompileSetup(val output: Output, val options: CompileOptions, val compilerVersion: String, val order: CompileOrder) object CompileSetup { // Equiv[CompileOrder.Value] dominates Equiv[CompileSetup] - implicit def equivCompileSetup(implicit equivFile: Equiv[File], equivOpts: Equiv[CompileOptions], equivComp: Equiv[String]/*, equivOrder: Equiv[CompileOrder]*/): Equiv[CompileSetup] = new Equiv[CompileSetup] { + implicit def equivCompileSetup(implicit equivOutput: Equiv[Output], equivOpts: Equiv[CompileOptions], equivComp: Equiv[String]/*, equivOrder: Equiv[CompileOrder]*/): Equiv[CompileSetup] = new Equiv[CompileSetup] { def equiv(a: CompileSetup, b: CompileSetup) = - equivFile.equiv(a.outputDirectory, b.outputDirectory) && + 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) } - implicit val equivOutputDirectory: Equiv[File] = new Equiv[File] { + implicit val equivFile: Equiv[File] = new Equiv[File] { def equiv(a: File, b: File) = a.getAbsoluteFile == b.getAbsoluteFile } + implicit val equivOutput: Equiv[Output] = new Equiv[Output] { + def equiv(out1: Output, out2: Output) = (out1, out2) match { + case (m1: MultipleOutput, m2: MultipleOutput) => + m1.outputGroups zip (m2.outputGroups) forall { + 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 + } + } implicit val equivOpts: Equiv[CompileOptions] = new Equiv[CompileOptions] { def equiv(a: CompileOptions, b: CompileOptions) = (a.options sameElements b.options) && @@ -38,4 +49,4 @@ object CompileSetup implicit val equivOrder: Equiv[CompileOrder] = new Equiv[CompileOrder] { def equiv(a: CompileOrder, b: CompileOrder) = a == b } -} \ No newline at end of file +} diff --git a/compile/inc/Incremental.scala b/compile/inc/Incremental.scala index dbc4b2d0b..1d228a764 100644 --- a/compile/inc/Incremental.scala +++ b/compile/inc/Incremental.scala @@ -77,7 +77,9 @@ object Incremental } def shortcutSameSource(a: Source, b: Source): Boolean = !a.hash.isEmpty && !b.hash.isEmpty && sameCompilation(a.compilation, b.compilation) && (a.hash.deep equals b.hash.deep) - def sameCompilation(a: Compilation, b: Compilation): Boolean = a.startTime == b.startTime && a.target == b.target + def sameCompilation(a: Compilation, b: Compilation): Boolean = a.startTime == b.startTime && a.outputs.corresponds(b.outputs){ + case (co1, co2) => co1.sourceDirectory == co2.sourceDirectory && co1.outputDirectory == co2.outputDirectory + } def changedInitial(entry: String => Option[File], sources: Set[File], previousAnalysis: Analysis, current: ReadStamps, forEntry: File => Option[Analysis])(implicit equivS: Equiv[Stamp]): InitialChanges = { diff --git a/compile/integration/AggressiveCompile.scala b/compile/integration/AggressiveCompile.scala index 77bfeb620..3a481d444 100644 --- a/compile/integration/AggressiveCompile.scala +++ b/compile/integration/AggressiveCompile.scala @@ -6,6 +6,7 @@ package compiler import inc._ + import scala.annotation.tailrec import java.io.File import classpath.ClasspathUtilities import classfile.Analyze @@ -15,31 +16,31 @@ import inc._ import xsbti.AnalysisCallback import xsbti.api.Source - import xsbti.compile.{CompileOrder, DependencyChanges, GlobalsCache} + 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 getAnalysis: File => Option[Analysis], val definesClass: DefinesClass, + val previousAnalysis: Analysis, val previousSetup: Option[CompileSetup], val currentSetup: CompileSetup, val progress: Option[CompileProgress], val getAnalysis: File => Option[Analysis], val definesClass: DefinesClass, val maxErrors: Int, val compiler: AnalyzingCompiler, val javac: xsbti.compile.JavaCompiler, val cache: GlobalsCache) class AggressiveCompile(cacheFile: File) { - def apply(compiler: AnalyzingCompiler, javac: xsbti.compile.JavaCompiler, sources: Seq[File], classpath: Seq[File], outputDirectory: File, cache: GlobalsCache, options: Seq[String] = Nil, javacOptions: Seq[String] = Nil, analysisMap: File => Option[Analysis] = { _ => 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], output: Output, cache: GlobalsCache, progress: Option[CompileProgress] = None, options: Seq[String] = Nil, javacOptions: Seq[String] = Nil, analysisMap: File => Option[Analysis] = { _ => 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, cache) + val setup = new CompileSetup(output, new CompileOptions(options, javacOptions), compiler.scalaInstance.actualVersion, compileOrder) + compile1(sources, classpath, setup, progress, store, analysisMap, definesClass, compiler, javac, maxErrors, skip, cache) } def withBootclasspath(args: CompilerArguments, classpath: Seq[File]): Seq[File] = args.bootClasspathFor(classpath) ++ args.finishClasspath(classpath) - def compile1(sources: Seq[File], classpath: Seq[File], setup: CompileSetup, store: AnalysisStore, analysis: File => Option[Analysis], definesClass: DefinesClass, compiler: AnalyzingCompiler, javac: xsbti.compile.JavaCompiler, maxErrors: Int, skip: Boolean, cache: GlobalsCache)(implicit log: Logger): Analysis = + def compile1(sources: Seq[File], classpath: Seq[File], setup: CompileSetup, progress: Option[CompileProgress], store: AnalysisStore, analysis: File => Option[Analysis], definesClass: DefinesClass, compiler: AnalyzingCompiler, javac: xsbti.compile.JavaCompiler, maxErrors: Int, skip: Boolean, cache: GlobalsCache)(implicit log: Logger): Analysis = { val (previousAnalysis, previousSetup) = extract(store.get()) if(skip) previousAnalysis else { - val config = new CompileConfiguration(sources, classpath, previousAnalysis, previousSetup, setup, analysis, definesClass, maxErrors, compiler, javac, cache) + val config = new CompileConfiguration(sources, classpath, previousAnalysis, previousSetup, setup, progress, analysis, definesClass, maxErrors, compiler, javac, cache) val (modified, result) = compile2(config) if(modified) store.set(result, setup) @@ -51,36 +52,60 @@ class AggressiveCompile(cacheFile: File) import config._ import currentSetup._ val absClasspath = classpath.map(_.getCanonicalFile) - val apiOption= (api: Either[Boolean, Source]) => api.right.toOption + 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) => { - IO.createDirectory(outputDirectory) + 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, outputDirectory) + 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, outputDirectory, options.options) + val arguments = cArgs(Nil, absClasspath, None, options.options) timed("Scala compilation", log) { - compiler.compile(sources, changes, arguments, callback, maxErrors, cache, log) + compiler.compile(sources, changes, arguments, output, callback, maxErrors, cache, log, progress) } } def compileJava() = if(!javaSrcs.isEmpty) { import Path._ - val loader = ClasspathUtilities.toLoader(searchClasspath) - def readAPI(source: File, classes: Seq[Class[_]]) { callback.api(source, ClassToAPI(classes)) } - timed("Java compilation and analysis", log) { - Analyze(outputDirectory, javaSrcs, log)(callback, loader, readAPI) { - timed("Java compilation", log) { - javac.compile(javaSrcs.toArray, absClasspath.toArray, outputDirectory, options.javacOptions.toArray, maxErrors, log) + @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) { + javac.compile(javaSrcs.toArray, absClasspath.toArray, output, options.javacOptions.toArray, maxErrors, log) + } + + def readAPI(source: File, classes: Seq[Class[_]]) { callback.api(source, ClassToAPI(classes)) } + + timed("Java analysis", log) { + for ((classesFinder, oldClasses, srcs) <- memo) { + val newClasses = Set(classesFinder.get: _*) -- oldClasses + Analyze(newClasses.toSeq, srcs, log)(callback, loader, readAPI) } } } @@ -92,7 +117,11 @@ class AggressiveCompile(cacheFile: File) case Some(previous) if equiv.equiv(previous, currentSetup) => previousAnalysis case _ => Incremental.prune(sourcesSet, previousAnalysis) } - IncrementalCompile(sourcesSet, entry, compile0, analysis, getAnalysis, outputDirectory, log) + IncrementalCompile(sourcesSet, entry, compile0, analysis, getAnalysis, output, log) + } + 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 = { @@ -102,13 +131,13 @@ class AggressiveCompile(cacheFile: File) log.debug(label + " took " + (elapsed/1e9) + " s") result } - private[this] def logInputs(log: Logger, javaCount: Int, scalaCount: Int, out: File) + 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 " + out.getAbsolutePath + "...")) + log.info(combined.mkString("Compiling ", " and ", " to " + outputDirs.map(_.getAbsolutePath).mkString(",") + "...")) } private def extract(previous: Option[(Analysis, CompileSetup)]): (Analysis, Option[CompileSetup]) = previous match diff --git a/compile/integration/IncrementalCompiler.scala b/compile/integration/IncrementalCompiler.scala index 7b84817f0..5d0224518 100644 --- a/compile/integration/IncrementalCompiler.scala +++ b/compile/integration/IncrementalCompiler.scala @@ -17,7 +17,7 @@ object IC extends IncrementalCompiler[Analysis, AnalyzingCompiler] val agg = new AggressiveCompile(setup.cacheFile) val aMap = (f: File) => m2o(analysisMap(f)) val defClass = (f: File) => { val dc = definesClass(f); (name: String) => dc.apply(name) } - agg(scalac, javac, sources, classpath, classesDirectory, cache, scalacOptions, javacOptions, aMap, defClass, maxErrors, order, skip)(log) + agg(scalac, javac, sources, classpath, output, cache, m2o(progress), 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) diff --git a/compile/interface/Analyzer.scala b/compile/interface/Analyzer.scala index e20dd46cc..43fa8da6c 100644 --- a/compile/interface/Analyzer.scala +++ b/compile/interface/Analyzer.scala @@ -28,8 +28,6 @@ final class Analyzer(val global: CallbackGlobal) extends Compat def name = Analyzer.name def run { - val outputDirectory = new File(global.settings.outdir.value) - for(unit <- currentRun.units if !unit.isJava) { // build dependencies structure @@ -64,8 +62,7 @@ final class Analyzer(val global: CallbackGlobal) extends Compat val sym = iclass.symbol def addGenerated(separatorRequired: Boolean) { - val classFile = fileForClass(outputDirectory, sym, separatorRequired) - if(classFile.exists) + for(classFile <- outputDirs map (fileForClass(_, sym, separatorRequired)) find (_.exists)) callback.generatedClass(sourceFile, classFile, className(sym, '.', separatorRequired)) } if(sym.isModuleClass && !sym.isImplClass) @@ -152,4 +149,4 @@ abstract class Compat private[this] def sourceCompatibilityOnly: Nothing = throw new RuntimeException("For source compatibility only: should not get here.") private[this] final implicit def miscCompat(n: AnyRef): MiscCompat = new MiscCompat -} \ No newline at end of file +} diff --git a/compile/interface/CompilerInterface.scala b/compile/interface/CompilerInterface.scala index 8797e2efb..afaed92a4 100644 --- a/compile/interface/CompilerInterface.scala +++ b/compile/interface/CompilerInterface.scala @@ -4,7 +4,7 @@ package xsbt import xsbti.{AnalysisCallback,Logger,Problem,Reporter,Severity} -import xsbti.compile.{CachedCompiler, DependencyChanges} +import xsbti.compile._ import scala.tools.nsc.{backend, io, reporters, symtab, util, Phase, Global, Settings, SubComponent} import backend.JavaPlatform import scala.tools.util.PathResolver @@ -19,19 +19,25 @@ import java.io.File final class CompilerInterface { - def newCompiler(options: Array[String], initialLog: Logger, initialDelegate: Reporter, resident: Boolean): CachedCompiler = - new CachedCompiler0(options, new WeakLog(initialLog, initialDelegate), resident) + def newCompiler(options: Array[String], output: Output, initialLog: Logger, initialDelegate: Reporter, resident: Boolean): CachedCompiler = + new CachedCompiler0(options, output, new WeakLog(initialLog, initialDelegate), resident) - def run(sources: Array[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger, delegate: Reporter, cached: CachedCompiler): Unit = - cached.run(sources, changes, callback, log, delegate) + def run(sources: Array[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger, delegate: Reporter, progress: CompileProgress, cached: CachedCompiler): Unit = + cached.run(sources, changes, callback, log, delegate, progress) } // for compatibility with Scala versions without Global.registerTopLevelSym (2.8.1 and earlier) sealed trait GlobalCompat { self: Global => def registerTopLevelSym(sym: Symbol): Unit } -sealed abstract class CallbackGlobal(settings: Settings, reporter: reporters.Reporter) extends Global(settings, reporter) with GlobalCompat { +sealed abstract class CallbackGlobal(settings: Settings, reporter: reporters.Reporter, output: Output) extends Global(settings, reporter) with GlobalCompat { def callback: AnalysisCallback def findClass(name: String): Option[(AbstractFile,Boolean)] + lazy val outputDirs: Iterable[File] = { + output match { + case single: SingleOutput => List(single.outputDirectory) + case multi: MultipleOutput => multi.outputGroups.toStream map (_.outputDirectory) + } + } } class InterfaceCompileFailed(val arguments: Array[String], val problems: Array[Problem], override val toString: String) extends xsbti.CompileFailed @@ -49,9 +55,17 @@ private final class WeakLog(private[this] var log: Logger, private[this] var del } } -private final class CachedCompiler0(args: Array[String], initialLog: WeakLog, resident: Boolean) extends CachedCompiler +private final class CachedCompiler0(args: Array[String], output: Output, initialLog: WeakLog, resident: Boolean) extends CachedCompiler { val settings = new Settings(s => initialLog(s)) + output match { + case multi: MultipleOutput => + for (out <- multi.outputGroups) + settings.outputDirs.add(out.sourceDirectory.getAbsolutePath, out.outputDirectory.getAbsolutePath) + case single: SingleOutput => + settings.outputDirs.setSingleOutput(single.outputDirectory.getAbsolutePath) + } + val command = Command(args.toList, settings) private[this] val dreporter = DelegatingReporter(settings, initialLog.reporter) try { @@ -65,14 +79,14 @@ private final class CachedCompiler0(args: Array[String], initialLog: WeakLog, re def noErrors(dreporter: DelegatingReporter) = !dreporter.hasErrors && command.ok - def run(sources: Array[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger, delegate: Reporter): Unit = synchronized + def run(sources: Array[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger, delegate: Reporter, progress: CompileProgress): Unit = synchronized { debug(log, "Running cached compiler " + hashCode.toHexString + ", interfacing (CompilerInterface) with Scala compiler " + scala.tools.nsc.Properties.versionString) val dreporter = DelegatingReporter(settings, delegate) - try { run(sources.toList, changes, callback, log, dreporter) } + try { run(sources.toList, changes, callback, log, dreporter, progress) } finally { dreporter.dropDelegate() } } - private[this] def run(sources: List[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger, dreporter: DelegatingReporter) + private[this] def run(sources: List[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger, dreporter: DelegatingReporter, compileProgress: CompileProgress) { if(command.shouldStopWithInfo) { @@ -84,8 +98,16 @@ private final class CachedCompiler0(args: Array[String], initialLog: WeakLog, re debug(log, args.mkString("Calling Scala compiler with arguments (CompilerInterface):\n\t", "\n\t", "")) compiler.set(callback, dreporter) try { - val run = new compiler.Run - if(resident) compiler.reload(changes) + val run = new compiler.Run { + override def informUnitStarting(phase: Phase, unit: compiler.CompilationUnit) { + compileProgress.startUnit(phase.name, unit.source.path) + } + override def progress(current: Int, total: Int) { + if (!compileProgress.advance(current, total)) + cancel + } + } + if (resident) compiler.reload(changes) val sortedSourceFiles = sources.map(_.getAbsolutePath).sortWith(_ < _) run compile sortedSourceFiles processUnreportedWarnings(run) @@ -113,7 +135,7 @@ private final class CachedCompiler0(args: Array[String], initialLog: WeakLog, re if(!warnings.isEmpty) compiler.logUnreportedWarnings(warnings.map(cw => ("" /*cw.what*/, cw.warnings.toList))) } - object compiler extends CallbackGlobal(command.settings, dreporter) + object compiler extends CallbackGlobal(command.settings, dreporter, output) { object dummy // temporary fix for #4426 object sbtAnalyzer extends @@ -145,7 +167,6 @@ private final class CachedCompiler0(args: Array[String], initialLog: WeakLog, re def name = phaseName } - val out = new File(settings.outdir.value) override lazy val phaseDescriptors = { phasesSet += sbtAnalyzer @@ -187,8 +208,9 @@ private final class CachedCompiler0(args: Array[String], initialLog: WeakLog, re def getOutputClass(name: String): Option[AbstractFile] = { - val f = new File(out, name.replace('.', '/') + ".class") - if(f.exists) Some(AbstractFile.getFile(f)) else None + // This could be improved if a hint where to look is given. + val className = name.replace('.', '/') + ".class" + outputDirs map (new File(_, className)) find (_.exists) map (AbstractFile.getFile(_)) } def findOnClassPath(name: String): Option[AbstractFile] = @@ -213,7 +235,7 @@ private final class CachedCompiler0(args: Array[String], initialLog: WeakLog, re // must drop whole CachedCompiler when !changes.isEmpty def reload(changes: DependencyChanges) { - inv(settings.outdir.value) + for ((_,out) <- settings.outputDirs.outputs) inv(out.path) } private[this] var toForget = mutable.Set[Symbol]() @@ -358,4 +380,4 @@ private final class CachedCompiler0(args: Array[String], initialLog: WeakLog, re def sourcepaths = delegate.sourcepaths } } -} \ No newline at end of file +} diff --git a/compile/persist/AnalysisFormats.scala b/compile/persist/AnalysisFormats.scala index 15c910eb4..a80e4fd45 100644 --- a/compile/persist/AnalysisFormats.scala +++ b/compile/persist/AnalysisFormats.scala @@ -6,10 +6,12 @@ package inc import xsbti.api.Source import xsbti.{Position,Problem,Severity} - import xsbti.compile.CompileOrder + import xsbti.compile.{CompileOrder, Output => APIOutput, SingleOutput, MultipleOutput} + import MultipleOutput.OutputGroup import java.io.File import sbinary._ import DefaultProtocol._ + import DefaultProtocol.tuple2Format import Logger.{m2o, position, problem} object AnalysisFormats @@ -63,8 +65,22 @@ object AnalysisFormats wrap[Severity, Byte]( _.ordinal.toByte, b => Severity.values.apply(b.toInt) ) - implicit def setupFormat(implicit outDirF: Format[File], optionF: Format[CompileOptions], compilerVersion: Format[String], orderF: Format[CompileOrder]): Format[CompileSetup] = - asProduct4[CompileSetup, File, CompileOptions, String, CompileOrder]( (a,b,c,d) => new CompileSetup(a,b,c,d) )(s => (s.outputDirectory, s.options, s.compilerVersion, s.order))(outDirF, optionF, compilerVersion, orderF) + implicit def setupFormat(implicit outputF: Format[APIOutput], optionF: Format[CompileOptions], compilerVersion: Format[String], orderF: Format[CompileOrder]): Format[CompileSetup] = + asProduct4[CompileSetup, APIOutput, CompileOptions, String, CompileOrder]( (a,b,c,d) => new CompileSetup(a,b,c,d) )(s => (s.output, s.options, s.compilerVersion, s.order))(outputF, optionF, compilerVersion, orderF) + + implicit val outputGroupFormat: Format[OutputGroup] = + asProduct2((a: File,b: File) => new OutputGroup{def sourceDirectory = a; def outputDirectory = b}) { out => (out.sourceDirectory, out.outputDirectory) }(fileFormat, fileFormat) + implicit val multipleOutputFormat: Format[MultipleOutput] = + wrap[MultipleOutput, Array[OutputGroup]]( + (_.outputGroups), + { groups => new MultipleOutput { def outputGroups = groups } } + ) + implicit val singleOutputFormat: Format[SingleOutput] = + wrap[SingleOutput, File]( + (_.outputDirectory), + {out => new SingleOutput{def outputDirectory = out}} + )(fileFormat) + implicit val outputFormat: Format[APIOutput] = asUnion(singleOutputFormat, multipleOutputFormat) implicit def stampsFormat(implicit prodF: Format[Map[File, Stamp]], srcF: Format[Map[File, Stamp]], binF: Format[Map[File, Stamp]], nameF: Format[Map[File, String]]): Format[Stamps] = asProduct4( Stamps.apply _ )( s => (s.products, s.sources, s.binaries, s.classNames) )(prodF, srcF, binF, nameF) diff --git a/interface/other b/interface/other index a8f5ba49c..111896f0b 100644 --- a/interface/other +++ b/interface/other @@ -9,9 +9,13 @@ SourceAPI packages : Package* definitions: Definition* +OutputSetting + sourceDirectory: String + outputDirectory: String + Compilation startTime: Long - target: String + outputs: OutputSetting* Package name: String diff --git a/interface/src/main/java/xsbti/compile/CachedCompiler.java b/interface/src/main/java/xsbti/compile/CachedCompiler.java index 2f97e395b..97a1a33b5 100644 --- a/interface/src/main/java/xsbti/compile/CachedCompiler.java +++ b/interface/src/main/java/xsbti/compile/CachedCompiler.java @@ -7,5 +7,5 @@ import java.io.File; public interface CachedCompiler { - public void run(File[] sources, DependencyChanges cpChanges, AnalysisCallback callback, Logger log, Reporter delegate); + public void run(File[] sources, DependencyChanges cpChanges, AnalysisCallback callback, Logger log, Reporter delegate, CompileProgress progress); } diff --git a/interface/src/main/java/xsbti/compile/CachedCompilerProvider.java b/interface/src/main/java/xsbti/compile/CachedCompilerProvider.java index f32c54e3d..313f27505 100644 --- a/interface/src/main/java/xsbti/compile/CachedCompilerProvider.java +++ b/interface/src/main/java/xsbti/compile/CachedCompilerProvider.java @@ -6,5 +6,5 @@ import xsbti.Reporter; public interface CachedCompilerProvider { ScalaInstance scalaInstance(); - CachedCompiler newCachedCompiler(String[] arguments, Logger log, Reporter reporter, boolean resident); + CachedCompiler newCachedCompiler(String[] arguments, Output output, Logger log, Reporter reporter, boolean resident); } \ No newline at end of file diff --git a/interface/src/main/java/xsbti/compile/CompileProgress.java b/interface/src/main/java/xsbti/compile/CompileProgress.java new file mode 100755 index 000000000..902a50018 --- /dev/null +++ b/interface/src/main/java/xsbti/compile/CompileProgress.java @@ -0,0 +1,7 @@ +package xsbti.compile; + +public interface CompileProgress { + void startUnit(String phase, String unitPath); + + boolean advance(int current, int total); +} \ No newline at end of file diff --git a/interface/src/main/java/xsbti/compile/GlobalsCache.java b/interface/src/main/java/xsbti/compile/GlobalsCache.java index 1e070a1f1..a3a412836 100644 --- a/interface/src/main/java/xsbti/compile/GlobalsCache.java +++ b/interface/src/main/java/xsbti/compile/GlobalsCache.java @@ -5,6 +5,6 @@ import xsbti.Reporter; public interface GlobalsCache { - public CachedCompiler apply(String[] args, boolean forceNew, CachedCompilerProvider provider, Logger log, Reporter reporter); + public CachedCompiler apply(String[] args, Output output, boolean forceNew, CachedCompilerProvider provider, Logger log, Reporter reporter); public void clear(); } diff --git a/interface/src/main/java/xsbti/compile/JavaCompiler.java b/interface/src/main/java/xsbti/compile/JavaCompiler.java index 05bafdfe1..c9947c700 100644 --- a/interface/src/main/java/xsbti/compile/JavaCompiler.java +++ b/interface/src/main/java/xsbti/compile/JavaCompiler.java @@ -11,5 +11,5 @@ public interface JavaCompiler /** Compiles Java sources using the provided classpath, output directory, and additional options. * If supported, the number of reported errors should be limited to `maximumErrors`. * Output should be sent to the provided logger.*/ - void compile(File[] sources, File[] classpath, File outputDirectory, String[] options, int maximumErrors, Logger log); + void compile(File[] sources, File[] classpath, Output output, String[] options, int maximumErrors, Logger log); } diff --git a/interface/src/main/java/xsbti/compile/MultipleOutput.java b/interface/src/main/java/xsbti/compile/MultipleOutput.java new file mode 100755 index 000000000..6ba3479e6 --- /dev/null +++ b/interface/src/main/java/xsbti/compile/MultipleOutput.java @@ -0,0 +1,20 @@ +package xsbti.compile; + +import java.io.File; + +public interface MultipleOutput extends Output { + + interface OutputGroup { + /** The directory where source files are stored for this group. + * Source directories should uniquely identify the group for a source file. */ + File sourceDirectory(); + + /** The directory where class files should be generated. + * Incremental compilation will manage the class files in this directory. + * In particular, outdated class files will be deleted before compilation. + * It is important that this directory is exclusively used for one set of sources. */ + File outputDirectory(); + } + + OutputGroup[] outputGroups(); +} \ No newline at end of file diff --git a/interface/src/main/java/xsbti/compile/Options.java b/interface/src/main/java/xsbti/compile/Options.java index 9e0d03e98..877ff5ebf 100644 --- a/interface/src/main/java/xsbti/compile/Options.java +++ b/interface/src/main/java/xsbti/compile/Options.java @@ -13,11 +13,8 @@ public interface Options * This should include Scala and Java sources, which are identified by their extension. */ File[] sources(); - /** The directory where class files should be generated. - * Incremental compilation will manage the class files in this directory. - * In particular, outdated class files will be deleted before compilation. - * It is important that this directory is exclusively used for one set of sources. */ - File classesDirectory(); + /** Output for the compilation. */ + Output output(); /** The options to pass to the Scala compiler other than the sources and classpath to use. */ String[] options(); diff --git a/interface/src/main/java/xsbti/compile/Output.java b/interface/src/main/java/xsbti/compile/Output.java new file mode 100755 index 000000000..c7f28a2f1 --- /dev/null +++ b/interface/src/main/java/xsbti/compile/Output.java @@ -0,0 +1,9 @@ +package xsbti.compile; + +import java.io.File; +/** Abstract interface denoting the output of the compilation. Inheritors are SingleOutput with a global output directory and + * MultipleOutput that specifies the output directory per source file. + */ +public interface Output +{ +} diff --git a/interface/src/main/java/xsbti/compile/Setup.java b/interface/src/main/java/xsbti/compile/Setup.java index cf261aa7a..1efc08782 100644 --- a/interface/src/main/java/xsbti/compile/Setup.java +++ b/interface/src/main/java/xsbti/compile/Setup.java @@ -23,4 +23,7 @@ public interface Setup File cacheFile(); GlobalsCache cache(); + + /** If returned, the progress that should be used to report scala compilation to. */ + Maybe progress(); } diff --git a/interface/src/main/java/xsbti/compile/SingleOutput.java b/interface/src/main/java/xsbti/compile/SingleOutput.java new file mode 100755 index 000000000..cb200c9b7 --- /dev/null +++ b/interface/src/main/java/xsbti/compile/SingleOutput.java @@ -0,0 +1,12 @@ +package xsbti.compile; + +import java.io.File; + +public interface SingleOutput extends Output { + + /** The directory where class files should be generated. + * Incremental compilation will manage the class files in this directory. + * In particular, outdated class files will be deleted before compilation. + * It is important that this directory is exclusively used for one set of sources. */ + File outputDirectory(); +} \ No newline at end of file diff --git a/main/actions/Compiler.scala b/main/actions/Compiler.scala index 89c13cea1..7d6da3c4c 100644 --- a/main/actions/Compiler.scala +++ b/main/actions/Compiler.scala @@ -76,6 +76,6 @@ object Compiler import in.incSetup._ val agg = new AggressiveCompile(cacheFile) - agg(scalac, javac, sources, classpath, classesDirectory, cache, options, javacOptions, analysisMap, definesClass, maxErrors, order, skip)(log) + agg(scalac, javac, sources, classpath, CompileOutput(classesDirectory), cache, None, options, javacOptions, analysisMap, definesClass, maxErrors, order, skip)(log) } } diff --git a/util/classfile/Analyze.scala b/util/classfile/Analyze.scala index 95942525c..e6b4ad63f 100644 --- a/util/classfile/Analyze.scala +++ b/util/classfile/Analyze.scala @@ -14,76 +14,64 @@ import java.lang.reflect.Modifier.{STATIC, PUBLIC, ABSTRACT} private[sbt] object Analyze { - def apply[T](outputDirectory: File, sources: Seq[File], log: Logger)(analysis: xsbti.AnalysisCallback, loader: ClassLoader, readAPI: (File,Seq[Class[_]]) => Unit)(compile: => Unit) + def apply[T](newClasses: Seq[File], sources: Seq[File], log: Logger)(analysis: xsbti.AnalysisCallback, loader: ClassLoader, readAPI: (File,Seq[Class[_]]) => Unit) { val sourceMap = sources.toSet[File].groupBy(_.getName) - val classesFinder = PathFinder(outputDirectory) ** "*.class" - val existingClasses = classesFinder.get def load(tpe: String, errMsg: => Option[String]): Option[Class[_]] = try { Some(Class.forName(tpe, false, loader)) } catch { case e => errMsg.foreach(msg => log.warn(msg + " : " +e.toString)); None } - // runs after compilation - def analyze() - { - val allClasses = Set(classesFinder.get: _*) - val newClasses = allClasses -- existingClasses - - val productToSource = new mutable.HashMap[File, File] - val sourceToClassFiles = new mutable.HashMap[File, Buffer[ClassFile]] + val productToSource = new mutable.HashMap[File, File] + val sourceToClassFiles = new mutable.HashMap[File, Buffer[ClassFile]] - // parse class files and assign classes to sources. This must be done before dependencies, since the information comes - // as class->class dependencies that must be mapped back to source->class dependencies using the source+class assignment - for(newClass <- newClasses; - classFile = Parser(newClass); - sourceFile <- classFile.sourceFile orElse guessSourceName(newClass.getName); - source <- guessSourcePath(sourceMap, classFile, log)) - { - analysis.beginSource(source) - analysis.generatedClass(source, newClass, classFile.className) - productToSource(newClass) = source - sourceToClassFiles.getOrElseUpdate(source, new ArrayBuffer[ClassFile]) += classFile - } + // parse class files and assign classes to sources. This must be done before dependencies, since the information comes + // as class->class dependencies that must be mapped back to source->class dependencies using the source+class assignment + for(newClass <- newClasses; + classFile = Parser(newClass); + sourceFile <- classFile.sourceFile orElse guessSourceName(newClass.getName); + source <- guessSourcePath(sourceMap, classFile, log)) + { + analysis.beginSource(source) + analysis.generatedClass(source, newClass, classFile.className) + productToSource(newClass) = source + sourceToClassFiles.getOrElseUpdate(source, new ArrayBuffer[ClassFile]) += classFile + } - // get class to class dependencies and map back to source to class dependencies - for( (source, classFiles) <- sourceToClassFiles ) + // get class to class dependencies and map back to source to class dependencies + for( (source, classFiles) <- sourceToClassFiles ) + { + def processDependency(tpe: String) { - def processDependency(tpe: String) + trapAndLog(log) { - trapAndLog(log) + for (url <- Option(loader.getResource(tpe.replace('.', '/') + ClassExt)); file <- IO.urlAsFile(url)) { - for (url <- Option(loader.getResource(tpe.replace('.', '/') + ClassExt)); file <- IO.urlAsFile(url)) + if(url.getProtocol == "jar") + analysis.binaryDependency(file, tpe, source) + else { - if(url.getProtocol == "jar") - analysis.binaryDependency(file, tpe, source) - else + assume(url.getProtocol == "file") + productToSource.get(file) match { - assume(url.getProtocol == "file") - productToSource.get(file) match - { - case Some(dependsOn) => analysis.sourceDependency(dependsOn, source) - case None => analysis.binaryDependency(file, tpe, source) - } + case Some(dependsOn) => analysis.sourceDependency(dependsOn, source) + case None => analysis.binaryDependency(file, tpe, source) } } } } + } - classFiles.flatMap(_.types).toSet.foreach(processDependency) - readAPI(source, classFiles.toSeq.flatMap(c => load(c.className, Some("Error reading API from class file") ))) - analysis.endSource(source) - } - - for( source <- sources filterNot sourceToClassFiles.keySet ) { - analysis.beginSource(source) - analysis.api(source, new xsbti.api.SourceAPI(Array(), Array())) - analysis.endSource(source) - } + classFiles.flatMap(_.types).toSet.foreach(processDependency) + readAPI(source, classFiles.toSeq.flatMap(c => load(c.className, Some("Error reading API from class file") ))) + analysis.endSource(source) + } + + for( source <- sources filterNot sourceToClassFiles.keySet ) { + analysis.beginSource(source) + analysis.api(source, new xsbti.api.SourceAPI(Array(), Array())) + analysis.endSource(source) } - - compile - analyze() } private def trapAndLog(log: Logger)(execute: => Unit) {