work on displaying task errors

This commit is contained in:
Mark Harrah 2011-03-20 22:54:01 -04:00
parent 7b4c16f294
commit 63b1c3441b
14 changed files with 118 additions and 53 deletions

View File

@ -5,7 +5,6 @@ package sbt
import Project.ScopedKey
import Keys.{sessionSettings, thisProject}
import CommandSupport.logger
import Load.BuildStructure
import complete.{DefaultParsers, Parser}
import DefaultParsers._

View File

@ -77,7 +77,7 @@ final object Aggregation
import std.TaskExtra._
val toRun = ts map { case KeyValue(k,t) => t.map(v => KeyValue(k,v)) } join;
val start = System.currentTimeMillis
val result = withStreams(structure){ str => runTask(toRun)(nodeView(s, str, extra.tasks, extra.values)) }
val result = withStreams(structure){ str => runTask(toRun, str)(nodeView(s, str, extra.tasks, extra.values)) }
val stop = System.currentTimeMillis
val log = logger(s)
lazy val extracted = Project.extract(s)

View File

@ -11,7 +11,7 @@ package sbt
import collection.mutable
import Compiler.{Compilers,Inputs}
import Project.{inScope, ScopedKey, ScopeLocal, Setting}
import Keys.{appConfiguration, baseDirectory, configuration, thisProject, thisProjectRef}
import Keys.{appConfiguration, baseDirectory, configuration, streams, thisProject, thisProjectRef}
import TypeFunctions.{Endo,Id}
import tools.nsc.reporters.ConsoleReporter
import Build.{analyzed, data}
@ -108,10 +108,11 @@ object EvaluateTask
import BuildStreams.{Streams, TaskStreams}
val SystemProcessors = Runtime.getRuntime.availableProcessors
val isDummyTask = AttributeKey[Boolean]("is-dummy-task")
val taskDefinitionKey = AttributeKey[ScopedKey[_]]("task-definition-key")
val (state, dummyState) = dummy[State]("state")
val (streamsManager, dummyStreamsManager) = dummy[Streams]("streams-manager")
val streams = TaskKey[TaskStreams]("streams")
val resolvedScoped = SettingKey[ScopedKey[_]]("resolved-scoped")
private[sbt] val parseResult: TaskKey[_] = TaskKey("$parse-result")
@ -121,7 +122,12 @@ object EvaluateTask
)
def dummy[T](name: String): (TaskKey[T], Task[T]) = (TaskKey[T](name), dummyTask(name))
def dummyTask[T](name: String): Task[T] = task( error("Dummy task '" + name + "' did not get converted to a full task.") ) named name
def dummyTask[T](name: String): Task[T] =
{
val base: Task[T] = task( error("Dummy task '" + name + "' did not get converted to a full task.") ) named name
base.copy(info = base.info.set(isDummyTask, true))
}
def isDummy(t: Task[_]): Boolean = t.info.attributes.get(isDummyTask) getOrElse false
def evalPluginDef(log: Logger)(pluginDef: BuildStructure, state: State): Seq[Attributed[File]] =
{
@ -134,9 +140,22 @@ object EvaluateTask
def evaluateTask[T](structure: BuildStructure, taskKey: ScopedKey[Task[T]], state: State, ref: ProjectRef, checkCycles: Boolean = false, maxWorkers: Int = SystemProcessors): Option[Result[T]] =
withStreams(structure) { str =>
for( (task, toNode) <- getTask(structure, taskKey, state, str, ref) ) yield
runTask(task, checkCycles, maxWorkers)(toNode)
runTask(task, str, checkCycles, maxWorkers)(toNode)
}
def logIncResult(result: Result[_], streams: Streams) = result match { case Inc(i) => logIncomplete(i, streams); case _ => () }
def logIncomplete(result: Incomplete, streams: Streams)
{
val log = streams(ScopedKey(GlobalScope, Keys.logged)).log
val all = for(Incomplete(Some(key: Project.ScopedKey[_]), _, msg, _, ex) <- Incomplete linearize result) yield (key, msg, ex)
for( (key, _, Some(ex)) <- all)
getStreams(key, streams).log.trace(ex)
log.error("Incomplete task(s):")
for( (key, msg, ex) <- all if(msg.isDefined || ex.isDefined) )
getStreams(key, streams).log.error(" " + Project.display(key) + ": " + (msg.toList ++ ex.toList).mkString("\n\t"))
log.error("Run 'last <task>' for the full log(s).")
}
def getStreams(key: ScopedKey[_], streams: Streams): TaskStreams =
streams(ScopedKey(Project.fillTaskAxis(key).scope, Keys.streams.key))
def withStreams[T](structure: BuildStructure)(f: Streams => T): T =
{
val str = std.Streams.closeable(structure.streams)
@ -153,13 +172,25 @@ object EvaluateTask
def nodeView[HL <: HList](state: State, streams: Streams, extraDummies: KList[Task, HL] = KNil, extraValues: HL = HNil): Execute.NodeView[Task] =
Transform(dummyStreamsManager :^: KCons(dummyState, extraDummies), streams :+: HCons(state, extraValues))
def runTask[Task[_] <: AnyRef, T](root: Task[T], checkCycles: Boolean = false, maxWorkers: Int = SystemProcessors)(implicit taskToNode: Execute.NodeView[Task]): Result[T] =
def runTask[Task[_] <: AnyRef, T](root: Task[T], streams: Streams, checkCycles: Boolean = false, maxWorkers: Int = SystemProcessors)(implicit taskToNode: Execute.NodeView[Task]): Result[T] =
{
val (service, shutdown) = CompletionService[Task[_], Completed](maxWorkers)
val x = new Execute[Task](checkCycles)(taskToNode)
try { x.run(root)(service) } finally { shutdown() }
val result = try { x.run(root)(service) } finally { shutdown() }
val replaced = transformInc(result)
logIncResult(replaced, streams)
replaced
}
def transformInc[T](result: Result[T]): Result[T] =
result.toEither.left.map { i =>
Incomplete.transform(i) {
case in @ Incomplete(Some(node: Task[_]), _, _, _, _) => in.copy(node = transformNode(node))
case _ => i
}
}
def transformNode(node: Task[_]): Option[ScopedKey[_]] =
node.info.attributes get taskDefinitionKey
def processResult[T](result: Result[T], log: Logger, show: Boolean = false): T =
onResult(result, log) { v => if(show) println("Result: " + v); v }
@ -167,9 +198,7 @@ object EvaluateTask
result match
{
case Value(v) => f(v)
case Inc(inc) =>
log.error(Incomplete.show(inc, true))
error("Task did not complete successfully")
case Inc(inc) => throw inc
}
// if the return type Seq[Setting[_]] is not explicitly given, scalac hangs
@ -287,7 +316,7 @@ object Load
// additionally, set the task axis to the defining key if it is not set
def finalTransforms(ss: Seq[Setting[_]]): Seq[Setting[_]] =
{
import EvaluateTask.{parseResult, resolvedScoped, streams}
import EvaluateTask.{parseResult, resolvedScoped}
def isSpecial(key: AttributeKey[_]) = key == streams.key || key == resolvedScoped.key || key == parseResult.key
def mapSpecial(to: ScopedKey[_]) = new (ScopedKey ~> ScopedKey){ def apply[T](key: ScopedKey[T]) =
if(isSpecial(key.key))
@ -298,7 +327,11 @@ object Load
}
else key
}
ss.map(s => s mapReferenced mapSpecial(s.key) )
def setDefining[T] = (key: ScopedKey[T], value: T) => value match {
case tk: Task[t] if !EvaluateTask.isDummy(tk) => Task(tk.info.set(EvaluateTask.taskDefinitionKey, key), tk.work).asInstanceOf[T]
case _ => value
}
ss.map(s => s mapReferenced mapSpecial(s.key) mapInit setDefining )
}
def structureIndex(settings: Settings[Scope]): StructureIndex =

View File

@ -41,21 +41,22 @@ EvalCommand + """ <expression>
Evaluates the given Scala expression and prints the result and type.
"""
val LastCommand = "last"
val LastGrepCommand = "last-grep"
val lastGrepBrief = (LastGrepCommand + " <pattern> <key>", "Shows lines from the last output for 'key' that match 'pattern'.")
val lastGrepDetailed =
LastGrepCommand + """ <pattern> <key>
<pattern> is a regular expression interpreted by java.util.Pattern
<pattern> is a regular expression interpreted by java.util.Pattern.
Lines that match 'pattern' from the last streams output associated with the key are displayed.
See also """ + LastCommand + "."
val LastCommand = "last"
val lastBrief = (LastCommand + " <key>", "Prints the last output associated with 'key'.")
val lastDetailed =
LastCommand + """ <key>
The last streams output associated with the key (typically a task key) is redisplayed.
Redisplays the last streams output associated with the key (typically a task key).
See also """ + LastGrepCommand + "."
val InspectCommand = "inspect"

View File

@ -8,7 +8,7 @@ package sbt
import compiler.Discovery
import Project.{inConfig, Initialize, inScope, inTask, ScopedKey, Setting}
import Configurations.{Compile => CompileConf, Test => TestConf}
import EvaluateTask.{resolvedScoped, streams}
import EvaluateTask.resolvedScoped
import complete._
import std.TaskExtra._

View File

@ -4,7 +4,7 @@
package sbt
import java.io.File
import EvaluateTask.{resolvedScoped, streams}
import EvaluateTask.resolvedScoped
import complete._
import inc.Analysis
import std.TaskExtra._
@ -19,6 +19,7 @@ object Keys
val logLevel = SettingKey[Level.Value]("log-level")
val persistLogLevel = SettingKey[Level.Value]("persist-log-level")
val traceLevel = SettingKey[Int]("trace-level")
val persistTraceLevel = SettingKey[Int]("persist-trace-level")
val showSuccess = SettingKey[Boolean]("show-success")
val showTiming = SettingKey[Boolean]("show-timing")
val timingFormat = SettingKey[java.text.DateFormat]("timing-format")
@ -201,4 +202,5 @@ object Keys
// special
val settings = TaskKey[Settings[Scope]]("settings")
val streams = TaskKey[BuildStreams.TaskStreams]("streams")
}

View File

@ -7,16 +7,18 @@ package sbt
import LogManager._
import std.Transform
import Project.ScopedKey
import Keys.{logLevel, persistLogLevel}
import Keys.{logLevel, persistLogLevel, persistTraceLevel, traceLevel}
object LogManager
{
def construct(data: Settings[Scope]) = (task: ScopedKey[_], to: PrintWriter) =>
{
val scope = task.scope
def level(key: AttributeKey[Level.Value], default: Level.Value): Level.Value = data.get(scope, key) getOrElse default
val screenLevel = level(logLevel.key, Level.Info)
val backingLevel = level(persistLogLevel.key, Level.Debug)
def getOr[T](key: AttributeKey[T], default: T): T = data.get(scope, key) getOrElse default
val screenLevel = getOr(logLevel.key, Level.Info)
val backingLevel = getOr(persistLogLevel.key, Level.Debug)
val screenTrace = getOr(traceLevel.key, -1)
val backingTrace = getOr(persistTraceLevel.key, Int.MaxValue)
val console = ConsoleLogger()
val backed = ConsoleLogger(ConsoleLogger.printWriterOut(to), useColor = false) // TODO: wrap this with a filter that strips ANSI codes
@ -27,6 +29,8 @@ object LogManager
// set the specific levels
console setLevel screenLevel
backed setLevel backingLevel
console setTrace screenTrace
backed setTrace backingTrace
multi: Logger
}
}

View File

@ -323,10 +323,17 @@ object BuiltinCommands
Project.setProject(session, structure, s)
}
def handleException(e: Throwable, s: State, trace: Boolean = true): State = {
val log = logger(s)
if(trace) log.trace(e)
log.error(e.toString)
def handleException(e: Throwable, s: State): State =
handleException(e, s, logger(s))
def handleException(e: Throwable, s: State, log: Logger): State =
{
e match
{
case i: Incomplete => () // already handled by evaluateTask
case _ =>
log.trace(e)
log.error(e.toString)
}
s.fail
}

View File

@ -8,7 +8,7 @@ package sbt
import Project._
import Types.Endo
import Keys.{appConfiguration, buildStructure, commands, configuration, historyPath, projectCommand, sessionSettings, shellPrompt, thisProject, thisProjectRef, watch}
import Scope.ThisScope
import Scope.{GlobalScope,ThisScope}
import CommandSupport.logger
import compiler.Eval
@ -72,6 +72,7 @@ object Project extends Init[Scope]
def getOrError[T](state: State, key: AttributeKey[T], msg: String): T = state get key getOrElse error(msg)
def structure(state: State): Load.BuildStructure = getOrError(state, buildStructure, "No build loaded.")
def session(state: State): SessionSettings = getOrError(state, sessionSettings, "Session not initialized.")
def extract(state: State): Extracted =
{
val se = session(state)

View File

@ -201,11 +201,6 @@ final class Execute[A[_] <: AnyRef](checkCycles: Boolean)(implicit view: NodeVie
reverse(node) = Seq()
viewCache.getOrUpdate(node, view(node))
}
def incomplete(in: A[_]): Option[(A[_], Incomplete)] =
results(in) match {
case Value(v) => None
case Inc(inc) => Some( (in, inc) )
}
/** Send the work for this node to the provided Strategy. */
def submit[T]( node: A[T] )(implicit strategy: Strategy)
{
@ -219,8 +214,8 @@ final class Execute[A[_] <: AnyRef](checkCycles: Boolean)(implicit view: NodeVie
def work[T](node: A[T], f: => Either[A[T], T])(implicit strategy: Strategy): Completed =
{
val result = wideConvert(f).left.map {
case i: Incomplete => i
case e => Incomplete(Incomplete.Error, directCause = Some(e))
case i: Incomplete => if(i.node.isEmpty) i.copy(node = Some(node)) else i
case e => Incomplete(Some(node), Incomplete.Error, directCause = Some(e))
}
completed {
result match {
@ -304,7 +299,7 @@ final class Execute[A[_] <: AnyRef](checkCycles: Boolean)(implicit view: NodeVie
allCallers(node)
if(all contains target) cyclic(node, target, "Cyclic reference")
}
def cyclic[T](caller: A[T], target: A[T], msg: String) = throw new Incomplete(message = Some(msg), directCause = Some( new CyclicException(caller, target, msg) ) )
def cyclic[T](caller: A[T], target: A[T], msg: String) = throw new Incomplete(Some(caller), message = Some(msg), directCause = Some( new CyclicException(caller, target, msg) ) )
final class CyclicException[T](val caller: A[T], val target: A[T], msg: String) extends Exception(msg)
// state testing

View File

@ -4,31 +4,47 @@
package sbt
import Incomplete.{Error, Value => IValue}
final case class Incomplete(tpe: IValue = Error, message: Option[String] = None, causes: Seq[Incomplete] = Nil, directCause: Option[Throwable] = None)
extends Exception(message.orNull, directCause.orNull)
final case class Incomplete(node: Option[AnyRef], tpe: IValue = Error, message: Option[String] = None, causes: Seq[Incomplete] = Nil, directCause: Option[Throwable] = None)
extends Exception(message.orNull, directCause.orNull) {
override def toString = "Incomplete(node=" + node + ", tpe=" + tpe + ", msg=" + message + ", causes=" + causes + ", directCause=" + directCause +")"
}
object Incomplete extends Enumeration {
val Skipped, Error = Value
def show(i: Incomplete, traces: Boolean): String =
def transform(i: Incomplete)(f: Incomplete => Incomplete): Incomplete =
{
val exceptions = allExceptions(i)
val traces = exceptions.map(ex => ex.getStackTrace.mkString(ex.toString + "\n\t", "\n\t", "\n"))
val causeStr = if(i.causes.isEmpty) "" else (i.causes.length + " cause(s)")
"Incomplete (" + show(i.tpe) + ") " + i.message.getOrElse("") + causeStr + "\n" + traces
}
import collection.JavaConversions._
val visited: collection.mutable.Map[Incomplete,Incomplete] = new java.util.IdentityHashMap[Incomplete, Incomplete]
def visit(inc: Incomplete): Incomplete =
visited.getOrElseUpdate(inc, visitCauses(f(inc)) )
def visitCauses(inc: Incomplete): Incomplete =
inc.copy(causes = inc.causes.map(visit) )
def allExceptions(is: Seq[Incomplete]): Iterable[Throwable] =
allExceptions(new Incomplete(causes = is))
def allExceptions(i: Incomplete): Iterable[Throwable] =
visit(i)
}
def visitAll(i: Incomplete)(f: Incomplete => Unit)
{
val exceptions = IDSet.create[Throwable]
val visited = IDSet.create[Incomplete]
def visit(inc: Incomplete): Unit =
visited.process(inc)( () ) {
exceptions ++= inc.directCause.toList
f(inc)
inc.causes.foreach(visit)
}
visit(i)
}
def linearize(i: Incomplete): Seq[Incomplete] =
{
var ordered = List[Incomplete]()
visitAll(i) { ordered ::= _ }
ordered
}
def allExceptions(is: Seq[Incomplete]): Iterable[Throwable] =
allExceptions(new Incomplete(None, causes = is))
def allExceptions(i: Incomplete): Iterable[Throwable] =
{
val exceptions = IDSet.create[Throwable]
visitAll(i) { exceptions ++= _.directCause.toList }
exceptions.all
}
def show(tpe: Value) = tpe match { case Skipped=> "skipped"; case Error => "error" }

View File

@ -36,4 +36,8 @@ object Result
r foreach tryValue[Unit]
tryValue[S](v)
}
implicit def fromEither[T](e: Either[Incomplete, T]): Result[T] = e match {
case Left(i) => Inc(i)
case Right(v) => Value(v)
}
}

View File

@ -206,20 +206,22 @@ object TaskExtra extends TaskExtra
}
def failM[T]: Result[T] => Incomplete = { case Inc(i) => i; case x => expectedFailure }
def expectedFailure = throw Incomplete(message = Some("Expected failure"))
def expectedFailure = throw Incomplete(None, message = Some("Expected dependency to fail."))
def successM[T]: Result[T] => T = { case Inc(i) => throw i; case Value(t) => t }
def allM[In <: HList]: Results[In] => In = in =>
{
val incs = failuresM(in)
if(incs.isEmpty) in.down(Result.tryValue) else throw Incomplete(causes = incs)
if(incs.isEmpty) in.down(Result.tryValue) else throw incompleteDeps(incs)
}
def failuresM[In <: HList]: Results[In] => Seq[Incomplete] = x => failures[Any](x.toList)
def all[D](in: Seq[Result[D]]) =
{
val incs = failures(in)
if(incs.isEmpty) in.map(Result.tryValue.fn[D]) else throw Incomplete(causes = incs)
if(incs.isEmpty) in.map(Result.tryValue.fn[D]) else throw incompleteDeps(incs)
}
def failures[A](results: Seq[Result[A]]): Seq[Incomplete] = results.collect { case Inc(i) => i }
def incompleteDeps(incs: Seq[Incomplete]): Incomplete = Incomplete(None, message = Some("Dependency did not complete successfully."), causes = incs)
}

View File

@ -180,6 +180,7 @@ trait Init[Scope]
def dependsOn: Seq[ScopedKey[_]] = remove(init.dependsOn, key)
def mapReferenced(g: MapScoped): Setting[T] = new Setting(key, init mapReferenced g)
def mapKey(g: MapScoped): Setting[T] = new Setting(g(key), init)
def mapInit(f: (ScopedKey[T], T) => T): Setting[T] = new Setting(key, init.map(t => f(key,t)))
override def toString = "setting(" + key + ")"
}