此页面介绍了范围。假设您已经阅读并理解了前面的页面,构建定义 和 任务图。
我们先前假定像 name
这样的键对应于 sbt 的键值对映射中的一个条目。这是一种简化。
实际上,每个键可以在多个称为范围的上下文中具有关联的值。
一些具体示例
compile
键可能对于您的主源代码和测试源代码具有不同的值,如果您想以不同的方式编译它们。packageOptions
键(包含用于创建 jar 包的选项)在打包类文件 (packageBin
) 或打包源代码 (packageSrc
) 时可能具有不同的值。给定键 name
没有单一值,因为值可能因范围而异。
但是,给定范围键有一个单一值。
如果您考虑 sbt 处理设置列表以生成描述项目的键值映射,如之前讨论过,该键值映射中的键是范围键。构建定义(例如在 build.sbt
中)中定义的每个设置也适用于范围键。
范围通常是隐式的或具有默认值,但如果默认值不正确,则需要在 build.sbt
中提及所需的范围。
范围轴是一个类似于 Option[A]
的类型构造器,用于在范围内形成一个组件。
有三个范围轴
如果您不熟悉轴的概念,我们可以考虑 RGB 颜色立方体为例
在 RGB 颜色模型中,所有颜色都由立方体中的一个点表示,该点的轴对应于由数字编码的红色、绿色和蓝色分量。类似地,sbt 中的完整范围由子项目、配置和任务值的元组形成
projA / Compile / console / scalacOptions
这是在 sbt 1.1 中引入的用于以下内容的斜杠语法:
scalacOptions in (
Select(projA: Reference),
Select(Compile: ConfigKey),
Select(console.key)
)
如果您将多个项目放入单个构建中,则每个项目都需要自己的设置。也就是说,键可以根据项目进行范围限定。
项目轴也可以设置为 ThisBuild
,这意味着“整个构建”,因此设置适用于整个构建,而不是单个项目。构建级设置通常用作项目未定义项目特定设置时的回退。我们将在本页面的后面部分讨论更多关于构建级设置的内容。
依赖项配置(简称“配置”)定义了一个库依赖项图,它可能具有自己的类路径、源代码、生成的包等。依赖项配置概念来自 Ivy,sbt 以前使用它来管理依赖项 库依赖项,以及来自 MavenScopes。
您将在 sbt 中看到的一些配置
Compile
定义了主构建 (src/main/scala
)。Test
定义了如何构建测试 (src/test/scala
)。Runtime
定义了 run
任务的类路径。默认情况下,与编译、打包和运行相关的所有键都范围限定到配置,因此它们在每个配置中的工作方式可能不同。最明显的例子是 compile
、package
和 run
任务键;但所有影响这些键的键(例如 sourceDirectories
或 scalacOptions
或 fullClasspath
)也范围限定到配置。
关于配置的另一件事是它可以扩展其他配置。下图显示了最常见配置之间的扩展关系。
Test
和 IntegrationTest
扩展 Runtime
;Runtime
扩展 Compile
;CompileInternal
扩展 Compile
、Optional
和 Provided
。
设置会影响任务的工作方式。例如,packageSrc
任务受 packageOptions
设置影响。
为了支持这一点,任务键(例如 packageSrc
)可以作为另一个键(例如 packageOptions
)的范围。
构建包的各种任务 (packageSrc
、packageBin
、packageDoc
) 可以共享与打包相关的键,例如 artifactName
和 packageOptions
。这些键可以对每个打包任务具有不同的值。
每个范围轴都可以用轴类型的一个实例(类似于 Some(_)
)填充,或者用特殊值 Zero
填充。因此,我们可以将 Zero
视为 None
。
Zero
是所有范围轴的通用回退,但在大多数情况下,其直接使用应保留给 sbt 和插件作者。
Global
是一个范围,它将所有轴设置为 Zero
:Zero / Zero / Zero
。换句话说,Global / someKey
是 Zero / Zero / Zero / someKey
的简写形式。
如果您在 build.sbt
中使用裸键创建设置,它将范围限定到(当前子项目 / 配置 Zero
/ 任务 Zero
)
lazy val root = (project in file("."))
.settings(
name := "hello"
)
运行 sbt 并输入 inspect name
以查看它是通过 ProjectRef(uri("file:/private/tmp/hello/"), "root") / name
提供的,即项目是 ProjectRef(uri("file:/Users/xxx/hello/"), "root")
,并且没有显示配置或任务范围(这意味着 Zero
)。
右侧的裸键也范围限定到(当前子项目 / 配置 Zero
/ 任务 Zero
)
organization := name.value
任何范围轴的类型都已进行方法富化,具有 /
运算符。/
的参数可以是键或另一个范围轴。因此,例如,虽然没有很好的理由这样做,但您可以将 name
键的一个实例范围限定到 Compile
配置
Compile / name := "hello"
或者您可以将范围限定到 packageBin
任务的名称(毫无意义!只是一个示例)
packageBin / name := "hello"
或者您可以使用多个范围轴设置 name
,例如在 Compile
配置的 packageBin
任务中
Compile / packageBin / name := "hello"
或者您可以使用 Global
// same as Zero / Zero / Zero / concurrentRestrictions
Global / concurrentRestrictions := Seq(
Tags.limitAll(1)
)
(Global / concurrentRestrictions
隐式转换为 Zero / Zero / Zero / concurrentRestrictions
,将所有轴设置为 Zero
范围组件;任务和配置默认情况下已经是 Zero
,因此这里的效果是使项目 Zero
,也就是说,定义 Zero / Zero / Zero / concurrentRestrictions
而不是 ProjectRef(uri("file:/tmp/hello/"), "root") / Zero / Zero / concurrentRestrictions
)
在命令行和 sbt shell 中,sbt 以这种方式显示(并解析)范围键
ref / Config / intask / key
ref
标识子项目轴。它可以是<project-id>
、ProjectRef(uri("file:..."), "id")
或表示“整个构建”范围的ThisBuild
。Config
使用大写的Scala标识符来标识配置轴。intask
标识任务轴。key
标识要作用域的键。Zero
可以出现在每个轴上。
如果您省略作用域键的一部分,它将按如下方式推断:
有关更多详细信息,请参阅与配置系统交互。
fullClasspath
仅指定一个键,因此使用默认作用域:当前项目、与键相关的配置和Zero
任务作用域。Test / fullClasspath
指定配置,因此这是Test
配置中的fullClasspath
,其他两个作用域轴使用默认值。root / fullClasspath
指定项目root
,其中项目由项目 ID 标识。root / Zero / fullClasspath
指定项目root
,并为配置指定Zero
,而不是默认配置。doc / fullClasspath
指定作用域到doc
任务的fullClasspath
键,项目和配置轴使用默认值。ProjectRef(uri("file:/tmp/hello/"), "root") / Test / fullClasspath
指定项目ProjectRef(uri("file:/tmp/hello/"), "root")
。还指定配置 Test,保留默认的任务轴。ThisBuild / version
将子项目轴设置为“整个构建”,其中构建为ThisBuild
,使用默认配置。Zero / fullClasspath
将子项目轴设置为Zero
,使用默认配置。root / Compile / doc / fullClasspath
设置所有三个作用域轴。在 sbt shell 中,您可以使用inspect
命令来理解键及其作用域。尝试 inspect Test/fullClasspath
$ sbt
sbt:Hello> inspect Test / fullClasspath
[info] Task: scala.collection.Seq[sbt.internal.util.Attributed[java.io.File]]
[info] Description:
[info] The exported classpath, consisting of build products and unmanaged and managed, internal and external dependencies.
[info] Provided by:
[info] ProjectRef(uri("file:/tmp/hello/"), "root") / Test / fullClasspath
[info] Defined at:
[info] (sbt.Classpaths.classpaths) Defaults.scala:1639
[info] Dependencies:
[info] Test / dependencyClasspath
[info] Test / exportedProducts
[info] Test / fullClasspath / streams
[info] Reverse dependencies:
[info] Test / testLoader
[info] Delegates:
[info] Test / fullClasspath
[info] Runtime / fullClasspath
[info] Compile / fullClasspath
[info] fullClasspath
[info] ThisBuild / Test / fullClasspath
[info] ThisBuild / Runtime / fullClasspath
[info] ThisBuild / Compile / fullClasspath
[info] ThisBuild / fullClasspath
[info] Zero / Test / fullClasspath
[info] Zero / Runtime / fullClasspath
[info] Zero / Compile / fullClasspath
[info] Global / fullClasspath
[info] Related:
[info] Compile / fullClasspath
[info] Runtime / fullClasspath
在第一行,您可以看到这是一个任务(与设置相反,如.sbt 构建定义中所述)。任务产生的值将具有类型scala.collection.Seq[sbt.Attributed[java.io.File]]
。
“由...提供”指向定义值的范围键,在本例中为ProjectRef(uri("file:/tmp/hello/"), "root") / Test / fullClasspath
(它是作用域到Test
配置和ProjectRef(uri("file:/tmp/hello/"), "root")
项目的fullClasspath
键)。
“依赖项”在上一页中进行了详细讨论。
我们将在后面讨论“委托”。
尝试 inspect fullClasspath
(与上面的示例不同,检查 Test / fullClasspath
)以了解差异。由于省略了配置,因此它被自动检测为Compile
。因此,inspect Compile / fullClasspath
应该与inspect fullClasspath
看起来相同。
尝试 inspect ThisBuild / Zero / fullClasspath
以进行另一种对比。默认情况下,fullClasspath
未在Zero
配置作用域中定义。
同样,有关更多详细信息,请参阅与配置系统交互。
如果所讨论的键通常有作用域,则需要指定作用域。例如,默认情况下,compile
任务的作用域为Compile
和Test
配置,并且在这些作用域之外不存在。
要更改与compile
键关联的值,您需要编写Compile / compile
或Test / compile
。使用普通的compile
将定义一个作用域到当前项目的新编译任务,而不是覆盖作用域到配置的标准编译任务。
如果您收到类似于“引用未定义设置”的错误,通常是因为您没有指定作用域,或者您指定了错误的作用域。您正在使用的键可能在其他作用域中定义。sbt 将尝试在错误消息中建议您的本意;寻找“您是说 Compile / compile 吗?”
一种思考方式是,名称仅仅是键的一部分。实际上,所有键都包含名称和作用域(其中作用域具有三个轴)。完整的表达式Compile / packageBin / packageOptions
是一个键名,换句话说。仅仅packageOptions
也是一个键名,但它是一个不同的键(对于没有斜杠的键,隐式地假设一个作用域:当前项目、Zero
配置、Zero
任务)。
在子项目之间提取通用设置的先进技术是将设置定义为作用域到ThisBuild
。
如果在特定子项目中作用域的键未找到,sbt 将在ThisBuild
中查找它作为后备。使用这种机制,我们可以为version
、scalaVersion
和organization
等常用键定义构建级默认设置。
ThisBuild / organization := "com.example",
ThisBuild / scalaVersion := "2.12.18",
ThisBuild / version := "0.1.0-SNAPSHOT"
lazy val root = (project in file("."))
.settings(
name := "Hello",
publish / skip := true
)
lazy val core = (project in file("core"))
.settings(
// other settings
)
lazy val util = (project in file("util"))
.settings(
// other settings
)
为了方便起见,有一个inThisBuild(...)
函数将把键和设置表达式的正文都作用域到ThisBuild
。将设置表达式放在那里等效于在可能的情况下在前面加上ThisBuild /
。
由于作用域委托的性质(我们将在后面讨论),构建级设置应该只设置为纯值或来自Global
或ThisBuild
作用域的设置。
如果在作用域中没有与之关联的值,作用域键可能未定义。
对于每个作用域轴,sbt 都具有由其他作用域值组成的后备搜索路径。通常,如果键在更具体的范围中没有关联的值,sbt 将尝试从更一般的范围(例如ThisBuild
范围)获取值。
此功能允许您在更一般的范围内设置一次值,从而允许多个更具体的范围继承该值。我们将在后面详细讨论作用域委托。