Blog

SBT Multi Project

When working on a project, you often discover that you need some functionality that would be useful in other projects, but using or creating a separate library is not desirable in separate workspace. Using an existing library is in many cases impossible since there may not be a library that meets your needs. Creating a completely separate library adds some overhead to the project. You have to create a new version control repository and you have to publish the binaries to an artifact repository.

An alternative, that still ensures that part of the code won’t have dependencies on the main part, is to use a multi project setup in SBT. Preventing these dependencies will result in more maintainable code in the long run. In SBT 0.12 this is very easy to set up.

The first thing you need to do is convert the build.sbt to Build.scala if you are using a .sbt build definition for your build configuration. Since the syntax is very similar, this probably won’t be a problem. More information about this subject is in the getting started guide of SBT.

When that is done, it is time to create a new project within your existing project. Using IntelliJ this is done by right clicking on the root folder of the project and choosing New→Module. Inside that module you have to obey the default SBT directory structure. Therefore the sources for your newly added project have to be placed in src/main/scala and the unit tests have to be placed in src/test/scala.
A very simple multi project structure could look like this:

import sbt._
import Keys._

object Build extends Build {
  lazy val root = Project("root", file("."))
    .dependsOn(util)

  lazy val util = Project("util", file("util"))
}

Each Project definition is read by SBT using reflection. The first argument specifies the name of the project. The second argument specifies the path to the project. This is relative to the root of the main project. Therefore the root project has the path “.” with the sources for that in and the util project has the path “util” with its sources in util/src/main/scala.

You have to specify the dependencies between your projects yourself. In this example the root project is dependent on the util project. This is done by adding a dependsOn to the project definition. When you don’t specify this dependency, it isn’t possible to make use of classes inside the util project from the root project. To automatically run tasks on another project when the a project is invoked you have to specify aggregate. For example, when running the unit tests for the root project, the unit tests for the util project aren’t automatically started. When the util project is specified as aggregate for the root project, then the unit tests are also run for the util project when the unit tests for the root project are started.

Because the project definition is written in Scala, you can make use of vals to define information once that is shared between the projects. An example of what such a project could look like is:

import sbt._
import Keys._

object Build extends Build {

  lazy val defaultSettings =
    Defaults.defaultSettings ++
      Seq(
        version := "1.0",
        scalaVersion := "2.10.0-RC5",
        scalacOptions := Seq(
          "-feature",
          "-language:implicitConversions",
          "-language:postfixOps",
          "-unchecked",
          "-deprecation",
          "-encoding", "utf8",
          "-Ywarn-adapted-args"
        )
      )

  lazy val root = Project("root",
    file("."),
    settings = defaultSettings ++ Seq(
      resolvers ++= Seq(
        "spray repo" at "http://repo.spray.io"
      ),
      libraryDependencies ++= Seq(
        "io.spray" % "spray-can" % "1.1-M4.2",
        "io.spray" % "spray-routing" % "1.1-M4.2",
        "io.spray" % "spray-testkit" % "1.1-M4.2"
      )
    )
  )
    .aggregate(util)
    .dependsOn(util)

  lazy val util = Project("util",
    file("util"),
    settings = defaultSettings
  )
}

More information about this subject can be found in the SBT Documentation, specifically on the page about Multi-Project Builds. A good example of a project that makes use of a multi project setup is Spray.

Spread the love