依存関係

ビルド時または実行時に AB を必要とする場合、ターゲット A はターゲット B に依存します。依存関係グラフとは、依存関係グラフと呼ばれる関係で、ターゲットに対して有向非巡回グラフ(DAG)を生成します。

ターゲットの直接依存関係は、依存関係グラフの長さ 1 のパスによって到達可能な他のターゲットです。ターゲットの推移的な依存関係は、グラフ内の任意の長さのパスを介して依存するターゲットです。

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

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

ターゲット X は、X を正しくビルドするために Y が存在し、ビルドされ、最新でなければならない場合、実際にターゲット Y に依存します。ビルドとは、生成、処理、コンパイル、リンク、アーカイブ、圧縮、実行など、ビルド中に定期的に行われるタスクを指します。

X のパッケージに X から Y への依存関係エッジがある場合、ターゲット X にはターゲット Y に対する宣言された依存関係があります。

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

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

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

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

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

依存関係は推移的であるため、よくある間違いにつながります。あるファイル内のコードが、間接的な依存関係(宣言された依存関係グラフの推移的だが直接的なエッジではない)によって提供されるコードを使用する場合があります。間接的な依存関係は 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 に追加した後、ビルドファイル a/BUILD で宣言するのを忘れると発生します。

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 など)もあります。これらの詳細については、Build Encyclopedia をご覧ください。

srcs の依存関係

ソースファイルを出力するルールによって直接使用されるファイル。

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 をデータ依存関係として参照できます。

BUILD ファイル 公開設定