このページでは、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 を必要とするリポジトリをフェッチできるようにする方法をご覧ください。