sbt 的存在理由
预备知识
在 Scala 中,库或程序使用 Scala 编译器 scalac
进行编译,正如 Scala 3 Book 中所记录的:
@main def hello() = println("Hello, World!")
$ scalac hello.scala
$ scala hello
Hello, World!
如果我们直接调用 scalac
,这个过程会变得乏味且缓慢,因为我们必须传递所有 Scala 源文件名。
此外,大多数非平凡的程序可能会有库依赖,因此也会传递性地依赖于它们的依赖项。对于 Scala 生态系统来说,这更加复杂,因为我们有 Scala 2.12,2.13 生态系统,Scala 3.x 生态系统,以及 JVM,JS 和 Native 平台。
与其使用 JAR 文件和 scalac
,我们可以通过引入更高级别的子项目抽象概念并使用构建工具来避免手动劳作。
sbt
sbt 是为 Scala 和 Java 创建的简单构建工具。它允许我们声明子项目及其各种依赖项和自定义任务,以确保我们始终能获得快速,可重复的构建。
为了实现这一目标,sbt 做了几件事:
- sbt 本身的版本记录在
project/build.properties
中。 - 定义了一种称为 build.sbt DSL 的领域特定语言,可以在
build.sbt
中声明 Scala 版本和其他子项目信息。 - 使用 Coursier 获取子项目依赖及其依赖项。
- 调用 Zinc 增量编译 Scala 和 Java 源代码。
- 在可能的情况下自动并行运行任务。
- 定义了如何将包发布到 Maven 仓库的约定,以便与更广泛的 JVM 生态系统进行互操作。
在很大程度上,sbt 标准化了构建给定程序或库所需的命令。
build.sbt DSL 的必要性
sbt 采用基于 Scala 的 build.sbt DSL 来声明子项目和任务图。如今,使用 DSL 而非 YAML 和 XML 等配置格式几乎不再是 sbt 的独特之处。许多构建工具,如 Gradle,Google 的 Bazel,Meta 的 Buck 以及 Apple 的 SwiftPM 都使用 DSL 来定义子项目。
build.sbt
最初几乎可以像 YAML 文件一样,仅声明 scalaVersion
和 libraryDependencies
,但随着您对构建系统需求的增长,它可以进行扩展:
- 为了避免重复相同的信息,例如库的版本号,
build.sbt
可以使用val
声明变量。 - 在需要时使用 Scala 语言结构(如
if
)来定义设置和任务。 - 静态类型的设置和任务,可在构建开始前捕获拼写错误和类型错误。类型还有助于将数据从一个任务传递到另一个任务。
- 通过
Initialized[Task[A]]
提供结构化并发。DSL 使用所谓的直接风格.value
语法来简洁地定义任务图。 - 使社区能够通过插件扩展 sbt,这些插件提供自定义任务或语言扩展,如 Scala.JS。