Bazel モジュールは、複数のバージョンを持つことができる Bazel プロジェクトで、それぞれが依存する他のモジュールのメタデータを公開します。これは、Maven アーティファクト、npm パッケージ、Go モジュール、Cargo crate など、他の依存関係管理システムにおける一般的なコンセプトに似ています。
モジュールのリポジトリ ルート(WORKSPACE
ファイルの横)に 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
ファイルで利用可能なディレクティブの全リストをご覧ください。
モジュール解決を実行するため、Bazel は最初にルート モジュールの MODULE.bazel
ファイルを読み取ってから、依存関係グラフ全体を検出するまで Bazel レジストリに依存関係の MODULE.bazel
ファイルを繰り返しリクエストします。
デフォルトでは、Bazel は使用する各モジュールのバージョンを 1 つ選択します。Bazel は、リポジトリを持つ各モジュールを表し、レジストリを再度参照して、各リポジトリの定義方法を確認します。
バージョンの形式
Bazel には多様なエコシステムがあり、プロジェクトではさまざまなバージョニング スキームが使用されます。圧倒的に人気のあるのは SemVer ですが、その他にも、バージョンが日付ベースの Abseil(20210324.2
など)のような、さまざまなスキームを使用する優れたプロジェクトがあります。
このため、Bzlmod では、より緩和された SemVer 仕様を採用しています。次の点が異なります。
- SemVer では、バージョンの「release」部分が 3 つのセグメント
MAJOR.MINOR.PATCH
で構成しなければならないことを規定しています。Bazel ではこの要件が緩和され、セグメントの数を問いません。 - SemVer では、「release」部分の各セグメントは数字のみにする必要があります。 Bazel では、文字も使用できるように拡張されています。また、比較セマンティクスは「プレリリース」部分の「識別子」と一致しています。
- また、メジャー バージョン、マイナー バージョン、パッチ バージョンの増加のセマンティクスも適用されません。ただし、下位互換性の表記方法については、互換性レベルをご覧ください。
有効な SemVer バージョンはすべて、有効な Bazel モジュール バージョンです。また、2 つの SemVer バージョン a
と b
は、Bazel モジュール バージョンと比較したときに同じである場合にのみ、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 1.2
以降が存在しても選択されません。MVS を使用すると、高忠実度かつ再現可能なバージョン選択プロセスが実現されます。
ヤンク バージョン
レジストリは、(セキュリティの脆弱性などで)特定のバージョンを避ける必要がある場合に、特定のバージョンを「アンク」として宣言できます。モジュールのヤンク バージョンを選択すると、Bazel はエラーをスローします。このエラーを修正するには、ヤンクされていない新しいバージョンにアップグレードするか、--allow_yanked_versions
フラグを使用して、ヤンクされたバージョンを明示的に許可します。
互換性レベル
Go では、MVS の下位互換性に関する想定は機能します。これは、モジュールの下位互換性のないバージョンを別のモジュールとして扱うためです。これは、SemVer の観点から見ると、A 1.x
と A 2.x
は別々のモジュールとみなされ、解決された依存関係グラフ内で共存できます。これは、Go のパッケージパスにメジャー バージョンをエンコードすることによって可能になるため、コンパイル時またはリンク時の競合は発生しません。
ただし、Bazel ではそのような動作を保証できないため、下位互換性のないバージョンを検出するには「メジャー バージョン」番号が必要です。この番号は「互換性レベル」と呼ばれ、module()
ディレクティブで各モジュール バージョンに指定されます。この情報により、Bazel は、同じモジュールの互換性レベルが異なるバージョンが解決済み依存関係グラフに存在することを検出すると、エラーをスローできます。
オーバーライド
Bazel モジュール解決の動作を変更するには、MODULE.bazel
ファイルでオーバーライドを指定します。有効になるのはルート モジュールのオーバーライドのみです。モジュールが依存関係として使用されている場合、そのオーバーライドは無視されます。
各オーバーライドは特定のモジュール名に対して指定され、依存関係グラフ内のすべてのバージョンに影響します。オーバーライドが有効になるのはルート モジュールのオーバーライドのみですが、これはルート モジュールが直接依存しない推移的な依存関係に対して行うこともできます。
単一バージョンのオーバーライド
single_version_override
は複数の目的を果たします。
version
属性を使用すると、依存関係グラフでリクエストされている依存関係のバージョンに関係なく、依存関係を特定のバージョンに固定できます。registry
属性を使用すると、通常のレジストリの選択プロセスに従う代わりに、この依存関係を特定のレジストリから取得するようにできます。patch*
属性を使用すると、ダウンロードしたモジュールに適用するパッチのセットを指定できます。
これらの属性はすべてオプションで、組み合わせて使用することができます。
複数バージョンのオーバーライド
multiple_version_override
を指定すると、解決された依存関係グラフ内に同じモジュールの複数のバージョンを共存させることができます。
モジュールに許可されるバージョンの明示的なリストを指定できます。このリストはすべて、解決前に依存関係グラフに存在する必要があります。つまり、許可される各バージョンに応じてなんらかの推移的依存関係が存在する必要があります。解決後、モジュールの許可されたバージョンのみが残りますが、Bazel はモジュールの他のバージョンを、同じ互換性レベルで最も近い、上位の許可されたバージョンにアップグレードします。同じ互換性レベルで上位に許可されているバージョンが存在しない場合、Bazel はエラーをスローします。
たとえば、バージョン 1.1
、1.3
、1.5
、1.7
、2.0
が解決前に依存関係グラフに存在し、メジャー バージョンが互換性レベルである場合、次のようになります。
1.3
、1.7
、2.0
を許可する複数バージョンのオーバーライドでは、1.1
が1.3
にアップグレードされ、1.5
が1.7
にアップグレードされ、他のバージョンは変更されません。1.5
と2.0
を許可する複数バージョンのオーバーライドでは、1.7
にアップグレードできる同じ互換性レベルの上位のバージョンがないため、エラーが発生します。1.9
と2.0
を許可する複数バージョンのオーバーライドでは、解決前に依存関係グラフに1.9
が存在しないため、エラーが発生します。
さらに、単一バージョンのオーバーライドと同様に、registry
属性を使用してレジストリをオーバーライドすることもできます。
レジストリ以外のオーバーライド
レジストリ以外のオーバーライドを使用すると、モジュールがバージョン解決から完全に削除されます。Bazel は、これらの MODULE.bazel
ファイルをレジストリではなく、リポジトリ自体からリクエストします。
Bazel は、次のレジストリ以外のオーバーライドをサポートしています。
リポジトリ名と strict の依存関係
モジュールをサポートするリポジトリの正規名は module_name~version
です(例: bazel_skylib~1.0.3
)。レジストリ以外のオーバーライドがあるモジュールの場合は、version
部分を文字列 override
に置き換えます。正規名の形式は依存すべき API ではなく、随時変更される可能性があります。
モジュールを直接依存するリポジトリの見かけ上の名前は、bazel_dep
ディレクティブの repo_name
属性で特に指定されていない限り、デフォルトでモジュール名になります。これは、モジュールがその直接依存関係のみを見つけることを意味します。これにより、推移的な依存関係の変更による偶発的な破損を防ぐことができます。
モジュール拡張では、モジュールの表示スコープに追加のリポジトリを導入することもできます。