Chapter 08

构建系统:build.zig 详解

用 Zig 代码描述构建流程,取代 Makefile/CMake 的现代构建系统

为什么 Zig 用代码描述构建?

传统构建系统(Makefile、CMake、Meson)使用特殊的 DSL 语言,这些语言有自己的语法规则、转义规则和局限性。Zig 的 build.zig 是一个普通的 Zig 文件,用 Zig 代码描述构建步骤。优势在于:

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)]});
}

本章小结

本章核心要点