Bazel のロックファイル機能を使用すると、プロジェクトに必要なソフトウェア ライブラリまたはパッケージの特定のバージョンや依存関係を記録できます。これは、モジュールの解決と拡張機能の評価の結果を保存することで実現されます。ロックファイルは再現可能なビルドを促進し、一貫した開発環境を確保します。また、プロジェクトの依存関係の変更の影響を受けない解決プロセスの部分を Bazel がスキップできるため、ビルドの効率が向上します。さらに、ロックファイルは外部ライブラリの予期しない更新や破壊的な変更を防ぐことで安定性を向上させ、バグが発生するリスクを軽減します。
ロックファイルの生成
ロックファイルは、ワークスペースのルートに MODULE.bazel.lock という名前で生成されます。ビルドプロセス中、具体的にはモジュールの解決と拡張機能の評価後に作成または更新されます。重要な点として、現在のビルドの呼び出しに含まれる依存関係のみが含まれます。
プロジェクトで依存関係に影響する変更が発生すると、ロックファイルは自動的に更新され、新しい状態が反映されます。これにより、ロックファイルは現在のビルドに必要な特定の依存関係のセットに焦点を当てたままになり、プロジェクトの解決済み依存関係を正確に表すことができます。
ロックファイルの使用
ロックファイルはフラグ
--lockfile_modeで
制御して、プロジェクトの状態が
ロックファイルと異なる場合の Bazel の動作をカスタマイズできます。使用可能なモードは次のとおりです。
update(デフォルト): ロックファイルに存在する情報を使用して、既知のレジストリ ファイルのダウンロードをスキップし、結果が最新の状態である拡張機能の再評価を回避します。情報が不足している場合は、ロックファイルに追加されます。このモードでは、Bazel は、変更されていない依存関係の取り消されたバージョンなど、変更可能な情報の更新も回避します。refresh:updateと同様ですが、このモードに切り替えるときと、このモードの間はほぼ 1 時間ごとに、変更可能な情報が常に更新されます。error:updateと同様ですが、情報が不足しているか古い場合、Bazel はエラーで失敗します。このモードでは、解決中にロックファイルが変更されたり、ネットワーク リクエストが実行されたりすることはありません。reproducibleとしてマークされたモジュール拡張機能は、ネットワーク リクエストを実行する可能性がありますが、常に同じ結果を生成することが想定されています。off: ロックファイルはチェックも更新もされません。
ロックファイルのメリット
ロックファイルには次のようなメリットがあり、さまざまな方法で活用できます。
再現可能なビルド。ソフトウェア ライブラリの特定のバージョンや依存関係をキャプチャすることで、ロックファイルは、さまざまな環境で、時間の経過とともにビルドを再現できるようにします。デベロッパーは、プロジェクトのビルド時に一貫した予測可能な結果を得ることができます。
高速な増分解決。ロックファイルを使用すると、Bazel は以前のビルドですでに使用されたレジストリ ファイルのダウンロードを回避できます。 これにより、特に解決に時間がかかるシナリオで、ビルドの効率が大幅に向上します。
安定性とリスクの軽減。ロックファイルは、外部ライブラリの予期しない更新や破壊的な変更を防ぐことで、安定性を維持するのに役立ちます。依存関係を特定のバージョンにロックすることで、互換性のない更新やテストされていない更新によってバグが発生するリスクを軽減できます。
非表示のロックファイル
Bazel は、
"$(bazel info output_base)"/MODULE.bazel.lock に別のロックファイルも保持します。このロックファイルの形式と内容は明示的に指定されていません。パフォーマンスの最適化としてのみ使用されます。bazel clean --expunge で出力ベースとともに削除できますが、削除する必要がある場合は、Bazel 自体またはモジュール拡張機能にバグがあります。
ロックファイルの内容
ロックファイルには、プロジェクトの状態が変更されたかどうかを判断するために必要なすべての情報が含まれています。また、現在の状態でプロジェクトをビルドした結果も含まれます。ロックファイルは主に次の 2 つの部分で構成されます。
- モジュールの解決への入力となるすべてのリモートファイルのハッシュ。
- モジュール拡張機能ごとに、ロックファイルには、
bzlTransitiveDigest、usagesDigestなどのフィールドで表される、その拡張機能に影響する入力と、その拡張機能の実行の出力(generatedRepoSpecsと呼ばれます)が含まれます。
ロックファイルの構造と各セクションの説明の例を次に示します。
{
"lockFileVersion": 10,
"registryFileHashes": {
"https://bcr.bazel.build/bazel_registry.json": "8a28e4af...5d5b3497",
"https://bcr.bazel.build/modules/foo/1.0/MODULE.bazel": "7cd0312e...5c96ace2",
"https://bcr.bazel.build/modules/foo/2.0/MODULE.bazel": "70390338... 9fc57589",
"https://bcr.bazel.build/modules/foo/2.0/source.json": "7e3a9adf...170d94ad",
"https://registry.mycorp.com/modules/foo/1.0/MODULE.bazel": "not found",
...
},
"selectedYankedVersions": {
"foo@2.0": "Yanked for demo purposes"
},
"moduleExtensions": {
"//:extension.bzl%lockfile_ext": {
"general": {
"bzlTransitiveDigest": "oWDzxG/aLnyY6Ubrfy....+Jp6maQvEPxn0pBM=",
"usagesDigest": "aLmqbvowmHkkBPve05yyDNGN7oh7QE9kBADr3QIZTZs=",
...,
"generatedRepoSpecs": {
"hello": {
"bzlFile": "@@//:extension.bzl",
...
}
}
}
},
"//:extension.bzl%lockfile_ext2": {
"os:macos": {
"bzlTransitiveDigest": "oWDzxG/aLnyY6Ubrfy....+Jp6maQvEPxn0pBM=",
"usagesDigest": "aLmqbvowmHkkBPve05y....yDNGN7oh7r3QIZTZs=",
...,
"generatedRepoSpecs": {
"hello": {
"bzlFile": "@@//:extension.bzl",
...
}
}
},
"os:linux": {
"bzlTransitiveDigest": "eWDzxG/aLsyY3Ubrto....+Jp4maQvEPxn0pLK=",
"usagesDigest": "aLmqbvowmHkkBPve05y....yDNGN7oh7r3QIZTZs=",
...,
"generatedRepoSpecs": {
"hello": {
"bzlFile": "@@//:extension.bzl",
...
}
}
}
}
}
}
レジストリ ファイルのハッシュ
registryFileHashes セクションには、モジュールの解決中にアクセスされたリモート レジストリのすべてのファイルのハッシュが含まれています。同じ入力が指定され、すべてのリモート入力がハッシュ化されると、解決アルゴリズムは完全に決定論的になるため、ロックファイル内のリモート情報の過剰な重複を回避しながら、完全に再現可能な解決結果を確保できます。特定のレジストリに特定のモジュールが含まれていないが、優先度の低いレジストリに含まれている場合(例の「not found」エントリを参照)も記録する必要があります。この本質的に変更可能な情報は、bazel mod deps --lockfile_mode=refresh で更新できます。
Bazel は、ロックファイルのハッシュを使用して、ダウンロードする前にリポジトリ キャッシュ内のレジストリ ファイルを検索します。これにより、後続の解決が高速化されます。
選択した取り消し済みバージョン
selectedYankedVersions セクションには、モジュールの解決によって選択されたモジュールの取り消し済みバージョンが含まれています。通常、ビルドを試行するとエラーが発生するため、このセクションは、--allow_yanked_versions または BZLMOD_ALLOW_YANKED_VERSIONS で取り消し済みバージョンが明示的に許可されている場合にのみ空ではありません。
モジュール ファイルと比較して、取り消し済みバージョン情報は本質的に変更可能であるため、ハッシュで参照できません。そのため、このフィールドが必要です。この情報は、bazel mod deps --lockfile_mode=refresh で更新できます。
モジュール拡張機能
moduleExtensions セクションは、現在の呼び出しまたは以前に呼び出された拡張機能のみを含むマップです。使用されなくなった拡張機能は除外されます。つまり、依存関係グラフ全体で拡張機能が使用されなくなった場合、moduleExtensions マップから削除されます。
拡張機能がオペレーティング システムまたはアーキテクチャ タイプに依存しない場合、このセクションには「general」エントリが 1 つだけ含まれます。それ以外の場合は、OS、アーキテクチャ、またはその両方の名前が付いた複数のエントリが含まれます。各エントリは、それらの詳細で拡張機能を評価した結果に対応します。
拡張機能マップの各エントリは、使用されている拡張機能に対応し、その拡張機能を含むファイルと名前で識別されます。各エントリの対応する値には、その拡張機能に関連する情報が含まれます。
bzlTransitiveDigestは、拡張機能の実装と、その拡張機能によって推移的に読み込まれた .bzl ファイルのダイジェストです。usagesDigestは、依存関係グラフ内の拡張機能の 使用状況のダイジェストです。これにはすべてのタグが含まれます。- 読み取るファイルやディレクトリの内容、使用する環境変数など、拡張機能へのその他の入力を追跡する、指定されていないフィールド。
generatedRepoSpecsは、現在の入力で拡張機能によって作成されたリポジトリをエンコードします。- 省略可の
moduleExtensionMetadataフィールドには、拡張機能によって提供されるメタデータが含まれます。たとえば、作成した特定のリポジトリをルート モジュールがuse_repoでインポートするかどうかなどです。この情報はbazel mod tidyコマンドで使用されます。
モジュール拡張機能は、reproducible = True で返されるメタデータを設定することで、ロックファイルに含めないようにできます。これにより、同じ入力が指定された場合に常に同じリポジトリが作成されることが保証されます。
ベスト プラクティス
ロックファイル機能のメリットを最大限に活用するには、次のベスト プラクティスを検討してください。
プロジェクトの依存関係や構成の変更を反映するように、ロックファイルを定期的に更新します。これにより、後続のビルドは最新かつ正確な依存関係のセットに基づいて行われます。すべての拡張機能を一度にロックするには、
bazel mod deps --lockfile_mode=updateを実行します。コラボレーションを促進し、チームメンバー全員が同じロックファイルにアクセスできるように、ロックファイルをバージョン管理に含めます。これにより、プロジェクト全体で一貫した開発環境が促進されます。
bazeliskを使用して Bazel を実行し、ロックファイルに対応する Bazel バージョンを指定する.bazelversionファイルをバージョン管理に含めます。Bazel 自体がビルドの依存関係であるため、ロックファイルは Bazel バージョンに固有であり、下位互換性のある Bazel リリース間でも変更されます。bazeliskを使用すると、すべてのデベロッパーがロックファイルと一致する Bazel バージョンを使用していることを確認できます。
これらのベスト プラクティスに従うことで、Bazel のロックファイル機能を効果的に活用し、ソフトウェア開発ワークフローの効率性、信頼性、コラボレーションを向上させることができます。
結合の競合
ロックファイルの形式は、結合の競合を最小限に抑えるように設計されていますが、発生する可能性はあります。
自動解決
Bazel には、これらの競合を自動的に解決するのに役立つカスタムの git マージ ドライバ が用意されています。
ドライバを設定するには、git リポジトリのルートにある .gitattributes ファイルに次の行を追加します。
# A custom merge driver for the Bazel lockfile.
# https://bazel.build/external/lockfile#automatic-resolution
MODULE.bazel.lock merge=bazel-lockfile-merge
次に、ドライバを使用する各デベロッパーは、次の手順でドライバを 1 回登録する必要があります。
- jq(1.5 以降)をインストールします。
- 次のコマンドを実行します。
jq_script=$(curl https://raw.githubusercontent.com/bazelbuild/bazel/master/scripts/bazel-lockfile-merge.jq)
printf '%s\n' "${jq_script}" | less # to optionally inspect the jq script
git config --global merge.bazel-lockfile-merge.name "Merge driver for the Bazel lockfile (MODULE.bazel.lock)"
git config --global merge.bazel-lockfile-merge.driver "jq -s '${jq_script}' -- %O %A %B > %A.jq_tmp && mv %A.jq_tmp %A"
手動解決
registryFileHashes フィールドと selectedYankedVersions フィールドの単純な結合の競合は、競合の両側からすべてのエントリを保持することで安全に解決できます。
他の種類の結合の競合は、手動で解決しないでください。この場合は次のように対応してください。
- ロックファイルの以前の状態を復元します
git reset MODULE.bazel.lock && git checkout MODULE.bazel.lockで。 MODULE.bazelファイルの競合を解決します。bazel mod depsを実行してロックファイルを更新します。