Multi-project builds 

This page introduces multiple subprojects in a single build.

Please read the earlier pages in the Getting Started Guide first, in particular you need to understand build.sbt before reading this page.

Multiple subprojects 

It can be useful to keep multiple related subprojects in a single build, especially if they depend on one another and you tend to modify them together.

Each subproject in a build has its own source directories, generates its own jar file when you run package, and in general works like any other project.

A project is defined by declaring a lazy val of type Project. For example, :

lazy val util = (project in file("util"))

lazy val core = (project in file("core"))

The name of the val is used as the subproject’s ID, which is used to refer to the subproject at the sbt shell.

Optionally the base directory may be omitted if it’s the same as the name of the val.

lazy val util = project

lazy val core = project

Common settings 

To factor out common settings across multiple projects, create a sequence named commonSettings and call settings method on each project.

lazy val commonSettings = Seq(
  organization := "com.example",
  version := "0.1.0-SNAPSHOT",
  scalaVersion := "2.12.1"
)

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

lazy val util = (project in file("util")).
  settings(
    commonSettings,
    // other settings
  )

Now we can bump up version in one place, and it will be reflected across subprojects when you reload the build.

Build-wide settings 

Another a bit advanced technique for factoring out common settings across subprojects is to define the settings scoped to ThisBuild. (See Scopes)

Dependencies 

Projects in the build can be completely independent of one another, but usually they will be related to one another by some kind of dependency. There are two types of dependencies: aggregate and classpath.

Aggregation 

Aggregation means that running a task on the aggregate project will also run it on the aggregated projects. For example,

lazy val root = (project in file("."))
  .aggregate(util, core)

lazy val util = (project in file("util"))

lazy val core = (project in file("core"))

In the above example, the root project aggregates util and core. Start up sbt with two subprojects as in the example, and try compile. You should see that all three projects are compiled.

In the project doing the aggregating, the root project in this case, you can control aggregation per-task. For example, to avoid aggregating the update task:

lazy val root = (project in file("."))
  .aggregate(util, core)
  .settings(
    aggregate in update := false
  )

[...]

aggregate in update is the aggregate key scoped to the update task. (See scopes.)

Note: aggregation will run the aggregated tasks in parallel and with no defined ordering between them.

Classpath dependencies 

A project may depend on code in another project. This is done by adding a dependsOn method call. For example, if core needed util on its classpath, you would define core as:

lazy val core = project.dependsOn(util)

Now code in core can use classes from util. This also creates an ordering between the projects when compiling them; util must be updated and compiled before core can be compiled.

To depend on multiple projects, use multiple arguments to dependsOn, like dependsOn(bar, baz).

Per-configuration classpath dependencies 

foo dependsOn(bar) means that the compile configuration in foo depends on the compile configuration in bar. You could write this explicitly as dependsOn(bar % "compile->compile").

The -> in "compile->compile" means “depends on” so "test->compile" means the test configuration in foo would depend on the compile configuration in bar.

Omitting the ->config part implies ->compile, so dependsOn(bar % "test") means that the test configuration in foo depends on the Compile configuration in bar.

A useful declaration is "test->test" which means test depends on test. This allows you to put utility code for testing in bar/src/test/scala and then use that code in foo/src/test/scala, for example.

You can have multiple configurations for a dependency, separated by semicolons. For example, dependsOn(bar % "test->test;compile->compile").

Default root project 

If a project is not defined for the root directory in the build, sbt creates a default one that aggregates all other projects in the build.

Because project hello-foo is defined with base = file("foo"), it will be contained in the subdirectory foo. Its sources could be directly under foo, like foo/Foo.scala, or in foo/src/main/scala. The usual sbt directory structure applies underneath foo with the exception of build definition files.

Any .sbt files in foo, say foo/build.sbt, will be merged with the build definition for the entire build, but scoped to the hello-foo project.

If your whole project is in hello, try defining a different version (version := "0.6") in hello/build.sbt, hello/foo/build.sbt, and hello/bar/build.sbt. Now show version at the sbt interactive prompt. You should get something like this (with whatever versions you defined):

> show version
[info] hello-foo/*:version
[info]  0.7
[info] hello-bar/*:version
[info]  0.9
[info] hello/*:version
[info]  0.5

hello-foo/*:version was defined in hello/foo/build.sbt, hello-bar/*:version was defined in hello/bar/build.sbt, and hello/*:version was defined in hello/build.sbt. Remember the syntax for scoped keys. Each version key is scoped to a project, based on the location of the build.sbt. But all three build.sbt are part of the same build definition.

You may find it cleaner to put everything including settings in .scala files in order to keep all build definition under a single project directory, however. It’s up to you.

You cannot have a project subdirectory or project/*.scala files in the sub-projects. foo/project/Build.scala would be ignored.

At the sbt interactive prompt, type projects to list your projects and project <projectname> to select a current project. When you run a task like compile, it runs on the current project. So you don’t necessarily have to compile the root project, you could compile only a subproject.

You can run a task in another project by explicitly specifying the project ID, such as subProjectID/compile.

Common code 

The definitions in .sbt files are not visible in other .sbt files. In order to share code between .sbt files, define one or more Scala files in the project/ directory of the build root.

See organizing the build for details.

Contents

sbt Reference Manual
    1. Multi-project builds