Example usage of sbt build system

The sbt build system

This is a writeup explaining basic usage of the Simple Build Tool system for Scala. The complete build system is fairly comprehensive so I will only be focusing on aspects like setting up the project and using basic commands. A nice property of the build tool is its support for continuous compilation and testing. While you’re working on sourcefiles, the sbt tool will monitor changes then trigger recompilation and retesting whenever you modify code. By the time you need to check for errors in compilation or tests, a report might already be waiting for you.

For this example I’ll refer to a standard text editor and console. Alternatively, there exists sbt integration for IDEs like Eclipse for those that prefer that.

Getting started

Beginning, make sure you have a properly configured Java. Once that is in place you then need to download the sbt-launch.jar (0.13.1) library file. There are two options when using sbt. You can either install it system-wide, or include it with your project. I opted for the last one in this post.

With the download ready you can setup the project structure as follows:

project-name/
  src/
    main/scala/  -- your project sourcefiles goes here
    test/scala/  -- your project testfiles goes here
  lib/  -- manually handled library jars goes here
  build.sbt  -- your project's sbt config file
  sbt  -- sbt launchscript for unix
  sbt.cmd  -- sbt launchscript for windows
  sbt-launch.jar  -- the sbt library

Content of sbt file (if Unix mark it as executable using chmod u+x sbt):

#!/bin/bash
java -Xmx512M -jar $(dirname $(readlink -f "$0"))/sbt-launch.jar run "$@"

Content of sbt.cmd file:

@echo off
set SCRIPT_DIR=%~dp0
java -Xmx512M -jar "%SCRIPT_DIR%sbt-launch.jar" %*

Content of build.sbt file:

// Notes on syntax
//  - settings are initialized with :=
//  - dependency paths given by %
//  - dependency paths against specific scala versions are given by %%
name := "Project"
 
version := "1.0"
 
scalaVersion := "2.10.3"
 
libraryDependencies += "org.scalatest" % "scalatest_2.10" % "2.0" % "test"

With that the project ready for use. All that remains is running sbt from console to pull in required library dependencies (or later manually with update):

D:\test-scala3>sbt
Getting org.fusesource.jansi jansi 1.11 ...
downloading http://repo1.maven.org/maven2/org/fusesource/jansi/jansi/1.11/jansi-1.11.jar ...
        [SUCCESSFUL ] org.fusesource.jansi#jansi;1.11!jansi.jar (639ms)
:: retrieving :: org.scala-sbt#boot-jansi
        confs: [default]
        1 artifacts copied, 0 already retrieved (111kB/26ms)
Getting org.scala-sbt sbt 0.13.1 ...
...

Once started sbt will continue to accept commands (if you need to quit it, simply write exit).

Lets add the sourcefile /src/main/scala/demo/Demo.scala and compile it:

package demo
 
object Demo {
  def clamp(value: Double, min: Double, max: Double): Double = {
    if (value < min) min else if (value > max) max else value
  }
}
> compile
[info] Updating {file:}...
...
[info] Done updating.
[info] Compiling 1 Scala source to target\scala-2.10\classes...
[success] Total time: 2 s, completed 22.jan.2014 18:09:53

Lets also add a test /src/test/scala/demo/DemoSpec.scala and test our assumptions:

package demo
 
import org.scalatest.FlatSpec
import org.scalatest.matchers.ShouldMatchers
import Demo.clamp
 
class DemoSpec extends FlatSpec {
  "clamp" should "handle minimum cases" in {
    assert(clamp(-1.0, 0.0, 1.0) == 0.0)
  }
}
> test
[info] Compiling 1 Scala source to target\scala-2.10\test-classes...
[info] DemoSpec:
[info] clamp
[info] - should handle minimum cases
[info] Run completed in 339 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 2 s, completed 22.jan.2014 18:12:13

Instead of issuing these commands manually each iteration, lets instead enable automatic testing with ~test (the tilde tells sbt to run the command continuously).

> ~test
[info] DemoSpec:
[info] clamp
[info] - should handle minimum cases
[info] Run completed in 488 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 1 s, completed 22.jan.2014 18:14:15

Updating /src/test/scala/demo/DemoSpec.scala with additional test-cases now triggers recompile and retesting:

  it should "handle maximum cases" in {
    assert(clamp(2.0, 0.0, 1.0) == 1.0)
  }
[info] Compiling 1 Scala source to target\scala-2.10\test-classes...
[info] DemoSpec:
[info] clamp
[info] - should handle minimum cases
[info] - should handle maximum cases
[info] Run completed in 346 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: 2 s, completed 22.jan.2014 18:16:16
  it should "handle inrange cases" in {
    assert(clamp(0.9, 0.0, 1.0) == 0.5)
  }
[info] Compiling 1 Scala source to target\scala-2.10\test-classes...
[info] DemoSpec:
[info] clamp
[info] - should handle minimum cases
[info] - should handle maximum cases
[info] - should handle inrange cases *** FAILED ***
[info]   0.9 did not equal 0.5 (DemoSpec.scala:15)
[info] Run completed in 411 milliseconds.
[info] Total number of tests run: 3
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 2, failed 1, canceled 0, ignored 0, pending 0
[info] *** 1 TEST FAILED ***
[error] Failed tests:
[error]         demo.DemoSpec
[error] (test:test) sbt.TestsFailedException: Tests unsuccessful
[error] Total time: 2 s, completed 22.jan.2014 18:16:40

Here the tests did not complete. The error message points to where corrections are needed:

  it should "handle inrange cases" in {
    assert(clamp(0.5, 0.0, 1.0) == 0.5)
  }
[info] Compiling 1 Scala source to target\scala-2.10\test-classes...
[info] DemoSpec:
[info] clamp
[info] - should handle minimum cases
[info] - should handle maximum cases
[info] - should handle inrange cases
[info] Run completed in 372 milliseconds.
[info] Total number of tests run: 3
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 3, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 2 s, completed 22.jan.2014 18:17:59
5. Waiting for source changes... (press enter to interrupt)

If you want API documentation generated, simply issue the doc command then locate them in /target/scala-version/api:

> doc
[info] Main Scala API documentation to target\scala-2.10\api...
model contains 3 documentable templates
[info] Main Scala API documentation successful.
[success] Total time: 1 s, completed 22.jan.2014 18:18:25

Once editing of sourcecode is done you can issue package commands to generate the final libraries:

> package
[info] Packaging target\scala-2.10\project_2.10-1.0.jar ...
[info] Done packaging.
[success] Total time: 0 s, completed 22.jan.2014 18:18:39
> package-doc
[info] Packaging target\scala-2.10\project_2.10-1.0-javadoc.jar ...
[info] Done packaging.
[success] Total time: 0 s, completed 22.jan.2014 18:18:52
> package-src
[info] Packaging target\scala-2.10\project_2.10-1.0-sources.jar ...
[info] Done packaging.
[success] Total time: 0 s, completed 22.jan.2014 18:19:06

Further pointers

Presented here was just the absolute basics of sbt to get you started. There are tons of commands available within sbt (use tab-completion to show them all), additional configuration options, and plugins which extend it. If you want more in-depth information about sbt then visit its documentation page. Development on sbt is still ongoing so expect some changes in the future, but by including the sbt build system with your project you can largely ignore upgrades until you’re ready for them.

5 thoughts on “Example usage of sbt build system

  1. steve h

    Thanks for this tutorial.

    There are a lot of old tutorials for prior versions that have sbt behaving *completely* differently than the current release, which e.g. doesn’t do any project set up for the user anymore. This tutorial helped me get started. Looks like a nice tool!

    Thanks again!

  2. Trond Post author

    Thanks. I wrote it since I felt the wiki for sbt had too many details for people who just wanted to get started. As of writing, its documentation is mostly developer notes and references, but that is probably because its author Mark Harrah is still improving its design.

  3. Guest

    Instead of just:

    java -Xmx512M -jar `dirname $0`/sbt-launch.jar “$@”

    Use something like this to actually execute, to (hopefully) prevent spaces messing up the path, and to allow cleanly linking the script into a bin directory, for much simpler installation.

    #!/bin/bash
    java -Xmx512M -jar $(dirname $(readlink -f “$0”))/sbt-launch.jar “$@”

  4. Guest

    Oh, actually, you want:

    #!/bin/bash
    java -Xmx512M -jar $(dirname $(readlink -f “$0″))/sbt-launch.jar run “$@”

Leave a Reply

Your email address will not be published. Required fields are marked *