sbt for dummies – A JJTV Tool Night presentation, January 2014 – In a nutshell



sbt for dummies – A JJTV Tool Night presentation, January 2014 – In a nutshell

0 0


jjtv-sbt

A JJTV Tool Night presentation on sbt

On Github holograph / jjtv-sbt

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

sbt is designed to provide the best experience for Scala developers, meaning: * It's Scala throughout (sbt itself, build definitions, plugins) * It attempts to tackle some of Scala's paint points (mixed-language projects, compilation times, advanced test frameworks)

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

Configurations are like lifecycle scopes, which essentially track Ivy configurations. Indirecting a task through its configuration is a powerful mechanism for referencing an atomic lifecycle stage, similar to a Maven goal; for example, compile:packageBin or test:test. A key is selected via a predefined search path (essentially compile->runtime->test and falling back to the parent project).

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: