Chapter 4
Simple Build Tool

The Simple Build Tool, or simply SBT, is a robust tool that uses the Scala language with an easy interface to create simple or complex behaviors within your project. While using SBT you can create a project with little to no configuration, and you can package/publish JARs and be presented with a very powerful shell that can be used to test your code from the outside.

With so many features, it's not hard to see why SBT is known as the de facto build tool for compiling, testing, and packaging Scala services and applications. Therefore, learning how to use and extend SBT for your own projects results in a very maintainable and extensible project.

To give a comparison, most developers have experiences in using build tools such as npm, gradle, and bundler. SBT allows you to pull in different dependencies, set up custom tasks, interact with external repositories, and even create your own testing frameworks through a slick DSL. All that's required is getting a basic understanding of the concepts to which SBT subscribes. Then build toward an understanding of how these techniques can support very small to very large scale Scala code bases. This is of course the purpose of this chapter.

While going over these techniques, keep in mind that the tool presented here is vast, and that configurations can become extremely customized toward your own goals. In this chapter we'll focus on the maintainable benefits of a highly structured SBT environment, and provide examples that support good SBT usage.

First, make sure that you have downloaded and installed SBT from the main website, currently http://www.scala-sbt.org/download.html, and follow the instructions for your particular platform. At the time of writing the current version number is SBT 13.9, but classically SBT has been very good about supporting backward comparability—so if you are on a later version, you should be able to follow along.

SBT Configs

SBT has a lot of flexibility around configuring where you want to keep your global settings and plugins for your projects. By default this content is stored in the following locations:

~/.sbtconfig: 

This file is deprecated, since it was used for global settings that should be moved into /usr/local/etc/sbtopts.

~/.sbt/0.13/plugins/: 

Here you can install a plugin for use in all projects.

~/.sbt/0.13/global.sbt:

Here you define global settings for plugins.

/usr/local/etc/sbtopts: 

You'll need to modify to adjust global settings applied to SBT on startup, for example, to change your debug port or max memory.

BASIC USAGE

SBT doesn't require a build definition file for simple programs, nor does it require that the file be placed in a standard directory structure. So, for quick scripting it can be easy to touch a file, add the main method, then start coding away.

For example, let's create a Scala file that does a simple factorial computation, since that's the rave in interviews on Scala:

1 object Factorial {
2   def main(args: Array[String]): Unit = {
3     println(factorial(5))
4   }
5
6   def factorial(n: Int): Int = {
7    if(n == 0){ 1 }
8    else{ n * factorial(n-1) }
9   }
10 } 
  • It's important to pay attention to the main signature, since if you return anything but a unit, SBT will be unable to find the main method.
  • By default SBT will compile and build the project with the same version of Scala that SBT was run with.

The next step is to run the SBT command in the same folder where the file exists and to use the run command. You should see a result of 120. This is just a simple example of running a Scala program through SBT, and more advanced usage of the SBT console will be provided as we go along.

If you decide to create multiple Scala files with main methods in that same folder and use the run command, SBT will automatically give you a choice of which file to run. This can be great when you have a test for your application and want to have a few choices on which example app to run.

Please take a look at https://github.com/milessabin/shapeless. Switch to the examples project and then execute the run command. It gives you a good breakdown of all the code examples that correlate to the features of the project. This same pattern is used in the code base used for this book, to create more interesting coding samples.

Project Structure

While the setup of a simple code snippet using SBT doesn't require any special ceremony other than having SBT run in the same directory as a Scala file with a main method, you'll quickly find that having some order to your upcoming project will make a good deal of sense. As such, the folder layout for any basic single Scala project should resemble the following:

src/
  main/
    resources/
       <files to include in main jar here>
    scala/
       <main Scala sources>
    java/
       <main Java sources>
  test/
    resources
       <files to include in test jar here>
    scala/
       <test Scala sources>
    java/
       <test Java sources>

In the event you need to add more directories to this model, such as the main or test folders, SBT will by default or ignore those custom directories.

Single Project

When first starting out with using SBT it can be helpful to implement the setup of a bare bones build file, and work toward a more complex example. SBT 13.9 has some nice improvements on another earlier version (mostly in a more opinionated multi-project setup). Using the previous example, you can create a build.sbt file in the same directory as the Factorial.scala file.

      1 lazy val root =
      2   (project in file("."))
      3     .settings(
      4       name := "Factorial",
      5       version := "1.0.0",
      6       scalaVersion := "2.11.7"
      7     ) 

You can now start up SBT again in interactive mode using the run command, which will still result in a 120. So, what changed? During the startup of SBT, an immutable map describing the build persisted within SBT that was fed from the build definition that was just provided.

For example, within the SBT prompt you can type out any of the values that were just provided:

       > name
       [info] Factorial
       > version
       [info] 1.0.0
       > scalaVersion
       [info] 2.11.7 

What has occurred is that a build file defines a sequence of Setting[T], in this case Setting[String], as name, version, etc. This feeds into SBT's immutable map of settings for each key/value on startup. You can also use those settings to expand out the build.sbt file programmatically and break up the settings easily into their own variable by abstracting the file out a bit further:

   1 lazy val buildSettings = Seq(
   2   organization := "com.professionalscala",
   3   version := "1.0.0",
   4   scalaVersion := "2.11.7"
   5 )
   6
   7 lazy val root =
   8   (project in file("."))
   9     .settings(buildSettings)
   10     .settings(
   11       name := "Factorial",
   12       description := s"${name.value} using ${scalaVersion.value}"
   13     ) 
  • You may notice that the code provided is using lazy val, which is purely to avoid order problems during the SBT startup process. You can use val, lazy val, or defs as well.

By doing this breakup of buildSettings in another variable, the technique allows reuse across other project definitions. Not only that, but since SettingKey's are computed, once on startup you can setup some interesting behaviors. If, for example, you create a build on Jenkins, you can have Jenkins increment an environment variable for a minor build version of the project. This is useful after deploying out to your cluster.

            3   version := {
            4     val minorV =
                        Option(System.getenv.get("currentMinorRelease"))
                                .getOrElse("0").toInt
            5     s"1.0.${minorV}"
            6   }, 

The above would result in the version being 1.0.0 unless Jenkins incremented the environment, in which case the output would be “1.0.1.” It's useful to remember that settingsKeys can be modified for any number of unique situations within your project. So far we have only used Setting Keys in the project definition, but it's important to note that there are three kinds of keys that can be used:

  • SettingKey[T]: A key for a value computed once (the value is computed when loading the project, and kept around)
  • TaskKey[T]: A key for a value, called a task, that has to be recomputed each time, potentially with side effects
  • InputKey[T]: A key for a task that has command line arguments as input

Scopes

When working with SBT, one of the concepts to understand is “scope.” Each key type can have an associated value in more than one context, which is described as a “scope.”

A scope can be overwritten in each sub-project through the use of the multi-project build.sbt files found in those multi-project directories. This enables different build settings, different packaging, and can allow for different values to be held within.

Scope axis is the phrase SBT uses to distinguish how particular settings or behaviors work together. The Scope axes are:

  • Projects
  • Configurations
  • Tasks

In the most basic terms, scope allows different values to apply to various events within SBT. For example, if you want to disable scaladoc generation in a play application, you can add the following: http://stackoverflow.com/a/21491331.

     // Disable scaladoc generation
      publishArtifact in (Compile, packageDoc) := false,
      publishArtifact in packageDoc := false,
      sources in (Compile,doc) := Seq.empty, 

The first line above shows that the key publishArtifact, when doing a compile or packagingDoc task, is instructed to NOT generate the projects documentation.

You typically will only need to muck with a scopes values if the plugin, custom code, or framework is doing something undesired, while you are in the life-cycle of development. Normally, you won't need to touch these values.

Custom Tasks

Custom tasks or TaskKey's can be useful in developing out build automation, and later creating custom plugins for SBT. Without going too heavily into creating a plugin for SBT, let's go over a very basic example inside the SBT interactive shell.

> set TaskKey[java.util.UUID]("newId") := { java.util.UUID.randomUUID } 

Execute that task by using the show command:

> show newId
[info] b43726fa-0c73-4e6c-ba7d-6c386f03a969
> show newId
[info] 91256835-8d62-446e-ae61-690304472c50 

Now you have an easy generator for new uuid's if you don't already have uuidgen available on your local environment.

Say, for example that you need a task that requires some information from the box it was currently running on, and that information couldn't be obtained through other Java libraries. You can build a custom task to accomplish this as well:

> set TaskKey[String]("getUname", "get the uname") := { Process("uname -a")!! }
> show getUname
[info] Linux bucket 3.13.0-75-generic …

Moving those custom tasks into your build only involves a slight change to the syntax within the build.sbt file.

val getUname = TaskKey[String]("getUname", "gets the local machines uname")
lazy val root =
  (project in file("."))
    .settings(buildSettings)
     .settings(
       name := "Factorial",
       getUname := {
        Process("uname -a")!!
       },
       description := "a simple factorial"
     )

After reloading SBT, you can then use show genUname to see the same result as the previous console execution results.

Dependencies

When working with the build file, you want to bring in third-party dependencies. This task can be accomplished with little effort by adding the JAR directly to the lib/ directory (in the base folder) as unmanaged dependencies, or by adding the dependency directly to the build.sbt file as a managed dependency.

      val http4sVersion = "0.11.2"
      lazy val commonSettings = Seq(
        resolvers ++= Seq(
          Resolver.sonatypeRepo("releases"),
          Resolver.sonatypeRepo("snapshots")
          ),
        libraryDependencies ++= Seq(
          "org.http4s"   %% "http4s-blaze-server" % http4sVersion,
          "org.http4s"   %% "http4s-dsl"          % http4sVersion,
          "org.http4s"   %% "http4s-circe"        % http4sVersion
        )
      ) 

Note the libraryDependencies section: since each of those entries directly maps to a formula similar to patterns, you may see a maven xml file.

groupId %% artifactId % version % scope

This pattern is used to look up the POM and JAR files stored on either the defined or default ivy2/maven repositories. Using the repo1.maven.org repository as an example, you can follow each stage of the formula being applied and then see how those endpoints line up when they are being pulled:

The double percentage sign is used to specify a lookup for a binary compatible version of the http4s-blaze-server, and was also used in the ArtifactId to silently append 2.11 onto that string. You can also specify a single percentage sign, which would be an exact string match, rather than depending on SBT to append the version of Scala to the lookup.

     "org.http4s" % "http4s-blaze-server_2.11" % http4sVersion

The above is an equivalent to using the double percentage signs. It's also worth noting that you may find some libraries using triple percentage signs, which is indicative of a scalajs dependency. More about that can be found in the Webjars section of the Scalajs chapter later in this book.

Resolvers

In the same way the above examples exposed third party dependencies, it can be necessary to specify third party repositories. This can be helpful if you are working with a third party library and need snapshots rather than stable releases. SBT comes with a few predefined repositories that can be found at http://www.scala-sbt.org/0.13/docs/Resolvers.html—but in most cases simply following the above example will be enough to clear up any missing dependencies.

In the event you are unable to find the repository that has the dependencies, you are attempting to add to your local ivy cache. You can do a manual search for them using the maven central repository (http://search.maven.org/) and looking up the dependency by its GroupId or ArtifactId. Once you've found the correct dependency, the information page will provide you with resolver and matching libraryDependency code for including that dependency in your build.sbt file.

ADVANCED USAGE

Often when building services in Scala, the problem of needing a multi services or a shared library of common code between said services will arise. As an option you can make this shared code into a completely separate project, and then include that as a dependency within the other sub projects. However, when a core library is being actively developed, that technique can create confusion and result in breakage within your code base.

As an alternative, SBT provides the means to set up a multi project, which gives you the flexibility to create a shared library. This can depend on the dependencies apart from the other sub projects and enables other core programmers to expand the shared library with more confidence that those changes won't break other sub projects depending on that core.

Unlike the single project setup, folders in the base directory are going to take on another form, where you can move the src folder under a common name for the service that it reflects. Such a folder structure may look similar to the following:

common/
  src/
    main/
    test/
services-a/
  src/
    main/
    test/
    resources/
services-b/
  src/
    main/
    test/
    resources/

Figuring out the locations of the build.sbt files is a crux, since nothing stops you from having all of your build.sbt files stay in the root folder. We advise against this, however, and place build.sbt files into each of those sub-projects. During your compiling/building of the root project, all of the subproject SBT files will merge together with the build definition for the entire project.

This is especially helpful if you prefer to run things under one large umbrella application, or run projects from their sub project view in the interactive console. It is helpful to keep build.sbt files in each sub-project, since when you attempt to package your application you can be specific about the dependencies and relationships of those sub projects—keeping them isolated for deployments.

After moving the common code into that sub directory you'll need to make a slight change to the root build.sbt file to specify the other sub-projects as projects.

lazy val buildSettings = Seq(
  organization := "com.example",
  version := "0.1.0",
  scalaVersion := "2.11.7"
)

lazy val core = (project in file("core"))
  .settings(buildSettings)
  .settings(
    // other settings
  )

lazy val service = (project in file("services-a"))
  .settings(buildSettings)
  .settings(
    // other settings
  )

lazy val services-b = (project in file("services-b"))
  .settings(buildSettings)
  .settings(
    // other settings
  ) 

Notice that the build settings are included in each of the services now defined. This will ensure that the basic information of organization and Scala version are defined for all projects, meaning they will be easier to maintain.

Modifications made in the sub-projects build.sbt files will be reflected in the SBT console as well. So, now you need to learn about how to depend on different sub-projects for using the core library in other sub-projects, which can be accomplished by using the dependOn method call:

lazy val services-a = (project in file("services-a"))
.dependsOn(core)
.settings(buildSettings)
  settings(
    // other settings
  ) 

Code in the core library will be accessible by services-a. This will allow you to run a task on the top level project that will force the other aggregated projects to also run the same task. You can add the aggregate method onto the service project in the same way as the dependsOn method.

Advanced Dependencies

A quick note on keeping things sane while doing large multi project builds: It's possible to have each build.sbt in each sub-project define its individual dependencies and also use the core projects dependencies as a dependedOn project that brings in the remainder. In doing this, however, duplication will arise between all of these files. In that case, creating a dependencies Scala file within the project folder project/Dependencies.scala can create a one stop shop when you want to add and update dependencies.

//Dependencies.scala
import sbt._

object Dependencies {
  // Versions
  lazy val akkaVersion = "2.3.8"
  lazy val http4sVersion = "0.11.2"

  // Libraries
  val akkaActor   = "com.typesafe.akka" %% "akka-actor" % akkaVersion
  val akkaCluster = "com.typesafe.akka" %% "akka-cluster" % akkaVersion
  val specs2core  = "org.specs2"  %% "specs2-core" % "2.4.14"
  val blazeServer = "org.http4s"  %% "http4s-blaze-server" % http4sVersion
  val http4sDSL   = "org.http4s"  %% "http4s-dsl" % http4sVersion
  val http4sCirce = "org.http4s"  %% "http4s-circe" % http4sVersion
  val psql        = "postgresql"  %  "postgresql"  % "9.1-901.jdbc4"
  val quill       = "io.getquill" %% "quill-async" % "0.2.1"

  // Projects
  val http4s = Seq(
    blazeServer, http4sDSL, http4sCirce
  )

  val db = Seq(
    psql, quill
  )
  val backendDeps =
    Seq(akkaActor, specs2core % Test) ++ http4s ++ db
}

To use them within your sub-projects, you only need to specify them under that group's individual settings.

//root build.sbt
import Dependencies._

scalaVersion in Global := "2.11.7"

lazy val buildSettings = Seq(
  organization := "com.example",
  description := "the root description.",
  version := "1.0.0",
  scalaVersion := "2.11.7"
)

resolvers ++= Seq(
  Resolver.sonatypeRepo("releases"),
  Resolver.sonatypeRepo("snapshots")
)

lazy val core =
  (project in file("core"))
    .settings(buildSettings)

lazy val servicesa =
  (project in file("services-a"))
    .settings(buildSettings)
    .dependsOn(core)
    .aggregate(core)

lazy val servicesb =
  (project in file("services-b"))
    .settings(buildSettings)
    .settings(libraryDependencies ++= backendDeps)
    .dependsOn(core)
    .aggregate(core)

Using this technique ensures that only the dependencies that you want for a sub-project are included per project.

Testing in the Console

By placing tests in the corresponding test directories in either a single or multi-project, you can use SBT to run through those tests with ease. First, you need to get a testing library and add it as a managed dependency. For this example you're going to use specs2, since it comes standard in a few popular Scala frameworks, and also has many similarities to scalatest. Start by adding the dependency to the Dependencies.scala file under the root project folder.

libraryDependencies ++= Seq("org.specs2" %% "specs2-core" % "3.7" % "test")

With that dependency defined, you'll need to either reload or restart SBT so that the dependency can be loaded up. Now, assuming you have tests in the standard paths of your project, you can begin by using test, test-only, and test-quck.

The test task takes no arguments, but once executed it will traverse all projects and find any tests that are in the correct locations, and then it runs all tests. The test-only task is useful when you have a particular test you want to run.

test-only com.example.FactorialSpec

The test-quick task allows similar behavior to test-only, but only under the following circumstances:

  • The tests that failed on the previous run
  • The tests that were not run before
  • The tests that have one or more transitive dependencies, maybe in a different project, recompiled

Remember, you can always prepend the test task execution with a tilda sig, which will re-test the specified tests after a change in your codebase.

Another nice fix that can enable a cleaner style within the testing side of your codebase is to filter on tests that adhere to a filename convention and only run those tests. For example, add the following to your build SBT:

testOptions in Test := Seq(Tests.Filter(s => s.endsWith("Spec")))

Generating Documentation

If you have been documenting your code using valid scaladoc syntax, you can use the SBT doc command to start generating Scala documentation in the target folder of your application. Running this from the root project will also pull in the sub-project documentation as well. There are a few settings that can be helpful before generating the Scala documentation, which is discussed at length in Chapter 8.

RELEASE MANAGEMENT

Before packaging your application, you'll need to decide if you are going to use the project as a dependency in another project, or if you are creating an application that will be stand alone. Assuming the former, you can use the command SBT package, which will create a simple JAR that contains the main artifact of your package. Then add that new JAR file to the lib folder of your other project as an unmanaged dependency.

That approach is the best, since you're going to end up having to manually migrate that JAR file every time you want to create updates. A better solution is to use the command sbt publish-local. That will store the new JAR inside your ivy2 cache (if you want to publish to your local maven repo, you can use the command publish-m2) and can then be referenced by your other project, by adding the dependency entry into your SBT or dependencies Scala file.

If you want to build an executable JAR with all of the included dependencies of your project, you'll need to use the sbt-assembly plugin and then invoke the command sbt assembly. This is considered a fat/uber JAR, because it's an all in one solution for deployment. You can then run it by using the command :

java -jar your.jar com.example.MainMethod

Your new Scala project should now be running properly. Thanks JVM.

Deploying to Sonatype

The Sonatype Nexus is a repository manager. It gives you a place to store all of the packaged JARs you create, and gives you a single place to then share those JARs with other developers. You can also set up your own local sonatype repo for your company by following the steps here: https://books.sonatype.com/nexus-book/reference/install.html, but the following documentation is for deploying to the central sonatype nexus.

If you have already read over the documentation and terms of service provided at http://central.sonatype.org/pages/ossrh-guide.html, and you have received email notice from sonatype about provisioning your repo, you can start setting up SBT to deploy to your repo.

First, you need to PGP sign your artifacts for the sonatype repository, using the sbt-pgp plugin. To do this, add the following to your ˜/.sbt/0.13/plugins/gpg.sbt file:

addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0")

This document assumes that you have already created a PGP key, and that you have sent the key to the keyserver pool.

Next, to publish to the maven repo, you'll need to add the settings:

publishMavenStyle := true

Add this to either the sub project or the root project. Then you'll need to add the repositories for pushing to sonatype:

publishTo := {
  val nexus = "https://oss.sonatype.org/"
  if (isSnapshot.value)
    Some("snapshots" at nexus + "content/repositories/snapshots")
  else
    Some("releases"  at nexus + "service/local/staging/deploy/maven2")
}

The next step is getting the POM metadata that isn't generated by SBT into the build. This can be accomplished by using pomExtra:

pomExtra := (
  <url>http://sample.org</url>
  <licenses>
    <license>
      <name>MIT-style</name>
      <url>http://opensource.org/licenses/MIT</url>
      <distribution>repo</distribution>
    </license>
  </licenses>
  <scm>
    <url>[email protected]:sample/sample.git</url>
    <connection>scm:git:[email protected]:sample/sample.git</connection>
  </scm>
  <developers>
    <developer>
      <id>sample</id>
      <name>John Doe</name>
      <url>http://github.com/sample</url>
    </developer>
  </developers>)

Finally, you need to add your credentials. This is normally handled from within the ˜/.sbt/0.13/sonatype.sbt.

credentials += Credentials("Sonatype Nexus Repository Manager", "oss.sonatype.org",
  "<your username>", "<your password>") 

Then, in SBT, you can run the publishSigned task that will package and deploy your application right from the interactive console. Assuming all goes well, you have just deployed your first artifact to Nexus.

Packaging with SBT-Native-Packager

The sbt-native-packager plugin can be useful, if not essential, in the packaging of your shiny new micro service, allowing all you need to package and deploy your services or applications to almost any environment. As of writing this you can deploy out to:

  • Universal zip,tar.gz, xz archives
  • deb and rpm packages for Debian/RHEL based systems
  • dmg for OSX
  • msi for Windows
  • Docker images

Choosing which environment to deploy to will depend on your business needs. That stated, this chapter focuses on deployment to Debian packages and Docker instances, since both are fairly standard.

Creating a Debian Package

First, append to your project/plugins.sbt file the following:

addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "x.y.z")

Then, from within your root build.sbt file, append:

enablePlugins(JavaServerAppPackaging)

It's worth mentioning that if you are currently using the Play framework, sbt-native-packager is already installed, but it will require that you add the following to create Debian packages:

enablePlugins(DebianPlugin)

Also, you will need to have installed the following system packages to provide native package creation:

  • dpkg-deb
  • dpkg-sig
  • dpkg-genchanges
  • lintian
  • fakeroot

Once completed, you'll also have to make sure that the build.sbt for the sub project contains at the minimum the following lines:

name := "Services-A"
version := "1.0"
maintainer := "John Doe <[email protected]>"
packageSummary := "The greatest test service ever"
packageDescription := "Lorem ipsum of ipsum Lorem" 

After that final step you only need to restart SBT and give packaging your application a try. In the SBT console navigate to one of the sub project created earlier, such as project services-a, and use the command debian:packageBin. This will by default create the native Debian package, which can be installed using dpkg, located in the target directory of the subproject.

The above steps are great for getting a native build out, but in terms of customization, there are a few steps you can take to make the build a bit more informative. You can specify dependencies that your build will require by adding the following to your build.sbt:

debianPackageDependencies in Debian ++= Seq("java8-runtime", "bash (>= 2.05a-11)")

You can also recommend packages by adding the following:

debianPackageRecommends in Debian += "cowsay"

Finally you can hook into the debian package life-cycle by adding the preinst, postinst, prerm, or postrm hooks into your build SBT file:

import DebianConstants._
maintainerScripts in Debian := maintainerScriptsAppend(
 (maintainerScripts in Debian).value)(
  Preinst -> "echo 'Thanks for installing Service-A'",
  Postinst -> s"echo 'installed ${(packageName in Debian).value}'"
)

Or you can add those hooks into your project's file structure:

src/debian/DEBIAN

You may also need to add specific extra definitions to your build. This can be accomplished by modifying your build.sbt and appending bashScriptExtraDefines:

bashScriptExtraDefines += """addJava "-Djava.net.preferIPv4Stack=true""""

Now that you've created a Debian package, you can follow similar instructions and create a Docker image.

Creating a Docker Image

Docker, as described in the documentation, “is an open platform for developing, shipping and running applications” through containers, which are a sandbox for images. To get a more general introduction to Docker you can visit the documentation: https://docs.docker.com/engine/understanding-docker/ or visit https://www.youtube.com/watch?v=aLipr7tTuA4, which has a really easy introduction to the technology.

Similar to the setup for the debian packager, you'll need to modify the build.sbt for the sub-project to include:

enablePlugins(DockerPlugin)

Once completed, you'll need to set some default configurations in build.sbt:

     packageName in Docker := "service-b"
     version in Docker := "latest"

There are some other customizations that you can use, such as specifying the base image, the Docker repository, or the specific image customizations. You can view those settings at http://www.scala-sbt.org/sbt-native-packager/formats/docker.html, but here is a basic setup:

  .settings(
    packageName in Docker := "service-b",
    version in Docker := "latest",
      NativePackagerKeys.dockerBaseImage := "dockerfile/java:oracle-java8",
      NativePackagerKeys.dockerExposedPorts := Seq(9000, 9443),
      NativePackagerKeys.dockerExposedVolumes := Seq("/opt/docker/logs"),
    ) 

One issue that's common, especially when working with multi project setups and creating Docker containers, is that you may end up wanting to create a Docker image for the root project. By default the creation of the Docker image for the root project will aggregate and generate Docker images for all of the sub projects.

One way to get around this is to disable the aggregation in Docker from within the root project definition:

lazy val root = (project in file("."))
  .enablePlugins(DockerPlugin)
  //…(any other plugins or settings)
  .settings( //see above
    packageName in Docker := "root-project",
    version in Docker := "latest",
      NativePackagerKeys.dockerBaseImage := "dockerfile/java:oracle-java8",
      NativePackagerKeys.dockerExposedPorts := Seq(4000, 4443),
      NativePackagerKeys.dockerExposedVolumes := Seq("/opt/docker/logs"),
    )
    .dependsOn(servicesa).aggregate(servicesa)
    .dependsOn(servicesb).aggregate(servicesb)
    .dependsOn(core).aggregate(core)
    .settings( // the below will disable the subproject docker generation
      aggregate in Docker := false
    )    

Publishing the Docker image can be broken down in one of two ways. docker:publishLocal will build the image using the local Docker server or docker:publish, which will do the same as publishLocal, and will then push the image to the configured remote repository.

Common SBT Commands

When you first start SBT, you can run standard tasks that come built into SBT. It's worth noting that you can use the ˜ symbol to watch for changes within your code base.

Common Commands

  • clean—Deletes files produced by the build
  • compile—Compiles sources
  • console—Starts the Scala interpreter
  • run—Runs a main class, along with any command line arguments
  • test—Executes all tests
  • testOnly—Executes the tests provided as arguments
  • testQuick—Executes the tests that failed before
  • update—Resolves and optionally retrieves dependencies, producing a report
  • Reload—Reloads the project in the current directory

REPL Commands

  • consoleProject—Starts the Scala interpreter with the sbt build definition on the classpath and useful imports
  • consoleQuick—Starts the Scala interpreter with the project dependencies on the classpath

Package Commands

  • package—Produces the main artifact, such as a binary JAR, which is typically an alias for the task that actually does the packaging
  • packageBin—Produces a main artifact, such as a binary JAR
  • packageDoc—Produces a documentation artifact, such as a JAR containing API documentation
  • packageSrc—Produces a source artifact, such as a JAR containing sources and resources

Documentation-Related Commands

  • doc—Generates API documentation

Publish Commands

  • publish—Publishes artifacts to a repository
  • publishLocal—Publishes artifacts to the local Ivy repository
  • publishM2—Publishes artifacts to the local Maven repository

Useful Plugins

  • sbt-dependency-graph https://github.com/jrudolph/sbt-dependency-graph::
    1. Creates a nice graph to visualize a project's dependencies. Really interesting for seeing where all of the cyclic references are in a project and getting an idea of why a project is taking a long time to compile.
  • sbt-revolver https://github.com/spray/sbt-revolver:
    1. When invoked, it creates a fork of the application in the background, which is great for fast background starting/stopping of applications and re-triggered starts. This is usually the first plugin added in this chapter to a new project, since it helps development move along quickly and doesn't require an alternative like dcevm. It's worth noting that Jrebel is no longer supported by sbt-revolver.
  • tut https://github.com/tpolecat/tut:
    1. A documentation tool for Scala that reads markdown files and results in being able to write documentation that is typechecked and run as part of your build. The plugin really shines when creating tutorials that need to be typechecked by default.
  • sbt-updates https://github.com/rtimush/sbt-updates:
    1. Used to find updates for all of your project dependencies. This is great for Monday morning, when you want to check out all of the updates that are now available.
  • sbt-git https://github.com/sbt/sbt-git:
    1. Installing this plugin means never having to leave the interactive console to check in a file. More importantly, having git at your command from within SBT can allow you to write custom tasks from within SBT that interact with git.
  • sbt-musical https://github.com/tototoshi/sbt-musical:
    1. One of the just-for-fun plugins, and assuming you are on a Mac with iTunes open, you can use this plugin to play music whenever you want for any task. Simply add the prefix before any command.
  • ammonite-repl http://lihaoyi.github.io/Ammonite/:
    1. Another favorite plugin, this is truly a great way to experience the console in SBT or in standalone mode. Ammonite boasts the ability to dynamically load ivy dependencies/artifacts, multiline edits, pretty printed output, and much more. You should place this in your global.sbt file:
      ~/.sbt/0.13/plugins/build.sbt 
  • sbt-release https://github.com/sbt/sbt-release:
    1. This is a customizable release process management. It is an all-in-one solution for setting up steps to distribute or manage releases. It can include release notes and check tests and it can do a number of remarkable things, all in an easy to digest DSL.

SUMMARY

You should now have a good understanding of how to use the Simple Build Tool with the Scala language to simplify complex behaviors within your project. SBT helps you package/publish JARs and also provides a powerful shell that can be used to test your code. You can see why SBT is the de facto build tool for compiling, testing, and packaging Scala services and applications. Take advantage of this popular tool and you will better enjoy your Scala experience.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.186.79