Bazel 标签(Label)精讲:掌握//path/to/package:target-name语法,精准引用构建目标
【免费下载链接】bazela fast, scalable, multi-language and extensible build system项目地址: https://gitcode.com/GitHub_Trending/ba/bazel
在 Bazel 的世界里,//main:hello-world、//lib:hello-time这样的字符串随处可见——无论是写在BUILD文件里,还是敲在命令行上。它们是 Bazel 定位构建目标的“地址”,官方称之为标签(Label)。本文以 Bazel 官方 C++ 教程中的《Use labels to reference targets》为基础,系统讲解标签的完整语法、规则目标与文件目标的差异、仓库根目录与同包引用等快捷写法,并结合本仓库的examples示例与docs/concepts/labels.mdx概念文档进行源码级印证。读完本文,你将能正确书写、解读并排错任何出现在BUILD文件或命令行中的 Bazel 标签。
标签语法:三段式结构
标签的通用语法为:
//path/to/package:target-name以两个连续斜杠//开头,中间以冒号:分隔,整体由三部分构成:
//:工作区根目录标识符,表示“从这个仓库的根目录开始寻址”;path/to/package:包路径,即从工作区根目录到包含BUILD文件的目录之间的相对路径;target-name:目标名称,即BUILD文件中规则的name属性值(规则目标)或文件相对于包根目录的路径(文件目标)。
例如//main:hello-world,含义是“位于工作区根目录下main包(该目录含BUILD文件)中的、名为hello-world的目标”。
工作区根的判断标准
标签中的path/to/package是相对工作区根目录计算的。在启用 Bzlmod 的现代 Bazel 项目中,工作区根目录就是存放MODULE.bazel文件的目录;在旧式工作区中则是存放WORKSPACE文件的目录。这一点在新旧版本文档中都有明确记载:当前 docs/tutorials/cpp-labels.mdx 写的是MODULE.bazel,而 docs/versions/7.6.1/tutorials/cpp-labels.mdx 等旧版文档仍以WORKSPACE文件为准,两处表述差异恰好反映了 Bazel 从 WORKSPACE 迁移到 MODULE.bazel 的历史演进。
规则目标与文件目标:target-name的两种含义
标签的冒号后部分target-name指代什么,取决于目标类型:
规则目标(Rule Target)
如果目标是规则目标(即BUILD文件中调用某个规则声明出来的目标),那么:
path/to/package是从工作区根到包含BUILD文件的目录的路径;target-name就是你在BUILD文件中为规则指定的name属性值。
以本仓库 examples/cpp/BUILD 为例:
cc_library( name = "hello-lib", srcs = ["hello-lib.cc"], hdrs = ["hello-lib.h"], ) cc_binary( name = "hello-world", srcs = ["hello-world.cc"], deps = [":hello-lib"], )这里cc_library规则实例化出的目标hello-lib,其标签为//examples/cpp:hello-lib;cc_binary目标hello-world的标签为//examples/cpp:hello-world。注意deps = [":hello-lib"]使用的是同包简写形式(见下文“同包引用”一节)。
文件目标(File Target)
如果目标是文件目标(仓库中的源文件,或由规则生成的文件),那么:
path/to/package是包根目录(即包含该包BUILD文件的目录)的路径;target-name是该文件的名称,包含它相对于包根目录的完整路径。
例如 docs/concepts/labels.mdx 中的例子:位于my/app/main/testdata子目录下的文件input.txt,其标签写作:
//my/app/main:testdata/input.txt包路径是my/app/main,而目标名部分是testdata/input.txt——冒号后的部分完整保留了文件在包内的相对路径,这正是文件目标与规则目标的显著差异。
本仓库中也有真实案例:examples/cpp/BUILD 里的runfile目标通过data = ["//examples:runfile.txt"]引用了一个文件目标:
cc_binary( name = "runfile", srcs = ["runfile.cc"], copts = ["-std=c++17"], data = ["//examples:runfile.txt"], deps = ["@rules_cc//cc/runfiles"], )//examples:runfile.txt指的就是工作区根下examples包中的文件runfile.txt。它之所以能被examples/cpp包中的目标引用,是因为 examples/BUILD 中声明了package(default_visibility = ["//visibility:public"])并执行了exports_files(["runfile.txt"])——这印证了文件目标的可见性同样受visibility规则约束。
快捷写法一:仓库根目录//:target-name
当目标位于仓库根目录的包(即根包,根目录本身包含BUILD文件)时,包路径为空,直接写成:
//:target-name本仓库根目录的 BUILD 文件即为根包的BUILD文件,其中的目标都可以用//:xxx形式引用。//:target-name与//<根包>:target-name是等价的。
快捷写法二:同包引用:target-name
当你在同一个BUILD文件内部引用目标时,连//工作区根标识符都可以省略,直接写:
:target-name最典型的例子就是 examples/cpp/BUILD 中的deps = [":hello-lib"]——hello-world和hello-lib同处examples/cpp包,因此无需写全//examples/cpp:hello-lib。官方 docs/start/cpp.mdx 教程 Stage 2 的main/BUILD文件也是如此:
cc_binary( name = "hello-world", srcs = ["hello-world.cc"], deps = [ ":hello-greet", ], )同包引用还能更省略
根据 docs/concepts/labels.mdx,当标签引用的就是当前所在包时,包名(以及冒号)都可以省略。因此以下三种写法在包my/app/main内部是等价的:
app_binary :app_binary //my/app/main:app_binary惯例上,文件引用通常省略冒号(如hello-lib.cc),规则引用通常保留冒号(如:hello-lib),但这只是书写约定,语法上并无强制区别。
省略包名的等价规则
当冒号后的目标名与包路径的最后一段相同时,目标名和冒号都可以省略。例如以下两个标签等价:
//my/app/lib //my/app/lib:lib这意味着//foo/bar/wiz永远是//foo/bar/wiz:wiz的简写——即使根本不存在foo/bar/wiz这个包,它也绝不会被解释为//foo:bar/wiz。
一个常见误区://my/app不等于“包里的所有目标”
//my/app这类字符串在 Bazel 期待标签的上下文中,等价于//my/app:app——它命名的是my/app包中名为app的目标,并不是指代整个包或包内全部目标。这是BUILD文件中非常典型的一类错误,docs/concepts/labels.mdx 专门对此做了强调。
不过,在package_group规范或.bzl文件中,//my/app表示包本身的用法是被鼓励的,因为它能清晰传达“包名是绝对的、扎根于工作区顶层目录”的含义。
相对标签不能跨包引用
相对标签(不带//与包名的写法)只能引用当前包内的目标,绝不能用相对路径去引用其他包的目标。假设源树中有两个包my/app和my/app/testdata(各自都有BUILD文件),后者包内有一个文件testdepot.zip。在//my/app:BUILD中引用它,正确与错误的写法分别是:
# 错误:testdata 是另一个包,不能用相对路径 testdata/testdepot.zip # 正确:必须写全路径 //my/app/testdata:testdepot.zip跨包引用必须完整给出仓库标识符与包名。这也是为什么多包项目里跨包deps都要写全标签,例如 docs/tutorials/cpp-use-cases.mdx 中的测试目标:
cc_test( name = "hello-test", srcs = ["hello-test.cc"], deps = [ "@googletest//:gtest_main", "//main:hello-greet", ], )其中//main:hello-greet是工作区内跨包引用,@googletest//:gtest_main则是带仓库名前缀的外部仓库引用。
带仓库名的完整标签形式
上述讨论都默认目标位于当前仓库。当涉及外部仓库时,标签前面还要加上仓库名:
@myrepo//my/app/main:app_binary其中@myrepo称为表象仓库名(apparent repo name),其解析结果可能因上下文而异。Bazel 还支持更严格的规范仓库名(canonical repo name),使用双@前缀:
@@myrepo//my/app/main:app_binary规范仓库名在工作区内全局唯一,能无歧义地定位目标。在同一个仓库内引用该仓库自身的目标时,仓库名部分可以省略——这就是我们前面一直讨论的//path/to/package:target-name形式。
还有一点值得注意:@@//开头的标签是对**主仓库(main repository)**的引用,即使从外部仓库中使用也依然指向主仓库。因此从外部仓库的角度看,@@//a/b/c与//a/b/c含义不同——前者指回主仓库,后者则在外部仓库自身内查找。这一点在编写会被外部仓库复用的规则时尤为关键。
词法规范:目标名与包名的合法字符
为了让标签便于在 shell 中使用、易于被工具和脚本(如 Bazel 查询语言)处理,Bazel 对标签的字符集有明确约束:
**目标名(target-name)**只能由以下字符组成:
a–z, A–Z, 0–9, 以及标点 !%-@^_"#$&'()*-+,;<=>?[]{|}~/.文件名形式的路径必须为规范形式的相对路径:
- 不能以
/开头或结尾(/foo、foo/均非法); - 不能包含连续斜杠(
foo//bar非法); - 禁止
..上跳引用与./当前目录引用。
**包名(package-name)**则要求:
- 允许小写字母
a–z、大写字母A–Z、数字0–9及一系列标点符号(含空格字符),以及目录分隔符/; - 不能以
/开头或结尾; - 不能包含子串
//; - 不能包含
/./、/../、/.../等子串,以避免逻辑包名与物理目录名转换时产生歧义。
实操层面,对于目录结构与模块体系强相关的语言(如 Java),建议包目录名选用语言合法的标识符,避免以数字开头及特殊字符;虽然 Bazel 支持工作区根包中的目标(如//:foo),但最好让根包保持为空,使所有有意义的包都拥有描述性的名称。
实战验证:构建与查询中的标签
标签最终要落实到命令与BUILD文件中。以下是贯穿官方 C++ 教程的可执行示例(详见 docs/start/cpp.mdx):
bazel build //main:hello-world其中//main:是BUILD文件相对工作区根的位置,hello-world是BUILD文件中的目标名。构建成功后,产物位于工作区根下的bazel-bin/main/hello-world。
多包场景下,跨包依赖通过完整标签建立。Stage 3 的main/BUILD中:
cc_binary( name = "hello-world", srcs = ["hello-world.cc"], deps = [ ":hello-greet", "//lib:hello-time", ], )//lib:hello-time将main包的目标与lib包的目标连接起来。要让构建成功,lib/BUILD中的hello-time还必须通过visibility显式对外开放——默认情况下目标只对同一BUILD文件内的其他目标可见:
cc_library( name = "hello-time", srcs = ["hello-time.cc"], hdrs = ["hello-time.h"], visibility = ["//main:__pkg__"], )标签同样可以用于检查依赖关系。在 docs/tutorials/cpp-dependency.mdx 中,通过bazel query结合标签,可以导出目标的依赖图:
bazel query --notool_deps --noimplicit_deps "deps(//main:hello-world)" \ --output graph将输出粘贴到 GraphViz 即可可视化;在 Ubuntu 上也可以安装graphviz与xdot后直接查看:
sudo apt update && sudo apt install graphviz xdot xdot <(bazel query --notool_deps --noimplicit_deps "deps(//main:hello-world)" \ --output graph)下图展示了 Stage 3 项目通过标签建立起的跨包依赖关系——//main:hello-world依赖//main:hello-greet与//lib:hello-time:
延伸阅读
- 标签概念详解(含仓库名、规范形式、词法规范):docs/concepts/labels.mdx
- C++ 项目构建入门教程(标签的实际构建用法):docs/start/cpp.mdx
- 依赖图的生成与可视化:docs/tutorials/cpp-dependency.mdx
- 常见 C++ 构建用例(跨包
deps与visibility实践):docs/tutorials/cpp-use-cases.mdx - 标签可见性机制:docs/concepts/visibility.mdx
- 仓库内可直接参考的完整示例:examples/cpp/BUILD 与 examples/BUILD
【免费下载链接】bazela fast, scalable, multi-language and extensible build system项目地址: https://gitcode.com/GitHub_Trending/ba/bazel
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考