1. 作用域

作用域 

此頁面說明作用域。它假設您已閱讀並理解先前的頁面,建置定義任務圖

關於鍵的完整說明 

先前我們假裝像是 `name` 的鍵對應到 sbt 的鍵值對應表中的一個項目。這是一種簡化。

事實上,每個鍵在一個以上的上下文中可以有相關聯的值,稱為 *作用域*。

一些具體範例

  • 如果您在建置定義中有多個專案(也稱為子專案),則每個專案中的鍵可以有不同的值。
  • 如果您想以不同的方式編譯,則 `compile` 鍵對於您的主要來源和測試來源可能有不同的值。
  • `packageOptions` 鍵(包含建立 jar 套件的選項)在封裝類別檔案 (`packageBin`) 或封裝原始程式碼 (`packageSrc`) 時可能有不同的值。

*給定的鍵 `name` 沒有單一值*,因為該值可能會因作用域而異。

但是,給定的 *已限定範圍的* 鍵只有一個值。

如果您考慮 sbt 處理設定列表以產生描述專案的鍵值對應表,如先前討論的那樣,則該鍵值對應表中的鍵是 *已限定範圍的* 鍵。建置定義(例如在 `build.sbt` 中)中定義的每個設定也會應用於已限定範圍的鍵。

通常,作用域是隱含的或具有預設值,但如果預設值錯誤,您需要在 `build.sbt` 中提及所需的作用域。

作用域軸 

*作用域軸* 是一種與 `Option[A]` 類似的類型建構子,用於在作用域中形成元件。

有三個作用域軸

  • 子專案軸
  • 依賴組態軸
  • 任務軸

如果您不熟悉 *軸* 的概念,我們可以將 RGB 色立方體作為範例

color cube

在 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` 定義主要建置 (`src/main/scala`)。
  • `Test` 定義如何建置測試 (`src/test/scala`)。
  • `Runtime` 定義 `run` 任務的類別路徑。

依預設,與編譯、封裝和執行相關的所有鍵都限定於組態,因此在每個組態中可能會以不同的方式運作。最明顯的範例是任務鍵 `compile`、`package` 和 `run`;但所有*影響*這些鍵的鍵(例如 `sourceDirectories` 或 `scalacOptions` 或 `fullClasspath`)也限定於組態。

關於組態的另一件事要注意的是,它可以擴充其他組態。下圖顯示最常見的組態之間的擴充關係。

dependency configurations

`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 Shell 中,sbt 會顯示(並剖析)像這樣的已限定範圍的鍵

ref / Config / intask / key
  • `ref` 識別子專案軸。它可以是 ``、`ProjectRef(uri("file:..."), "id")` 或表示「整個建置」作用域的 `ThisBuild`。
  • `Config` 使用大寫的 Scala 識別碼識別組態軸。
  • `intask` 識別任務軸。
  • `key` 識別正在限定範圍的鍵。

`Zero` 可以出現在每個軸中。

如果您省略了已限定範圍的鍵的一部分,則將按如下方式推斷

  • 如果您省略了專案,則將使用目前專案。
  • 如果您省略組態或任務,則將自動偵測鍵相關的組態。

更多詳細資訊,請參閱與組態系統互動

在 sbt shell 中作用域鍵符號的範例 

  • 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 任務的作用域限定為 CompileTest 組態,並且不存在於這些作用域之外。

若要變更與 compile 鍵相關聯的值,您需要寫入 Compile / compileTest / compile。使用純粹的 compile 會定義一個作用域為目前專案的新編譯任務,而不是覆寫作用域為組態的標準編譯任務。

如果您收到類似「參考未定義的設定」的錯誤,通常是因為您未能指定作用域,或者您指定了錯誤的作用域。您使用的鍵可能在其他作用域中定義。sbt 會嘗試在錯誤訊息中建議您可能的意思;請尋找「您是指 Compile / compile 嗎?」

可以將其視為名稱只是鍵的一部分。實際上,所有鍵都由名稱和作用域組成(其中作用域有三個軸)。換句話說,整個表達式 Compile / packageBin / packageOptions 是一個鍵名稱。簡單的 packageOptions 也是一個鍵名稱,但不同(對於沒有斜線的鍵,會隱式假設一個作用域:當前專案、Zero 組態、Zero 任務)。

建置層級設定 

一種跨子專案分解通用設定的高階技術是定義作用域限定為 ThisBuild 的設定。

如果找不到作用域限定於特定子專案的鍵,sbt 會在 ThisBuild 中尋找它作為備用。使用此機制,我們可以為常用的鍵(例如 versionscalaVersionorganization)定義建置層級的預設設定。

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 /

由於稍後我們將介紹的作用域委派的本質,建置層級設定應僅設定為純值或來自 GlobalThisBuild 作用域的設定。

作用域委派 

如果已作用域的鍵在其作用域中沒有相關聯的值,則可能未定義。

對於每個作用域軸,sbt 都有一個由其他作用域值組成的備用搜尋路徑。通常,如果某個鍵在更具體的作用域中沒有相關聯的值,sbt 會嘗試從更一般的作用域(例如 ThisBuild 作用域)取得值。

此功能允許您在更一般的作用域中設定一次值,允許多個更具體的作用域繼承該值。我們稍後將詳細討論作用域委派