How to compile, run, and package a Scala project with SBT

This is an excerpt from the 1st Edition of the Scala Cookbook (partially modified for the internet). This is Recipe 18.2, “How to compile, run, and package a Scala project with SBT.”

Problem

You want to use the SBT build tool to compile and run a Scala project, and then package the project as a JAR file.

Solution

Create a directory layout to match what SBT expects, then run sbt compile to compile your project, sbt run to run your project, and sbt package to package your project as a JAR file.

To demonstrate this, create a new SBT project directory structure as shown in Recipe 18.1, and then create a file named Hello.scala in the src/main/scala directory with these contents:

package foo.bar.baz

object Main extends App {
    println("Hello, world")
}

Unlike Java, in Scala, the file’s package name doesn’t have to match the directory name. In fact, for simple tests like this, you can place this file in the root directory of your SBT project, if you prefer.

From the root directory of the project, you can compile the project:

$ sbt compile

run the project:

$ sbt run

and package the project into a JAR file:

$ sbt package

Discussion

The first time you run SBT, it may take a while to download all the dependencies it needs, but after that first run, it will download new dependencies only as needed. The commands executed in the Solution, along with their output, are shown here:

$ sbt compile
[info] Loading global plugins from /Users/Al/.sbt/plugins
[info] Set current project to Basic (in build file:/Users/Al/SbtTests/)
[success] Total time: 0 s

$ sbt run
[info] Loading global plugins from /Users/Al/.sbt/plugins
[info] Set current project to Basic (in build file:/Users/Al/SbtTests/)
[info] Running foo.bar.baz.Main
Hello, world
[success] Total time: 1 s

$ sbt package
[info] Loading global plugins from /Users/Al/.sbt/plugins
[info] Set current project to Basic (in build file:/Users/Al/SbtTests/)
[info] Packaging /Users/Al/SbtTests/target/scala-2.10/basic_2.10-1.0.jar ...
[info] Done packaging.
[success] Total time: 0 s

Because compile is a dependency of run, you don’t have to run compile before each run; just type sbt run.

The JAR file created with sbt package is a normal Java JAR file. You can list its contents with the usual jar tvf command:

$ jar tvf target/scala-2.10/basic_2.10-1.0.jar
   261 Sat Apr 13 13:58:44 MDT 2013 META-INF/MANIFEST.MF
     0 Sat Apr 13 13:58:44 MDT 2013 foo/
     0 Sat Apr 13 13:58:44 MDT 2013 foo/bar/
     0 Sat Apr 13 13:58:44 MDT 2013 foo/bar/baz/
  2146 Sat Apr 13 13:57:52 MDT 2013 foo/bar/baz/Main$.class
  1003 Sat Apr 13 13:57:52 MDT 2013 foo/bar/baz/Main.class
   759 Sat Apr 13 13:57:52 MDT 2013 foo/bar/baz/Main$delayedInit$body.class

You can also execute the main method in the JAR file with the Scala interpreter:

$ scala target/scala-2.10/basic_2.10-1.0.jar
Hello, world
SBT commands

As with any Java-based command, there can be a little startup lag time involved with running SBT commands, so when you’re using SBT quite a bit, it’s common to run these commands in interactive mode from the SBT shell prompt to improve the speed of the process:

$ sbt
> compile
> run
> package

You can run multiple commands at one time, such as running clean before compile:

> clean compile

Common SBT commands

At the time of this writing, there are 247 SBT commands available (which I just found out by hitting the Tab key at the SBT shell prompt, which triggered SBT’s tab completion). Table 18-1 shows a list of the most common commands.

Table 18-1. Descriptions of the most common SBT commands

Command Description
clean Removes all generated files from the target directory.
compile Compiles source code files that are in src/main/scala, src/main/java, and the root directory of the project.
~ compile Automatically recompiles source code files while you’re running SBT in interactive mode (i.e., while you’re at the SBT command prompt).
console Compiles the source code files in the project, puts them on the classpath, and starts the Scala interpreter (REPL).
consoleQuick Starts the Scala interpreter (REPL) with the project dependencies on the classpath.
doc Generates API documentation from your Scala source code using scaladoc.
help   Issued by itself, the help command lists the common commands that are currently available. When given a command, help provides a description of that command.
inspect Displays information about . For instance, inspect library-dependencies displays information about the libraryDependencies setting. (Variables in build.sbt are written in camelCase, but at the SBT prompt, you type them using this hyphen format instead of camelCase.)
package Creates a JAR file (or WAR file for web projects) containing the files in src/main/scala, src/main/java, and resources in src/main/resources.
package-doc Creates a JAR file containing API documentation generated from your Scala source code.
publish Publishes your project to a remote repository. See Recipe 18.15, “Publishing Your Library”.
publish-local Publishes your project to a local Ivy repository. See Recipe 18.15, “Publishing Your Library”. reload Reloads the build definition files (build.sbt, project/.scala, and project/.sbt), which is necessary if you change them while you’re in an interactive SBT session.
run Compiles your code, and runs the main class from your project, in the same JVM as SBT. If your project has multiple main methods (or objects that extend App), you’ll be prompted to select one to run.
test Compiles and runs all tests.
test:console Compiles the source code files in the project, puts them on the classpath, and starts the Scala interpreter (REPL) in “test” mode (so you can use ScalaTest, Specs2, ScalaCheck, etc.).
update Updates external dependencies.

There are many other SBT commands available, and when you use plug-ins, they can also make their own commands available. For instance, Recipe 18.7, “Using SBT with Eclipse” shows that the “sbteclipse” plug-in adds an eclipse command. See the SBT documentation for more information.

Continuous compiling with SBT

As mentioned, you can eliminate the SBT startup lag time by starting the SBT interpreter in “interactive mode.” To do this, type sbt at your operating system command line:

$ sbt
>

When you issue your commands from the SBT shell, they’ll run noticeably faster.

As shown in the Solution, you can issue the compile command from within the SBT shell, but you can also take this a step further and continuously compile your source code by using the ~ compile command instead. When you issue this command, SBT watches your source code files, and automatically recompiles them whenever it sees the code change.

To demonstrate this, start the SBT shell from the root directory of your project:

$ sbt

Then issue the ~ compile command:

> ~ compile
[info] Compiling 1 Scala source to /Users/Al/SbtTests/target/scala-2.10/classes
[success] Total time: 4 s, completed Apr 13, 2013 2:34:23 PM
1. Waiting for source changes... (press enter to interrupt)

Now, any time you change and save a source code file, SBT automatically recompiles it. You’ll see these new lines of output when SBT recompiles the code:

[info] Compiling 1 Scala source to /Users/Al/SbtTests/target/scala-2.10/classes
[success] Total time: 2 s, completed Apr 13, 2013 2:34:32 PM
2. Waiting for source changes... (press enter to interrupt)
Use last to get more information on the last command

From time to time when working in the SBT shell you may have a problem, such as with incremental compiling. When issues like this come up, you may be able to use the shell’s last command to see what happened.

For instance, you may issue a compile command, and then see something wrong in the output:

> compile
[info] Updating ...
[info] Resolving com.typesafe#config;1.0.0 ...
18.2. Compiling, Running, and Packaging a Scala Project with SBT | 587
[info] Compiling 1 Scala source to
YIKES!

I made up the “YIKES!” part, but you get the idea; something goes wrong. To see what happened, issue the last compile command:

> last compile
[debug]
[debug] Initial source changes:

[debug]  removed:Set()
[debug]  added: Set(/Users/Al/Projects/Scala/Foo/Test.scala)
[debug]  modified: Set()
[debug] Removed products: Set()
[debug] Modified external sources: Set()
many more lines of debug output here ...

The last command prints logging information for the last command that was executed. This can help you understand what’s happening, including understanding why something is being recompiled over and over when using incremental compilation.

Typing help last in the SBT interpreter shows a few additional details, including a note about the last-grep command, which can be useful when you need to filter a large amount of output.

More Scala/SBT information