adds task progress

Fixes #4362

This implements an instance of ExecuteProgress that is enabled by default to report progress on the shell.
Combined with the scroll up logger, this takes over the bottom lines of the terminal screen and display a count up clock of the currently executing task.
This commit is contained in:
Eugene Yokota 2018-10-01 02:29:55 -04:00
parent 4be4017f06
commit c316c80093
3 changed files with 154 additions and 6 deletions

View File

@ -7,7 +7,7 @@
package sbt
import sbt.internal.{ Load, BuildStructure, TaskTimings, TaskName, GCUtil }
import sbt.internal.{ Load, BuildStructure, TaskTimings, TaskName, GCUtil, TaskProgress }
import sbt.internal.util.{ Attributed, ConsoleAppender, ErrorHandling, HList, RMap, Signals, Types }
import sbt.util.{ Logger, Show }
import sbt.librarymanagement.{ Resolver, UpdateReport }
@ -163,13 +163,18 @@ object EvaluateTask {
lazy private val sharedProgress = new TaskTimings(shutdown = true)
private[sbt] def defaultProgress: ExecuteProgress[Task] =
// sbt-pgp calls this
private[sbt] def defaultProgress(): ExecuteProgress[Task] = ExecuteProgress.empty[Task]
private[sbt] def defaultProgress(currentRef: ProjectRef): ExecuteProgress[Task] =
if (java.lang.Boolean.getBoolean("sbt.task.timings")) {
if (java.lang.Boolean.getBoolean("sbt.task.timings.on.shutdown"))
sharedProgress
else
new TaskTimings(shutdown = false)
} else ExecuteProgress.empty[Task]
} else {
if (ConsoleAppender.showProgress) new TaskProgress(currentRef)
else ExecuteProgress.empty[Task]
}
val SystemProcessors = Runtime.getRuntime.availableProcessors
@ -229,7 +234,7 @@ object EvaluateTask {
import Types.const
val maker: State => Keys.TaskProgress = getSetting(
Keys.executeProgress,
const(new Keys.TaskProgress(defaultProgress)),
const(new Keys.TaskProgress(defaultProgress(extracted.currentRef))),
extracted,
structure
)
@ -380,6 +385,10 @@ object EvaluateTask {
val currentlyRunningEngine: AtomicReference[(State, RunningTaskEngine)] = new AtomicReference()
/**
* The main method for the task engine.
* See also Aggregation.runTasks.
*/
def runTask[T](
root: Task[T],
state: State,

View File

@ -77,6 +77,8 @@ object Aggregation {
if (show.taskValues) printSettings(r, show.print)
}
if (show.success) printSuccess(start, stop, extracted, success, log)
// wait for async logger to catch up
Thread.sleep(100)
}
def timedRun[T](
@ -129,9 +131,9 @@ object Aggregation {
if (get(showSuccess)) {
if (get(showTiming)) {
val msg = timingString(start, stop, structure.data, currentRef)
if (success) log.success(msg) else log.error(msg)
if (success) log.success(msg + "\n") else log.error(msg + "\n")
} else if (success)
log.success("")
log.success("" + "\n")
}
}

View File

@ -0,0 +1,137 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt
package internal
import sbt.internal.util.{ RMap, ConsoleOut }
import java.util.concurrent.ConcurrentHashMap
import scala.concurrent.{ blocking, Future, ExecutionContext }
import java.util.concurrent.atomic.AtomicBoolean
import scala.collection.JavaConverters._
import scala.collection.concurrent.TrieMap
import TaskProgress._
/**
* implements task progress display on the shell.
*/
private[sbt] final class TaskProgress(currentRef: ProjectRef) extends ExecuteProgress[Task] {
type S = Unit
private[this] val showScopedKey = Def.showRelativeKey2(currentRef)
// private[this] var start = 0L
private[this] val activeTasks = new ConcurrentHashMap[Task[_], Long]
private[this] val timings = new ConcurrentHashMap[Task[_], Long]
private[this] val calledBy = new ConcurrentHashMap[Task[_], Task[_]]
private[this] val anonOwners = new ConcurrentHashMap[Task[_], Task[_]]
private[this] val isReady = new AtomicBoolean(false)
private[this] val isAllCompleted = new AtomicBoolean(false)
override def initial: Unit = ()
override def registered(
state: Unit,
task: Task[_],
allDeps: Iterable[Task[_]],
pendingDeps: Iterable[Task[_]]
): Unit = {
// we need this to infer anonymous task names
pendingDeps foreach { t =>
if (TaskName.transformNode(t).isEmpty) {
anonOwners.put(t, task)
}
}
}
override def ready(state: Unit, task: Task[_]): Unit = {
isReady.set(true)
}
override def workStarting(task: Task[_]): Unit = {
activeTasks.put(task, System.nanoTime)
()
}
override def workFinished[A](task: Task[A], result: Either[Task[A], Result[A]]): Unit = {
activeTasks.remove(task)
timings.put(task, System.nanoTime - activeTasks.get(task))
// we need this to infer anonymous task names
result.left.foreach { t =>
calledBy.put(t, task)
}
}
override def completed[A](state: Unit, task: Task[A], result: Result[A]): Unit = ()
override def allCompleted(state: Unit, results: RMap[Task, Result]): Unit = {
isAllCompleted.set(true)
}
import ExecutionContext.Implicits._
Future {
while (!isReady.get) {
blocking {
Thread.sleep(500)
}
}
readyLog()
while (!isAllCompleted.get) {
blocking {
report()
Thread.sleep(500)
}
}
}
private[this] val console = ConsoleOut.systemOut
private[this] def readyLog(): Unit = {
console.println("")
console.println("")
console.println("")
console.println("")
console.println("")
console.println("")
console.print(CursorUp5)
}
private[this] val stopReportTask =
Set("run", "bgRun", "fgRun", "scala", "console", "consoleProject")
private[this] def report(): Unit = console.lockObject.synchronized {
val currentTasks = activeTasks.asScala.toList
def report0: Unit = {
console.print(s"$CursorDown1")
currentTasks foreach {
case (task, start) =>
val elapsed = (System.nanoTime - start) / 1000000000L
console.println(s"$DeleteLine1 | => ${taskName(task)} ${elapsed}s")
}
console.print(cursorUp(currentTasks.size + 1))
}
val isStop = currentTasks
.map({ case (t, _) => taskName(t) })
.exists(n => stopReportTask.exists(m => n.endsWith("/ " + m)))
if (isStop) ()
else report0
}
private[this] val taskNameCache = TrieMap.empty[Task[_], String]
private[this] def taskName(t: Task[_]): String =
taskNameCache.getOrElseUpdate(t, taskName0(t))
private[this] def taskName0(t: Task[_]): String = {
def definedName(node: Task[_]): Option[String] =
node.info.name orElse TaskName.transformNode(node).map(showScopedKey.show)
def inferredName(t: Task[_]): Option[String] = nameDelegate(t) map taskName
def nameDelegate(t: Task[_]): Option[Task[_]] =
Option(anonOwners.get(t)) orElse Option(calledBy.get(t))
definedName(t) orElse inferredName(t) getOrElse TaskName.anonymousName(t)
}
}
private[sbt] object TaskProgress {
final val DeleteLine1 = "\u001B[2K"
final val CursorUp5 = cursorUp(5)
def cursorUp(n: Int): String = s"\u001B[${n}A"
def cursorDown(n: Int): String = s"\u001B[${n}B"
final val CursorDown1 = cursorDown(1)
}