Permalink
Browse files

Failed task execution should still preserve State changes. Fixes #804.

Candidate for inclusion in 0.13.0 if there is another RC, otherwise
scheduled for 0.13.1.
  • Loading branch information...
1 parent 3e7bedd commit 919d0ac63d83a24adc2c99087bf2fe5c231db638 @harrah harrah committed Jul 3, 2013
@@ -75,23 +75,11 @@ object MainLoop
case Left(t) => handleException(t, state)
}
- import ExceptionCategory._
+ @deprecated("Use State.handleError", "0.13.0")
+ def handleException(e: Throwable, s: State): State = s.handleError(e)
- def handleException(e: Throwable, s: State): State =
- handleException(e, s, s.log)
- def handleException(t: Throwable, s: State, log: Logger): State =
- {
- ExceptionCategory(t) match {
- case AlreadyHandled => ()
- case m: MessageOnly => log.error(m.message)
- case f: Full => logFullException(f.exception, log)
- }
- s.fail
- }
- def logFullException(e: Throwable, log: Logger)
- {
- log.trace(e)
- log.error(ErrorHandling reducedToString e)
- log.error("Use 'last' for the full log.")
- }
+ @deprecated("Use State.handleError", "0.13.0")
+ def handleException(t: Throwable, s: State, log: Logger): State = State.handleException(t, s, log)
+
+ def logFullException(e: Throwable, log: Logger): Unit = State.logFullException(e, log)
}
@@ -79,6 +79,12 @@ trait StateOps {
/** Marks the currently executing command as failing. This triggers failure handling by the command processor. See also `State.onFailure`*/
def fail: State
+ /** Marks the currently executing command as failing due to the given exception.
+ * This displays the error appropriately and triggers failure handling by the command processor.
+ * Note that this does not throw an exception and returns normally.
+ * It is only once control is returned to the command processor that failure handling at the command level occurs. */
+ def handleError(t: Throwable): State
+
/** Schedules `newCommands` to be run after any remaining commands. */
def ++ (newCommands: Seq[Command]): State
/** Schedules `newCommand` to be run after any remaining commands. */
@@ -190,6 +196,7 @@ object State
def has(key: AttributeKey[_]) = s.attributes contains key
def remove(key: AttributeKey[_]) = s.copy(attributes = s.attributes remove key)
def log = s.globalLogging.full
+ def handleError(t: Throwable): State = handleException(t, s, log)
def fail =
{
val remaining = s.remainingCommands.dropWhile(_ != FailureWall)
@@ -205,6 +212,7 @@ object State
case None => noHandler
}
+
def addExitHook(act: => Unit): State =
s.copy(exitHooks = s.exitHooks + ExitHook(act))
def runExitHooks(): State = {
@@ -221,4 +229,22 @@ object State
def initializeClassLoaderCache = s.put(BasicKeys.classLoaderCache, newClassLoaderCache)
private[this] def newClassLoaderCache = new classpath.ClassLoaderCache(s.configuration.provider.scalaProvider.launcher.topLoader)
}
+
+ import ExceptionCategory._
+
+ private[sbt] def handleException(t: Throwable, s: State, log: Logger): State =
+ {
+ ExceptionCategory(t) match {
+ case AlreadyHandled => ()
+ case m: MessageOnly => log.error(m.message)
+ case f: Full => logFullException(f.exception, log)
+ }
+ s.fail
+ }
+ private[sbt] def logFullException(e: Throwable, log: Logger)
+ {
+ log.trace(e)
+ log.error(ErrorHandling reducedToString e)
+ log.error("Use 'last' for the full log.")
+ }
}
@@ -47,8 +47,8 @@ final object Aggregation
val log = state.log
val extracted = Project extract state
val success = results match { case Value(_) => true; case Inc(_) => false }
- try { EvaluateTask.onResult(results, log) { results => if(show.taskValues) printSettings(results, show.print) } }
- finally { if(show.success) printSuccess(start, stop, extracted, success, log) }
+ results.toEither.right.foreach { r => if(show.taskValues) printSettings(r, show.print) }
+ if(show.success) printSuccess(start, stop, extracted, success, log)
}
def timedRun[T](s: State, ts: Values[Task[T]], extra: DummyTaskMap): Complete[T] =
{
@@ -73,7 +73,10 @@ final object Aggregation
def runTasks[HL <: HList, T](s: State, structure: BuildStructure, ts: Values[Task[T]], extra: DummyTaskMap, show: ShowConfig)(implicit display: Show[ScopedKey[_]]): State = {
val complete = timedRun[T](s, ts, extra)
showRun(complete, show)
- complete.state
+ complete.results match {
+ case Inc(i) => complete.state.handleError(i)
+ case Value(_) => complete.state
+ }
}
def printSuccess(start: Long, stop: Long, extracted: Extracted, success: Boolean, log: Logger)
@@ -0,0 +1,27 @@
+import sbt._
+import Keys._
+
+object TestBuild extends Build
+{
+ lazy val akey = AttributeKey[Int]("TestKey")
+ lazy val t = TaskKey[String]("test-task")
+ lazy val check = InputKey[Unit]("check")
+ lazy val root = Project("root", file(".")).aggregate(a, b).settings(
+ check := checkState(checkParser.parsed, state.value)
+ )
+
+ lazy val a = Project("a", file("a")).settings(t := error("Failing"))
+
+ lazy val b = Project("b", file("b")).settings(t <<= Def.task("").updateState(updater))
+
+ def checkState(runs: Int, s: State) {
+ val stored = s.get(akey).getOrElse(0)
+ assert(stored == runs, "Expected " + runs + ", got " + stored)
+ }
+
+ def updater(s: State, a: AnyRef): State = s.update(akey)(_.getOrElse(0) + 1)
+
+ import complete.DefaultParsers._
+
+ lazy val checkParser = token(Space ~> IntBasic)
+}
@@ -0,0 +1,9 @@
+> check 0
+-> test-task
+> check 1
+-> test-task
+> check 2
+> b/test-task
+> check 3
+-> a/test-task
+> check 3

0 comments on commit 919d0ac

Please sign in to comment.