本教學課程說明如何使用預先建構的 Bazel 專案,透過 Bazel 追蹤程式碼中的依附元件。
如要瞭解語言和 --output
標記的詳細資料,請參閱 Bazel 查詢參考資料和 Bazel cquery 參考資料手冊。在指令列中輸入 bazel help query
或 bazel help cquery
,即可在 IDE 中取得說明。
目標
本指南會逐步說明一組基本查詢,協助您進一步瞭解專案的檔案依附元件。本指南適用於剛開始使用 Bazel 的開發人員,並假設您已基本瞭解 Bazel 和 BUILD
檔案的運作方式。
必要條件
首先,請安裝 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 的不同套件組成。這些項目分為:restaurant
、ingredients
、dishes
、customers
和 reviews
。這些套件中的規則定義了 Cafe 的不同元件,並包含各種標記和依附元件。
執行建構作業
這項專案的 Runner.java
內含主要方法,您可以執行該方法來列印咖啡廳的菜單。使用 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
查詢傳回的結果顯示,pizza 和 macAndCheese 都依賴 cheese。真是驚喜!
根據標記尋找目標
兩位顧客走進 Bazel 咖啡廳:Amir 和 Jenny。除了姓名之外,我們對他們一無所知。幸好,他們在「customers」BUILD
檔案中標記了訂單。如何存取這個代碼?
開發人員可以為 Bazel 目標加上不同的 ID,通常是為了進行測試。舉例來說,測試標記可註解測試在偵錯和發布程序中的角色,特別是 C++ 和 Python 測試,因為這類測試缺少任何執行階段註解功能。使用標記和大小元素,可根據程式碼庫的簽入政策,彈性組合測試套件。
在本例中,標記為 pizza
或 macAndCheese
,代表菜單項目。這個指令會查詢特定套件中,標記與 ID 相符的目標。
bazel query 'attr(tags, "pizza", //src/main/java/com/example/customers/...)'
這項查詢會傳回「customers」套件中所有標記為「pizza」的目標。
自我測試
使用這項查詢瞭解 Jenny 想訂購的商品。
解答
起司通心粉
新增依附元件
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()
如果想查詢某個套件為何依附於另一個套件,該怎麼做?顯示兩者之間的依附元件路徑,即可找出答案。
您可以使用 somepath()
和 allpaths()
這兩個函式,找出依附元件路徑。假設起點為 S,終點為 E,請使用 somepath(S,E)
找出 S 和 E 之間的路徑。
請查看「Chef」和「Cheese」目標之間的關係,瞭解這兩項功能的差異。從一個目標到另一個目標,可能有多種路徑:
- Chef → MacAndCheese → Cheese
- Chef → Pizza → Cheese
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
輸出內容會遵循「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 查詢,找出撰寫評論的人,以及他們描述的菜餚。
提示
檢查標記和依附元件,找出實用資訊。
解答
這則評論描述的是披薩,而 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 訂購了披薩,而且他是評論者,因此我們得到答案。
總結
恭喜!您現在已執行多個基本查詢,可以在自己的專案中試用。如要進一步瞭解查詢語言語法,請參閱查詢參考資料頁面。想瞭解更進階的查詢方式嗎?如需更多應用實例,請參閱查詢指南。