此頁面說明作用域。它假設您已閱讀並理解先前的頁面,建置定義和任務圖。
先前我們假裝像是 `name` 的鍵對應到 sbt 的鍵值對應表中的一個項目。這是一種簡化。
事實上,每個鍵在一個以上的上下文中可以有相關聯的值,稱為 *作用域*。
一些具體範例
*給定的鍵 `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 過去使用 Ivy 來管理依賴函式庫依賴,以及來自MavenScopes。
您將在 sbt 中看到的一些組態
依預設,與編譯、封裝和執行相關的所有鍵都限定於組態,因此在每個組態中可能會以不同的方式運作。最明顯的範例是任務鍵 `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
`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
作用域)取得值。
此功能允許您在更一般的作用域中設定一次值,允許多個更具體的作用域繼承該值。我們稍後將詳細討論作用域委派。