Skip to content

Design Overview

harrah edited this page Oct 17, 2012 · 1 revision

Overview of sbt's Design

The main execution layers that comprise sbt are the launcher, the command engine, and the build tool, which is subdivided into project configuration and task execution.

Launcher

Execution starts with the launcher, which is a self-contained jar that pulls down an application and its dependencies according to a configuration file and then runs it. For sbt, this means getting the requested version of sbt (from project/build.properties) and the right Scala version and then starting sbt itself. The launcher also provides several services to launched applications, like reloading a different version of the application or retrieving other Scala versions. It is possible to use the launcher to launch other applications as well as documented on the launcher page. Conscript is a lightweight application distribution mechanism built on top of this functionality.

Command Engine

The next level is the command engine, which is where State and Commands come in. When sbt is launched, it sets up an initial State that registers the default Commands (set, reload, alias, ...) and sets the initial commands to run. The initial commands are: add commands from .sbtrc files, load the project, and enter the interactive prompt if no commands were specified. Then, sbt starts the engine with MainLoop.runLogged(initialState), which runs until all commands are processed and then exits. You can see this configuration in xMain.run, which is the entry-point to sbt.

State keeps the list of scheduled command strings in remainingCommands. runLogged processes State by:

  1. taking the next command string from remainingCommands (a Seq[String])
  2. parsing it according to the current State's definedCommands (a Seq[Command])
  3. producing the command's State => State function from this parse
  4. applying the function to the current State (running the command)
  5. looping back to 1 if the new State has remainingCommands

There are actually two alternative entry-points to sbt that are defined below the main one, ScriptMain and ConsoleMain, that provide the different front-ends described on the Scripts page. In addition, the command engine can be used to write standalone interactive command line applications.

Build tool

It is on top of this command engine that the build tool part of sbt is built. The build tool part is kicked off with the command that loads the build: reload (called reload because users usually call it in the context of reloading the build). The other important build-tool-related command runs tasks on a loaded build.

Build loading

The reload command's job is to produce a BuildStructure and put it in State for future commands like task execution to use. BuildStructure is the data type that represents everything about a build: projects and relationships, evaluated settings, and logging configuration. It produces a BuildStructure by evaluating build loaders. The default build loader configures sbt using the standard .sbt and project/Build.scala files that you know. The build loaders page shows an example of how one might write a build loader to read from a pom.xml instead (you couldn't define custom tasks or anything like that without another file, though).

Once the reload command has the BuildStructure value, it stores it in State.attributes, keyed by Keys.stateBuildStructure.

State.attributes

Now, a diversion back to State to cover attributes... State.attributes is a typesafe map. Keys are of type AttributeKey[T] and you can only associate values of type T with that key. State has convenience methods set/get that delegate to the underlying attributes map.

To pass information between commands, you put data in the attributes map. An example of this is release plugin, which sets skipTests according to the command line options. The release command itself schedules other commands to run and those can configure themselves from skipTests in State.attributes. This is one way a command can change the behavior of tasks without needing to reload the project: it sets attributes in State and the task accesses State via the state task.

Project.extract

The Project.extract(state) call at its core calls state.get(Keys.stateBuildStructure) to get the BuildStructure back. It does some other things as well:

  • throws a nicer exception if a project isn't loaded
  • loads the session with state.get(Keys.sessionSettings)
  • returns the session and structure in an Extracted value, which provides a better interface to them

Session settings

The SessionSettings datatype tracks a few pieces of information that are not persisted. The two main pieces are:

  • the current project: changed by the project command, for example
  • additional settings: added by the set command, for example

SessionSettings only tracks this information; setting these values on a SessionSettings object does not apply the changes. In particular, the project has to be reloaded for the additional settings to take effect. Reloading checks the settings for problems like references to non-existing settings and then the settings are reevaluated. The release plugin has a reapply method that shows the proper way to add settings to the current project.

Modifying settings

Given a sequence of settings (Seq[Setting[_]]), the Load.transformSettings method resolves any unspecified scopes in the raw settings sequence. This usually means associating the settings with the current project. Then, BuiltinCommands.reapply actually makes the settings take effect. It checks, processes, and loads the new settings, updates BuildStructure, and stuffs everything back into State.

Tasks

The task execution command pulls out the current, loaded project from State (via Project.extract), looks up the task to run, and runs it. Commands can get the values produced by tasks, but tasks don't directly transform State or run commands (there are some rare exceptions). Tasks can get the current State via the state task, which is a special task that gets injected by the task execution command and set to the current State.

Comments, requests for clarification

Clone this wiki locally