This page explains other ways to create a Setting, beyond the basic :=
method. It assumes you’ve read .sbt build definition and
scopes.
Remember, a build definition creates a list of Setting
,
which is then used to transform sbt’s description of the build (which is
a map of key-value pairs). A Setting is a transformation with sbt’s
earlier map as input and a new map as output. The new map becomes sbt’s
new state.
Different settings transform the map in different ways.
Earlier, you read about the :=
method.
The Setting
which :=
creates puts a fixed, constant value in the new,
transformed map. For example, if you transform a map with the setting
name := "hello"
the new map has the string "hello"
stored under the key
name.
Settings must end up in the master list of settings to do any good (all
lines in a build.sbt
automatically end up in the list, but in a
.scala file you can get it wrong by creating a Setting
without putting it where sbt will find it).
+=
and ++=
Assignment with :=
is the simplest transformation, but keys have other
methods as well. If the T
in SettingKey[T]
is a sequence, i.e. the key’s
value type is a sequence, you can append to the sequence rather than
replacing it.
+=
will append a single element to the sequence.
++=
will concatenate another sequence.
For example, the key sourceDirectories in Compile
has a Seq[File]
as its
value. By default this key’s value would include src/main/scala
. If you
wanted to also compile source code in a directory called source (since
you just have to be nonstandard), you could add that directory:
sourceDirectories in Compile += new File("source")
Or, using the file()
function from the sbt package for convenience:
sourceDirectories in Compile += file("source")
(file()
just creates a new File
.)
You could use ++=
to add more than one directory at a time:
sourceDirectories in Compile ++= Seq(file("sources1"), file("sources2"))
Where Seq(a, b, c, ...)
is standard Scala syntax to construct a
sequence.
To replace the default source directories entirely, you use :=
of
course:
sourceDirectories in Compile := Seq(file("sources1"), file("sources2"))
Reference the value of another task or setting by calling value on the
key for the task or setting. The value method is special and may only be
called in the argument to :=
, +=
, or ++=
.
As a first example, consider defining the project organization to be the same as the project name.
// name our organization after our project (both are SettingKey[String])
organization := name.value
Or, set the name to the name of the project’s directory:
// name is a Key[String], baseDirectory is a Key[File]
// name the project after the directory it's inside
name := baseDirectory.value.getName
This transforms the value of baseDirectory
using the standard getName
method of java.io.File
.
Using multiple inputs is similar. For example,
name := "project " + name.value + " from " + organization.value + " version " + version.value
This sets the name in terms of its previous value as well as the organization and version settings.
In the setting name := baseDirectory.value.getName
, name
will have a
dependency on baseDirectory
. If you place the above in build.sbt
and
run the sbt interactive console, then type inspect name
, you should see
(in part):
[info] Dependencies:
[info] *:baseDirectory
This is how sbt knows which settings depend on which other settings. Remember that some settings describe tasks, so this approach also creates dependencies between tasks.
For example, if you inspect compile
you’ll see it depends on another key
compileInputs
, and if you inspect compileInputs
it in turn depends on
other keys. Keep following the dependency chains and magic happens. When
you type compile
sbt automatically performs an update
, for example. It
Just Works because the values required as inputs to the compile
computation require sbt to do the update
computation first.
In this way, all build dependencies in sbt are automatic rather than explicitly declared. If you use a key’s value in another computation, then the computation depends on that key. It just works!
Whenever a setting uses :=
, +=
, or ++=
to create a dependency on itself
or another key’s value, the value it depends on must exist. If it does
not, sbt will complain. It might say “Reference to undefined setting“,
for example. When this happens, be sure you’re using the key in the
scope that defines it.
It’s possible to create cycles, which is an error; sbt will tell you if you do this.
You can compute values of some tasks or settings to define or append value for another task. It’s done by using Def.task
and taskValue
, as argument to :=
, +=
or ++=
.
As a first example, consider appending a source generator using the project base directory and compilation classpath.
sourceGenerators in Compile += Def.task {
myGenerator(baseDirectory.value, (managedClasspath in Compile).value)
}.taskValue
As noted in .sbt build definition, task keys create a
Setting[Task[T]]
rather than a Setting[T]
when you build a setting with
:=
, etc. Tasks can use settings as inputs, but settings cannot use tasks
as inputs.
Take these two keys (from Keys):
val scalacOptions = taskKey[Seq[String]]("Options for the Scala compiler.")
val checksums = settingKey[Seq[String]]("The list of checksums to generate and to verify for dependencies.")
(scalacOptions
and checksums
have nothing to do with each other, they
are just two keys with the same value type, where one is a task.)
It is possible to compile a build.sbt
that aliases scalacOptions
to
checksums
, but not the other way. For example, this is allowed:
// The scalacOptions task may be defined in terms of the checksums setting
scalacOptions := checksums.value
There is no way to go the other direction. That is, a setting key can’t depend on a task key. That’s because a setting key is only computed once on project load, so the task would not be re-run every time, and tasks expect to re-run every time.
// The checksums setting may not be defined in terms of the scalacOptions task
checksums := scalacOptions.value
+=
and ++=
Other keys can be used when appending to an existing setting or task,
just like they can for assigning with :=
.
For example, say you have a coverage report named after the project, and you want to add it to the files removed by clean:
cleanFiles += file("coverage-report-" + name.value + ".txt")