本頁說明範圍委派。它假設您已閱讀並理解先前的頁面,建置定義和範圍。
現在我們已經涵蓋了範圍的所有細節,我們可以詳細解釋 .value
查找。如果您是第一次閱讀此頁面,可以跳過此部分。
總結一下我們到目前為止所學到的內容
Zero
。ThisBuild
。Test
擴展 Runtime
,而 Runtime
擴展 Compile
組態。${current subproject} / Zero / Zero
的範圍內。/
運算子來限定索引鍵的範圍。現在假設我們有以下建置定義
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
的設定主體內,宣告了對範圍索引鍵 Test / bar
的相依性。但是,儘管 projX
中未定義 Test / bar
,sbt 仍然能夠將 Test / bar
解析為另一個範圍索引鍵,導致 foo
初始化為 2
。
sbt 有一個明確定義的回溯搜尋路徑,稱為範圍委派。此功能可讓您在更一般的範圍中設定值一次,從而允許多個更具體的範圍繼承該值。
以下是範圍委派的規則
Zero
,它是範圍的非任務範圍版本。Zero
(與未設定範圍的組態軸相同)。ThisBuild
,然後是 Zero
。我們將在本頁的其餘部分中查看每個規則。
換句話說,給定兩個範圍候選者,如果其中一個在子專案軸上具有更具體的值,則無論組態或任務範圍如何,它都將始終獲勝。同樣地,如果子專案相同,則具有更具體組態值的值將始終獲勝,無論任務範圍如何。我們將看到更多規則來定義更具體。
Zero
,它是範圍的非任務範圍版本。這裡我們有一個具體的規則,說明 sbt 如何針對給定的索引鍵產生委派範圍。請記住,我們嘗試顯示給定任意 (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
會自動限定在 projA / Zero / Zero
的範圍內,因此 packageBin / scalaVersion
會變成 projA / Zero / packageBin / scalaVersion
。該特定的範圍索引鍵未定義。透過使用規則 2,sbt 會將任務軸替換為 Zero
,即 projA / Zero / Zero
(或 projA / scalaVersion
)。該範圍索引鍵定義為 "2.11.11"
。
Zero
(與未設定範圍的組態軸相同)。該範例是我們先前看到的 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
)
如果我們再次寫出完整範圍,則它是 projX / Test / Zero
。另請回想一下,Test
擴展 Runtime
,而 Runtime
擴展 Compile
。
Test / bar
未定義,但是由於規則 3,sbt 會尋找範圍限定在 projX / Test / Zero
、projX / Runtime / Zero
,然後是 projX / Compile / Zero
中的 bar
。找到最後一個,即 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
的 organization
,它在 projB
中定義為 "org.tempuri"
。它的優先順序高於建置層級設定 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
。範圍限定在 projC / Zero / packageBin
的 scalaVersion
未定義。規則 2 找到 projC / Zero / Zero
。規則 4 找到 ThisBuild / Zero / packageBin
。在這種情況下,規則 1 指出,子專案軸上更具體的值獲勝,即定義為 "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
,因為它具有子專案軸 projD
,且組態軸的優先順序高於任務軸。
接下來,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
請注意,「由...提供」顯示 projD / Compile / console / scalacOptions
是由 projD / Compile / scalacOptions
提供。此外,在「委派」下,列出了所有可能的委派候選者,並依優先順序排列!
projD
為範圍的範圍,然後是 ThisBuild
和 Zero
。Compile
為範圍的範圍,然後回退到 Zero
。console /
和沒有任務範圍的範圍。請注意,範圍委派感覺類似於物件導向語言中的類別繼承,但存在差異。在像 Scala 這樣的物件導向語言中,如果 trait Shape
上有一個名為 drawShape
的方法,即使 drawShape
被 Shape
trait 中的其他方法使用,其子類別也可以覆寫該行為,這稱為動態調度。
然而,在 sbt 中,範圍委派可以將範圍委派給更通用的範圍,例如將專案級別的設定委派給建置級別的設定,但該建置級別的設定不能參照專案級別的設定。
練習 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
。由於這個原因,建置層級設定應該主要限於簡單的值指派。
練習 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,它將轉到另一個範圍鍵。讓我們首先擺脫 +=
,並註解舊值的委派
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)
}
)