Created Design Overview (markdown)

harrah 2012-10-17 08:47:36 -07:00
parent b5c289b309
commit c80c0b71d2
1 changed files with 114 additions and 0 deletions

114
Design-Overview.md Normal file

@ -0,0 +1,114 @@
# 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
[xMain.run]: http://www.scala-sbt.org/0.12.1/sxr/Main.scala.html#11563
[launcher]: http://www.scala-sbt.org/release/docs/Detailed-Topics/Launcher.html
[command line applications]: http://www.scala-sbt.org/release/docs/Extending/Command-Line-Applications.html
[Scala Days 2012 talk]: http://skillsmatter.com/podcast/scala/tab-completion-parser-combinators
[Conscript]: https://github.com/n8han/conscript
[State]: http://www.scala-sbt.org/release/docs/Extending/Build-State.html
[Commands]: http://www.scala-sbt.org/release/docs/Extending/Commands.html
[ScriptMain]: http://www.scala-sbt.org/0.12.1/sxr/Main.scala.html#11564
[ConsoleMain]: http://www.scala-sbt.org/0.12.1/sxr/Main.scala.html#11565
[Scripts]: http://www.scala-sbt.org/release/docs/Detailed-Topics/Scripts.html
[build loaders]: http://www.scala-sbt.org/release/docs/Extending/Build-Loaders.html
[BuildStructure]: http://www.scala-sbt.org/0.12.1/api/sbt/Load$$BuildStructure.html
[Extracted]: http://www.scala-sbt.org/0.12.1/api/sbt/Extracted.html
[typesafe map]: http://www.scala-sbt.org/0.12.1/api/sbt/AttributeMap.html
[SessionSettings]: http://www.scala-sbt.org/0.12.1/api/sbt/SessionSettings.html
[release plugin]: https://github.com/sbt/sbt-release
[reapply]: https://github.com/sbt/sbt-release/blob/master/src/main/scala/ReleaseExtra.scala#L228