From d77930394f02f3a6c14e56e1c8b14624f8fd1b59 Mon Sep 17 00:00:00 2001 From: Grzegorz Kossakowski Date: Fri, 19 Jul 2013 14:39:26 -0700 Subject: [PATCH] Handle compilation cancellation properly. Incremental compiler didn't have any explicit logic to handle cancelled compilation so it would go into inconsistent state. Specifically, what would happen is that it would treat cancelled compilation as a compilation that finished normally and try to produce a new Analysis object out of partial information collected in AnalysisCallback. The most obvious outcome would be that the new Analysis would contain latest hashes for source files. The next time incremental compiler was asked to recompile the same files that it didn't recompile due to cancelled compilation it would think they were already successfully compiled and would do nothing. We fix that problem by following the same logic that handles compilation errors, cleans up partial results (produced class files) and makes sure that no Analysis is created out of broken state. We do that by introducing a new exception `CompileCancelled` and throwing it at the same spot as an exception signalizing compilation errors is being thrown. We also modify `IncrementalCompile` to catch that exception and gracefully return as there was no compilation invoked. NOTE: In case there were compilation errors reported _before_ compilation cancellations was requested we'll still report them using an old mechanism so partial errors are not lost in case of cancelled compilation. --- compile/inc/src/main/scala/sbt/inc/Compile.scala | 10 +++++++++- .../src/main/scala/xsbt/CompilerInterface.scala | 11 +++++++++++ interface/src/main/java/xsbti/CompileCancelled.java | 9 +++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 interface/src/main/java/xsbti/CompileCancelled.java diff --git a/compile/inc/src/main/scala/sbt/inc/Compile.scala b/compile/inc/src/main/scala/sbt/inc/Compile.scala index 61a19d3dd..e5dae387c 100644 --- a/compile/inc/src/main/scala/sbt/inc/Compile.scala +++ b/compile/inc/src/main/scala/sbt/inc/Compile.scala @@ -22,7 +22,15 @@ object IncrementalCompile 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, output, options), log, options) + try { + Incremental.compile(sources, entry, previous, current, forEntry, doCompile(compile, internalMap, externalAPI, current, output, options), log, options) + } catch { + case e: xsbti.CompileCancelled => + log.info("Compilation has been cancelled") + // in case compilation got cancelled potential partial compilation results (e.g. produced classs files) got rolled back + // and we can report back as there was no change (false) and return a previous Analysis which is still up-to-date + (false, previous) + } } def doCompile(compile: (Set[File], DependencyChanges, xsbti.AnalysisCallback) => Unit, internalMap: File => Option[File], externalAPI: (File, String) => Option[Source], current: ReadStamps, output: Output, options: IncOptions) = (srcs: Set[File], changes: DependencyChanges) => { diff --git a/compile/interface/src/main/scala/xsbt/CompilerInterface.scala b/compile/interface/src/main/scala/xsbt/CompilerInterface.scala index 3a02bd138..7f94d1dab 100644 --- a/compile/interface/src/main/scala/xsbt/CompilerInterface.scala +++ b/compile/interface/src/main/scala/xsbt/CompilerInterface.scala @@ -50,6 +50,8 @@ sealed abstract class CallbackGlobal(settings: Settings, reporter: reporters.Rep } class InterfaceCompileFailed(val arguments: Array[String], val problems: Array[Problem], override val toString: String) extends xsbti.CompileFailed +class InterfaceCompileCancelled(val arguments: Array[String], override val toString: String) extends xsbti.CompileCancelled + private final class WeakLog(private[this] var log: Logger, private[this] var delegate: Reporter) { def apply(message: String) { @@ -124,12 +126,21 @@ private final class CachedCompiler0(args: Array[String], output: Output, initial } dreporter.printSummary() if(!noErrors(dreporter)) handleErrors(dreporter, log) + // the case where we cancelled compilation _after_ some compilation errors got reported + // will be handled by line above so errors still will be reported properly just potentially not + // all of them (because we cancelled the compilation) + if (dreporter.cancelled) handleCompilationCancellation(dreporter, log) } def handleErrors(dreporter: DelegatingReporter, log: Logger): Nothing = { debug(log, "Compilation failed (CompilerInterface)") throw new InterfaceCompileFailed(args, dreporter.problems, "Compilation failed") } + def handleCompilationCancellation(dreporter: DelegatingReporter, log: Logger): Nothing = { + assert(dreporter.cancelled, "We should get here only if when compilation got cancelled") + debug(log, "Compilation cancelled (CompilerInterface)") + throw new InterfaceCompileCancelled(args, "Compilation has been cancelled") + } def processUnreportedWarnings(run: compiler.Run) { // allConditionalWarnings and the ConditionalWarning class are only in 2.10+ diff --git a/interface/src/main/java/xsbti/CompileCancelled.java b/interface/src/main/java/xsbti/CompileCancelled.java new file mode 100644 index 000000000..bcd3695dd --- /dev/null +++ b/interface/src/main/java/xsbti/CompileCancelled.java @@ -0,0 +1,9 @@ +package xsbti; + +/** + * An exception thrown when compilation cancellation has been requested during + * Scala compiler run. + */ +public abstract class CompileCancelled extends RuntimeException { + public abstract String[] arguments(); +}