SBT CRASH COURSEBy /
Michal Bigos @teliatko
SBT CRASH COURSE1. Why SBT2. How SBT works3. Example of simple project4. Core concepts
WHY SBTGOOD BUILD TOOL CRITERIA
Reproducibility - automating build, gives you more time todo real stuffConventions - using sensible defaults, no need to specifyevery last option and commandExperience - distilation of developer wisdom, e.g. testbefore publishPortability - good build tool should protect you fromdifferences betweeen systemsEcosystem - allow to extend build easily
WHY SBTLITTLE HISTORY: APACHE ANT
Former standard build tool for Java projectsPros:
1. Portability - build is defined in XML by chaining tasks2. Experience - abillity to explicitly define dependencies
between tasksCons:
1. Conventions - no default build lifecycle, each build ispiece of art
2. Ecosystem - not easy to extend, task definitiondistributed as jar
WHY SBTLITTLE HISTORY: APACHE MAVEN
Currently heavily used in enterprise Java projectsMost opinionated build toolPros:
1. Portability - build is defined in XML so called POM2. Experience, Conventions - Maven introduces default
lifecycle with its phases and tasks. It promotesdeclarative dependency management.
Cons:1. Ecosystem - not easy to extend. Maven plug-in
archtecrure requires plug-in written in Java, then POMsand packaging and availability in repository.
WHY SBTLITTLE HISTORY: GOOD PARTS
Default project layout (Maven)Default project behavior (Maven)Declarative dependency management (Maven)Portability (Ant, Maven)
WHY SBTFEATURES AT GLACE
Uses Scala to describe buildCan be used for Java and ScalaMinimal configuration (inspired by Maven)A number of build-in tasksDeclarative dependency management (using Apache Ivy)PortabilityReactive development environmentAllows use Scala REPLIncremental compilationAutomatic re-compilation with different versions of Scala
HOW SBT WORKSTASKS
Task based, more like ANT, no phases like in MavenIf you want to do something you execute a taskIf you want to ensure that a task runs after another, add anexplicit dependency between the tasksOutput of a task is value, which can be of any type and pastto any another taskMultiple tasks can depend upon the output of the sametaskBy default tasks are executed in parallelUsing dependency tree sbt can work out what can be run inparallel or in sequence
HOW SBT WORKSDEFAULT 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
HOW SBT WORKSTASKS 'VS PLUGINS
Appear directly in build definition file, shared via VCSTask can be turned into plugin and shared via repository
val gitHeadCommitSHA = taskKey[String]("Determines the current git commit SHA")gitHeadComitSHA := Process("git rev-parse HEAD").lines.head
HOW SBT WORKSPHASES 'VS TASK DEPENDENCIES
In Maven, the order of execution tasks in phases alwaysleads to confusion
Default goals for a phase are executed before explicitlydefined and those are executed in implicit order ofdefinition in POM file
Implicit order of execution can cause problems whenparallelizing build, if there are dependencies between goalsSBT is per default parallel, that's why explicit definition oftask dependencies is needed
It's similar to definition a custom lifecycle in Maven,which is not easy too
HOW SBT WORKSPARALLEL EXECUTION
If task A depends on B, and C also depends on B => SBT willrun B first and then A and C in parallel
HOW SBT WORKSPASSING INFORMATION BETWEEN TASKS
In Maven and ANT it's very hard to pass an informationbetween tasks
Usually through an intermediate fileIn SBT, you can simply return the value from the task anduse it in another dependent taskThis makes chaining tasks a lot easier
HOW SBT WORKSWORKING WITH SCALA
Cross compilation for multiple Scala versions => Scala isbinary 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"})
HOW SBT WORKSWORKING WITH SCALA, TAKE TWO
Scala compiler generates lots more classes and JVM takeslonger to start-up => interactive environmentScala compilation is slow (in comparision to Java) =>incremental compilationMulti-module builds => parallel execution, child modulesdon't need to know about parent module
EXAMPLE OF SIMPLE PROJECTA COMMON DEVELOPERS USAGE
CORE CONCEPTSBUILD 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"
CORE CONCEPTSSETTINGS
Mechanism to configure a build to to perform th ework weneed to
SBT reads all the settings defined in build at load time andruns their initializations
name := "big-project"| | |key operator intialization
CORE CONCEPTSSETTINGS, TAKE TWO
Settings are typesafe => every key has only one type and anyvalue placed into setting must match exact type
Grouping of SettingKey[T] with Initialize[T]creates Setting[T]
name := "big-project"| |SettingKey[String] Initialize[String]
CORE CONCEPTSSETTINGS, 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")
CORE CONCEPTSSETTINGS, TAKE FOUR
Initializations are a Scala expressions that can produce valueof desired type
Intializations may use other settings via setting.valuemethod.
version := "1.0"
libraryDependencies += ("org.scalatest" %% "scalatest" % version.value)| |SetingKey[ModuleID] Initialization[ModuleId] \ / Setting[ModuleID]
CORE CONCEPTSDEFINING DEPENDENCIES
Definition of exact version
Cross-compiled dependency
groupId % artifactId % version % scope
groupId %% artifactId % version % scope | Use appropriate scala version from project
CORE CONCEPTSCIRCULAR REFERENCES
Because SBT can use values of one setting to instatiateanother, it's possible to create circular referencesBuild will fail to load when circular references aredetected.
CORE CONCEPTSCUSTOM TASKS AND SETTINGS
For version 0.12.4 have to be defined in Scala file not sbtoneFrom 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
Definitions are compiled first and can reference anotherdefinitionsSettings are executed after definitions, hence can refenceany definitionTasks are executed every time the value is requested
CORE CONCEPTSCUSTOM TASKS AND SETTINGS
For version 0.12.4 have to be defined in Scala file not sbtoneFrom 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
Definitions are compiled first and can reference anotherdefinitionsSettings are executed after definitions, hence can refenceany definitionTasks are executed every time the value is requested
MORE TO COVER1. Scopes2. Multi-module projects3. Basic SBT objects in Scala
THANKS FOR YOUR ATTENTION