查詢快速入門導覽課程

本教學課程說明如何與 Bazel 搭配使用,以使用預先建立的 Bazel 專案追蹤程式碼中的依附元件。

如需語言和 --output 標記的詳細資料,請參閱 Bazel 查詢參考資料Bazel cquery 參考資料手冊。在指令列中輸入 bazel help querybazel help cquery,即可在 IDE 中取得協助。

目標

本指南將逐步說明基本查詢,您可以透過這些查詢進一步瞭解專案的檔案依附元件。適用對象為對 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'

情境

請設想一下,Cof 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 檔案找出所需的資訊,並只使用查詢函式。

專案包含構成咖啡廳的不同套裝行程。分為 restaurantingredientsdishescustomersreviews。這些套件中的規則會使用各種標記和依附元件定義 Cafe 的不同元件。

執行建構

此專案包含 Runner.java 中的主要方法,可用來輸出 Cafe 的選單。使用 Bazel 指令執行 bazel build 指令,並使用 : 表示目標名稱為 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() 查詢函式至少使用兩個引數:universe_scope (相關目錄) 和 target。Bazel 會在提供的 universe_scope 中搜尋目標的反向依附元件。rdeps() 運算子可接受選用的第三個引數:指定搜尋深度上限的整數常值。

如要尋找整個專案「//...」範圍內的目標 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:Amir 和 Jenny。除了名稱外,沒有什麼東西認識。幸好,他們已在「customers」的 BUILD 檔案中標記訂單。如何存取這個代碼?

開發人員可以使用不同的 ID 標記 Bazel 目標,通常用於測試用途。舉例來說,測試中的標記可以在偵錯和發布程序中為測試的角色加上註解,尤其是 C++ 和 Python 測試,因為這類測試不具備任何執行階段註解能力。使用標記和大小元素可讓您根據程式碼集的簽入政策,靈活組合測試套件。

在這個範例中,標記為 pizzamacAndCheese 之一,代表選單項目。這個指令會查詢特定套件內含有與 ID 相符的目標。

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

此查詢會傳回「customers」套件中具有「披薩」標記的所有目標。

測試自己

請透過這項查詢瞭解 Jenny 想要訂購的商品。

解答

澳門和起司

新增依附元件

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 檔案中新增這些檔案做為規則。為每個新食材建立新的 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,確認沒有錯誤。如果能成功建構,恭喜!您已新增「咖啡廳」的依附元件。如果不是,請檢查是否有拼字錯誤和套件名稱。如要進一步瞭解如何編寫 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()

如何查詢某個套件依附另一個套件的原因?顯示兩者之間的依附關係路徑即可解決這個問題。

您可使用 somepath()allpaths() 這兩個函式找出依附元件路徑。指定起始目標 S 和終點 E,使用 somepath(S,E) 找出 S 和 E 之間的路徑。

檢視「Chef」和「Cheese」目標之間的關係,以探索這兩項功能的差異。從一個目標到另一個目標可能有不同的可能路徑:

  • 主廚 → MacAndCheese → 起司
  • 廚師 → 披薩 → 起司

somepath() 提供兩個選項中的一個路徑,而「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

輸出內容緊接在「咖啡廳」→「 Chef」→「MacAndCheese」→ 「起司」,如果改用 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)' 這個指令的結果顯示,Amir 是審查者!接下來,由於你知道評論者是阿密,因此可以使用查詢函式找出阿密的「BUILD」檔案所含的哪個標記,查看餐點中有哪些餐點。 bazel query 'attr(tags, "pizza", //src/main/java/com/example/customers/...)' 指令輸出:Amir 是唯一訂購披薩的客戶,而且是查看答案的審查人員。

設定程序即將完成

恭喜!您已經執行了幾項基本查詢,並可在自己的專案中試用這些查詢。如要進一步瞭解查詢語言語法,請參閱查詢參考資料頁面。需要更多進階查詢嗎?查詢指南列出了本指南未涵蓋的其他用途,