Bzlmod による外部依存関係の管理

Bzlmod は、Bazel 5.0 で導入された新しい外部依存関係システムのコードネームで、 これは、 incremental 進的に容易に修正することができなかった古いシステムのいくつかの問題に対処するために導入されました。詳しくは、元の設計ドキュメントの問題記述セクションをご覧ください。

Bazel 5.0 では、Bzlmod がデフォルトでオンになっていません。以下を有効にするには、--experimental_enable_bzlmod フラグを指定する必要があります。フラグ名が示すように、この機能は現在試験運用中です。機能が正式にリリースされるまで、API と動作は変更される可能性があります。

Bazel モジュール

WORKSPACE ベースの古い外部依存関係システムは、リポジトリ ルールで作成されたリポジトリ(またはリポジトリ)を中心に使用しています。 リポジトリ ルールをご覧ください)。新しいシステムではリポジトリも重要な概念ですが、モジュールは依存関係の中心的な単位です。

モジュールは、基本的に、複数のバージョンを持つことのできる Bazel プロジェクトです。各バージョンは、依存する他のモジュールに関するメタデータを公開します。これは、Maven アーティファクト、npm パッケージ、Cargo クレートなど、他の依存関係管理システムの使い慣れたコンセプトと類似しています。 、Go モジュールなど

モジュールは、WORKSPACE の特定の URL ではなく、nameversion のペアを使用して依存関係を指定します。依存関係は 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 ファイルとは異なり、推移的な依存関係を指定する必要はありません。代わりに、直接的な依存関係のみを指定する必要があり、依存関係の MODULE.bazel ファイルは、推移的な依存関係を自動的に検出するように処理されます。

MODULE.bazel ファイルは、なんらかの制御フローをサポートしていないため、BUILD ファイルに似ています。また、load ステートメントを禁止します。MODULE.bazel ファイルがサポートするディレクティブは次のとおりです。

バージョンの形式

Bazel のエコシステムは多様で、プロジェクトはさまざまなバージョニング スキームを使用しています。中でも最も注目されているのは SemVer ですが、Abseil のような異なるスキームを使用している有名なプロジェクトもあります。日付ベース(例: 20210324.2

このため、Bzlmod はよりくつろげるバージョンの SemVer 仕様を採用しています。特に、バージョンの「release」部分で数字のシーケンスを任意に指定できるようになっています(SemVer で規定されている 3 桁だけではありません): MAJOR.MINOR.PATCH など)。また、メジャー、マイナー、パッチのバージョン増加のセマンティクスが適用されません。(ただし、下位互換性を示す方法については、互換性レベルをご覧ください)。 SemVer 仕様の他の部分(プレリリース版を示すハイフンなど)は変更されません。

バージョンの解決

ダイヤモンド依存関係の問題は、バージョニングされた依存関係管理空間の要です。次の依存関係グラフがあるとします。

       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 1.2 以降が存在する場合でもそれは選択されません。これには、バージョンの選択が「高 high 実度」と「再現可能」であるという利点があります。

バージョン解決は、レジストリではなく、マシンのローカルに実行されます。

互換性レベル

MVS では、下位互換性のないバージョンのモジュールを別のモジュールとして扱うだけなので、下位互換性に関する想定は実現可能です。SemVer の観点から見ると、A 1.x と A 2.x は別々のモジュールと見なされ、解決された依存関係グラフに共存できます。これは、Go のパッケージ パスにメジャー バージョンがエンコードされているため、コンパイル時やリンク時の競合がないことにあります。

Bazel の場合、このような保証はありません。したがって、下位互換性のないバージョンを検出するために、「メジャー バージョン」番号を示す方法が必要です。この番号は互換性レベルと呼ばれ、モジュール バージョンごとに module() ディレクティブで指定します。 この情報があれば、同じモジュールの異なる互換性バージョンがバージョンの解決された依存関係グラフに検出されると、エラーをスローできます。

リポジトリ名

Bazel では、すべての外部依存関係にリポジトリ名があります。異なるリポジトリ名(たとえば、@io_bazel_skylib@bazel_skylib の両方が Bazel skylib を意味するなど)で同じ依存関係が使用されていることがあります。また、同じ依存関係が使用されている場合もあります。リポジトリ名は、プロジェクトごとに異なる依存関係に使用される場合があります。

Bzlmod では、Bazel モジュールとモジュール拡張機能によってリポジトリを生成できます。リポジトリ名の競合を解決するため、Google では新しいシステムにリポジトリ マッピング メカニズムを導入しています。ここでは、2 つの重要な概念について説明します。

  • 正規リポジトリ名: 各リポジトリのグローバルに一意のリポジトリ名。これは、リポジトリが存在するディレクトリ名です。
    これは、次のように構成されています(警告: 正規名の形式は、依存すべき 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
  • ローカル リポジトリ名: リポジトリ内の BUILD ファイルと .bzl ファイルで使用されるリポジトリ名。同じ依存関係で、リポジトリごとに異なるローカル名を使用できます。
    次のように算出されます。

    • Bazel モジュール リポジトリの場合: module_name(デフォルト)。または、bazel_deprepo_name 属性で指定された名前。
    • モジュール拡張リポジトリの場合: use_repo で導入されたリポジトリ名。

すべてのリポジトリには、直接的な依存関係のリポジトリ マッピング辞書があります。これは、ローカル リポジトリ名から正規リポジトリ名へのマップですリポジトリ マッピングを使用して、ラベルを作成するときにリポジトリ名を解決します。正規リポジトリ名の競合はなく、MODULE.bazel ファイルを解析することでローカル リポジトリ名の使用を検出できるため、競合の検出と解決は簡単に行えます。その他の依存関係

strict の依存関係

新しい依存関係の仕様形式を使用すると、より厳密なチェックを実行できます。特に、モジュールは直接の依存関係から作成されたリポジトリのみを使用するようになりました。これにより、推移的な依存関係グラフ内の何かが変化した場合の、偶発的およびデバッグが困難な破損を防止できます。

厳格な依存関係は、リポジトリ マッピングに基づいて実装されます。基本的に、各リポジトリのリポジトリ マッピングには、そのすべての直接的な依存関係が含まれ、他のリポジトリは表示されません。各リポジトリの目に見える依存関係は、次のように決定されます。

  • Bazel モジュール リポジトリでは、MODULE.bazel ファイルに導入されたすべてのリポジトリを bazel_depuse_repo で確認できます。
  • モジュール拡張機能リポジトリは、拡張機能を提供するモジュールのすべての依存関係と、同じモジュール拡張で生成された他のすべてのリポジトリを参照できます。

レジストリ

Bzlmod は、Bazel レジストリから依存関係の情報をリクエストすることで、その依存関係を検出します。Bazel レジストリは、単に Bazel モジュールのデータベースです。サポートされているレジストリは、インデックス レジストリのみです。インデックス レジストリは、特定の形式に沿ったローカル ディレクトリまたは静的 HTTP サーバーです。将来的には、プロジェクトのソースと履歴を含む Git リポジトリであるシングル モジュール レジストリのサポートを追加する予定です。

インデックス レジストリ

インデックス レジストリはローカル ディレクトリまたは静的 HTTP サーバーであり、モジュール、ホームページ、メンテナンス担当者、各バージョンの MODULE.bazel ファイル、各ソースの取得方法などのリストに関する情報が含まれます version。特に、ソース アーカイブそのものを提供する必要はありません

インデックス レジストリは、次の形式に従う必要があります。

  • /bazel_registry.json: レジストリのメタデータを含む JSON ファイル。現在のところ、鍵は mirrors 1 つだけで、ソース アーカイブに使用するミラーのリストを指定します。
  • /modules: このレジストリの各モジュールのサブディレクトリを含むディレクトリ。
  • /modules/$MODULE: このモジュールの各バージョンのサブディレクトリを含むディレクトリと、次のファイル。
    • metadata.json: モジュールに関する情報を含む JSON ファイル。次のフィールドがあります。
      • homepage: プロジェクトのホームページの URL。
      • maintainers: JSON オブジェクトのリスト。各オブジェクトは、レジストリ内のモジュールの管理者情報に対応しています。これは、プロジェクトの作成者と同じであるとは限りません。
      • versions: このレジストリ内で検出されるこのモジュールのすべてのバージョンのリスト。
      • yanked_versions: このモジュールの「ヤンク済み」バージョンのリスト。これは現時点では何も行われませんが、将来的には、ヤンクしたバージョンをスキップしたり、エラーを出したりする可能性があります。
  • /modules/$MODULE/$VERSION: 次のファイルを含むディレクトリ。
    • MODULE.bazel: このモジュール バージョンの MODULE.bazel ファイル。
    • source.json: このモジュール バージョンのソースの取得方法に関する情報を含む JSON ファイル。次のフィールドが含まれます。
      • url: ソース アーカイブの URL。
      • integrity: アーカイブのサブリソースの整合性チェックサム。
      • strip_prefix: ソース アーカイブの抽出時に削除するディレクトリ接頭辞。
      • patches: 文字列のリスト。各文字列には、抽出されたアーカイブに適用するパッチファイルの名前が付けられます。パッチファイルは、/modules/$MODULE/$VERSION/patches ディレクトリにあります。
      • patch_strip: Unix パッチの --strip 引数と同じです。
    • patches/: パッチファイルを含むオプションのディレクトリ。

Bazel セントラル レジストリ

Bazel セントラル レジストリ(BCR)は、registry.bazel.build にあるインデックス レジストリです。その内容は GitHub リポジトリ bazelbuild/bazel-central-registry によってサポートされています。

BCR は Bazel コミュニティによって管理されています。コントリビューターは pull リクエストを送信できます。Bazel セントラル レジストリのポリシーと手順をご覧ください。

BCR では、通常のインデックス レジストリの形式に加えて、モジュール バージョン(/modules/$MODULE/$VERSION/presubmit.yml)ごとに presubmit.yml ファイルが必要です。このファイルは、このモジュール バージョンの有効性をサニティ チェックに使用できる重要なビルド ターゲットとテスト ターゲットをいくつか指定し、BCR の CI パイプラインが BCR 内のモジュール間の相互運用性を確保するために使用します。

レジストリを選択する

繰り返し可能な Bazel フラグ --registry を使用すると、モジュールをリクエストするためのレジストリのリストを指定できます。これにより、サードパーティ レジストリまたは内部レジストリから依存関係を取得するようにプロジェクトを設定できます。以前のレジストリが優先されます。便宜上、プロジェクトの .bazelrc ファイルに --registry フラグのリストを含めることができます。

モジュール拡張

Module Extensions を使用すると、依存関係グラフ全体でモジュールから入力データを読み取り、依存関係を解決するために必要なロジックを実行し、最後にリポジトリルールを呼び出してリポジトリを作成できます。現在の WORKSPACE マクロと機能は似ていますが、モジュールや推移的な依存関係の世界に適しています。

モジュールの拡張機能は、リポジトリ ルールや WORKSPACE マクロと同様に、.bzl ファイルで定義します。直接呼び出されません。代わりに、各モジュールは拡張機能が読み込むタグと呼ばれるデータを指定できます。モジュール バージョンの解決後、モジュール拡張機能が実行されます。各拡張機能は、モジュールの解決後(ビルドが実際に行われる前)に 1 回実行され、依存関係グラフ全体でそれに属するすべてのタグを読み取ります。

          [ 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 を使用しているが、C が原因で D 1.4 にアップグレードしたとします)、拡張機能「maven」が実行され、すべての maven.* タグを読み取ります。その情報を使用して、作成するリポジトリを決定します。「cargo」拡張機能についても同様です。

拡張機能の使用状況

拡張機能は Bazel モジュール自体でホストされているため、モジュール内で拡張機能を使用するには、まずモジュールに bazel_dep を追加してから use_extension 組み込み関数を使用してスコープ設定を行う必要があります。次の例は、rules_jvm_external モジュールで定義された架空の「maven」拡張機能を使用する MODULE.bazel ファイルからのスニペットです。

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

拡張機能をスコープに設定したら、ドット構文を使用してタグを指定できます。タグは、対応するタグクラスで定義されたスキーマに準拠している必要があります(下記の拡張機能の定義を参照)。maven.dep タグと maven.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 ディレクティブを使用して宣言します。これは、厳密な依存関係の条件を満たし、ローカル リポジトリ名の競合を避けるためです。

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_classes があり、それぞれの属性にはいくつかの属性があります。タグクラスは、この拡張機能で使用されるタグのスキーマを定義します。引き続き、以下の架空の「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.dep タグと maven.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 ユーティリティ Coursier を呼び出して、Maven に接続し、解決を行います。最後に、解決結果を使用して、架空の maven_single_jar リポルールを使用していくつかのリポジトリを作成します。