Wednesday 4 February 2015

SBT (Scala Build Tool): a getting started guide for veteran newbs

Go forth and build! ...my code!

Sooooo... Scala Build Tool.

Like all things with me recently, it started out by reading a book about Scala while commuting to work (riding the tube for 2 hours daily gives you a LOT of time to read). Now, this is like... the 8th (?) programming language I've learned in my life? Anyway, suffice to say, by the time I got around to feeding my OCD and writing the obligatory "Hello World" using Scala IDE for Eclipse, I probably knew more Scala than was good for me.

Anyway, I was determined to mingle with the cool kids, so I started looking at Akka's actors. Within minutes, I was overwhelmed by a desire to use Akka for re-enacting a movie scene. So here it is, from The Big Lebowski, a scene with The Dude (Akka actor Jeff Bridges) and Jackie (Akka actor Ben Gazzara):

object BigLebowski extends App {
  implicit val system = ActorSystem("the-stage")
  // our actors
  val gazzara = system.actorOf(Props[BenGazzara])
  val bridges = system.actorOf(Props[JeffBridges])

  // the roles
  val jackie = new Movie.Role("Jackie Treehorn")
  val dude = new Movie.Role("The Dude")

  // the script for our scene
  val script = Movie.Script("BigLebowski-Scene-1",
    // gazzara plays jackie, and bridges plays "the dude"
    Map(gazzara -> jackie, bridges -> dude),
    // here's the scene's dialogue:
    (jackie, "Interactive erotic software. The wave of the future, Dude. " +
      "One hundred percent electronic!"),
    (dude, "Yeah well, I still jerk off manually."))

  // director distributes script, and... Action!
  system.actorSelection("/user/*") ! script
}
Unfortunately, we had a problem. Well, two actually, but let's ignore for a moment that the cool kids think 90s movies are lame. From a technical perspective, my problem was that as soon as I ventured outside the standard library, I needed a tool to manage my build. Something that would look up and download dependencies, compile code in the proper order (using the appropriate classpath), etc. And thus the cool kids pointed me to SBT: the Scala Build Tool.

Hello, SBT

XML is so very lame, that the cool kids don't even know what that is. It is rumored an old man once tried to explain it to them, but none of them payed attention (except the few that died of boredom 30 seconds into the explanation). The net outcome was that the cool kids never knew the joys of Maven and Ant, but at least they lived to write tools like Gradle and SBT. But this is all beside the point. The point is, you wanna be jaw-dropping-awesome. And here's how you do it:

version := "0.0.1"

name := "big-lebowski-movie"

scalaVersion  := "2.11.4"

resolvers += Opts.resolver.mavenLocalFile

resolvers += "Typesafe Releases" at "http://repo.typesafe.com/typesafe/releases/"

libraryDependencies ++= {
  val akkaV = "2.3.8"
  Seq(
    "com.typesafe.akka"          %%  "akka-actor"              % akkaV
  )
}
The file above is called build.sbt and should be placed at the root folder of your project (by the way, no need to copy-paste: you can fetch the entire code from my github repository, here). You should have a look over there now. Ignore for a moment the project folder and notice how your Scala sources should reside in relative path src/main/scala. So basically, just add that build.sbt and you're ready to rock. But let's go over it now line-by-line, to see what it all means.

SBT's model and API

The first thing you should know about this file is that the content is actually regular Scala: there is no such thing as some special SBT language that you must learn to define your project build. The theory goes... if you know Scala you already know how to write a build.sbt file. But that's only half the truth really. It is half the truth because there is such a thing as an SBT library, which defines a Scala API for describing your build. So to write a build.sbt you need to know Scala and also the SBT library API.

Which brings us to the second thing you need to know about build.sbt: the following lines are implied to exist at the very beginning of the build.sbt file:

import sbt._
import Process._
import Keys._

// actual content of build.sbt follows here
Now, these statements pull into scope that magic SBT library API so you can use things like the := operator prevalent in the first few lines, the %% and % methods used in the line close to the bottom, etc. All of these are actually methods defined by the SBT library and implicitly brought in scope using the implied import lines above. We next go over each line and discuss the SBT API being used.

It's all keys and settings

Here's an inconvenient truth: assume you could disregard for a moment all the coolness of SBT, what do you think you'd be left with? It may come as a surprise, but you'd actually find yourself thinking of a Map data structure. Yes, I mean that simple data structure that associates keys with values. But in the case of SBT, we have several pre-defined keys with special meaning, and values that are instances of the Scala class Setting[T] (defined by the SBT library). So here's that file again, this time with enlightening comments:
// the key "version" is assigned a Setting[String] value of "0.0.1"
version := "0.0.1"

// the key "name" is assigned a Setting[String] value of "big-lebowski-movie"
name := "big-lebowski-movie"

// the key "scalaVersion" is assigned a Setting[String] value of "2.11.4"
scalaVersion  := "2.11.4"

// we'll discuss the rest later
As you can see, each line associates a key with a value. The keys used -- version, name, scalaVersion -- are not random. SBT defines these keys in its library and uses their assigned values for specific purposes during the build. You can probably guess that name and version refer to your project and what you want to call it. They are used for things like the name of the JAR file generated when you package our project. The key scalaVersion on the other hand tells SBT what version of Scala to use when compiling your code. The := is actually a method that is defined on SBT's Key class, which accepts a single argument and assigns a Setting[T] value to the key. Therefore the statement name := "big-lebowski-movie" is in fact the Scala expression name.:=("big-lebowski-movie"), assigning a Setting[String] value to the key name. No magic there...

We now discuss the special key resolvers, along with the += method. We have invoked resolvers.+=(...) twice:

// this tells SBT to look for dependencies in your local maven repository
resolvers += Opts.resolver.mavenLocalFile
// above line is actually resolvers.+=(Opts.resolver.mavenLocalFile)

// this tells SBT to look for dependencies in TypeSafe's Ivy repository
// at https://repo.typesafe.com/typesafe/ivy-releases
resolvers += Resolver.typesafeIvyRepo("releases")
// above line is actually resolvers.+=(Resolver.typesafeIvyRepo("releases"))
The special key resolvers tells SBT where to look for library dependencies needed to build your projects. The same way the := method replaces a key's Setting value with a new one, the += appends an extra value to the existing one. So in this case, we add two sources for resolving dependencies, namely our local maven repository and TypeSafe's Ivy repository (the latest releases of Akka are there). The parameters used in each case are also from SBT's library. There really is a Resolver.typesafeIvyRepo(s:String) defined among others in there. You can see a list of these predefined settings in the SBT documentaion.

And the final key we used is libraryDependencies, whose value is a Setting[Seq[ModuleID]], where ModuleID is an SBT class representing a dependency:

// libraryDependencies.++=(...)
libraryDependencies ++= {
  // the curly braces are simply a Scala block of code. therefore the block's
  // value is equal to the last expression in the block

  // but first, we assign a value variable
  val akkaV = "2.3.8"

  // try adding this line and running sbt again:
  // println("YES, IT'S REALLY JUST SCALA CODE!")

  // this is the last expression of the code block.
  // as such, the entire block evaluates to the statement below
  Seq(
    // the three strings below are the organization, name and revision respectively
    // the %% and % methods create a ModuleID object from the 3 strings
    "com.typesafe.akka"          %%  "akka-actor"              % akkaV
    // so the net result is a Seq[ModuleID], with a single element in it
  )
}
The ++= method works on keys whose value is a sequence of something (i.e. Setting[Seq[T]] and is used to append a sequence of elements to the existing value. In our case we append a sequence with a single dependency (akka-actor).

The %% and % methods are used to construct a ModuleID object using three String values. Notice how we use the variable akkaV for the revision. In an actual use case where we might pull in several Akka modules, keeping the version in a single place will make it easy to change to another version.

Blank lines of coolness

The only open point right now should be the blank lines. If you look back at our original file, you will see that each key-setting definition is followed by one (blank line). This is NOT a coincidence. In fact, if you remove one of them, you will see SBT complaining while parsing the file. The reason for this is that SBT expects build.sbt to contain a list of key-setting mapping expressions (as opposed to statements). Therefore some separator needs to be used. The newline character happens to be what SBT chose as a delimiter. You can read all about it here.

Conclusion

A few last words. Like other build tools, SBT is extensible. Several plug-ins exist that extend its capabilities. One way to add plugins is by using the project/plugins.sbt file, which we use to install a plugin called sbt-assembly:

addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.12.0")

This adds the capability to assemble a fat JAR with all of your code and its dependencies. If you run sbt assembly you will find a target/scala-2.11/big-lebowski-movie-assembly-0.0.1.jar archive including everything needed by your project. More information can be found on this plugin's web page.

Another plugin you may find useful if you use Scala IDE for Eclipse is the sbteclipse-plugin. This will generate Eclipse project files for your code so that you may work with it in Eclipse. You just need to run sbt eclipse and the required files (.project and .classpath) will be generated. All you need is to add the plugin:

addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "3.0.0")

I hope you found this post useful.

No comments:

Post a Comment