依存関係の管理

問題を報告する ソースを表示 夜間 7.4 をタップします。 7.3 7.2 7.1 7.0 6.5

前のページを見ていくと、1 つのテーマが何度も繰り返されています。 独自のコードはかなり簡単ですが、依存関係の管理はとても簡単です。 困難になります。依存関係にはさまざまな種類があり、場合によっては タスクへの依存関係(例: 「リリースをマークする前にドキュメントを push する」 「 「コードをビルドするには、最新バージョンのコンピュータ ビジョン ライブラリが必要です」)。 コードベースの他の部分に内部依存関係がある場合があります。 場合によっては、別のチームが所有するコードやデータに外部から依存している 認証できます。いずれにしても、「これを行うにはあれが必要」という考えは、ビルドシステムの設計で繰り返し発生するものであり、依存関係の管理はビルドシステムの最も基本的な作業と言えます。

モジュールと依存関係の取り扱い

Bazel などのアーティファクトベースのビルドシステムを使用するプロジェクトは、一連のモジュールに分割され、モジュールは BUILD ファイルを使用して相互の依存関係を表現します。これらのモジュールと依存関係を適切に整理すると、ビルドシステムのパフォーマンスとメンテナンスに必要な作業量の両方に大きな影響を与える可能性があります。

きめ細かいモジュールと 1:1:1 ルールの使用

アーティファクト ベースのビルドを構造化する際に最初に発生する問題は、個々のモジュールに含める機能の量を決定することです。Bazel では、モジュールは、java_librarygo_binary などのビルド可能ユニットを指定するターゲットで表されます。極端な例として、1 つの BUILD ファイルをルートに配置し、そのプロジェクトのすべてのソースファイルを再帰的にグルーピングすることで、プロジェクト全体を 1 つのモジュールに含めることができます。極端な場合、ほぼすべてのソースファイルを独自のモジュールにできます。その場合、各ファイルは、依存する他のすべてのファイルを BUILD ファイルにリストする必要があります。

ほとんどのプロジェクトはこれらの極端な範囲に位置し、選択には パフォーマンスと保守性のトレードオフが 考慮されます各モジュールに 1 つのモジュールを プロジェクト全体という意味で、BUILD ファイル 外部依存関係を追加するときに使用しますが、その場合、ビルドシステムには必ず 常にプロジェクト全体を一度にビルドします。つまり、ビルドの一部を並列化または分散することはできず、すでにビルドされている部分をキャッシュに保存することもできません。ファイルごとに 1 つのモジュールの場合はその逆です。ビルドシステムはビルドのステップのキャッシュとスケジューリングにおいて最大限の柔軟性がありますが、エンジニアは、どのファイルを参照するかを変更するたびに依存関係のリストを維持するためにより多くの労力を費やす必要があります。

正確な粒度は言語によって異なりますが(場合によっては言語内でも異なります)、Google では、タスクベースのビルドシステムで通常作成するモジュールよりもはるかに小さいモジュールを優先する傾向があります。標準的な本番環境バイナリは Google は何万ものターゲットに依存していることが多く、中規模の企業でさえ コードベース内で数百のターゲットを 所有できますたとえば、 パッケージの強力な組み込み概念がある Java。通常、各ディレクトリは 1 つのパッケージ、ターゲット、BUILD ファイルを含む(Pants、別のビルドシステム) (1:1:1 ルールと呼んでいます)。パッケージ化規則が緩い言語では、BUILD ファイルごとに複数のターゲットが定義されることがよくあります。

小さいビルド ターゲットのメリットは、分散ビルドの速度が向上し、ターゲットの再ビルド頻度が低下するため、大規模な場合に特に顕著になります。テストが登場すると、利点はさらに魅力的になります。なぜなら、 ターゲットを細かく設定することで、ビルドシステムが 特定のテストによって影響を受ける可能性がある一部のテストのみを実行する あります。Google は、小さなターゲットを使用することのシステム的なメリットを信じています。そのため、デベロッパーの負担を軽減するために、BUILD ファイルを自動的に管理するツールに投資することで、デメリットを軽減する取り組みを進めています。

buildifierbuildozer などの一部のツールは、Bazel の buildtools ディレクトリで使用できます。

モジュールの可視性の最小化

Bazel などのビルドシステムでは、各ターゲットに可視性(他のターゲットが依存する可能性があるターゲットを決定するプロパティ)を指定できます。プライベート ターゲット その BUILD ファイル内でのみ参照できます。ターゲットは、より広範囲の 明示的に定義された BUILD ファイルのリストのターゲットに対する可視性、または すべてのターゲットに公開されます

ほとんどのプログラミング言語と同様に、通常は可視性を最小限に抑えることが 必要があります。通常、Google のチームは、 これらのターゲットは、Google のあらゆるチームが利用できる、広く使用されているライブラリを表します。 コードを使用する前に他のチームとの調整が必要なチームは、ターゲットの公開設定としてカスタマー ターゲットの許可リストを維持します。各チームの内部実装ターゲットは、チームが所有するディレクトリのみに制限されます。また、ほとんどの BUILD ファイルには、非公開ではないターゲットが 1 つだけあります。

依存関係の管理

モジュールは互いを参照できる必要があります。データ量を少なくするデメリットは コードベースをきめ細かなモジュールに分割するには、依存関係と (ただし、ツールを使えば自動化できます)。通常、これらの依存関係を表現することが、BUILD ファイルのコンテンツの大部分になります。

内部依存関係

きめ細かいモジュールに分割された大規模なプロジェクトでは、ほとんどの依存関係が内部的なもの(同じソース リポジトリで定義され、ビルドされた別のターゲット)である可能性があります。内部依存関係が外部依存関係と異なる点は、 ビルド済みアーティファクトとしてダウンロードされるのではなく、ソースからビルドされます。 表示されます。また、内部依存関係に「バージョン」という概念はありません。ターゲットとその内部依存関係はすべて、リポジトリ内の同じ commit / リビジョンで常にビルドされます。解決すべき問題として 慎重に扱う必要がありますが、 推移的依存関係(図 1)を示します。ターゲット A がターゲット B に依存し、ターゲット B が共通ライブラリ ターゲット C に依存しているとします。ターゲット A がクラスを使用できるようにする 定義されているでしょうか。

推移的依存関係

図 1. 推移的依存関係

基盤となるツールに関する限り、問題ありません。両方 B と C は、ビルド時にターゲット A にリンクされるため、 C は A に認識されます。Bazel では長年この機能が許可されていましたが、Google の成長に伴い問題が発生し始めました。B がリファクタリングされ、C に依存する必要がなくなったとします。B の C への依存関係が削除された場合、A と、B への依存関係を介して C を使用している他のターゲットが破損します。実質的に、ターゲットの依存関係は公開コントラクトの一部となり、安全に変更できなくなりました。つまり、時間の経過とともに依存関係が蓄積され、Google で構築される 遅くなり始めました。

Google は最終的に「厳格な推移的 (Bazel で依存関係モード)を見てみましょう。このモードでは、Bazel は、ターゲットがシンボルに直接依存せずにシンボルを参照しようとしているかどうかを検出します。そうである場合、エラーと、依存関係を自動的に挿入するために使用できるシェルコマンドを返して失敗します。この変更を Google のコードベース全体にロールアウトし、 何百万ものビルド ターゲットをリファクタリングして、 数年にわたる取り組みでしたが、それだけの価値がありました。当社のビルドは ターゲットの不要な依存関係が少なくなったため、はるかに高速になりました。 エンジニアは、心配することなく、不要な依存関係を削除できる 依存するターゲットの破棄について学びました。

通常どおり、厳格な推移的依存関係を適用すると、トレードオフが発生します。その結果 よく使用するライブラリをリストする必要があるため、ビルドファイルの内容が冗長になる 多くの場所に明示的に取り込んだり、 BUILD ファイルに依存関係を追加するのに多くの時間を費やす必要がありました。それ以来 このトイルを軽減するツールを開発しました。 開発せずに BUILD ファイルに追加することもできます。 できます。ただし、このようなツールがなくても、コードベースのスケーリングに伴うトレードオフは十分に価値があると判断しています。BUILD ファイルに依存関係を明示的に追加するのは 1 回限りの費用ですが、暗黙的な伝播依存関係を処理すると、ビルド ターゲットが存在する限り、継続的な問題が発生する可能性があります。Bazel 厳格な推移的依存関係を Java コードにネイティブに実装されています

外部依存関係

依存関係が内部的でない場合は、外部にする必要があります。外部依存関係は、 ビルドシステムの外部でビルドおよび保存されたアーティファクトに 保存されたデータなどです「 依存関係がアーティファクト リポジトリ(通常は インターネット経由で転送されます。また、ソースから構築されるのではなく、そのまま使用されます。外部依存関係と内部依存関係の最大の違いの 1 つは、外部依存関係にはバージョンがあり、それらのバージョンはプロジェクトのソースコードとは独立して存在することです。

自動と手動の依存関係管理

ビルドシステムを使用すると、外部依存関係のバージョンを管理できる 手動または自動で行えます手動で管理した場合、ビルドファイルは アーティファクト リポジトリからダウンロードするバージョンを明示的にリストする セマンティック バージョン文字列がよく使用されます。 として 1.1.4 にします。自動的に管理される場合、ソースファイルには ビルドシステムは常に最新バージョンをダウンロードします。たとえば、Gradle では依存関係のバージョンを「1.+」として宣言し、メジャー バージョンが 1 であれば、依存関係のマイナー バージョンまたはパッチ バージョンをすべて使用可能に指定できます。

自動管理される依存関係は小規模なプロジェクトでは便利ですが、通常、規模が大きいプロジェクトや複数のエンジニアが作業しているプロジェクトでは、災害の原因になります。自動的に管理される依存関係の問題は、バージョンが更新されるタイミングを制御できないことです。外部の関係者によるセキュリティ侵害や 更新(セマンティック バージョニングの使用を主張している場合も含む)が伴うため、 変更された内容を簡単に検出できない 動作状態にロールバックできますビルドが壊れなくても 追跡できない微妙な動作やパフォーマンスの変化などが考えられます。

一方、手動で管理される依存関係はソース管理の変更を必要とするため、簡単に検出してロールバックできます。また、古いバージョンのリポジトリをチェックアウトして、古い依存関係でビルドすることもできます。Bazel では、すべての依存関係のバージョンを手動で指定する必要があります。中程度の規模でも、手動バージョン管理のオーバーヘッドは、安定性を確保するために十分な価値があります。

1 バージョンのルール

通常、ライブラリの異なるバージョンは異なるアーティファクトで表されるため、理論的には、同じ外部依存関係の異なるバージョンをビルドシステムで異なる名前で宣言できない理由はありません。これにより、各ターゲットは使用する依存関係のバージョンを選択できます。実際には多くの問題が発生するため、Google は厳格な 1 つのバージョンのルール コードベースでサードパーティのすべての依存関係を モニタリングできます

複数バージョンを許可する際の最大の問題は、ダイヤモンドの依存関係 あります。ターゲット A がターゲット B と外部ライブラリの v1 に依存しているとします。後でターゲット B をリファクタリングして、同じ外部ライブラリの v2 への依存関係を追加すると、ターゲット A は破綻します。これは、同じライブラリの 2 つの異なるバージョンに暗黙的に依存することになるためです。ターゲットのユーザーがすでに別のバージョンに依存している可能性があるため、ターゲットから複数のバージョンがあるサードパーティ ライブラリに新しい依存関係を追加することは、実際には安全ではありません。1 つのバージョン ルールに従うと、特定のバージョンに target は、サードパーティのライブラリへの依存関係を追加する 同じバージョンにあるものなので 問題なく共存できます

推移的な外部依存関係

外部依存関係の転送依存関係の処理は特に困難です。Maven Central などの多くのアーティファクト リポジトリでは、 他のアーティファクトの特定のバージョンへの依存関係を 使用できます。Maven や Gradle などのビルドツールは、多くの場合、それぞれを再帰的にダウンロードします。 デフォルトで推移的依存関係があります。つまり、依存関係を 1 つ追加すると、 プロジェクトで数十のアーティファクトがダウンロードされ、 確認できます。

これはとても便利です。新しいライブラリへの依存関係を追加する際に、 ライブラリの推移的依存関係をそれぞれ追跡するのは大変な労力です。 すべて手動で追加できますただし、大きなデメリットもあります。 同じサードパーティ ライブラリの異なるバージョンに依存している可能性があります。 必然的に単一バージョン ルールに違反し、 解決します。ターゲットが、2 つの外部ライブラリを使用して 同じ依存関係の異なるバージョンを使用する場合、どの依存関係を あります。また、外部依存関係を更新すると、新しいバージョンで一部の依存関係の競合バージョンが pull され始めると、コードベース全体で関連性のないと思われる障害が発生する可能性があります。

このため、Bazel は推移的な依存関係を自動的にダウンロードしません。残念ながら、解決策はありません。Bazel の代替手段は、 このグローバル ファイルには、リポジトリの外部接続の モジュール全体を通じて依存関係に使用される明示的なバージョンを できます。幸い Bazel が提供するツールを使うと 一連の Maven の推移的依存関係を含むファイルを生成する アーティファクトですこのツールを 1 回実行して、最初の WORKSPACE ファイルを生成できます。 そのファイルを手動で更新してバージョンを 必要があります。

ここでも、利便性とスケーラビリティのどちらかを選択する必要があります。小 プロジェクトでは、推移的依存関係の管理について心配する必要がない方がよい 自己回帰モデルを使用することで、 確認します。この戦略の魅力は、組織がイノベーションを起こすにつれ コードベースが増大し、競合や予期しない結果はますます増加する あります。規模が大きくなると、依存関係を手動で管理するコストが大きくなる 自動依存関係によって引き起こされる問題に対処するコストよりも あります。

外部依存関係を使用したビルド結果のキャッシュ保存

外部依存関係は、多くの場合、安定版のライブラリをリリースするサードパーティによって提供されます。ソースコードが提供されないこともあります。一部 組織は、独自のコードの一部を 他のコードがサードパーティとして依存できるようにし、 内部依存関係よりもはるかに効率的ですアーティファクトのビルドは遅いがダウンロードは速い場合、理論的にはビルドを高速化できます。

しかし、これには多くのオーバーヘッドと複雑さが伴います。 各アーティファクトのビルドと アーティファクト リポジトリに保管されます。クライアントは、その状態を維持し、 できます。また、システムのさまざまな部分がリポジトリ内のさまざまなポイントからビルドされるため、ソースツリーが一貫したビューではなくなり、デバッグがはるかに難しくなります。

ビルドに時間がかかるアーティファクトの問題を解決するには、 前述のように、リモート キャッシュをサポートするビルドシステムを使用する。そのような ビルドシステムは、すべてのビルドの結果のアーティファクトを特定の場所に保存する エンジニア間で共有されます。そのため、開発者が依存するアーティファクトが 第三者によって最近ビルドされた場合、ビルドシステムは モデルに任せることができます。これにより、Google Cloud のインフラストラクチャが提供する アーティファクトに直接依存しながらも、ビルドがそのまま 常に同じソースからビルドされているかのように一貫性が保たれます。これは Google で内部的に使用されている戦略であり、Bazel はリモート キャッシュを使用するように構成できます。

外部依存関係のセキュリティと信頼性

サードパーティのソースからのアーティファクトへの依存には、本質的にリスクがあります。こちらの サードパーティ ソース(アーティファクト リポジトリなど)が ダウンロードできない場合、ビルド全体が停止する可能性があります。 使用します。セキュリティ リスクもあります。サードパーティ システムが攻撃者に侵害された場合、攻撃者は参照されているアーティファクトを独自の設計のものに置き換え、任意のコードをビルドに挿入できるようになります。どちらの問題も、作成したアーティファクトをミラーリングすることで軽減できます。 管理しているサーバーに依存し、ビルドシステムによるアクセスをブロックします。 サードパーティのアーティファクトリポジトリに デプロイできますトレードオフとして、これらのミラーの維持には労力とリソースが必要になるため、使用するかどうかはプロジェクトの規模によって異なります。セキュリティ問題は 完全に防止できます。そのため、各 API のハッシュを サードパーティ アーティファクトがソース リポジトリで指定されると、ビルドが アーティファクトが改ざんされた場合に失敗します。もう一つの選択肢は プロジェクトの依存関係をベンダリングすることで問題を回避できます。プロジェクトが ベンダーが依存してソース管理にチェックインし、 プロジェクトのソースコードをソースまたはバイナリとして提供します。これは実質的に、 すべての外部依存関係が内部リソースに変換されることを 確認します。Google では、このアプローチを社内で使用し、Google 全体で参照されているすべてのサードパーティ ライブラリを、Google ソースツリーのルートにある third_party ディレクトリにチェックしています。ただし、これが Google で機能するのは、Google の ソース管理システムは極めて大規模な monorepo を処理するためにカスタムビルドされているため、 ベンダリングは、すべての組織にとって選択肢となるとは限りません。