使用 Bzlmod 管理外部依附元件

回報問題 查看原始碼 。 。 。 。 夜間。 。 7.3 。 。 7.2 。 。 7.1 。 。 7.0 。 。 6.5

Bzlmod 是新外部依附元件系統的代號 可在 Bazel 5.0 中導入該領域說明瞭 無法漸進式修正的舊系統請參閱 原始設計文件的問題陳述部分 ,掌握更多詳細資訊。

在 Bazel 5.0 中,Bzlmod 預設為不開啟。旗幟 請指定 --experimental_enable_bzlmod,下列項目才能取得 效果。顧名思義,這項功能目前為實驗功能。 在正式推出功能前,API 和行為可能會有所變動。

如要將專案遷移至 Bzlmod,請按照 Bzlmod 遷移指南的說明操作。 您也可以在 examples 存放區中找到 Bzlmod 的用法範例。

Bazel 模組

舊版 WORKSPACE 外部依附元件系統的中心位置是 存放區 (或「存放區」),透過存放區規則 (或存放區規則) 建立的。 存放區仍是新系統的重要概念,模組是 依附元件的核心單位

基本上,「模組」就是可以有多個版本的 Bazel 專案。每個模組 ,其中會發布依附的其他模組相關中繼資料。這是 類似於其他依附元件管理系統中的熟悉概念:Maven Artifact、npm 套件、Cargo crate、Go 模組

模組會直接使用 nameversion 組合指定依附元件。 而不是 WORKSPACE 中的特定網址。然後查詢依附元件 Bazel 註冊資料庫;根據預設 Bazel Central Registry。在工作區中 會轉換成存放區

MODULE.bazel

每個模組版本都有一個 MODULE.bazel 檔案,宣告其 依附元件和其他中繼資料基本範例如下:

module(
    name = "my-module",
    version = "1.0",
)

bazel_dep(name = "rules_cc", version = "0.0.1")
bazel_dep(name = "protobuf", version = "3.19.0")

MODULE.bazel 檔案應位於工作區目錄的根目錄 (位於 WORKSPACE 檔案旁)。與 WORKSPACE 檔案不同的是,您不需要 指定遞移依附元件;而是只要指定 「direct」依附元件和依附元件的 MODULE.bazel 檔案 ,自動找出遞移依附元件。

MODULE.bazel 檔案和 BUILD 檔案十分相似,因為不支援所有 控制流程的形式也會禁止 load 陳述式。指令 支援的 MODULE.bazel 檔案如下:

版本格式

Bazel 的生態系統五花八門,而專案會使用不同的版本管理架構。 目前為止最受歡迎的是 SemVer 也經常使用不同配置來知名的專案 Abseil, 版本都是以日期表示,例如 20210324.2)。

因此,Bzlmod 採用較寬鬆的 SemVer 規格版本。 差異包括:

  • SemVer 闡述了「release」部分版本必須包含 3 個 區隔:MAJOR.MINOR.PATCH。在 Bazel 中,這項要求會放寬,因此 ,任意數量的區隔都可使用。
  • 在 SemVer 中,「release」中的每個區段部分只能由數字組成。 在 Bazel 中,這會導致字母和比較 語意符合「ID」「預先發布版」部分。
  • 另外,還有大、小和修補程式版本的語意 沒有強制執行的指令(但請參閱相容性等級,瞭解 以及有關回溯相容性的詳細說明)。

任何有效的 SemVer 版本都是有效的 Bazel 模組版本。此外,兩個 如果 SemVer 版本 ab 處於相同訴訟保留,就會比較 a < b

版本解析度

鑽石依附元件問題是版本化依附元件中的基石 管理空間假設您包含下列依附元件圖表:

       A 1.0
      /     \
   B 1.0    C 1.1
     |        |
   D 1.0    D 1.1

應使用哪個版本的 D?為解決這個問題,Bzlmod 使用 選擇最小版本 Go 模組系統導入的 (MVS) 演算法。MVS 會假設 模組的版本具有回溯相容性,因此只會從 為任何相依版本 (在本範例中為 D 1.1) 指定的版本。將其稱為「最小化」 因為這裡的 D.1.1 是最小版本,能滿足我們的需求。 即使有 D.2 以上版本,也不會選取還有一項額外福利 選擇「高保真」且「可重現」

版本解析作業是在電腦本機執行,而非註冊資料庫。

相容性等級

請注意,MVS 對於回溯相容性的假設是可行的,因為 只是將回溯不相容的模組視為個別模組。 就 SemVer 而言,1.x 和 A 2.x 被視為不同模組 且可以存在於已解析的依附元件圖表中。這就是為什麼 主要版本已經過編碼,主要版本 Go,確保編譯時間或連結時間不會發生衝突。

在 Bazel 中,我們沒有這類保證。因此,我們需要一個方式 版本」數字,以便偵測回溯不相容的版本。這個號碼 稱為相容性等級,由 其 module() 指令。掌握這些資訊後 當我們偵測到同一個模組的版本不同且相容性時 所對應的依附元件圖表

存放區名稱

在 Bazel 中,每個外部依附元件都有存放區名稱。有時相同 依附元件可以透過不同的存放區名稱使用 (例如: @io_bazel_skylib@bazel_skylib 平均值 Bazel skylib) 或 存放區名稱可用於不同專案中的不同依附元件。

在 Bzlmod 中,存放區可由 Bazel 模組產生 模組擴充功能。為解決存放區名稱衝突 我們採用了存放區對應 建立新系統的機制以下提供兩個重要概念:

  • 標準存放區名稱:各個存放區的全域不重複存放區名稱 Cloud Storage 也提供目錄同步處理功能這會是存放區所在的目錄名稱。
    其建構方式如下 (警告:標準名稱格式為 而非應仰賴的 API,且隨時可能變更):

    • 適用於 Bazel 模組存放區:module_name~version
      (範例@bazel_skylib~1.0.3)
    • 適用於模組擴充功能存放區:module_name~version~extension_name~repo_name
      (範例@rules_cc~0.0.1~cc_configure~local_config_cc)
  • 「Apparent repository name」:要在 BUILD 和 存放區中的 .bzl 檔案相同的依附元件可能有不同的明顯 多個不同存放區中的不同名稱
    判定方式如下:

    • 適用於 Bazel 模組存放區:module_name,來源: 或是 repo_name 屬性在 bazel_dep
    • 模組擴充功能存放區:透過 use_repo

每個存放區都有直接依附元件的存放區對應字典 從顯目存放區名稱到標準存放區名稱的對應。 建構容器時,我們會使用存放區對應來解析存放區名稱 標籤。請注意,標準存放區名稱沒有衝突, 藉由剖析 MODULE.bazel,即可找出明顯的存放區名稱使用情況 因此,您可以輕鬆及早發現衝突並加以解決 和其他依附元件

嚴格依附元件

新的依附元件規格格式可讓我們執行更嚴格的檢查。於 具體來說,我們現在強制讓模組只能使用從其建立的存放區 直接依附元件這有助於避免意外和難以偵錯的故障情形 當遞移依附關係圖中的某個項目變更時。

嚴格依附元件的實作 存放區對應。基本上 每個存放區的存放區對應都包含其所有直接依附元件, 其他存放區不會顯示每個存放區可見的依附元件 判斷方式如下:

  • Bazel 模組存放區可查看所有在 MODULE.bazel 檔案中導入的存放區 透過 bazel_depuse_repo
  • 模組擴充功能存放區,能查看模組中所有可用的依附元件 提供擴充功能,以及由同一模組產生的所有其他存放區 。

登錄檔

Bzlmod 透過 Bazel 要求其資訊來探索依附元件 註冊資料庫。Bazel 註冊資料庫只是 Bazel 模組的資料庫,只有 是一種索引註冊資料庫, 本機目錄或靜態 HTTP 伺服器 (遵循特定格式)。在 未來,我們打算新增單一模組註冊資料庫支援功能, 含有專案原始碼和記錄的 Git 存放區。

索引註冊資料庫

索引註冊資料庫是本機目錄或含有 包括模組清單的首頁、維護人員、 每個版本的 MODULE.bazel 檔案,以及如何擷取各個版本的原始碼 版本。值得注意的是,「不需要」提供來源封存。

索引註冊資料庫必須採用以下格式:

  • /bazel_registry.json:包含登錄檔中繼資料的 JSON 檔案,例如:
    • mirrors,指定用於來源封存的鏡像清單。
    • module_base_path,指定含有 source.json 檔案中的 local_repository 類型。
  • /modules:包含子目錄中每個模組的子目錄 註冊資料庫
  • /modules/$MODULE:包含每個版本的子目錄的目錄 詳細內容,以及下列檔案:
    • metadata.json:包含模組相關資訊的 JSON 檔案。 包含下列欄位:
      • homepage:專案的首頁網址。
      • maintainers:JSON 物件清單,每個物件都對應至 登錄檔中模組維護人員的資訊。 請注意,此指示不一定等於文件的作者 專案。
      • versions:可顯示此模組的所有版本 這個登錄檔
      • yanked_versions:此模組的「yanked」版本的清單。這個 目前免人工管理,但日後就會 略過或產生錯誤
  • /modules/$MODULE/$VERSION:包含下列檔案的目錄:
    • MODULE.bazel:此模組版本的 MODULE.bazel 檔案。
    • source.json:JSON 檔案,內含如何擷取 請務必提供此模組版本的原始碼
      • 預設類型為「封存」包含下列欄位:
        • url:來源封存網址。
        • integrity子資源完整性 檢查碼的總和檢查碼
        • strip_prefix:擷取 原始碼封存檔
        • patches:字串清單,每個字串都為修補檔案命名 並套用至擷取的封存檔您可以在 /modules/$MODULE/$VERSION/patches 目錄。
        • patch_strip:與 Unix 修補程式的 --strip 引數相同。
      • 您可以改為將類型變更為使用以下欄位的本機路徑:
        • typelocal_path
        • path:存放區的本機路徑,計算方式如下:
          • 如果路徑是絕對路徑,則會依原樣使用。
          • 如果路徑是相對路徑,且 module_base_path 是絕對路徑, 路徑已解析為 <module_base_path>/<path>
          • 如果路徑和 module_base_path 都是相對路徑,則路徑為 解析為 <registry_path>/<module_base_path>/<path>。 註冊資料庫必須由本機託管,並由 --registry=file://<registry_path> 使用。 否則 Bazel 會擲回錯誤。
    • patches/:包含修補檔案的選用目錄,只有在 source.json 具有「封存」時才使用類型。

Bazel Central Registry

Bazel Central Registry (BCR) 是位於 bcr.bazel.build.其內容 由 GitHub 存放區支援 bazelbuild/bazel-central-registry

BCR 由 Bazel 社群維護。歡迎捐款者 提取要求詳情請見 Bazel Central Registry 政策和程序

除了遵循一般索引註冊資料庫的格式之外,BCR 也規定 每個模組版本都有一個 presubmit.yml 檔案 (/modules/$MODULE/$VERSION/presubmit.yml)。這個檔案指出一些 建構和測試目標,用於對此次評估的有效性 模組版本,並由 BCR 的 CI 管道使用,以確保互通性 繫結至 BCR 的各個模組間

選取登錄檔

可重複的 Bazel 標記 --registry 可用來指定 向其要求模組的註冊資料庫,因此您可以設定專案 安裝自第三方或內部註冊資料庫早期的註冊資料庫 優先順序。為了方便起見,您可以在--registry .bazelrc 檔案。

模組擴充功能

模組擴充功能可讓您讀取輸入資料,藉此擴充模組系統 從依附元件圖的模組中學習,並執行必要的邏輯來解決 最後是呼叫存放區規則來建立存放區。兩者相似 用於今天的 WORKSPACE 巨集,但更適用於 模組和遞移依附元件

模組擴充功能是在 .bzl 檔案中定義,就像存放區規則或 WORKSPACE 巨集。不會直接叫用;而每個單元 指定名為「標記」的資料,讓擴充功能讀取。接著是 版本解析完畢後,就會執行模組擴充功能。系統已執行每項擴充功能 模組解析後一次 (直到任何版本實際發生之前)。 在整個依附元件圖表中,讀取屬於該物件的所有標記。

          [ A 1.1                ]
          [   * maven.dep(X 2.1) ]
          [   * maven.pom(...)   ]
              /              \
   bazel_dep /                \ bazel_dep
            /                  \
[ B 1.2                ]     [ C 1.0                ]
[   * maven.dep(X 1.2) ]     [   * maven.dep(X 2.1) ]
[   * maven.dep(Y 1.3) ]     [   * cargo.dep(P 1.1) ]
            \                  /
   bazel_dep \                / bazel_dep
              \              /
          [ D 1.4                ]
          [   * maven.dep(Z 1.4) ]
          [   * cargo.dep(Q 1.1) ]

在上方的依附元件圖表範例中,A 1.1B 1.2 等是 Bazel 模組。 您可將每個項目視為 MODULE.bazel 檔案。每個模組皆可指定 模組擴充功能的標記;這裡有些是針對「maven」擴充功能指定 有些則指定為「cargo」此依附元件圖完成之後 (適用於 舉例來說,假設 B 1.2 其實在 D 1.3bazel_dep,但升級為 D 1.4,因為 C),擴充功能「maven」該函式可讀取所有 maven.* 標記,使用該資訊決定要建立哪些存放區。 與「cargo」相當類似。

擴充功能使用情形

擴充功能是由 Bazel 模組自行代管,因此如要在 您必須先在該模組中新增 bazel_dep,然後呼叫 內建 use_extension 來納入範圍請參考以下範例 使用假想「maven」的 MODULE.bazel 檔案您可以使用 rules_jvm_external 模組:

bazel_dep(name = "rules_jvm_external", version = "1.0")
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")

讓擴充功能進入涵蓋範圍後, 並指定其標記請注意,標記必須遵循 對應的標記類別 (請參閱擴充功能定義) )。以下範例是指定一些 maven.depmaven.pom 標記。

maven.dep(coord="org.junit:junit:3.0")
maven.dep(coord="com.google.guava:guava:1.2")
maven.pom(pom_xml="//:pom.xml")

如果擴充功能產生了要在模組中使用的存放區,請使用 要宣告的 use_repo 指令 具體做法是指示 Kubernetes 建立並維護 一或多個代表這些 Pod 的物件以便滿足嚴格的 deps 條件,避免使用本機存放區名稱 衝突。

use_repo(
    maven,
    "org_junit_junit",
    guava="com_google_guava_guava",
)

擴充功能產生的存放區屬於其 API 的一部分,因此您所在的標記 但請注意,「maven」就能產生 名為「org_junit_junit」和「com_google_guava_guava」的存放區。取代為 use_repo,您可以視需要在模組範圍內重新命名,例如: 「guava」此處。

擴充功能定義

模組擴充功能的定義方式與存放區規則類似, module_extension 函式。 兩者都有實作函式雖然存放區規則有許多 模組擴充功能有許多屬性 tag_class,每個 屬性的數量標記類別會定義此項目所用標記的結構定義 。延續上述假設,假設「maven」副檔名:

# @rules_jvm_external//:extensions.bzl
maven_dep = tag_class(attrs = {"coord": attr.string()})
maven_pom = tag_class(attrs = {"pom_xml": attr.label()})
maven = module_extension(
    implementation=_maven_impl,
    tag_classes={"dep": maven_dep, "pom": maven_pom},
)

這些宣告會明確指出 maven.depmaven.pom 標記可以 並透過上述定義的屬性結構定義進行指定。

實作函式與 WORKSPACE 巨集類似,差別在於 取得 module_ctx 物件 依附元件圖表和所有相關標記的存取權。實作方式 函式應呼叫存放區規則來產生存放區:

# @rules_jvm_external//:extensions.bzl
load("//:repo_rules.bzl", "maven_single_jar")
def _maven_impl(ctx):
  coords = []
  for mod in ctx.modules:
    coords += [dep.coord for dep in mod.tags.dep]
  output = ctx.execute(["coursier", "resolve", coords])  # hypothetical call
  repo_attrs = process_coursier(output)
  [maven_single_jar(**attrs) for attrs in repo_attrs]

在上例中,我們逐一介紹依附元件圖表中的所有模組 (ctx.modules),每個表格都是 包含 tags 欄位的 bazel_module 物件 會顯示模組中的所有 maven.* 標記。然後叫用 CLI 公用程式 與 Maven 聯絡並執行解決方案。最後,使用 使用假設 maven_single_jar 建立多個存放區的結果 儲存空間規則