Settings

Mark Harrah edited this page Jan 4, 2013 · 6 revisions

Wiki Maintenance Note: This page has been partly replaced by Getting Started Basic Def and Getting Started More About Settings. It has some obsolete terminology:

  • we now avoid referring to build definition as "configuration" to avoid confusion with compile configurations
  • we now avoid referring to basic/light/quick vs. full configuration, in favor of ".sbt build definition files" and ".scala build definition files"

However, it may still be worth combing this page for examples or points that are not made in new pages. We may want to add FAQs or topic pages to supplement the Getting Started pages with some of that information. After doing so, this page could simply be a redirect (delete the content, link to the new pages about build definition).

Introduction

A build definition is written in Scala. There are two types of definitions: light and full. A light definition is a quick way of configuring a build, consisting of a list of Scala expressions describing project settings. A full definition is made up of one or more Scala source files that describe relationships between projects and introduce new configurations and settings. This page introduces the Setting type, which is used by light and full definitions for general configuration.

Introductory Examples

Basic examples of each type of definition are shown below for the purpose of getting an idea of what they look like, not for full comprehension of details, which are described at light definition and full definition.

<base>/build.sbt (light)

name := "My Project"

libraryDependencies += "junit" % "junit" % "4.8" % "test"

<base>/project/Build.scala (full)

import sbt._
import Keys._

object MyBuild extends Build
{
   lazy val root = Project("root", file(".")) dependsOn(sub)
   lazy val sub = Project("sub", file("sub")) settings(
      name := "My Project",
      libraryDependencies += "junit" % "junit" % "4.8" % "test"
   )
}

Important Settings Background

The fundamental type of a configurable in sbt is a Setting[T]. Each line in the build.sbt example above is of this type. The arguments to the settings method in the Build.scala example are of type Setting[T]. Specifically, the name setting has type Setting[String] and the libraryDependencies setting has type Setting[Seq[ModuleID]], where ModuleID represents a dependency.

Throughout the documentation, many examples show a setting, such as:

libraryDependencies += "junit" % "junit" % "4.8" % "test"

This setting expression either goes in a light definition (build.sbt) as is or in the settings of a Project instance in a full definition (Build.scala) as shown in the example. This is an important point to understanding the context of examples in the documentation. (That is, you now know where to copy and paste examples now.)

A Setting[T] describes how to initialize a setting of type T. The settings shown in the examples are expressions, not statements. In particular, there is no hidden mutable map that is being modified. Each Setting[T] is a value that describes an update to a map. The actual map is rarely directly referenced by user code. It is not the final map that is usually important, but the operations on the map.

To emphasize this, the setting in the following Build.scala fragment is ignored because it is a value that need to be included in the settings of a Project. (Unfortunately, Scala will discard non-Unit values to get Unit, which is why there is no compile error.)

object Bad extends Build {
  libraryDependencies += "junit" % "junit" % "4.8" % "test"
}
object Good extends Build
{
  lazy val root = Project("root", file(".")) settings(
    libraryDependencies += "junit" % "junit" % "4.8" % "test"
  )
}

Declaring a Setting

There is fundamentally one type of initialization, represented by the <<= method. The other initialization methods :=, +=, ++=, <+=, <++=, and ~= are convenience methods that can be defined in terms of <<=.

The motivation behind the method names is:

  • All methods end with = to obtain the lowest possible infix precedence.
  • A method starting with < indicates that the initialization uses other settings.
  • A single + means a single value is expected and will be appended to the current sequence.
  • ++ means a Seq[T] is expected. The sequence will be appended to the current sequence.

The following sections include descriptions and examples of each initialization method. The descriptions use "will initialize" or "will append" to emphasize that they construct a value describing an update and do not mutate anything. Each setting may be directly included in a light configuration (build.sbt), appropriately separated by blank lines. For a full configuration (Build.scala), the setting must go in a settings Seq as described in the previous section. Information about the types of the left and right hand sides of the methods follows this section.

:=

:= is used to define a setting that overwrites any previous value without referring to other settings. For example, the following defines a setting that will set name to "My Project" regardless of whether name has already been initialized.

name := "My Project"

No other settings are used. The value assigned is just a constant.

+= and ++=

+= is used to define a setting that will append a single value to the current sequence without referring to other settings. For example, the following defines a setting that will append a JUnit dependency to libraryDependencies. No other settings are referenced.

libraryDependencies += "junit" % "junit" % "4.8" % "test"

The related method ++= appends a sequence to the current sequence, also without using other settings. For example, the following defines a setting that will add dependencies on ScalaCheck and specs to the current list of dependencies. Because it will append a Seq, it uses ++= instead of +=.

libraryDependencies ++= Seq(
   "org.scala-tools.testing" %% "scalacheck" % "1.9" % "test",
   "org.scala-tools.testing" %% "specs" % "1.6.8" % "test"
    )
)

The types involved in += and ++= are constrained by the existence of an implicit parameter of type Append.Value[A,B] in the case of += or Append.Values[A,B] in the case of ++=. Here, B is the type of the value being appended and A is the type of the setting that the value is being appended to. See Append for the provided instances.

~=

~= is used to transform the current value of a setting. For example, the following defines a setting that will remove -Y compiler options from the current list of compiler options.

scalacOptions in Compile ~= { (options: Seq[String]) =>
   options filterNot ( _ startsWith "-Y" )
}

The earlier declaration of JUnit as a library dependency using += could also be written as:

libraryDependencies ~= { (deps: Seq[ModuleID]) =>
  deps :+ ("junit" % "junit" % "4.8" % "test")
}

<<=

The most general method is <<=. All other methods can be implemented in terms of <<=. <<= defines a setting using other settings, possibly including the previous value of the setting being defined. For example, declaring JUnit as a dependency using <<= would look like:

libraryDependencies <<= libraryDependencies apply { (deps: Seq[ModuleID]) =>
   // Note that :+ is a method on Seq that appends a single value
   deps :+ ("junit" % "junit" % "4.8" % "test")
}

This defines a setting that will apply the provided function to the previous value of libraryDependencies. apply and Seq[ModuleID] are explicit for demonstration only and may be omitted.

<+= and <++=

The <+= method is a hybrid of the += and <<= methods. Similarly, <++= is a hybrid of the ++= and <<= methods. These methods are convenience methods for using other settings to append to the current value of a setting.

For example, the following will add a dependency on the Scala compiler to the current list of dependencies. Because the scalaVersion setting is used, the method is <+= instead of +=.

libraryDependencies <+= scalaVersion( "org.scala-lang" % "scala-compiler" % _ )

This next example adds a dependency on the Scala compiler to the current list of dependencies. Because another setting (scalaVersion) is used and a Seq is appended, the method is <++=.

libraryDependencies <++= scalaVersion { sv =>
  ("org.scala-lang" % "scala-compiler" % sv) ::
  ("org.scala-lang" % "scala-swing" % sv) ::
  Nil
}

The types involved in <+= and <++=, like += and ++=, are constrained by the existence of an implicit parameter of type Append.Value[A,B] in the case of <+= or Append.Values[A,B] in the case of <++=. Here, B is the type of the value being appended and A is the type of the setting that the value is being appended to. See Append for the provided instances.

Setting types

This section provides information about the types of the left and right-hand sides of the initialization methods. It is currently incomplete.

Setting Keys

The left hand side of a setting definition is of type ScopedSetting. This type has two parts: a key (of type SettingKey) and a scope (of type Scope). An unspecified scope is like using this to refer to the current context. The previous examples on this page have not defined an explicit scope. See Inspecting Settings for details on the axes that make up scopes.

The target (the value on the left) of a method like := identifies one of the main constructs in sbt: a setting, a task, or an input task. It is not an actual setting or task, but a key representing a setting or task. A setting is a value assigned when a project is loaded. A task is a unit of work that is run on-demand after a project is loaded and produces a value. An input task, previously known as a method task in sbt 0.7 and earlier, accepts an input string and produces a task to be run. (The renaming is because it can accept arbitrary input in 0.10+ and not just a space-delimited sequence of arguments like in 0.7.)

A setting key has type SettingKey, a task key has type TaskKey, and an input task has type InputKey. The remainder of this section only discusses settings. See Tasks and Input Tasks for details on the other types (those pages assume an understanding of this page).

To construct a ScopedSetting, select the key and then scope it using the in method (see the ScopedSetting for API details). For example, the setting for compiler options for the test sources is referenced using the scalacOptions key and the Test configuration in the current project.

val ref: ScopedSetting[Seq[String]] = scalacOptions in Test

The current project doesn't need to be explicitly specified, since that is the default in most cases. Some settings are specific to a task, in which case the task should be specified as part of the scope as well. For example, the compiler options used for the console task for test sources is referenced like:

val ref: ScopedSetting[Seq[String]] = scalacOptions in Test in console

In these examples, the type of the setting reference key is given explicitly and the key is assigned to a value to emphasize that it is a normal (immutable) Scala value and can be manipulated and passed around as such.

Computing the value for a setting

The right hand side of a setting definition varies by the initialization method used. In the case of :=, +=, ++=, and ~=, the type of the argument is straightforward (see the ScopedSetting API). For <<=, <+=, and <++=, the type is Initialize[T] (for <<= and <+=) or Initialize[Seq[T]] (for <++=). This section discusses the Initialize type.

A value of type Initialize[T] represents a computation that takes the values of other settings as inputs. For example, in the following setting, the argument to <<= is of type Initialize[File]:

scalaSource in Compile <<= baseDirectory {
   (base: File) => base / "src"
}

This example can be written more explicitly as:

{
  val key: ScopedSetting[File] = scalaSource.in(Compile)
  val init: Initialize[File] = baseDirectory.apply( (base: File) => base / "src" )
  key.<<=(init)
}

To construct a value of type Initialize, construct a tuple of up to nine input ScopedSettings. Then, define the function that will compute the value of the setting given the values for these input settings.

val path: Initialize[File] =
  (baseDirectory, name, version).apply( (base: File, n: String, v: String) =>
    base / (n + "-" + v + ".jar")
  )

This example takes the base directory, project name, and project version as inputs. The keys for these settings are defined in [sbt.Keys], along with all other built-in keys. The argument to the apply method is a function that takes the values of those settings and computes a new value. In this case, that value is the path of a jar.

Initialize[Task[T]]

To initialize tasks, the procedure is similar. There are a few differences. First, the inputs are of type [ScopedTaskable]. The means that either settings (ScopedSetting) or tasks ([ScopedTask]) may be used as the input to a task. Second, the name of the method used is map instead of apply and the resulting value is of type Initialize[Task[T]]. In the following example, the inputs are the [report|Update-Report] produced by the update task and the context configuration. The function computes the locations of the dependencies for that configuration.

val mainDeps: Initialize[Task[File]] =
  (update, configuration).map( (report: UpdateReport, config: Configuration) =>
    report.select(configuration = config.name)
  )

As before, update and configuration are defined in Keys. update is of type TaskKey[UpdateReport] and configuration is of type SettingKey[Configuration].