アーティファクト ベースのビルドシステム

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

このページでは、アーティファクト ベースのビルドシステムと、その背後にある考え方について説明します。 あります。Bazel はアーティファクト ベースのビルドシステムです。タスクベースのビルドは は、ビルド スクリプトよりも優れたステップであるため、 エンジニアがそれぞれ独自のタスクを定義できるようにします。

アーティファクト ベースのビルドシステムには、システムによって定義される少数のタスクがある 制限されています。エンジニアは依然としてシステムに 何をビルドするかですが、ビルド方法はビルドシステムによって決定されます。同じ タスクベースのビルドシステム、Bazel などのアーティファクト ベースのビルドシステム、 ビルドファイルの内容が大きく異なります。代わりに 命令型よりも多くの命令型で記述された完全なスクリプト言語の 出力の生成方法について記述されていますが、Bazel の buildfile は宣言型です。 ビルドするアーティファクト、その依存関係、ビルドするアーティファクトが その作成方法に影響を与える選択肢が限られています。エンジニアが bazel を実行したとき ビルドするターゲットのセット(対象)を指定します。 Bazel は、コンパイルの構成、実行、スケジュール設定を行います。 の手順(方法)。ビルドシステムは、リソースへのアクセスを はるかに強固な保証が可能になり 正確さを保証しながら効率が上がります

機能的な視点

アーティファクト ベースのビルドシステムと関数型ビルドシステムは、簡単にたとえられます。 説明します。従来の命令型プログラミング言語(Java、C、 Python など)を使用して、連続して実行するステートメントのリストを指定します。 タスクベースのビルドシステムを使用すると、プログラマーが一連のステップを 必要があります。関数型プログラミング言語(Haskell や ML など)を 一連の数式のように構造化されています。イン 関数型言語では、プログラマーは実行する計算を記述しますが、 計算がいつ、どのように実行されるかの詳細は あります。

これは、アーティファクト ベースのビルドシステムでマニフェストを宣言するという考え方に対応しています。 システムにビルドの実行方法を 決めさせます多くの問題は 関数型プログラミングを使用すると簡単に表現できますが、 この言語は多くの場合、このような処理を その正しさを強固に保証します 命令型の言語では不可能です。ML モデルを使って表現する最も 関数型プログラミングとは、1 つの部分を 一連のルールや関数を使って 別のデータに変換しますこれが ビルドシステムとは何かです。システム全体が実質的には ソースファイル(およびコンパイラなどのツール)を入力として受け取り、 出力として使用します。したがって ビルドのベースにするのが 根本的な問題になります

アーティファクト ベースのビルドシステムについて

Google のビルドシステムである Blaze は、最初のアーティファクト ベースのビルドシステムでした。Bazel Blaze のオープンソース版です。

Bazel でのビルドファイル(通常は BUILD という名前)は次のようになります。

java_binary(
    name = "MyBinary",
    srcs = ["MyBinary.java"],
    deps = [
        ":mylib",
    ],
)
java_library(
    name = "mylib",
    srcs = ["MyLibrary.java", "MyHelper.java"],
    visibility = ["//java/com/example/myproduct:__subpackages__"],
    deps = [
        "//java/com/example/common",
        "//java/com/example/myproduct/otherlib",
    ],
)

Bazel では、BUILD ファイルでターゲットを定義します。ここに示す 2 種類のターゲット java_binaryjava_library。すべてのターゲットは、1 つのアーティファクトに システムによって作成可能:バイナリターゲットは、転送可能なバイナリを生成する 実行され、ライブラリ ターゲットによって生成されるライブラリは、 使用できます。すべての標的には以下があります。

  • name: コマンドラインや他の環境からターゲットを参照する方法 ターゲット
  • srcs: ターゲットのアーティファクトを作成するためにコンパイルするソースファイル
  • deps: このターゲットの前にビルドされ、リンクされる必要がある他のターゲット

依存関係は、同じパッケージ(MyBinary:mylib への依存関係)または同じソース階層内の別のパッケージへの依存関係 (//java/com/example/common に対する mylib の依存関係など)。

タスクベースのビルドシステムと同様に、Bazel のコマンドラインを使用してビルドを実行します。 ツールです。MyBinary ターゲットをビルドするには、bazel build :MyBinary を実行します。変更後 次のコマンドをクリーンなリポジトリの Bazel に初めて入力すると、

  1. ワークスペース内のすべての BUILD ファイルを解析し、依存関係のグラフを作成する いくつかあります。
  2. グラフを使用して MyBinary の推移的依存関係を特定します。 MyBinary が依存するすべてのターゲットと、そのターゲットが依存するすべてのターゲットです。 再帰的に確認します。
  3. それぞれの依存関係を順番にビルドします。Bazel ではまず、コンテナ イメージの 他の依存関係がなく、どの依存関係が ターゲットごとにビルドする必要がありますすべてのターゲットの 作成されたら、Bazel はそのターゲットのビルドを開始します。このプロセス MyBinary の推移的依存関係のすべてが満たされるまで継続します。 構築しました。
  4. MyBinary をビルドし、すべてのインスタンスとリンクする最終的な実行可能バイナリを生成します。 依存関係をインストールします。

基本的に、ここで起きていることは タスクベースのビルドシステムを使用する場合とは異なります。実際、 最終的な結果は同じバイナリであり、それを生成するプロセスは それらの依存関係を見つけるために多くのステップを分析し 説明しますただし、重大な違いがあります。1 つ目は、 ステップ 3: Bazel は、各ターゲットが Java ライブラリのみを生成することを認識しているため、 Java コンパイラを実行するだけでよく、 これらのステップを並行して実行しても安全であることを認識します。 これにより、事前トレーニング済みモデルよりも マルチコア マシン上で一度に 1 つのターゲットを 1 つずつターゲットとし、 アーティファクト ベースのアプローチでは、実行はビルドシステムが独自に行う 並列処理に関する保証を強化できます。

ただし、利点は並列処理にとどまりません。次に、 アプローチにより、デベロッパーが変更を加えずに 2 度目に bazel build :MyBinary を入力すると、Bazel が短時間で終了することが明らかになります。 ターゲットが最新であるというメッセージとともに 1 秒経過します。これは、 前に説明した関数型プログラミングのパラダイムによって (Bazel は、各ターゲットが Java の実行のみの結果であることを認識しています) 生成され、Java コンパイラからの出力が 入力内容が変更されない限り、出力を再利用できます。 この分析はあらゆるレベルで機能します。MyBinary.java が変更された場合、Bazel は MyBinary を再構築しますが、mylib は再利用します。宣言型のソースファイルが //java/com/example/common の変更により、Bazel はそのライブラリを再ビルドします。 mylibMyBinary ですが、//java/com/example/myproduct/otherlib を再利用します。 Bazel は各ステップで実行されるツールの特性を認識しているため、 毎回最小限のアーティファクトしか再構築できず 古いビルドが生成されないことを保証します。

タスクではなくアーティファクトの観点からビルドプロセスを再構成するのは微妙 パワフルですプログラマーにさらされる柔軟性を下げて、ビルドの ビルドの各ステップで行われていることを詳しく知ることができます。機能 この知識を活用してビルドを並列化し、ビルドを効率化します。 その出力の再利用に役立ちますしかし、これはほんの最初のステップで、 並列処理の構成要素と再利用が分散システムの 基盤を形成します 構築できます

Bazel のその他の便利な使い方

アーティファクト ベースのビルドシステムは、並列処理に関する問題を根本的に解決する タスクベースのビルドシステムに 固有の特性がありますしかし、 対処していない問題を簡単に解決できます。Bazel の優れた機能 それらについて話し合ってから次に進みます。

依存関係としてのツール

以前直面した問題の一つは、ビルドがインストールされたツールに依存することで さまざまなシステム間でビルドを再現することは、 ツールのバージョンや場所に応じて異なります。この問題はさらに困難になります 使用する言語によって、言語ごとに異なるツールが ビルドまたはコンパイルしているプラットフォーム(Windows と Linux など) それぞれのプラットフォームでは 同じジョブです

Bazel は、この問題の第 1 段階で、ツールを依存関係として扱い、 作成されます。ワークスペース内のすべての java_library が暗黙的に Java に依存する 広く知られたコンパイラになりますBazel がビルドしたバージョンを java_library: 指定されたコンパイラが利用可能かどうかを確認します。 検出できます。他の依存関係と同様に、Java コンパイラが 依存するすべてのアーティファクトが再構築されます。

Bazel では、この問題の 2 つ目の要素であるプラットフォームの独立性を、 ビルド構成。Google Pixel 8 と 構成の種類によって異なります。

  • ホスト構成: ビルド中に実行するツールをビルドする
  • ターゲット構成: 最終的にリクエストしたバイナリのビルド

ビルドシステムの拡張

Bazel では、一般的なプログラミング言語向けのターゲットが エンジニアは常により多くのことを希望します。これはタスクベースの あらゆる種類のビルドプロセスをサポートできる点です。 アーティファクト ベースのビルドシステムであきらめない方がよいでしょう。 幸い、Bazel ではサポート対象のターゲット タイプを カスタムルールの追加をご覧ください。

Bazel でルールを定義するには、ルールの作成者がルールで宣言する入力を宣言します。 (BUILD ファイルで渡される属性の形式)と固定の 出力のセットを指定します。作成者は、作成するアクションも定義します。 そのルールによって生成されます。各アクションは入力と出力を宣言し、 特定の実行可能ファイルの実行、特定の文字列のファイルへの書き込みを行います。 入力と出力を介して他のアクションに接続できます。つまり、アクションが は、ビルドシステムの最下位レベルのコンポーズ可能な単位であり、アクションで 宣言された入力と出力のみを使用する限り、任意のデータ Bazel は、必要に応じてアクションのスケジューリングと結果のキャッシュを行います。

アクション デベロッパーを停止する方法がないため、このシステムは完全ではない プロセスの一部として非決定的プロセスを導入するなど、 できます。しかし、これは実際にはなかなか起こるわけではなく、 不正行為の可能性が アクションレベルまで大幅に低下 エラーの可能性が高まります。多くの一般的な言語とツールをサポートするルールは、 ほとんどのプロジェクトで、独自にプロジェクトを定義する必要は できます。そのような場合でも、1 つのルールでルールを定義するだけで済みます。 リポジトリの一元的な場所に配置できるため、ほとんどのエンジニアが それらのルールの実装を気にする必要がありません。

環境の隔離

アクションは、他のタスクと同じ問題が発生するかもしれない 両方で同じ書き込みを行うアクションを どうなるでしょうかBazel はこれらを サンドボックスを使用することで、競合を防げます。ON: サポート対象 すべてのアクションがファイルシステムを介して他のすべてのアクションから分離されている できます。実質的には、各アクションが参照できるのは、 宣言した入力と出力を含むファイル システム 生成します。これは、Linux 上の LXC などのシステムで強制適用されます。 Docker の背後で行われます。つまり、あるアクションが他のアクションと競合することは もう一つは、宣言していないファイルを読み取ることができないため、 宣言されていないファイルは、アクションが呼び出されたときに破棄されます。 終了します。Bazel ではサンドボックスを使用して、アクションによる通信を制限しています。 通信できます。

外部依存関係を決定論的にする

残っている問題が 1 つあります。それは、多くの場合、ビルドシステムにダウンロードし、 依存関係(ツールやライブラリ)を、外部ソースから 直接構築できます。これは、 @com_google_common_guava_guava//jar 依存関係(JAR ファイルをダウンロードする) ダウンロードします。

現在のワークスペースの外部にあるファイルに依存するとリスクが高くなります。これらのファイルは いつでも変更できるため、ビルドシステムが定期的に 新鮮さを失いません対応する変更なしでリモート ファイルが変更された場合 また、再現不能なビルド(ビルド)につながる可能性もあります。 ある日は機能し、気づかれないという理由で明確な理由もなく次の日は失敗することもあります。 必要があります。最後に、外部依存関係により、新たなセキュリティ リスクが リスクとは、攻撃者がネットワークへの侵入に 依存関係ファイルを何かに置き換えることができます。 ユーザーがビルドを完全に制御できる可能性がある その出力を確認します。

根本的な問題は、このようなイベントをビルドシステムに認識させたいことです。 ソース管理にチェックインせずに使用できます。依存関係の更新 意識的に選択する必要がありますが、その選択は 個々のエンジニアが管理したり、管理者が自動的に ありませんこれは、「Live at Head」モデルであっても、引き続きビルドが必要であるためです。 確定的になります。つまり、過去 1 か月間に 週には、依存関係が現状ではなくそのままの状態で表示されます。 います。

Bazel やその他のビルドシステムでは、この問題に対処するために、 外部 IP アドレスに関連付けられたすべての暗号ハッシュがリストされた、Workspace 全体のマニフェスト ファイル。 依存関係を確認できます。ハッシュは、各トークンを一意に表現するための ファイル全体をソース管理システムにチェックインせずに、新しい 外部依存関係がワークスペースから参照される場合、その依存関係のハッシュが 手動で追加することも、自動的に追加することもできます。Bazel がコンテナ イメージを実行した場合、 キャッシュに保存された依存関係の実際のハッシュを、想定される ハッシュが異なる場合のみファイルを再ダウンロードします。

ダウンロードしたアーティファクトのハッシュが、 そのマニフェスト内のハッシュが更新されない限り、ビルドは失敗します。この 変更は自動的に行われますが、その変更が承認され、チェックインされる必要があります。 ビルドが新しい依存関係を受け入れる前に、ソース管理を行う必要があります。つまり 依存関係がいつ更新されたかという記録があり、 ワークスペース ソースで対応する変更を行わないと依存関係を変更できません。 また、古いバージョンのソースコードをチェックアウトすると、 ビルドでも、その時点で使用されていたのと同じ依存関係が使用されることが保証される チェックインしたときに失敗します(そうしないと、それらの依存関係がインクルードされると失敗します)。 はご利用いただけなくなりました)。

もちろん、リモート サーバーが利用できなくなったり、 破損したデータの提供が開始されるため、すべてのビルドが失敗する可能性があります。 依存関係の別のコピーがない場合は、これを回避するには 重要なプロジェクトの場合は、そのすべてのプロジェクトのすべての 信頼して制御しているサーバーやサービスにデプロイできます。それ以外の場合 ビルドシステムの設定については、常にサードパーティが管理し、 安全であることを保証します。