构建定义基础
本页讨论 build.sbt 构建定义。
什么是构建定义?
构建定义在 build.sbt 中定义,由一组项目(类型为 Project)组成。由于项目一词可能产生歧义,本指南中我们通常称其为 subproject(子项目)。
例如,在 build.sbt 中您这样定义位于当前目录的子项目:
scalaVersion := "3.3.3"
name := "Hello"
或更明确地:
lazy val root = rootProject
.settings(
scalaVersion := "3.3.3",
name := "Hello",
)
每个子项目通过键值对配置。例如,name 是一个键,映射到字符串值,即您子项目的名称。键值对列在 .settings(...) 方法下。
build.sbt DSL
build.sbt 使用名为 build.sbt DSL 的 DSL 定义子项目,该 DSL 基于 Scala。最初您可以像使用 YAML 文件一样使用 build.sbt DSL,仅声明 scalaVersion 和 libraryDependencies,但随着构建规模增大,它支持更多功能以保持构建定义井然有序。
类型化设置表达式
让我们仔细看看 build.sbt DSL:
organization := "com.example"
^^^^^^^^^^^^ ^^^^^^^^ ^^^^^^^^^^^^^
key operator (setting/task) body
每个条目称为 setting expression(设置表达式)。其中一些也称为 task expression(任务表达式)。我们将在本页后面更详细地了解两者的区别。
设置表达式由三部分组成:
- 左侧是键。
- 运算符,此处为
:= - 右侧称为主体或设置/任务主体。
在左侧,name、version 和 scalaVersion 是 keys(键)。键是 SettingKey[A]、TaskKey[A] 或 InputKey[A] 的实例,其中 A 是期望的值类型。
由于键 name 的类型为 SettingKey[String],name 上的 := 运算符也专门类型为 String。如果您使用错误的值类型,构建定义将无法编译:
name := 42 // will not compile
val 和 lazy val
为避免重复相同信息(如库的版本号),build.sbt 中可以穿插使用 val、lazy val 和 def。
val toolkitV = "0.2.0"
val toolkit = "org.scala-lang" %% "toolkit" % toolkitV
val toolkitTest = "org.scala-lang" %% "toolkit-test" % toolkitV
scalaVersion := "3.8.1"
libraryDependencies += toolkit
libraryDependencies += (toolkitTest % Test)
在上面的代码中,val 定义变量,变量从上到下初始化。这意味着 toolkitV 必须在被引用之前定义。
以下是一个错误示例:
// bad example
val toolkit = "org.scala-lang" %% "toolkit" % toolkitV // uninitialized reference!
val toolkitTest = "org.scala-lang" %% "toolkit-test" % toolkitV // uninitialized reference!
val toolkitV = "0.2.0"
如果您的 build.sbt 包含未初始化的前向引用,sbt 将无法加载,并因 NullPointerException 而抛出 java.lang.ExceptionInInitializerError。让编译器解决此问题的一种方法是将变量定义为 lazy:
lazy val toolkit = "org.scala-lang" %% "toolkit" % toolkitV
lazy val toolkitTest = "org.scala-lang" %% "toolkit-test" % toolkitV
lazy val toolkitV = "0.2.0"
有些人反对过多使用 lazy val,但 Scala 3 的 lazy val 效率很高,我们认为它使构建定义在复制粘贴时更加稳健。