Typesafe Activator

An introduction to sbt

An introduction to sbt

typesafehub
Source
March 12, 2014
sbt java scala

Guides the reader through the basics of sbt given the complete development cycle.

How to get "An introduction to sbt" on your computer

There are several ways to get this template.

Option 1: Choose hello-sbt in the Typesafe Activator UI.

Already have Typesafe Activator (get it here)? Launch the UI then search for hello-sbt in the list of templates.

Option 2: Download the hello-sbt project as a zip archive

If you haven't installed Activator, you can get the code by downloading the template bundle for hello-sbt.

  1. Download the Template Bundle for "An introduction to sbt"
  2. Extract the downloaded zip file to your system
  3. The bundle includes a small bootstrap script that can start Activator. To start Typesafe Activator's UI:

    In your File Explorer, navigate into the directory that the template was extracted to, right-click on the file named "activator.bat", then select "Open", and if prompted with a warning, click to continue:

    Or from a command line:

     C:\Users\typesafe\hello-sbt> activator ui 
    This will start Typesafe Activator and open this template in your browser.

Option 3: Create a hello-sbt project from the command line

If you have Typesafe Activator, use its command line mode to create a new project from this template. Type activator new PROJECTNAME hello-sbt on the command line.

Option 4: View the template source

The creator of this template maintains it at https://github.com/typesafehub/hello-sbt#master.

Option 5: Preview the tutorial below

We've included the text of this template's tutorial below, but it may work better if you view it inside Activator on your computer. Activator tutorials are often designed to be interactive.

Preview the tutorial

Introduction to sbt

sbt is a build tool that brings convention over configuration and the flexibility of customizing builds without requiring a deep understanding of the tool. Projects of simple to medium complexity should be easy to configure using sbt.

This tutorial will cover the following topics

  • Why sbt
  • The CLI
  • The anatomy of an sbt project
  • Settings
  • Tasks
  • Commands
  • Sub-projects
  • Publishing

Why sbt

There are some great build tools out there - why the need for yet another? The well known tool named ANT is actually an acronym for Another Neat Tool; and ANT has been around since the year 2000!

Maven was quite revolutionary when it was introduced given its main goal of "project comprehension". Other developers should be able to come to your project and rapidly comprehend its structure and build behaviors given convention. Prior to Maven many build tools provided a great deal of flexibility but it was often difficult to rapidly become familiar with the associated project.

Gradle is another great tool that has a focus on project comprehension but it also permits the build to be customised quite easily. The approach to customisation is simpler than with Maven though and this is of great appeal to many developers.

sbt has similar goals to Gradle in that project comprehension is an important concern. sbt is also very flexible in permitting custom builds.

What distinguishes sbt is that it takes on a functional approach to expressing a build. Build scripts are aggregated to form an immutable dependency graph of settings and tasks in order to perform a build. Commands are used to transition one state of a build to a new state. More on settings, tasks and commands later.

Given sbt's functional approach a high degree of parallelism can be achieved. The motivation for sbt is therefore performance while also being able to leverage the design of build tools that came before it; in particular to hold the torch of project comprehension high.

The Command Line Interface (CLI)

Another point of differentiation with sbt is that it is not just a tool for building your project's target. sbt is also an interactive development environment in that a CLI is provided where you can obtain the value of settings, evaluate tasks and perform commands.

sbt can be invoked via typing activator or sbt. Activator is a great place to get started (you're using it now!) and its CLI allows you to jump into its UI and also select templates. For now though, we'll use the sbt command to refer to invoking either Activator or sbt.

The sbt command accepts parameters where each parameter is an sbt command e.g:

        
sbt compile
        
    

The above will compile your project. If you type sbt without any command parameter then it will launch you into an interactive session where you can assign, evaluate and perform commands.

The Anatomy of an sbt project

To get started with sbt you don't need anything other than Scala or Java source in the familiar src/main/scala or src/main/java directories. You can just type the sbt command at the root level of that project and start compiling and running code.

The Build File

While you can do the above it is more typical that a project has a build.sbt file. A common question when people first see a build.sbt file is, "why the blank lines?". There are historical reasons for this with one being that each line is a distinct compilation unit. Thus build.sbt files are incrementally compiled. Given advances in Scala incremental compilation this constraint may be relaxed in the future.

A project typically declares its organization, name and version. While not strictly necessary it is also considered good practice to declare the version of Scala to use. If this is not declared then the project's Scala version will correspond to the version used by sbt.

Our build shows that scalaiformSettings are to be used. This is how a plugin's settings are declared. The settings are simply a sequence of descriptors that specify the default build configuration for the given plugin. The appearance of scalaiformSettings here actually tells sbt to format Scala code in a conventional manner before performing a compilation. You don't need these settings or this plugin; it is shown here merely as an example of how a plugin can be referenced.

libraryDependencies is a setting that you'll find yourself assigning to a great deal. These are the libraries that your project depends on. sbt has a short-hand notation for expressing the coordinates of a library; typically three strings separated with a % character. The format is:

        
organization + "%" [+ "%"] + name + "%" + version
        
    

The additional % can appear between the organization and name to indicate which version of the library should be used in accordance with the version of Scala being used for the project. Java libraries do not hold this convention; it is only used when referencing Scala libraries in order to resolve the correct binary level compatibility.

Our libraryDependencies here indicate that we are to use the Specs2 test library for performing the project's tests.

The last section is concerned with publishing and we will come back to this later.

build.sbt files can also be represented using a Scala file in the project's project folder. We don't have any Scala files there but be assured that the build.sbt is roughly equivalent to:

        
import sbt._
import Keys._

object MyBuild extends Build {
  val helloSbt =
    project.in(file(".")).settings(Seq(
      organization := "org.example",
      name := "hello-sbt",
      version := "1.0.0-SNAPSHOT",
      scalaVersion := "2.10.3",
      scalacOptions += "-deprecation",
      ...
    ))
}
        
    

The Project Folder

Any .sbt or .scala files in the project folder will be compiled and used for the build. This powerful facility accommodates the crafting of complex tasks that can be invoked as part of the build. build.sbt files also permit a great deal of flexibility and so are typically used for the bulk of build declaration; what we call the "shape of the build". build.sbt should be able to be referenced and comprehended at a glance.

Two other files commonly found here are:

  • build.properties
  • plugins.sbt

build.properties is used primarily to describe the version of sbt to be used by the project. One really nice feature of sbt is that you can launch other versions of sbt to build a project, and the version of sbt that is required by the project will always be used.

plugins.sbt declares the plugins that will be used by the project. The file is named so by convention, but you could easily use scalaiform.sbt to declare a corresponding dependency instead. If you have many plugins then this approach may be more suitable in order to reduce the length and complexity of a single plugins.sbt file.

addSbtPlugin is slightly different to simply adding a dependency on to the libraryDependencies setting. Adding an sbt plugin will also seek a plugin associated with the version of sbt and its corresponding Scala version.

The rest of the project structure may be familiar to you if you come from Maven or Gradle. src/main and src/test provide source for the main and testing classpaths respectively. target is where build artifacts such as jar files are constructed.

Settings

Settings provide your build configuration. Settings are values evaluated once and then cached for next time. Here is an example of a setting being declared and then assigned:

        
val mySetting = setting[Boolean]("Some boolean setting")

mySetting := true
        
    

Note the := assignment operator. This is a special symbol declaring that true should be assigned to the value of the setting described by mySetting. The = operator and the := operator are not the same.

Settings may depend on other settings e.g.:

        
val anotherSetting = setting[String]("Some other setting")

anotherSetting := mySetting.value.toString
        
    

The .value method access not only retrieves the value associated with the setting, but it also declares that anotherSetting has a dependency on the mySetting setting. sbt will form a dependency graph of settings and ensure that mySetting is assigned before anotherSetting.

If the setting is declared as a sequence then you can append to it:

        
scalacOptions += "-deprecated"

scalacOptions ++= Seq("-deprecated", "-feature")
        
    

sbt declares many standard settings e.g. scalacOptions so you should always look first in sbt's sbt.Keys Scala source to see if there is one you can use before declaring your own.

Tasks

Tasks are similar to settings with the main difference being that they are evaluated once per build. Here is an example task declaration and assignment:

        
val myTask = task[String]("some task")

myTask := "Hello world"
        
    

Tasks may depend on other tasks and settings where settings can only depend on other settings. Just like settings, tasks can also be appended to if they are declared as a sequence.

Commands

You generally don't end up creating your own commands (although you certainly can). Command examples:

  • compile
  • test
  • clean
  • update

Commands take the state of a previous build and produce a new state. Commands can also take parameters and generally depend on tasks and settings.

Commands can also watch for files to change. This is done by prefixing a command with a ~. For example:

        
~test
        
    

In the above, testing will occur when a file changes.

Multi-Project Builds

Multiple projects sharing the same parent hierarchy are easy to accommodate and come in two forms:

  • classpath; and
  • aggregate.

A regular sub-project structure simply requires that there is a sub-folder to the parent. There is no project folder for a sub project as project build meta data is shared across the parent and the sub projects. For example the parent project will declare the plugins that the the sub projects will use.

Given a sub-project folder the parent build.sbt file will declare the following:

        
lazy val root = project.in(file("."))

lazy val mysubproj = project.dependsOn(root)
        
    

In the above, mysubproj is also the name of the sub-directory (this can be overridden with a .in method call). The dependsOn makes the classpath of the root project available to the mysubproj sub-project.

Declaring the val as a lazy val isn't a strict requirement above but, if used, the order of declarations becomes less important. It is therefore considered good practice to use lazy declarations when using the project function.

Aggregate projects can be declared such that commands and tasks performed on the root project will be performed on the associated projects. This behavior is similar to Maven when operating on a parent pom file. To declare an aggregate project:

        
lazy val root = project.in(file(".")).aggregate(mysubproj)

lazy val mysubproj = project
        
    

Performing a compile command on the root project will be performed on all of the aggregated projects. Note how that this time, mysubproj cannot depend on the root given that they'd be a circular dependency in Scala. Also aggregated projects can be built in parallel and therefore cannot depend on each other.

Publishing

Toward the end of the build.sbt file is a section devoted to publishing.

Build artifacts can be published either in ivy or Maven form; their repository structures are different. In general ivy is used for publishing plugins or private repositories and Maven is for public repositories to be consumed by multiple build tools.

The publishTo key declares the repository that will be used for publishing depending on whether the project version indicates a final release or development release. When publishing you will also need to consider whether credentials are required. The sbt documentation details how credentials are provided.

When publishing to Sonatype or other Maven repositories then constraints may be enforced requiring the type of license being used, the name of the developer etc. pomExtra declares the additional XML required to be included in the published artifact's metadata.

To perform a publish you can do one of the following three commands:

  • publish-local
  • publish
  • publish-signed

publish-local is used for development purposes and will publish to your local ivy repository. publish will publish to the repository indicated by the publishTo setting. publishSigned will do the same as publish but also sign the artifact using PGP.

Summary

This template introduces the basics of what you need to become effective with sbt. You can get most things done with the knowledge you have acquired. If you find yourself needing to customize your build then please consult the sbt documentation as you will need to understand the concepts introduced in greater detail.

Welcome to the world of sbt, an integrated development tool focused on build performance!

comments powered by Disqus