このページでは、Bazel の外部依存関係に関するよくある質問への回答を示します。
MODULE.bazel
Bazel モジュールをバージョン管理するにはどうすればよいですか?
ソース アーカイブ MODULE.bazel の module ディレクティブで version を設定すると、慎重に管理しないと、いくつかのデメリットと予期しない副作用が生じる可能性があります。
重複: モジュールの新しいバージョンをリリースするには、通常、
MODULE.bazelのバージョンを増分し、リリースにタグを付けるという 2 つの別々の手順が必要になります。この 2 つの手順は同期が取れなくなる可能性があります。自動化によってこのリスクを軽減できますが、完全に回避する方が簡単で安全です。不整合: 非レジストリ オーバーライドを使用して特定のコミットでモジュールをオーバーライドするユーザーには、誤ったバージョンが表示されます。たとえば、ソース アーカイブの
MODULE.bazelがversion = "0.3.0"を設定しているが、そのリリース以降に追加のコミットが行われている場合、ユーザーがそれらのコミットの 1 つでオーバーライドしても、0.3.0が表示されます。実際には、バージョンはリリースより先行していることを反映する必要があります(例:0.3.1-rc1)。レジストリ以外のオーバーライドに関する問題: プレースホルダ値を使用すると、ユーザーがレジストリ以外のオーバーライドでモジュールをオーバーライドするときに問題が発生する可能性があります。たとえば、
0.0.0は最も高いバージョンとして並べ替えられません。これは通常、レジストリ以外のオーバーライドを行う際にユーザーが期待する動作です。
そのため、ソース アーカイブ MODULE.bazel でバージョンを設定しないことをおすすめします。代わりに、レジストリ(Bazel Central Registry など)に保存されている MODULE.bazel で設定します。これは、Bazel の外部依存関係解決時のモジュール バージョンの実際の真実のソースです(Bazel レジストリを参照)。
通常、これは自動化されています。たとえば、rules-template のサンプルルールのリポジトリでは、bazel-contrib/publish-to-bcr publish.yaml GitHub Action を使用して、リリースを BCR に公開します。このアクションは、リリース バージョンを使用してソース アーカイブ MODULE.bazel のパッチを生成します。このパッチはレジストリに保存され、Bazel の外部依存関係の解決中にモジュールが取得されるときに適用されます。
このようにすると、レジストリのリリース バージョンが正しく設定されるため、bazel_dep、single_version_override、multiple_version_override が想定どおりに動作します。また、ソース アーカイブのバージョンはデフォルト値('')になるため、レジストリ以外のオーバーライドを行う際に発生する可能性のある問題を回避できます。この値は常に正しく処理され(デフォルトのバージョン値であるため)、並べ替えの際に想定どおりに動作します(空の文字列は最も高いバージョンとして扱われます)。
互換性レベルを増やすタイミング
Bazel モジュールの compatibility_level は、下位互換性のない(「破壊的」)変更を導入する同じ commit 内でインクリメントする必要があります。
ただし、解決された依存関係グラフに互換性レベルが異なる同じモジュールのバージョンが存在することが検出された場合、Bazel はエラーをスローする可能性があります。たとえば、2 つのモジュールが互換性レベルの異なる 3 つ目のモジュールのバージョンに依存している場合などに発生します。
そのため、compatibility_level を頻繁に増やすと、非常に中断が多くなるため、推奨されません。この状況を回避するため、compatibility_level は、破壊的変更がほとんどのユースケースに影響し、移行や回避が容易でない場合にのみインクリメントする必要があります。
MODULE.bazel が load をサポートしていないのはなぜですか?
依存関係の解決中に、参照されるすべての外部依存関係の MODULE.bazel ファイルがレジストリから取得されます。この段階では、依存関係のソース アーカイブはまだ取得されていません。そのため、MODULE.bazel ファイルが別のファイルを load している場合、Bazel がソース アーカイブ全体を取得せずにそのファイルを取得する方法はありません。MODULE.bazel ファイル自体はレジストリで直接ホストされるため、特別なファイルです。
MODULE.bazel で load をリクエストするユーザーが一般的に関心を持っているユースケースがいくつかあり、それらは load なしで解決できます。
- MODULE.bazel に記載されているバージョンが、他の場所に保存されているビルド メタデータ(.bzl ファイルなど)と一致していることを確認する。これは、BUILD ファイルから読み込まれた .bzl ファイルで
native.module_versionメソッドを使用することで実現できます。 - 非常に大きな MODULE.bazel ファイルを管理しやすいセクションに分割する(特にモノレポの場合): ルート モジュールは
includeディレクティブを使用して、MODULE.bazel ファイルを複数のセグメントに分割できます。MODULE.bazel ファイルでloadが許可されていないのと同じ理由で、ルート以外のモジュールではincludeを使用できません。 - 以前の WORKSPACE システムのユーザーは、リポジトリを宣言してから、すぐにそのリポジトリから
loadして複雑なロジックを実行したことを覚えているかもしれません。この機能はモジュール拡張機能に置き換えられました。
bazel_dep の SemVer 範囲を指定できますか?
いいえ。npm や Cargo などの他のパッケージ マネージャーは、バージョン範囲を(暗黙的または明示的に)サポートしています。多くの場合、これには制約ソルバが必要となり(ユーザーにとって出力の予測が難しくなります)、ロックファイルがないとバージョン解決が再現できなくなります。
Bazel は、Go のような最小バージョン選択を使用します。これにより、出力の予測が容易になり、再現性が保証されます。これは、Bazel の設計目標に合致するトレードオフです。
また、Bazel モジュール バージョンは SemVer のスーパーセットであるため、厳格な SemVer 環境で意味のあることが、Bazel モジュール バージョンに常に引き継がれるとは限りません。
bazel_dep の最新バージョンを自動的に取得できますか?
一部のユーザーから、bazel_dep(name = "foo",
version = "latest") を指定して dep の最新バージョンを自動的に取得できるようにしてほしいというリクエストが寄せられることがあります。これは、SemVer 範囲に関する質問と似ていますが、答えは「いいえ」です。
この問題を解決するには、自動化を利用することをおすすめします。たとえば、Renovate は Bazel モジュールをサポートしています。
この質問をするユーザーは、ローカル開発中に迅速に反復処理を行う方法を探している場合があります。これは、local_path_override を使用することで実現できます。
なぜ use_repo が必要なのですか?
MODULE.bazel ファイルのモジュール拡張機能の使用には、大きな use_repo ディレクティブが付属していることがあります。たとえば、gazelle の go_deps 拡張機能の一般的な使用方法は次のようになります。
go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
go_deps.from_file(go_mod = "//:go.mod")
use_repo(
go_deps,
"com_github_gogo_protobuf",
"com_github_golang_mock",
"com_github_golang_protobuf",
"org_golang_x_net",
... # potentially dozens of lines...
)
この情報は参照先の go.mod ファイルにすでに含まれているため、長い use_repo ディレクティブは冗長に見えるかもしれません。
Bazel がこの use_repo ディレクティブを必要とするのは、モジュール拡張機能を遅延実行するためです。つまり、モジュール拡張機能は、その結果が観測された場合にのみ実行されます。モジュール拡張機能の「出力」はリポジトリ定義であるため、これは、定義するリポジトリがリクエストされた場合にのみ(上記の例では、ターゲット @org_golang_x_net//:foo がビルドされた場合など)、モジュール拡張機能を実行することを意味します。ただし、モジュール拡張機能がどのリポジトリを定義するかは、実行するまでわかりません。ここで use_repo ディレクティブが登場します。ユーザーは、拡張機能で生成されるリポジトリを Bazel に伝えることができます。Bazel は、これらの特定のリポジトリが使用される場合にのみ拡張機能を実行します。
この use_repo ディレクティブを維持するために、モジュール拡張機能は実装関数から extension_metadata オブジェクトを返すことができます。ユーザーは bazel mod tidy コマンドを実行して、これらのモジュール拡張機能の use_repo ディレクティブを更新できます。
Bzlmod の移行
MODULE.bazel と WORKSPACE のどちらが先に評価されますか?
--enable_bzlmod と --enable_workspace の両方が設定されている場合、どちらのシステムが最初に参照されるのか疑問に思うのは当然です。簡単に言うと、MODULE.bazel(Bzlmod)が最初に評価されます。
長い回答は、「どちらが先に評価されるか」は適切な質問ではないということです。適切な質問は、「正規名 @@foo のリポジトリのコンテキストで、リポジトリの表示名 @bar は何に解決されるか」です。または、@@base のリポジトリ マッピングは何ですか?
明らかなリポジトリ名(先頭に 1 つの @)を含むラベルは、解決元のコンテキストに基づいて異なるものを参照できます。ラベル @bar//:baz を見て、それが実際に何を指しているのか疑問に思った場合は、まずコンテキスト リポジトリが何かを調べる必要があります。たとえば、ラベルがリポジトリ @@foo にある BUILD ファイルにある場合、コンテキスト リポジトリは @@foo です。
次に、コンテキスト リポジトリに応じて、移行ガイドの「リポジトリの公開設定」の表を使用して、見かけ上の名前が実際にどのリポジトリに解決されるかを確認できます。
- コンテキスト リポジトリがメイン リポジトリ(
@@)の場合:barがルート モジュールの MODULE.bazel ファイル(bazel_dep、use_repo、module、use_repo_ruleのいずれか)で導入されたリポジトリ名である場合、@barはその MODULE.bazel ファイルが宣言する内容に解決されます。- それ以外の場合、
barが WORKSPACE で定義されたリポジトリ(つまり、正規名が@@bar)の場合、@barは@@barに解決されます。 - それ以外の場合、
@barは@@[unknown repo 'bar' requested from @@]のようなものに解決され、最終的にエラーが発生します。
- コンテキスト リポジトリが Bzlmod-world リポジトリ(つまり、ルート以外の Bazel モジュールに対応するか、モジュール拡張機能によって生成される)の場合、他の Bzlmod-world リポジトリのみを参照し、WORKSPACE-world リポジトリは参照しません。
- 特に、ルート モジュールの
non_module_depsのようなモジュール拡張機能で導入されたリポジトリや、ルート モジュールのuse_repo_ruleインスタンス化が含まれます。
- 特に、ルート モジュールの
- コンテキスト リポジトリが WORKSPACE で定義されている場合:
- まず、コンテキスト リポジトリ定義に魔法の
repo_mapping属性があるかどうかを確認します。その場合は、まずマッピングを確認します(repo_mapping = {"@bar": "@baz"}で定義されたリポジトリの場合、以下の@bazを確認します)。 barがルート モジュールの MODULE.bazel ファイルで導入されたリポジトリ名である場合、@barはその MODULE.bazel ファイルが宣言する内容に解決されます。(これはメイン リポジトリのケースの項目 1 と同じです)。- それ以外の場合は、
@barは@@barになります。これは通常、WORKSPACE で定義されたリポジトリbarを指します。このようなリポジトリが定義されていない場合、Bazel はエラーをスローします。
- まず、コンテキスト リポジトリ定義に魔法の
より簡潔なバージョン:
- Bzlmod-world リポジトリ(メイン リポジトリを除く)には、Bzlmod-world リポジトリのみが表示されます。
- WORKSPACE-world リポジトリ(メイン リポジトリを含む)は、まず Bzlmod world のルート モジュールが定義する内容を確認し、次に WORKSPACE-world リポジトリを確認します。
なお、Bazel コマンドラインのラベル(Starlark フラグ、ラベル型のフラグ値、ビルド/テスト ターゲット パターンなど)は、メイン リポジトリをコンテキスト リポジトリとして扱われます。
その他
オフライン ビルドを準備して実行するにはどうすればよいですか?
bazel fetch コマンドを使用してリポジトリをプリフェッチします。--repo フラグ(bazel fetch --repo @foo など)を使用すると、リポジトリ @foo(メイン リポジトリのコンテキストで解決済み。上記の質問を参照)のみを取得できます。また、ターゲット パターン(bazel fetch @foo//:bar など)を使用すると、@foo//:bar のすべての推移的依存関係を取得できます(これは bazel build --nobuild @foo//:bar と同等です)。
ビルド中にフェッチが行われないようにするには、--nofetch を使用します。具体的には、これにより、ローカル以外のリポジトリ ルールを実行しようとすると失敗します。
リポジトリを取得してローカルでテストするために変更する場合は、bazel vendor コマンドの使用を検討してください。
HTTP プロキシを使用するにはどうすればよいですか?
Bazel は、curl など、他のプログラムで一般的に受け入れられている http_proxy と HTTPS_PROXY の環境変数を尊重します。
デュアルスタック IPv4/IPv6 設定で Bazel が IPv6 を優先するようにするにはどうすればよいですか?
IPv6 専用マシンでは、Bazel は変更なしで依存関係をダウンロードできます。ただし、デュアルスタック IPv4/IPv6 マシンでは、Bazel は Java と同じ規則に従い、IPv4 が有効になっている場合は IPv4 を優先します。たとえば、IPv4 ネットワークが外部アドレスを解決または到達できない場合、Network
unreachable 例外が発生し、ビルドが失敗することがあります。このような場合は、java.net.preferIPv6Addresses=true システム プロパティを使用して、IPv6 を優先するように Bazel の動作をオーバーライドできます。詳細:
--host_jvm_args=-Djava.net.preferIPv6Addresses=true起動オプションを使用します。たとえば、.bazelrcファイルに次の行を追加します。startup --host_jvm_args=-Djava.net.preferIPv6Addresses=trueインターネットに接続する必要がある Java ビルド ターゲット(統合テストなど)を実行する場合は、
--jvmopt=-Djava.net.preferIPv6Addresses=trueツールフラグを使用します。たとえば、.bazelrcファイルに以下を含めます。build --jvmopt=-Djava.net.preferIPv6Addresses依存関係のバージョンの解決に
rules_jvm_externalを使用している場合は、-Djava.net.preferIPv6Addresses=trueをCOURSIER_OPTS環境変数に追加して、Coursier の JVM オプションを指定します。
リモート実行で repo ルールをリモートで実行できますか?
いいえ。少なくとも現時点ではそうではありません。リモート実行サービスを使用してビルドを高速化しているユーザーは、repo ルールがローカルで実行されていることに気づくかもしれません。たとえば、http_archive は最初にローカルマシンにダウンロードされ(該当する場合はローカル ダウンロード キャッシュを使用)、抽出されてから、各ソースファイルが入力ファイルとしてリモート実行サービスにアップロードされます。リモート実行サービスがそのアーカイブをダウンロードして抽出するだけで、無駄なラウンド トリップを回避できるのに、なぜそうしないのかと疑問に思うのは当然です。
その理由の一つは、リポジトリルール(およびモジュール拡張機能)が Bazel 自体によって実行される「スクリプト」に似ているためです。リモート エグゼキュータに Bazel がインストールされているとは限りません。
もう 1 つの理由は、Bazel がダウンロードして抽出したアーカイブ内の BUILD ファイルを読み込みと分析に必要とすることが多く、それらはローカルで実行されるためです。
この問題を解決するために、リポジトリルールをビルドルールとして再考するという予備的なアイデアがあります。これにより、ルールをリモートで実行できるようになりますが、逆に新しいアーキテクチャ上の懸念が生じます(たとえば、query コマンドでアクションを実行する必要が生じ、設計が複雑になる可能性があります)。
このトピックに関する以前のディスカッションについては、Bazel を必要とするリポジトリをフェッチできるようにする方法をご覧ください。