Simple Build Tool - SBT
Software development typically includes activities such as compiling source code into binary code, executing tests, packaging binary code into archives, and deploying archives to production systems. A build is a term that refers to compiled code packaged and deployed on production. A build is a technical term for a deliverable. Deliverable is the set of executable code expected by the stakeholders. The process through which a build has to go to become the deliverable is called a build process. Thus a build process, in general, is comprised of compilation, testing, packaging, and deployment.
Build automation is the act of automating the aforementioned build process by means of a build tool. These build tools require you to define the project configuration and dependencies in an artifact called build definition.
Note A dependency is the term that refers to interdependencies between software constructs and is measured by the term coupling. Coupling is the degree of interdependencies between software constructs.
Figure 11-1 shows a quick overview of the popular build tools in the timeline. Make1 is atypical in this list, but it was included because it pioneered the build automation. All the build tools except Make in Figure 11-1 are popular in the JVM landscape.
Figure 11-1. Build tools timeline
As mentioned, Make pioneered the build automation and allowed dependency management from the outset. Make remains widely used, especially in Unix.
Apache Ant2 is similar to Make but is implemented in the Java language and unlike Make, it uses XML to describe the build process and its dependencies. It necessitates the Java platform, and befits building Java projects. Contrary to Ant, Maven3 allows convention over configuration for the build procedure by providing sensible default behavior for projects.
Note Convention over configuration (CoC) refers to, in general, a development approach centered on conventions. It enables developers to specify and configure only the unconventional aspects of the development.
Apache Ivy4 is a dependency manager. It is a sub-project of the Apache Ant project, and helps to resolve project dependencies.
Note Apache Ivy competes to a large extent with Apache Maven, which also manages dependencies. However, Maven is a complete build tool, whereas Ivy focuses purely on managing dependencies.
Gradle5 builds upon Apache Ant and Apache Maven and but uses a Groovy-based domain-specific language (DSL) to declare the project configuration instead of XML.
Although you can use Ant and Maven to build your Scala projects, SBT6, is the standard build tool for Scala applications. Like Maven, SBT uses the same directory structure and convention over configuration approach and Apache Ivy for handling dependency management.
In essence, the build automation automates all the steps in the build process with build tools like Ant, Maven, Ivy, Gradle, or SBT and the best practice is to run the build continuously. This process is known as continuous integration. There are several tools that offer continuous integration such as Hudson7 and Jenkins8.
Getting Started with SBT
In this section you will see sbt in action using a simple hello world project. First you need to install sbt.
Installing SBT
The installation of sbt, in a nutshell, is akin to launching a JARsbt-launch.jar which can be downloaded from http://www.scala-sbt.org/0.13/tutorial/Manual-Installation.html, if you want to install sbt manually. Or you can install using sbtmsi, which you can download from http://www.scala-sbt.org/download.html.
SBT provides different installation packages for Mac, Windows, Linux, Typesafe9 Activator, or manual installation. You can find information on installation at http://www.scala-sbt.org/0.13.5/docs/Getting-Started/Setup.html#installing-sbt.
Typesafe Activator is a custom version of sbt that adds two extra commands, activator ui and activator new. The activator command is a superset of sbt, in short. You can obtain Activator from typesafe.com. Activator offers two downloads; the small “minimal” download contains only the wrapper script and launch jar, while the large “full” download contains a preloaded Ivy cache with jars for Scala, Akka10, and the Play11 Framework. You will use both sbt and activator in the next chapter when you build web application in Scala.
Once you have downloaded sbt you can check the version as shown:
Creating Hello World Project
In order to see sbt in action, set up a simple hello world project by creating a project directory helloworld with a hello.scala source file in it as shown in Listing 11-1.
Listing 11-1. hello.scala
object Helloworld {
def main(args: Array[String]) = println("Hello world!")
}
Now you can run sbt by typing sbt as shown
We will explain the [info] part of the preceding output a little later. When you run sbt in your project directory helloworld with no command-line arguments, sbt starts in an interactive mode. The interactive mode provides a command prompt with features such as tab completion and history.
Interactive mode remembers what you typed previously (history), even if you exit sbt and restart it. Sbt lists all the history commands when you type ! on sbt prompt as shown.
Sbt provides a long list of commands. For a more complete list, see Command Line Reference at http://www.scala-sbt.org/release/docs/Command-Line-Reference.html.
Here are some of the most common sbt commands.
Command |
Description |
---|---|
clean |
Deletes all generated files (in the target directory) |
compile |
Compiles the main sources (in src/main/scala and src/main/java directories) |
run |
Runs the main class for the project in the same virtual machine as sbt |
help <command> |
Displays help for the specific command when you type help<command>. Displays brief description of all the commands if you type just help. |
You can exit the interactive mode by typing exit.
Now you can run the project by typing run at the sbt prompt of the interactive mode as shown:
As you can see in the preceding output, sbt runs the helloworld project and displays Hello World!. We will now explain the [info] part of the output.
Sbt requires you to set the name of the project to helloworld and allows you generate a build definition. We will explain build definition in greater detail later in this chapter. Meanwhile, enter the following on sbt console:
set name:= "helloworld"
You will get the following output:
Now save the session by entering the following:
session save
Now you will see the following output:
Now enter exit and this generates build.sbt in the root directory, helloworld. Listing 11-2 shows the generated build.sbt.
Listing 11-2. generatedbuild.sbt
name:= "helloworld"
You can edit the build.sbt to add the basic information as follows:
Listing 11-3. generatedbuild.sbt
name := "helloworld"
version := "1.0"
scalaVersion := "2.11.1"
We will go through the code in the Listing 11-3 and related concepts in the next section.
Build Definition
As we mentioned earlier, a build tool requires you to define the project configuration and dependencies in an artifact called build definition.
In Scala, there are three types of build definition:
As you learned earlier, the base directory of your hello world project—helloworld directory—is comprised of the build definition.sbt file. In addition to .sbt file there may also be build definitions as a .scala file located in the project/ subdirectory of the base directory. A sbt build definition consists of a list of settings as shown in Listing 11-4.
Listing 11-4. .sbt Build Definition
name := "helloworld"
version := "1.0"
scalaVersion := "2.11.1"
In the previous version of sbt, you were required to separate the setting expression by blank lines. You could not write an .sbtfile as shown in Listing 11-5 as it did not compile because of absence of blank lines.
Listing 11-5. .sbt Without Blank Lines (will not compile)
name := "helloworld"
version := "1.0"
scalaVersion := "2.10.x"
This restriction does not exist any longer from sbt0.13.7. but we mentioned this because you may find yourself into situations where older versions of SBT are still being used with newer versions of scala, and in such cases the build file without the blank lines won’t compile.
You learned how to set up a hello world scala project in sbt, but in general, industry strength applications are far more complex than a hello world project. In order to use sbt with industry strength applications efficiently, it is important to understand how the build definition works. We insert the Listing 11-3 to explain the build definition as Listing 11-6.
Listing 11-6. Generated build.sbt (from Listing 11-3)
name := "helloworld"
version := "1.0"
scalaVersion := "2.11.1"
A build definition is made up of build properties, where each property is a key value pair. From Listing 11-6 one build property is shown as follows:
name := "helloworld"
name is the key and the String "helloworld" is its value type and := is an operator method for transformation. Assignment with := is the simplest of all the transformation that sbt provides. The other transformation methods will be introduced in the later section of this chapter.
In SBT, keys are defined for different purposes. A key can be categorized into one of the following categories:
Key |
Description |
---|---|
Setting key |
When you define the key as a Setting key, the value of the key is computed on loading the project |
Task key |
When you define the key as a Task key, the value of the key is recomputed each time it is executed |
Input key |
When you define the key as an Input key, the value of the key take command-line arguments as input |
The Setting keys provide build configuration. The keys such as name, version, and scalaVersion that you saw in Listing 11-3 are Setting keys. We will look at two other useful types of setting keys: libraryDependencies and resolvers keys in the following section.
The Task keys, as the name suggests, are geared toward tasks such as clean, compile, test, and so on.
Note Because a Task key is computed on each execution, a Setting key cannot depend on a Task key. Trying to do so will throw an error.
The Input keys are the keys that have command-line arguments. An example of an input key is run key. The run key is used to run a main class with the command-line arguments. If no arguments are provided, a blank string is used. You execute run without any arguments when you run the hello world project.
Note Each key can have more than one value, but in different context called scopes. In a given scope, a key has only one value.
In the following section you will learn about the Setting keys called libraryDependencies key and resolvers key.
LibraryDependencies and Resolvers
The libraryDependencies key is used to declare managed dependencies and the resolvers key is used to provide additional resource URIs for automatically managed dependencies. As we mentioned earlier, SBT uses Apache Ivy to implement managed dependencies. You should list your dependencies in the setting libraryDependencies. You can declare the dependency as shown in Listing 11-7.
Listing 11-7. Declaring Dependencies
libraryDependencies += groupID % artifactID % revision
In Listing 11-7, groupId, artifactId, and revision are strings.
You can also declare the dependencies as shown in Listing 11-8.
Listing 11-8. Declaring Dependencies With Configuration Value
libraryDependencies += groupID % artifactID % revision % configuration
In Listing 11-8, configuration can be a string or val.
In Listing 11-7 and Listing 11-8, you add a ModuleID created by % method to libraryDependencies. An astute reader might have noticed that we used a different operator method for transformation in Listing 11-7 and Listing 11-8 than the assignment operator method you learned in Listing 11-6.
The operator += appends to the existing value. You can also use another operator ++=, which appends the sequence of values to the existing value as shown in Listing 11-9.
Listing 11-9. Using ++=
libraryDependencies ++= Seq(
groupID % artifactID % revision,
groupID % otherID % otherRevision
)
Listing 11-9 uses++= to add a list of dependencies at the same time.
Listing 11-10 shows using the Apache Derby as the library dependency.
Listing 11-10. Using Apache Derby Dependency
libraryDependencies += "org.apache.derby" % "derby" % "10.4.1.3"
In Listing 11-10, SBT uses the default repository for downloading Derby. If you want to use the library dependency that is not in one of the default repositories, you need to add a resolver to help Ivy locate it. In order to provide the location of repository you can use the following syntax:
resolvers += name at location
Here name is the String name of the repository and location is the String location of the repository.
Listing 11-11 shows how to add the additional repository.
Listing 11-11. Using Resolvers
resolvers += "Sonatype OSS Snapshots" at https://oss.sonatype.org/content/repositories/snapshots
In Listing 11-11 we add the sonatypeoss snapshots repository, which is located at the given URL.
Plugins
A plugin is an artifact that extends the build definition, usually by adding new settings. In order to declare this plugin dependency you need to pass the plugin’s Ivy module ID to addSbtPlugin as shown in Listing 11-12. Next we show you how to use an eclipse plugin using the hello world project we created earlier. You can obtain the sbt eclipse plugin's Ivy module ID from https://github.com/typesafehub/sbteclipse.
First create an .sbt file for the plugin in the helloworld project directory as shown in Listing 11-12.
Listing 11-12. plugin.sbt
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "3.0.0")
Now you can run the eclipse command on the sbt prompt as shown:
This creates the project directory structure for eclipse that adheres to convention over configuration and is same as the Maven directory structure.
Now open Eclipse and navigate File Import as illustrated in the Figure 11-2.
Figure 11-2. Importing the project
Once you click Import, you will see the select form as illustrated in Figure 11-3.
Figure 11-3. Selecting existing projects into workspace
Now navigate to “Existing Projects into Workspace.” You will now see the Import dialog as illustrated in Figure 11-4. Click Browse to get to the base directory helloworld as illustrated in Figure 11-4.
Figure 11-4. Selecting the root directory
Now click Finish to see the project structure shown in Figure 11-5.
Figure 11-5. Project directory structure
SBT also allows you to generate IDEA project configuration. To do this you use an sbt plugin located at https://github.com/mpeltonen/sbt-idea. To generate NetBeans configuration you can find the sbt plugin for NetBeans at https://github.com/dcaoyuan/nbsbt.
One chapter is not enough to list all the features of SBT and it deserves a book of its own. For a detailed treatment on SBT we recommend you to go through the reference manual of SBT at http://www.scala-sbt.org/release/docs/.
This chapter did provide you a brief introduction to SBT, which is sufficient to use it in the next chapter for building a web application.
Summary
In this chapter you learned how to create a simple project with SBT and execute basic operations, such as compile, test, and run. You learned how to configure library dependencies and how to configure a dependency when it is not in a default repository. In the next chapter you will learn how to create web applications in Scala.
_____________________