पाठ 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.

Three stacked module boxes connected by downward arrows, with a build file icon on the side.
Figure 7.1 — api depends on persistence, which depends on domain.

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.

त्वरित जाँच: 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.