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

問題を報告 ソースを表示

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

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

目的

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

前提条件

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

1 つのプロジェクトは、Cafe を構成するさまざまなパッケージで構成されています。restaurantingredientsdishescustomersreviews に分かれています。これらのパッケージ内のルールでは、さまざまなタグや依存関係を使用して、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/...

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 つの引数を取ります。1 つは universe_scope(該当するディレクトリ)で、もう 1 つは 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 の両方がチーズに依存していることを示しています。びっくりです!

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

2 人のお客様が Bazel Cafe に入ります。Amir と Jenny です。名前以外は何もわかっていません。幸いなことに、この会社の注文は「customers」BUILD ファイルでタグ付けされています。このタグにアクセスするには、どうすればよいでしょうか。

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

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

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

このクエリは、'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",
    ],
)

最後に、スムージーを依存関係として 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

最初のグラフと同じグラフ。ただし、シェフのターゲットがスムージーに由来するスポークが、バナナとストロベリーに由来しています。

graph2.png を見ると、Smoothie には他の料理との依存関係はありませんが、Chef が依存しているターゲットの 1 つであることがわかります。

somepath() と allpaths()

あるパッケージが別のパッケージに依存している理由をクエリしたい場合はどうすればよいでしょうか。両者間の依存関係パスを表示することで、答えが得られます。

依存関係パスを見つけるには、somepath()allpaths() の 2 つの関数を使用できます。始点 S と終点 E が与えられた場合、somepath(S,E) を使って S と E 間の経路を求めます。

「シェフ」ターゲットと「チーズ」ターゲットの関係を見て、これら 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 クエリからのみ、レビューを書いていた人と、そのレビューを書いていた料理を探してみてください。

ヒント

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

回答

このレビューはピザを説明したもので、アミールはレビュアーでした。このルールで 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 がピザを注文した唯一の顧客であり、回答を提供するレビュアーであることを示しています。

まとめ

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