diff --git a/main/Act.scala b/main/Act.scala index dcb3e34fb..43ab23a6f 100644 --- a/main/Act.scala +++ b/main/Act.scala @@ -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._ diff --git a/main/Aggregation.scala b/main/Aggregation.scala index a427ffbe2..ac8b0e345 100644 --- a/main/Aggregation.scala +++ b/main/Aggregation.scala @@ -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) diff --git a/main/Build.scala b/main/Build.scala index 3e654a121..3e1aa36c3 100644 --- a/main/Build.scala +++ b/main/Build.scala @@ -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 ' 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 = diff --git a/main/CommandSupport.scala b/main/CommandSupport.scala index 675692877..ea765ff2a 100644 --- a/main/CommandSupport.scala +++ b/main/CommandSupport.scala @@ -41,21 +41,22 @@ EvalCommand + """ Evaluates the given Scala expression and prints the result and type. """ + val LastCommand = "last" val LastGrepCommand = "last-grep" + val lastGrepBrief = (LastGrepCommand + " ", "Shows lines from the last output for 'key' that match 'pattern'.") val lastGrepDetailed = LastGrepCommand + """ - is a regular expression interpreted by java.util.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 + " ", "Prints the last output associated with 'key'.") val lastDetailed = LastCommand + """ - 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" diff --git a/main/Defaults.scala b/main/Defaults.scala index 3a8330402..382768a58 100755 --- a/main/Defaults.scala +++ b/main/Defaults.scala @@ -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._ diff --git a/main/Keys.scala b/main/Keys.scala index 6517e947a..23ddcad91 100644 --- a/main/Keys.scala +++ b/main/Keys.scala @@ -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") } \ No newline at end of file diff --git a/main/LogManager.scala b/main/LogManager.scala index 8d1a6afc0..5a0960847 100644 --- a/main/LogManager.scala +++ b/main/LogManager.scala @@ -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 } } \ No newline at end of file diff --git a/main/Main.scala b/main/Main.scala index c3477e335..05787a417 100644 --- a/main/Main.scala +++ b/main/Main.scala @@ -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 } diff --git a/main/Project.scala b/main/Project.scala index 11fb7a21b..16b57e242 100644 --- a/main/Project.scala +++ b/main/Project.scala @@ -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) diff --git a/tasks/Execute.scala b/tasks/Execute.scala index f2955ac55..ba7028b6e 100644 --- a/tasks/Execute.scala +++ b/tasks/Execute.scala @@ -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 diff --git a/tasks/Incomplete.scala b/tasks/Incomplete.scala index c3fa5fc23..28c30ef8c 100644 --- a/tasks/Incomplete.scala +++ b/tasks/Incomplete.scala @@ -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" } diff --git a/tasks/Result.scala b/tasks/Result.scala index 99b6a2b07..8ad99d5c0 100644 --- a/tasks/Result.scala +++ b/tasks/Result.scala @@ -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) + } } \ No newline at end of file diff --git a/tasks/standard/TaskExtra.scala b/tasks/standard/TaskExtra.scala index 66bab1ca1..613cb6310 100644 --- a/tasks/standard/TaskExtra.scala +++ b/tasks/standard/TaskExtra.scala @@ -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) } \ No newline at end of file diff --git a/util/collection/Settings.scala b/util/collection/Settings.scala index ef6261c51..9450ee10e 100644 --- a/util/collection/Settings.scala +++ b/util/collection/Settings.scala @@ -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 + ")" }