在 Bazel 中,明确地定义依赖关系是构建系统成功的关键,这也正是它高效、可重复、可缓存的原因之一。Bazel 永远不会猜测构建顺序,而是根据你定义的依赖图来调度每一个构建动作。 可以把 Bazel 理解为一个大型的 “声明式有向图构建器”,每一个节点(rule)都要告诉 Bazel:“我需要哪些前置节点”,它才能高效调度和增量构建。
下面系统性地总结一下Bazel中定义依赖关系的方式,理解这些依赖的写法,基本就掌握了Bazel构建图的构造方式。
Bazel 中的依赖关系类型
可以从 3 个层次来看:
1. 规则(Rule)中内建属性来定义依赖
-
适用于常见规则如
cc_binary
,py_library
,genrule
,filegroup
等。 -
常用字段有:
字段名 | 作用 | 用途示例 |
---|---|---|
srcs | 源文件或源规则 | 通常指源码或生成文件的依赖 |
deps | 其他规则的依赖(编译时链接) | 通常用于编译依赖 |
data | 运行时需要的文件 | 用于测试或 runtime 资源 |
hdrs | C/C++ 的头文件依赖 | 指定需要包含的头文件 |
tools | genrule 或 action 的工具依赖 | 用于生成过程调用的工具 |
outs | 声明的输出文件 | 其他规则可以依赖这些输出 |
✅ 示例 1:cc_binary
依赖 cc_library
cc_library(
name = "util_lib",
srcs = ["util.cc"],
hdrs = ["util.h"],
)
cc_binary(
name = "main_app",
srcs = ["main.cc"],
deps = [":util_lib"],
)
👉 main_app
编译时会用到 util_lib
提供的源码和头文件。
✅ 示例 2:使用 genrule
生成文件并被其他规则依赖
genrule(
name = "generate_config",
outs = ["config.txt"],
cmd = "echo 'config=1' > $@",
)
filegroup(
name = "generated_files",
srcs = [":generate_config"],
)
sh_binary(
name = "my_script",
srcs = ["main.sh"],
data = [":generated_files"], # 运行时依赖
)
👉 my_script
在运行时能访问 config.txt
。
✅ 示例 3:使用 tools
字段
genrule(
name = "generate_data",
outs = ["data.txt"],
tools = ["//tools:my_tool"],
cmd = "$(location //tools:my_tool) > $@",
)
👉 Bazel 会自动确保 my_tool
被构建出来,并传递其路径到 $@
。
2. 在 macro
中连接规则依赖
宏(macro)是生成规则的函数,要手动连接规则之间的依赖。
✅ 示例 4:Macro 中手动指定依赖顺序
def my_pipeline(name):
native.genrule(
name = name + "_step1",
outs = ["out1.txt"],
cmd = "echo step1 > $@",
)
native.genrule(
name = name + "_step2",
srcs = [":" + name + "_step1"],
outs = ["out2.txt"],
cmd = "cat $(SRCS) > $@ && echo step2 >> $@",
)
使用:
load("//build_defs:my_macros.bzl", "my_pipeline")
my_pipeline(name = "build_data")
👉 step2
明确依赖 step1
的输出。
3. Starlark 自定义规则中显式声明依赖(高级用法)
如果你自定义 rule()
,你需要在 attrs
里声明依赖字段,在 implementation
中使用依赖内容。
✅ 示例 5:自定义 rule 使用 deps
def _my_rule_impl(ctx):
for dep in ctx.attr.deps:
print(dep.label)
return []
my_rule = rule(
implementation = _my_rule_impl,
attrs = {
"deps": attr.label_list(),
}
)
BUILD 文件中用法:
my_rule(
name = "hello",
deps = ["//lib:foo", "//lib:bar"],
)
👉 这允许你在规则实现中读取依赖规则的信息(如文件、标签等)。
📦 额外的依赖类型和技巧
✅ label_list
/ label
的其他字段定义依赖
例如你可以在自定义规则中定义:
attrs = {
"srcs": attr.label_list(allow_files = True),
"hdrs": attr.label_list(allow_files = True),
"tools": attr.label_list(cfg = "exec"),
}
-
allow_files = True
表示可以接受文件依赖 -
cfg = "exec"
表示运行在执行环境(比如用作生成工具)
✅ filegroup
用于聚合文件或规则
filegroup(
name = "all_headers",
srcs = glob(["*.h"]),
)
cc_library(
name = "lib_with_headers",
hdrs = [":all_headers"],
srcs = ["main.cc"],
)
总结:Bazel 中定义依赖的方式
场景 | 写法 | 用途说明 |
---|---|---|
编译依赖 | deps = [":other_rule"] | C/C++、Python、Java 等代码依赖 |
文件依赖 | srcs = [...] , hdrs = [...] | 源文件或头文件 |
运行时依赖 | data = [...] | 测试或脚本运行依赖文件 |
工具依赖 | tools = [...] | 生成器工具,如代码生成器 |
宏中依赖 | srcs = [":prev_rule"] | 宏中手动指定依赖顺序 |
自定义规则中依赖 | attrs = { "deps": attr.label_list() } | 高级场景:Starlark rule |