An introduction to SBT and how it works internally.
Talk from September 2013 Slovak Scala User Group meet-up, http://www.meetup.com/slovak-scala/events/133327122/
2. SBT CRASH COURSE
1. Why SBT
2. How SBT works
3. Example of simple project
4. Core concepts
3. WHY SBT
GOOD BUILD TOOL CRITERIA
Reproducibility - automating build, gives you more time to
do real stuff
Conventions - using sensible defaults, no need to specify
every last option and command
Experience - distilation of developer wisdom, e.g. test
before publish
Portability - good build tool should protect you from
differences betweeen systems
Ecosystem - allow to extend build easily
4. WHY SBT
LITTLE HISTORY: APACHE ANT
Former standard build tool for Java projects
Pros:
1. Portability - build is defined in XML by chaining tasks
2. Experience - abillity to explicitly define dependencies
between tasks
Cons:
1. Conventions - no default build lifecycle, each build is
piece of art
2. Ecosystem - not easy to extend, task definition
distributed as jar
5. WHY SBT
LITTLE HISTORY: APACHE MAVEN
Currently heavily used in enterprise Java projects
Most opinionated build tool
Pros:
1. Portability - build is defined in XML so called POM
2. Experience, Conventions - Maven introduces default
lifecycle with its phases and tasks. It promotes
declarative dependency management.
Cons:
1. Ecosystem - not easy to extend. Maven plug-in
archtecrure requires plug-in written in Java, then POMs
and packaging and availability in repository.
6. WHY SBT
LITTLE HISTORY: GOOD PARTS
Default project layout (Maven)
Default project behavior (Maven)
Declarative dependency management (Maven)
Portability (Ant, Maven)
7. WHY SBT
FEATURES AT GLACE
Uses Scala to describe build
Can be used for Java and Scala
Minimal configuration (inspired by Maven)
A number of build-in tasks
Declarative dependency management (using Apache Ivy)
Portability
Reactive development environment
Allows use Scala REPL
Incremental compilation
Automatic re-compilation with different versions of Scala
8. HOW SBT WORKS
TASKS
Task based, more like ANT, no phases like in Maven
If you want to do something you execute a task
If you want to ensure that a task runs after another, add an
explicit dependency between the tasks
Output of a task is value, which can be of any type and past
to any another task
Multiple tasks can depend upon the output of the same
task
By default tasks are executed in parallel
Using dependency tree sbt can work out what can be run in
parallel or in sequence
9. HOW SBT WORKS
DEFAULT STRUCTURE AND LAYOUT
Inspired by Maven
{project root}
project/
build.properties
plugins.sbt
src/
main/
scala/
java/
resources/
test/
scala/
java/
resources/
target/
build.sbt
10. HOW SBT WORKS
TASKS 'VS PLUGINS
Appear directly in build definition file, shared via VCS
Task can be turned into plugin and shared via repository
val gitHeadCommitSHA = taskKey[String]("Determines the current git comm
it SHA")
gitHeadComitSHA := Process("git rev-parse HEAD").lines.head
11. HOW SBT WORKS
PHASES 'VS TASK DEPENDENCIES
In Maven, the order of execution tasks in phases always
leads to confusion
Default goals for a phase are executed before explicitly
defined and those are executed in implicit order of
definition in POM file
Implicit order of execution can cause problems when
parallelizing build, if there are dependencies between goals
SBT is per default parallel, that's why explicit definition of
task dependencies is needed
It's similar to definition a custom lifecycle in Maven,
which is not easy too
12. HOW SBT WORKS
PARALLEL EXECUTION
If task A depends on B, and C also depends on B => SBT will
run B first and then A and C in parallel
13. HOW SBT WORKS
PASSING INFORMATION BETWEEN TASKS
In Maven and ANT it's very hard to pass an information
between tasks
Usually through an intermediate file
In SBT, you can simply return the value from the task and
use it in another dependent task
This makes chaining tasks a lot easier
14. HOW SBT WORKS
WORKING WITH SCALA
Cross compilation for multiple Scala versions => Scala is
binary compatible only between minor version releases
Not restricted to Scala versions either
scalaVersion := "2.10.1"
crossScalaVersions := Seq("2.8.2", "2.9.2")
libraryDependencies += (scalaBinaryVersion.value match {
case "2.10" => "org.specs2" %% "specs2" % "2.0"
case "2.9.1" => "org.specs2" %% "specs2" % "1.12.4"
})
15. HOW SBT WORKS
WORKING WITH SCALA, TAKE TWO
Scala compiler generates lots more classes and JVM takes
longer to start-up => interactive environment
Scala compilation is slow (in comparision to Java) =>
incremental compilation
Multi-module builds => parallel execution, child modules
don't need to know about parent module
17. CORE CONCEPTS
BUILD FILES
build.properties
build.sbt
Blank line is required between settings
project/
build.properties - defines SBT version
plugins.sbt - defines SBT plugins
build.sbt - defines actual build, project settings
sbt.version = 0.12.4
name := "big-project"
version := "1.0"
scalaVersion := "2.10.0"
libraryDependencies += "org.scalatest" %% "scalatest" % "1.9.1" % "
test"
18.
19. CORE CONCEPTS
SETTINGS
Mechanism to configure a build to to perform th ework we
need to
SBT reads all the settings defined in build at load time and
runs their initializations
name := "big-project"
| | |
key operator intialization
20. CORE CONCEPTS
SETTINGS, TAKE TWO
Settings are typesafe => every key has only one type and any
value placed into setting must match exact type
Grouping of SettingKey[T]with Initialize[T]
creates Setting[T]
name := "big-project"
| |
SettingKey[String] Initialize[String]
21. CORE CONCEPTS
SETTINGS, TAKE THREE
Operators used with settings
Types have to match
name := "big-project"
|
assignment
libraryDependencies += "org.scalatest" %% "scalatest" % "1.9.1"
|
append
append multiple values
|
libraryDependencies ++= Seq(
"org.scalatest" %% "scalatest" % "1.9.1" % "test",
"org.specs2" %% "specs2" % "2.0" % "test"
)
22. CORE CONCEPTS
SETTINGS, TAKE FOUR
Initializations are a Scala expressions that can produce value
of desired type
Intializations may use other settings via setting.value
method.
version := "1.0"
libraryDependencies += ("org.scalatest" %% "scalatest" % version.value)
| |
SetingKey[ModuleID] Initialization[ModuleId]
/
Setting[ModuleID]
23. CORE CONCEPTS
DEFINING DEPENDENCIES
Definition of exact version
Cross-compiled dependency
groupId % artifactId % version % scope
groupId %% artifactId % version % scope
|
Use appropriate scala version from project
24. CORE CONCEPTS
CIRCULAR REFERENCES
Because SBT can use values of one setting to instatiate
another, it's possible to create circular references
Build will fail to load when circular references are
detected.
25. CORE CONCEPTS
CUSTOM TASKS AND SETTINGS
For version 0.12.4 have to be defined in Scala file not sbt
one
From 0.13.0 they can be defined in sbt too
val gitHeadCommitSHA = taskKey[String]("Determines the current git
commit SHA")
|
setting/task definition
gitHeadComitSHA := Process("git rev-parse HEAD").lines.head
| |
setting/task key block of code returning value
26. Definitions are compiled first and can reference another
definitions
Settings are executed after definitions, hence can refence
any definition
Tasks are executed every time the value is requested
27. CORE CONCEPTS
CUSTOM TASKS AND SETTINGS
For version 0.12.4 have to be defined in Scala file not sbt
one
From 0.13.0 they can be defined in sbt too
val gitHeadCommitSHA = taskKey[String]("Determines the current git
commit SHA")
|
setting/task definition
gitHeadComitSHA := Process("git rev-parse HEAD").lines.head
| |
setting/task key block of code returning value
28. Definitions are compiled first and can reference another
definitions
Settings are executed after definitions, hence can refence
any definition
Tasks are executed every time the value is requested
29. MORE TO COVER
1. Scopes
2. Multi-module projects
3. Basic SBT objects in Scala