
本文介绍如何利用 subpackages() 函数(来自 bazel skylib)自动发现并聚合子目录中的目标,避免在根 build 文件中硬编码 deps,实现插件式架构下的可扩展依赖管理。
本文介绍如何利用 subpackages() 函数(来自 bazel skylib)自动发现并聚合子目录中的目标,避免在根 build 文件中硬编码 deps,实现插件式架构下的可扩展依赖管理。
在构建模块化 Python 项目(如插件系统)时,常需让主模块动态依赖所有已注册插件的目标,而非手动维护 deps 列表。Bazel 原生 glob() 只能匹配文件路径,无法解析或生成合法的标签(label),因此不能直接用于构造 deps。真正的解决方案是借助 包级发现机制 —— 即通过 subpackages() 动态获取符合命名模式的子包路径,再组合成有效目标标签。
✅ 推荐方案:使用 bazel_skylib 的 subpackages
首先确保已在工作区中引入 bazel_skylib(推荐 v1.5.0+):
# WORKSPACE.bzlmod(Bzlmod 模式,推荐) bazel_dep(name = "bazel_skylib", version = "1.5.0")
或传统 WORKSPACE:
# WORKSPACE
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "bazel_skylib",
urls = ["https://github.com/bazelbuild/bazel-skylib/releases/download/1.5.0/bazel-skylib-1.5.0.tar.gz"],
sha256 = "cd55a0779d4cc603416c88683879195a1544959f2b0884b04a71361e08112243",
)然后在根 BUILD 文件中(即 //plugins_folder/BUILD)编写如下逻辑:
# //plugins_folder/BUILD
load("@bazel_skylib//lib:subpackages.bzl", "subpackages")
py_library(
name = "code0",
srcs = ["code0.py"],
# 动态发现 plugin1/、plugin2/ 等子包,并引用其默认目标(如 :all 或 :code1-0)
deps = [
"//plugins_folder/%s:code1-0" % pkg for pkg in subpackages(include = ["plugin*"])
] + [
"//plugins_folder/%s:code2-0" % pkg for pkg in subpackages(include = ["plugin*"])
],
)⚠️ 注意:上述写法假设每个插件子目录(如 plugin1/)的 BUILD 文件中定义了名为 code1-0 的目标。若希望统一引用每个插件的公共目标(例如统一命名为 :main 或 :all),更健壮的做法是:
py_library(
name = "code0",
srcs = ["code0.py"],
deps = [
"//plugins_folder/%s:main" % pkg for pkg in subpackages(include = ["plugin*"])
],
)对应地,每个插件子目录的 BUILD 文件应包含:
# //plugins_folder/plugin1/BUILD
py_library(
name = "main",
srcs = ["code1-0.py", "code1-1.py"],
)? 验证依赖是否正确生成
运行以下命令检查实际解析出的依赖图:
bazel query 'deps(//plugins_folder:code0)' --notool_deps --noimplicit_deps
输出应类似:
//plugins_folder:code0 //plugins_folder/plugin1:main //plugins_folder/plugin2:main
⚠️ 关键注意事项
- subpackages(include=["plugin*"]) 返回的是相对子包路径(如 "plugin1"、"plugin2"),不带前导 / 或 //,需手动拼接完整标签;
- subpackages() 仅在加载阶段(loading phase)执行,不支持在规则实现函数中调用;
- 子包必须存在有效的 BUILD 文件(哪怕为空),否则不会被识别;
- 若需排除特定子包,可用 exclude 参数:subpackages(include=["plugin*"], exclude=["plugin_test"]);
- 不要混淆 subpackages 与 glob():前者操作于 包层级(package boundaries),后者仅扫描 文件系统路径。
✅ 总结
通过 bazel_skylib 的 subpackages(),你可以在 Bazel 中优雅实现“插件自动注册”模式:无需修改根 BUILD 文件即可新增插件,只要其目录名匹配规则且含合法 BUILD,就会被自动纳入依赖。这显著提升了大型模块化项目的可维护性与可扩展性,是构建可插拔系统的核心实践之一。










