2013-03-27 14:17:53 +01:00
|
|
|
==========================
|
|
|
|
|
Tasks/Settings: Motivation
|
|
|
|
|
==========================
|
2012-09-15 00:08:35 +02:00
|
|
|
|
2013-03-27 14:17:53 +01:00
|
|
|
This page motivates the task and settings system.
|
|
|
|
|
You should already know how to use tasks and settings, which are
|
|
|
|
|
described in the :doc:`getting started guide </Getting-Started/More-About-Settings>`
|
|
|
|
|
and on the :doc:`Tasks` page.
|
2012-09-15 00:08:35 +02:00
|
|
|
|
2013-03-27 14:17:53 +01:00
|
|
|
An important aspect of the task system is to combine two common, related steps in a build:
|
2012-09-15 00:08:35 +02:00
|
|
|
|
|
|
|
|
1. Ensure some other task is performed.
|
|
|
|
|
2. Use some result from that task.
|
|
|
|
|
|
2013-03-27 14:17:53 +01:00
|
|
|
Earlier versions of sbt configured these steps separately using
|
2012-09-15 00:08:35 +02:00
|
|
|
|
|
|
|
|
1. Dependency declarations
|
|
|
|
|
2. Some form of shared state
|
|
|
|
|
|
|
|
|
|
To see why it is advantageous to combine them, compare the situation to
|
|
|
|
|
that of deferring initialization of a variable in Scala. This Scala code
|
|
|
|
|
is a bad way to expose a value whose initialization is deferred:
|
|
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
|
|
|
|
// Define a variable that will be initialized at some point
|
|
|
|
|
// We don't want to do it right away, because it might be expensive
|
|
|
|
|
var foo: Foo = _
|
|
|
|
|
|
|
|
|
|
// Define a function to initialize the variable
|
|
|
|
|
def makeFoo(): Unit = ... initialize foo ...
|
|
|
|
|
|
|
|
|
|
Typical usage would be:
|
|
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
|
|
|
|
makeFoo()
|
|
|
|
|
doSomething( foo )
|
|
|
|
|
|
|
|
|
|
This example is rather exaggerated in its badness, but I claim it is
|
|
|
|
|
nearly the same situation as our two step task definitions. Particular
|
|
|
|
|
reasons this is bad include:
|
|
|
|
|
|
|
|
|
|
1. A client needs to know to call ``makeFoo()`` first.
|
|
|
|
|
2. ``foo`` could be changed by other code. There could be a
|
|
|
|
|
``def makeFoo2()``, for example.
|
|
|
|
|
3. Access to foo is not thread safe.
|
|
|
|
|
|
|
|
|
|
The first point is like declaring a task dependency, the second is like
|
|
|
|
|
two tasks modifying the same state (either project variables or files),
|
|
|
|
|
and the third is a consequence of unsynchronized, shared state.
|
|
|
|
|
|
2012-11-19 02:29:01 +01:00
|
|
|
In Scala, we have the built-in functionality to easily fix this: ``lazy val``.
|
2012-09-15 00:08:35 +02:00
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
|
|
|
|
lazy val foo: Foo = ... initialize foo ...
|
|
|
|
|
|
|
|
|
|
with the example usage:
|
|
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
|
|
|
|
doSomething( foo )
|
|
|
|
|
|
|
|
|
|
Here, ``lazy val`` gives us thread safety, guaranteed initialization
|
|
|
|
|
before access, and immutability all in one, DRY construct. The task
|
|
|
|
|
system in sbt does the same thing for tasks (and more, but we won't go
|
|
|
|
|
into that here) that ``lazy val`` did for our bad example.
|
|
|
|
|
|
|
|
|
|
A task definition must declare its inputs and the type of its output.
|
|
|
|
|
sbt will ensure that the input tasks have run and will then provide
|
|
|
|
|
their results to the function that implements the task, which will
|
|
|
|
|
generate its own result. Other tasks can use this result and be assured
|
|
|
|
|
that the task has run (once) and be thread-safe and typesafe in the
|
|
|
|
|
process.
|
|
|
|
|
|
|
|
|
|
The general form of a task definition looks like:
|
|
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
2012-11-19 02:29:01 +01:00
|
|
|
myTask := {
|
|
|
|
|
val a: A = aTask.value
|
|
|
|
|
val b: B = bTask.value
|
2012-09-15 00:08:35 +02:00
|
|
|
... do something with a, b and generate a result ...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
(This is only intended to be a discussion of the ideas behind tasks, so
|
|
|
|
|
see the :doc:`sbt Tasks </Detailed-Topics/Tasks>` page
|
2012-11-19 02:29:01 +01:00
|
|
|
for details on usage.) Here, ``aTask`` is assumed to produce a
|
2012-09-15 00:08:35 +02:00
|
|
|
result of type ``A`` and ``bTask`` is assumed to produce a result of
|
|
|
|
|
type ``B``.
|
|
|
|
|
|
|
|
|
|
Application
|
|
|
|
|
-----------
|
|
|
|
|
|
|
|
|
|
As an example, consider generating a zip file containing the binary jar,
|
|
|
|
|
source jar, and documentation jar for your project. First, determine
|
|
|
|
|
what tasks produce the jars. In this case, the input tasks are
|
|
|
|
|
``packageBin``, ``packageSrc``, and ``packageDoc`` in the main
|
|
|
|
|
``Compile`` scope. The result of each of these tasks is the File for the
|
|
|
|
|
jar that they generated. Our zip file task is defined by mapping these
|
|
|
|
|
package tasks and including their outputs in a zip file. As good
|
|
|
|
|
practice, we then return the File for this zip so that other tasks can
|
|
|
|
|
map on the zip task.
|
|
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
2012-11-19 02:29:01 +01:00
|
|
|
zip := {
|
|
|
|
|
val bin: File = (packageBin in Compile).value
|
|
|
|
|
val src: File = (packageSrc in Compile).value
|
|
|
|
|
val doc: File = (packageDoc in Compile).value
|
|
|
|
|
val out: File = zipPath.value
|
2012-09-15 00:08:35 +02:00
|
|
|
val inputs: Seq[(File,String)] = Seq(bin, src, doc) x Path.flat
|
|
|
|
|
IO.zip(inputs, out)
|
|
|
|
|
out
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
The ``val inputs`` line defines how the input files are mapped to paths
|
|
|
|
|
in the zip. See :doc:`/Detailed-Topics/Mapping-Files` for details.
|
|
|
|
|
The explicit types are not required, but are included for clarity.
|
|
|
|
|
|
|
|
|
|
The ``zipPath`` input would be a custom task to define the location of
|
|
|
|
|
the zip file. For example:
|
|
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
2012-11-19 02:29:01 +01:00
|
|
|
zipPath :=
|
|
|
|
|
target.value / "out.zip"
|
2012-09-15 00:08:35 +02:00
|
|
|
|