このチュートリアルでは、事前作成された Bazel プロジェクトを使用して、Bazel でコード内の依存関係をトレースする方法について説明します。
言語と --output
フラグの詳細については、Bazel クエリ リファレンスと Bazel cquery リファレンスのマニュアルをご覧ください。コマンドラインで bazel help query
または bazel help cquery
と入力して、IDE でヘルプを取得します。
目標
このガイドでは、プロジェクトのファイル依存関係について詳しく確認するために使用できる一連の基本的なクエリについて説明します。Bazel と BUILD
ファイルの仕組みに関する基本的な知識をお持ちの、新しい Bazel デベロッパーを対象としています。
前提条件
まず、Bazel をインストールします(まだインストールしていない場合)。このチュートリアルではソース管理に Git を使用するため、最適な結果を得るには Git もインストールしてください。
依存関係グラフを可視化するために、Graphviz というツールを使用します。このツールは、ダウンロードして使用できます。
サンプル プロジェクトを取得する
次に、任意のコマンドライン ツールで次のコマンドを実行して、Bazel の Examples リポジトリからサンプルアプリを取得します。
git clone https://github.com/bazelbuild/examples.git
このチュートリアルのサンプル プロジェクトは examples/query-quickstart
ディレクトリにあります。
スタートガイド
Bazel クエリとは
クエリを使用すると、BUILD
ファイル間の関係を分析し、出力から有用な情報を調べることで、Bazel コードベースについて学習できます。このガイドでは、基本的なクエリ関数をいくつか紹介しますが、その他のオプションについては、クエリガイドをご覧ください。クエリを使用すると、BUILD
ファイルを手動で移動しなくても、大規模なプロジェクトの依存関係を確認できます。
クエリを実行するには、コマンドライン ターミナルを開いて次のコマンドを入力します。
bazel query 'query_function'
シナリオ
Cafe Bazel とそのシェフの関係を詳しく調べるシナリオを想像してみてください。このカフェでは、ピザとマカロニ チーズのみを販売しています。プロジェクトの構造は次のとおりです。
bazelqueryguide
├── BUILD
├── src
│ └── main
│ └── java
│ └── com
│ └── example
│ ├── customers
│ │ ├── Jenny.java
│ │ ├── Amir.java
│ │ └── BUILD
│ ├── dishes
│ │ ├── Pizza.java
│ │ ├── MacAndCheese.java
│ │ └── BUILD
│ ├── ingredients
│ │ ├── Cheese.java
│ │ ├── Tomatoes.java
│ │ ├── Dough.java
│ │ ├── Macaroni.java
│ │ └── BUILD
│ ├── restaurant
│ │ ├── Cafe.java
│ │ ├── Chef.java
│ │ └── BUILD
│ ├── reviews
│ │ ├── Review.java
│ │ └── BUILD
│ └── Runner.java
└── MODULE.bazel
このチュートリアルでは、特に指示がない限り、必要な情報を BUILD
ファイルで探さず、クエリ関数のみを使用してください。
プロジェクトは、Cafe を構成するさまざまなパッケージで構成されています。これらは、restaurant
、ingredients
、dishes
、customers
、reviews
に分割されています。これらのパッケージ内のルールは、さまざまなタグと依存関係を使用して Cafe のさまざまなコンポーネントを定義します。
ビルドの実行
このプロジェクトには、Runner.java
内にメインメソッドが含まれています。このメソッドを実行すると、カフェのメニューが印刷されます。bazel build
コマンドを使用して Bazel でプロジェクトをビルドし、:
を使用してターゲットの名前が runner
であることを通知します。ターゲットを参照する方法については、ターゲット名をご覧ください。
このプロジェクトをビルドするには、ターミナルに次のコマンドを貼り付けます。
bazel build :runner
ビルドが成功すると、出力は次のようになります。
INFO: Analyzed target //:runner (49 packages loaded, 784 targets configured).
INFO: Found 1 target...
Target //:runner up-to-date:
bazel-bin/runner.jar
bazel-bin/runner
INFO: Elapsed time: 16.593s, Critical Path: 4.32s
INFO: 23 processes: 4 internal, 10 darwin-sandbox, 9 worker.
INFO: Build completed successfully, 23 total actions
ビルドが正常に完了したら、次のコマンドを貼り付けてアプリケーションを実行します。
bazel-bin/runner
--------------------- MENU -------------------------
Pizza - Cheesy Delicious Goodness
Macaroni & Cheese - Kid-approved Dinner
----------------------------------------------------
メニュー項目のリストと簡単な説明が表示されます。
ターゲットの探索
このプロジェクトでは、材料と料理が独自のパッケージにリストされています。クエリを使用してパッケージのルールを表示するには、bazel query package/…
コマンドを実行します。
この場合、次のコマンドを実行して、このカフェの材料と料理を確認できます。
bazel query //src/main/java/com/example/dishes/...
bazel query //src/main/java/com/example/ingredients/...
ingredients パッケージのターゲットをクエリすると、出力は次のようになります。
//src/main/java/com/example/ingredients:cheese
//src/main/java/com/example/ingredients:dough
//src/main/java/com/example/ingredients:macaroni
//src/main/java/com/example/ingredients:tomato
依存関係の検索
ランナーが実行に使用するターゲット
ファイルシステムを調べることなくプロジェクトの構造を詳しく調べたい場合(大規模なプロジェクトでは不可能な場合もあります)Cafe Bazel ではどのようなルールが適用されますか?
この例のように、ランナーのターゲットが runner
の場合は、次のコマンドを実行してターゲットの基盤となる依存関係を検出します。
bazel query --noimplicit_deps "deps(target)"
bazel query --noimplicit_deps "deps(:runner)"
//:runner
//:src/main/java/com/example/Runner.java
//src/main/java/com/example/dishes:MacAndCheese.java
//src/main/java/com/example/dishes:Pizza.java
//src/main/java/com/example/dishes:macAndCheese
//src/main/java/com/example/dishes:pizza
//src/main/java/com/example/ingredients:Cheese.java
//src/main/java/com/example/ingredients:Dough.java
//src/main/java/com/example/ingredients:Macaroni.java
//src/main/java/com/example/ingredients:Tomato.java
//src/main/java/com/example/ingredients:cheese
//src/main/java/com/example/ingredients:dough
//src/main/java/com/example/ingredients:macaroni
//src/main/java/com/example/ingredients:tomato
//src/main/java/com/example/restaurant:Cafe.java
//src/main/java/com/example/restaurant:Chef.java
//src/main/java/com/example/restaurant:cafe
//src/main/java/com/example/restaurant:chef
ほとんどの場合、クエリ関数 deps()
を使用して、特定のターゲットの個々の出力依存関係を確認します。
依存関係グラフの可視化(省略可)
このセクションでは、特定のクエリの依存関係パスを可視化する方法について説明します。Graphviz を使用すると、パスをフラット化されたリストではなく、有向非巡回グラフ画像として表示できます。Bazel クエリグラフの表示は、さまざまな --output
コマンドライン オプションを使用して変更できます。オプションについては、出力形式をご覧ください。
まず、目的のクエリを実行し、--noimplicit_deps
フラグを追加して、不要なツール依存関係を削除します。次に、クエリの後に出力フラグを指定して、グラフを graph.in
というファイルに保存し、グラフのテキスト表現を作成します。
ターゲット :runner
のすべての依存関係を検索し、出力をグラフとしてフォーマットするには:
bazel query --noimplicit_deps 'deps(:runner)' --output graph > graph.in
これにより、graph.in
というファイルが作成されます。これは、ビルドグラフのテキスト表現です。Graphviz は、テキストをビジュアリゼーションに変換するツールである dot
を使用して png を作成します。
dot -Tpng < graph.in > graph.png
graph.png
を開くと、次のような表示になります。以下のグラフは、このガイドで重要なパスの詳細を明確にするために簡素化されています。
これは、このガイドで説明するさまざまなクエリ関数の出力を確認する際に役立ちます。
逆依存関係の検索
特定のルールに依存するターゲットを分析する場合は、クエリを使用して、そのルールに依存するターゲットを調べることができます。これは「逆依存関係」と呼ばれます。rdeps()
を使用すると、慣れていないコードベース内のファイルを編集するときに役立ちます。また、そのファイルに依存する他のファイルを知らずに破損してしまうことを防ぐことができます。
たとえば、コンポーネント cheese
を編集するとします。Cafe Bazel で問題が発生しないようにするには、cheese
に依存する料理を確認する必要があります。
特定のターゲットまたはパッケージに依存するターゲットを確認するには、rdeps(universe_scope, target)
を使用します。rdeps()
クエリ関数は、少なくとも 2 つの引数(universe_scope
- 関連するディレクトリ - と target
)を取ります。Bazel は、指定された universe_scope
内でターゲットのリバース依存関係を検索します。rdeps()
演算子は、検索の深さの上限を指定する整数リテラルというオプションの 3 番目の引数を受け取ります。
プロジェクト全体のスコープ「//…」内でターゲット cheese
の逆依存関係を検索するには、次のコマンドを実行します。
bazel query "rdeps(universe_scope, target)"
ex) bazel query "rdeps(//... , //src/main/java/com/example/ingredients:cheese)"
//:runner
//src/main/java/com/example/dishes:macAndCheese
//src/main/java/com/example/dishes:pizza
//src/main/java/com/example/ingredients:cheese
//src/main/java/com/example/restaurant:cafe
//src/main/java/com/example/restaurant:chef
クエリの結果から、チーズは pizza と macAndCheese の両方で使用されていることがわかります。なんていうことだろう!
タグに基づくターゲットの検索
2 人の顧客(Amir と Jenny)が Bazel Cafe に入店します。名前以外は何もわかっていません。幸い、注文には「customers」BUILD
ファイルでタグが付けられています。このタグにアクセスするにはどうすればよいですか?
デベロッパーは、テスト目的で Bazel ターゲットにさまざまな ID をタグ付けできます。たとえば、テストのタグを使用して、デバッグ プロセスとリリース プロセスにおけるテストの役割をアノテーションできます。特に、ランタイム アノテーション機能のない C++ テストや Python テストでは、この方法が有効です。タグとサイズ要素を使用すると、コードベースのチェックイン ポリシーに基づいてテストスイートを柔軟に組み立てることができます。
この例では、タグはメニュー アイテムを表す pizza
または macAndCheese
のいずれかです。このコマンドは、特定のパッケージ内の識別子に一致するタグを持つターゲットをクエリします。
bazel query 'attr(tags, "pizza", //src/main/java/com/example/customers/...)'
このクエリは、「pizza」というタグを持つ「customers」パッケージ内のすべてのターゲットを返します。
自己診断
このクエリを使用して、ジェニーが注文したい商品を確認します。
回答
マカロニチーズ
新しい依存関係の追加
Cafe Bazel のメニューが拡充され、スムージーを注文できるようになりました。このスムージーは、Strawberry
と Banana
という 2 つの材料で構成されています。
まず、スムージーが依存する材料(Strawberry.java
と Banana.java
)を追加します。空の Java クラスを追加します。
src/main/java/com/example/ingredients/Strawberry.java
package com.example.ingredients;
public class Strawberry {
}
src/main/java/com/example/ingredients/Banana.java
package com.example.ingredients;
public class Banana {
}
次に、適切なディレクトリ(dishes
)に Smoothie.java
を追加します。
src/main/java/com/example/dishes/Smoothie.java
package com.example.dishes;
public class Smoothie {
public static final String DISH_NAME = "Smoothie";
public static final String DESCRIPTION = "Yummy and Refreshing";
}
最後に、これらのファイルを適切な BUILD
ファイルにルールとして追加します。新しい成分ごとに新しい Java ライブラリを作成します。名前、公開設定、新しく作成された「src」ファイルが含まれます。更新された BUILD
ファイルは次のようになります。
src/main/java/com/example/ingredients/BUILD
java_library(
name = "cheese",
visibility = ["//visibility:public"],
srcs = ["Cheese.java"],
)
java_library(
name = "dough",
visibility = ["//visibility:public"],
srcs = ["Dough.java"],
)
java_library(
name = "macaroni",
visibility = ["//visibility:public"],
srcs = ["Macaroni.java"],
)
java_library(
name = "tomato",
visibility = ["//visibility:public"],
srcs = ["Tomato.java"],
)
java_library(
name = "strawberry",
visibility = ["//visibility:public"],
srcs = ["Strawberry.java"],
)
java_library(
name = "banana",
visibility = ["//visibility:public"],
srcs = ["Banana.java"],
)
料理の BUILD
ファイルで、Smoothie
の新しいルールを追加します。これにより、Smoothie
用に作成された Java ファイルが「src」ファイルとして含まれ、スムージーの各成分に対して作成した新しいルールが含まれます。
src/main/java/com/example/dishes/BUILD
java_library(
name = "macAndCheese",
visibility = ["//visibility:public"],
srcs = ["MacAndCheese.java"],
deps = [
"//src/main/java/com/example/ingredients:cheese",
"//src/main/java/com/example/ingredients:macaroni",
],
)
java_library(
name = "pizza",
visibility = ["//visibility:public"],
srcs = ["Pizza.java"],
deps = [
"//src/main/java/com/example/ingredients:cheese",
"//src/main/java/com/example/ingredients:dough",
"//src/main/java/com/example/ingredients:tomato",
],
)
java_library(
name = "smoothie",
visibility = ["//visibility:public"],
srcs = ["Smoothie.java"],
deps = [
"//src/main/java/com/example/ingredients:strawberry",
"//src/main/java/com/example/ingredients:banana",
],
)
最後に、シェフの BUILD
ファイルにスムージーを依存関係として含めます。
src/main/java/com/example/restaurant/BUILD
java\_library(
name = "chef",
visibility = ["//visibility:public"],
srcs = [
"Chef.java",
],
deps = [
"//src/main/java/com/example/dishes:macAndCheese",
"//src/main/java/com/example/dishes:pizza",
"//src/main/java/com/example/dishes:smoothie",
],
)
java\_library(
name = "cafe",
visibility = ["//visibility:public"],
srcs = [
"Cafe.java",
],
deps = [
":chef",
],
)
cafe
を再度ビルドして、エラーがないことを確認します。ビルドが正常に完了したら、おめでとうございます。「Cafe」の新しい依存関係が追加されました。問題が解決しない場合は、スペルミスとパッケージ名に注意してください。BUILD
ファイルの作成方法については、BUILD スタイルガイドをご覧ください。
Smoothie
を追加した新しい依存関係グラフを可視化して、以前のグラフと比較します。わかりやすくするために、グラフ入力の名前を graph2.in
と graph2.png
にします。
bazel query --noimplicit_deps 'deps(:runner)' --output graph > graph2.in
dot -Tpng < graph2.in > graph2.png
graph2.png
を見ると、Smoothie
は他の料理と共有依存関係を持っておらず、Chef
が依存する別のターゲットにすぎないことがわかります。
somepath() と allpaths()
あるパッケージが別のパッケージに依存している理由をクエリする場合はどうすればよいですか。2 つの依存関係パスを表示すると、答えがわかります。
依存関係パスを見つけるのに役立つ関数は 2 つあります。somepath()
と allpaths()
です。開始ターゲット S と終点 E が指定されている場合、somepath(S,E)
を使用して S と E 間のパスを検索します。
「Chef」ターゲットと「Cheese」ターゲットの関係を確認して、これらの 2 つの関数の違いを確認します。1 つのターゲットから別のターゲットへの経路は複数あります。
- Chef → MacAndCheese → Cheese
- シェフ → ピザ → チーズ
somepath()
は 2 つのオプションから 1 つのパスを返しますが、allpaths() は考えられるすべてのパスを出力します。
Cafe Bazel を例として、次のコマンドを実行します。
bazel query "somepath(//src/main/java/com/example/restaurant/..., //src/main/java/com/example/ingredients:cheese)"
//src/main/java/com/example/restaurant:cafe
//src/main/java/com/example/restaurant:chef
//src/main/java/com/example/dishes:macAndCheese
//src/main/java/com/example/ingredients:cheese
出力は、最初のパス Cafe → Chef → MacAndCheese → Cheese に沿って行われます。代わりに allpaths()
を使用すると、次のように表示されます。
bazel query "allpaths(//src/main/java/com/example/restaurant/..., //src/main/java/com/example/ingredients:cheese)"
//src/main/java/com/example/dishes:macAndCheese
//src/main/java/com/example/dishes:pizza
//src/main/java/com/example/ingredients:cheese
//src/main/java/com/example/restaurant:cafe
//src/main/java/com/example/restaurant:chef
allpaths()
の出力は、依存関係のフラット化されたリストであるため、読みづらい場合があります。Graphviz を使用してこのグラフを可視化すると、関係をより明確に把握できます。
自己診断
Cafe Bazel のお客様から、レストランの最初のレビューが投稿されました。申し訳ございませんが、レビューに投稿者の身元や、どの料理について言及しているかなどの詳細が欠落しています。幸い、この情報には Bazel でアクセスできます。reviews
パッケージには、覆面調査の顧客からのレビューを出力するプログラムが含まれています。次のコマンドを使用してビルドして実行します。
bazel build //src/main/java/com/example/reviews:review
bazel-bin/src/main/java/com/example/reviews/review
Bazel クエリのみを使用して、レビューを書いたユーザーと、そのユーザーが説明している料理を特定してみてください。
ヒント
タグと依存関係で有用な情報を確認します。
回答
このレビューはピザについてのものであり、レビューしたユーザーは Amir でした。bazel query --noimplicit\_deps 'deps(//src/main/java/com/example/reviews:review)'
を使用して、このルールにどのような依存関係があったかを確認します。このコマンドの結果から、Amir が審査担当者であることがわかります。次に、レビュー担当者が Amir であることがわかっているので、クエリ関数を使用して Amir が「BUILD」ファイルに設定しているタグを検索し、どのような料理が含まれているかを確認できます。コマンド bazel query 'attr(tags, "pizza", //src/main/java/com/example/customers/...)'
の出力から、Amir がピザを注文した唯一の顧客であり、回答を提供したレビュアーであることがわかります。
まとめ
これで、これで、いくつかの基本的なクエリを実行しました。これらのクエリは、ご自身のプロジェクトで試すことができます。クエリ言語の構文について詳しくは、クエリのリファレンス ページをご覧ください。より高度なクエリを実行したい場合は、クエリガイドでは、このガイドで取り上げているユースケース以外にも、詳細なユースケースのリストを紹介しています。