Bazel ロックファイル

問題を報告する ソースを表示 Nightly · 8.4 · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

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 つの部分で構成されています。

  1. モジュール解決の入力となるすべてのリモート ファイルのハッシュ。
  2. モジュール拡張機能ごとに、ロックファイルには、bzlTransitiveDigestusagesDigest などのフィールドで表される、その拡張機能に影響する入力と、その拡張機能の実行の出力(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 で明示的に許可されている yanked バージョンがある場合にのみ空ではありません。

モジュール ファイルとは異なり、取り消されたバージョン情報は本質的に変更可能であるため、ハッシュで参照できません。そのため、このフィールドが必要です。この情報は bazel mod deps --lockfile_mode=refresh で更新できます。

モジュール拡張機能

moduleExtensions セクションは、現在の呼び出しまたは以前の呼び出しで使用された拡張機能のみを含むマップです。使用されなくなった拡張機能は除外されます。つまり、依存関係グラフ全体で拡張機能が使用されなくなった場合、その拡張機能は moduleExtensions マップから削除されます。

拡張機能がオペレーティング システムやアーキテクチャ タイプに依存しない場合、このセクションには「一般」というエントリが 1 つだけ表示されます。それ以外の場合は、OS、アーキテクチャ、またはその両方の名前が付けられた複数のエントリが含まれます。各エントリは、それらの仕様で拡張機能を評価した結果に対応しています。

拡張機能マップの各エントリは、使用されている拡張機能に対応しており、そのファイルと名前で識別されます。各エントリの対応する値には、その拡張機能に関連付けられた関連情報が含まれます。

  1. bzlTransitiveDigest は、拡張機能の実装と、それによって推移的に読み込まれる .bzl ファイルのダイジェストです。
  2. usagesDigest は、すべてのタグを含む依存関係グラフ内の拡張機能の使用状況のダイジェストです。
  3. 拡張機能への他の入力(読み取るファイルやディレクトリの内容、使用する環境変数など)を追跡する、指定されていないその他のフィールド。
  4. generatedRepoSpecs は、拡張機能によって作成されたリポジトリを現在の入力でエンコードします。
  5. オプションの 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 merge ドライバを提供します。

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 回登録する必要があります。

  1. jq(1.5 以降)をインストールします。
  2. 次のコマンドを実行します。
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 フィールドの単純なマージ競合は、競合の両側のすべてのエントリを保持することで安全に解決できます。

他のタイプのマージ競合は手動で解決しないでください。この場合は次のように対応してください。

  1. git reset MODULE.bazel.lock && git checkout MODULE.bazel.lock を介してロックファイルの以前の状態を復元します。
  2. MODULE.bazel ファイルの競合を解決します。
  3. bazel mod deps を実行してロックファイルを更新します。