Build Tools and Dependency Management: SBT, Mill, and Scala CLI
Build tools are essential for managing Scala projects, handling dependencies, compilation, testing, and deployment. Scala has a rich ecosystem of build tools, each with its own strengths. In this lesson, we'll explore the three main build tools: SBT (the traditional standard), Mill (the modern alternative), and Scala CLI (for scripting and simple projects).
Understanding Build Tools in Scala
Build tools automate common development tasks:
- Dependency Management: Downloading and managing external libraries
- Compilation: Compiling Scala and Java source code
- Testing: Running unit and integration tests
- Packaging: Creating JARs, Docker images, or native binaries
- Publishing: Deploying artifacts to repositories
- Documentation: Generating API documentation
SBT: The Simple Build Tool
SBT is the most widely used build tool in the Scala ecosystem, known for its incremental compilation and powerful DSL.
Basic SBT Project Structure
my-scala-project/
├── build.sbt # Main build definition
├── project/
│ ├── build.properties # SBT version
│ ├── plugins.sbt # SBT plugins
│ └── Dependencies.scala # Dependency definitions (optional)
├── src/
│ ├── main/
│ │ ├── scala/ # Main Scala sources
│ │ ├── java/ # Main Java sources (optional)
│ │ └── resources/ # Main resources
│ └── test/
│ ├── scala/ # Test Scala sources
│ ├── java/ # Test Java sources (optional)
│ └── resources/ # Test resources
└── target/ # Compiled artifacts (generated)
Basic build.sbt Configuration
// build.sbt
ThisBuild / scalaVersion := "3.3.1"
ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / organization := "com.example"
lazy val root = (project in file("."))
.settings(
name := "my-scala-project",
libraryDependencies ++= Seq(
// Core dependencies
"org.typelevel" %% "cats-core" % "2.10.0",
"org.typelevel" %% "cats-effect" % "3.5.2",
"io.circe" %% "circe-core" % "0.14.6",
"io.circe" %% "circe-generic" % "0.14.6",
"io.circe" %% "circe-parser" % "0.14.6",
// Test dependencies
"org.scalatest" %% "scalatest" % "3.2.17" % Test,
"org.scalatestplus" %% "scalacheck-1-17" % "3.2.17.0" % Test,
"org.typelevel" %% "cats-effect-testing-scalatest" % "1.5.0" % Test
),
// Compiler options
scalacOptions ++= Seq(
"-deprecation",
"-feature",
"-unchecked",
"-Xfatal-warnings",
"-Xlint"
),
// Test configuration
Test / parallelExecution := false,
Test / testOptions += Tests.Argument(TestFrameworks.ScalaTest, "-oD")
)
project/build.properties
sbt.version=1.9.7
Advanced SBT Configuration
Multi-Module Projects
// build.sbt
ThisBuild / scalaVersion := "3.3.1"
ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / organization := "com.example"
lazy val commonSettings = Seq(
libraryDependencies ++= Seq(
"org.scalatest" %% "scalatest" % "3.2.17" % Test
),
scalacOptions ++= Seq(
"-deprecation",
"-feature",
"-unchecked"
)
)
lazy val core = (project in file("core"))
.settings(
commonSettings,
name := "my-project-core",
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-core" % "2.10.0",
"org.typelevel" %% "cats-effect" % "3.5.2"
)
)
lazy val api = (project in file("api"))
.settings(
commonSettings,
name := "my-project-api",
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-http" % "10.5.3",
"com.typesafe.akka" %% "akka-stream" % "2.8.5",
"de.heikoseeberger" %% "akka-http-circe" % "1.39.2"
)
)
.dependsOn(core)
lazy val cli = (project in file("cli"))
.settings(
commonSettings,
name := "my-project-cli",
libraryDependencies ++= Seq(
"com.github.scopt" %% "scopt" % "4.1.0"
)
)
.dependsOn(core)
lazy val root = (project in file("."))
.settings(
name := "my-project"
)
.aggregate(core, api, cli)
Mill: The Modern Build Tool
Mill is a newer build tool that aims to be simpler and faster than SBT, with better IDE integration and clearer semantics.
Basic Mill Project Structure
my-mill-project/
├── build.sc # Build definition
├── src/ # Main sources
│ └── Main.scala
├── test/
│ └── src/
│ └── MainTest.scala
└── out/ # Build output (generated)
Basic build.sc Configuration
// build.sc
import mill._, scalalib._
object myproject extends ScalaModule {
def scalaVersion = "3.3.1"
def ivyDeps = Agg(
ivy"org.typelevel::cats-core:2.10.0",
ivy"org.typelevel::cats-effect:3.5.2",
ivy"io.circe::circe-core:0.14.6",
ivy"io.circe::circe-generic:0.14.6",
ivy"io.circe::circe-parser:0.14.6"
)
def scalacOptions = Seq(
"-deprecation",
"-feature",
"-unchecked",
"-Xfatal-warnings"
)
object test extends ScalaTests {
def ivyDeps = Agg(
ivy"org.scalatest::scalatest:3.2.17",
ivy"org.scalatestplus::scalacheck-1-17:3.2.17.0"
)
def testFramework = "org.scalatest.tools.Framework"
}
}
Scala CLI: For Scripts and Simple Projects
Scala CLI is a lightweight tool for running Scala scripts and managing simple projects without complex build configurations.
Installing Scala CLI
# Install via coursier
cs install scala-cli
# Or download directly
curl -fL https://github.com/Virtuslab/scala-cli/releases/latest/download/scala-cli-x86_64-pc-linux.gz | gzip -d > scala-cli
chmod +x scala-cli
Basic Scala CLI Usage
// hello.scala
//> using scala "3.3.1"
//> using dep "org.typelevel::cats-core:2.10.0"
import cats.implicits._
@main def hello(name: String = "World"): Unit =
println(s"Hello, $name!".some.fold("No name")(_ + " 🎉"))
# Run script
scala-cli run hello.scala -- "Scala CLI"
# Compile to JAR
scala-cli package hello.scala -o hello.jar
# Run compiled JAR
java -jar hello.jar "From JAR"
Dependency Management Best Practices
Version Management
// build.sbt - Using variables for version management
val CatsVersion = "2.10.0"
val CatsEffectVersion = "3.5.2"
val CirceVersion = "0.14.6"
val Http4sVersion = "0.23.23"
val ScalaTestVersion = "3.2.17"
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-core" % CatsVersion,
"org.typelevel" %% "cats-effect" % CatsEffectVersion,
"io.circe" %% "circe-core" % CirceVersion,
"io.circe" %% "circe-generic" % CirceVersion,
"io.circe" %% "circe-parser" % CirceVersion,
"org.http4s" %% "http4s-ember-client" % Http4sVersion,
"org.http4s" %% "http4s-ember-server" % Http4sVersion,
"org.scalatest" %% "scalatest" % ScalaTestVersion % Test
)
Dependency Scopes
libraryDependencies ++= Seq(
// Compile scope (default)
"org.typelevel" %% "cats-core" % "2.10.0",
// Test scope
"org.scalatest" %% "scalatest" % "3.2.17" % Test,
// Provided scope (available at compile time, not packaged)
"javax.servlet" % "javax.servlet-api" % "4.0.1" % Provided,
// Runtime scope
"ch.qos.logback" % "logback-classic" % "1.4.11" % Runtime,
// Optional scope
"com.typesafe" % "config" % "1.4.3" % Optional
)
Build Optimization
Parallel Compilation
// build.sbt
Global / concurrentRestrictions += Tags.limit(Tags.Compile, 4)
ThisBuild / parallelExecution := true
Test / parallelExecution := false // Tests might conflict
Incremental Compilation
// build.sbt
ThisBuild / incOptions := incOptions.value.withLogRecompileOnMacro(false)
Continuous Integration Configuration
GitHub Actions with SBT
# .github/workflows/ci.yml
name: CI
Comments
Be the first to comment on this lesson!