本页描述 sbt 构建定义,包括一些“理论”和 build.sbt
的语法。假设您已经安装了最新版本的 sbt,例如 sbt 1.9.8,知道如何 使用 sbt,并且已经阅读了入门指南中的前几页。
本页讨论 build.sbt
构建定义。
作为构建定义的一部分,您将指定构建使用的 sbt 版本。这使具有不同 sbt 启动器版本的人能够使用一致的结果构建相同的项目。为此,创建一个名为 project/build.properties
的文件,其中指定 sbt 版本如下
sbt.version=1.9.8
如果本地没有所需的版本,sbt
启动器将为您下载。如果此文件不存在,sbt
启动器将选择任意版本,这并不鼓励,因为它会使您的构建不可移植。
构建定义在 build.sbt
中定义,它包含一组项目(类型为 Project
)。由于术语项目可能含糊不清,因此在本指南中我们通常称其为子项目。
例如,在 build.sbt
中,您可以像这样定义位于当前目录中的子项目
lazy val root = (project in file("."))
.settings(
name := "Hello",
scalaVersion := "2.12.7"
)
每个子项目都由键值对配置。
例如,一个键是 name
,它映射到一个字符串值,即子项目的名称。键值对列在 .settings(...)
方法下,如下所示
lazy val root = (project in file("."))
.settings(
name := "Hello",
scalaVersion := "2.12.7"
)
build.sbt
定义子项目,它使用build.sbt 领域特定语言 (DSL) 持有一系列称为设置表达式的键值对。
ThisBuild / organization := "com.example"
ThisBuild / scalaVersion := "2.12.18"
ThisBuild / version := "0.1.0-SNAPSHOT"
lazy val root = (project in file("."))
.settings(
name := "hello"
)
让我们仔细看看 build.sbt
DSL:
每个条目都称为设置表达式。其中一些也被称为任务表达式。我们将在本页的后面看到更多关于它们的区别。
设置表达式包含三个部分
:=
在左侧,name
、version
和 scalaVersion
是键。键是 SettingKey[T]
、TaskKey[T]
或 InputKey[T]
的实例,其中 T
是预期的值类型。键的种类将在下面解释。
因为键 name
的类型为 SettingKey[String]
,所以 name
上的 :=
运算符也专门类型化为 String
。如果您使用错误的值类型,构建定义将无法编译
lazy val root = (project in file("."))
.settings(
name := 42 // will not compile
)
build.sbt
也可能与 val
、lazy val
和 def
交织在一起。不允许在 build.sbt
中使用顶层 object
和 class
。这些应该作为 Scala 源文件放在 project/
目录中。
键有三种类型
SettingKey[T]
:仅评估一次的值的键(该值在加载子项目时计算,并保留)。TaskKey[T]
:值的键,称为任务,每次引用它时都会评估(类似于 Scala 函数),可能会产生副作用。InputKey[T]
:具有命令行参数作为输入的任务的键。有关更多详细信息,请查看 输入任务。内置键只是名为 Keys 的对象的字段。build.sbt
隐式包含 import sbt.Keys._
,因此 sbt.Keys.name
可以称为 name
。
自定义键可以使用它们各自的创建方法定义:settingKey
、taskKey
和 inputKey
。每个方法都期望与键关联的值的类型以及描述。键的名称取自分配给键的 val
。例如,要定义一个名为 hello
的新任务的键,
lazy val hello = taskKey[Unit]("An example task")
这里我们使用了这样一个事实,即 .sbt
文件除了设置之外还可以包含 val
和 def
。所有此类定义都在设置之前评估,无论它们在文件中定义的位置如何。
注意: 通常,使用
lazy val
而不是val
来避免初始化顺序问题。
TaskKey[T]
被称为定义任务。任务是诸如 compile
或 package
之类的操作。它们可能返回 Unit
(Unit
是 Scala 的 void
),或者它们可能返回与任务相关的值,例如 package
是一个 TaskKey[File]
,它的值是它创建的 jar 文件。
每次您开始执行任务时,例如在交互式 sbt 提示符下键入 compile
,sbt 将只执行一次涉及的任何任务。
sbt 描述子项目的键值对可以保留一个设置(例如名称)的固定字符串值,但它必须保留一个任务(例如 compile
)的一些可执行代码,即使该可执行代码最终返回一个字符串,它也必须每次都重新运行。
给定键始终引用任务或普通设置。 也就是说,“任务性”(是否每次都重新运行)是键的属性,而不是值的属性。
可以通过在 sbt 提示符下键入 settings
或 settings -v
来获取当前构建定义中存在的设置键列表。
同样,可以通过键入 tasks
或 tasks -v
来获取当前定义的任务键列表。您还可以查看 命令行参考,了解在 sbt 提示符下常用的内置任务的讨论。
如果键满足以下条件,它将打印在生成的列表中
name
或 scalaVersion
)您也可以在 sbt 提示符下键入 help <key>
以获取更多信息。
使用 :=
,您可以为设置分配一个值,为任务分配一个计算。对于设置,该值将在项目加载时计算一次。对于任务,每次执行任务时都会重新运行该计算。
例如,要实现上一节中的 hello
任务
lazy val hello = taskKey[Unit]("An example task")
lazy val root = (project in file("."))
.settings(
hello := { println("Hello!") }
)
我们已经看到定义设置的示例,当我们定义项目的名称时,
lazy val root = (project in file("."))
.settings(
name := "hello"
)
从类型系统的角度来看,从任务键创建的 Setting
与从设置键创建的 Setting
略有不同。taskKey := 42
会生成一个 Setting[Task[T]]
,而 settingKey := 42
会生成一个 Setting[T]
。在大多数情况下,这没有任何区别;任务键在任务执行时仍然会创建一个类型为 T
的值。
T
与 Task[T]
类型差异具有以下含义:设置不能依赖于任务,因为设置仅在项目加载时评估一次,不会重新运行。有关更多信息,请参阅 任务图。
在 sbt shell 中,您可以键入任何任务的名称来执行该任务。这就是为什么键入 compile
会运行 compile
任务的原因。compile
是一个任务键。
如果你输入的是设置键的名称而不是任务键的名称,将显示设置键的值。输入任务键的名称会执行任务,但不会显示结果值;要查看任务的结果,请使用 `show <任务名称>` 而不是简单的 `<任务名称>`。键名称的约定是使用 `camelCase`,这样命令行名称和 Scala 标识符就相同。
要了解更多关于任何键的信息,请在 sbt 交互式提示符中键入 `inspect <键名>`。有些 `inspect` 显示的信息现在可能没有意义,但在顶部会显示设置的值类型和简要描述。
你可以在 `build.sbt` 的顶部放置导入语句;它们不需要用空白行分隔。
有一些隐含的默认导入,如下所示
import sbt._
import Keys._
(此外,如果你有自动插件,则 `autoImport` 下标记的名称将被导入。)
可以将设置直接写入 `build.sbt` 文件,而不是将它们放在 `settings(...)` 调用中。我们称之为“裸风格”。
ThisBuild / version := "1.0"
ThisBuild / scalaVersion := "2.12.18"
这种语法推荐用于 `ThisBuild` 范围的设置和添加插件。请参阅后面关于范围和插件的部分。
要依赖第三方库,有两种选择。第一种是在 `lib/` 中放置 jar 文件(非托管依赖项),另一种是添加托管依赖项,它将在 `build.sbt` 中如下所示
val derby = "org.apache.derby" % "derby" % "10.4.1.3"
ThisBuild / organization := "com.example"
ThisBuild / scalaVersion := "2.12.18"
ThisBuild / version := "0.1.0-SNAPSHOT"
lazy val root = (project in file("."))
.settings(
name := "Hello",
libraryDependencies += derby
)
这是如何在 `build.sbt` 中添加对 Apache Derby 库(版本 10.4.1.3)的托管依赖项的方法。
`libraryDependencies` 键包含两个复杂之处: `+=` 而不是 `:=`,以及 `%` 方法。 `+=` 将附加到键的旧值而不是替换它,这在 任务图 中解释。 `%` 方法用于从字符串构造 Ivy 模块 ID,在 库依赖项 中解释。
我们将跳过库依赖项的细节,直到后面在入门指南中介绍。在后面的 整页 中会覆盖它。