依存関係

問題を報告する ソースを表示

ターゲット A は、ビルド時または実行時に AA を必要とする場合、ターゲット B に依存します。リレーションにより関係は有向非巡回グラフ(DAG)をターゲットに誘発し、依存関係グラフと呼ばれます

ターゲットの直接的な依存関係は、依存関係グラフの長さ 1 のパスで到達できるその他のターゲットです。ターゲットの推移的依存関係とは、グラフ上の任意の長さのパスを介して依存するターゲットです。

実際、ビルドのコンテキストには、2 つの依存関係グラフ、実際の依存関係のグラフ宣言された依存関係のグラフがあります。ほとんどの場合、2 つのグラフはよく似ているため、この区別を行う必要はありませんが、以下の説明は役に立ちます。

実際の依存関係と宣言された依存関係

X が正しくビルドされるように Y が存在し、ビルドされ、最新の状態である必要がある場合、ターゲット X は、ターゲット Y実際には依存します。構築とは、生成、処理、コンパイル、リンク、アーカイブ、圧縮、実行、その他ビルド時に日常的に行われるあらゆる種類のタスクを意味します。

X のパッケージに X から Y への依存関係エッジがある場合、ターゲット X はターゲット Y への依存関係を宣言しています

正しいビルドの場合、実際の依存関係のグラフ A は、宣言された依存関係のグラフ D のサブグラフにする必要があります。つまり、A で直接接続されたノード x --> y のすべてのペアも D に直接接続する必要があります。DA近似値と言えます。

BUILD ファイル ライターは、すべてのルールの実際の直接依存関係をビルドシステムに明示的に宣言する必要があります。

この原則を守らなかった場合、未定義の動作が発生します。その結果、ビルドに失敗する可能性はありますが、ビルドが以前のオペレーションに依存していることや、推移的に宣言された依存関係がターゲットに存在する場合があります。Bazel は依存関係の欠落をチェックし、エラーを報告しますが、すべてのケースで完了するわけではありません。

実行時に A によって必要な場合でも、間接的にインポートされたすべてのものをリスト化する必要はありません(そうすべきではありません)。

ターゲット X のビルド中、ビルドツールは X の依存関係の推移的なクロージング全体を検査し、これらのターゲットの変更が最終結果に反映されることを確認するとともに、必要に応じて中間ツールを再構築します。

依存関係の推移性は一般的なミスにつながります。場合によっては、1 つのファイルのコードで間接的依存関係で提供されるコードを使用することがあります。宣言された依存関係グラフでは、推移的だが直接的なエッジではありません。BUILD ファイルには、間接的な依存関係は表示されません。このルールはプロバイダに直接依存しないため、次のタイムライン例に示すように、変更を追跡する方法はありません。

1. 宣言された依存関係が実際の依存関係と一致する

最初はすべてうまくいきますが、パッケージ a のコードはパッケージ b のコードを使用しています。 b パッケージのコードはパッケージ c のコードを使用しているため、ac に推移的に依存します。

a/BUILD b/BUILD
rule(
    name = "a",
    srcs = "a.in",
    deps = "//b:b",
)
      
rule(
    name = "b",
    srcs = "b.in",
    deps = "//c:c",
)
      
a / a.in b / b.in
import b;
b.foo();
    
import c;
function foo() {
  c.bar();
}
      
a、b、c をつなぐ矢印で宣言された依存関係グラフ
宣言された依存関係グラフ
宣言された依存関係グラフと、a、b、c をつなぐ矢印と一致する実際の依存関係グラフ
実際の依存関係グラフ

宣言された依存関係は、実際の依存関係に近似しています。問題ございません。

2. 宣言されていない依存関係を追加する

c に直接的な実際の依存関係を作成しているのに、ビルドファイル a/BUILD で宣言するのを忘れたコードを誰かが a に追加すると、潜在的な危険が発生します。

a / a.in  
        import b;
        import c;
        b.foo();
        c.garply();
      
 
a、b、c をつなぐ矢印で宣言された依存関係グラフ
宣言された依存関係グラフ
a、b、c をつなぐ矢印付きの実際の依存関係グラフ。矢印が A と C を接続するようになりました。これは宣言された依存関係グラフと一致しません
実際の依存関係グラフ

宣言された依存関係が実際の依存関係に近似することはなくなりました。2 つのグラフの推移的なクロージャが等しいが、a には c に対する未宣言の実際の依存関係があるので、問題ない場合があります。

3.宣言された依存関係グラフと実際の依存関係グラフとの違い

このハザードは、誰かが b をリファクタリングして c に依存しなくなると明らかになります。これにより、誤って a に不具合が生じることはありません。

  b/BUILD
 
rule(
    name = "b",
    srcs = "b.in",
    deps = "//d:d",
)
      
  b / b.in
 
      import d;
      function foo() {
        d.baz();
      }
      
a と b をつなぐ矢印で宣言された依存関係グラフ
                  b が c に接続しなくなると、a と c の接続が解除される
宣言された依存関係グラフ
b と c に接続しているが、b は c に接続しなくなった実際の依存関係グラフ
実際の依存関係グラフ

推移的に閉じられた場合でも、宣言された依存関係グラフは実際の依存関係の過小評価になります。ビルドが失敗する可能性があります。

この問題は、ステップ 2 で導入された a から c への実際の依存関係を BUILD ファイルで適切に宣言することで回避できました。

依存関係の種類

ほとんどのビルドルールには、さまざまな種類の汎用依存関係を指定するための 3 つの属性(srcsdepsdata)があります。以下で説明します。詳細については、すべてのルールに共通の属性をご覧ください。

多くのルールには、compilerresources など、ルール固有の依存関係用の追加属性もあります。詳細については、ビルド百科事典をご覧ください。

srcs の依存関係

ソースファイルを出力する 1 つまたは複数のルールによって直接使用されるファイル。

deps の依存関係

ヘッダー ファイル、シンボル、ライブラリ、データなどを提供する、個別にコンパイルされたモジュールを指すルール。

data の依存関係

ビルド ターゲットを正しく実行するには、一部のデータファイルが必要になる場合があります。これらのデータファイルはソースコードではなく、ターゲットのビルド方法には影響しません。たとえば、単体テストで関数の出力とファイルの内容を比較できます。単体テストをビルドする際にファイルは必要ありませんが、テストの実行時に必要になります。実行中に起動されるツールについても同様です。

ビルドシステムは、data としてリストされているファイルのみが利用可能である、分離されたディレクトリでテストを実行します。したがって、バイナリ、ライブラリ、テストの実行にファイルが必要な場合は、data(またはそれらを含むビルドルール)を指定します。例:

# I need a config file from a directory named env:
java_binary(
    name = "setenv",
    ...
    data = [":env/default_env.txt"],
)

# I need test data from another directory
sh_test(
    name = "regtest",
    srcs = ["regtest.sh"],
    data = [
        "//data:file1.txt",
        "//data:file2.txt",
        ...
    ],
)

これらのファイルは、相対パス path/to/data/file を使用してアクセスできます。テストでは、テストのソース ディレクトリのパスとワークスペースの相対パス(${TEST_SRCDIR}/workspace/path/to/data/file など)を結合することで、これらのファイルを参照できます。

ラベルを使用してディレクトリを参照する

BUILD ファイルを調べると、一部の data ラベルがディレクトリを参照していることがわかります。これらのラベルは、次の例のように /. または / で終わります。

非推奨 - data = ["//data/regression:unittest/."]

非推奨 - data = ["testdata/."]

非推奨 - data = ["testdata/"]

テストの場合、ディレクトリ内のすべてのデータファイルを使用できるため、この方法は特に便利です。

ただし、この操作は行わないでください。変更後に正しい増分再ビルド(およびテストの再実行)を確実に行うには、ビルドシステムがビルド(またはテスト)に入力されるファイルの完全なセットを認識している必要があります。ディレクトリを指定すると、(ファイルの追加や削除により)ディレクトリ自体が変更された場合にのみビルドシステムが再ビルドを行いますが、これらの変更はディレクトリ内のディレクトリに影響しないため、個々のファイルの編集を検出できません。ビルドシステムへの入力としてディレクトリを指定するのではなく、ディレクトリに含まれているファイルのセットを列挙するか、明示的にまたは glob() 関数を使用する必要があります。(** を使用して glob() を再帰化させます)。

推奨 - data = glob(["testdata/**"])

ただし、ディレクトリ ラベルを使用する必要があるシナリオもあります。たとえば、testdata ディレクトリにラベル構文に準拠していないファイルが含まれている場合、ファイルを明示的に列挙するか、glob() 関数を使用すると、無効なラベルエラーが発生します。この場合、ディレクトリ ラベルを使用する必要がありますが、上記の誤った再ビルドのリスクに注意してください。

ディレクトリ ラベルを使用する必要がある場合は、相対 ../ パスで親パッケージを参照できないため、//data/regression:unittest/. のような絶対パスを使用します。

複数のファイルを使用する必要がある外部ルール(テストなど)では、すべてのファイルへの依存関係を明示的に宣言する必要があります。filegroup() を使用して、BUILD ファイルにファイルをグループ化できます。

filegroup(
        name = 'my_data',
        srcs = glob(['my_unittest_data/*'])
)

これにより、テストのデータ依存関係としてラベル my_data を参照できます。

ビルド ファイル 公開設定