このチュートリアルでは、Bazel と既製の Bazel プロジェクトを使用してコード内の依存関係をトレースする方法について説明します。
言語と --output
フラグについて詳しくは、Bazel クエリ リファレンスと Bazel cquery リファレンス のマニュアルをご覧ください。IDE でヘルプを表示するには、コマンドラインで「bazel help query
」または「bazel help cquery
」と入力します。
目標
このガイドでは、プロジェクトのファイルの依存関係の詳細を確認するために使用できる一連の基本的なクエリについて説明します。Bazel と BUILD
ファイルの仕組みに関する基本的な知識がある、Bazel の新しいデベロッパーを対象としています。
前提条件
Bazel をまだインストールしていない場合は、まずインストールします。このチュートリアルではソース管理に Git を使用するため、最適な結果を得るには Git もインストールしてください。
依存関係グラフを可視化するには、Graphviz というツールを使用します。このツールを使用して、ダウンロードできます。
サンプル プロジェクトを取得する
次に、任意のコマンドライン ツールで次のコマンドを実行して、Bazel の Example リポジトリからサンプルアプリを取得します。
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
ファイルを調べるのではなく、クエリ関数のみを使用してください。
プロジェクトは、カフェを構成するさまざまなパッケージで構成されます。これらは、restaurant
、ingredients
、dishes
、customers
、reviews
に分けられます。これらのパッケージ内のルールにより、さまざまなタグや依存関係を使用して Cafe のさまざまなコンポーネントが定義されます。
ビルドの実行
このプロジェクトには、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/…
コマンドを実行します。
この例では、これを使用して、この Cafe が用意している食材や料理を調べることができます。
bazel query //src/main/java/com/example/dishes/...
bazel query //src/main/java/com/example/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
フラグを追加して、過剰なツール依存関係を削除します。次に、output フラグを指定してクエリに従い、グラフを 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
クエリ戻りは、チーズがピザと macAndCheese の両方に依存していることを示しています。なんてことだ!
タグに基づいてターゲットを見つける
Bazel Cafe には、アミールとジェニーという 2 人の客が入ってきました。名前以外は何も知られていない。幸いなことに、それらの注文は「customers」の BUILD
ファイルにタグ付けされています。このタグへのアクセス方法
デベロッパーは、多くの場合テスト目的で、異なる識別子で Bazel ターゲットをタグ付けできます。たとえば、テストのタグにより、デバッグとリリースのプロセスにおけるテストの役割にアノテーションを付けることができます。特に、ランタイム アノテーション機能がない C++ と Python のテストではアノテーションを付けることができます。タグとサイズ要素を使用すると、コードベースのチェックイン ポリシーに基づいてテストスイートを柔軟にアセンブルできます。
この例では、タグは pizza
または macAndCheese
のいずれかで、メニュー項目を表します。このコマンドは、特定のパッケージ内の識別子と一致するタグを持つターゲットをクエリします。
bazel query 'attr(tags, "pizza", //src/main/java/com/example/customers/...)'
このクエリは、「customers」パッケージ内で「ピザ」のタグを持つすべてのターゲットを返します。
自分をテストする
このクエリを使用して、ジェニーさんが注文したいものを確認します。
正解
マック&チーズ
新しい依存関係の追加
Cafe Bazel はメニューを拡大し、スムージーを注文できるようになりました。このスムージーは Strawberry
と Banana
で構成されています。
まず、スムージーに必要な材料(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 {
}
次に、Smoothie.java
を適切なディレクトリ(dishes
)に追加します。
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",
],
)
最後に、スムージーを依存関係として Chef の 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 つの間の依存関係パスを表示することで答えが得られます。
somepath()
と allpaths()
という 2 つの関数が依存関係パスを見つけるのに役立ちます。始点と終点 E から、somepath(S,E)
を使って S と E を結ぶ経路を見つけます。
「Chef」ターゲットと「Cheese」ターゲット間の関係を見て、この 2 つの関数の違いを確認してください。ターゲット間で取得するパスはさまざまです。
- シェフ → MacAndCheese → チーズ
- シェフ → ピザ → チーズ
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 のお客様の 1 人が、このレストランの最初のクチコミを投稿してくれました。残念ながら、クチコミ投稿者の身元や参照している料理などの詳細情報が不足しています。幸いなことに、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 がピザを注文した唯一の顧客であり、Amir が答えを提供したレビュアーであることを出力します。
まとめ
これで完了です。これで、いくつかの基本的なクエリを実行できました。これらのクエリは、独自のプロジェクトで試してみることができます。クエリ言語の構文の詳細については、クエリのリファレンス ページをご覧ください。より高度なクエリが必要な場合は、クエリガイドには、このガイドで取り上げていないユースケースの詳細なリストが記載されています。