Zig 构建系统
何时使用 Zig 构建系统?
基本的 `zig build-exe`、`zig build-lib`、`zig build-obj` 和 `zig test` 命令通常就足够了。然而,有时项目需要另一层抽象来管理从源代码构建的复杂性。
例如,可能适用于以下情况之一
- 命令行变得太长且难以操作,并且您希望有一个地方可以将其记录下来。
- 您想构建许多东西,或者构建过程包含许多步骤。
- 您想利用并发和缓存来减少构建时间。
- 您想为项目公开配置选项。
- 构建过程因目标系统和其他选项而异。
- 您依赖于其他项目。
- 您希望避免对 cmake、make、shell、msvc、python 等不必要的依赖,从而使更多贡献者能够访问该项目。
- 您想提供一个软件包供第三方使用。
- 您想提供一种标准化方式,让 IDE 等工具能够语义化地理解如何构建项目。
如果以上任何一条适用,该项目将受益于使用 Zig 构建系统。
入门
简单可执行文件
此构建脚本从包含公共 `main` 函数定义的 Zig 文件创建可执行文件。
const std = @import("std");
pub fn main() !void {
std.debug.print("Hello World!\n", .{});
}
const std = @import("std");
pub fn build(b: *std.Build) void {
const exe = b.addExecutable(.{
.name = "hello",
.root_source_file = b.path("hello.zig"),
.target = b.graph.host,
});
b.installArtifact(exe);
}
$ zig build --summary all Build Summary: 3/3 steps succeeded install success └─ install hello success └─ zig build-exe hello Debug native success 3s MaxRSS:209M
安装构建产物
Zig 构建系统,与大多数构建系统一样,基于将项目建模为步骤的有向无环图 (DAG),这些步骤可以独立并行运行。
默认情况下,图中的主要步骤是**安装**步骤,其目的是将构建产物复制到最终位置。安装步骤不依赖于任何东西,因此当运行 `zig build` 时不会发生任何事情。项目的构建脚本必须添加要安装的事物集,这正是上面 `installArtifact` 函数调用所做的。
输出
├── build.zig
├── hello.zig
├── .zig-cache
└── zig-out
└── bin
└── hello
此输出中有两个生成的目录:`.zig-cache` 和 `zig-out`。第一个包含的文件将使后续构建更快,但这些文件不应提交到源代码控制中,并且此目录可以随时完全删除,不会产生任何后果。
第二个,`zig-out`,是一个“安装前缀”。这映射到标准的文件系统层次结构概念。此目录不是由项目选择的,而是由 `zig build` 用户通过 `--prefix` 标志(简写为 `-p`)选择的。
您作为项目维护者,选择要放入此目录的内容,但用户选择在他们的系统中安装到哪里。构建脚本不能硬编码输出路径,因为这会破坏缓存、并发性和可组合性,并可能惹恼最终用户。
添加运行应用程序的便捷步骤
通常会添加一个**运行**步骤,以提供直接从构建命令运行主应用程序的方式。
const std = @import("std");
pub fn main() !void {
std.debug.print("Hello World!\n", .{});
}
const std = @import("std");
pub fn build(b: *std.Build) void {
const exe = b.addExecutable(.{
.name = "hello",
.root_source_file = b.path("hello.zig"),
.target = b.graph.host,
});
b.installArtifact(exe);
const run_exe = b.addRunArtifact(exe);
const run_step = b.step("run", "Run the application");
run_step.dependOn(&run_exe.step);
}
$ zig build run --summary all Hello World! Build Summary: 3/3 steps succeeded run success └─ run hello success 169us MaxRSS:1M └─ zig build-exe hello Debug native success 3s MaxRSS:210M
基础知识
用户提供的选项
使用 `b.option` 使构建脚本可配置,供最终用户以及将项目作为包依赖的其他项目使用。
const std = @import("std");
pub fn build(b: *std.Build) void {
const windows = b.option(bool, "windows", "Target Microsoft Windows") orelse false;
const exe = b.addExecutable(.{
.name = "hello",
.root_source_file = b.path("example.zig"),
.target = b.resolveTargetQuery(.{
.os_tag = if (windows) .windows else null,
}),
});
b.installArtifact(exe);
}
$ zig build --help Usage: /home/ci/deps/zig-linux-x86_64-0.14.0/zig build [steps] [options] Steps: install (default) Copy build artifacts to prefix path uninstall Remove build artifacts from prefix path General Options: -p, --prefix [path] Where to install files (default: zig-out) --prefix-lib-dir [path] Where to install libraries --prefix-exe-dir [path] Where to install executables --prefix-include-dir [path] Where to install C header files --release[=mode] Request release mode, optionally specifying a preferred optimization mode: fast, safe, small -fdarling, -fno-darling Integration with system-installed Darling to execute macOS programs on Linux hosts (default: no) -fqemu, -fno-qemu Integration with system-installed QEMU to execute foreign-architecture programs on Linux hosts (default: no) --glibc-runtimes [path] Enhances QEMU integration by providing glibc built for multiple foreign architectures, allowing execution of non-native programs that link with glibc. -frosetta, -fno-rosetta Rely on Rosetta to execute x86_64 programs on ARM64 macOS hosts. (default: no) -fwasmtime, -fno-wasmtime Integration with system-installed wasmtime to execute WASI binaries. (default: no) -fwine, -fno-wine Integration with system-installed Wine to execute Windows programs on Linux hosts. (default: no) -h, --help Print this help and exit -l, --list-steps Print available steps --verbose Print commands before executing them --color [auto|off|on] Enable or disable colored error messages --prominent-compile-errors Buffer compile errors and display at end --summary [mode] Control the printing of the build summary all Print the build summary in its entirety new Omit cached steps failures (Default) Only print failed steps none Do not print the build summary -j<N> Limit concurrent jobs (default is to use all CPU cores) --maxrss <bytes> Limit memory usage (default is to use available memory) --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss --fetch Exit after fetching dependency tree --watch Continuously rebuild when source files are modified --fuzz Continuously search for unit test failures --debounce <ms> Delay before rebuilding after changed file detected -fincremental Enable incremental compilation -fno-incremental Disable incremental compilation Project-Specific Options: -Dwindows=[bool] Target Microsoft Windows System Integration Options: --search-prefix [path] Add a path to look for binaries, libraries, headers --sysroot [path] Set the system root directory (usually /) --libc [file] Provide a file which specifies libc paths --system [pkgdir] Disable package fetching; enable all integrations -fsys=[name] Enable a system integration -fno-sys=[name] Disable a system integration Available System Integrations: Enabled: (none) - Advanced Options: -freference-trace[=num] How many lines of reference trace should be shown per compile error -fno-reference-trace Disable reference trace -fallow-so-scripts Allows .so files to be GNU ld scripts -fno-allow-so-scripts (default) .so files must be ELF files --build-file [file] Override path to build.zig --cache-dir [path] Override path to local Zig cache directory --global-cache-dir [path] Override path to global Zig cache directory --zig-lib-dir [arg] Override path to Zig lib directory --build-runner [file] Override path to build runner --seed [integer] For shuffling dependency traversal order (default: random) --debug-log [scope] Enable debugging the compiler --debug-pkg-config Fail if unknown pkg-config flags encountered --debug-rt Debug compiler runtime libraries --verbose-link Enable compiler debug output for linking --verbose-air Enable compiler debug output for Zig AIR --verbose-llvm-ir[=file] Enable compiler debug output for LLVM IR --verbose-llvm-bc=[file] Enable compiler debug output for LLVM BC --verbose-cimport Enable compiler debug output for C imports --verbose-cc Enable compiler debug output for C compilation --verbose-llvm-cpu-features Enable compiler debug output for LLVM CPU features
const std = @import("std");
pub fn main() !void {
std.debug.print("Hello World!\n", .{});
}
请注意这些行
Project-Specific Options:
-Dwindows=[bool] Target Microsoft Windows
帮助菜单的这一部分是根据运行 `build.zig` 逻辑自动生成的。用户可以通过这种方式发现构建脚本的配置选项。
标准配置选项
以前,我们使用布尔标志来指示是否为 Windows 构建。然而,我们可以做得更好。
大多数项目都希望提供更改目标和优化设置的能力。为了鼓励这些选项采用标准命名约定,Zig 提供了辅助函数 `standardTargetOptions` 和 `standardOptimizeOption`。
标准目标选项允许运行 `zig build` 的人选择要构建的目标。默认情况下,允许任何目标,并且不选择意味着以宿主系统为目标。还提供了其他限制受支持目标集的选项。
标准优化选项允许运行 `zig build` 的人选择 `Debug`、`ReleaseSafe`、`ReleaseFast` 和 `ReleaseSmall`。默认情况下,构建脚本不认为任何发布选项是首选,用户必须做出决定才能创建发布版本。
const std = @import("std");
pub fn main() !void {
std.debug.print("Hello World!\n", .{});
}
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "hello",
.root_source_file = b.path("hello.zig"),
.target = target,
.optimize = optimize,
});
b.installArtifact(exe);
}
$ zig build -Dtarget=x86_64-windows -Doptimize=ReleaseSmall --summary all Build Summary: 3/3 steps succeeded install success └─ install hello success └─ zig build-exe hello ReleaseSmall x86_64-windows success 279ms MaxRSS:110M
现在,我们的 `--help` 菜单包含更多项
Project-Specific Options:
-Dtarget=[string] The CPU architecture, OS, and ABI to build for
-Dcpu=[string] Target CPU features to add or subtract
-Doptimize=[enum] Prioritize performance, safety, or binary size (-O flag)
Supported Values:
Debug
ReleaseSafe
ReleaseFast
ReleaseSmall
完全可以直接通过 `b.option` 创建这些选项,但此 API 为这些常用设置提供了通用的命名约定。
在我们的终端输出中,请注意我们传递了 `-Dtarget=x86_64-windows -Doptimize=ReleaseSmall`。与第一个示例相比,现在我们在安装前缀中看到了不同的文件
zig-out/
└── bin
└── hello.exe
条件编译选项
要将选项从构建脚本传递到项目的 Zig 代码中,请使用 `Options` 步骤。
const std = @import("std");
const config = @import("config");
const semver = std.SemanticVersion.parse(config.version) catch unreachable;
extern fn foo_bar() void;
pub fn main() !void {
if (semver.major < 1) {
@compileError("too old");
}
std.debug.print("version: {s}\n", .{config.version});
if (config.have_libfoo) {
foo_bar();
}
}
const std = @import("std");
pub fn build(b: *std.Build) void {
const exe = b.addExecutable(.{
.name = "app",
.root_source_file = b.path("app.zig"),
.target = b.graph.host,
});
const version = b.option([]const u8, "version", "application version string") orelse "0.0.0";
const enable_foo = detectWhetherToEnableLibFoo();
const options = b.addOptions();
options.addOption([]const u8, "version", version);
options.addOption(bool, "have_libfoo", enable_foo);
exe.root_module.addOptions("config", options);
b.installArtifact(exe);
}
fn detectWhetherToEnableLibFoo() bool {
return false;
}
$ zig build -Dversion=1.2.3 --summary all Build Summary: 4/4 steps succeeded install success └─ install app success └─ zig build-exe app Debug native success 3s MaxRSS:211M └─ options success
在此示例中,`@import("config")` 提供的数据在编译时已知,从而阻止了 `@compileError` 的触发。如果我们传递了 `-Dversion=0.2.3` 或省略了该选项,那么我们将看到 `app.zig` 的编译因“太旧”错误而失败。
静态库
此构建脚本从 Zig 代码创建一个静态库,然后还从使用该库的其他 Zig 代码创建一个可执行文件。
export fn fizzbuzz(n: usize) ?[*:0]const u8 {
if (n % 5 == 0) {
if (n % 3 == 0) {
return "fizzbuzz";
} else {
return "fizz";
}
} else if (n % 3 == 0) {
return "buzz";
}
return null;
}
const std = @import("std");
extern fn fizzbuzz(n: usize) ?[*:0]const u8;
pub fn main() !void {
const stdout = std.io.getStdOut();
var bw = std.io.bufferedWriter(stdout.writer());
const w = bw.writer();
for (0..100) |n| {
if (fizzbuzz(n)) |s| {
try w.print("{s}\n", .{s});
} else {
try w.print("{d}\n", .{n});
}
}
try bw.flush();
}
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const libfizzbuzz = b.addStaticLibrary(.{
.name = "fizzbuzz",
.root_source_file = b.path("fizzbuzz.zig"),
.target = target,
.optimize = optimize,
});
const exe = b.addExecutable(.{
.name = "demo",
.root_source_file = b.path("demo.zig"),
.target = target,
.optimize = optimize,
});
exe.linkLibrary(libfizzbuzz);
b.installArtifact(libfizzbuzz);
if (b.option(bool, "enable-demo", "install the demo too") orelse false) {
b.installArtifact(exe);
}
}
$ zig build --summary all Build Summary: 3/3 steps succeeded install success └─ install fizzbuzz success └─ zig build-lib fizzbuzz Debug native success 138ms MaxRSS:101M
在这种情况下,只有静态库最终被安装
zig-out/
└── lib
└── libfizzbuzz.a
然而,如果您仔细查看,构建脚本包含一个选项,也可以安装演示文件。如果我们额外传递 `-Denable-demo`,那么我们将在安装前缀中看到此内容
zig-out/
├── bin
│ └── demo
└── lib
└── libfizzbuzz.a
请注意,尽管无条件调用了 `addExecutable`,但构建系统实际上并不会浪费时间构建 `demo` 可执行文件,除非通过 `-Denable-demo` 请求它,因为构建系统是基于带有依赖边的有向无环图。
动态库
这里我们保留与 静态库 示例中相同的所有文件,除了 `build.zig` 文件被更改。
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const libfizzbuzz = b.addSharedLibrary(.{
.name = "fizzbuzz",
.root_source_file = b.path("fizzbuzz.zig"),
.target = target,
.optimize = optimize,
.version = .{ .major = 1, .minor = 2, .patch = 3 },
});
b.installArtifact(libfizzbuzz);
}
$ zig build --summary all Build Summary: 3/3 steps succeeded install success └─ install fizzbuzz success └─ zig build-lib fizzbuzz Debug native success 2s MaxRSS:208M
输出
zig-out
└── lib
├── libfizzbuzz.so -> libfizzbuzz.so.1
├── libfizzbuzz.so.1 -> libfizzbuzz.so.1.2.3
└── libfizzbuzz.so.1.2.3
如静态库示例所示,要使可执行文件链接到它,请使用如下代码
exe.linkLibrary(libfizzbuzz);
测试
可以使用 `zig test foo.zig` 直接测试单个文件,然而,更复杂的用例可以通过构建脚本协调测试来解决。
使用构建脚本时,单元测试在构建图中分为两个不同的步骤:**编译**步骤和**运行**步骤。如果没有调用 `addRunArtifact`(它在这两个步骤之间建立依赖边),单元测试将不会执行。
*编译*步骤的配置方式与任何可执行文件、库或目标文件相同,例如通过链接到系统库、设置目标选项或添加额外的编译单元。
*运行*步骤的配置方式与任何运行步骤相同,例如,当宿主无法执行二进制文件时跳过执行。
当使用构建系统运行单元测试时,构建运行器和测试运行器通过 *stdin* 和 *stdout* 进行通信,以便并发运行多个单元测试套件,并以有意义的方式报告测试失败,而不会使它们的输出混杂在一起。这是为什么在单元测试中写入 *标准输出* 会有问题的原因之一——它会干扰此通信通道。另一方面,这种机制将启用一个即将推出的功能,即单元测试预期 *panic* 的能力。
const std = @import("std");
test "simple test" {
var list = std.ArrayList(i32).init(std.testing.allocator);
defer list.deinit();
try list.append(42);
try std.testing.expectEqual(@as(i32, 42), list.pop());
}
const std = @import("std");
const test_targets = [_]std.Target.Query{
.{}, // native
.{
.cpu_arch = .x86_64,
.os_tag = .linux,
},
.{
.cpu_arch = .aarch64,
.os_tag = .macos,
},
};
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Run unit tests");
for (test_targets) |target| {
const unit_tests = b.addTest(.{
.root_source_file = b.path("main.zig"),
.target = b.resolveTargetQuery(target),
});
const run_unit_tests = b.addRunArtifact(unit_tests);
test_step.dependOn(&run_unit_tests.step);
}
}
$ zig build test --summary all test └─ run test failure error: the host system (x86_64-linux.5.10...5.10-gnu.2.31) is unable to execute binaries from the target (aarch64-macos.13.0...15.3.1-none) Build Summary: 5/7 steps succeeded; 1 failed; 2/2 tests passed test transitive failure ├─ run test 1 passed 512us MaxRSS:1M │ └─ zig test Debug native success 4s MaxRSS:237M ├─ run test 1 passed 483us MaxRSS:1M │ └─ zig test Debug x86_64-linux success 4s MaxRSS:241M └─ run test failure └─ zig test Debug aarch64-macos success 4s MaxRSS:243M error: the following build command failed with exit code 1: /home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/zig-code/build-system/unit-testing/.zig-cache/o/c4c795eeda6baaaf1cc7f8d3924aa4ca/build /home/ci/deps/zig-linux-x86_64-0.14.0/zig /home/ci/deps/zig-linux-x86_64-0.14.0/lib /home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/zig-code/build-system/unit-testing /home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/zig-code/build-system/unit-testing/.zig-cache /home/ci/.cache/zig --seed 0xaf90ffc5 -Z89eb9bd8737112d8 --color on test --summary all
在这种情况下,为单元测试启用 `skip_foreign_checks` 可能是一个不错的调整。
@@ -23,6 +23,7 @@
});
const run_unit_tests = b.addRunArtifact(unit_tests);
+ run_unit_tests.skip_foreign_checks = true;
test_step.dependOn(&run_unit_tests.step);
}
}
const std = @import("std");
const test_targets = [_]std.Target.Query{
.{}, // native
.{
.cpu_arch = .x86_64,
.os_tag = .linux,
},
.{
.cpu_arch = .aarch64,
.os_tag = .macos,
},
};
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Run unit tests");
for (test_targets) |target| {
const unit_tests = b.addTest(.{
.root_source_file = b.path("main.zig"),
.target = b.resolveTargetQuery(target),
});
const run_unit_tests = b.addRunArtifact(unit_tests);
run_unit_tests.skip_foreign_checks = true;
test_step.dependOn(&run_unit_tests.step);
}
}
// zig-doctest: build-system --collapseable -- test --summary all
$ zig build --summary all Build Summary: 1/1 steps succeeded install cached
链接到系统库
为了满足库依赖,有两种选择
对于上游项目维护者的用例,通过 Zig 构建系统获取这些库提供了最小的摩擦,并将配置能力掌握在这些维护者手中。以这种方式构建的每个人都将获得彼此可重现、一致的结果,并且它将在每个操作系统上工作,甚至支持交叉编译。此外,它允许项目精确决定其整个依赖树中希望构建所针对的确切版本。这有望成为依赖外部库的普遍首选方式。
然而,对于将软件打包到 Debian、Homebrew 或 Nix 等仓库的用例,强制要求链接到系统库。因此,构建脚本必须检测构建模式并进行相应配置。
const std = @import("std");
pub fn build(b: *std.Build) void {
const exe = b.addExecutable(.{
.name = "zip",
.root_source_file = b.path("zip.zig"),
.target = b.graph.host,
});
exe.linkSystemLibrary("z");
exe.linkLibC();
b.installArtifact(exe);
}
$ zig build --summary all Build Summary: 3/3 steps succeeded install success └─ install zip success └─ zig build-exe zip Debug native success 4s MaxRSS:216M
`zig build` 的用户可以使用 `--search-prefix` 提供额外的目录,这些目录被视为“系统目录”,用于查找静态和动态库。
生成文件
运行系统工具
这个版本的 hello world 期望在同一路径中找到 `word.txt` 文件,我们想使用系统工具从 JSON 文件生成它。
请注意,系统依赖将使您的项目更难为用户构建。例如,此构建脚本依赖于 `jq`,它在大多数 Linux 发行版中默认不存在,并且对于 Windows 用户来说可能是一个不熟悉的工具。
下一节将把 `jq` 替换为源代码树中包含的 Zig 工具,这是首选方法。
words.json
{
"en": "world",
"it": "mondo",
"ja": "世界"
}
const std = @import("std");
pub fn main() !void {
var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena_state.deinit();
const arena = arena_state.allocator();
const self_exe_dir_path = try std.fs.selfExeDirPathAlloc(arena);
var self_exe_dir = try std.fs.cwd().openDir(self_exe_dir_path, .{});
defer self_exe_dir.close();
const word = try self_exe_dir.readFileAlloc(arena, "word.txt", 1000);
try std.io.getStdOut().writer().print("Hello {s}\n", .{word});
}
const std = @import("std");
pub fn build(b: *std.Build) void {
const lang = b.option([]const u8, "language", "language of the greeting") orelse "en";
const tool_run = b.addSystemCommand(&.{"jq"});
tool_run.addArgs(&.{
b.fmt(
\\.["{s}"]
, .{lang}),
"-r", // raw output to omit quotes around the selected string
});
tool_run.addFileArg(b.path("words.json"));
const output = tool_run.captureStdOut();
b.getInstallStep().dependOn(&b.addInstallFileWithDir(output, .prefix, "word.txt").step);
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "hello",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
const install_artifact = b.addInstallArtifact(exe, .{
.dest_dir = .{ .override = .prefix },
});
b.getInstallStep().dependOn(&install_artifact.step);
}
$ zig build -Dlanguage=ja --summary all Build Summary: 5/5 steps succeeded install success ├─ install generated to word.txt success │ └─ run jq success 85ms MaxRSS:3M └─ install hello success └─ zig build-exe hello Debug native success 3s MaxRSS:211M
输出
zig-out
├── hello
└── word.txt
请注意 `captureStdOut` 如何创建一个临时文件,其中包含 `jq` 调用的输出。
运行项目工具
这个版本的 hello world 期望在同一路径中找到 `word.txt` 文件,我们希望在构建时通过对 JSON 文件调用 Zig 程序来生成它。
tools/words.json
{
"en": "world",
"it": "mondo",
"ja": "世界"
}
const std = @import("std");
pub fn main() !void {
var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena_state.deinit();
const arena = arena_state.allocator();
const self_exe_dir_path = try std.fs.selfExeDirPathAlloc(arena);
var self_exe_dir = try std.fs.cwd().openDir(self_exe_dir_path, .{});
defer self_exe_dir.close();
const word = try self_exe_dir.readFileAlloc(arena, "word.txt", 1000);
try std.io.getStdOut().writer().print("Hello {s}\n", .{word});
}
const std = @import("std");
const usage =
\\Usage: ./word_select [options]
\\
\\Options:
\\ --input-file INPUT_JSON_FILE
\\ --output-file OUTPUT_TXT_FILE
\\ --lang LANG
\\
;
pub fn main() !void {
var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena_state.deinit();
const arena = arena_state.allocator();
const args = try std.process.argsAlloc(arena);
var opt_input_file_path: ?[]const u8 = null;
var opt_output_file_path: ?[]const u8 = null;
var opt_lang: ?[]const u8 = null;
{
var i: usize = 1;
while (i < args.len) : (i += 1) {
const arg = args[i];
if (std.mem.eql(u8, "-h", arg) or std.mem.eql(u8, "--help", arg)) {
try std.io.getStdOut().writeAll(usage);
return std.process.cleanExit();
} else if (std.mem.eql(u8, "--input-file", arg)) {
i += 1;
if (i > args.len) fatal("expected arg after '{s}'", .{arg});
if (opt_input_file_path != null) fatal("duplicated {s} argument", .{arg});
opt_input_file_path = args[i];
} else if (std.mem.eql(u8, "--output-file", arg)) {
i += 1;
if (i > args.len) fatal("expected arg after '{s}'", .{arg});
if (opt_output_file_path != null) fatal("duplicated {s} argument", .{arg});
opt_output_file_path = args[i];
} else if (std.mem.eql(u8, "--lang", arg)) {
i += 1;
if (i > args.len) fatal("expected arg after '{s}'", .{arg});
if (opt_lang != null) fatal("duplicated {s} argument", .{arg});
opt_lang = args[i];
} else {
fatal("unrecognized arg: '{s}'", .{arg});
}
}
}
const input_file_path = opt_input_file_path orelse fatal("missing --input-file", .{});
const output_file_path = opt_output_file_path orelse fatal("missing --output-file", .{});
const lang = opt_lang orelse fatal("missing --lang", .{});
var input_file = std.fs.cwd().openFile(input_file_path, .{}) catch |err| {
fatal("unable to open '{s}': {s}", .{ input_file_path, @errorName(err) });
};
defer input_file.close();
var output_file = std.fs.cwd().createFile(output_file_path, .{}) catch |err| {
fatal("unable to open '{s}': {s}", .{ output_file_path, @errorName(err) });
};
defer output_file.close();
var json_reader = std.json.reader(arena, input_file.reader());
var words = try std.json.ArrayHashMap([]const u8).jsonParse(arena, &json_reader, .{
.allocate = .alloc_if_needed,
.max_value_len = 1000,
});
const w = words.map.get(lang) orelse fatal("Lang not found in JSON file", .{});
try output_file.writeAll(w);
return std.process.cleanExit();
}
fn fatal(comptime format: []const u8, args: anytype) noreturn {
std.debug.print(format, args);
std.process.exit(1);
}
const std = @import("std");
pub fn build(b: *std.Build) void {
const lang = b.option([]const u8, "language", "language of the greeting") orelse "en";
const tool = b.addExecutable(.{
.name = "word_select",
.root_source_file = b.path("tools/word_select.zig"),
.target = b.graph.host,
});
const tool_step = b.addRunArtifact(tool);
tool_step.addArg("--input-file");
tool_step.addFileArg(b.path("tools/words.json"));
tool_step.addArg("--output-file");
const output = tool_step.addOutputFileArg("word.txt");
tool_step.addArgs(&.{ "--lang", lang });
b.getInstallStep().dependOn(&b.addInstallFileWithDir(output, .prefix, "word.txt").step);
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "hello",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
const install_artifact = b.addInstallArtifact(exe, .{
.dest_dir = .{ .override = .prefix },
});
b.getInstallStep().dependOn(&install_artifact.step);
}
$ zig build --summary all Build Summary: 6/6 steps succeeded install success ├─ install generated to word.txt success │ └─ run word_select (word.txt) success 34us MaxRSS:1M │ └─ zig build-exe word_select Debug native success 4s MaxRSS:226M └─ install hello success └─ zig build-exe hello Debug native success 3s MaxRSS:211M
输出
zig-out
├── hello
└── word.txt
为 `@embedFile` 生成资产
这个版本的 hello world 希望 `@embedFile` 一个在构建时生成的资产,我们将使用用 Zig 编写的工具来生成它。
tools/words.json
{
"en": "world",
"it": "mondo",
"ja": "世界"
}
const std = @import("std");
const word = @embedFile("word");
pub fn main() !void {
try std.io.getStdOut().writer().print("Hello {s}\n", .{word});
}
const std = @import("std");
const usage =
\\Usage: ./word_select [options]
\\
\\Options:
\\ --input-file INPUT_JSON_FILE
\\ --output-file OUTPUT_TXT_FILE
\\ --lang LANG
\\
;
pub fn main() !void {
var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena_state.deinit();
const arena = arena_state.allocator();
const args = try std.process.argsAlloc(arena);
var opt_input_file_path: ?[]const u8 = null;
var opt_output_file_path: ?[]const u8 = null;
var opt_lang: ?[]const u8 = null;
{
var i: usize = 1;
while (i < args.len) : (i += 1) {
const arg = args[i];
if (std.mem.eql(u8, "-h", arg) or std.mem.eql(u8, "--help", arg)) {
try std.io.getStdOut().writeAll(usage);
return std.process.cleanExit();
} else if (std.mem.eql(u8, "--input-file", arg)) {
i += 1;
if (i > args.len) fatal("expected arg after '{s}'", .{arg});
if (opt_input_file_path != null) fatal("duplicated {s} argument", .{arg});
opt_input_file_path = args[i];
} else if (std.mem.eql(u8, "--output-file", arg)) {
i += 1;
if (i > args.len) fatal("expected arg after '{s}'", .{arg});
if (opt_output_file_path != null) fatal("duplicated {s} argument", .{arg});
opt_output_file_path = args[i];
} else if (std.mem.eql(u8, "--lang", arg)) {
i += 1;
if (i > args.len) fatal("expected arg after '{s}'", .{arg});
if (opt_lang != null) fatal("duplicated {s} argument", .{arg});
opt_lang = args[i];
} else {
fatal("unrecognized arg: '{s}'", .{arg});
}
}
}
const input_file_path = opt_input_file_path orelse fatal("missing --input-file", .{});
const output_file_path = opt_output_file_path orelse fatal("missing --output-file", .{});
const lang = opt_lang orelse fatal("missing --lang", .{});
var input_file = std.fs.cwd().openFile(input_file_path, .{}) catch |err| {
fatal("unable to open '{s}': {s}", .{ input_file_path, @errorName(err) });
};
defer input_file.close();
var output_file = std.fs.cwd().createFile(output_file_path, .{}) catch |err| {
fatal("unable to open '{s}': {s}", .{ output_file_path, @errorName(err) });
};
defer output_file.close();
var json_reader = std.json.reader(arena, input_file.reader());
var words = try std.json.ArrayHashMap([]const u8).jsonParse(arena, &json_reader, .{
.allocate = .alloc_if_needed,
.max_value_len = 1000,
});
const w = words.map.get(lang) orelse fatal("Lang not found in JSON file", .{});
try output_file.writeAll(w);
return std.process.cleanExit();
}
fn fatal(comptime format: []const u8, args: anytype) noreturn {
std.debug.print(format, args);
std.process.exit(1);
}
const std = @import("std");
pub fn build(b: *std.Build) void {
const lang = b.option([]const u8, "language", "language of the greeting") orelse "en";
const tool = b.addExecutable(.{
.name = "word_select",
.root_source_file = b.path("tools/word_select.zig"),
.target = b.graph.host,
});
const tool_step = b.addRunArtifact(tool);
tool_step.addArg("--input-file");
tool_step.addFileArg(b.path("tools/words.json"));
tool_step.addArg("--output-file");
const output = tool_step.addOutputFileArg("word.txt");
tool_step.addArgs(&.{ "--lang", lang });
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "hello",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
exe.root_module.addAnonymousImport("word", .{
.root_source_file = output,
});
b.installArtifact(exe);
}
$ zig build --summary all Build Summary: 5/5 steps succeeded install success └─ install hello success └─ zig build-exe hello Debug native success 2s MaxRSS:211M └─ run word_select (word.txt) success 39us MaxRSS:1M └─ zig build-exe word_select Debug native success 4s MaxRSS:227M
输出
zig-out/
└── bin
└── hello
生成 Zig 源代码
此构建文件使用 Zig 程序生成一个 Zig 文件,然后将其作为模块依赖项公开给主程序。
const std = @import("std");
const Person = @import("person").Person;
pub fn main() !void {
const p: Person = .{};
try std.io.getStdOut().writer().print("Hello {any}\n", .{p});
}
const std = @import("std");
pub fn main() !void {
var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena_state.deinit();
const arena = arena_state.allocator();
const args = try std.process.argsAlloc(arena);
if (args.len != 2) fatal("wrong number of arguments", .{});
const output_file_path = args[1];
var output_file = std.fs.cwd().createFile(output_file_path, .{}) catch |err| {
fatal("unable to open '{s}': {s}", .{ output_file_path, @errorName(err) });
};
defer output_file.close();
try output_file.writeAll(
\\pub const Person = struct {
\\ age: usize = 18,
\\ name: []const u8 = "foo"
\\};
);
return std.process.cleanExit();
}
fn fatal(comptime format: []const u8, args: anytype) noreturn {
std.debug.print(format, args);
std.process.exit(1);
}
const std = @import("std");
pub fn build(b: *std.Build) void {
const tool = b.addExecutable(.{
.name = "generate_struct",
.root_source_file = b.path("tools/generate_struct.zig"),
.target = b.graph.host,
});
const tool_step = b.addRunArtifact(tool);
const output = tool_step.addOutputFileArg("person.zig");
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "hello",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
exe.root_module.addAnonymousImport("person", .{
.root_source_file = output,
});
b.installArtifact(exe);
}
$ zig build --summary all Build Summary: 5/5 steps succeeded install success └─ install hello success └─ zig build-exe hello Debug native success 3s MaxRSS:212M └─ run generate_struct (person.zig) success 43us MaxRSS:1M └─ zig build-exe generate_struct Debug native success 3s MaxRSS:211M
输出
zig-out/
└── bin
└── hello
处理一个或多个生成文件
**WriteFiles** 步骤提供了一种生成一个或多个共享父目录文件的方式。生成的目录位于本地 `.zig-cache` 内部,每个生成的文件都可作为独立的 `std.Build.LazyPath`。父目录本身也可作为 `LazyPath`。
此 API 支持将任意字符串写入生成的目录,以及将文件复制到其中。
const std = @import("std");
pub fn main() !void {
std.debug.print("hello world\n", .{});
}
const std = @import("std");
pub fn build(b: *std.Build) void {
const exe = b.addExecutable(.{
.name = "app",
.root_source_file = b.path("src/main.zig"),
.target = b.graph.host,
});
const version = b.option([]const u8, "version", "application version string") orelse "0.0.0";
const wf = b.addWriteFiles();
const app_exe_name = b.fmt("project/{s}", .{exe.out_filename});
_ = wf.addCopyFile(exe.getEmittedBin(), app_exe_name);
_ = wf.add("project/version.txt", version);
const tar = b.addSystemCommand(&.{ "tar", "czf" });
tar.setCwd(wf.getDirectory());
const out_file = tar.addOutputFileArg("project.tar.gz");
tar.addArgs(&.{"project/"});
const install_tar = b.addInstallFileWithDir(out_file, .prefix, "project.tar.gz");
b.getInstallStep().dependOn(&install_tar.step);
}
$ zig build --summary all Build Summary: 5/5 steps succeeded install success └─ install generated to project.tar.gz success └─ run tar (project.tar.gz) success 155ms MaxRSS:3M └─ WriteFile project/app success └─ zig build-exe app Debug native success 2s MaxRSS:210M
输出
zig-out/
└── project.tar.gz
就地修改源文件
项目将生成的文件提交到版本控制中并不常见,但有时会发生。当生成的文件很少更新,并且更新过程具有繁重的系统依赖性时,这可能很有用,但*仅限*在更新过程中。
为此,**WriteFiles** 提供了一种完成此任务的方法。这是未来 Zig 版本中将从 WriteFiles 提取到其自己的构建步骤的功能。
请谨慎使用此功能;它不应在正常构建过程中使用,而应作为开发人员有意更新源文件时运行的实用工具,然后这些源文件将被提交到版本控制。如果在正常构建过程中执行此操作,将导致缓存和并发错误。
const std = @import("std");
pub fn main() !void {
var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena_state.deinit();
const arena = arena_state.allocator();
const args = try std.process.argsAlloc(arena);
if (args.len != 2) fatal("wrong number of arguments", .{});
const output_file_path = args[1];
var output_file = std.fs.cwd().createFile(output_file_path, .{}) catch |err| {
fatal("unable to open '{s}': {s}", .{ output_file_path, @errorName(err) });
};
defer output_file.close();
try output_file.writeAll(
\\pub const Header = extern struct {
\\ magic: u64,
\\ width: u32,
\\ height: u32,
\\};
);
return std.process.cleanExit();
}
fn fatal(comptime format: []const u8, args: anytype) noreturn {
std.debug.print(format, args);
std.process.exit(1);
}
const std = @import("std");
const Protocol = @import("protocol.zig");
pub fn main() !void {
const header = try std.io.getStdIn().reader().readStruct(Protocol.Header);
std.debug.print("header: {any}\n", .{header});
}
pub const Header = extern struct {
magic: u64,
width: u32,
height: u32,
};
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const exe = b.addExecutable(.{
.name = "demo",
.root_source_file = b.path("src/main.zig"),
.target = target,
});
b.installArtifact(exe);
const proto_gen = b.addExecutable(.{
.name = "proto_gen",
.root_source_file = b.path("tools/proto_gen.zig"),
.target = target,
});
const run = b.addRunArtifact(proto_gen);
const generated_protocol_file = run.addOutputFileArg("protocol.zig");
const wf = b.addUpdateSourceFiles();
wf.addCopyFileToSource(generated_protocol_file, "src/protocol.zig");
const update_protocol_step = b.step("update-protocol", "update src/protocol.zig to latest");
update_protocol_step.dependOn(&wf.step);
}
fn detectWhetherToEnableLibFoo() bool {
return false;
}
$ zig build update-protocol --summary all
Build Summary: 4/4 steps succeeded
update-protocol success
└─ WriteFile success
└─ run proto_gen (protocol.zig) success 401us MaxRSS:1M
└─ zig build-exe proto_gen Debug native success 1s MaxRSS:183M
运行此命令后,`src/protocol.zig` 会就地更新。
实用示例
为多个目标构建以发布
在此示例中,我们将在创建 `InstallArtifact` 步骤时更改一些默认设置,以便将每个目标的构建产物放入安装路径内的一个单独子目录中。
const std = @import("std");
const targets: []const std.Target.Query = &.{
.{ .cpu_arch = .aarch64, .os_tag = .macos },
.{ .cpu_arch = .aarch64, .os_tag = .linux },
.{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .gnu },
.{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .musl },
.{ .cpu_arch = .x86_64, .os_tag = .windows },
};
pub fn build(b: *std.Build) !void {
for (targets) |t| {
const exe = b.addExecutable(.{
.name = "hello",
.root_source_file = b.path("hello.zig"),
.target = b.resolveTargetQuery(t),
.optimize = .ReleaseSafe,
});
const target_output = b.addInstallArtifact(exe, .{
.dest_dir = .{
.override = .{
.custom = try t.zigTriple(b.allocator),
},
},
});
b.getInstallStep().dependOn(&target_output.step);
}
}
$ zig build --summary all Build Summary: 11/11 steps succeeded install success ├─ install hello success │ └─ zig build-exe hello ReleaseSafe aarch64-macos success 21s MaxRSS:241M ├─ install hello success │ └─ zig build-exe hello ReleaseSafe aarch64-linux success 19s MaxRSS:244M ├─ install hello success │ └─ zig build-exe hello ReleaseSafe x86_64-linux-gnu success 19s MaxRSS:224M ├─ install hello success │ └─ zig build-exe hello ReleaseSafe x86_64-linux-musl success 19s MaxRSS:223M └─ install hello success └─ zig build-exe hello ReleaseSafe x86_64-windows success 19s MaxRSS:239M
const std = @import("std");
pub fn main() !void {
std.debug.print("Hello World!\n", .{});
}
输出
zig-out
├── aarch64-linux
│ └── hello
├── aarch64-macos
│ └── hello
├── x86_64-linux-gnu
│ └── hello
├── x86_64-linux-musl
│ └── hello
└── x86_64-windows
├── hello.exe
└── hello.pdb