本教程介绍如何使用预先构建的 Bazel 项目,通过 Bazel 跟踪代码中的依赖项。
如需详细了解语言和 --output
标志,请参阅 Bazel 查询参考和 Bazel cquery 参考手册。在 IDE 中,通过在命令行中输入 bazel help query
或 bazel help cquery
获取帮助。
目标
本指南将引导您完成一组基本查询,以便您详细了解项目的文件依赖项。本教程面向对 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
└── MODULE.bazel
在本教程中,除非另有说明,否则请尽量不要查看 BUILD
文件来查找所需信息,而仅使用查询功能。
项目由构成咖啡馆的不同软件包组成。它们分为:restaurant
、ingredients
、dishes
、customers
和 reviews
。这些软件包中的规则定义了 Cafe 的不同组件,并包含各种标记和依赖项。
运行 build
此项目在 Runner.java
中包含一个 main 方法,您可以执行该方法来打印咖啡馆的菜单。使用 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/...
如果您查询的是软件包的 target,输出应如下所示:
//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 的目标是 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
的文件,该文件是 build 图的文本表示形式。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 目标添加不同的标识符,通常是为了进行测试。例如,测试上的标记可以注释测试在调试和发布过程中的角色,尤其是对于缺乏任何运行时注释功能的 C++ 和 Python 测试。使用标记和大小元素可以灵活地根据代码库的签入政策组装测试套件。
在此示例中,标记为 pizza
或 macAndCheese
,表示菜单项。此命令会查询特定软件包中具有与您的标识符匹配的标记的目标。
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
文件中将 smoothie 作为依赖项包含在内。
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.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 → MacAndCheese → 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 是唯一订购披萨的客户,也是评价者,因此我们得到了答案。
总结
恭喜!您现在已经运行了几个基本查询,可以在自己的项目中试用这些查询。如需详细了解查询语言语法,请参阅查询参考页面。想要了解更高级的查询?查询指南中详细列出了比本指南中介绍的更多的使用场景。