sbt for dummies

A JJTV Tool Night presentation, January 2014

Tomer Gabel (@tomerg), Wix

In a nutshell

sbt is a Scala-centric build tool

  • Written in Scala
  • ... for Scala

The punchline

sbt gives you...

Convention over configuration

  • Maven-ish directory structure
  • Maven-ish lifecycle
  • Ivy dependency management

Scala-centric

  • Build "scripts" defined in Scala
  • Mixed-language projects
  • Incremental compilation
  • Support for advanced testing frameworks

REPL

  • Introspection
  • Continuous build/test
  • Interactive flows

Exposition

What's in a build tool?

Project model

  • Multi-project builds
  • Lifecycle
  • Properties
  • Tasks

sbt's model is immutable

Dependency management

  • Based on Ivy
  • Supports Maven repositories
  • Automatic Scala version handling

Plugin model

  • Mix in additional features
  • Customize lifecycle stages
  • Examples include:
    • IDE integration
    • Web containers/Servlet API
    • Code generation
    • Release process and publishing

The sbt project model

Fundamentals

  • Keys
  • Configurations
  • Settings
  • Tasks

Everything is keyed

Settings


val scalaVersion: SettingKey[String]
val scalacOptions: TaskKey[Seq[String]]
val libraryDependencies: SettingKey[Seq[ModuleID]]
						

Tasks


val compile: TaskKey[Analysis]
val test: TaskKey[Unit]
						

Everything is scoped

The three axes:

  • Project
  • Configuration
  • Task

For example:


scalacOptions in ( Compile, doc ) := Seq.empty
publishArtifact in Test := false
						

Delegations

  • A setting or task exists in multiple scopes
    • scalacOptions in compile and test
  • Each key has a list of delegates
  • The list is searched in-order for a bound value


> inspect test:scalacOptions
[info] Task: scala.collection.Seq[java.lang.String]
[info] Description:
[info] 	Options for the Scala compiler.
...
[info] Delegates:
[info] 	my-project/test:scalacOptions
[info] 	my-project/runtime:scalacOptions
[info] 	my-project/compile:scalacOptions
[info] 	my-project/*:scalacOptions
[info] 	{.}/test:scalacOptions
...
						

Dependencies

Each setting or task can depend on another. For example:


libraryDependencies <+= 	// The '<' prefix denotes a dependency
  scalaVersion( "org.scala-lang" % "scala-reflect" % _ % "provided" )
						

A dependency is implicitly created:


 inspect libraryDependencies
[info] Setting: scala.collection.Seq[sbt.ModuleID] = List(org.scala-lang:scala-library:2.10.3, org.scala-lang:scala-reflect:2.10.3:provided, org.scalatest:scalatest:2.0:test)
[info] Description:
[info] 	Declares managed dependencies.
...
[info] Dependencies:
[info] 	my-project/*:scalaVersion
[info] Reverse dependencies:
[info] 	my-project/*:allDependencies
...
						

Multi-project builds

  • One root project
  • Child projects are explicitly declared
  • Projects can depend on siblings
  • Projects can aggregate siblings



lazy val common = Project in file( "common" )

lazy val model = Project in file( "model" ) dependsOn( common )

lazy val webapp = Project in file( "webapp" ) dependsOn( common, model )

lazy val root = Project in file( "." ) aggregate( common, model, webapp )
						

sbt project structure

Build definitions

  • Full-blown definitions in project/<file>.scala
  • Short-hand definitions in <file>.sbt
    • Really just a sequence of settings
    • May depend on full definitions
    • Take precedence over full definitions

Library dependencies

  • Drop unmanaged JARs under <project>/lib
  • Add managed dependencies to libraryDependencies
  • Reference artifacts with the helper operator %
    • Dependencies can be scoped to a configuration
    • Use %% helper for Scala dependencies



libraryDependencies ++= Seq(
  "org.springframework" %  "spring-context" % "3.2.5.RELEASE"         ,
  "org.springframework" %  "spring-test"    % "3.2.5.RELEASE" % "test",
  "org.scalatest"       %% "scalatest"      % "2.0"           % "test"						
)
						

Plugins

  • Added to the build project classpath
  • Usually provide extra settings and tasks
  • By convention, declared in <project>/<plugin>.sbt


project/Release.sbt:


addSbtPlugin( "com.earldouglas" % "xsbt-web-plugin" % "0.6.0" )
						

build.sbt:


import com.earldouglas.xsbtwebplugin.PluginKeys._

seq( webSettings:_* )

port in container.Configuration := 12345
						

The sbt REPL

Introspection

Use the REPL to inspect your model:

  • inspect <key> inspects the key metadata
  • inspect tree <key> inspects the dependency tree
  • show <key> lists the value


The REPL provides a specialized key syntax:

[project/][config:][task][::key]

Inspecting trees


> inspect tree compile		// Simplified output
[info] my-project/compile:compile = Task[Analysis]
[info]   +-my-project/compile:compile::compileInputs = Task[Compiler$Inputs]
[info]   | +-*/*:maxErrors = 100
[info]   | +-my-project/compile:dependencyClasspath = Task[Seq[File]]
[info]   | +-my-project/compile:sources = Task[Seq[File]]
[info]   | +-my-project/compile:scalacOptions = Task[Seq[String]]
[info]   | +-my-project/compile:classDirectory = core/target/scala-2.10/classes
[info]   | +-*/*:javacOptions = Task[Seq[String]]
[info]   |
[info]   +-my-project/compile:compile::streams = Task[...]
[info]     +-*/*:streamsManager = Task[...]						
...
						

Execution

Executing tasks:


> test
[info] ResultMatchersTest:
[info] RuleViolationMatcher
[info] - should correctly match a rule violation based on value
[info] - should fail to match a non-matching rule violation
[info] Run completed in 314 milliseconds.
[info] Total number of tests run: 2
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 2, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 1 s, completed Jan 20, 2014 3:52:58 PM						
						

Continuous execution:


> ~test
[info] Compiling 11 Scala sources to /Users/tomer/dev/accord/core/target/scala-2.10/test-classes...
[error] /Users/tomer/dev/accord/core/src/test/scala/com/wix/accord/tests/dsl/OrderingOpsDslSpec.scala:52: not enough arguments for method between: (lowerBound: T, upperBound: T)(implicit evidence$1: Ordering[T])com.wix.accord.dsl.Between[T].
[error] one error found
[error] (accord-core/test:compile) Compilation failed
[error] Total time: 2 s, completed Jan 20, 2014 3:52:24 PM
1. Waiting for source changes... (press enter to interrupt)
						

Interactive flows


arilou:dev tomer$ activator new

Enter an application name
> hello-scala

The new application will be created in /home/typesafe/Desktop/hello-scala

Enter a template name, or hit tab to see a list of possible templates
> 
hello-akka        hello-play        hello-scala       reactive-stocks   
> hello-scala
OK, application "hello-scala" is being created using the
"hello-scala" template.

To run "hello-scala" from the command-line, run:
/home/typesafe/Desktop/hello-scala/activator run
						

Caveats

It looks scary


... but is actually pretty well-documented

Ecosystem is small


  • ... relative to Maven
  • Active group on Google Groups
  • Community is active on StackOverflow
  • Plenty of powerful plugins!

Details may be vague


Dive into sources every now and then

... you'll learn a lot

Questions?

Thank you!


This presentation is available on GitHub under:
Creative Commons License