
本文介绍如何利用 subpackages() 函数(推荐来自 bazel_skylib 的版本)自动发现并引用多个子包中的目标,避免在根 build 文件中硬编码依赖路径,实现可扩展、可维护的插件式依赖管理。
本文介绍如何利用 subpackages() 函数(推荐来自 bazel_skylib 的版本)自动发现并引用多个子包中的目标,避免在根 build 文件中硬编码依赖路径,实现可扩展、可维护的插件式依赖管理。
在构建大型、模块化 Python 项目(如插件系统)时,手动维护 deps 列表极易出错且难以扩展。例如,当新增 plugin3/ 目录后,必须同步修改根 BUILD 文件——这违背了 Bazel “声明即配置” 和“可复现构建”的设计哲学。
Bazel 原生 glob() 仅匹配文件路径,无法解析 target;而 native.subpackages()(已废弃)或更优的 bazel_skylib 提供的 subpackages 才是解决动态 target 发现的正确工具。
✅ 推荐方案:使用 bazel_skylib 的 subpackages
首先,在工作区根目录的 WORKSPACE 或 MODULE.bazel 中引入 bazel_skylib(Bazel ≥ 6.0 推荐用 MODULE):
# MODULE.bazel bazel_dep(name = "bazel_skylib", version = "1.5.0")
然后,在 //plugins_folder/BUILD 中定义各插件的 py_library,并导出统一别名(如 all):
# plugins_folder/plugin1/BUILD py_library( name = "code1-0", srcs = ["code1-0.py"], ) py_library( name = "code1-1", srcs = ["code1-1.py"], ) # 导出聚合目标(关键!) py_library( name = "all", srcs = [], deps = [":code1-0", ":code1-1"], )
同理为 plugin2/ 编写 BUILD,确保每个插件子目录均提供 :all 目标。
最后,在根 //plugins_folder/BUILD 中动态聚合所有插件依赖:
# plugins_folder/BUILD load("@bazel_skylib//lib:subpackages.bzl", "subpackages") py_library( name = "code0", srcs = ["code0.py"], deps = [ "//plugins_folder/%s:all" % pkg for pkg in subpackages(include = ["plugin*"]) ], )
? subpackages(include = [“plugin*”]) 返回形如 [“plugin1”, “plugin2”] 的字符串列表,不包含前导 /,也不含 //plugins_folder/ 前缀——因此拼接时需显式补全完整包路径。
? 验证与调试
运行以下命令确认依赖图是否符合预期:
bazel query 'deps(//plugins_folder:code0)' --notool_deps --noimplicit_deps
输出应包含 //plugins_folder/plugin1:all、//plugins_folder/plugin2:all 及其内部 targets(如 :code1-0, :code2-0 等)。
⚠️ 注意事项
- 每个插件子目录 必须存在 BUILD 文件,且其中需明确定义 :all(或其他约定名称)目标;
- subpackages() 在加载阶段执行,不支持条件逻辑或 runtime 计算,但足以满足静态目录结构发现;
- 若需更细粒度控制(如排除测试插件),可用 exclude = [“plugin_test”] 参数;
- 不要混用原生 native.subpackages()(自 Bazel 6.0 起标记为 deprecated)与 bazel_skylib 版本。
通过该模式,新增插件只需创建目录 + BUILD 文件,无需触碰根构建配置,真正实现“零配置扩展”,大幅提升工程可维护性与协作效率。