日程の確定: BazelCon 2023 は、Google ミュンヘンで 10 月 24 ~ 25 日に開催されます。詳細

クエリ クイックスタート

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

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

目的

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

Prerequisites

Bazel をインストールします(まだインストールしていない場合)。このチュートリアルでは、ソース管理に Git を使用するため、Git もインストールすることをおすすめします。

依存関係グラフを可視化するために、Graphviz というツールを使用します。このツールはダウンロードして追跡できます。

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

次に、任意のコマンドライン ツールで以下のコマンドを実行して、Bazel のサンプル リポジトリからサンプルアプリを取得します。

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
└── WORKSPACE

このチュートリアル全体を通して、特に指示がない限り、必要な情報を BUILD ファイルから探さず、クエリ関数だけを使用してください。

プロジェクトは、Cafe を構成するさまざまなパッケージで構成されています。restaurantingredientsdishescustomersreviews に分割されています。これらのパッケージ内のルールは、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/...

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

//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 を使用すると、パスをフラット化されたリストではなく、有向非巡回グラフ画像として表示できます。さまざまな --output コマンドライン オプションを使用して、Bazel クエリグラフの表示を変更できます。オプションについては、出力形式をご覧ください。

まず、目的のクエリを実行し、--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

このクエリの結果は、チーズが ピザと macAndCheese の両方に基づいていることを示しています。サプライズ

タグに基づいてターゲットを検索する

Bazel カフェを訪れる 2 人のお客様: Amir と Jenny。名前以外には知られていません。お客様の注文が「customers」BUILDファイルでタグ付けされています。このタグにアクセスするには、どうすればよいですか?

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

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

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

このクエリは、「customers」パッケージ内のすべてのターゲットを「zza」のタグで返します。

自分をテスト

このクエリを使用して、Jenny が注文する内容を確認します。

正解

Mac とチーズ

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

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 に関する新しいルールを追加します。これには、「src」ファイルとして Smoothie 用に作成された Java ファイルと、スムージーの各材料用に作成した新しいルールが含まれます。

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.ingraph2.png という名前を付けます。

bazel query --noimplicit_deps 'deps(:runner)' --output graph > graph2.in
dot -Tpng < graph2.in > graph2.png

1 つ目と同じグラフですが、シェフのターゲットに由来するスポークと、スムージーのバナナ、イチゴ

graph2.png を見ると、Smoothie が他の料理との依存関係は共有していないが、Chef が依存しているもう一つのターゲットであることがわかります。

somepath() と allpaths()

あるパッケージが別のパッケージに依存している理由を知りたい場合はどうすればよいでしょうか。2 つの間の依存関係パスを表示すれば、答えが見つかります。

依存関係のパスは、somepath()allpaths() という 2 つの関数で確認できます。開始ターゲット S とエンドポイント E が指定された場合、somepath(S,E) を使用して S と E の間のパスを見つけます。

「Chef」ターゲットと「Cheese」ターゲットの関係を確認して、この 2 つの機能の違いを調べます。あるターゲットから別のターゲットに到達するには、次のようないくつかの方法があります。

  • Chef → 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 の顧客がレストランの初めてのレビューを投稿しました。残念ですが、レビューの ID や料理名などの詳細情報が不足しています。幸い、この情報は 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)' を使用していた依存関係を確認したところ、Amir がレビュー担当者であることがわかりました。次に、審査担当者は Amir とわかっているため、クエリ関数を使用して、「BUILD」ファイルで Amir に含まれているタグを検索し、そこにある料理を確認できます。 bazel query 'attr(tags, "pizza", //src/main/java/com/example/customers/...)' コマンドの出力を見ると、Amir さんはピザを注文した唯一の顧客であり、クチコミの投稿者であることがわかります。

まとめ

これで完了です。これで、複数のプロジェクトで基本的なクエリを実行できました。これを自分のプロジェクトで試すことができます。クエリ言語構文の詳細については、クエリ リファレンス ページをご覧ください。より高度なクエリが必要な場合クエリガイドには、このガイドで取り上げていない多くのユースケースが列挙されています。