为什么 Zig 用代码描述构建?
传统构建系统(Makefile、CMake、Meson)使用特殊的 DSL 语言,这些语言有自己的语法规则、转义规则和局限性。Zig 的 build.zig 是一个普通的 Zig 文件,用 Zig 代码描述构建步骤。优势在于:
- 可以用完整的 Zig 语言逻辑(循环、条件、函数)处理复杂构建需求
- 类型安全:参数错误在编译时发现
- 无需学习额外的 DSL 语法
- 内置支持交叉编译、测试、文档生成
build.zig 基本结构
// build.zig
const std = @import("std");
pub fn build(b: *std.Build) void {
// 1. 构建目标(target):编译到哪个平台
const target = b.standardTargetOptions(.{});
// 2. 优化级别:Debug/ReleaseSafe/ReleaseFast/ReleaseSmall
const optimize = b.standardOptimizeOption(.{});
// 3. 添加可执行文件
const exe = b.addExecutable(.{
.name = "myapp",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// 4. 安装到 zig-out/bin/
b.installArtifact(exe);
// 5. 添加 `zig build run` 命令
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| run_cmd.addArgs(args);
const run_step = b.step("run", "Run the application");
run_step.dependOn(&run_cmd.step);
// 6. 添加测试
const unit_tests = b.addTest(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
const run_tests = b.addRunArtifact(unit_tests);
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_tests.step);
}
添加库
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// 静态库
const lib = b.addStaticLibrary(.{
.name = "mylib",
.root_source_file = b.path("src/lib.zig"),
.target = target,
.optimize = optimize,
});
b.installArtifact(lib);
// 动态库
const shared_lib = b.addSharedLibrary(.{
.name = "mylib",
.root_source_file = b.path("src/lib.zig"),
.target = target,
.optimize = optimize,
.version = .{ .major = 1, .minor = 0, .patch = 0 },
});
b.installArtifact(shared_lib);
// 可执行文件链接静态库
const exe = b.addExecutable(.{
.name = "app",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
exe.linkLibrary(lib); // 链接我们的静态库
b.installArtifact(exe);
}
依赖管理:build.zig.zon
// build.zig.zon(类似 package.json)
.{
.name = "myapp",
.version = "0.1.0",
.minimum_zig_version = "0.13.0",
.dependencies = .{
.httpz = .{
.url = "https://github.com/karlseguin/http.zig/archive/refs/tags/v0.9.0.tar.gz",
.hash = "12209cd9a232b73acbcc8e4b7a6f6cb9e22e9b9d8e1f6f3a5e7d2c4f1a3b5d7e9f2",
},
},
.paths = .{
"", // 包含整个项目目录
},
}
// 在 build.zig 中使用依赖
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// 获取依赖
const httpz_dep = b.dependency("httpz", .{
.target = target,
.optimize = optimize,
});
const exe = b.addExecutable(.{
.name = "server",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// 添加依赖模块
exe.root_module.addImport("httpz", httpz_dep.module("httpz"));
b.installArtifact(exe);
}
添加依赖的命令
# 添加依赖(自动更新 build.zig.zon 的 hash)
zig fetch --save https://github.com/karlseguin/http.zig/archive/refs/tags/v0.9.0.tar.gz
# 构建并运行
zig build run
# 运行所有测试,显示详细结果
zig build test --summary all
# 构建特定优化级别
zig build -Doptimize=ReleaseFast
# 查看所有可用步骤
zig build --help
交叉编译
# 查看所有支持的目标
zig targets | head -50
# 编译到 Windows x86_64(在 macOS/Linux 上)
zig build -Dtarget=x86_64-windows
# 编译到 Linux ARM64
zig build -Dtarget=aarch64-linux
# 编译到 WebAssembly
zig build -Dtarget=wasm32-wasi
# 生成最小体积的 Release 二进制
zig build -Doptimize=ReleaseSmall -Dtarget=x86_64-linux
自定义构建步骤
// 在 build.zig 中添加代码生成步骤
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// 自定义步骤:运行代码生成器
const gen = b.addExecutable(.{
.name = "codegen",
.root_source_file = b.path("tools/codegen.zig"),
.target = target,
.optimize = .Debug,
});
const run_gen = b.addRunArtifact(gen);
run_gen.addArg("src/generated.zig");
// 主程序依赖代码生成步骤
const exe = b.addExecutable(.{
.name = "app",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
exe.step.dependOn(&run_gen.step); // 先生成,再编译
b.installArtifact(exe);
}
build.zig 是 Zig 代码
build.zig 在构建时由 Zig 编译器编译执行,所以你可以在里面写任意 Zig 代码。例如读取环境变量、生成版本号字符串、动态添加源文件列表——这些在 Makefile 中需要复杂 shell 脚本的事情,在 build.zig 里只是普通的 Zig 代码。
测试集成
Zig 的测试框架内置于语言中,build.zig 提供了运行所有测试的标准方式:
// build.zig:添加测试目标
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// 添加主程序
const exe = b.addExecutable(.{
.name = "myapp",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
b.installArtifact(exe);
// 添加测试步骤
const unit_tests = b.addTest(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
const run_unit_tests = b.addRunArtifact(unit_tests);
// zig build test 命令
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_unit_tests.step);
}
// src/math.zig:编写测试
const std = @import("std");
pub fn add(a: i64, b: i64) i64 {
return a + b;
}
pub fn fibonacci(n: u32) u64 {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// 测试函数:以 test 开头的块
test "add 基本测试" {
try std.testing.expectEqual(@as(i64, 5), add(2, 3));
try std.testing.expectEqual(@as(i64, 0), add(-1, 1));
try std.testing.expectEqual(@as(i64, -3), add(-1, -2));
}
test "fibonacci 序列" {
try std.testing.expectEqual(@as(u64, 0), fibonacci(0));
try std.testing.expectEqual(@as(u64, 1), fibonacci(1));
try std.testing.expectEqual(@as(u64, 55), fibonacci(10));
}
// 错误场景测试
test "整数溢出检测" {
// 在 Debug/ReleaseSafe 模式下,溢出会触发 panic
// 用 expectError 捕获预期的错误
const result = std.math.add(i8, 127, 1);
try std.testing.expectError(error.Overflow, result);
}
# 运行所有测试
zig build test
# 只运行名称包含 "add" 的测试
zig build test -- --test-filter add
# 显示详细测试输出
zig test src/math.zig --verbose-runtime-errors
# 直接运行测试文件(不经过 build.zig)
zig test src/math.zig
嵌入文件:@embedFile
Zig 可以在编译时将文件内容嵌入到二进制中,生成完全自包含的单文件可执行程序:
const std = @import("std");
// @embedFile 将文件内容作为编译期常量嵌入
// 路径相对于当前 .zig 文件
const html_template = @embedFile("templates/index.html");
const default_config = @embedFile("config/default.json");
const wasm_module = @embedFile("../zig-out/lib/module.wasm");
pub fn main() void {
// html_template 是 []const u8,直接在代码中使用
std.debug.print("Template size: {} bytes\n", .{html_template.len});
std.debug.print("First 100 chars:\n{s}\n", .{html_template[0..@min(100, html_template.len)]});
}
本章小结
本章核心要点
- build.zig 是普通 Zig 代码:不是 DSL,可以使用完整的语言特性;std.Build 提供 API 来描述构建目标、步骤和依赖关系。
- 标准构建选项:
standardTargetOptions和standardOptimizeOption让用户通过-Dtarget和-Doptimize控制构建目标;四种优化模式(Debug/ReleaseFast/ReleaseSafe/ReleaseSmall)各有适用场景。 - 交叉编译是一等公民:一行命令即可交叉编译到 Windows/Linux/macOS/WebAssembly,无需额外工具链;Zig 内置了所有主流平台的系统 libc。
- build.zig.zon 管理依赖:通过 URL + hash 锁定外部依赖;
zig fetch --save URL自动添加依赖;依赖可以是 Git 仓库快照或 tarball。 - 测试集成:
b.addTest()添加测试目标,zig build test运行;测试代码直接写在源文件中(test 块),无需单独文件。 - @embedFile 实现单文件部署:将资源文件编译进二进制,无需附带资源目录;适合工具程序、命令行应用等。