Bazel C++ 构建系列文档(三):构建第一个 C++ 项目
1. 项目概述
本篇将从零开始创建一个完整的 C++ 项目,包含一个可执行程序、一个库和对应的测试。通过这个实战项目,你将掌握 Bazel C++ 构建的基本流程。
最终项目结构:
hello-bazel/
├── WORKSPACE
├── .bazelversion
├── .bazelrc
├── BUILD
├── src/
│ ├── BUILD
│ ├── main.cc
│ ├── greeter.h
│ └── greeter.cc
└── tests/
├── BUILD
└── greeter_test.cc
2. 创建项目骨架
2.1 创建目录结构
bash
mkdir -p hello-bazel/src
mkdir -p hello-bazel/tests
cd hello-bazel
2.2 固定 Bazel 版本
bash
echo "7.3.1" > .bazelversion
2.3 创建 WORKSPACE 文件
python
# WORKSPACE
workspace(name = "hello_bazel")
如果你使用 Bzlmod(推荐),可以用
MODULE.bazel代替:
python# MODULE.bazel module(name = "hello_bazel", version = "1.0.0")本系列后续章节会详细讲解 Bzlmod。
2.4 创建 .bazelrc 文件
ini
# .bazelrc --- 项目级构建配置
# 默认使用优化模式构建
build -c opt
# Windows 特定配置
build:windows --copt=/W4
build:windows --cxxopt=/std:c++17
# Linux/macOS 特定配置
build:linux --copt=-Wall
build:linux --cxxopt=-std=c++17
# 测试配置
test --test_output=errors
# 启用 Bzlmod(Bazel 6+)
common --enable_bzlmod
3. 编写源代码
3.1 库:Greeter
创建 src/greeter.h:
cpp
// src/greeter.h
#ifndef SRC_GREETER_H_
#define SRC_GREETER_H_
#include <string>
namespace hello_bazel {
class Greeter {
public:
explicit Greeter(const std::string& name);
std::string Greet() const;
std::string GreetWithTime(const std::string& time_of_day) const;
private:
std::string name_;
};
} // namespace hello_bazel
#endif // SRC_GREETER_H_
创建 src/greeter.cc:
cpp
// src/greeter.cc
#include "src/greeter.h"
#include <sstream>
namespace hello_bazel {
Greeter::Greeter(const std::string& name) : name_(name) {}
std::string Greeter::Greet() const {
return "Hello, " + name_ + "!";
}
std::string Greeter::GreetWithTime(const std::string& time_of_day) const {
std::ostringstream oss;
oss << "Good " << time_of_day << ", " << name_ << "!";
return oss.str();
}
} // namespace hello_bazel
3.2 主程序
创建 src/main.cc:
cpp
// src/main.cc
#include <iostream>
#include "src/greeter.h"
int main() {
hello_bazel::Greeter greeter("Bazel");
std::cout << greeter.Greet() << std::endl;
std::cout << greeter.GreetWithTime("morning") << std::endl;
return 0;
}
4. 编写 BUILD 文件
4.1 根目录 BUILD(可选)
python
# BUILD
# 根包可以放置项目级别的别名或聚合目标
alias(
name = "app",
actual = "//src:hello_bazel",
visibility = ["//visibility:public"],
)
4.2 src/BUILD
python
# src/BUILD
package(default_visibility = ["//visibility:public"])
cc_library(
name = "greeter",
srcs = ["greeter.cc"],
hdrs = ["greeter.h"],
)
cc_binary(
name = "hello_bazel",
srcs = ["main.cc"],
deps = [":greeter"],
)
逐行解析:
python
# 设置包的默认可见性为公开(否则其他包无法引用 greeter 库)
package(default_visibility = ["//visibility:public"])
# 定义 greeter 库
cc_library(
name = "greeter", # 目标名,其他包通过 //src:greeter 引用
srcs = ["greeter.cc"], # 实现文件(私有,不传播)
hdrs = ["greeter.h"], # 公共头文件(传播给依赖者)
)
# 定义可执行目标
cc_binary(
name = "hello_bazel", # 产出 hello_bazel 可执行文件
srcs = ["main.cc"], # 入口源文件
deps = [":greeter"], # 依赖 greeter 库
# : 前缀表示同包依赖
)
5. 构建与运行
5.1 构建项目
bash
# 构建可执行程序
bazel build //src:hello_bazel
# 输出示例:
# INFO: Analyzed target //src:hello_bazel (15 packages loaded, 56 targets configured).
# INFO: Found 1 target...
# Target //src:hello_bazel up-to-date:
# bazel-bin/src/hello_bazel.exe
# INFO: Elapsed time: 3.456s, Critical Path: 1.23s
# INFO: 6 processes: 3 internal, 3 windows-sandbox.
# INFO: Build completed successfully, 6 total actions
5.2 运行程序
bash
# 方式一:bazel run(构建+运行一步到位)
bazel run //src:hello_bazel
# 输出:
# Hello, Bazel!
# Good morning, Bazel!
# 方式二:直接运行产物
./bazel-bin/src/hello_bazel # Linux/macOS
bazel-bin\src\hello_bazel.exe # Windows
5.3 构建产出目录
Bazel 的构建产出存放在特定的目录结构中:
bazel-<workspace-name>/
├── bazel-bin/ ← 构建产出(符号链接)
│ └── src/
│ ├── hello_bazel.exe
│ └── libgreeter.a (或 greeter.lib)
├── bazel-out/ ← 所有配置的输出根
├── bazel-testlogs/ ← 测试日志
├── bazel-<workspace>/ ← 执行根
└── bazel-genfiles/ ← 代码生成文件
注意 :不要直接修改
bazel-*目录下的文件,它们由 Bazel 管理。
5.4 构建库目标
bash
# 构建库(只编译不链接)
bazel build //src:greeter
# 输出示例:
# Target //src:greeter up-to-date:
# bazel-bin/src/libgreeter.a
# bazel-bin/src/greeter.h
6. 编写测试
6.1 添加 Google Test 依赖
编辑 WORKSPACE 文件:
python
# WORKSPACE
workspace(name = "hello_bazel")
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
# Google Test
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",
)
如果你使用 Bzlmod,在
MODULE.bazel中添加:
pythonbazel_dep(name = "googletest", version = "1.14.0")
6.2 编写测试代码
创建 tests/greeter_test.cc:
cpp
// tests/greeter_test.cc
#include "src/greeter.h"
#include "gtest/gtest.h"
namespace hello_bazel {
namespace {
TEST(GreeterTest, GreetReturnsCorrectMessage) {
Greeter greeter("World");
EXPECT_EQ(greeter.Greet(), "Hello, World!");
}
TEST(GreeterTest, GreetWithTimeReturnsCorrectMessage) {
Greeter greeter("World");
EXPECT_EQ(greeter.GreetWithTime("evening"), "Good evening, World!");
}
TEST(GreeterTest, GreetWithEmptyName) {
Greeter greeter("");
EXPECT_EQ(greeter.Greet(), "Hello, !");
}
} // namespace
} // namespace hello_bazel
6.3 编写测试 BUILD 文件
创建 tests/BUILD:
python
# tests/BUILD
cc_test(
name = "greeter_test",
srcs = ["greeter_test.cc"],
deps = [
"//src:greeter",
"@com_google_googletest//:gtest_main",
],
)
6.4 运行测试
bash
# 运行测试
bazel test //tests:greeter_test
# 输出示例:
# INFO: Analyzed target //tests:greeter_test (0 packages loaded, 0 targets configured).
# INFO: Found 1 test target...
# Target //tests:greeter_test up-to-date:
# bazel-bin/tests/greeter_test.exe
# INFO: Elapsed time: 1.234s, Critical Path: 0.89s
# INFO: 3 processes: 2 internal, 1 windows-sandbox.
# //tests:greeter_test PASSED in 0.1s
# 显示详细输出
bazel test //tests:greeter_test --test_output=all
# 运行所有测试
bazel test //...
# 仅运行失败的测试
bazel test //... --test_output=errors
7. 增量构建体验
Bazel 的一大优势是精确的增量构建。让我们来验证:
7.1 首次构建
bash
bazel build //src:hello_bazel
# INFO: 6 processes: 3 internal, 3 windows-sandbox.
7.2 无修改再次构建
bash
bazel build //src:hello_bazel
# INFO: 1 process: 1 internal.
# Target //src:hello_bazel up-to-date:
# ...
注意:Bazel 检测到没有变化,几乎瞬间完成。
7.3 修改源文件后构建
bash
# 修改 src/greeter.cc
echo '// modified' >> src/greeter.cc
bazel build //src:hello_bazel
# INFO: 3 processes: 1 internal, 2 windows-sandbox.
注意:只重新编译了 greeter.cc 及其下游目标,main.cc 没有被重新编译。
7.4 修改头文件后构建
bash
# 修改 src/greeter.h(恢复 greeter.cc 的修改)
echo '// modified header' >> src/greeter.h
bazel build //src:hello_bazel
# INFO: 4 processes: 1 internal, 3 windows-sandbox.
注意:由于 greeter.h 是公共头文件,greeter.cc 和 main.cc 都需要重新编译。
8. 常用构建操作汇总
bash
# ── 构建 ────────────────────────────────
bazel build //src:hello_bazel # 构建指定目标
bazel build //src:greeter # 构建库
bazel build //... # 构建所有目标
# ── 运行 ────────────────────────────────
bazel run //src:hello_bazel # 构建并运行
bazel run //src:hello_bazel -- --help # 传递参数
# ── 测试 ────────────────────────────────
bazel test //tests:greeter_test # 运行测试
bazel test //... # 运行所有测试
bazel test //... --test_output=all # 显示所有测试输出
# ── 清理 ────────────────────────────────
bazel clean # 清理构建输出
bazel clean --expunge # 完全清理
# ── 查询 ────────────────────────────────
bazel query //src:* # 列出 src 包的所有目标
bazel query "deps(//src:hello_bazel)" # 查看依赖图
bazel query "kind(cc_*, //...)" # 列出所有 C++ 目标
# ── 信息 ────────────────────────────────
bazel info # 查看构建信息
bazel info bazel-bin # 查看产出目录路径
9. 项目完整文件清单
hello-bazel/
├── WORKSPACE # 工作区定义 + 外部依赖
├── .bazelversion # Bazel 版本 7.3.1
├── .bazelrc # 构建配置
├── BUILD # 根包(别名)
├── src/
│ ├── BUILD # src 包构建规则
│ ├── greeter.h # Greeter 类声明
│ ├── greeter.cc # Greeter 类实现
│ └── main.cc # 主程序入口
└── tests/
├── BUILD # 测试构建规则
└── greeter_test.cc # Greeter 测试
10. 小结
本篇通过一个完整的实战项目,你学会了:
- ✅ 创建 Bazel C++ 项目的基本结构
- ✅ 编写
cc_library、cc_binary、cc_test规则 - ✅ 使用
WORKSPACE声明外部依赖(Google Test) - ✅ 构建和运行 C++ 程序
- ✅ 编写和运行 C++ 测试
- ✅ 体验 Bazel 的增量构建能力
- ✅ 常用的构建命令操作