Why sbt exists
Preliminaries
In Scala, a library or a program is compiled using the Scala compiler, scalac
, as documented in the Scala 3 Book:
@main def hello() = println("Hello, World!")
$ scalac hello.scala
$ scala hello
Hello, World!
This process gets tedious and slow if we were to invoke scalac
directly since we'd have to pass all the Scala source file names.
Furthermore, most non-trivial programs will likely have library dependencies, and will therefore also depend transitively on their dependencies. This is doubly complicated for Scala ecosystem because we have Scala 2.12, 2.13 ecosystem, Scala 3.x ecosystem, JVM, JS, and Native platforms.
Rather than working with JAR files and scalac
, we can avoid manual toil by introducing a higher-level subproject abstraction and by using a build tool.
sbt
sbt is a simple build tool created for Scala and Java. It lets us declare subprojects and their various dependencies and custom tasks to ensure that we'll always get a fast, repeatable build.
To accomplish this goal, sbt does several things:
- The version of sbt itself is tracked in
project/build.properties
. - Defines a domain-specific language (DSL) called build.sbt DSL that can declare the Scala version and other subproject information in
build.sbt
. - Uses Coursier to fetch subprojects dependencies and their dependencies.
- Invokes Zinc to incrementally compile Scala and Java sources.
- Automatically runs tasks in parallel whenever possible.
- Defines conventions on how packages are published to Maven repositories to interoperate with the wider JVM ecosystem.
To a large extent, sbt standardizes the commands needed to build a given program or library.
Why build.sbt DSL?
build.sbt DSL makes sbt a unique build tool,
as opposed to other tools that use configuration file formats like YAML, TOML, and XML.
Originally developed beween 2010 and 2013, build.sbt
can start almost like a YAML file, declaring just scalaVersion
and libraryDependencies
,
but it can supports more features to keep the build definition organized as the build grows larger:
- To avoid repeating the same information, like the version number for a library,
build.sbt
can declare variables usingval
. - Uses Scala language constructs like
if
to define settings and tasks, when needed. - Statically typed settings and tasks, to catch typos and type errors before the build starts. The type also helps passing data from one task from another.
- Provides structured concurrency via
Initialized[Task[A]]
. The DSL uses direct style.value
syntax to concisely define task graphs. - Enpowers the community to extend sbt with plugins that provide custom tasks or language extensions like Scala.JS.