Defining Multi-project Builds with sbt

Posted on October 18, 2017 · 3 min read

Let's setup a simple multi-project build using sbt, assuming we're already familiar with regular single-project builds.

Goal

We have two deliverable sub-projects (multi1 and multi2) and a common-stuff sub-project (common) on which they both depend on.

To simplify development and maintenance we can use a single build.sbt file which allows for centralized configuration, dependency and build management.

Then each sub-project contains nothing but its source code, while they can depend on other sub-projects.

Finally, we only need to generate artifacts for deliverable sub-projects.

File structure

This is the project file tree:

  • sbt-multi-project-example/
    • common/
      • src/
      • test/
    • multi1/
      • src/
      • test/
    • multi2/
      • src/
      • test/
    • project/
      • build.properties
      • plugins.sbt
    • build.sbt

Build configuration

Our build file will include all the configuration we need:

  • Sub-projects definition
  • Build settings
  • Dependencies
  • Artifacts generation

Projects definition

First of all we define our projects:

  • global: the parent project, aggregating the others
  • common: where the common code is
  • multi1 and multi2: the two deliverables, which depend on common
https://gist.github.com/pbassiner/5ec3209743f42b3f67fbb54b72b446f4#file-projects-sbt

Using aggregate in global implies that running a task on the aggregate project will also run it on the aggregated ones. On the other hand, using dependsOn sets the code dependency between sub-projects.

By default, dependency is set for the compile configuration. If we'd need to also depend on the test configuration (i.e. we have tests depending on other sub-project's test source code), we'd need to define dependency as follows: common % "compile->compile;test->test".

Since the global project is in the project root folder, we need to explicitly specify it using in(file("."). There's no need to do so with the other sub-projects since they are located in a folder with the same name.

Settings

We can setup some global settings as follows:

https://gist.github.com/pbassiner/5ec3209743f42b3f67fbb54b72b446f4#file-globalsettings-sbt

Settings that are subject to change can be defined as values that can later be used by each sub-project. Here we're defining the Scala compiler options and some extra repositories:

https://gist.github.com/pbassiner/5ec3209743f42b3f67fbb54b72b446f4#file-settings-sbt

Then we can use them in the sub-projects:

https://gist.github.com/pbassiner/5ec3209743f42b3f67fbb54b72b446f4#file-settingsinprojects-sbt

Dependencies

We'll follow the same strategy with the dependencies. First we define our catalog of dependencies:

https://gist.github.com/pbassiner/5ec3209743f42b3f67fbb54b72b446f4#file-dependencies-sbt

Then we identify the common ones used by all sub-projects:

https://gist.github.com/pbassiner/5ec3209743f42b3f67fbb54b72b446f4#file-commondependencies-sbt

And finally we define each sub-project's dependencies combining the common with the specific ones:

https://gist.github.com/pbassiner/5ec3209743f42b3f67fbb54b72b446f4#file-dependenciesinprojects-sbt

The parent project, global, needs no dependencies since it has no code at all. The other sub-projects have the common dependencies along with some specific ones: multi1 requires monocle and multi2 requires pureconfig.

Artifacts generation

To generate artifacts for our deliverable sub-projects we'll use sbt-assembly to produce a fat-jar (if we'd need to generate Docker images we could use sbt-native-packager instead).

First we define the corresponding settings:

https://gist.github.com/pbassiner/5ec3209743f42b3f67fbb54b72b446f4#file-sbtassemblysettings-sbt

And then we add them only to the settings of our deliverable sub-projects multi1 and multi2:

https://gist.github.com/pbassiner/5ec3209743f42b3f67fbb54b72b446f4#file-sbtassemblysettingsinprojects-sbt

Putting it all together

Simply running sbt clean compile test assembly will clean, compile and test all the sub-projects and generate a fat-jar only for our two deliverable sub-projects.

The full source code of this example can be found at this repository.

References


Comments

Would you like to leave a comment? Since this blog is hosted on GitHub Pages there's no straightforward way to do so.

Instead, you can add a comment in this GitHub issue. If you'd like to see it here, refresh this page after posting the comment.