Skip to content

Commit

Permalink
Failed task execution should still preserve State changes. Fixes sbt#804
Browse files Browse the repository at this point in the history
.

Candidate for inclusion in 0.13.0 if there is another RC, otherwise
scheduled for 0.13.1.
  • Loading branch information
harrah committed Jul 3, 2013
1 parent 3e7bedd commit 919d0ac
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 21 deletions.
24 changes: 6 additions & 18 deletions main/command/src/main/scala/sbt/MainLoop.scala
Expand Up @@ -75,23 +75,11 @@ object MainLoop
case Left(t) => handleException(t, state) 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 = @deprecated("Use State.handleError", "0.13.0")
handleException(e, s, s.log) def handleException(t: Throwable, s: State, log: Logger): State = State.handleException(t, s, log)
def handleException(t: Throwable, s: State, log: Logger): State =
{ def logFullException(e: Throwable, log: Logger): Unit = State.logFullException(e, log)
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.")
}
} }
26 changes: 26 additions & 0 deletions main/command/src/main/scala/sbt/State.scala
Expand Up @@ -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`*/ /** Marks the currently executing command as failing. This triggers failure handling by the command processor. See also `State.onFailure`*/
def fail: State 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. */ /** Schedules `newCommands` to be run after any remaining commands. */
def ++ (newCommands: Seq[Command]): State def ++ (newCommands: Seq[Command]): State
/** Schedules `newCommand` to be run after any remaining commands. */ /** Schedules `newCommand` to be run after any remaining commands. */
Expand Down Expand Up @@ -190,6 +196,7 @@ object State
def has(key: AttributeKey[_]) = s.attributes contains key def has(key: AttributeKey[_]) = s.attributes contains key
def remove(key: AttributeKey[_]) = s.copy(attributes = s.attributes remove key) def remove(key: AttributeKey[_]) = s.copy(attributes = s.attributes remove key)
def log = s.globalLogging.full def log = s.globalLogging.full
def handleError(t: Throwable): State = handleException(t, s, log)
def fail = def fail =
{ {
val remaining = s.remainingCommands.dropWhile(_ != FailureWall) val remaining = s.remainingCommands.dropWhile(_ != FailureWall)
Expand All @@ -205,6 +212,7 @@ object State
case None => noHandler case None => noHandler
} }



def addExitHook(act: => Unit): State = def addExitHook(act: => Unit): State =
s.copy(exitHooks = s.exitHooks + ExitHook(act)) s.copy(exitHooks = s.exitHooks + ExitHook(act))
def runExitHooks(): State = { def runExitHooks(): State = {
Expand All @@ -221,4 +229,22 @@ object State
def initializeClassLoaderCache = s.put(BasicKeys.classLoaderCache, newClassLoaderCache) def initializeClassLoaderCache = s.put(BasicKeys.classLoaderCache, newClassLoaderCache)
private[this] def newClassLoaderCache = new classpath.ClassLoaderCache(s.configuration.provider.scalaProvider.launcher.topLoader) 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.")
}
} }
9 changes: 6 additions & 3 deletions main/src/main/scala/sbt/Aggregation.scala
Expand Up @@ -47,8 +47,8 @@ final object Aggregation
val log = state.log val log = state.log
val extracted = Project extract state val extracted = Project extract state
val success = results match { case Value(_) => true; case Inc(_) => false } val success = results match { case Value(_) => true; case Inc(_) => false }
try { EvaluateTask.onResult(results, log) { results => if(show.taskValues) printSettings(results, show.print) } } results.toEither.right.foreach { r => if(show.taskValues) printSettings(r, show.print) }
finally { if(show.success) printSuccess(start, stop, extracted, success, log) } if(show.success) printSuccess(start, stop, extracted, success, log)
} }
def timedRun[T](s: State, ts: Values[Task[T]], extra: DummyTaskMap): Complete[T] = def timedRun[T](s: State, ts: Values[Task[T]], extra: DummyTaskMap): Complete[T] =
{ {
Expand All @@ -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 = { 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) val complete = timedRun[T](s, ts, extra)
showRun(complete, show) 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) def printSuccess(start: Long, stop: Long, extracted: Extracted, success: Boolean, log: Logger)
Expand Down
27 changes: 27 additions & 0 deletions sbt/src/sbt-test/actions/update-state-fail/project/Build.scala
@@ -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)
}
9 changes: 9 additions & 0 deletions sbt/src/sbt-test/actions/update-state-fail/test
@@ -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.