mirror of https://github.com/sbt/sbt.git
521 lines
16 KiB
ReStructuredText
521 lines
16 KiB
ReStructuredText
=====
|
|
Tasks
|
|
=====
|
|
|
|
Tasks and settings are now introduced in the :doc:`getting started guide </Getting-Started/Basic-Def>`,
|
|
which you may wish to read first. This older page has some additional detail.
|
|
|
|
*Wiki Maintenance Note:* This page should have its overlap with the
|
|
getting started guide cleaned up, and just have any advanced or
|
|
additional notes. It should maybe also be consolidated with
|
|
:doc:`TaskInputs`.
|
|
|
|
Introduction
|
|
============
|
|
|
|
sbt 0.10+ has a new task system that integrates with the new settings
|
|
system. Both settings and tasks produce values, but there are two major
|
|
differences between them:
|
|
|
|
1. Settings are evaluated at project load time. Tasks are executed on
|
|
demand, often in response to a command from the user.
|
|
2. At the beginning of project loading, settings and their dependencies
|
|
are fixed. Tasks can introduce new tasks during execution, however.
|
|
|
|
Features
|
|
========
|
|
|
|
There are several features of the task system:
|
|
|
|
1. By integrating with the settings system, tasks can be added, removed,
|
|
and modified as easily and flexibly as settings.
|
|
2. :doc:`Input Tasks <TaskInputs>`, the successor to method tasks, use
|
|
:doc:`parser combinators <Parsing-Input>` to define the syntax for their
|
|
arguments. This allows flexible syntax and tab-completions in the
|
|
same way as :doc:`/Extending/Commands`.
|
|
3. Tasks produce values. Other tasks can access a task's value by calling
|
|
``value`` on it within a task definition.
|
|
4. Dynamically changing the structure of the task graph is possible.
|
|
Tasks can be injected into the execution graph based on the result of another task.
|
|
5. There are ways to handle task failure, similar to ``try/catch/finally``.
|
|
6. Each task has access to its own Logger that by default persists the
|
|
logging for that task at a more verbose level than is initially
|
|
printed to the screen.
|
|
|
|
These features are discussed in detail in the following sections. The
|
|
context for the code snippets will be either the body of a ``Build``
|
|
object in a :doc:`.scala file </Getting-Started/Full-Def>` or an expression
|
|
in a :doc:`build.sbt </Getting-Started/Basic-Def>`.
|
|
|
|
Defining a New Task
|
|
===================
|
|
|
|
Hello World example (sbt)
|
|
-------------------------
|
|
|
|
build.sbt
|
|
|
|
::
|
|
|
|
val hello = taskKey[Unit]("Prints 'Hello World'")
|
|
|
|
hello := println("hello world!")
|
|
|
|
Hello World example (scala)
|
|
---------------------------
|
|
|
|
project/Build.scala
|
|
|
|
::
|
|
|
|
|
|
import sbt._
|
|
import Keys._
|
|
|
|
object HelloBuild extends Build {
|
|
val hwsettings = Defaults.defaultSettings ++ Seq(
|
|
organization := "hello",
|
|
name := "world",
|
|
version := "1.0-SNAPSHOT",
|
|
scalaVersion := "2.9.0-1"
|
|
)
|
|
|
|
val hello = taskKey[Unit]("Prints 'Hello World'")
|
|
|
|
val helloTask = hello := {
|
|
println("Hello World")
|
|
}
|
|
|
|
lazy val project = Project (
|
|
"project",
|
|
file ("."),
|
|
settings = hwsettings ++ Seq(helloTask)
|
|
)
|
|
}
|
|
|
|
Run "sbt hello" from command line to invoke the task. Run "sbt tasks" to
|
|
see this task listed.
|
|
|
|
Define the key
|
|
--------------
|
|
|
|
To declare a new task, define a val of type ``TaskKey``, either in ``.sbt`` or ``.scala``:
|
|
|
|
::
|
|
|
|
val sampleTask = taskKey[Int]("A sample task.")
|
|
|
|
The name of the ``val`` is used when referring to the task in Scala
|
|
code and at the command line. The string passed to the ``TaskKey`` method is a description of the task. The type parameter passed to ``TaskKey`` (here, ``Int``) is the type of value produced by the task.
|
|
|
|
We'll define a couple of other of tasks for the examples:
|
|
|
|
::
|
|
|
|
val intTask = taskKey[Int]("An int task")
|
|
val stringTask = taskKey[String]("A string task")
|
|
|
|
The examples themselves are valid entries in a ``build.sbt`` or can be
|
|
provided as part of a sequence to ``Project.settings`` (see
|
|
:doc:`Full Configuration </Getting-Started/Full-Def>`).
|
|
|
|
Implement the task
|
|
------------------
|
|
|
|
There are three main parts to implementing a task once its key is
|
|
defined:
|
|
|
|
1. Determine the settings and other tasks needed by the task. They are
|
|
the task's inputs.
|
|
2. Define the code that implements the task in terms of these inputs.
|
|
3. Determine the scope the task will go in.
|
|
|
|
These parts are then combined like the parts of a setting are combined.
|
|
|
|
Defining a basic task
|
|
~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
A task is defined using ``:=``
|
|
|
|
::
|
|
|
|
intTask := 1 + 2
|
|
|
|
stringTask := System.getProperty("user.name")
|
|
|
|
sampleTask := {
|
|
val sum = 1 + 2
|
|
println("sum: " + sum)
|
|
sum
|
|
}
|
|
|
|
As mentioned in the introduction, a task is evaluated on demand.
|
|
Each time ``sampleTask`` is invoked, for example, it will print the sum.
|
|
If the username changes between runs, ``stringTask`` will take different values in those separate runs.
|
|
(Within a run, each task is evaluated at most once.)
|
|
In contrast, settings are evaluated once on project load and are fixed until the next reload.
|
|
|
|
Tasks with inputs
|
|
~~~~~~~~~~~~~~~~~
|
|
|
|
Tasks with other tasks or settings as inputs are also defined using ``:=``.
|
|
The values of the inputs are referenced by the ``value`` method. This method
|
|
is special syntax and can only be called when defining a task, such as in the
|
|
argument to ``:=``. The following defines a task that adds one to the value
|
|
produced by ``intTask`` and returns the result.
|
|
|
|
::
|
|
|
|
sampleTask := intTask.value + 1
|
|
|
|
Multiple settings are handled similarly:
|
|
|
|
::
|
|
|
|
stringTask := "Sample: " + sampleTask.value + ", int: " + intValue.value
|
|
|
|
Task Scope
|
|
~~~~~~~~~~
|
|
|
|
As with settings, tasks can be defined in a specific scope. For example,
|
|
there are separate ``compile`` tasks for the ``compile`` and ``test``
|
|
scopes. The scope of a task is defined the same as for a setting. In the
|
|
following example, ``test:sampleTask`` uses the result of
|
|
``compile:intTask``.
|
|
|
|
::
|
|
|
|
sampleTask.in(Test) :=
|
|
intTask.in(Compile).value * 3
|
|
|
|
// with a different punctuation style
|
|
sampleTask in Test := (intTask in Compile).value * 3
|
|
|
|
On precedence
|
|
~~~~~~~~~~~~~
|
|
|
|
As a reminder, method precedence is by the name of the method.
|
|
|
|
1. Assignment methods have the lowest precedence. These are methods with
|
|
names ending in ``=``, except for ``!=``, ``<=``, ``>=``, and names
|
|
that start with ``=``.
|
|
2. Methods starting with a letter have the next highest precedence.
|
|
3. Methods with names that start with a symbol and aren't included in 1.
|
|
have the highest precedence. (This category is divided further
|
|
according to the specific character it starts with. See the Scala
|
|
specification for details.)
|
|
|
|
Therefore, the second variant in the previous example is equivalent to
|
|
the following:
|
|
|
|
::
|
|
|
|
(sampleTask in Test) := ( (intTask in Compile).value * 3 )
|
|
|
|
Modifying an Existing Task
|
|
==========================
|
|
|
|
The examples in this section use the following key definitions, which
|
|
would go in a ``Build`` object in a ``.scala`` file or directly in a ``.sbt`` file.
|
|
|
|
::
|
|
|
|
val unitTask = taskKey[Unit]("A side-effecting task.")
|
|
val intTask = taskKey[Int]("A task that returns an integer.")
|
|
val stringTask = taskKey[String]("A task that returns String")
|
|
|
|
The examples themselves are valid settings in a ``build.sbt`` file or as
|
|
part of a sequence provided to ``Project.settings``.
|
|
|
|
In the general case, modify a task by declaring the previous task as an
|
|
input.
|
|
|
|
::
|
|
|
|
// initial definition
|
|
intTask := 3
|
|
|
|
// overriding definition that references the previous definition
|
|
intTask := intTask.value + 1
|
|
|
|
Completely override a task by not declaring the previous task as an
|
|
input. Each of the definitions in the following example completely
|
|
overrides the previous one. That is, when ``intTask`` is run, it will
|
|
only print ``#3``.
|
|
|
|
::
|
|
|
|
intTask := {
|
|
println("#1")
|
|
3
|
|
}
|
|
|
|
intTask := {
|
|
println("#2")
|
|
5
|
|
}
|
|
|
|
intTask := {
|
|
println("#3")
|
|
sampleTask.value - 3
|
|
}
|
|
|
|
To apply a transformation to a single task, without using additional
|
|
tasks as inputs, use ``~=``. This accepts the function to apply to the
|
|
task's result:
|
|
|
|
::
|
|
|
|
intTask := 3
|
|
|
|
// increment the value returned by intTask
|
|
intTask ~= { (x: Int) => x + 1 }
|
|
|
|
Advanced Task Operations
|
|
========================
|
|
|
|
The previous sections demonstrated the most common way to define a task.
|
|
Advanced task definitions require the implementation to be separate from the binding.
|
|
For example, a basic separate definition looks like:
|
|
|
|
::
|
|
|
|
// Define a new, standalone task implemention
|
|
val intTaskImpl: Initialize[Task[Int]] = Def.task { sampleTask.value - 3 }
|
|
|
|
// Bind the implementation to a specific key
|
|
intTask := intTaskImpl.value
|
|
|
|
Note that whenever ``.value`` is used, it must be within a task definition, such as
|
|
within ``Def.task`` above or as an argument to ``:=``.
|
|
|
|
The examples in this section use the task keys defined in the previous section.
|
|
|
|
Dependencies
|
|
------------
|
|
|
|
To depend on the side effect of some tasks without using their values
|
|
and without doing additional work, use ``dependOn`` on a sequence of
|
|
tasks. The defining task key (the part on the left side of ``:=``) must
|
|
be of type ``Unit``, since no value is returned.
|
|
|
|
::
|
|
|
|
val unitTaskImpl: Initialize[Task[Unit]] = Seq(stringTask, sampleTask).dependOn
|
|
|
|
unitTask := unitTaskImpl.value
|
|
|
|
To add dependencies to an existing task without using their values, call
|
|
``dependsOn`` on the task and provide the tasks to depend on. For
|
|
example, the second task definition here modifies the original to
|
|
require that ``stringTask`` and ``sampleTask`` run first:
|
|
|
|
::
|
|
|
|
intTask := 4
|
|
|
|
val intTaskImpl = intTask.dependsOn(stringTask, sampleTask)
|
|
|
|
intTask := intTaskImpl.value
|
|
|
|
Note that you can sometimes use the usual syntax:
|
|
|
|
::
|
|
|
|
intTask := 4
|
|
|
|
intTask := {
|
|
val ignore = (stringTask.value, sampleTask.value)
|
|
intTask.value // use the original result
|
|
}
|
|
|
|
Streams: Per-task logging
|
|
-------------------------
|
|
|
|
New in sbt 0.10+ are per-task loggers, which are part of a more general
|
|
system for task-specific data called Streams. This allows controlling
|
|
the verbosity of stack traces and logging individually for tasks as well
|
|
as recalling the last logging for a task. Tasks also have access to
|
|
their own persisted binary or text data.
|
|
|
|
To use Streams, get the value of the ``streams`` task. This is a
|
|
special task that provides an instance of
|
|
`TaskStreams <../../api/sbt/std/TaskStreams.html>`_
|
|
for the defining task. This type provides access to named binary and
|
|
text streams, named loggers, and a default logger. The default
|
|
`Logger <../../api/sbt/Logger.html>`_,
|
|
which is the most commonly used aspect, is obtained by the ``log``
|
|
method:
|
|
|
|
::
|
|
|
|
myTask := {
|
|
val s: TaskStreams = streams.value
|
|
s.log.debug("Saying hi...")
|
|
s.log.info("Hello!")
|
|
}
|
|
|
|
You can scope logging settings by the specific task's scope:
|
|
|
|
::
|
|
|
|
logLevel in myTask := Level.Debug
|
|
|
|
traceLevel in myTask := 5
|
|
|
|
To obtain the last logging output from a task, use the ``last`` command:
|
|
|
|
.. code-block:: console
|
|
|
|
$ last myTask
|
|
[debug] Saying hi...
|
|
[info] Hello!
|
|
|
|
The verbosity with which logging is persisted is controlled using the
|
|
``persistLogLevel`` and ``persistTraceLevel`` settings. The ``last``
|
|
command displays what was logged according to these levels. The levels
|
|
do not affect already logged information.
|
|
|
|
Handling Failure
|
|
----------------
|
|
|
|
This section discusses the ``failure``, ``result``, and ``andFinally``
|
|
methods, which are used to handle failure of other tasks.
|
|
|
|
``failure``
|
|
~~~~~~~~~~~
|
|
|
|
The ``failure`` method creates a new task that returns the ``Incomplete`` value
|
|
when the original task fails to complete normally. If the original task succeeds,
|
|
the new task fails.
|
|
`Incomplete <../../api/sbt/Incomplete.html>`_
|
|
is an exception with information about any tasks that caused the failure
|
|
and any underlying exceptions thrown during task execution.
|
|
|
|
For example:
|
|
|
|
::
|
|
|
|
intTask := error("Failed.")
|
|
|
|
val intTask := {
|
|
println("Ignoring failure: " + intTask.failure.value)
|
|
3
|
|
}
|
|
|
|
This overrides the ``intTask`` so that the original exception is printed and the constant ``3`` is returned.
|
|
|
|
``failure`` does not prevent other tasks that depend on the target
|
|
from failing. Consider the following example:
|
|
|
|
::
|
|
|
|
intTask := if(shouldSucceed) 5 else error("Failed.")
|
|
|
|
// Return 3 if intTask fails. If intTask succeeds, this task will fail.
|
|
aTask := intTask.failure.value - 2
|
|
|
|
// A new task that increments the result of intTask.
|
|
bTask := intTask.value + 1
|
|
|
|
cTask := aTask.value + bTask.value
|
|
|
|
The following table lists the results of each task depending on the initially invoked task:
|
|
|
|
============== =============== ============= ============== ============== ==============
|
|
invoked task intTask result aTask result bTask result cTask result overall result
|
|
============== =============== ============= ============== ============== ==============
|
|
intTask failure not run not run not run failure
|
|
aTask failure success not run not run success
|
|
bTask failure not run failure not run failure
|
|
cTask failure success failure failure failure
|
|
intTask success not run not run not run success
|
|
aTask success failure not run not run failure
|
|
bTask success not run success not run success
|
|
cTask success failure success failure failure
|
|
============== =============== ============= ============== ============== ==============
|
|
|
|
The overall result is always the same as the root task (the directly
|
|
invoked task). A ``failure`` turns a success into a failure, and a failure into an ``Incomplete``.
|
|
A normal task definition fails when any of its inputs fail and computes its value otherwise.
|
|
|
|
``result``
|
|
~~~~~~~~~~
|
|
|
|
The ``result`` method creates a new task that returns the full ``Result[T]`` value for the original task.
|
|
`Result <../../api/sbt/Result.html>`_
|
|
has the same structure as ``Either[Incomplete, T]`` for a task result of
|
|
type ``T``. That is, it has two subtypes:
|
|
|
|
- ``Inc``, which wraps ``Incomplete`` in case of failure
|
|
- ``Value``, which wraps a task's result in case of success.
|
|
|
|
Thus, the task created by ``result`` executes whether or not the original task succeeds or fails.
|
|
|
|
For example:
|
|
|
|
::
|
|
|
|
intTask := error("Failed.")
|
|
|
|
intTask := intTask.result.value match {
|
|
case Inc(inc: Incomplete) =>
|
|
println("Ignoring failure: " + inc)
|
|
3
|
|
case Value(v) =>
|
|
println("Using successful result: " + v)
|
|
v
|
|
}
|
|
|
|
This overrides the original ``intTask`` definition so that if the original task fails, the exception is printed and the constant ``3`` is returned. If it succeeds, the value is printed and returned.
|
|
|
|
|
|
andFinally
|
|
~~~~~~~~~~
|
|
|
|
The ``andFinally`` method defines a new task that runs the original task
|
|
and evaluates a side effect regardless of whether the original task
|
|
succeeded. The result of the task is the result of the original task.
|
|
For example:
|
|
|
|
::
|
|
|
|
intTask := error("I didn't succeed.")
|
|
|
|
val intTaskImpl = intTask andFinally { println("andFinally") }
|
|
|
|
intTask := intTaskImpl.value
|
|
|
|
This modifies the original ``intTask`` to always print "andFinally" even
|
|
if the task fails.
|
|
|
|
Note that ``andFinally`` constructs a new task. This means that the new
|
|
task has to be invoked in order for the extra block to run. This is
|
|
important when calling andFinally on another task instead of overriding
|
|
a task like in the previous example. For example, consider this code:
|
|
|
|
::
|
|
|
|
intTask := error("I didn't succeed.")
|
|
|
|
val intTaskImpl = intTask andFinally { println("andFinally") }
|
|
|
|
otherIntTask := intTaskImpl.value
|
|
|
|
If ``intTask`` is run directly, ``otherIntTask`` is never involved in
|
|
execution. This case is similar to the following plain Scala code:
|
|
|
|
::
|
|
|
|
def intTask(): Int =
|
|
error("I didn't succeed.")
|
|
|
|
def otherIntTask(): Int =
|
|
try { intTask() }
|
|
finally { println("finally") }
|
|
|
|
intTask()
|
|
|
|
It is obvious here that calling intTask() will never result in "finally"
|
|
being printed.
|