概览
模块解析为我们提供了推理编译器图的词汇。现在我们将这些词汇转化为基础设施。本章深入探讨std.Build的基础知识之外,探索构件巡览和库/可执行工作区。我们将有意注册模块,组合多包工作区,在不接触shell脚本的情况下生成构建输出,并从单个build.zig驱动跨目标矩阵。参见Build.zig。
你将学习命名写入文件、匿名模块和resolveTargetQuery如何为构建运行器提供输入,如何保持vendored代码与注册表依赖隔离,以及如何连接CI作业来证明你的图在Debug和Release构建中表现一致。参见build_runner.zig。
构建系统如何执行
在进入高级模式之前,必须先理解std.Build的执行方式。下图展示了从 Zig 编译器调用你的build.zig脚本到最终制品安装的完整流程:
你的build.zig是由编译器编译并执行的普通 Zig 程序。入口是build()函数,它接收一个*std.Build实例,提供定义步骤、制品和依赖的 API。构建参数(-D 选项)由b.option()解析,并以编译期常量的形式流入你的构建逻辑。构建运行器随后遍历你声明的步骤依赖图,只执行满足所请求目标所需的步骤(默认是安装步骤)。这种声明式模型确保可复现性:相同的输入总是产生相同的构建图。
学习目标
- 显式注册可复用模块与匿名包,控制哪些名称出现在导入命名空间。25
- 使用具名写入文件从构建图生成可确定的制品(报告、清单),而非临时的 shell 脚本。
- 使用
resolveTargetQuery协调多目标构建,包括宿主健康检查与跨编译流水线。22,Compile.zig - 构造复合工作区,使引入的 vendored 模块保持私有,同时注册表包保持自洽。24
- 在 CI 中固化可复现性:安装步骤、运行步骤与生成的制品全部依附于
std.Build.Step的依赖关系。
构建工作区外层接口
工作区本质上是具有清晰命名空间边界的构建图。下例提升三个模块——analytics、reporting,以及一个引入的adapters助手——并展示根可执行文件如何消费它们。我们强调哪些模块是全局注册的,哪些保持匿名,以及如何直接从构建图发射文档。
const std = @import("std");
pub fn build(b: *std.Build) void {
// 标准目标和优化选项允许通过CLI标志配置构建
// 用于不同的架构和优化级别
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// 创建analytics模块 - 提供核心度量计算和分析功能的基础模块
const analytics_mod = b.addModule("analytics", .{
.root_source_file = b.path("workspace/analytics/lib.zig"),
.target = target,
.optimize = optimize,
});
// 创建reporting模块 - 依赖analytics来格式化和显示度量
// 使用addModule()一步创建和注册模块
const reporting_mod = b.addModule("reporting", .{
.root_source_file = b.path("workspace/reporting/lib.zig"),
.target = target,
.optimize = optimize,
// 导入analytics模块以访问度量类型和计算函数
.imports = &.{.{ .name = "analytics", .module = analytics_mod }},
});
// 使用createModule()创建adapters模块 - 创建但不注册
// 这演示了一个匿名模块,其他代码可以导入但不会
// 出现在全局模块命名空间中
const adapters_mod = b.createModule(.{
.root_source_file = b.path("workspace/adapters/vendored.zig"),
.target = target,
.optimize = optimize,
// Adapters需要analytics来序列化度量数据
.imports = &.{.{ .name = "analytics", .module = analytics_mod }},
});
// 创建编排所有依赖的主应用程序模块
// 这演示了根模块如何组合多个导入的模块
const app_module = b.createModule(.{
.name = "app", // 给模块一个名字
.root_source_file = b.path("workspace/app/main.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
// 导入所有三个工作区模块以访问其功能
.{ .name = "analytics", .module = analytics_mod },
.{ .name = "reporting", .module = reporting_mod },
.{ .name = "adapters", .module = adapters_mod },
},
});
// 使用组合的app模块作为其根创建可执行文件
// root_module字段替换了传统的root_source_file方法
const exe = b.addExecutable(.{
.name = "workspace-app",
.root_module = app_module,
});
// 将可执行文件安装到zig-out/bin,以便构建后可以运行
b.installArtifact(exe);
// 设置执行构建可执行文件的运行命令
const run_cmd = b.addRunArtifact(exe);
// 转发传递给构建系统的任何命令行参数到可执行文件
if (b.args) |args| {
run_cmd.addArgs(args);
}
// 创建自定义构建步骤"run",用户可以使用`zig build run`调用
const run_step = b.step("run", "Run workspace app with registered modules");
run_step.dependOn(&run_cmd.step);
// 创建命名写入文件步骤来记录模块依赖关系图
// 这有助于理解工作区结构而不读取代码
const graph_files = b.addNamedWriteFiles("graph");
// 生成记录模块注册层次结构的文本文件
_ = graph_files.add("module-graph.txt",
\\workspace module registration map:
\\ analytics -> workspace/analytics/lib.zig
\\ reporting -> workspace/reporting/lib.zig (imports analytics)
\\ adapters -> (anonymous) workspace/adapters/vendored.zig
\\ exe root -> workspace/app/main.zig
);
// 创建自定义构建步骤"graph",生成模块文档
// 用户可以使用`zig build graph`调用此步骤来输出依赖关系图
const graph_step = b.step("graph", "Emit module graph summary to zig-out");
graph_step.dependOn(&graph_files.step);
}
build()函数遵循刻意的节奏:
b.addModule("analytics", …)注册一个公共名称,使整个工作区都能@import("analytics")。Module.zigb.createModule创建一个私有模块(adapters),仅根可执行文件可见——适合不应被消费者直接访问的 vendored 代码。24b.addNamedWriteFiles("workspace-graph")在zig-out/生成module-graph.txt,无需定制工具即可记录命名空间映射。- 所有依赖都通过
.imports穿线,编译器不再退回到文件系统猜测。25
$ zig build --build-file 01_workspace_build.zig runmetric: response_ms
count: 6
mean: 12.95
deviation: 1.82
profile: stable
json export: {
"name": "response_ms",
"mean": 12.950,
"deviation": 1.819,
"profile": "stable"
}$ zig build --build-file 01_workspace_build.zig graphNo stdout expected.具名写入文件遵循缓存:在无变更时重复运行zig build … graph是瞬时的。查看zig-out/graph/module-graph.txt以了解构建运行器输出的映射。
工作区的库代码
为保持示例自包含,模块与构建脚本并排存放。可按需调整,或替换为在build.zig.zon中声明的注册表依赖。
// Analytics library for statistical calculations on metrics
// 用于度量统计计算的分析库
const std = @import("std");
// Represents a named metric with associated numerical values
// 表示具有相关数值的命名度量
pub const Metric = struct {
name: []const u8,
values: []const f64,
};
// Calculates the arithmetic mean (average) of all values in a metric
// 计算度量中所有值的算术平均值
// Returns the sum of all values divided by the count
// 返回所有值的总和除以计数
pub fn mean(metric: Metric) f64 {
var total: f64 = 0;
for (metric.values) |value| {
total += value;
}
return total / @as(f64, @floatFromInt(metric.values.len));
}
// Calculates the standard deviation of values in a metric
// 计算度量中值的标准差
// Uses the population standard deviation formula: sqrt(sum((x - mean)^2) / n)
// 使用总体标准差公式:sqrt(sum((x - mean)^2) / n)
pub fn deviation(metric: Metric) f64 {
const avg = mean(metric);
var accum: f64 = 0;
// Sum the squared differences from the mean
// 求和每个值与平均值之差的平方
for (metric.values) |value| {
const delta = value - avg;
accum += delta * delta;
}
// Return the square root of the variance
// 返回方差的平方根
return std.math.sqrt(accum / @as(f64, @floatFromInt(metric.values.len)));
}
// Classifies a metric as "variable" or "stable" based on its standard deviation
// 根据度量的标准差将其分类为“可变”或“稳定”
// Metrics with deviation > 3.0 are considered variable, otherwise stable
// 偏差 > 3.0 的度量被认为是可变的,否则是稳定的
pub fn highlight(metric: Metric) []const u8 {
return if (deviation(metric) > 3.0)
"variable"
else
"stable";
}
// ! Reporting module for displaying analytics metrics in various formats.
// ! 用于以各种格式显示分析指标的报告模块。
// ! This module provides utilities to render metrics as human-readable text
// ! 该模块提供将指标渲染为人类可读文本的工具
// ! or export them in CSV format for further analysis.
// ! 或以 CSV 格式导出它们以进行进一步分析。
const std = @import("std");
const analytics = @import("analytics");
// / Renders a metric's statistics to a writer in a human-readable format.
// / 以人类可读的格式将指标的统计数据渲染到写入器。
// / Outputs the metric name, number of data points, mean, standard deviation,
// / 输出指标名称、数据点数量、平均值、标准差、
// / and performance profile label.
// / 和性能配置文件标签。
///
/// Parameters:
// / - metric: The analytics metric to render
// / - metric: 要渲染的分析指标
// / - writer: Any writer interface that supports the print() method
// / - writer: 支持 print() 方法的任何写入器接口
///
// / Returns an error if writing to the output fails.
// / 如果写入输出失败,则返回错误。
pub fn render(metric: analytics.Metric, writer: anytype) !void {
try writer.print("metric: {s}\n", .{metric.name});
try writer.print("count: {}\n", .{metric.values.len});
try writer.print("mean: {d:.2}\n", .{analytics.mean(metric)});
try writer.print("deviation: {d:.2}\n", .{analytics.deviation(metric)});
try writer.print("profile: {s}\n", .{analytics.highlight(metric)});
}
// / Exports a metric's statistics as a CSV-formatted string.
// / 将指标的统计数据导出为 CSV 格式的字符串。
// / Creates a two-row CSV with headers and a single data row containing
// / 创建一个包含标题的两行 CSV,以及一个包含
// / the metric's name, mean, deviation, and highlight label.
// / 指标名称、平均值、偏差和高亮标签的单行数据。
///
/// Parameters:
// / - metric: The analytics metric to export
// / - metric: 要导出的分析指标
// / - allocator: Memory allocator for the resulting string
// / - allocator: 用于结果字符串的内存分配器
///
// / Returns a heap-allocated CSV string, or an error if allocation or formatting fails.
// / 返回一个堆分配的 CSV 字符串,如果分配或格式化失败,则返回错误。
// / Caller is responsible for freeing the returned memory.
// / 调用者负责释放返回的内存。
pub fn csv(metric: analytics.Metric, allocator: std.mem.Allocator) ![]u8 {
return std.fmt.allocPrint(
allocator,
"name,mean,deviation,label\n{s},{d:.3},{d:.3},{s}\n",
.{ metric.name, analytics.mean(metric), analytics.deviation(metric), analytics.highlight(metric) },
);
}
const std = @import("std");
const analytics = @import("analytics");
// 将度量序列化为JSON格式的字符串表示。
///
// 创建一个格式化的JSON对象,包含度量名称、计算的平均值、
// 标准差和性能配置文件分类。调用者拥有
// 返回的内存,并在使用完毕后必须释放。
///
// 返回一个包含JSON表示的已分配字符串,如果分配失败则返回错误。
pub fn emitJson(metric: analytics.Metric, allocator: std.mem.Allocator) ![]u8 {
return std.fmt.allocPrint(
allocator,
"{{\n \"name\": \"{s}\",\n \"mean\": {d:.3},\n \"deviation\": {d:.3},\n \"profile\": \"{s}\"\n}}\n",
.{ metric.name, analytics.mean(metric), analytics.deviation(metric), analytics.highlight(metric) },
);
}
// Import standard library for core functionality
// 导入标准库以获取核心功能
const std = @import("std");
// Import analytics module for metric data structures
// 导入 analytics 模块以获取度量数据结构
const analytics = @import("analytics");
// Import reporting module for metric rendering
// 导入 reporting 模块以进行度量渲染
const reporting = @import("reporting");
// Import adapters module for data format conversion
// 导入 adapters 模块以进行数据格式转换
const adapters = @import("adapters");
// Application entry point demonstrating workspace dependency usage
// 演示工作区依赖项使用的应用程序入口点
// Shows how to use multiple workspace modules together for metric processing
// 展示如何将多个工作区模块结合用于度量处理
pub fn main() !void {
// Create a fixed-size buffer for stdout operations to avoid dynamic allocation
// 为标准输出操作创建一个固定大小的缓冲区以避免动态分配
var stdout_buffer: [512]u8 = undefined;
// Initialize a buffered writer for stdout to improve I/O performance
// 初始化一个缓冲写入器以提高标准输出I/O性能
var writer_state = std.fs.File.stdout().writer(&stdout_buffer);
const out = &writer_state.interface;
// Create a sample metric with response time measurements in milliseconds
// 创建一个包含响应时间测量(毫秒)的示例度量
const metric = analytics.Metric{
.name = "response_ms",
.values = &.{ 12.0, 12.4, 11.9, 12.1, 17.0, 12.3 },
};
// Render the metric using the reporting module's formatting
// 使用 reporting 模块的格式化功能渲染度量
try reporting.render(metric, out);
// Initialize general purpose allocator for JSON serialization
// 初始化用于JSON序列化的通用分配器
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
// Ensure allocator cleanup on function exit
// 确保函数退出时清理分配器
defer _ = gpa.deinit();
// Convert metric to JSON format using the adapters module
// 使用 adapters 模块将度量转换为JSON格式
const json = try adapters.emitJson(metric, gpa.allocator());
// Free allocated JSON string when done
// 完成后释放已分配的JSON字符串
defer gpa.allocator().free(json);
// Output the JSON representation of the metric
// 输出度量的JSON表示
try out.print("json export: {s}\n", .{json});
// Flush buffered output to ensure all data is written
// 刷新缓冲输出以确保所有数据写入
try out.flush();
}
依赖卫生检查清单
- 以明确名称注册 vendored 模块,并仅通过
.imports共享。除非确有需要让消费者直接导入,否则不要通过b.addModule泄露它们。 - 将
zig-out/workspace-graph/module-graph.txt视为活文档。提交输出以便 CI 校验,或通过 diff 捕捉意外的命名空间变化。 - 对于注册表依赖,
b.dependency()的句柄只转发一次,并用本地模块包裹起来,从而将升级噪音隔离。24
将构建选项作为配置
构建选项为工作区可配置性提供了强有力的机制。下图展示了命令行-D参数如何流经b.option(),通过b.addOptions()加入生成模块,并作为编译期常量通过@import("build_options")访问:
该模式对参数化工作区至关重要。使用b.option(bool, "feature-x", "Enable feature X")声明选项,然后调用options.addOption("feature_x", feature_x)使其在编译期可用。选项变化时生成模块将自动重建,确保二进制始终反映当前配置。此技术适用于版本字符串、特性开关、调试设置以及代码所需的任何构建期常量。
目标矩阵与发布通道
复杂项目通常会交付多套二进制:为贡献者提供调试工具、面向生产的 ReleaseFast 构建,以及用于自动化的 WASI 制品。与其为每个目标复制构建逻辑,不如组装一个遍历std.Target.Query定义的矩阵。
理解目标解析
在遍历目标之前,理解b.resolveTargetQuery如何将部分规格转换为完整解析的目标非常重要。下图展示了解析过程:
当你传入的Target.Query在 CPU 或 OS 字段为null时,解析器会检测你的本机平台并填充具体值。同样地,如果你指定了 OS 但未给出 ABI,解析器会为该 OS 应用默认 ABI(例如 Linux 的.gnu、Windows 的.msvc)。解析每个查询仅发生一次,并产生一个ResolvedTarget,其中包含完整指定的Target,以及值是否来自本机检测的元数据。理解这一点对跨编译至关重要:由于 CPU 型号与特性检测不同,.cpu_arch = .x86_64且.os_tag = .linux的查询在每个宿主平台上得到的解析目标可能不同。
const std = @import("std");
/// 表示构建矩阵中目标/优化组合的结构
/// 每个组合定义具有描述性名称的唯一构建配置
const Combo = struct {
// 构建配置的人类可读标识符
name: []const u8,
// 指定CPU架构、操作系统和ABI的目标查询
query: std.Target.Query,
// 优化级别(Debug、ReleaseSafe、ReleaseFast或ReleaseSmall)
optimize: std.builtin.OptimizeMode,
};
pub fn build(b: *std.Build) void {
// 定义要构建的目标/优化组合矩阵
// 这演示了交叉编译能力和优化策略
const combos = [_]Combo{
// 用于开发的带调试符号的原生构建
.{ .name = "native-debug", .query = .{}, .optimize = .Debug },
// 针对最大性能优化的Linux x86_64构建
.{ .name = "linux-fast", .query = .{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .gnu }, .optimize = .ReleaseFast },
// 针对最小二进制大小优化的WebAssembly构建
.{ .name = "wasi-small", .query = .{ .cpu_arch = .wasm32, .os_tag = .wasi }, .optimize = .ReleaseSmall },
};
// 创建构建所有目标/优化组合的顶级步骤
// 用户可以使用`zig build matrix`调用
const matrix_step = b.step("matrix", "Build every target/optimize pair");
// 跟踪第一个(主机)可执行文件的运行步骤以创建健全性检查
var host_run_step: ?*std.Build.Step = null;
// 迭代每个组合以创建和配置构建工件
for (combos, 0..) |combo, index| {
// 将目标查询解析为具体的目标规范
// 这验证查询并用默认值填充任何未指定的字段
const resolved = b.resolveTargetQuery(combo.query);
// 使用解析的目标和优化设置创建模块
// 使用createModule允许对编译参数进行精确控制
const module = b.createModule(.{
.root_source_file = b.path("matrix/app.zig"),
.target = resolved,
.optimize = combo.optimize,
});
// 为此组合创建具有唯一名称的可执行文件工件
// 名称包括组合标识符以区分构建输出
const exe = b.addExecutable(.{
.name = b.fmt("matrix-{s}", .{combo.name}),
.root_module = module,
});
// 将可执行文件安装到zig-out/bin以便分发
b.installArtifact(exe);
// 将此可执行文件的构建步骤添加为矩阵步骤的依赖项
// 这确保在运行`zig build matrix`时构建所有可执行文件
matrix_step.dependOn(&exe.step);
// 对于第一个组合(假定为主机/宿主目标),
// 为快速测试和验证创建运行步骤
if (index == 0) {
// 创建运行主机可执行文件的命令
const run_cmd = b.addRunArtifact(exe);
// 将任何命令行参数转发到可执行文件
if (b.args) |args| {
run_cmd.addArgs(args);
}
// 创建用于运行主机变体的专用步骤
const run_step = b.step("run-host", "Run host variant for sanity checks");
run_step.dependOn(&run_cmd.step);
// 存储运行步骤以供矩阵步骤后续使用
host_run_step = run_step;
}
}
// 如果创建了主机运行步骤,将其添加为矩阵步骤的依赖项
// 这确保构建矩阵也在主机可执行文件上运行健全性检查
if (host_run_step) |run_step| {
matrix_step.dependOn(run_step);
}
}
关键技巧:
- 预先声明一组
{ name, query, optimize }组合。查询与zig build -Dtarget语义一致,同时保持类型检查。 b.resolveTargetQuery将每个查询转换为ResolvedTarget,使模块继承规范的 CPU/OS 默认值。- 将所有内容聚合到一个
matrix步骤下可保持 CI 接线简洁:调用zig build -Drelease-mode=fast matrix(或保留默认),并让依赖确保制品存在。 - 作为矩阵的一部分运行第一个(宿主)目标,无需跨运行器仿真即可捕获回归。若需更深覆盖,在调用
addRunArtifact之前启用b.enable_qemu/b.enable_wasmtime。
$ zig build --build-file 02_multi_target_matrix.zig matrixtarget: x86_64-linux-gnu optimize: Debug
运行跨编译目标
当矩阵包含跨平台编译的目标时,实际运行二进制需要外部执行器。构建系统会根据宿主/目标兼容性自动选择合适的执行器:
在调用addRunArtifact之前,通过设置b.enable_qemu = true或b.enable_wasmtime = true在构建脚本中启用仿真器。在 macOS ARM 宿主上,x86_64 目标会自动使用 Rosetta 2。对于 Linux 的跨架构测试,当操作系统匹配时,QEMU 用户态仿真可透明运行 ARM/RISC-V/MIPS 二进制。WASI 目标需要 Wasmtime;而在 Linux 上运行 Windows 二进制可使用 Wine。若无可用执行器,运行步骤会以Executor.bad_os_or_cpu失败——请在代表性 CI 宿主上尽早通过矩阵覆盖测试进行检测。
依赖本机系统库(如 glibc)的跨目标需要合适的 sysroot 包。在将这些组合加入生产流水线之前,设置ZIG_LIBC或配置b.libc_file。
Vendor 与注册表依赖
- 注册表优先:保持
build.zig.zon哈希为权威,然后通过b.dependency()与module.addImport()注册每个依赖模块。24 - 本地引入优先:将源码置于
deps/<name>/并用b.addAnonymousModule或b.createModule进行连接。在module-graph.txt中记录溯源,让协作者了解哪些代码被本地钉住。 - 无论选择何种策略,都在 CI 中记录一条策略:若
zig out/workspace-graph/module-graph.txt出现意外变更则使步骤失败,或添加检查 vendored 目录 LICENSE 文件的 lint。
CI 场景与自动化钩子
步骤依赖的实践
理解构建步骤的组合方式可为 CI 流水线带来收益。下图展示了来自 Zig 编译器自身构建系统的真实步骤依赖图:
注意默认的安装步骤(zig build)依赖二进制安装、文档与库文件,但不依赖测试。与此同时,测试步骤依赖编译以及所有测试子步骤。此分离允许 CI 在并行作业中分别运行用于发布制品的zig build与用于验证的zig build test。借助内容寻址缓存,每个步骤仅在其依赖发生变化时执行。你可以在本地通过zig build --verbose或添加自定义步骤以转储依赖来检查该图。
自动化模式
- 制品校验:添加
zig build graph作业,将module-graph.txt与已编译二进制一并上传。消费者可在版本间 diff 命名空间。 - 矩阵扩展:通过构建选项(
-Dinclude-windows=true)参数化组合数组。使用b.option(bool, "include-windows", …)让 CI 在无需修改源码的情况下切换额外目标。 - 安全姿态:在矩阵运行前串接
zig build --fetch(第 24 章),以便离线的跨作业运行之前填充缓存。参见24。 - 可复现性:让 CI 连续运行两次
zig build install并断言两次运行间无文件变化。由于std.Build遵循内容哈希,除非输入改变,第二次调用应不做任何工作。
高级测试组织
对于综合性项目,将测试按类别组织并应用矩阵需要谨慎的步骤组合。下图展示了生产级的测试层次结构:
总控测试步骤聚合所有测试类别,使你可通过zig build test运行完整套件。各类别也可单独调用(zig build test-fmt、zig build test-modules)以加快迭代。注意仅模块测试接收矩阵配置——格式校验与 CLI 测试不因目标而异。使用b.option([]const u8, "test-filter", …)让 CI 运行子集,并根据测试类型选择性应用优化模式。该模式可扩展到数百个测试文件,同时通过并行与缓存保持可管理的构建时间。
注意与警示
b.addModule在当前构建图中全局注册一个名称;b.createModule则使模块保持私有。混淆它们会导致意外的导入或符号缺失。25- 具名写入文件遵循缓存。如需从零再生成,请删除
.zig-cache;否则该步骤可能让你误以为变更已生效,实则命中缓存。 - 遍历矩阵时,务必使用
zig build uninstall(或自定义Step.RemoveDir)清理陈旧二进制,以避免跨版本混淆。
底层原理:依赖跟踪
构建系统的缓存与增量行为依赖于编译器复杂的依赖跟踪基础设施。理解这一点有助于解释为何缓存构建如此之快,以及为何某些变更会触发超出预期的更大范围重建。
编译器以多种粒度跟踪依赖:源文件哈希(src_hash_deps)、导航值(nav_val_deps)、类型(nav_ty_deps)、驻留常量、ZON 文件、嵌入文件与命名空间成员关系。这些映射都指向共享的dep_entries数组,其中包含形成链表的DepEntry结构。每个条目参与两条链:一条连接依赖某个被依赖者的所有分析单元(失效时遍历),另一条连接某个分析单元的所有被依赖者(清理时遍历)。当你修改源文件时,编译器为其哈希,在src_hash_deps中查找依赖者,并仅将那些分析单元标记为过时。这种细粒度跟踪使得在一个文件中更改私有函数不会重建无关模块——依赖图精确刻画了谁真正依赖于谁。构建系统通过内容寻址利用此基础设施:步骤输出以其输入哈希进行缓存,输入未变更时复用缓存。
练习
- 扩展
01_workspace_build.zig,使graph步骤同时输出人类可读表格与 JSON 文档。提示:使用std.json输出调用graph_files.add("module-graph.json", …)。参见json.zig。 - 为
02_multi_target_matrix.zig添加-Dtarget-filter选项,将矩阵执行限制为逗号分隔的允许列表。使用std.mem.splitScalar解析其值。22 - 通过
b.dependency("logging", .{})引入一个注册表依赖,并使用module.addImport("logging", dep.module("logging"))向工作区暴露它。在module-graph.txt中记录新的命名空间。
注意事项、替代方案与边界情况
- 大型工作区可能超出默认安装目录限制。在添加制品前使用
b.setInstallPrefix或b.setLibDir将输出路由到每个目标的专属目录。 - 在 Windows 上,若期望生成 MSVC 兼容制品,
resolveTargetQuery需要abi = .msvc;默认的.gnuABI 产生 MinGW 二进制。 - 若向依赖提供匿名模块,需注意它们不会去重。当多个制品需要相同的 vendored 代码时,请复用同一个
b.createModule实例。
总结
- 当你显式注册每个模块并通过具名写入文件记录映射时,工作区可保持可预期。
resolveTargetQuery与迭代友好的组合,使你无需复制粘贴构建逻辑即可扩展到多目标。- CI 作业得益于
std.Build原语:步骤清晰表达依赖,运行制品作为健康检查的闸门,具名制品捕捉可复现的元数据。
结合第 22–25 章,你现已具备在包、目标与发布通道间扩展、并保持确定性的 Zig 构建图的工具。