Permalink
Browse files

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.
  • Loading branch information...
1 parent 94e6bb4 commit d77930394f02f3a6c14e56e1c8b14624f8fd1b59 @gkossakowski gkossakowski committed Jul 19, 2013
@@ -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) => {
@@ -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+
@@ -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();
+}

0 comments on commit d779303

Please sign in to comment.