mirror of https://github.com/sbt/sbt.git
work on displaying task errors
This commit is contained in:
parent
7b4c16f294
commit
63b1c3441b
|
|
@ -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._
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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._
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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" }
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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 + ")"
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue