
sbt.EvaluateTask.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of main_2.11 Show documentation
Show all versions of main_2.11 Show documentation
sbt is an interactive build tool
The newest version!
/* sbt -- Simple Build Tool
* Copyright 2011 Mark Harrah
*/
package sbt
import sbt.internal.{ Load, BuildStructure, TaskTimings, TaskName, GCUtil }
import sbt.internal.util.{ ErrorHandling, RMap, Show, Signals, Types }
import sbt.util.Logger
import sbt.librarymanagement.{ Resolver, UpdateReport }
import scala.concurrent.duration.Duration
import java.io.File
import Def.{ displayFull, dummyState, ScopedKey, Setting }
import Keys.{ streams, Streams, TaskStreams }
import Keys.{ dummyRoots, dummyStreamsManager, executionRoots, pluginData, streamsManager, taskDefinitionKey, transformState }
import Project.richInitializeTask
import Scope.{ GlobalScope, ThisScope }
import sbt.internal.util.Types.const
import sbt.internal.util.{ Attributed, HList }
import scala.Console.RED
import std.Transform.{ DummyTaskMap, TaskAndValue }
import TaskName._
@deprecated("Use EvaluateTaskConfig instead.", "0.13.5")
final case class EvaluateConfig(cancelable: Boolean, restrictions: Seq[Tags.Rule], checkCycles: Boolean = false, progress: ExecuteProgress[Task] = EvaluateTask.defaultProgress)
/**
* An API that allows you to cancel executing tasks upon some signal.
*
* For example, this is implemented by the TaskEngine; invoking `cancel()` allows you
* to cancel the current task exeuction. A `TaskCancel` is passed to the
* [[TaskEvalautionCancelHandler]] which is responsible for calling `cancel()` when
* appropriate.
*/
trait RunningTaskEngine {
/** Attempts to kill and shutdown the running task engine.*/
def cancelAndShutdown(): Unit
}
/**
* A startegy for being able to cancle tasks.
*
* Implementations of this trait determine what will trigger `cancel()` for
* the task engine, providing in the `start` method.
*
* All methods on this API are expected to be called from the same thread.
*/
trait TaskCancellationStrategy {
/** The state used by this task. */
type State
/**
* Called when task evaluation starts.
*
* @param canceller An object that can cancel the current task evaluation session.
* @return Whatever state you need to cleanup in your finish method.
*/
def onTaskEngineStart(canceller: RunningTaskEngine): State
/** Called when task evaluation completes, either in success or failure. */
def onTaskEngineFinish(state: State): Unit
}
object TaskCancellationStrategy {
/** An empty handler that does not cancel tasks. */
object Null extends TaskCancellationStrategy {
type State = Unit
def onTaskEngineStart(canceller: RunningTaskEngine): Unit = ()
def onTaskEngineFinish(state: Unit): Unit = ()
override def toString: String = "Null"
}
/** Cancel handler which registers for SIGINT and cancels tasks when it is received. */
object Signal extends TaskCancellationStrategy {
type State = Signals.Registration
def onTaskEngineStart(canceller: RunningTaskEngine): Signals.Registration = {
Signals.register(() => canceller.cancelAndShutdown())
}
def onTaskEngineFinish(registration: Signals.Registration): Unit =
registration.remove()
override def toString: String = "Signal"
}
}
/**
* The new API for running tasks.
*
* This represents all the hooks possible when running the task engine.
* We expose this trait so that we can, in a binary compatible way, modify what is used
* inside this configuration and how to construct it.
*/
sealed trait EvaluateTaskConfig {
def restrictions: Seq[Tags.Rule]
def checkCycles: Boolean
def progressReporter: ExecuteProgress[Task]
def cancelStrategy: TaskCancellationStrategy
/**
* If true, we force a finalizer/gc run (or two) after task execution completes when needed.
*/
def forceGarbageCollection: Boolean
/**
* Interval to force GC.
*/
def minForcegcInterval: Duration
}
final object EvaluateTaskConfig {
/** Pulls in the old configuration format. */
def apply(old: EvaluateConfig): EvaluateTaskConfig = {
object AdaptedTaskConfig extends EvaluateTaskConfig {
def restrictions: Seq[Tags.Rule] = old.restrictions
def checkCycles: Boolean = old.checkCycles
def progressReporter: ExecuteProgress[Task] = old.progress
def cancelStrategy: TaskCancellationStrategy =
if (old.cancelable) TaskCancellationStrategy.Signal
else TaskCancellationStrategy.Null
def forceGarbageCollection = GCUtil.defaultForceGarbageCollection
def minForcegcInterval = GCUtil.defaultMinForcegcInterval
}
AdaptedTaskConfig
}
@deprecated("Use the alternative that specifies minForcegcInterval", "0.13.9")
def apply(restrictions: Seq[Tags.Rule],
checkCycles: Boolean,
progressReporter: ExecuteProgress[Task],
cancelStrategy: TaskCancellationStrategy,
forceGarbageCollection: Boolean): EvaluateTaskConfig =
apply(restrictions, checkCycles, progressReporter, cancelStrategy, forceGarbageCollection,
GCUtil.defaultMinForcegcInterval)
/** Raw constructor for EvaluateTaskConfig. */
def apply(restrictions: Seq[Tags.Rule],
checkCycles: Boolean,
progressReporter: ExecuteProgress[Task],
cancelStrategy: TaskCancellationStrategy,
forceGarbageCollection: Boolean,
minForcegcInterval: Duration): EvaluateTaskConfig = {
val r = restrictions
val check = checkCycles
val cs = cancelStrategy
val pr = progressReporter
val fgc = forceGarbageCollection
val mfi = minForcegcInterval
object SimpleEvaluateTaskConfig extends EvaluateTaskConfig {
def restrictions = r
def checkCycles = check
def progressReporter = pr
def cancelStrategy = cs
def forceGarbageCollection = fgc
def minForcegcInterval = mfi
}
SimpleEvaluateTaskConfig
}
}
final case class PluginData(dependencyClasspath: Seq[Attributed[File]], definitionClasspath: Seq[Attributed[File]], resolvers: Option[Seq[Resolver]], report: Option[UpdateReport], scalacOptions: Seq[String]) {
val classpath: Seq[Attributed[File]] = definitionClasspath ++ dependencyClasspath
}
object PluginData {
@deprecated("Use the alternative that specifies the compiler options and specific classpaths.", "0.13.1")
def apply(dependencyClasspath: Seq[Attributed[File]], definitionClasspath: Seq[Attributed[File]], resolvers: Option[Seq[Resolver]], report: Option[UpdateReport]): PluginData =
PluginData(dependencyClasspath, definitionClasspath, resolvers, report, Nil)
@deprecated("Use the alternative that specifies the specific classpaths.", "0.13.0")
def apply(classpath: Seq[Attributed[File]], resolvers: Option[Seq[Resolver]], report: Option[UpdateReport]): PluginData =
PluginData(classpath, Nil, resolvers, report, Nil)
}
object EvaluateTask {
import std.{ TaskExtra, Transform }
import TaskExtra._
import Keys.state
private[sbt] def defaultProgress: ExecuteProgress[Task] =
if (java.lang.Boolean.getBoolean("sbt.task.timings")) new TaskTimings else ExecuteProgress.empty[Task]
val SystemProcessors = Runtime.getRuntime.availableProcessors
@deprecated("Use extractedTaskConfig.", "0.13.0")
def defaultConfig(state: State): EvaluateConfig =
{
val extracted = Project.extract(state)
extractedConfig(extracted, extracted.structure, state)
}
@deprecated("Use extractedTaskConfig.", "0.13.0")
def defaultConfig(extracted: Extracted, structure: BuildStructure) =
EvaluateConfig(false, restrictions(extracted, structure), progress = defaultProgress)
@deprecated("Use other extractedTaskConfig", "0.13.2")
def extractedConfig(extracted: Extracted, structure: BuildStructure): EvaluateConfig =
{
val workers = restrictions(extracted, structure)
val canCancel = cancelable(extracted, structure)
EvaluateConfig(cancelable = canCancel, restrictions = workers, progress = defaultProgress)
}
@deprecated("Use other extractedTaskConfig", "0.13.5")
def extractedConfig(extracted: Extracted, structure: BuildStructure, state: State): EvaluateConfig =
{
val workers = restrictions(extracted, structure)
val canCancel = cancelable(extracted, structure)
val progress = executeProgress(extracted, structure, state)
EvaluateConfig(cancelable = canCancel, restrictions = workers, progress = progress)
}
def extractedTaskConfig(extracted: Extracted, structure: BuildStructure, state: State): EvaluateTaskConfig =
{
val rs = restrictions(extracted, structure)
val canceller = cancelStrategy(extracted, structure, state)
val progress = executeProgress(extracted, structure, state)
val fgc = forcegc(extracted, structure)
val mfi = minForcegcInterval(extracted, structure)
EvaluateTaskConfig(rs, false, progress, canceller, fgc, mfi)
}
def defaultRestrictions(maxWorkers: Int) = Tags.limitAll(maxWorkers) :: Nil
def defaultRestrictions(extracted: Extracted, structure: BuildStructure): Seq[Tags.Rule] =
Tags.limitAll(maxWorkers(extracted, structure)) :: Nil
def restrictions(state: State): Seq[Tags.Rule] =
{
val extracted = Project.extract(state)
restrictions(extracted, extracted.structure)
}
def restrictions(extracted: Extracted, structure: BuildStructure): Seq[Tags.Rule] =
getSetting(Keys.concurrentRestrictions, defaultRestrictions(extracted, structure), extracted, structure)
def maxWorkers(extracted: Extracted, structure: BuildStructure): Int =
if (getSetting(Keys.parallelExecution, true, extracted, structure))
SystemProcessors
else
1
def cancelable(extracted: Extracted, structure: BuildStructure): Boolean =
getSetting(Keys.cancelable, false, extracted, structure)
def cancelStrategy(extracted: Extracted, structure: BuildStructure, state: State): TaskCancellationStrategy =
getSetting(Keys.taskCancelStrategy, { (_: State) => TaskCancellationStrategy.Null }, extracted, structure)(state)
private[sbt] def executeProgress(extracted: Extracted, structure: BuildStructure, state: State): ExecuteProgress[Task] = {
import Types.const
val maker: State => Keys.TaskProgress = getSetting(Keys.executeProgress, const(new Keys.TaskProgress(defaultProgress)), extracted, structure)
maker(state).progress
}
// TODO - Should this pull from Global or from the project itself?
private[sbt] def forcegc(extracted: Extracted, structure: BuildStructure): Boolean =
getSetting(Keys.forcegc in Global, GCUtil.defaultForceGarbageCollection, extracted, structure)
// TODO - Should this pull from Global or from the project itself?
private[sbt] def minForcegcInterval(extracted: Extracted, structure: BuildStructure): Duration =
getSetting(Keys.minForcegcInterval in Global, GCUtil.defaultMinForcegcInterval, extracted, structure)
def getSetting[T](key: SettingKey[T], default: T, extracted: Extracted, structure: BuildStructure): T =
key in extracted.currentRef get structure.data getOrElse default
def injectSettings: Seq[Setting[_]] = Seq(
(state in GlobalScope) ::= dummyState,
(streamsManager in GlobalScope) ::= dummyStreamsManager,
(executionRoots in GlobalScope) ::= dummyRoots
)
def evalPluginDef(log: Logger)(pluginDef: BuildStructure, state: State): PluginData =
{
val root = ProjectRef(pluginDef.root, Load.getRootProject(pluginDef.units)(pluginDef.root))
val pluginKey = pluginData
val config = extractedTaskConfig(Project.extract(state), pluginDef, state)
val evaluated = apply(pluginDef, ScopedKey(pluginKey.scope, pluginKey.key), state, root, config)
val (newS, result) = evaluated getOrElse sys.error("Plugin data does not exist for plugin definition at " + pluginDef.root)
Project.runUnloadHooks(newS) // discard states
processResult(result, log)
}
@deprecated("This method does not apply state changes requested during task execution and does not honor concurrent execution restrictions. Use 'apply' instead.", "0.11.1")
def evaluateTask[T](structure: BuildStructure, taskKey: ScopedKey[Task[T]], state: State, ref: ProjectRef, checkCycles: Boolean = false, maxWorkers: Int = SystemProcessors): Option[Result[T]] =
apply(structure, taskKey, state, ref, EvaluateConfig(false, defaultRestrictions(maxWorkers), checkCycles)).map(_._2)
/**
* Evaluates `taskKey` and returns the new State and the result of the task wrapped in Some.
* If the task is not defined, None is returned. The provided task key is resolved against the current project `ref`.
* Task execution is configured according to settings defined in the loaded project.
*/
def apply[T](structure: BuildStructure, taskKey: ScopedKey[Task[T]], state: State, ref: ProjectRef): Option[(State, Result[T])] =
apply[T](structure, taskKey, state, ref, extractedTaskConfig(Project.extract(state), structure, state))
/**
* Evaluates `taskKey` and returns the new State and the result of the task wrapped in Some.
* If the task is not defined, None is returned. The provided task key is resolved against the current project `ref`.
* `config` configures concurrency and canceling of task execution.
*/
@deprecated("Use EvaluateTaskConfig option instead.", "0.13.5")
def apply[T](structure: BuildStructure, taskKey: ScopedKey[Task[T]], state: State, ref: ProjectRef, config: EvaluateConfig): Option[(State, Result[T])] =
apply(structure, taskKey, state, ref, EvaluateTaskConfig(config))
def apply[T](structure: BuildStructure, taskKey: ScopedKey[Task[T]], state: State, ref: ProjectRef, config: EvaluateTaskConfig): Option[(State, Result[T])] = {
withStreams(structure, state) { str =>
for ((task, toNode) <- getTask(structure, taskKey, state, str, ref)) yield runTask(task, state, str, structure.index.triggers, config)(toNode)
}
}
def logIncResult(result: Result[_], state: State, streams: Streams) = result match { case Inc(i) => logIncomplete(i, state, streams); case _ => () }
def logIncomplete(result: Incomplete, state: State, streams: Streams): Unit = {
val all = Incomplete linearize result
val keyed = for (Incomplete(Some(key: ScopedKey[_]), _, msg, _, ex) <- all) yield (key, msg, ex)
val un = all.filter { i => i.node.isEmpty || i.message.isEmpty }
import ExceptionCategory._
for ((key, msg, Some(ex)) <- keyed) {
def log = getStreams(key, streams).log
ExceptionCategory(ex) match {
case AlreadyHandled => ()
case m: MessageOnly => if (msg.isEmpty) log.error(m.message)
case f: Full => log.trace(f.exception)
}
}
for ((key, msg, ex) <- keyed if (msg.isDefined || ex.isDefined)) {
val msgString = (msg.toList ++ ex.toList.map(ErrorHandling.reducedToString)).mkString("\n\t")
val log = getStreams(key, streams).log
val display = contextDisplay(state, log.ansiCodesSupported)
log.error("(" + display(key) + ") " + msgString)
}
}
private[this] def contextDisplay(state: State, highlight: Boolean) = Project.showContextKey(state, if (highlight) Some(RED) else None)
def suppressedMessage(key: ScopedKey[_])(implicit display: Show[ScopedKey[_]]): String =
"Stack trace suppressed. Run 'last %s' for the full log.".format(display(key))
def getStreams(key: ScopedKey[_], streams: Streams): TaskStreams =
streams(ScopedKey(Project.fillTaskAxis(key).scope, Keys.streams.key))
def withStreams[T](structure: BuildStructure, state: State)(f: Streams => T): T =
{
val str = std.Streams.closeable(structure.streams(state))
try { f(str) } finally { str.close() }
}
def getTask[T](structure: BuildStructure, taskKey: ScopedKey[Task[T]], state: State, streams: Streams, ref: ProjectRef): Option[(Task[T], NodeView[Task])] =
{
val thisScope = Load.projectScope(ref)
val resolvedScope = Scope.replaceThis(thisScope)(taskKey.scope)
for (t <- structure.data.get(resolvedScope, taskKey.key)) yield (t, nodeView(state, streams, taskKey :: Nil))
}
def nodeView[HL <: HList](state: State, streams: Streams, roots: Seq[ScopedKey[_]], dummies: DummyTaskMap = DummyTaskMap(Nil)): NodeView[Task] =
Transform((dummyRoots, roots) :: (dummyStreamsManager, streams) :: (dummyState, state) :: dummies)
@deprecated("Use new EvaluateTaskConfig option to runTask", "0.13.5")
def runTask[T](root: Task[T], state: State, streams: Streams, triggers: Triggers[Task], config: EvaluateConfig)(implicit taskToNode: NodeView[Task]): (State, Result[T]) =
{
val newConfig = EvaluateTaskConfig(config)
runTask(root, state, streams, triggers, newConfig)(taskToNode)
}
def runTask[T](root: Task[T], state: State, streams: Streams, triggers: Triggers[Task], config: EvaluateTaskConfig)(implicit taskToNode: NodeView[Task]): (State, Result[T]) =
{
import ConcurrentRestrictions.{ completionService, TagMap, Tag, tagged, tagsKey }
val log = state.log
log.debug(s"Running task... Cancel: ${config.cancelStrategy}, check cycles: ${config.checkCycles}, forcegc: ${config.forceGarbageCollection}")
val tags = tagged[Task[_]](_.info get tagsKey getOrElse Map.empty, Tags.predicate(config.restrictions))
val (service, shutdownThreads) = completionService[Task[_], Completed](tags, (s: String) => log.warn(s))
def shutdown(): Unit = {
// First ensure that all threads are stopped for task execution.
shutdownThreads()
// Now we run the gc cleanup to force finalizers to clear out file handles (yay GC!)
if (config.forceGarbageCollection) {
GCUtil.forceGcWithInterval(config.minForcegcInterval, log)
}
}
// propagate the defining key for reporting the origin
def overwriteNode(i: Incomplete): Boolean = i.node match {
case Some(t: Task[_]) => transformNode(t).isEmpty
case _ => true
}
def run() = {
val x = new Execute[Task](Execute.config(config.checkCycles, overwriteNode), triggers, config.progressReporter)(taskToNode)
val (newState, result) =
try {
val results = x.runKeep(root)(service)
storeValuesForPrevious(results, state, streams)
applyResults(results, state, root)
} catch { case inc: Incomplete => (state, Inc(inc)) }
finally shutdown()
val replaced = transformInc(result)
logIncResult(replaced, state, streams)
(newState, replaced)
}
object runningEngine extends RunningTaskEngine {
def cancelAndShutdown(): Unit = {
println("")
log.warn("Canceling execution...")
shutdown()
}
}
// Register with our cancel handler we're about to start.
val strat = config.cancelStrategy
val cancelState = strat.onTaskEngineStart(runningEngine)
try run()
finally strat.onTaskEngineFinish(cancelState)
}
private[this] def storeValuesForPrevious(results: RMap[Task, Result], state: State, streams: Streams): Unit =
for (referenced <- Previous.references in Global get Project.structure(state).data)
Previous.complete(referenced, results, streams)
def applyResults[T](results: RMap[Task, Result], state: State, root: Task[T]): (State, Result[T]) =
(stateTransform(results)(state), results(root))
def stateTransform(results: RMap[Task, Result]): State => State =
Function.chain(
results.toTypedSeq flatMap {
case results.TPair(Task(info, _), Value(v)) => info.post(v) get transformState
case _ => Nil
}
)
def transformInc[T](result: Result[T]): Result[T] =
// taskToKey needs to be before liftAnonymous. liftA only lifts non-keyed (anonymous) Incompletes.
result.toEither.left.map { i => Incomplete.transformBU(i)(convertCyclicInc andThen taskToKey andThen liftAnonymous) }
def taskToKey: Incomplete => Incomplete = {
case in @ Incomplete(Some(node: Task[_]), _, _, _, _) => in.copy(node = transformNode(node))
case i => i
}
type AnyCyclic = Execute[Task]#CyclicException[_]
def convertCyclicInc: Incomplete => Incomplete = {
case in @ Incomplete(_, _, _, _, Some(c: AnyCyclic)) =>
in.copy(directCause = Some(new RuntimeException(convertCyclic(c))))
case i => i
}
def convertCyclic(c: AnyCyclic): String =
(c.caller, c.target) match {
case (caller: Task[_], target: Task[_]) =>
c.toString + (if (caller eq target) "(task: " + name(caller) + ")" else "(caller: " + name(caller) + ", target: " + name(target) + ")")
case _ => c.toString
}
def liftAnonymous: Incomplete => Incomplete = {
case i @ Incomplete(node, tpe, None, causes, None) =>
causes.find(inc => inc.node.isEmpty && (inc.message.isDefined || inc.directCause.isDefined)) match {
case Some(lift) => i.copy(directCause = lift.directCause, message = lift.message)
case None => i
}
case i => i
}
def processResult[T](result: Result[T], log: Logger, show: Boolean = false): T =
onResult(result, log) { v => if (show) println("Result: " + v); v }
def onResult[T, S](result: Result[T], log: Logger)(f: T => S): S =
result match {
case Value(v) => f(v)
case Inc(inc) => throw inc
}
// if the return type Seq[Setting[_]] is not explicitly given, scalac hangs
val injectStreams: ScopedKey[_] => Seq[Setting[_]] = scoped =>
if (scoped.key == streams.key)
Seq(streams in scoped.scope <<= streamsManager map { mgr =>
val stream = mgr(scoped)
stream.open()
stream
})
else
Nil
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy