Bazel モジュール

問題を報告 ソースを表示

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

モジュールには、リポジトリのルート(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")

Bazel はモジュールの解決を行うために、まずルート モジュールの MODULE.bazel ファイルを読み取り、依存関係グラフ全体を検出するまで、Bazel レジストリに依存関係の MODULE.bazel ファイルを繰り返しリクエストします。

デフォルトでは、Bazel は使用する各モジュールの 1 つのバージョンを選択します。Bazel は各モジュールをリポジトリで表し、もう一度レジストリを参照して各リポジトリの定義方法を確認します。

バージョン形式

Bazel には多様なエコシステムがあり、プロジェクトではさまざまなバージョニング スキームが使用されています。特によく利用されているのが SemVer ですが、Abseil など、さまざまなスキームを使用する著名なプロジェクトも存在します。このバージョンのバージョンは日付ベースのものです(例: 20210324.2)。

このため、Bzlmod では SemVer 仕様のより緩和されたバージョンを採用しています。相違点は次のとおりです。

  • SemVer では、バージョンの「リリース」部分は 3 つのセグメント(MAJOR.MINOR.PATCH)で構成する必要があると規定されています。Bazel ではこの要件が緩和され、任意の数のセグメントが許容されます。
  • SemVer では、「リリース」部分の各セグメントは数字のみで構成する必要があります。 Bazel では、文字も許容するように緩和され、比較セマンティクスは「プレリリース」部分の「識別子」と一致します。
  • また、メジャー バージョン、マイナー バージョン、パッチ バージョンの増加のセマンティクスは適用されません。ただし、下位互換性を示す方法について詳しくは、互換性レベルをご覧ください。

有効な SemVer バージョンとは、有効な Bazel モジュール バージョンのことです。また、2 つの SemVer バージョン ab は、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 以降が存在しても、Google では選択しません。MVS を使用すると、忠実度が高く再現可能なバージョン選択プロセスが作成されます。

ヤンク バージョン

回避する必要がある場合は、レジストリで特定のバージョンを「ヤンク済み」として宣言できます(セキュリティの脆弱性など)。ヤンクされたバージョンのモジュールを選択すると、Bazel はエラーをスローします。このエラーを修正するには、ヤンクされていない新しいバージョンにアップグレードするか、--allow_yanked_versions フラグを使用してヤンクされたバージョンを明示的に許可します。

互換性レベル

Go では、MVS の下位互換性に関する想定は機能します。これは、モジュールの下位互換性のないバージョンを別のモジュールとして扱うためです。つまり、SemVer に関しては、A 1.xA 2.x は別々のモジュールと見なされ、解決済みの依存関係グラフ内に共存できます。これは、Go のパッケージパスでメジャー バージョンをエンコードすることで可能になるため、コンパイル時またはリンク時の競合は発生しません。

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

オーバーライド

Bazel モジュール解決の動作を変更するには、MODULE.bazel ファイルでオーバーライドを指定します。ルート モジュールのオーバーライドのみが有効になります。モジュールが依存関係として使用されている場合、そのオーバーライドは無視されます。

各オーバーライドは特定のモジュール名に対して指定され、依存関係グラフ内のすべてのバージョンに影響します。ルート モジュールのオーバーライドのみが有効になりますが、ルート モジュールが直接依存しない推移的依存関係のオーバーライドが有効になる場合があります。

単一バージョンのオーバーライド

single_version_override は複数の目的で使用されます。

  • version 属性を使用すると、依存関係グラフでリクエストされた依存関係のバージョンに関係なく、依存関係を特定のバージョンに固定できます。
  • registry 属性を使用すると、通常のレジストリ選択プロセスに従う代わりに、この依存関係を特定のレジストリから取得させることができます。
  • patch* 属性を使用すると、ダウンロードしたモジュールに適用するパッチのセットを指定できます。

これらの属性はすべて省略可能で、組み合わせて相互に使用することもできます。

複数バージョンのオーバーライド

multiple_version_override を指定すると、解決された依存関係グラフ内に同じモジュールの複数のバージョンが共存できます。

モジュールに対して許可されるバージョンの明示的なリストを指定できます。このリストはすべて、解決前に依存関係グラフ内に存在する必要があります。許可される各バージョンに応じて、なんらかの推移的依存関係が存在する必要があります。解決後、モジュールの許可されたバージョンのみが残り、Bazel はモジュールの他のバージョンを、同じ互換性レベルで許可されたバージョンの中で最も近いものにアップグレードします。同じ互換性レベルで許可されているバージョンより上位のバージョンが存在しない場合、Bazel はエラーをスローします。

たとえば、解決前に依存関係グラフにバージョン 1.11.31.51.72.0 が存在し、メジャー バージョンが互換性レベルである場合:

  • 複数バージョンのオーバーライドで 1.31.72.0 を許可すると、1.11.3 にアップグレードされ、1.51.7 にアップグレードされ、他のバージョンは変わりません。
  • 1.7 にはアップグレード先の互換性レベルの上位のバージョンがないため、複数バージョンのオーバーライドで 1.52.0 を許可するとエラーが発生します。
  • 解決前に 1.9 が依存関係グラフに存在しないため、複数バージョンのオーバーライドで 1.92.0 を許可するとエラーが発生します。

さらに、ユーザーは単一バージョンのオーバーライドと同様に、registry 属性を使用してレジストリをオーバーライドできます。

レジストリ以外のオーバーライド

レジストリ以外のオーバーライドは、バージョン解決からモジュールを完全に削除します。Bazel は、これらの MODULE.bazel ファイルをレジストリではなく、リポジトリ自体からリクエストします。

Bazel は、次のレジストリ以外のオーバーライドをサポートしています。

リポジトリ名と厳格な依存関係

モジュールをバックアップするリポジトリの正規名module_name~version です(例: bazel_skylib~1.0.3)。レジストリ以外のオーバーライドがあるモジュールの場合は、version 部分を文字列 override に置き換えます。正規名の形式は API ではなく、いつでも変更される可能性があります。

bazel_dep ディレクティブの repo_name 属性に特に指定のない限り、モジュールを直接依存するリポジトリの見かけ上の名前は、デフォルトでそのモジュール名になります。これは、モジュールが直接的な依存関係のみを見つけることができることを意味します。これにより、推移的な依存関係の変更による偶発的な破損を防ぐことができます。

モジュール拡張機能を使用すると、モジュールの可視スコープに追加リポジトリを導入することもできます。