Bazel C++ 构建系列文档(三):构建第一个 C++ 项目

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 中添加:

python 复制代码
bazel_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.ccmain.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_librarycc_binarycc_test 规则
  • ✅ 使用 WORKSPACE 声明外部依赖(Google Test)
  • ✅ 构建和运行 C++ 程序
  • ✅ 编写和运行 C++ 测试
  • ✅ 体验 Bazel 的增量构建能力
  • ✅ 常用的构建命令操作
相关推荐
KANGBboy1 小时前
java知识五(继承)
java·开发语言
AI人工智能+电脑小能手1 小时前
【大白话说Java面试题 第117题】【并发篇】第17题:线程有几种状态,之间如何转换?
java·开发语言·面试
旖-旎1 小时前
《LeetCode 695 岛屿的最大面积 FloodFill DFS 解法》
c++·算法·力扣·深度优先遍历·floodfill
森G2 小时前
61、信号与槽机制在 TCP 编程中的应用---------网络编程
网络·c++·qt·网络协议·tcp/ip
syagain_zsx2 小时前
STL 之 vector 讲练结合
c++·算法
聚名网2 小时前
域名net,com,cn有区别吗?有哪些不同呢?
服务器·开发语言·php
牛油果子哥q2 小时前
STL set与map底层精讲,红黑树适配原理、有序去重特性、迭代器遍历、API实战与面试核心考点全解
开发语言·数据结构·c++·面试
foundbug9992 小时前
直流电机 PID 速度控制 MATLAB 仿真程序
开发语言·matlab
奇妙方程式3 小时前
2026年第九届GXCPC广西大学生程序设计大赛(热身赛)题解
c++·编程比赛·编程竞赛·gxcpc