Bazel コードベース

<ph type="x-smartling-placeholder"></ph> 問題を報告する ソースを表示 夜間 · 7.3 · 7.2 · 7.1 · 7.0 · 6.5

このドキュメントでは、コードベースと Bazel の構造について説明します。これは、 は、エンドユーザー向けではなく、Bazel への貢献に積極的なユーザーを対象としています。

はじめに

Bazel のコードベースは大規模(約 350 KLOC の本番環境コードと約 260 KLOC のテスト) この環境全体に精通している人はいません。 谷合の情報を知っている人はほとんどいませんが、 方向です。

旅の途中で出会うことがないように、 単純な道が失われて暗くなっている場合に、この文書は 簡単に使い始められるように、コードベースの概要を説明します。 あります。

Bazel のソースコードの公開バージョンは GitHub github.com/bazelbuild/bazelこれは、 「信頼できる情報源」。Google 内部のソースツリーから取得され、 Google 以外では役に立たない追加機能が含まれている。「 長期的な目標は GitHub を信頼できる情報源にすることです

コントリビューションは、通常の GitHub pull リクエスト メカニズムを通じて受け付けられます。 Google 社員が手動で内部ソースツリーにインポートした後、 GitHub に再エクスポートされます

クライアント/サーバー アーキテクチャ

Bazel の大部分は、ビルド間で RAM にとどまるサーバー プロセスに存在します。 これにより、Bazel はビルド間で状態を維持できます。

そのため、Bazel コマンドラインには起動と起動の 2 種類のオプションがあります。 使用できます。コマンドラインでは次のようになります。

    bazel --host_jvm_args=-Xmx8G build -c opt //foo:bar

一部のオプション(--host_jvm_args=)は、実行するコマンドの名前の前にあります 一部は(-c opt)より後です。「スタートアップ オプション」と呼ばれるものです。および コマンドはサーバー プロセス全体に影響しますが、後者の場合、 1 つのコマンドにのみ影響します。

各サーバー インスタンスには単一のソースツリー(「ワークスペース」)が関連付けられており、 アクティブなサーバー インスタンスが 1 つ存在します。これは回避できます カスタムの出力ベースを指定できます(詳細については、「ディレクトリ レイアウト」セクションをご覧ください)。 情報)が含まれます。

Bazel は、有効な .zip ファイルである単一の ELF 実行可能ファイルとして配布されます。 「bazel」と入力すると、上記の ELF 実行可能ファイルが C++( 「クライアント」)が制御を取得します。このコマンドを使用して、適切なサーバー プロセスをセットアップします。 手順は次のとおりです。

  1. すでに自身を抽出しているかどうかを確認します。そうでない場合は、それが実行されます。この サーバーの実装元です
  2. 正常に動作するアクティブなサーバー インスタンスがあるかどうかを確認します。実行中のサーバー インスタンス、 適切な起動オプションが設定され、適切なワークスペース ディレクトリが使用されています。これは、 $OUTPUT_BASE/server ディレクトリを参照して実行中のサーバーを見つけます。 サーバーがリッスンしているポートを含むロックファイルがあります。
  3. 必要に応じて、古いサーバー プロセスを強制終了する
  4. 必要に応じて、新しいサーバー プロセスを起動する

適切なサーバー プロセスの準備ができたら、次のコマンドを実行する必要があります。 gRPC インターフェースを介して通信されると、Bazel の出力がパイプで返されます。 送信します。同時に実行できるコマンドは 1 つのみです。これは、 C++ の部分と 2 つの部分からなる Java です。複数のコマンドを並行して実行するためのインフラストラクチャが存在しますが、 bazel version を別のコマンドと並行して実行できないため、 厄介な問題です主な阻害要因は BlazeModule のライフサイクルです。 BlazeRuntime の状態などです。

コマンドの最後に、Bazel サーバーは終了コードをクライアントに送信します。 返されます。興味深いのは bazel run の実装です。 このコマンドのジョブは、Bazel でビルドしたものを実行することですが、実行できません サーバー プロセスから分離します。その代わりに、 どのバイナリとどの引数を使用してujexec()するかをクライアントに提供します。

Ctrl+C を押すと、クライアントによって gRPC の Cancel 呼び出しに変換されます。 コマンドをできるだけ早く終了しようとします。 3 回目の Ctrl+C キーを押すと、クライアントは SIGKILL をサーバーに送信します。

クライアントのソースコードは src/main/cpp にあり、使用するプロトコルは サーバーとの通信は src/main/protobuf/command_server.proto にあります。

サーバーの主なエントリ ポイントは BlazeRuntime.main() で、gRPC 呼び出し (クライアントからの)は GrpcServerImpl.run() によって処理されます。

ディレクトリ レイアウト

Bazel は、ビルド中にやや複雑なディレクトリ セットを作成します。フル 説明が出力ディレクトリ レイアウトに記載されています。

「ワークスペース」Bazel が実行されるソースツリーです。通常は チェックした項目です

Bazel では、すべてのデータが「出力ユーザー ルート」に置かれます。これは通常 $HOME/.cache/bazel/_bazel_${USER}。ただし、次のコマンドを使用してオーバーライドできます。 --output_user_root 起動オプション。

「インストール ベース」Bazel を抽出する場所です。これは自動的に行われます Bazel の各バージョンは、チェックサムに基づいてサブディレクトリを インストール ベースデフォルトは $OUTPUT_USER_ROOT/install で、変更できます --install_base コマンドライン オプションを使用します。

出力ベース特定のインスタンスにアタッチされた Bazel インスタンスが 表示されます。各出力ベースに含まれる Bazel サーバー インスタンスは 1 つだけです いつでも実行できます。いつもは $OUTPUT_USER_ROOT/<checksum of the path to the workspace> です。これは、--output_base 起動オプションを使用して変更できます。 これは、特に、特定の組織やフォルダにのみ存在する どのワークスペースでも同時に 1 つの Bazel インスタンスを実行できます。

出力ディレクトリには、特に次のものが含まれます。

  • $OUTPUT_BASE/external で取得された外部リポジトリ。
  • 実行ルート(すべてのソースへのシンボリック リンクを含むディレクトリ) 確認できます。$OUTPUT_BASE/execroot にあります。イベント中 ビルドの場合、作業ディレクトリは $EXECROOT/<name of main repository> です。現在は $EXECROOT に変更される予定ですが、現在、 互換性のない変更だからです。
  • ビルド中にビルドされたファイル。

コマンドを実行するプロセス

Bazel サーバーが制御を取得し、コマンドに関する通知を受け取ったら、以下を実行します。 実行すると、次の一連のイベントが発生します。

  1. BlazeCommandDispatcher に新しいリクエストが通知されます。判断 コマンドの実行に必要なワークスペースが必要かどうか( ソースコードとは関係のないものについては、 help など)、別のコマンドが実行中かどうかを確認します。

  2. 正しいコマンドが見つかりました。各コマンドでインターフェースを実装する必要がある BlazeCommand@Command アノテーションを付ける必要があります(これは少し コマンドに必要なメタデータがすべて BlazeCommand のメソッドで記述)

  3. コマンドライン オプションが解析されます。各コマンドには、それぞれ異なるコマンドライン オプション。@Command アノテーションに記述されています。

  4. イベントバスが作成されます。イベントバスは、発生したイベントのストリーム おすすめします。その一部は、 ビルドがどのように行われるかを世界に伝えるために、 できます。

  5. コマンドが制御を取得します。最も興味深いコマンドは、 ビルド、テスト、実行、カバレッジなど、 BuildTool によって実装される。

  6. コマンドラインのターゲット パターンのセットが解析され、 //pkg:all//pkg/... が解決されました。これは AnalysisPhaseRunner.evaluateTargetPatterns()し、Skyframe で次のように具体化しました TargetPatternPhaseValue

  7. 読み込み/分析フェーズでは、アクション グラフ(有向 ビルドで実行する必要があるコマンドの非巡回グラフなど)。

  8. 実行フェーズが実行されます。つまり、必要なすべてのアクションを ビルドされたトップレベル ターゲットが実行されます。

コマンドライン オプション

Bazel 呼び出しのコマンドライン オプションについては、 OptionsParsingResult オブジェクトが返され、このオブジェクトには「option」からのマップが含まれます。 クラス」オプションの値にマッピングされます。「オプション クラス」のサブクラスである OptionsBase: それぞれに関連するコマンドライン オプションをグループ化します。 あります。例:

  1. プログラミング言語に関連するオプション(CppOptions または JavaOptions)。 これらは FragmentOptions のサブクラスである必要があり、最終的に BuildOptions オブジェクトに変換します。
  2. Bazel によるアクションの実行方法に関連するオプション(ExecutionOptions

これらのオプションは、分析フェーズで使用するように設計されており、 Java では RuleContext.getFragment()、Starlark では ctx.fragments)。 一部(C++ スキャンを含むかどうかなど)は、読み取られます。 ただし、そのためには常に明示的なプラミングが必要になります。 その日は BuildConfiguration はご利用になれません。詳しくは、 [構成]セクションに進みましょう

警告: ここでは、OptionsBase インスタンスは不変であり、 そのように使用する必要があります(SkyKeys の一部など)。これは違います。 変更することは、難しい方法で Bazel を壊すための良い方法です。 デバッグします。残念なことに、これらを実際に不変にするのは多大な労力を要します。 (コンストラクションの直後に他の誰よりも早く FragmentOptions を変更する) equals() または hashCode() が作成される前に、そのオブジェクトへの参照を保持します。 呼び出すことは問題ありません)。

Bazel は、次の方法でオプション クラスを学習します。

  1. Bazel に組み込まれているものもあります(CommonCommandOptions
  2. 各 Bazel コマンドの @Command アノテーションから
  3. ConfiguredRuleClassProvider から(これらは関連するコマンドライン オプションです) 個々のプログラミング言語)
  4. Starlark のルールでは、独自のオプションを定義することもできます( こちら

各オプション(Starlark で定義されたオプションを除く)は、 @Option アノテーションを持つ FragmentOptions サブクラス。このサブクラスは、 コマンドラインオプションの名前とタイプ、そしてヘルプテキストです。

コマンドライン オプションの値の Java の型は通常、単純なものです。 (文字列、整数、ブール値、ラベルなど)。また より複雑なタイプのオプション今回の例では、 コマンドライン文字列のデータ型は、 com.google.devtools.common.options.Converter

Bazel で表示されたソースツリー

Bazel はソフトウェアの構築に使用されています。ソフトウェアの構築は、 見ていきます。Bazel が動作するソースコード全体 「ワークスペース」と呼びます。リポジトリ、パッケージ、リソースという構成で できます。

リポジトリ

「リポジトリ」開発者が作業するソースツリーです。通常は 1 つのプロジェクトを表します。Bazel の祖先である Blaze は monorepo で実行されていました。 つまり、ビルドの実行に使用されるすべてのソースコードを含む単一のソースツリーです。 一方、Bazel は、ソースコードが複数のプロジェクトにまたがって できます。Bazel が呼び出されるリポジトリは「メイン」 その他は「外部リポジトリ」と呼ばれます。

リポジトリは、WORKSPACE(または WORKSPACE.bazel)というファイルでマークされています。 ルート ディレクトリに配置されます。このファイルには「グローバル」の情報が含まれています。全体 たとえば、使用可能な一連の外部リポジトリをビルドします。これは、 通常の Starlark ファイル。つまり、他の Starlark ファイルを load() できます。 これは、リポジトリが必要とするリポジトリを pull するためによく使用されます。 明示的に参照される(これを「deps.bzl パターン」と呼びます)

外部リポジトリのコードはシンボリック リンクされているか、 $OUTPUT_BASE/external

ビルドを実行するときは、ソースツリー全体をつなぎ合わせる必要があります。 メイン リポジトリ内のすべてのパッケージをシンボリック リンクする SymlinkForest によって行われます。 すべての外部リポジトリを $EXECROOT/external または$EXECROOT $EXECROOT/..(もちろん、前者はパッケージを メイン リポジトリにある external。そのため Google Cloud の 。

パッケージ

すべてのリポジトリは、パッケージ、関連ファイルのコレクション、 依存関係の仕様を指定します。これらは、Terraform という BUILD または BUILD.bazel。両方が存在する場合、Bazel は BUILD.bazel を優先します。理由 BUILD ファイルがまだ受け入れられているのは、Bazel の祖先である Blaze が ファイル名を指定します。しかし、このセグメントは一般的に使用されるパスセグメントです。特に、 Windows では、ファイル名の大文字と小文字は区別されません。

パッケージは互いに独立している(パッケージの BUILD ファイルに対する変更) 他のパッケージは変更できません。BUILD ファイルの追加、削除 再帰 glob はパッケージ境界で停止するため、他のパッケージを変更できます。 BUILD ファイルが存在する場合、再帰は停止されます。

BUILD ファイルの評価は「パッケージの読み込み」と呼ばれます。実装されている クラス PackageFactory: Starlark インタープリタを呼び出して機能し、 使用可能なルールクラスのセットに関する知識が必要です。パッケージの結果 読み込みは Package オブジェクトです。ほとんどは、文字列(名前)からのマップです。 ターゲット自体に追加されます。

パッケージ読み込みの複雑さの大部分は「グロビング」です。Bazel は すべてのソースファイルを明示的にリストする必要があり、代わりに glob を (glob(["**/*.java"]) など)。シェルとは異なり、再帰的 glob をサポートしています。 サブディレクトリに降格します(サブパッケージにはできません)。これには、以下へのアクセス権が必要です。 読み込みには時間がかかることがあるため、 可能な限り効率的に並列実行できます

グロビングは次のクラスに実装されています。

  • LegacyGlobber は、スカイフレームをまったく知らない、高速で快適な地球儀です。
  • SkyframeHybridGlobber: Skyframe を使用して以前の 「スカイフレームの再起動」を回避するために以前の globber を使用(以下で説明)。

Package クラス自体には、イベントにのみ使用されるメンバーがいくつか含まれています。 実際のパッケージでは意味をなさない部分を、WORKSPACE ファイルを解析します。これは、 設計上の欠陥の一例です。通常のパッケージを記述するオブジェクトには、 その他の情報を表すフィールドが作成されます。指標には以下が含まれます。

  • リポジトリのマッピング
  • 登録済みのツールチェーン
  • 登録された実行プラットフォーム

理想的には、WORKSPACE ファイルのパースと Package がニーズに対応する必要がないように通常のパッケージを解析する あります。この 2 つは関連しあっているため、これは残念ながら困難です。 深く掘り下げることができます

ラベル、ターゲット、ルール

パッケージは、次のタイプのターゲットで構成されます。

  1. ファイル: ビルドの入力または出力です。イン Bazel の用語では、これらはアーティファクト(別の場所で説明)と呼ばれます。すべてではありません ビルド中に作成されるファイルがターゲットになります。通常、出力は Bazel にラベルが関連付けられていない。
  2. ルール: 入力から出力を導出する手順を説明します。。 通常はプログラミング言語(cc_libraryjava_librarypy_library など)ですが、言語に依存しないものもあります (genrulefilegroup など)
  3. パッケージ グループ: 公開設定のセクションを参照してください。

ターゲットの名前はラベルと呼ばれます。ラベルの構文は、 @repo//pac/kage:namerepo はラベルがあるリポジトリの名前です) pac/kage はその BUILD ファイルがあるディレクトリ、name はパスです。 ファイル(ラベルがソースファイルを参照している場合)の相対パスで、 パッケージ化されています。コマンドラインでターゲットを参照する場合、ラベルの一部は 省略できます。

  1. リポジトリを省略すると、ラベルはメインのリポジトリに配置されます。 できます。
  2. パッケージ部分(name:name など)が省略されている場合は、ラベルが使用されます。 現在の作業ディレクトリのパッケージに配置されます(相対パス) 上位レベルの参照(..)を含むことはできません)

ルールの一種(「C++ ライブラリ」など)は「ルールクラス」と呼ばれます。ルールクラスは、 Starlark(rule() 関数)または Java( 「ネイティブ ルール」、RuleClass と入力します)。長期的には Starlark に実装されますが、Java や Java などの古いルール ファミリーもあります。 や C++ など)を含むライブラリは当面は Java のままとなります。

Starlark ルールクラスを BUILD ファイルの先頭でインポートする必要があります Java のルールクラスは load() ステートメントを使用して知名度 Bazel は ConfiguredRuleClassProvider に登録されているためです。

ルールクラスには、以下のような情報が含まれます。

  1. 属性(srcsdeps など): 型、デフォルト値、 制約など
  2. 各属性に関連付けられた構成の遷移とアスペクト(存在する場合)
  3. ルールの実装
  4. 推移的情報プロバイダは、ルールを「通常」作成

用語に関する注: コードベースでは、ターゲットと 作成します。Starlark のユーザー向けドキュメントでは、 「ルール」は、ルールクラス自体を参照する目的でのみ使用します。ターゲット 単なる「ターゲット」ですまた、RuleClass には "class" があるにもかかわらず、内で ルールクラスとターゲットの間に Java 継承関係はありません。 作成します。

スカイフレーム

Bazel の基盤となる評価フレームワークは Skyframe です。モデルは、 ビルド中に構築する必要があるすべてのものが、 エッジが任意のデータからその依存関係を指している非巡回グラフ つまり 構築するために知る必要のある他のデータです

グラフ内のノードは SkyValue と呼ばれ、その名前は SkyKey 秒。どちらもほとんど不変です。不変のオブジェクトのみを 内部 IP アドレスを使用して通信できますこの不変条件はほとんどの場合に当てはまりますが、 (たとえば、個々のオプション クラス BuildOptions は、 BuildConfigurationValue やその SkyKey など)を変更しないようにするために、 外部からは観察できない方法でのみ 変更したりできます このことから、Skyframe 内で計算されるもの( 構成済みのターゲット)も不変である必要があります。

Skyframe グラフを観察する最も便利な方法は、bazel dump --skyframe=deps を実行することです。これにより、1 行に 1 つの SkyValue がダンプされます。最高 非常に大きくなる可能性があるため、小さなビルドでこれを行う必要があります。

スカイフレームは com.google.devtools.build.skyframe パッケージにあります。「 類似した名前のパッケージ com.google.devtools.build.lib.skyframe には、 Skyframe 上に Bazel を実装しています。Skyframe について詳しくは、 こちらをご覧ください。

特定の SkyKeySkyValue に評価するために、Skyframe は 鍵の型に対応する SkyFunction。関数の Skyframe から他の依存関係をリクエストして、 SkyFunction.Environment.getValue() のさまざまなオーバーロード。これには、 これらの依存関係を Skyframe の内部グラフに登録することによる副作用が発生するため、 Skyframe の依存関係が関数に再評価されることを あります。言い換えれば、Skyframe のキャッシュ保存と増分計算は SkyFunctionSkyValue の粒度。

SkyFunction が利用できない依存関係をリクエストするたびに、getValue() null が返されます。この関数で Skyframe に制御を戻す必要があります。 null を返します。しばらくすると Skyframe が 関数を最初から再起動するだけです。 getValue() 呼び出しが null 以外の結果で成功する時間。

そのため、SkyFunction 内で実行される計算は 処理を繰り返す必要があります。ただし、これには キャッシュに保存される依存関係 SkyValues を評価します。そのため 次の方法でこの問題を回避します。

  1. 依存関係をバッチで宣言する(getValuesAndExceptions() を使用) 再起動の回数を制限します。
  2. SkyValue を異なる要素によって計算された複数の部分に分割する 個別に計算してキャッシュに保存できるように、SkyFunction を作成します。この メモリを増やす可能性があるため、戦略的に行う必要があります。 できます。
  3. 再起動と再起動の間に状態を保存する( SkyFunction.Environment.getState()、またはアドホック静的キャッシュの保持 「skyframe の背面」です。

基本的に、このような回避策が必要なのは、 何十万もの処理中の Skyframe ノードが必要です 軽量のスレッドです。

スターラーク

Starlark は、ユーザーが構成と拡張に使用するドメイン固有の言語 Bazel。Python の制限付きサブセットとして 想定されていますが 制御フローの制限が増え、最も重要なこととして、不変性が強くなります。 同時読み取りが可能になります。チューリング完全ではないため、 一部の(すべてではない)ユーザーが、一般的な目的を達成しようと試みることを プログラミング タスクに使用できます。

Starlark は net.starlark.java パッケージに実装されています。 また、独立した Go 実装も備えています。 こちらをご覧ください。Java Bazel で使用される実装は、現時点ではインタープリタです。

Starlark は、次のような複数のコンテキストで使用されます。

  1. BUILD 言語。ここで新しいルールを定義します。Starlark コード このコンテキストで実行すると、BUILD ファイルの内容にのみアクセスできます。 それによって読み込まれた .bzl ファイルが含まれます。
  2. ルールの定義。新しいルール(新しいルールのサポートなど)が、 言語)が定義されます。このコンテキストで実行される Starlark コードには、 直接依存関係によって提供される構成とデータ(詳しくは あります)。
  3. WORKSPACE ファイル。外部リポジトリ( )が定義されます。
  4. リポジトリ ルールの定義。これが新しい外部リポジトリ タイプ 定義します。このコンテキストで実行される Starlark のコードは、 Bazel が実行されているマシンから分離して、ワークスペースの外部にリーチできます。

BUILD ファイルと .bzl ファイルで使用できる言語が若干異なる 表現力が異なるからです差異のリストは こちらをご覧ください。

Starlark について詳しくはこちらをご覧ください こちらをご覧ください。

読み込み/分析フェーズ

読み込み/分析フェーズでは、Bazel は必要なアクションを判断します。 作成します。基本単位は「構成済みのターゲット」です。 (ターゲットと構成)のペアになります。

これは「読み込み/分析フェーズ」と呼ばれる2 つに分けることができるため 以前はシリアル化されていた別々の部分が、時間的に重複しています。

  1. パッケージを読み込む(BUILD ファイルを Package オブジェクトに変換する) 各要素を表す
  2. 構成済みのターゲットの分析、つまり、 使用してアクション グラフを生成する

構成された各ターゲットの推移的クロージャ内の構成済みターゲット ボトムアップで分析する必要があります。つまり、リーフノードは 次にコマンドラインで指定される関数に移ります。分析のための入力は、 単一の構成済みターゲットは次のとおりです。

  1. 構成。(そのルールの「作成方法」。たとえば、ターゲット コマンドラインオプションなども 使用できます C++ コンパイラに渡されます)
  2. 直接的な依存関係。一時的な情報の提供元は 追加します。このように呼ばれるのは 「ロールアップ」構成された 2 つの推移の たとえば、クラスパス上のすべての .jar ファイル、 C++ バイナリにリンクする必要がある)
  3. ターゲット自体。ターゲットにパッケージを読み込んだ結果 ありますルールの場合、これには属性が含まれます。 重要です
  4. 構成されたターゲットの実装。ルールの場合、この設定は次のいずれかです。 Starlark か Java ですルール以外の構成ターゲットがすべて実装されています 説明します。

構成されたターゲットの分析の出力は次のようになります。

  1. それに依存するターゲットを構成した推移的情報プロバイダは、 アクセス
  2. 作成可能なアーティファクトと、それを生成するアクション。

Java ルールに提供される API は RuleContext です。これは、 Starlark ルールの ctx 引数。API はより強力ですが、 Bad ThingsTM の作業も簡単です Bazel サーバーがクラッシュする条件を 2 次関数(2 次)にし、 Java 例外が発生する、または不変条件に違反した場合(例: Options インスタンスを使用するか、構成されたターゲットを変更可能にします)

構成されたターゲットの直接的な依存関係を判断するアルゴリズム DependencyResolver.dependentNodeMap() に住んでいる。

構成

構成とは、どのプラットフォームを何で コマンドライン オプションなど

同じビルド内の複数の構成に対して同じターゲットをビルドできます。この ツールは、たとえば、実行中のツールに同じコードを使用する場合に ビルドとターゲット コードのコンパイルが ファットな Android アプリ(複数の CPU に対応するネイティブ コードを含むもの)を作成する アーキテクチャ)

概念的には、この構成は BuildOptions インスタンスです。ただし、 BuildOptionsBuildConfiguration でラップされており、次の値を提供します。 追加機能も備えていますレイヤの上から順に 表示されます。変更があった場合は、ビルドを次のように変更する必要があります。 再分析します

その結果、ビルド全体を再分析しなければならないような異常が生じます。 リクエストされたテスト実行の数は変わりませんが、 テスト ターゲットに影響します(構成を「トリミング」して、 まだ準備ができていません)。

ルールの実装を構成に含める必要がある場合は、 RuleClass.Builder.requiresConfigurationFragments() を使用して定義の中で をタップします。これは誤り(Java フラグメントを使用する Python ルールなど)を回避するためと、 構成のトリミングを容易にして、Python のオプションが変更された場合に、 ターゲットを再分析する必要はありません

ルールの構成は、そのルールの「親」の構成と必ずしも同じではない 適用できます。依存関係エッジで構成を変更するプロセスは、 「構成の移行」です。これは、次の 2 つの場所で発生します。

  1. 依存関係エッジ。これらの遷移は、 Attribute.Builder.cfg() は、Rule の関数です(ここで、 BuildOptions(元の構成)を 1 つの変換に または複数の BuildOptions(出力構成)。
  2. 構成されたターゲットへの任意の受信エッジ。これらは RuleClass.Builder.cfg()

関連するクラスは TransitionFactoryConfigurationTransition です。

次のような構成の遷移が使用される。

  1. ビルド中に特定の依存関係が使用され、その依存関係が その都度実行アーキテクチャに
  2. 特定の依存関係を複数の依存関係用にビルドする必要があることを宣言する アーキテクチャ(ファットな Android APK のネイティブ コードなど)

構成の遷移によって複数の構成が生じることを、 あります。

構成の遷移は Starlark でも実装できます(ドキュメント こちら

一時的な情報提供者

推移的情報プロバイダは、構成されたターゲットのための方法(および唯一の方法) 依存する他の構成済みターゲットに関する情報を提供します。なぜなら、 「推移的」これは通常 なんらかの形で統合され 設定済みターゲットの推移的クロージング

通常、Java の推移的情報プロバイダ間には 1 対 1 の対応関係があります。 Starlark のもの(例外は DefaultInfo は、 FileProviderFilesToRunProviderRunfilesProvider です。これは、この API が Java の文字変換よりも Starlark っぽいと考えられています)。 キーは次のいずれかです。

  1. Java クラス オブジェクト。これを利用できるのは、 Starlark からもアクセスできます。これらのプロバイダは、 TransitiveInfoProvider
  2. 文字列。これは遺産であり、脆弱性の影響を受けやすいため、強くおすすめしません。 おすすめします。このような推移的情報プロバイダは、 build.lib.packages.Info
  3. プロバイダ シンボル。これは、provider() を使用して Starlark から作成できます。 新しいプロバイダを作成する場合には、この方法をおすすめします。記号は、 Java では Provider.Key インスタンスで表されます。

Java で実装された新しいプロバイダは、BuiltinProvider を使用して実装する必要があります。 NativeProvider はサポートが終了している(まだ削除できていない) Starlark から TransitiveInfoProvider サブクラスにはアクセスできません。

構成済みのターゲット

構成済みのターゲットは RuleConfiguredTargetFactory として実装されます。こちらの サブクラスを 1 つ実装します。Starlark の構成済みターゲット StarlarkRuleConfiguredTargetUtil.buildRule() を使用して作成されます。

構成されたターゲット ファクトリで RuleConfiguredTargetBuilder を使用して以下を行う必要があります。 戻り値を構築します。以下の要素で構成されます。

  1. filesToBuild」は、「このルールの一連のファイル」という曖昧なコンセプトです。 説明します。これらは、構成済みのターゲットの作成時にビルドされる コマンドラインまたは genrule の srcs に配置します。
  2. 実行ファイル、通常のファイル、およびデータです。
  3. 出力グループ。これらはさまざまな「その他のファイルセット」ルールは、 構築できます。Deployment の output_group 属性を使用して、 BUILD のファイル グループ ルールと Java の OutputGroupInfo プロバイダを使用します。

実行ファイル

一部のバイナリは、実行するためにデータファイルを必要とします。典型的な例が、3 つのアプリケーションの 使用できます。Bazel では、「runfiles」のコンセプトで表されます。 「runfiles ツリー」特定のバイナリのデータファイルのディレクトリツリーです。 ファイル システムで、個々のシンボリック リンクを含むシンボリック リンク ツリーとして作成されます。 出力ツリーのソース内のファイルを指します。

実行ファイルのセットは Runfiles インスタンスとして表されます。これは概念的には、 runfiles ツリー内のファイルのパスから Artifact インスタンスへマッピングを 意味します。2 つで Map を 1 つ使うよりも少し複雑です。 理由:

  • ほとんどの場合、ファイルの runfiles パスは execpath と同じです。 これを使用して RAM を節約します。
  • 実行ファイル ツリーにはさまざまな種類のエントリがあり、それらも 表します

実行ファイルは RunfilesProvider(このクラスのインスタンス)を使用して収集されます。 構成されたターゲット(ライブラリなど)およびその推移的なランファイルを表します。 ネストされたセットのように収集されます(実際、 実装されています。各ターゲットは runfile を UNION します。 依存関係の追加を行ってから、その依存関係を 必要があります。RunfilesProvider インスタンスには 2 つの Runfiles が含まれます。 1 つは「data」型ルールに依存する場合で、属性と 1 つずつ作成するようにしますこれは、ターゲットが データ属性を通じて依存されている場合、異なるランファイルを提示することがある 向上しますこれはこれまで気づかなかった、望ましくない従来の動作です まだ削除中です。

バイナリの実行ファイルは RunfilesSupport のインスタンスとして表されます。この は、RunfilesSupport には次の機能があるため、Runfiles とは異なります。 (単なるマッピングである Runfiles とは異なります)。この 次の追加コンポーネントが必要です。

  • 入力 runfile マニフェスト。これは、データセットのシリアル化された説明です。 runfiles ツリーを使用します。runfiles ツリーの内容のプロキシとして使用 Bazel は、runfiles ツリーが変化したと想定するのは、 維持できます。
  • 出力の runfile マニフェスト。これはランタイム ライブラリが runfile ツリーを処理します。特に Windows では、 シンボリック リンク。
  • runfiles 中間者。runfile ツリーを作成するには、 シンボリック リンク ツリーとシンボリック リンクが参照するアーティファクトを構築します。順序 依存関係のエッジを減らすために、runfiles 中間者を すべてを表すために使用されます
  • コマンドライン引数。その実行ファイルがあるバイナリを実行するための RunfilesSupport オブジェクトが表すもの。

アスペクト

アスペクトは、「計算を依存関係グラフに伝播」する方法です。内容は次のとおりです。 Bazel のユーザー向けに記載 こちらをご覧ください。良い 重要な例は、プロトコル バッファです。proto_library ルールは認識すべきではありません。 プロトコルの実装を構築する際には、 任意のプログラミングにおけるバッファ メッセージ(プロトコル バッファの「基本単位」) 言語は proto_library ルールに結合し、2 つのターゲットが 同じ言語が同じプロトコル バッファに依存しているため、ビルドは 1 回だけです。

構成されたターゲットと同様に、Skyframe では SkyValue として表されます。 その構成方法は、構成済みのターゲットと ビルドされています。たとえば、ConfiguredAspectFactory というファクトリ クラスがあり、 RuleContext にアクセスできますが、構成されたターゲット ファクトリとは異なり、 プロバイダに関する情報を確認できます。

依存関係グラフに伝播されるアスペクトのセットは、 Attribute.Builder.aspects() 関数を使用して属性を追加します。他にも プロセスに参加している

  1. AspectClass はアスペクトの実装です。Java や (この場合はサブクラス)または Starlark(この場合は StarlarkAspectClass のインスタンスです)。これは、 RuleConfiguredTargetFactory
  2. AspectDefinition はアスペクトの定義です。次の要素が含まれます: プロバイダとその提供プロバイダが記載されています。また、プロバイダが (適切な AspectClass インスタンスなど)。です。 RuleClass に似ています。
  3. AspectParameters は、伝播されるアスペクトをパラメータ化する方法です。 依存関係グラフを作成します。現在は文字列から文字列へのマップです。良い例 これが役立つ理由のほとんどは、プロトコル バッファです。1 つの言語に複数の API がある場合、 どの API のためにプロトコル バッファを構築すべきかについて 反映されます。
  4. Aspect は、特徴値を計算するために必要なすべてのデータを表します。 依存関係グラフの下まで伝播しますこれは、アスペクト クラス、 定義とパラメータを指定します。
  5. RuleAspect は、特定のルールの要素を決定する関数です。 伝播されます。Rule です。->Aspect 関数を使用します。

やや予想外に複雑になるのは、アスペクトが他のアスペクトに結び付く可能性があることです。 たとえば、アスペクトが Java IDE のクラスパスを収集する場合、 クラスパス上のすべての .jar ファイルについて知る必要がありますが、その一部は プロトコル バッファです。その場合、IDE 側でこれにアタッチするのは (proto_library ルール + Java proto アスペクト)ペア。

アスペクトの複雑さはクラスでキャプチャされます AspectCollection

プラットフォームとツールチェーン

Bazel はマルチプラットフォーム ビルド(つまり、存在する可能性があるビルド)をサポートしています ビルド アクションを実行する複数のアーキテクチャと、 構築するコードを指定しますこれらのアーキテクチャは、Bazel ではプラットフォームと呼ばれます。 用語(詳細ドキュメント) こちら

プラットフォームは、制約設定( 「CPU アーキテクチャ」の概念)を制約値(特定の CPU や (x86_64 など)。「辞書」という最も一般的な制約の @platforms リポジトリ内の設定と値。

ツールチェーンのコンセプトは、 ビルドが実行されているか、ターゲット プラットフォームが特定されるため、 異なるコンパイラ。たとえば、特定の C++ ツールチェーンが 他の OS をターゲットにすることもできますBazel は C++ と セット実行とターゲット プラットフォームに基づいて使用されるコンパイラ (ツールチェーンのドキュメントは、 こちらをご覧ください)。

そのために、ツールチェーンに実行セットのアノテーションを付け、 ターゲット プラットフォームの制約に応じて異なります。そのためには、 ツールチェーンは、次の 2 つの部分に分かれています。

  1. 実行とターゲットのセットを記述する toolchain() ルール ツールチェーンがサポートしている制約と、リソースの種類(C++ や Java など)を ツールチェーン。後者は toolchain_type() ルールで表されます。
  2. 実際のツールチェーン( cc_toolchain()

このようにして、すべてのリソースに対して制約を ツールチェーンを使用して、ツールチェーンの解決や言語固有の *_toolchain() ルールにはそれよりはるかに多くの情報が含まれているため、ルールよりも多くの処理が必要になります。 時間がかかります。

実行プラットフォームは、次のいずれかの方法で指定します。

  1. WORKSPACE ファイルで、register_execution_platforms() 関数を使用する
  2. コマンドライン(--extra_execution_platforms コマンドラインを使用) オプション

使用可能な実行プラットフォームのセットは、 RegisteredExecutionPlatformsFunction

構成されたターゲットのターゲット プラットフォームは、 PlatformOptions.computeTargetPlatform()これはプラットフォームのリストです 複数のターゲット プラットフォームをサポートしたいが、実装されていない まだです。

構成済みのターゲットに使用するツールチェーンのセットは、 ToolchainResolutionFunction。次の関数です。

  • 登録済みのツールチェーンのセット(WORKSPACE ファイルと 構成)
  • 目的の実行プラットフォームとターゲット プラットフォーム(構成内)
  • 構成されたターゲット( UnloadedToolchainContextKey)
  • 構成されたターゲットの実行プラットフォーム制約のセット( exec_compatible_with 属性)と構成 (--experimental_add_exec_constraints_to_targets)、 UnloadedToolchainContextKey

その結果は UnloadedToolchainContext になります。これは基本的に、 ツールチェーン タイプ(ToolchainTypeInfo インスタンスとして表される)を、 ツールチェーン)が表示されます。これは“アンロード”と呼ばれ単語が含まれていないため、 そのラベルのみが含まれます。

次に、ツールチェーンが実際に ResolvedToolchainContext.load() を使用して読み込まれます。 リクエスト元の構成済みターゲットの実装で使用されます。

単一の「ホスト」に依存しているレガシーシステムもあります。 さまざまな形式で表される構成とターゲット構成が 構成フラグ(--cpu など)。上の移行に段階的に移行しています。 ありませんユーザーが従来の構成に依存しているケースに対応するには 実装しています。 プラットフォームのマッピング 従来のフラグと新しいスタイルのプラットフォーム制約の間の変換が必要になります。 コードは PlatformMappingFunction にあり、Starlark 以外の「little」 表示されます。

制約

場合によっては、あるターゲットと互換性があるように指定したい 説明します。残念ながら、Bazel にはこの目的を達成するための複数のメカニズムがあります。

  • ルール固有の制約
  • environment_group() / environment()
  • プラットフォームの制約

ルール固有の制約は主に Google for Java ルールで使用されます。彼らは Bazel では利用できない場合でも、ソースコードが そのエンティティへの参照が含まれます。これを制御する属性は constraints=

environment_group() と environment()

これらのルールは従来のメカニズムであり、広くは使用されていません。

すべてのビルドルールで、どの「環境」をターゲットとなるのは 「environment」environment() ルールのインスタンスです。

ルールでサポートされている環境を指定するには、いくつかの方法があります。

  1. restricted_to= 属性を使用する。これは最も直接的な形式で、 仕様ルールでサポートされる環境のセットを正確に宣言する 選択します。
  2. compatible_with= 属性を使用する。環境をルールとして宣言します。 「standard」のほかに、Google Cloud でサポートされる できます。
  3. パッケージ レベルの属性 default_restricted_to=default_compatible_with=
  4. environment_group() ルールのデフォルトの指定を使用する。毎 が属しているグループ(例: 「CPU 使用率」、 アーキテクチャ、JDK バージョン(例: 「モバイル オペレーティング システム」)。「 環境グループの定義には、次の環境のうちどれが 「default」によってサポートされる restricted_to= / environment() 属性。該当する属性がないルールは、 属性はすべてデフォルトを継承します。
  5. ルールクラスによるデフォルト。これは、すべての 割り当てられています。これを使用して、たとえば 各インスタンスを明示的に指定することなく、すべての *_test ルールをテスト可能 宣言します。

environment() は通常のルールとして実装されますが、environment_group() は は Target のサブクラスであり、RuleEnvironmentGroup)ではない、かつ Starlark からデフォルトで使用できる (StarlarkLibrary.environmentGroup())で、最終的には同名の あります。これは、各依存関係が反復的な依存関係にあり、 所属する環境グループを宣言し、それぞれの環境を 環境グループでデフォルトの環境を宣言する必要があります。

ビルドを特定の環境に制限するには、 --target_environment コマンドライン オプション。

制約チェックの実装は、Terraform の RuleContextConstraintSemanticsTopLevelConstraintSemantics

プラットフォームの制約

現在の「公式」ターゲットが対応しているプラットフォームを説明する ツールチェーンとプラットフォームの記述に使用したのと同じ制約を使用します。 pull リクエストで審査中です #10945

公開設定

(Google のような)多数の開発者が関与する大規模なコードベースを扱っている場合、 他の誰かが自分のロールに依存することがないよう、 できます。それ以外の場合は、ハイラムの法則に従い、 導入できると見なした行動にユーザーが依存するようになる 表示されます。

Bazel は、可視性と呼ばれるメカニズムによってこれをサポートしています。つまり、 特定のターゲットを限定するには、 visibility 属性を使用します。この 属性は少し特殊です。これは、ラベルのリストを保持していますが、 任意のパッケージ名へのポインタではなく、パッケージ名のパターンを 特定できます。(はい、これは設計上の欠陥です)。

これは次の場所に実装されています。

  • RuleVisibility インターフェースは可視性の宣言を表します。機能 定数(完全に公開または完全に非公開)またはラベルのリストのいずれかです。
  • ラベルは、パッケージ グループ(事前定義されたパッケージのリスト)を参照したり、 パッケージの直接(//pkg:__pkg__)またはパッケージのサブツリー (//pkg:__subpackages__)。これはコマンドライン構文とは異なります これは //pkg:* または //pkg/... を使用します。
  • パッケージ グループは独自のターゲット(PackageGroup)として実装されます。 設定されたターゲット(PackageGroupConfiguredTarget)。おそらく 単純なルールに置き換えることもできます。ロジックが実装されている PackageSpecification を使用します。これは //pkg/... のような単一のパターンPackageGroupContents: これは 単一の package_grouppackages 属性にマッピングします。および PackageSpecificationProviderpackage_group を集計し、 推移的な includes です。
  • 公開設定ラベルリストから依存関係への変換は、 DependencyResolver.visitTargetVisibility、その他 できます。
  • 実際のチェックは CommonPrerequisiteValidator.validateDirectPrerequisiteVisibility()

ネストされたセット

多くの場合、構成されたターゲットは、その依存関係から一連のファイルを集約し、 集約セットを推移的情報プロバイダにラップするため、 依存するターゲットが構成済みの場合、それらは同様に動作します。例:

  • ビルドに使用される C++ ヘッダー ファイル
  • cc_library の推移的クロージャを表すオブジェクト ファイル
  • Java ルールのクラスパス上に配置する必要がある一連の .jar ファイル。 コンパイルまたは実行
  • Python ルールの推移的クロージャ内の Python ファイルのセット

ListSet などを使用してこれを単純な方法で行った場合、次のようになります。 二次的なメモリ使用量: N 個のルールのチェーンがあり、各ルールが コレクションのメンバーは 1+2+...+N です

この問題を回避するために、 NestedSet。これは、他の NestedSet で構成されるデータ構造です。 それによって、有向非巡回グラフが形成されます。 セットの集合です。これらは不変であり、そのメンバーは反復処理できます。定義 複数の反復順序(NestedSet.Order): preorder、postorder、トポロジ (ノードは常に祖先の後に来ます)「気にしないではありますが、 “毎回同じ”とします

Starlark では、同じデータ構造が depset と呼ばれます。

アーティファクトとアクション

実際のビルドは、実行する必要がある一連のコマンドから構成され、 出力を出力できます。コマンドは、 クラス Action。ファイルは、クラスのインスタンスとして表されます。 Artifact。これらは二部グラフ(有向非巡回グラフ)に並んでおり、 「アクション グラフ」のようなものです。

アーティファクトには、ソース アーティファクト(利用可能なソース アーティファクト)の 2 種類 (Bazel の実行が開始される前)と派生アーティファクト( 構築)。派生アーティファクト自体には、複数の種類があります。

  1. **通常のアーティファクト。**これらは、 チェックサム(mtime をショートカットとして使用)ファイルのチェックサムは、 ctime は変わっていません。
  2. 未解決のシンボリック リンク アーティファクト。これらは Google の API によって readlink() を呼び出しています。通常のアーティファクトとは異なり、これらはぶら下がる シンボリック リンク。通常、いくつかのファイルを 1 つの アーカイブしてるんだ。
  3. ツリー アーティファクト。これらは単一のファイルではなく、ディレクトリ ツリーです。。 最新の状態がチェックされます。 できます。TreeArtifact として表されます。
  4. 継続的なメタデータ アーティファクト。これらのアーティファクトを変更しても、 行います。これはビルドスタンプ情報にのみ使用されます。 再ビルドする必要があります。

ソース アーティファクトがツリー アーティファクトや、 未解決のシンボリック リンク アーティファクトがある場合、これはまだ実装されていないだけです( ただし、BUILD ファイル内のソース ディレクトリを参照するのは、 Bazel に関して以前から報告されている不正確さのごくわずかな問題利用可能な この種の機能の実装には、 BAZEL_TRACK_SOURCE_DIRECTORIES=1 JVM プロパティ)

注目すべき種類の Artifact は仲介業者です。Artifact で示されます。 MiddlemanAction の出力であるインスタンス。これらは、 次のようなことを念頭に置いてください。

  • アーティファクトをグループ化するには、集約中間者を使用します。これは、 多くのアクションが同じ大規模な入力セットを使用する場合、N*M は存在しない 依存エッジ、N+M のみ(ネストされたセットに置き換えられる)
  • 依存関係ミドルマンのスケジュールによって、あるアクションが別のアクションより先に実行されるようにします。 主に lint チェックに使用されますが、C++ コンパイルにも使用されます( CcCompilationContext.createMiddleman(): 説明)。
  • 実行ファイル ミドルメンは、runfiles ツリーの存在を確認するために使用されます。 出力マニフェストや各モジュールに個別に依存する必要は runfiles ツリーで参照される単一のアーティファクトです。

アクションは、実行する必要があるコマンド、つまり環境 生成する出力のセットが含まれます。主なものは次のとおりです。 次の要素が含まれます。

  • 実行する必要があるコマンドライン
  • 必要な入力アーティファクト
  • 設定する必要がある環境変数
  • 実行する必要のある環境(プラットフォームなど)を記述するアノテーション \

他にも、内容が指定されたファイルを書き込むなど、いくつかの特殊なケースがあります。 使用されます。AbstractAction のサブクラスです。ほとんどのアクションは SpawnAction または StarlarkAction(同じ。 Java と C++ にはそれぞれ独自のアクション型がありますが、 (JavaCompileActionCppCompileActionCppLinkAction)。

最終的には、すべてを SpawnAction に移行します。JavaCompileAction は かなり似ていますが、C++ は少し特殊なケースです。.d ファイルの解析と変換の スキャンが含まれます。

アクション グラフのほとんどは「埋め込まれている」グラフに表現されます。概念的には、 アクションの実行は、サービスの呼び出しとして表され、 ActionExecutionFunction。アクション グラフの依存関係エッジから スカイフレームの依存関係エッジについては、 ActionExecutionFunction.getInputDeps()Artifact.key() などがあり、 Skyframe のエッジの数を低く抑えるために、次のように最適化します。

  • 派生アーティファクトには、独自の SkyValue はありません。代わりに Artifact.getGeneratingActionKey() は、鍵を見つけるために使用されます。 アクションを生成
  • ネストされたセットには独自のスカイフレーム キーがあります。

共有操作

一部のアクションは、複数の構成済みターゲットによって生成されます。Starlark のルールは次のとおりです。 派生アクションを 1 つのコンテナにしか格納できないため、 構成とパッケージによって決定されます(ただし、 競合する可能性がありますが、Java で実装されたルールでは 保存できます。

これは誤りと考えられますが、取り除くのはとても困難です 実行時間を大幅に短縮できるからです ソースファイルはなんらかの方法で処理する必要があり、 複数のルールがあります(例: 手を振る、手を振る)。これには RAM がいくらかかかります。 各インスタンスは個別にメモリに保存する必要があります。

2 つのアクションが同じ出力ファイルを生成する場合は、完全に同じである必要があります。 同じ入力、同じ出力を指定し、同じコマンドラインを実行します。この 等価関係は Actions.canBeShared() に実装されています。 すべてのアクションを検証する必要があります。 これは SkyframeActionExecutor.findAndStoreArtifactConflicts() に実装されています。 Bazel で「グローバル」なものを必要とする数少ない場所の 1 つです。ビュー 構築できます。

実行フェーズ

このとき、Bazel が実際にビルド アクションの実行を開始します。 出力を生成します。

分析フェーズの後、Bazel が最初に行うのは、 アーティファクトを構築する必要があります。このロジックは、Python コードで TopLevelArtifactHelper、大まかに言えば、モデルの filesToBuild です。 コマンドラインで構成されたターゲットと、 「もしこのターゲットがコマンドに含まれているか」を明示する目的で、 これらのアーティファクトをビルドします

次のステップでは、実行ルートを作成します。Bazel には、リソースを読み取るオプションが ファイル システム(--package_path)の異なる場所にあるソース パッケージ 完全なソースツリーを使用して、ローカルで実行されるアクションを提供する必要があります。これは、 クラス SymlinkForest によって処理され、すべてのターゲットをメモすることで動作します。 シンボリック リンクする単一のディレクトリ ツリーを構築します。 使用済みのターゲットを持つすべてのパッケージを実際の場所から追跡できます。別の方法として、 コマンドに正しいパスを渡す必要があります(--package_path を考慮します)。 次の理由により、これは適切ではありません。

  • パッケージがパッケージパスから移動されると、アクションのコマンドラインが変更されます。 別のエントリ(以前はよく発生します)
  • アクションをリモートで実行する場合とリモート実行する場合とで、異なるコマンドラインになります。 ローカルで実行され
  • 使用するツールに固有のコマンドライン変換が必要である (Java クラスパスと C++ インクルード パスの違いを考慮してください)
  • アクションのコマンドラインを変更すると、そのアクションのキャッシュ エントリが無効になる
  • --package_path は段階的に非推奨に

次に、Bazel はアクション グラフ(二部有向グラフ)の走査を開始します。 アクションとその入力および出力アーティファクトで構成される)と、実行中のアクションです。 各アクションの実行は SkyValue のインスタンスによって表される クラス ActionExecutionValue

アクションの実行にはコストがかかるため、複数のレイヤを使用して SkyFrame の後ろにぶつかる:

  • ActionExecutionFunction.stateMap には、Skyframe を再起動するためのデータが含まれています。 /ActionExecutionFunction の低価格
  • ローカル アクション キャッシュには、ファイル システムの状態に関するデータが含まれています。
  • リモート実行システムには通常、独自のキャッシュも含まれている

ローカル アクション キャッシュ

このキャッシュは Skyframe の背後にあるもう 1 つのレイヤです。あるアクションが Skyframe で再実行された場合、ローカル アクション キャッシュでヒットする可能性があります。これは、 ローカル ファイル システムの状態を表し、ディスクにシリアル化されます。 新しい Bazel サーバーを起動すると、ローカル アクション キャッシュを取得できます ヒットします。

このキャッシュでは、メソッドを使用してヒットがチェックされます。 ActionCacheChecker.getTokenIfNeedToExecute()

名前とは異なり、派生アーティファクトのパスから 検出できます。このアクションは次のように記述されます。

  1. 入出力ファイルとそれらのチェックサムのセット
  2. 「アクションキー」(通常は実行されたコマンドライン)ですが、 アプリケーション リソースのチェックサムでキャプチャされないもの、 入力ファイル(たとえば FileWriteAction の場合、これはデータの 記録される)

高度に実験的な「トップダウン アクション キャッシュ」もあります。まだ これは、推移的ハッシュを使用して、できるだけ多くの あります。

入力検出と入力プルーニング

一部のアクションは、単に一連の入力を取得するよりも複雑な場合があります。変更点 アクションの入力セットには、次の 2 つの形式があります。

  • アクションは、実行前に新しい入力を検出したり、一部の入力を その入力が実際には必要ありません。典型的な例は C++です C++ レイヤでどのヘッダー ファイルがファイルなのか、 一時的なクロージャから解放されるようにします。これにより、 リモートエグゼキュータに送信します。すべてのケースを登録する代わりに ヘッダー ファイルを「入力」として使用するが、ソースファイルをスキャンして推移的 そのヘッダー ファイルのみを含む入力としてマークし、 #include ステートメントで言及(実際よりも多く見積もっているため、 (完全な C プリプロセッサを実装) 「false」Bazel で行われ、Google 内でのみ使用されます。
  • アクションの実行中に一部のファイルが使用されていないことに気付く場合があります。イン C++ の場合、これは「.d ファイル」と呼ばれます。コンパイラは、どのヘッダー ファイルが 悪化させたという恥ずかしさを避けるために、 インクリメンタリティであるため、Bazel はこの事実を利用しています。これにより、 インクルード スキャナはコンパイラに依存しているため、見積もりはインクルード スキャナよりも高くなります。

これらは Action のメソッドを使用して実装されます。

  1. Action.discoverInputs() が呼び出されます。ネストされたフィールドのセットが返されます。 必要と判断されたアーティファクト。これらはソース アーティファクトであること 依存関係のない依存関係エッジがアクション グラフに残らないようにします。 同等の値が示されます。
  2. このアクションは、Action.execute() を呼び出すことによって実行されます。
  3. Action.execute() の最後で、アクションは以下を呼び出すことができます。 Action.updateInputs() で、入力の一部がないことを Bazel に指示します 必要ありません。これにより、使用された入力が次の場合に、増分ビルドが正しく行われないことがあります。 未使用と報告されます

アクション キャッシュが新しいアクション インスタンス(作成された Bazel は updateInputs() 自体を呼び出し、 入力内容には、以前に実施した入力の検出とプルーニングの結果が反映されます。

Starlark のアクションは、この機能を利用して一部の入力を未使用として宣言できます。 unused_inputs_list= 引数を使用して、 ctx.actions.run()

アクションを実行するさまざまな方法: Strategies/ActionContexts

一部のアクションはさまざまな方法で実行できます。たとえば、コマンドラインは、 さまざまなサンドボックス、またはリモートで実行されるようにします。「 これを実現するコンセプトを ActionContext(または Strategy 名前変更の半分しか成功しなかった...)

アクション コンテキストのライフサイクルは次のとおりです。

  1. 実行フェーズが開始されると、BlazeModule インスタンスに対して、 コンテキストに基づいて生成されます。これは、Terraform のコンストラクタで ExecutionTool。アクション コンテキスト タイプは Java Class で識別されます ActionContext のサブインターフェースを参照するインスタンスと、 実装する必要があるインターフェースです。
  2. 利用可能なアクション コンテキストから適切なアクション コンテキストが選択され、 ActionExecutionContextBlazeExecutor に転送。
  3. アクションは、ActionExecutionContext.getContext() を使用してコンテキストをリクエストします。 BlazeExecutor.getStrategy()(実際には、実行する方法は 1 つ 。

戦略は、仕事をするために他の戦略を自由に呼び出すことができます。これは Pod の ローカルとリモートの両方でアクションを開始する動的戦略の場合、 先に終了したほうを使用します。

注目すべき戦略の一つは、永続ワーカー プロセスを実装することです。 (WorkerSpawnStrategy)。一部のツールは起動に時間がかかるという 別のアクションをまたいで再利用するのではなく すべてのアクション(これは正確性の問題である可能性があります。Bazel は ワーカー プロセスが Observability の Promise に依存していることに 状態を指定する)

ツールが変更された場合は、ワーカー プロセスを再起動する必要があります。ワーカーが ツールのチェックサムを計算して、 WorkerFilesHash。アクションのどの入力が何を表しているかを知ることにより、 入力を表す部分です。クリエイターによって決定されます アクション Spawn.getToolFiles()Spawn の実行ファイルは次のとおりです。 ツールの一部としてカウントされます

戦略(またはアクションのコンテキスト)について詳しくは、以下をご覧ください。

  • アクションを実行するためのさまざまな戦略に関する情報を利用可能 こちらをご覧ください。
  • アクションを実行するダイナミック戦略に関する情報 結果が出ているほうをローカルとリモートから選択し、 こちらをご覧ください。
  • ローカルでのアクション実行の複雑さに関する情報は こちらをご覧ください。

ローカル リソース マネージャー

Bazel では、多くのアクションを並行して実行できます。ローカル アクションの数は、 並行して実行することが推奨されています。 同時に実行するインスタンスの数を減らして、この問題を回避してください。 過負荷状態になります。

これはクラス ResourceManager で実装されます。各アクションは、 必要なローカル リソースの推定値が、 ResourceSet インスタンス(CPU と RAM)。アクション コンテキストがなんらかの動作を ローカル リソースを必要とする場合、ResourceManager.acquireResources() を呼び出します。 必要なリソースが使用可能になるまでブロックされます。

ローカル リソース管理のより詳細な説明については、 こちらをご覧ください。

出力ディレクトリの構造

各アクションは、出力ディレクトリ内に個別の場所として配置する必要があります。 確認します。通常、派生アーティファクトの場所は次のようになります。

$EXECROOT/bazel-out/<configuration>/bin/<package>/<artifact name>

特定の ID に関連付けられたディレクトリの名前は何ですか。 どうなるでしょうか望ましいプロパティには相反するものが 2 つあります。

  1. 同じビルドで 2 つの構成を作成できる場合は、 別々のディレクトリを作成し、両方に同じバージョンのインスタンスを アクション:このコマンドで 2 つの構成で相違がある場合に 同じ出力ファイルを生成するアクションの行で、Bazel はどの行が 選択するアクション(「アクションの競合」)
  2. 2 つの構成が「おおまか」を表す場合、同じものとして、 そうすると、一方で実行されるアクションを他方で再利用できるようになります。 たとえば、コマンドライン オプションを変更し、 Java コンパイラでは、C++ コンパイル アクションが再実行されません。

今のところ、この問題を解決する原則的な解決策は見つかっていません。 構成トリミングの問題と類似しています長めのディスカッション オプションが用意されています。 こちらをご覧ください。 主な問題点は Starlark のルールです(Starlark の作成者は通常、 (Bazel についてよく知っているもの)とさまざまな側面を取り上げており、 「同じ」ものを生成できるものの、出力ファイルです。

現在のアプローチでは、構成のパスセグメントは <CPU>-<compilation mode> に、さまざまなサフィックスを追加して、構成に Java で実装された遷移によってアクションの競合が発生しません。また、 一連の Starlark 構成遷移のチェックサムが追加され、ユーザーが アクションの競合を引き起こさないようにします。これは完璧とは程遠いものです。これは OutputDirectories.buildMnemonic()。各構成フラグメントに依存します。 出力ディレクトリの名前に独自の部分を追加します。

テスト

Bazel は、テストを実行するための豊富なサポートを備えています。以下がサポートされます。

  • リモートでのテストの実行(リモート実行バックエンドが利用可能な場合)
  • テストを複数回並行して実行する(デフレークまたはタイミングの収集のため) データ)
  • テストのシャーディング(同じテスト内のテストケースを複数のプロセスに分割する) スピードを重視)
  • 不安定なテストの再実行
  • テストをテストスイートにグループ化する

テストは、TestProvider を持つ通常の構成済みターゲットです。 テストの実行方法を以下に示します。

  • ビルドの結果、テストが実行されるアーティファクト。これは「キャッシュ」で ステータス」シリアル化された TestResultData メッセージを含むファイル
  • テストの実行回数
  • テストを分割するシャードの数
  • テストの実行方法に関するパラメータ(テストのタイムアウトなど)

実行するテストの決定

実行するテストの決定は複雑なプロセスです。

まず、ターゲット パターンの解析中に、テストスイートが再帰的に展開されます。「 拡張は TestsForTargetPatternFunction に実装されています。ややそう思う 驚くべき点として、テストスイートでテストが宣言されていない場合、 そのパッケージ内のすべてのテストを実行します。これは Package.beforeBuild() で次のように実装されています。 テストスイートのルールに $implicit_tests という暗黙の属性を追加します。

次に、テストは、ルールに従ってサイズ、タグ、タイムアウト、言語でフィルタされます。 コマンドライン オプションを使用します。これは TestFilter で実装され、 ターゲット解析中は TargetPatternPhaseFunction.determineTests()、 結果は TargetPatternPhaseValue.getTestsToRunLabels() に読み込まれます。その理由は、 フィルタできるルール属性を構成できないのは、 実行されるので、構成は分析フェーズには含まれません。 できます。

その後、BuildView.createResult() でさらに処理されます。 分析が失敗した場合は除外され、テストは排他的 非独占的なテストも行いますこれを AnalysisResult に挿入します。これにより、 ExecutionTool は実行するテストを認識します。

この複雑なプロセスに透明性を持たせるため、tests() クエリ演算子(TestsFunction に実装)を使用して、 コマンドラインで特定のターゲットを指定すると、です。 残念なことに再実装なので、上記の説明から いくつかあります。

テストの実行

テストを実行するには、キャッシュ ステータスのアーティファクトをリクエストします。その後、 TestRunnerAction が実行され、最終的に --test_strategy コマンドライン オプションによって選択された TestActionContext は、リクエストされた方法でテストを実行します。

テストは、環境変数を使用する複雑なプロトコルに従って実行される テストに何を求めるかですBazel の詳細 テストで想定されるもの、Bazel で想定されるテストを確認できます こちらをご覧ください。Google 最もシンプルです。終了コードが 0 の場合は成功、それ以外は失敗を意味します。

各テストプロセスは、キャッシュ ステータス ファイル以外にも、 できます。テストログ ディレクトリに配置されます。このサブディレクトリの名前は ターゲット構成の出力ディレクトリの testlogs:

  • test.xml: 個々のテストケースの詳細を示す JUnit スタイルの XML ファイル。 テストシャード
  • test.log: テストのコンソール出力。stdout と stderr は あります。
  • test.outputs: 「宣言されていない出力ディレクトリ」。テストで使用します。 ターミナルに出力する内容に加えてファイルを出力したいアプリケーションに適しています。

テスト実行中に発生する可能性は 2 つありますが、 排他的なテスト実行と出力ストリーミング。

一部のテストは排他モードで実行する必要がある(例: テストします。これを起動するには、tags=["exclusive"] を テストルールを実行するか、--test_strategy=exclusive を使用してテストを実行します。各限定 別の Skyframe 呼び出しによってテストが実行され、 「main」の後にテストする構築できます。これは SkyframeExecutor.runExclusiveTest()

通常のアクションとは異なり、アクションの実行後にターミナル出力がダンプされます。 終了すると、ユーザーはテストの出力をストリーミングするようリクエストして、 長時間実行テストの進行状況を確認できます。これは、 --test_output=streamed コマンドライン オプション。排他テストを示します。 異なるテストの出力が混在しないようにする必要があります。

これは適切な名前の StreamedTestOutput クラスに実装されており、次のように機能します。 該当するテストの test.log ファイルに変更をポーリングし、新しいコードをダンプ Bazel ルールのターミナルにルーティングされます。

実行したテストの結果は、 (TestAttemptTestResultTestingCompleteEvent など)。 Build Event Protocol にダンプされてコンソールに出力される 作成者: AggregatingTestListener

カバレッジの収集

テストのカバレッジは、ファイルの LCOV 形式で報告されます。 bazel-testlogs/$PACKAGE/$TARGET/coverage.dat

カバレッジを収集するために、各テスト実行は collect_coverage.sh

このスクリプトは、カバレッジの収集を有効にするテスト環境をセットアップします。 カバレッジ ファイルが書き込まれる場所が、カバレッジ ランタイムによって判別されます。 その後、テストを実行します。テスト自体が複数のサブプロセスを実行し、 複数の異なるプログラミング言語で記述された部分を カバレッジ収集ランタイムなど)ですラッパー スクリプトは、アプリケーションの 結果のファイルを必要に応じて LCOV 形式に変換し、1 つの 表示されます。

collect_coverage.sh の介在は、テスト戦略によって行われます。 テストの入力に collect_coverage.sh が必要です。これは、 暗黙的な属性 :coverage_support によって実現されます。これは、 構成フラグ --coverage_support の値( TestConfiguration.TestOptions.coverageSupport)

一部の言語ではオフライン計測を行います。つまり、 インストルメンテーションはコンパイル時に追加され(C++など)、その他はオンラインで行われます インストルメンテーション。つまり、実行時にカバレッジ インストルメンテーションが追加される あります。

もう 1 つのコアコンセプトは、ベースライン カバレッジです。これは図書館のニュースです コードが実行されていないかどうかをテストします。解決する問題は バイナリのテスト カバレッジを計算したい場合は、 すべてのテストのカバレッジを確保できます。これは、バイナリに標準外のコードが含まれている可能性があるためです。 すべてのテストにリンクされますしたがって、すべてのアクティビティに対してカバレッジ ファイルを カバレッジ対象のファイルのみを含むバイナリです。 作成します。ターゲットのベースライン カバレッジ ファイルは、 bazel-testlogs/$PACKAGE/$TARGET/baseline_coverage.datまた、 テストに加えて、バイナリとライブラリの --nobuild_tests_only フラグを Bazel に設定。

ベースライン カバレッジは現在、破損しています。

カバレッジ コレクションは、ルールごとに、 インストルメンテーション・メタデータ・ファイルのセットが含まれます。

インストゥルメント化されたファイルのセットは、計測するファイルのセットだけです。対象 これはランタイム時にこれを使用して あります。ベースライン カバレッジの実装にも使用されます。

インストルメンテーション メタデータ ファイルのセットは、テストに必要な追加のファイルのセット Bazel が必要とする LCOV ファイルを生成できます。実際には ランタイム固有のファイルたとえば、gcc はコンパイル時に .gcno ファイルを出力します。 これらは、カバレッジ モードが次の場合にテスト アクションの入力のセットに追加されます。 有効にします。

カバレッジが収集されているかどうかは、 BuildConfiguration。テストの変更を簡単にするため、これは便利です。 アクションとアクショングラフが 異なる場合もありますが すべてのターゲットを再分析する必要があります( C++ では、カバレッジを収集できるコードを出力するために、さまざまなコンパイラ オプションが必要です。 この問題はある程度軽減されます。それでいずれにせよ再分析が必要になるからです)。

カバレッジ サポート ファイルは、暗黙的な オーバーライドして、呼び出しポリシーでオーバーライドできるようにします。これにより、 Bazel のバージョンが異なっている必要があります。できれば、これらの そのうちの 1 つを標準化しました。

「カバレッジレポート」も生成します。収集されたカバレッジが Bazel 呼び出しのすべてのテスト。この処理には CoverageReportActionFactory で、BuildView.createResult() から呼び出されます。これは、 必要なツールにアクセスするには、:coverage_report_generator 属性を宣言します。

クエリエンジン

Bazel は 小さな言葉 さまざまなグラフについてさまざまな質問をしています。次のクエリの種類 次のオプションが提供されます。

  • bazel query は、ターゲット グラフの調査に使用されます。
  • bazel cquery は、構成されたターゲット グラフを調査するために使用されます。
  • bazel aquery はアクション グラフの調査に使用されます。

これらはそれぞれ、AbstractBlazeQueryEnvironment をサブクラス化することで実装されます。 QueryFunction をサブクラス化することで、その他のクエリ関数を実行できます。 をタップします。クエリ結果をストリーミングで収集するのではなく、 query2.engine.CallbackQueryFunction に渡され、 返すことができます。

クエリの結果は、ラベル、ラベル、ルールなど、さまざまな方法で出力できる XML、protobuf などです。これらは、Terraform のサブクラスとして実装され、 OutputFormatter

一部のクエリ出力形式(proto は当然)の微妙な要件は、 Bazel は、パッケージの読み込みが提供するすべての情報を出力して、 出力の差分を比較して、特定のターゲットに変化があったかどうかを判断できます。 そのため、属性値はシリアル化可能である必要があります。そのため、 複雑な Starlark を持つ属性を持たない属性タイプはごくわずかです。 使用できます。通常の回避策は、ラベルを使用して複雑な 追加することもできます。あまり満足のいく回避策ではない この要件を解除できると便利です

モジュール システム

Bazel は、モジュールを追加することで拡張できます。各モジュールは、サブクラス内に BlazeModule(この名前は、Bazel が以前使われていた頃の Bazel の歴史の遺物です Blaze)が使用され、実行中にさまざまなイベントに関する情報が できます。

これらは主に、さまざまな「非コア」コンポーネントを実装するために使用されます。機能 一部のバージョンの Bazel(Google で使用しているバージョンなど)でのみ必要とする場合は、

  • リモート実行システムへのインターフェース
  • 次のコマンドを新しく導入しました。

BlazeModule が提供する拡張ポイントのセットは、やや漠然としています。すべきでないこと 良い設計原則の例として使えます

イベントバス

BlazeModule が Bazel の他の部分と通信する主な方法は、イベントバスによって行われます。 (EventBus): Bazel のさまざまな部分、ビルドごとに新しいインスタンスが作成されます イベントをポストでき、モジュールは対象のイベントのリスナーを登録できます。 できます。たとえば、以下がイベントとして表されます。

  • ビルドするビルド ターゲットのリストが決まっています (TargetParsingCompleteEvent)
  • 最上位の構成が決まっています (BuildConfigurationEvent)
  • ターゲットは正常にビルドされましたが、正常にビルドされませんでした(TargetCompleteEvent
  • テストが実行されました(TestAttemptTestSummary

これらのイベントの一部は、 Build イベント プロトコルBuildEvent です)。これにより、BlazeModule だけでなく、 Bazel プロセス外でビルドを監視します。この API には ファイル(Bazel はプロトコル メッセージを含む)が格納されているサーバー(いわゆる Build Event Service など)を使用して、イベントをストリーミングできます。

これは build.lib.buildeventservicebuild.lib.buildeventstream Java パッケージ。

外部リポジトリ

一方、Bazel は元々 monorepo(単一のソース Bazel は、構築に必要なすべてのものを含む 必ずしもそうとは限りません「外部リポジトリ」抽象化は、 この 2 つの橋渡しをしてくれます。これらはビルドに必要なコードですが、 メイン ソースツリーにはありません。

WORKSPACE ファイル

外部リポジトリのセットは、WORKSPACE ファイルを解析することによって決定されます。 たとえば、次のような宣言を行います。

    local_repository(name="foo", path="/foo/bar")

@foo という名前のリポジトリが利用可能になります。取得されるデータ Starlark ファイルで新しいリポジトリ ルールを定義して これを使用して新しい Starlark コードを読み込み、新しい Starlark コードを リポジトリ ルールなどです。

このケースに対処するため、WORKSPACE ファイルの解析( WorkspaceFileFunction など)は、load() で区切られたチャンクに分割されます。 ステートメント。チャンク インデックスは WorkspaceFileKey.getIndex() で示され、 インデックス X まで WorkspaceFileFunction を計算するということは、 X 番目の load() ステートメント。

リポジトリの取得

リポジトリのコードを Bazel で使用できるようにするには、以下を行う必要があります。 取得済みです。これにより、Bazel は $OUTPUT_BASE/external/<repository name>

リポジトリの取得は次の手順で行われます。

  1. PackageLookupFunction は、リポジトリが必要であることを認識し、 RepositoryLoaderFunction を呼び出す SkyKey としての RepositoryName
  2. RepositoryLoaderFunction がリクエストを次のアドレスに転送します。 不明確な理由で RepositoryDelegatorFunction(コードには、 Skyframe が再起動した場合に備えてダウンロードし直さないでください。ただし、 非常に確固たる推論)
  3. RepositoryDelegatorFunction は、リクエストされたリポジトリ ルールを検出します 要求があるまで WORKSPACE ファイルのチャンクを反復処理して取得し、 リポジトリが見つかりました
  4. リポジトリを実装する適切な RepositoryFunction が見つかる fetching;リポジトリの Starlark 実装か マップがハードコードされています。

リポジトリの取得は非常に困難であるため、キャッシュにはさまざまなレイヤがあります。 高価:

  1. チェックサムをキーとする、ダウンロードしたファイルのキャッシュがある (RepositoryCache)。そのためには、ソースコード内でチェックサムを とにかく密閉性に優れています。共有元 同じワークステーション上のすべての Bazel サーバー インスタンスを または出力ベースを指定できます。
  2. 「マーカー ファイル」$OUTPUT_BASE/external の下に、リポジトリごとに書き込まれます。 このオブジェクトには、その取得に使用されたルールのチェックサムが格納されています。Bazel による チェックサムが変更されず、再取得も行われません。この RepositoryDelegatorFunction.DigestWriter に実装されています。
  3. --distdir コマンドライン オプションは、別のキャッシュを使用して ダウンロードするアーティファクトを検索します。これは企業の設定に便利です Bazel はインターネットからランダムに取得すべきではありません。これは、 DownloadManager で実装されます。

リポジトリをダウンロードすると、リポジトリ内のアーティファクトがソースとして扱われます。 アーティファクトです通常、Bazel は最新性をチェックするため、これによって問題が発生します。 stat() を呼び出して、ソース アーティファクトの状態を確認できます。これらのアーティファクトは、 リポジトリの定義が変更されると無効になります。したがって、次のようになります。 外部リポジトリ内のアーティファクトの FileStateValue は、 外部リポジトリに保存しましたこれは ExternalFilesHelper によって処理されます。

マネージド ディレクトリ

外部リポジトリでは、Workspace のルートにあるファイルの変更が必要になる場合があります (たとえば、ダウンロードしたパッケージをアプリケーションのサブディレクトリのサブディレクトリに保管するパッケージ管理システムなど) 表示されます。これは、Bazel がソースをソースとして使用するという前提とは矛盾しています。 ユーザーによってのみ変更され、自身による変更ではなくなります。これにより、パッケージに すべてのディレクトリを参照します。このような 外部リポジトリの作業では、Bazel は次の 2 つのことを行います。

  1. Bazel が存在しないワークスペースのサブディレクトリをユーザーが指定できるようにします。 通信できるようになります。これらは .bazelignore というファイルにリストされており、 この機能は BlacklistedPackagePrefixesFunction に実装されています。
  2. ワークスペースのサブディレクトリから外部ディレクトリへのマッピングをエンコード ManagedDirectoriesKnowledge に取り込んで処理し、 FileStateValue は通常の場合と同じように、それらを参照します。 使用できます。

リポジトリのマッピング

複数のリポジトリが同じリポジトリに依存したい場合や、 異なるバージョン(これは「ダイヤモンド依存関係のインスタンスで あります)。たとえば、ビルド時に別々のリポジトリにある 2 つのバイナリが、 両方がラベル付きの Guava を参照する可能性があります。 @guava// から始まります。これは異なるバージョンを意味すると想定します。

そのため、Bazel では外部リポジトリのラベルを再マッピングして、 文字列 @guava// は、同じディレクトリ内の 1 つの Guava リポジトリ(@guava1// など)を参照できます。 1 つのバイナリ リポジトリと、別の Guava リポジトリ(@guava2// など)を 作成されます。

または、ダイヤモンドの結合にも使用できます。リポジトリが @guava1// に依存し、もう 1 つは @guava2// に依存し、リポジトリ マッピング これにより、正規の @guava// リポジトリを使用するように両方のリポジトリを再マッピングできます。

このマッピングは、WORKSPACE ファイルで repo_mapping 属性として指定されています。 定義できます。Skyframe に WorkspaceFileValue: 次の場所に組み込まれます。

  • Package.Builder.repositoryMapping: ラベル値を持つ変換に使用されます。 パッケージ内のルールの属性を RuleClass.populateRuleAttributeValues()
  • Package.repositoryMapping: 分析フェーズ( 読み込みで解析されない $(location) などの問題を解決 フェーズ)
  • load() ステートメントのラベルを解決するための BzlLoadFunction

JNI ビット

Bazel のサーバーは、ほとんど Java で記述されています。ただし、例外は、 Java は、実装時に単独では、または単独では実行できません。この 主にファイル システム、プロセス制御、アプリケーション システムとの 低レベルの要素です。

C++ コードは src/main/native の下にあり、Java クラスは native メソッドは次のとおりです。

  • NativePosixFilesNativePosixFileSystem
  • ProcessUtils
  • WindowsFileOperationsWindowsFileProcesses
  • com.google.devtools.build.lib.platform

コンソール出力

コンソール出力を出力するのは簡単そうに思えますが、 きめ細かなキャッシュ、詳細なキャッシュ保存、 きれいでカラフルなターミナル出力があり、 長時間稼働しているサーバーがあれば これは容易ではありません。

クライアントから RPC 呼び出しが到着した直後に、2 つの RpcOutputStream 出力されたデータを転送するためのインスタンスが作成されます(stdout と stderr の場合)。 クライアントに提供しますこれらはその後、OutErr(stdout、stderr)でラップされます。 ペア)。コンソールに出力する必要があるものはすべて、 使用できます。これらのストリームは BlazeCommandDispatcher.execExclusively()

出力はデフォルトで ANSI エスケープ シーケンスを使用して出力されます。そうでない場合 必要な場合(--color=no)、AnsiStrippingOutputStream によって削除されます。イン さらに、System.outSystem.err はこれらの出力ストリームにリダイレクトされます。 これは、デバッグ情報を出力するために System.err.println()。ただし、最終的にクライアントのターミナル出力に入ります。 (サーバーとは異なります)。プロセスが予期せず終了した場合は、 バイナリ出力(bazel query --output=proto など)を生成し、stdout を変更しない 行われます。

短いメッセージ(エラー、警告など)は、 EventHandler インターフェース。注目すべき点は、個人が投稿する内容とは EventBus(わかりにくい)。各 Event には EventKind(エラー、 警告、情報など)が含まれていて、Location( イベントの発生原因となったソースコードなど)が含まれます。

一部の EventHandler 実装は、受け取ったイベントを保存します。これは、 キャッシュされた各種処理で生じた情報を UI にリプレイする たとえば、キャッシュに保存された構成済みターゲットによって発行された警告などです。

一部のEventHandlerでは、最終的に投稿のきっかけとなったイベントも投稿できます。 イベントバス(通常の Event は表示されません)。これらは ExtendedEventHandler の実装とその主な用途は、キャッシュされたコンテンツの再生です。 EventBus 件のイベント。以下の EventBus イベントはすべて Postable を実装しますが、 EventBus に投稿されるものすべてに、必ずこのインターフェースを実装します。 ExtendedEventHandler によってキャッシュされたもののみ(これは、 ほとんど変わりません。適用されません)

ターミナルの出力は、ほとんど UiEventHandler から出力されます。 Bazel のあらゆる高度な出力フォーマットと進捗状況レポートを担当します。 あります。入力は 2 つあります。

  • イベントバス
  • Reporter を介してこのイベント ストリームにパイプで

コマンド実行マシン(たとえば、 Bazel など)からクライアントへの RPC ストリームに Reporter.getOutErr() を経由する必要があります。 これらのストリームに直接アクセスできますこれは、コマンドに必要な 大量のバイナリデータ(bazel query など)をダンプする

Bazel のプロファイリング

Bazel は高速です。Bazel も低速です。ビルドが増加する傾向が 100% に達するまで 画期的なことですそのため、Bazel にはプロファイラが含まれており、プロファイラを Bazel 自体をプロファイリングします。このクラスは、 適切な名前は Profiler です。デフォルトでオンになっていますが、 オーバーヘッドが許容されるように要約されたデータ。コマンドライン --record_full_profiler_data は、可能なものをすべて録画するようにします。

Chrome Profiler 形式でプロファイルを出力します。Chrome での表示が最適です データモデルはタスクスタックのものです。タスクを開始して終了し、 互いにきちんとネストされていることが前提です各 Java スレッドは 独自のタスクスタックを作成できますTODO: アクションと 継続を渡す方法

プロファイラは BlazeRuntime.initProfiler() で開始、停止し、 各 BlazeRuntime.afterCommand() で指定し、存続を試行します。 すべてをプロファイリングしますプロファイルに項目を追加するには Profiler.instance().profile() を呼び出します。Closeable を返します。そのクロージャは タスクの終わりを表します。try-with-resources と組み合わせて使用すると、 ステートメント。

MemoryProfiler でも基本的なメモリ プロファイリングが行われます。いつでもオンにできる 主に最大ヒープサイズと GC 動作を記録します

Bazel のテスト

Bazel には主に 2 種類のテストがあります。1 つは Bazel を「ブラック ボックス」として観察するテストです。および 分析フェーズのみを実行する リソースがあります前者を「統合テスト」と呼びます 後者の「単体テスト」がありますが、これらは統合テストに似ており、 統合性に劣ります実際の単体テストもあります。 できます。

インテグレーション テストには次の 2 種類があります。

  1. 1 つは、非常に複雑な bash テストフレームワークを使用して実装されたものです。 src/test/shell
  2. Java で実装されたもの。これらは、Terraform のサブクラスとして実装され、 BuildIntegrationTestCase

統合テスト フレームワークとして推奨されるものは BuildIntegrationTestCase です。 ほとんどのテストシナリオに十分対応できますJava フレームワークであるため、 多くの一般的な開発手法とデバッグ性とシームレスな統合を提供 できます。Google Cloud のインフラストラクチャには、BuildIntegrationTestCase クラスの多くの例が Bazel リポジトリ。

分析テストは BuildViewTestCase のサブクラスとして実装されます。こちらの BUILD ファイル、各種ヘルパーの書き込みに使用できるスクラッチ ファイル システム メソッドでは、構成済みターゲットのリクエスト、構成の変更、アサートを行うことができます さまざまな情報を提供します。