This page was translated mostly with Google Translate. Please send a pull request to improve it.
此页面描述 scope 委托。 假定您已经阅读并理解了先前的页面 .sbt 构建定义 和 scopes。
既然我们已经涵盖了 scope 界定的所有细节,我们就可以详细解释 .value 查找。 如果您是第一次阅读此页面,则可以跳过本节。
总结到目前为止我们已经学到的东西:
Zero。
ThisBuild。
Test 扩展了 Runtime,而 Runtime 扩展了 Compile configuration。
${current subproject} / Zero / Zero。
/ 运算符确定 key 的 scope。
现在,假设我们具有以下构建定义:
lazy val foo = settingKey[Int]("")
lazy val bar = settingKey[Int]("")
lazy val projX = (project in file("x"))
.settings(
foo := {
(Test / bar).value + 1
},
Compile / bar := 1
)
在 foo 的 setting 主体内部,声明了对 scoped key Test / bar 的依赖。
但是,尽管在 projX 中未定义 Test / bar,sbt 仍然能够将 Test / bar
解析为另一个 scoped key,导致 foo 初始化为 2。
sbt 具有定义明确的后备搜索路径,称为 scope 委托。 此功能使您可以在更广泛的 scope 内设置一次值,从而允许多个更特定的 scope 继承该值。
以下是 scope 委托的规则:
Zero (这是 scope 的非 task scope 版本)。
Zero(与无作用域的 configuration 轴相同)。
ThisBuild,然后为 Zero。
我们将在本页面的其余部分中查看每个规则。
换句话说,给定两个作用域候选者,如果一个在 subproject 轴上具有更特定的值,则无论 configuration 或 task scope 如何,它将始终获胜。 同样,如果 subproject 相同,则无论 task scope 如何,具有更具体 configuration 值的子项目将始终获胜。 我们将看到更多定义更具体的规则。
Zero (这是 scope 的非 task scope 版本)。
对于给定 key,sbt 将如何生成委托 scope,这里有一个具体规则。 记住,我们试图显示给定任意 (xxx / yyy).value 的搜索路径。
练习题 A: 给出以下构建定义:
lazy val projA = (project in file("a"))
.settings(
name := {
"foo-" + (packageBin / scalaVersion).value
},
scalaVersion := "2.11.11"
)
projA / name 的值是什么?
"foo-2.11.11"
"foo-2.12.18"
答案是 "foo-2.11.11"。
在 .settings(...) 内部,scalaVersion 的 scope 将自动设置为 projA / Zero / Zero,
因此 packageBin / scalaVersion 变为 projA / Zero / packageBin / scalaVersion。
该特定 scoped key 是未定义的。
通过使用规则2,sbt 将把 task 轴替换 Zero 作为 projA / Zero / Zero (或 projA / scalaVersion)。该 scoped key 定义为 "2.11.11"。
Zero(与无作用域的 configuration 轴相同)。
我们前面看到的例子是 projX:
lazy val foo = settingKey[Int]("")
lazy val bar = settingKey[Int]("")
lazy val projX = (project in file("x"))
.settings(
foo := {
(Test / bar).value + 1
},
Compile / bar := 1
)
如果我们再次写出完整 scope,projX / Test / Zero 。
还记得 Test 扩展了 Runtime ,Runtime 扩展了 Compile 。
Test / bar 是未定义的,但是由于规则3,sbt 将查找 scope 为 projX / Test / Zero,
projX / Runtime / Zero,然后 projX / Compile / Zero。
找到最后一个,即 Compile / bar。
ThisBuild,然后为 Zero。
练习题 B: 给出以下构建定义:
ThisBuild / organization := "com.example"
lazy val projB = (project in file("b"))
.settings(
name := "abc-" + organization.value,
organization := "org.tempuri"
)
projB / name 的值是什么?
"abc-com.example"
"abc-org.tempuri"
答案是 abc-org.tempuri。
因此,根据规则4,第一个搜索路径是具有 projB / Zero / Zero scope 的 organization,在 projB 中定义为 "org.tempuri"。
它的优先级高于构建级别 setting ThisBuild / organization。
练习题 C: 给出以下构建定义:
ThisBuild / packageBin / scalaVersion := "2.12.2"
lazy val projC = (project in file("c"))
.settings(
name := {
"foo-" + (packageBin / scalaVersion).value
},
scalaVersion := "2.11.11"
)
projC / name 值是什么?
"foo-2.12.2"
"foo-2.11.11"
答案是 foo-2.11.11。
scope 为 projC / Zero / packageBin 的 scalaVersion 未定义。
scalaVersion scoped to projC / Zero / packageBin is undefined.
规则2找到 projC / Zero / Zero。 规则4找到 ThisBuild / Zero / packageBin。
在这种情况下,规则1决定在 subproject 轴上赢得更具体的价值,这是定义为 "2.11.11" 的 projC / Zero / Zero。
练习题 D: 给出以下构建定义:
ThisBuild / scalacOptions += "-Ywarn-unused-import"
lazy val projD = (project in file("d"))
.settings(
test := {
println((Compile / console / scalacOptions).value)
},
console / scalacOptions -= "-Ywarn-unused-import",
Compile / scalacOptions := scalacOptions.value // added by sbt
)
如果您进行了 projD/test 您会看到什么?
List()
List(-Ywarn-unused-import)
答案是 List(-Ywarn-unused-import)。
规则2找到 projD / Compile / Zero,
规则3找到 projD / Zero / console,
规则4找到 ThisBuild / Zero / Zero。
规则1选择 projD / Compile / Zero 因为它具有 subproject 轴 projD,并且 configuration 轴的优先级高于 task 轴。
接下来, Compile / scalacOptions 引用 scalacOptions.value,我们接下来需要找到 projD / Zero / Zero 的委托。 规则4找到 ThisBuild / Zero / Zero,然后解析为 List(-Ywarn-unused-import)。
您可能需要快速查找正在发生的事情。
这是可以使用 inspect 地方。
sbt:projd> inspect projD / Compile / console / scalacOptions
[info] Task: scala.collection.Seq[java.lang.String]
[info] Description:
[info] Options for the Scala compiler.
[info] Provided by:
[info] ProjectRef(uri("file:/tmp/projd/"), "projD") / Compile / scalacOptions
[info] Defined at:
[info] /tmp/projd/build.sbt:9
[info] Reverse dependencies:
[info] projD / test
[info] projD / Compile / console
[info] Delegates:
[info] projD / Compile / console / scalacOptions
[info] projD / Compile / scalacOptions
[info] projD / console / scalacOptions
[info] projD / scalacOptions
[info] ThisBuild / Compile / console / scalacOptions
[info] ThisBuild / Compile / scalacOptions
[info] ThisBuild / console / scalacOptions
[info] ThisBuild / scalacOptions
[info] Zero / Compile / console / scalacOptions
[info] Zero / Compile / scalacOptions
[info] Zero / console / scalacOptions
[info] Global / scalacOptions
请注意,“Provided by” 如何显示 projD / Compile / console / scalacOptions 提供了 projD / Compile / scalacOptions。
同样在 “Delegates” (委托),按优先顺序列出了所有可能的委托候选人!
projD scope 的所有 scope,然后列出 ThisBuild 和 Zero。
Compile scope 的 scope,然后退回到 Zero。
console / 的所有 scope,然后列出没有 task scope console / 的所有 scope。
请注意,scope 委托感觉类似于面向对象语言中的类继承,但是有区别。
在像 Scala 这样的 OO语言中,如果在 trait Shape 上有一个名为 drawShape 的 method,则即使 Shape trait 中的其他 method 使用了 drawShape,其子类也可以覆盖行为,这称为动态调度。
但是,在 sbt 中,scope 委托可以将 scope 委托给更通用的 scope,例如将 project-level 的 setting 委托给 build-level setting,但是该 build-level setting 不能引用 project-level setting。
练习题 E: 给出以下构建定义:
lazy val root = (project in file("."))
.settings(
inThisBuild(List(
organization := "com.example",
scalaVersion := "2.12.2",
version := scalaVersion.value + "_0.1.0"
)),
name := "Hello"
)
lazy val projE = (project in file("e"))
.settings(
scalaVersion := "2.11.11"
)
projE / version 返回什么?
"2.12.2_0.1.0"
"2.11.11_0.1.0"
答案是 2.12.2_0.1.0。
projE / version 委托 ThisBuild / version,
它取决于 ThisBuild / scalaVersion。
因此,build-level setting 应主要限于简单的值分配。
练习题 F: 给出以下构建定义:
ThisBuild / scalacOptions += "-D0"
scalacOptions += "-D1"
lazy val projF = (project in file("f"))
.settings(
compile / scalacOptions += "-D2",
Compile / scalacOptions += "-D3",
Compile / compile / scalacOptions += "-D4",
test := {
println("bippy" + (Compile / compile / scalacOptions).value.mkString)
}
)
projF / test 显示什么?
"bippy-D4"
"bippy-D2-D4"
"bippy-D0-D3-D4"
答案是 "bippy-D0-D3-D4"。
这是 Paul Phillips 最初创建的练习的变体。
这是所有规则的很好展示,因为 someKey += "x" 扩展为
someKey := {
val old = someKey.value
old :+ "x"
}
检索旧值将导致委托,并且由于规则5,它将转到另一个 scoped key。
让我们先摆脱 +=,然后为旧值注释委托:
ThisBuild / scalacOptions := {
// Global / scalacOptions <- Rule 4
val old = (ThisBuild / scalacOptions).value
old :+ "-D0"
}
scalacOptions := {
// ThisBuild / scalacOptions <- Rule 4
val old = scalacOptions.value
old :+ "-D1"
}
lazy val projF = (project in file("f"))
.settings(
compile / scalacOptions := {
// ThisBuild / scalacOptions <- Rules 2 and 4
val old = (compile / scalacOptions).value
old :+ "-D2"
},
Compile / scalacOptions := {
// ThisBuild / scalacOptions <- Rules 3 and 4
val old = (Compile / scalacOptions).value
old :+ "-D3"
},
Compile / compile / scalacOptions := {
// projF / Compile / scalacOptions <- Rules 1 and 2
val old = (Compile / compile / scalacOptions).value
old :+ "-D4"
},
test := {
println("bippy" + (Compile / compile / scalacOptions).value.mkString)
}
)
变成:
ThisBuild / scalacOptions := {
Nil :+ "-D0"
}
scalacOptions := {
List("-D0") :+ "-D1"
}
lazy val projF = (project in file("f"))
.settings(
compile / scalacOptions := List("-D0") :+ "-D2",
Compile / scalacOptions := List("-D0") :+ "-D3",
Compile / compile / scalacOptions := List("-D0", "-D3") :+ "-D4",
test := {
println("bippy" + (Compile / compile / scalacOptions).value.mkString)
}
)