クエリのクイックスタート

このチュートリアルでは、Bazel を使用して、事前に作成された Bazel プロジェクトを使用してコード内の依存関係をトレースする方法について説明します。

言語と --output フラグの詳細については、Bazel クエリ リファレンスBazel cquery リファレンス マニュアルをご覧ください。IDE でヘルプを表示するには、コマンドラインで bazel help query または bazel help cquery と入力します。

目標

このガイドでは、プロジェクトのファイル依存関係について詳しく知るために使用できる一連の基本的なクエリについて説明します。Bazel と BUILD ファイルの仕組みに関する基本的な知識を持つ Bazel の新規デベロッパーを対象としています。

前提条件

まだインストールしていない場合は、まず Bazel をインストールします。このチュートリアルでは、ソース管理に Git を使用します。最適な結果を得るには、Git もインストールしてください。

依存関係グラフを視覚化するには、Graphviz というツールを使用します。このツールをダウンロードして、手順に沿って操作できます。

サンプル プロジェクトを取得する

次に、任意のコマンドライン ツールで次のコマンドを実行して、Bazel's Examples repository からサンプルアプリを取得します。

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 ファイルで検索するのではなく、クエリ関数のみを使用してください。

プロジェクトは、カフェを構成するさまざまなパッケージで構成されています。これらのパッケージは、restaurantingredientsdishescustomersreviews に分かれています。これらのパッケージ内のルールは、さまざまなタグと依存関係を持つカフェのさまざまなコンポーネントを定義します。

ビルドの実行

このプロジェクトには、Runner.java 内に main メソッドが含まれています。このメソッドを実行すると カフェのメニューが出力されます。コマンド 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/...

材料パッケージのターゲットをクエリすると、出力は次のようになります。

//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_scopetarget)を取ります。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

クエリの戻り値は、チーズがピザとマカロニチーズの両方に依存していることを示しています。驚きです。

タグに基づいてターゲットを見つける

アミールとジェニーという 2 人のお客様が Bazel カフェに入店しました。名前以外は何もわかりません。幸いなことに、注文には「customers」BUILD ファイルでタグが付けられています。このタグにアクセスするにはどうすればよいですか?

デベロッパーは、多くの場合テスト目的で、さまざまな識別子を使用して Bazel ターゲットにタグを付けることができます。たとえば、テストのタグは、デバッグ プロセスとリリース プロセスにおけるテストの役割をアノテーションできます。特に、ランタイム アノテーション機能がない C++ テストと Python テストに有効です。タグとサイズ要素を使用すると、コードベースのチェックイン ポリシーに基づいてテストスイートを柔軟に組み立てることができます。

この例では、タグはメニュー項目を表す pizza または macAndCheese のいずれかです。このコマンドは、特定のパッケージ内で識別子に一致するタグを持つターゲットをクエリします。

bazel query 'attr(tags, "pizza", //src/main/java/com/example/customers/...)'

このクエリは、「pizza」というタグが付いた「customers」パッケージ内のすべてのターゲットを返します。

テスト

このクエリを使用して、ジェニーが注文したいものを確認します。

回答

マカロニチーズ

新しい依存関係を追加する

Cafe Bazel のメニューが増えました。スムージーを注文できるようになりました。この特定のスムージーは、StrawberryBanana の材料で構成されています。

まず、スムージーが依存する材料(Strawberry.javaBanana.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 ファイルにルールとして追加します。新しい材料ごとに、名前、公開設定、新しく作成した「src」ファイルを含む新しい Java ライブラリを作成します。更新された 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 を再度ビルドして、エラーがないことを確認します。ビルドが成功したら、おめでとうございます。「カフェ」の新しい依存関係を追加しました。エラーが発生した場合は、スペルミスとパッケージ名を確認してください。BUILD ファイルの作成の詳細については、BUILD スタイルガイドをご覧ください。

次に、Smoothie を追加して新しい依存関係グラフを視覚化し、以前のグラフと比較します。わかりやすくするために、グラフの入力に graph2.ingraph2.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 つの関数を使用できます。開始ターゲット S と終了点 E が指定されている場合は、somepath(S,E) を使用して S と E の間のパスを見つけます。

「Chef」ターゲットと「Cheese」ターゲットの関係を確認して、これら 2 つの関数の違いを確認します。あるターゲットから別のターゲットに移動するには、さまざまなパスがあります。

  • Chef → MacAndCheese → Cheese
  • Chef → Pizza → 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 クエリのみを使用して、レビューを書いたユーザーと、そのユーザーが説明した料理を特定します。

ヒント

タグと依存関係を確認して、有用な情報を探します。

回答

このレビューはピザについて説明したもので、レビュー投稿者はアミールでした。 を使用して、このルールにどのような依存関係があるかを確認すると、このコマンドの結果から、レビュー投稿者がアミールであることがわかります。 bazel query --noimplicit\_deps 'deps(//src/main/java/com/example/reviews:review)'次に、レビュー投稿者がアミールであることがわかったので、クエリ関数を使用して、アミールが `BUILD` ファイルにどのようなタグを持っているかを確認し、どのような料理があるかを確認できます。 コマンド bazel query 'attr(tags, "pizza", //src/main/java/com/example/customers/...)' を実行すると、ピザを注文したお客様はアミールのみであり、レビュー投稿者であることがわかります。

まとめ

おめでとうございます!これで、いくつかの基本的なクエリを実行しました。これらのクエリは、独自のプロジェクトで試すことができます。クエリ言語の構文の詳細については、クエリ リファレンス ページをご覧ください。より高度なクエリが必要ですか?クエリガイドでは、このガイドで説明されているよりも多くのユースケースの詳細なリストを紹介しています。