Lesson 19 / 25
Projects with sbt
Structure multi-module builds, manage dependencies and run common tasks.
Working with sbt
An sbt project has build.sbt at the root, project/build.properties pinning the sbt version, project/plugins.sbt for plugins, and sources in src/main/scala and src/test/scala (the Maven layout). Settings are key-value pairs such as scalaVersion, libraryDependencies and scalacOptions. Multi-module builds declare several projects with dependsOn between them, for example domain, persistence and api, to enforce architecture boundaries and speed up incremental compilation. Common tasks: compile, test, testOnly *CartSpec, run, console (a REPL with the project classpath), reload after changing the build and ~test to rerun tests on every save. Useful plugins include sbt-assembly (fat JARs), sbt-native-packager (Docker images and archives), sbt-scalafmt, sbt-scalafix, sbt-tpolecat (strict compiler flags) and sbt-revolver. Enable compiler warnings as errors in CI (-Werror) along with -Wunused:all. Keep the build simple: most problems come from clever build logic, not from code.
A multi-module build
Modules depend on each other in one direction, and sbt compiles each incrementally.
A multi-module build.sbt
Shared settings, module dependencies and test libraries.
// build.sbt
ThisBuild / scalaVersion := "3.3.6"
ThisBuild / organization := "in.example"
lazy val commonSettings = Seq(
scalacOptions ++= Seq("-deprecation", "-feature", "-Wunused:all"),
libraryDependencies += "org.scalameta" %% "munit" % "1.1.0" % Test
)
lazy val domain = project
.settings(commonSettings)
lazy val persistence = project
.dependsOn(domain)
.settings(
commonSettings,
libraryDependencies += "org.postgresql" % "postgresql" % "42.7.5" // a Java library uses %
)
lazy val api = project
.dependsOn(domain, persistence)
.settings(commonSettings)
lazy val root = (project in file("."))
.aggregate(domain, persistence, api) // sbt test runs tests in all modules
// project/build.properties: sbt.version=1.10.11
// shell: sbt then ~api/test or testOnly *CartSuite% versus %%
Use %% for Scala libraries (published per Scala version) and a single % for plain Java libraries such as JDBC drivers. Using %% with a Java library fails to resolve.
Quick check: What does aggregate do in an sbt root project?
- Runs tasks such as compile and test across the listed modules
- Merges all source files into one module
- Creates a fat JAR
- Downloads Scala
Answer
Runs tasks such as compile and test across the listed modules — Aggregation forwards tasks to the aggregated projects.