本文节选自《Zig 构建系统指南》一书 https://jiacai2050.github.io/x/zig-build/
在了解 Zig 构建系统的设计之前,先看一下传统 C/C++ 构建工具的演进,以及它们在工程实践中常遇到的问题。
早期源码较少时,直接调用单行编译命令(如
cc main.c -o main)即可完成构建。随着代码规模增长和平台增加,构建工具逐渐演变出不同形态。
graph LR
subgraph S_Gen1 ["第一代:规则驱动 (Rule-Driven)"]
G1_Make["Make (1976)<br/>文件时间戳比对<br/>Tab 缩进与 Shell 语法混杂"]
end
subgraph S_Gen2 ["第二代:元构建系统 (Meta-Build)"]
G2_Auto["Autotools (GNU M4 / sh)<br/>生成目标系统的 Makefile"]
G2_CMake["CMake (2000)<br/>跨平台 DSL 生成器<br/>输出 Make / Ninja / VS 工程"]
end
subgraph S_Gen3 ["第三代:极速低级引擎 (Fast Engines)"]
G3_Ninja["Ninja (2011)<br/>专为高并发设计的扁平执行器"]
end
G1_Make -- "配置复杂度上升" --> G2_Auto
G2_Auto -- "跨平台抽象" --> G2_CMake
G2_CMake -- "调度性能优化" --> G3_Ninja
classDef default stroke:#495057;
style S_Gen1 stroke:#ff9900,stroke-width:2px;
style S_Gen2 stroke:#0066cc,stroke-width:2px;
style S_Gen3 stroke:#009900,stroke-width:2px;
style G1_Make stroke:#ff9900,stroke-width:2px;
style G2_Auto stroke:#0066cc,stroke-width:2px;
style G2_CMake stroke:#0066cc,stroke-width:2px;
style G3_Ninja stroke:#009900,stroke-width:2px;mtime)决定是否需要重新编译。rm -rf、cp),跨平台(尤其是
Windows)支持需要额外适配;-O2 改为
-O3),Make 不会触发重编。find_package、Git submodule 或外部集成脚本。Rust(Cargo)和 Go(Go Modules)将包管理器与构建工具集成在语言发行版中,改善了纯原生语言代码的构建体验。
graph TD
subgraph S_Rust ["Rust 构建生态 (Cargo)"]
R_Cargo["Cargo 包管理器 & 构建入口"]
R_Script["build.rs 自定义桥接脚本"]
R_Ext["外部 C/C++ 库 (cc-rs / cmake-rs)"]
R_Cargo --> R_Script
R_Script --> R_Ext
end
subgraph S_Go ["Go 构建生态 (go build)"]
G_Go["go build 单体命令"]
G_Cgo["CGo (需依赖宿主 GCC/Clang)"]
G_Ext["外部 C 依赖"]
G_Go --> G_Cgo
G_Cgo --> G_Ext
end
classDef default stroke:#495057;
style S_Rust stroke:#ff9900,stroke-width:2px;
style S_Go stroke:#0066cc,stroke-width:2px;
style R_Cargo stroke:#ff9900,stroke-width:2px;
style R_Script stroke:#0066cc,stroke-width:2px;
style R_Ext stroke:#495057,stroke-width:2px;
style G_Go stroke:#0066cc,stroke-width:2px;
style G_Cgo stroke:#ffc107,stroke-width:2px;
style G_Ext stroke:#495057,stroke-width:2px;然而涉及 C/C++ 依赖时,仍存在工具链边界:
build.rs 的桥接成本: Cargo
主要负责 Rust 代码编译。当项目依赖 OpenSSL、SQLite、RocksDB 等 C/C++
库时,通常在 build.rs 中通过 cc 或
cmake crate 调用宿主机的编译器和
CMake,宿主机依然需要安装完整的外部编译工具。GOOS 和 GOARCH 交叉编译;但开启
CGO_ENABLED=1
后,需要宿主机自行安装目标平台的交叉编译工具链(如
aarch64-linux-gnu-gcc)。传统工具链在交叉编译时通常面临以下问题:
version 'GLIBC_2.XX' not found
错误。传统做法通常是在旧版系统容器中编译,或单独准备一套旧版 glibc 的
sysroot。toolchain.cmake,配置不当容易误引入宿主系统的头文件和动态库。对比不同构建工具的实现策略:
| 维度 | 传统 Make / CMake | Cargo + build.rs | 目标诉求 |
|---|---|---|---|
| 脚本语言 | 专用 DSL / Shell | Rust + 外部工具 | 通用编程语言(类型检查与复用性) |
| C/C++ 原生支持 | 原生支持,配置分散 | 依赖外部调用 | 原生内嵌 C/C++ 编译能力 |
| 跨平台交叉编译 | 依赖外部 sysroot 与工具链 | 依赖宿主交叉工具链 | 开箱可用,无需额外安装外部工具 |
| 构建图模型 | 规则展开 | 过程式脚本 | 显式声明的有向无环图 (DAG) |
| 缓存策略 | 基于文件 mtime | 基于内容哈希 (仅限 Rust) | 基于内容哈希与环境参数比对 |
不过,将编译器、链接器和 libc 符号整合进同一个工具链中,也会增加分发包的体积和维护成本。下一章将介绍 Zig 构建系统的设计哲学与具体的权衡取舍。
Zig 中文社区是一个开放的组织,我们致力于推广 Zig 在中文群体中的使用,有多种方式可以参与进来: 1. 供稿,分享自己使用 Zig 的心得 2. 改进 ZigCC 组织下的开源项目 3. 加入微信群、QQ 群、QQ 频道、Telegram 群组、Google Groups 与更多 Zig 爱好者交流