找回密码
立即注册
搜索
发回帖 发新帖

5323

积分

0

好友

685

主题
发表于 昨天 23:26 | 查看: 11| 回复: 0

1 Bazel 简介

1.1 Bazel 是什么

Bazel 是 Google 开源的构建 + 测试一体化工具,前身是谷歌内部的构建系统 Blaze,2015 年对外开源。有意思的是,Bazel 这个名字其实就是 Blaze 的字母重组词。

Bazel 官方标识

定位上,Bazel 与 Make / CMake / Gradle 属于同一类工具,但它的设计目标很明确:专门面向巨型单体仓库(monorepo)、多语言、跨平台、可复现构建场景。

1.2 核心特点

声明式构建,Starlark 语言

  • 使用 BUILD 文件(语法是 Starlark,Python 的子集)来描述构建目标,例如 cc_binary、cc_library、java_library、py_test 等。
  • 文件中描述的是目标(target)和依赖关系,而不是直接写编译器命令。Bazel 会自动推导编译链路,这与 Makefile 那种直接写 shell 命令的思路完全不同。
  • 另外还有 WORKSPACE 文件,用来声明外部依赖,比如远程 Git 仓库、Maven 包等。

内容哈希缓存 + 增量构建 + 远程缓存

  • 只重新编译被改动的目标,而且判断依据不是文件修改时间,而是文件内容哈希。
  • 支持远程缓存和远程执行:多开发者、CI 可以共享构建产物,大仓库的构建速度提升非常明显,Google 内部的缓存命中率据说是极高的。
  • 自动并行执行互不依赖的构建 / 测试任务。

多语言、跨平台

  • 原生支持 C/C++、Java、Go、Python、Rust、TypeScript、Objective-C。
  • 同一套 BUILD 文件,可以编译 Linux / macOS / Windows 服务端,以及 Android、iOS 移动端程序,真正的一套定义多平台输出。

1.3 适合什么项目

  • 大型单体仓库 monorepo,多语言混编(如 C++ + Java + Python 共存)
  • 跨平台构建(同时编译服务器 + 移动端)
  • 大量自动化测试,CI 构建压力较大的团队
  • 要求构建结果可复现、多开发者协作的环境

2 在 Ubuntu 中安装 Bazel 工具

curl -LO https://github.com/bazelbuild/bazel/releases/download/7.4.0/bazel-7.4.0-linux-x86_64
chmod +x bazel-7.4.0-linux-x86_64
sudo mv bazel-7.4.0-linux-x86_64 /usr/local/bin/bazel
bazel --version

在 Ubuntu 中直接下载比较慢,实测在 Windows 中下载速度会快不少,可以先在 Windows 下载再复制到 Ubuntu 中继续操作。

Ubuntu 终端中下载安装 Bazel 7.4.0 并验证版本的完整命令过程

执行 bazel --version 后可以看到版本号为 7.4.0,说明安装成功。

3 Bazel 构建测试

参考 Bazel 官方示例代码: https://github.com/bazelbuild/examples/tree/main/cpp-tutorial/stage1

3.1 测试代码

首先看 MODULE.bazel 文件:

bazel_dep(name = "rules_cc", version = "0.0.17")

main/BUILD 文件:

load("@rules_cc//cc:cc_binary.bzl", "cc_binary")

cc_binary(
    name = "hello",
    srcs = ["hello.cpp"],
)

main/hello.cpp 文件:

#include <ctime>
#include <string>
#include <iostream>

std::string get_greet(const std::string& who){
    return "Hello " + who;
}

void print_localtime(){
    std::time_t result = std::time(nullptr);
    std::cout << std::asctime(std::localtime(&result));
}

int main(int argc, char** argv){
    std::string who = "world";
    if (argc > 1) {
        who = argv[1];
    }
    std::cout << get_greet(who) << std::endl;
    print_localtime();
    return 0;
}

项目结构如下:

xxpcb@xxpcb-ubuntu20:~/myTest/Bazel/test_demo$ tree
.
└── test1
    ├── main
    │   ├── BUILD
    │   └── hello.cpp
    └── MODULE.bazel

2 directories, 3 files
xxpcb@xxpcb-ubuntu20:~/myTest/Bazel/test_demo$

3.2 开始构建

执行构建后,Bazel 会尝试从 GitHub 的 bazelbuild 仓库下载文件,但在国内网络环境下很容易报错。

尝试在 ~/.bazelrc 中配置了一个国内镜像地址:

common --registry=https://mirror.ghproxy.com/https://bcr.bazel.build

但再次构建仍然失败:

Bazel 构建时从 GitHub 下载 rules_cc 超时报错及尝试配置 ghproxy 镜像后依旧失败的终端截图

注意到项目中自动生成了一个 MODULE.bazel.lock 文件:

代码编辑器中打开的 MODULE.bazel.lock 文件,包含 BCR 仓库各模块的 SHA256 哈希记录

这个文件是 Bzlmod 从 BCR(Bazel Central Registry)下载 rules_cc 时生成的。虽然刚才配置了 ghproxy 的 registry 镜像,但 ghproxy 的访问稳定性并不好,经常直接超时。

3.3 构建失败问题解决

面对这个问题,有两种思路:

  • 方案 A:出于入门学习目的,直接关掉 Bzlmod,不使用 MODULE.bazel,回到经典的 WORKSPACE 模式,这样就完全不需要联网下载 rules_cc 了。
  • 方案 B:继续使用 Bzlmod + MODULE.bazel,但需要配置 --module_mirrors,配置比较复杂,可以后续再深入研究。

本文先采用方案 A。

3.3.1 代码修改

# 删除 MODULE.bazel,Bzlmod 就不会自动拉取 rules_cc 了
rm MODULE.bazel

# 创建 WORKSPACE 文件(空文件即可,作为经典老工作区的标记)
touch WORKSPACE

main/BUILD 文件去掉开头的 load 那一行:

cc_binary(
    name = "hello",
    srcs = ["hello.cpp"],
)

3.3.2 关闭 Bzlmod

命令行参数方式(单次生效):

bazel build --noenable_bzlmod //main:hello

写入 .bazelrc 永久关闭(推荐,一劳永逸):

echo "common --noenable_bzlmod" >> ~/.bazelrc

3.3.3 再次执行

bazel build //main:hello

这里简单解释一下 //main:hello 这个目标表达式的含义:

  • // 双斜杠:表示工作区根目录起点
    • // 就代表项目根(本例中是 test1/,里面有 WORKSPACE 文件)
    • //main 就是根目录下的 main 文件夹
  • : 冒号:分隔 package 路径和 target 目标名
    • 冒号前面是 package 路径(//main)
    • 冒号后面是 target 名称(在 BUILD 文件中定义的 hello)

这次构建成功了,同时也生成了多个 bazel 相关的目录:

Bazel 关闭 Bzlmod 后使用 WORKSPACE 模式构建成功,生成了多个 bazel 目录的终端与文件管理器截图

3.4 测试可执行程序的运行

可以直接找到 bazel-bin 下的产物运行,也可以使用 bazel run 命令:

终端中分别使用直接路径和 bazel run 方式运行 hello 程序,均输出 Hello world 及本地时间

3.5 构建产物分析

构建完成后,工作目录下会多出几个 bazel 开头的目录:

  • bazel-bin:编译输出的二进制、库、头文件,最终产物都在这里
  • bazel-out:构建的核心工作目录,存放编译中间产物(体量最大)
  • bazel-test1:Bazel 的执行根目录(execution root)
  • bazel-testlogs:单元测试的日志目录

3.5.1 bazel 目录软链接

这些生成的 bazel 目录实际上都是软链接,真实指向 ~/.cache 目录下的位置:

xxpcb@xxpcb-ubuntu20:~/myTest/Bazel/test_demo/test1$ ls -l
total 20
lrwxrwxrwx 1 xxpcb xxpcb  115 Oct 18 16:29 bazel-bin -> /home/xxpcb/.cache/bazel/_bazel_xxpcb/139b038fc59e32075309080b94868442/execroot/__main__/bazel-out/k8-fastbuild/bin
lrwxrwxrwx 1 xxpcb xxpcb   98 Oct 18 16:29 bazel-out -> /home/xxpcb/.cache/bazel/_bazel_xxpcb/139b038fc59e32075309080b94868442/execroot/__main__/bazel-out
lrwxrwxrwx 1 xxpcb xxpcb   88 Oct 18 16:29 bazel-test1 -> /home/xxpcb/.cache/bazel/_bazel_xxpcb/139b038fc59e32075309080b94868442/execroot/__main__
lrwxrwxrwx 1 xxpcb xxpcb  120 Oct 18 16:29 bazel-testlogs -> /home/xxpcb/.cache/bazel/_bazel_xxpcb/139b038fc59e32075309080b94868442/execroot/__main__/bazel-out/k8-fastbuild/testlogs
drwxrwxr-x 2 xxpcb xxpcb 4096 Oct 18 16:28 main
-rw-rw-r-- 1 xxpcb xxpcb    0 Oct 18 16:27 WORKSPACE
xxpcb@xxpcb-ubuntu20:~/myTest/Bazel/test_demo/test1$

进入 .cache 目录查看:

xxpcb@xxpcb-ubuntu20:~/myTest/Bazel/test_demo/test1/bazel-out$ cd ~/.cache/
xxpcb@xxpcb-ubuntu20:~/.cache$ ls
bazel                          evolution      ibus         mesa_shader_cache     mozilla     thunderbird    update-manager-core
bazelisk                       fontconfig     ibus-table   Microsoft             rhythmbox   tracker        yelp
event-sound-cache.tdb.9fe5b643f31a452cad31d0c22abb2f43.x86_64-pc-linux-gnu  gstreamer-1.0  libgweather  motd.legal-displayed  thumbnails  ubuntu-report
xxpcb@xxpcb-ubuntu20:~/.cache$ cd bazel
xxpcb@xxpcb-ubuntu20:~/.cache/bazel$ ls
_bazel_xxpcb
xxpcb@xxpcb-ubuntu20:~/.cache/bazel$ cd _bazel_xxpcb/
xxpcb@xxpcb-ubuntu20:~/.cache/bazel/_bazel_xxpcb$ ls
139b038fc59e32075309080b94868442  cache  install

进入 bazel/_bazel_xxpcb 后可以看到 3 个子目录。路径 ~/.cache/bazel/_bazel_xxpcb/ 这一层被称作 outputUserRoot,当前用户 xxpcb 的全部 Bazel 数据都存放在这里。

  • 139b038fc59e32075309080b94868442:output_base,输出基目录,以 MD5 哈希命名
  • cache:全局共享仓库缓存(repository cache)
  • install:Bazel 二进制首次运行时,会把内嵌的 Java 服务 jar、linux-sandbox、辅助工具脚本等解压到这里

3.5.2 三个子目录详解

MD5 哈希目录

  • 名字是项目工作区路径做 MD5 哈希生成的字符串,一个项目对应一个哈希文件夹
  • 存放 Bazel Server 进程信息、编译沙盒、external/ 外部仓库、编译 action 缓存、编译中间产物
  • 还保存 Bazel 服务的 PID、日志、JVM 日志、沙盒执行目录 execroot
  • 执行 bazel clean --expunge 会清空这个目录

Bazel 缓存目录中 output_base 内部的哈希文件树结构及 tree 命令展示

如何确认项目对应的哈希值?可以使用这条指令:

xxpcb@xxpcb-ubuntu20:~/myTest/Bazel/test_demo/test1$ bazel info output_base
/home/xxpcb/.cache/bazel/_bazel_xxpcb/139b038fc59e32075309080b94868442
xxpcb@xxpcb-ubuntu20:~/myTest/Bazel/test_demo/test1$

cache 目录

  • 跨项目共享!所有 Bazel 工程共用这个缓存
  • 通过 http_archive / Bzlmod 下载的外部压缩包(如 rules_cc、protobuf 等第三方依赖),按 SHA256 哈希存储
  • 同一个第三方包,A 项目下载过,B 项目再使用时可以直接复用,无需重复联网下载

Bazel 全局缓存 cache 目录的 content_addressable/sha256 哈希存储结构

install 目录

  • 不同 Bazel 版本会按哈希分开存放
  • 包含 A-server.jar:Bazel 后台 Server 的核心程序
  • 沙盒工具、process-wrapper 等底层执行工具
  • 只要不升级 Bazel 版本,这个目录基本不会变动

Bazel install 安装目录的树状结构,包含 A-server.jar、embedded_tools、rules_java 等

3.5.3 构建产物清除

  • bazel clean:清除工作区中 bazel 软链接指向的那些生成文件
  • bazel clean --expunge:清除 cache 中对应的哈希目录

终端中分别执行 bazel clean 与 bazel clean --expunge 清理构建产物及缓存的对比效果

4 总结

本篇介绍了 Bazel 构建工具的基础知识,并在 Ubuntu 中完成了环境配置,用一个最基础的 hello world 程序进行了编译和运行测试。过程中遇到了 Bzlmod 模式下国内网络下载 rules_cc 超时的问题,通过切换到 WORKSPACE 模式绕开了网络依赖。此外,还分析了构建产物目录的结构,包括 output_base 的哈希目录、全局共享缓存和 install 安装目录,对 Bazel 的存储布局有了一个初步认识。如果你对构建系统的底层机制感兴趣,后续可以继续深入研究 Bzlmod 的镜像配置以及远程缓存的使用。




上一篇:嵌入式Linux C++ WiFi配网实战:调用wpa_supplicant与交叉编译验证
下一篇:16G 显存本地跑,Qwen-Image-2.1:文生图+编辑+透明底 PNG 一个模型搞定
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-10-22 05:48 , Processed in 0.097685 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表