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.
This commit is contained in:
Mark Harrah 2013-07-03 17:15:26 -04:00
parent 3e7bedd11b
commit 919d0ac63d
5 changed files with 74 additions and 21 deletions

View File

@ -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)
}

View File

@ -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.")
}
}

View File

@ -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)

View File

@ -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)
}

View File

@ -0,0 +1,9 @@
> check 0
-> test-task
> check 1
-> test-task
> check 2
> b/test-task
> check 3
-> a/test-task
> check 3