1. Bazel 依赖管理概述
C++ 项目通常依赖大量第三方库(如 protobuf、gtest、abseil、boost 等)。Bazel 提供了多种方式来管理这些依赖:
| Bzlmod | ||
| WORKSPACE | ||
| 本地路径引用 | ||
| Git 子模块 + local_repository |
2. Bzlmod:现代依赖管理(推荐)
Bzlmod 是 Bazel 6.0 引入的新依赖管理系统,通过 MODULE.bazel 文件声明模块依赖,替代传统的 WORKSPACE 方式。
2.1 为什么选择 Bzlmod?
WORKSPACE 方式的痛点 Bzlmod 的改进─────────────────────── ────────────────────依赖传递需要手动传播 自动传递依赖(像 npm/cargo)依赖冲突难以解决 支持版本解析(MVS 算法)宏污染全局命名空间 模块化隔离依赖声明冗长复杂 简洁声明式无法区分开发依赖和运行依赖 支持 dev_dependency
2.2 启用 Bzlmod
在 .bazelrc 中添加:
common --enable_bzlmod2.3 MODULE.bazel 基础语法
# MODULE.bazelmodule(name = "my_project",version = "1.0.0",compatibility_level = 1,)# 声明依赖bazel_dep(name = "googletest", version = "1.14.0")bazel_dep(name = "abseil-cpp", version = "20230802.1")bazel_dep(name = "protobuf", version = "24.4")# 开发依赖(仅当前模块使用,不传播给依赖者)bazel_dep(name = "rules_proto", version = "5.3.0", dev_dependency = True)
2.4 关键概念
- 模块(Module)
:一个可被 Bazel 项目复用的功能单元,有唯一的名称和版本 - 模块扩展(Module Extension)
:用于引入非 Bazel 原生仓库的机制 - 仓库(Repo)
:模块提供的可被 deps引用的外部仓库 - 兼容性级别(Compatibility Level)
:语义版本之外的破坏性变更标记
2.5 完整示例
# MODULE.bazelmodule(name = "my_cpp_project",version = "2.0.0",compatibility_level = 1,)# ── 核心依赖 ────────────────────────────bazel_dep(name = "abseil-cpp", version = "20230802.1")bazel_dep(name = "protobuf", version = "24.4")# ── 测试依赖 ────────────────────────────bazel_dep(name = "googletest", version = "1.14.0", dev_dependency = True)bazel_dep(name = "benchmark", version = "1.8.3", dev_dependency = True)# ── 规则依赖 ────────────────────────────bazel_dep(name = "rules_proto", version = "5.3.0")bazel_dep(name = "rules_cc", version = "0.0.9")
BUILD 文件中使用:
# src/BUILDcc_library(name = "string_utils",srcs = ["string_utils.cc"],hdrs = ["string_utils.h"],deps = ["@abseil-cpp//absl/strings", # 通过 Bzlmod 引用"@protobuf//:protobuf",],)cc_test(name = "string_utils_test",srcs = ["string_utils_test.cc"],deps = [":string_utils","@googletest//:gtest_main", # 测试依赖],)
3. WORKSPACE:传统依赖管理
虽然 Bzlmod 是推荐方式,但许多现有项目仍在使用 WORKSPACE 方式,理解它仍然重要。
3.1 http_archive — 下载发布包
最常用的依赖引入方式,从 URL 下载压缩包:
# WORKSPACEload("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")# Google Testhttp_archive(name = "com_google_googletest",urls = ["https://github.com/google/googletest/archive/refs/tags/v1.14.0.tar.gz",],strip_prefix = "googletest-1.14.0",sha256 = "8ad598c73ad796e0d8280b082cebd82a630d73e73cd3c70057938a6501bba5d7",)# Abseilhttp_archive(name = "com_google_absl",urls = ["https://github.com/abseil/abseil-cpp/archive/refs/tags/20230802.1.tar.gz",],strip_prefix = "abseil-cpp-20230802.1",sha256 = "f50e5ac3a4e287a2286637d218e9bb3e2b0307c5d2a4e7c3ef8c6694f1c93c8f",)# Protocol Buffershttp_archive(name = "com_google_protobuf",urls = ["https://github.com/protocolbuffers/protobuf/archive/refs/tags/v24.4.tar.gz",],strip_prefix = "protobuf-24.4",sha256 = "6d0ebd6a8a15ea7564eb7c6ea4a2e4e210a5ef5c0e3c2d054c7e9cc0e1b2f5e3",)
关键参数说明:
name | @name// 引用 |
urls | |
sha256 | |
strip_prefix | |
type | |
patches | |
patch_args |
3.2 git_repository — 从 Git 仓库获取
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")git_repository(name = "com_github_mylib",remote = "https://github.com/example/mylib.git",branch = "main",# 或使用特定的 commit/tag# commit = "a1b2c3d4e5f6...",# tag = "v1.2.3",)# 也可以使用 new_git_repository(需要指定 BUILD 文件)new_git_repository(name = "com_github_simplelib",remote = "https://github.com/example/simplelib.git",tag = "v2.0.0",build_file = "//third_party:simplelib.BUILD", # 指定 BUILD 文件# 或使用 build_file_content# build_file_content = """cc_library(name = "simplelib", ...)""",)
最佳实践:优先使用
http_archive而不是git_repository,因为前者更快速且可重现。
3.3 local_repository — 引用本地项目
load("@bazel_tools//tools/build_defs/repo:local.bzl", "local_repository")# 引用本地另一个 Bazel 项目local_repository(name = "my_local_lib",path = "../my-local-lib", # 相对于 WORKSPACE 的路径)
3.4 传递依赖问题
WORKSPACE 方式的最大痛点是传递依赖需要手动声明。如果库 A 依赖库 B,在 WORKSPACE 中必须同时声明两者:
# ❌ 错误:只声明了 A,缺少 Bhttp_archive(name = "com_example_A", ...)# ✅ 正确:必须同时声明 A 的所有传递依赖http_archive(name = "com_example_B", ...)http_archive(name = "com_example_A", ...)
这也是 Bzlmod 被推荐的主要原因——它自动处理传递依赖。
4. 为非 Bazel 项目编写 BUILD 文件
很多 C++ 第三方库并不自带 BUILD 文件。Bazel 提供了 new_http_archive 和 build_file 来解决这个问题。
4.1 使用 new_http_archive + build_file
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")http_archive(name = "spdlog",urls = ["https://github.com/gabime/spdlog/archive/refs/tags/v1.12.0.tar.gz",],strip_prefix = "spdlog-1.12.0",sha256 = "4dccf2d10f410c1e2c73a028c7a0c4d5c527e7fd0c7bedb4b3f47d2a1cb5b2f0",build_file = "//third_party:spdlog.BUILD", # 指定自定义 BUILD 文件)
4.2 编写第三方库的 BUILD 文件
创建 third_party/spdlog.BUILD:
# third_party/spdlog.BUILDpackage(default_visibility = ["//visibility:public"])cc_library(name = "spdlog",hdrs = glob(["include/spdlog/**/*.h",]),includes = ["include"],defines = ["SPDLOG_COMPILED_LIB","FMT_HEADER_ONLY=1",],deps = ["@fmt", # 如果 spdlog 依赖 fmt 库],)
4.3 常见 C++ 库的 BUILD 文件模板
Header-only 库
cc_library(name = "header_only_lib",hdrs = glob(["include/**/*.h"]),includes = ["include"],visibility = ["//visibility:public"],)
源码库
cc_library(name = "source_lib",srcs = glob(["src/**/*.cc"]),hdrs = glob(["include/**/*.h"]),includes = ["include"],visibility = ["//visibility:public"],)
带配置的库
cc_library(name = "configured_lib",srcs = glob(["src/**/*.cc"]) + ["config.h"], # config.h 由 genrule 生成hdrs = glob(["include/**/*.h"]),includes = ["include", "."], # "." 用于找到 config.hdefines = ["HAVE_CONFIG_H=1"],copts = ["-w"], # 抑制第三方库的警告visibility = ["//visibility:public"],)
5. 使用 Bzlmod 的模块扩展引入非 Bazel 依赖
Bzlmod 通过模块扩展来引入不支持 Bazel 的第三方依赖。这是 WORKSPACE 方式在新体系下的替代方案。
5.1 基本结构
my-project/├── MODULE.bazel├── extensions.bzl ← 模块扩展定义├── third_party/│ ├── spdlog.BUILD ← 第三方库 BUILD 文件│ └── fmt.BUILD└── ...
5.2 定义模块扩展
创建 extensions.bzl:
# extensions.bzlload("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")def _third_party_impl(module_ctx):# 所有使用此扩展的模块声明的依赖for mod in module_ctx.modules:for dep in mod.tags.http_archive:http_archive(name = dep.name,urls = dep.urls,strip_prefix = dep.strip_prefix,sha256 = dep.sha256,build_file = dep.build_file,)# 定义标签(tag)用于声明依赖_http_archive_tag = tag_class(attrs = {"name": attr.string(mandatory = True),"urls": attr.string_list(mandatory = True),"strip_prefix": attr.string(),"sha256": attr.string(),"build_file": attr.label(),},)third_party = module_extension(implementation = _third_party_impl,tag_classes = {"http_archive": _http_archive_tag,},)
5.3 在 MODULE.bazel 中使用扩展
# MODULE.bazelmodule(name = "my_project", version = "1.0.0")# 声明模块扩展third_party = use_extension("//:extensions.bzl", "third_party")# 通过扩展引入第三方依赖third_party.http_archive(name = "spdlog",urls = ["https://github.com/gabime/spdlog/archive/refs/tags/v1.12.0.tar.gz"],strip_prefix = "spdlog-1.12.0",sha256 = "4dccf2d10f410c1e2c73a028c7a0c4d5c527e7fd0c7bedb4b3f47d2a1cb5b2f0",build_file = "//third_party:spdlog.BUILD",)# 使用依赖bazel_dep(name = "googletest", version = "1.14.0")
6. 常见 C++ 第三方库引入示例
6.1 Google Test / Google Mock
Bzlmod 方式:
bazel_dep(name = "googletest", version = "1.14.0")WORKSPACE 方式:
http_archive(name = "com_google_googletest",urls = ["https://github.com/google/googletest/archive/refs/tags/v1.14.0.tar.gz"],strip_prefix = "googletest-1.14.0",sha256 = "8ad598c73ad796e0d8280b082cebd82a630d73e73cd3c70057938a6501bba5d7",)
使用:
cc_test(name = "my_test",srcs = ["my_test.cc"],deps = ["//src:my_lib","@googletest//:gtest_main", # Bzlmod# "@com_google_googletest//:gtest_main", # WORKSPACE],)
6.2 Abseil
Bzlmod 方式:
bazel_dep(name = "abseil-cpp", version = "20230802.1")使用:
cc_library(name = "my_lib",deps = ["@abseil-cpp//absl/strings","@abseil-cpp//absl/types:optional","@abseil-cpp//absl/container:flat_hash_map",],)
6.3 Protocol Buffers
Bzlmod 方式:
bazel_dep(name = "protobuf", version = "24.4")bazel_dep(name = "rules_proto", version = "5.3.0")bazel_dep(name = "rules_cc", version = "0.0.9")
使用:
proto_library(name = "person_proto",srcs = ["person.proto"],)cc_proto_library(name = "person_proto_cc",deps = [":person_proto"],)cc_library(name = "person_utils",deps = [":person_proto_cc"],)
6.4 fmt / spdlog
Bzlmod 方式(如已注册到 BCR):
bazel_dep(name = "fmt", version = "10.1.1")手动方式:
# extensions.bzl 或 WORKSPACEhttp_archive(name = "fmt",urls = ["https://github.com/fmtlib/fmt/archive/refs/tags/10.1.1.tar.gz"],strip_prefix = "fmt-10.1.1",sha256 = "...此处填入校验和...",)
7. 依赖版本锁定
7.1 锁定文件
Bzlmod 会自动生成锁定文件 MODULE.bazel.lock,确保可重现构建:
# 此文件由 Bazel 自动生成,不要手动编辑# 提交到版本控制中以确保团队构建一致性
7.2 更新依赖
# 更新所有依赖到最新兼容版本bazel clean --expungebazel deps update# 更新特定依赖bazel deps update --repo=@abseil-cpp
7.3 检查依赖树
# 查看依赖树bazel query "deps(//src:my_lib)" --output=label# 查看外部依赖bazel query "kind(http_archive, //external:*)"# 使用 Bzlmod 查看模块依赖图bazel mod graphbazel mod show_repo @abseil-cpp
8. 依赖管理最佳实践
8.1 通用原则
✅ 推荐做法 ❌ 避免做法───────────────────────── ─────────────────────────优先使用 Bzlmod 继续新建 WORKSPACE 项目始终指定 sha256 校验和 使用不可信来源的依赖固定版本号(tag/commit) 使用浮动版本(branch)将锁定文件提交到版本控制 忽略 MODULE.bazel.lock统一团队依赖版本 各自使用不同版本使用镜像 URL 只使用单一源
8.2 第三方库 BUILD 文件管理
third_party/├── BUILD # 空文件或 exports├── spdlog.BUILD # spdlog 的构建定义├── fmt.BUILD # fmt 的构建定义└── README.md # 第三方依赖说明
为每个第三方库维护独立的 BUILD 文件:
记录版本号和来源 记录引入的补丁和修改 保持 BUILD 文件简洁
8.3 依赖隔离
# 通过 visibility 控制第三方依赖的传播范围# 第三方库 BUILDcc_library(name = "spdlog",hdrs = glob(["include/**/*.h"]),includes = ["include"],visibility = ["//visibility:public"],)# 项目内部封装cc_library(name = "logger", # 封装 spdlog,对外只暴露 loggerhdrs = ["logger.h"],deps = ["@spdlog"], # 内部使用 spdlogvisibility = ["//visibility:public"],)
9. 小结
本篇详细介绍了 Bazel 的依赖管理:
✅ Bzlmod 现代依赖管理系统(推荐) ✅ WORKSPACE 传统依赖管理方式 ✅ http_archive、git_repository、local_repository ✅ 为非 Bazel 项目编写 BUILD 文件 ✅ Bzlmod 模块扩展机制 ✅ 常见 C++ 第三方库引入示例 ✅ 依赖版本锁定与最佳实践
夜雨聆风