authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-05-26 18:00:16+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-05-26 18:00:16+02:00
log0ff175b69ef806f421820d33dade7a8163fe3f16
tree3ca6325f7c0de9632dc94fee68b868e9c31d0c31
parentc84f0f49d692e08c235ff939d4322fe723fe2823
parentc9619d7086a4aa61b1bd69faff4ce58a7f9c811c

Merge pull request 'zig build: separate the maker process from the configurer process' (#35428) from build-runner-process into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/35428

104 files changed, 19998 insertions(+), 13649 deletions(-)

build.zig+36-27
......@@ -16,6 +16,8 @@ const IoMode = enum { threaded, evented };
1616const ValueInterpretMode = enum { direct, by_name };
1717
1818pub fn build(b: *std.Build) !void {
19 const arena = b.graph.arena;
20
1921 const only_c = b.option(bool, "only-c", "Translate the Zig compiler to C code, with only the C backend enabled") orelse false;
2022 const target = b.standardTargetOptions(.{
2123 .default_target = .{
......@@ -35,7 +37,7 @@ pub fn build(b: *std.Build) !void {
3537 const no_bin = b.option(bool, "no-bin", "skip emitting compiler binary") orelse false;
3638 const enable_superhtml = b.option(bool, "enable-superhtml", "Check langref output HTML validity") orelse false;
3739
38 const langref_file = generateLangRef(b);
40 const langref_file = try generateLangRef(b);
3941 const install_langref = b.addInstallFileWithDir(langref_file, .prefix, "doc/langref.html");
4042 const check_langref = superHtmlCheck(b, langref_file);
4143 if (enable_superhtml) install_langref.step.dependOn(check_langref);
......@@ -208,7 +210,8 @@ pub fn build(b: *std.Build) !void {
208210 .single_threaded = single_threaded,
209211 });
210212 exe.pie = pie;
211 exe.entitlements = entitlements;
213 // https://codeberg.org/ziglang/zig/issues/32173
214 exe.entitlements = if (entitlements) |p| .{ .cwd_relative = p } else null;
212215 exe.use_new_linker = b.option(bool, "new-linker", "Use the new linker");
213216
214217 const use_llvm = b.option(bool, "use-llvm", "Use the llvm backend");
......@@ -261,7 +264,7 @@ pub fn build(b: *std.Build) !void {
261264 var code: u8 = undefined;
262265 const git_describe_untrimmed = b.runAllowFail(&[_][]const u8{
263266 "git",
264 "-C", b.build_root.path orelse ".", // affects the --git-dir argument
267 "-C", b.fmt("{f}", .{b.root}), // affects the --git-dir argument
265268 "--git-dir", ".git", // affected by the -C argument
266269 "describe", "--match", "*.*.*", //
267270 "--tags", "--abbrev=9",
......@@ -307,7 +310,7 @@ pub fn build(b: *std.Build) !void {
307310 },
308311 }
309312 };
310 const version = try b.allocator.dupeSentinel(u8, version_slice, 0);
313 const version = try arena.dupeSentinel(u8, version_slice, 0);
311314 exe_options.addOption([:0]const u8, "version", version);
312315
313316 if (enable_llvm) {
......@@ -315,7 +318,7 @@ pub fn build(b: *std.Build) !void {
315318 const io = b.graph.io;
316319 const cwd: Io.Dir = .cwd();
317320 if (findConfigH(b, config_h_path_option)) |config_h_path| {
318 const file_contents = cwd.readFileAlloc(io, config_h_path, b.allocator, .limited(max_config_h_bytes)) catch unreachable;
321 const file_contents = cwd.readFileAlloc(io, config_h_path, arena, .limited(max_config_h_bytes)) catch unreachable;
319322 break :blk parseConfigH(b, file_contents);
320323 } else {
321324 std.log.warn("config.h could not be located automatically. Consider providing it explicitly via \"-Dconfig_h\"", .{});
......@@ -424,8 +427,8 @@ pub fn build(b: *std.Build) !void {
424427 else
425428 null;
426429
427 const fmt_include_paths = &.{ "lib", "src", "test", "tools", "build.zig", "build.zig.zon" };
428 const fmt_exclude_paths = &.{ "test/cases", "test/behavior/zon" };
430 const fmt_include_paths = b.pathList(&.{ "lib", "src", "test", "tools", "build.zig", "build.zig.zon" });
431 const fmt_exclude_paths = b.pathList(&.{ "test/cases", "test/behavior/zon" });
429432 const do_fmt = b.addFmt(.{
430433 .paths = fmt_include_paths,
431434 .exclude_paths = fmt_exclude_paths,
......@@ -975,11 +978,12 @@ fn addCxxKnownPath(
975978 errtxt: ?[]const u8,
976979 need_cpp_includes: bool,
977980) !void {
978 if (!std.process.can_spawn)
979 return error.RequiredLibraryNotFound;
981 if (!std.process.can_spawn) return error.RequiredLibraryNotFound;
982
983 const arena = b.graph.arena;
980984
981985 const path_padded = run: {
982 var args = std.array_list.Managed([]const u8).init(b.allocator);
986 var args = std.array_list.Managed([]const u8).init(arena);
983987 try args.append(ctx.cxx_compiler);
984988 var it = std.mem.tokenizeAny(u8, ctx.cxx_compiler_arg1, &std.ascii.whitespace);
985989 while (it.next()) |arg| try args.append(arg);
......@@ -1048,6 +1052,7 @@ const CMakeConfig = struct {
10481052const max_config_h_bytes = 1 * 1024 * 1024;
10491053
10501054fn findConfigH(b: *std.Build, config_h_path_option: ?[]const u8) ?[]const u8 {
1055 const arena = b.graph.arena;
10511056 const io = b.graph.io;
10521057 const cwd: Io.Dir = .cwd();
10531058
......@@ -1072,7 +1077,7 @@ fn findConfigH(b: *std.Build, config_h_path_option: ?[]const u8) ?[]const u8 {
10721077 if (config_h_or_err) |*file| {
10731078 file.close(io);
10741079 return fs.path.join(
1075 b.allocator,
1080 arena,
10761081 &[_][]const u8{ check_dir, "config.h" },
10771082 ) catch unreachable;
10781083 } else |e| switch (e) {
......@@ -1197,7 +1202,8 @@ fn parseConfigH(b: *std.Build, config_h_text: []const u8) ?CMakeConfig {
11971202}
11981203
11991204fn toNativePathSep(b: *std.Build, s: []const u8) []u8 {
1200 const duplicated = b.allocator.dupe(u8, s) catch unreachable;
1205 const arena = b.graph.arena;
1206 const duplicated = arena.dupe(u8, s) catch unreachable;
12011207 for (duplicated) |*byte| switch (byte.*) {
12021208 '/' => byte.* = fs.path.sep,
12031209 else => {},
......@@ -1486,8 +1492,9 @@ const llvm_libs_xtensa = [_][]const u8{
14861492 "LLVMXtensaInfo",
14871493};
14881494
1489fn generateLangRef(b: *std.Build) std.Build.LazyPath {
1495fn generateLangRef(b: *std.Build) !std.Build.LazyPath {
14901496 const io = b.graph.io;
1497 const arena = b.graph.arena;
14911498
14921499 const doctest_exe = b.addExecutable(.{
14931500 .name = "doctest",
......@@ -1498,11 +1505,10 @@ fn generateLangRef(b: *std.Build) std.Build.LazyPath {
14981505 }),
14991506 });
15001507
1501 var dir = b.build_root.handle.openDir(io, "doc/langref", .{ .iterate = true }) catch |err| {
1502 std.debug.panic("unable to open '{f}doc/langref' directory: {s}", .{
1503 b.build_root, @errorName(err),
1504 });
1505 };
1508 const langref_path = try b.root.join(arena, "doc/langref");
1509
1510 var dir = langref_path.root_dir.handle.openDir(io, langref_path.sub_path, .{ .iterate = true }) catch |err|
1511 std.debug.panic("unable to open directory {f}: {t}", .{ langref_path, err });
15061512 defer dir.close(io);
15071513
15081514 var wf = b.addWriteFiles();
......@@ -1515,17 +1521,20 @@ fn generateLangRef(b: *std.Build) std.Build.LazyPath {
15151521
15161522 const out_basename = b.fmt("{s}.out", .{std.fs.path.stem(entry.name)});
15171523 const cmd = b.addRunArtifact(doctest_exe);
1518 cmd.addArgs(&.{
1519 "--zig", b.graph.zig_exe,
1520 // TODO: enhance doctest to use "--listen=-" rather than operating
1521 // in a temporary directory
1522 "--cache-root", b.cache_root.path orelse ".",
1523 });
1524 cmd.addArgs(&.{ "--zig-lib-dir", b.fmt("{f}", .{b.graph.zig_lib_directory}) });
1525 cmd.addArgs(&.{"-i"});
1524
1525 cmd.addArg("--zig");
1526 cmd.addFileArg(.zig_exe);
1527
1528 cmd.addArg("--cache-root");
1529 cmd.addDirectoryArg(.cache_root);
1530
1531 cmd.addArg("--zig-lib-dir");
1532 cmd.addDirectoryArg(.zig_lib);
1533
1534 cmd.addArg("-i");
15261535 cmd.addFileArg(b.path(b.fmt("doc/langref/{s}", .{entry.name})));
15271536
1528 cmd.addArgs(&.{"-o"});
1537 cmd.addArg("-o");
15291538 _ = wf.addCopyFile(cmd.addOutputFileArg(out_basename), out_basename);
15301539 }
15311540
ci/aarch64-freebsd-release.sh-1
......@@ -59,7 +59,6 @@ stage3-release/bin/zig build \
5959 -Duse-zig-libcxx \
6060 -Dversion-string="$(stage3-release/bin/zig version)"
6161
62# diff returns an error code if the files differ.
6362echo "If the following command fails, it means nondeterminism has been"
6463echo "introduced, making stage3 and stage4 no longer byte-for-byte identical."
6564diff stage3-release/bin/zig stage4-release/bin/zig
ci/aarch64-linux-release.sh-1
......@@ -64,7 +64,6 @@ stage3-release/bin/zig build \
6464 -Duse-zig-libcxx \
6565 -Dversion-string="$(stage3-release/bin/zig version)"
6666
67# diff returns an error code if the files differ.
6867echo "If the following command fails, it means nondeterminism has been"
6968echo "introduced, making stage3 and stage4 no longer byte-for-byte identical."
7069diff stage3-release/bin/zig stage4-release/bin/zig
ci/aarch64-macos-release.sh-1
......@@ -73,7 +73,6 @@ stage3-release/bin/zig build \
7373 -Duse-zig-libcxx \
7474 -Dversion-string="$(stage3-release/bin/zig version)"
7575
76# diff returns an error code if the files differ.
7776echo "If the following command fails, it means nondeterminism has been"
7877echo "introduced, making stage3 and stage4 no longer byte-for-byte identical."
7978diff stage3-release/bin/zig stage4-release/bin/zig
ci/aarch64-netbsd-release.sh-1
......@@ -59,7 +59,6 @@ stage3-release/bin/zig build \
5959 -Duse-zig-libcxx \
6060 -Dversion-string="$(stage3-release/bin/zig version)"
6161
62# diff returns an error code if the files differ.
6362echo "If the following command fails, it means nondeterminism has been"
6463echo "introduced, making stage3 and stage4 no longer byte-for-byte identical."
6564diff stage3-release/bin/zig stage4-release/bin/zig
ci/loongarch64-linux-release.sh-1
......@@ -61,7 +61,6 @@ stage3-release/bin/zig build \
6161 -Duse-zig-libcxx \
6262 -Dversion-string="$(stage3-release/bin/zig version)"
6363
64# diff returns an error code if the files differ.
6564echo "If the following command fails, it means nondeterminism has been"
6665echo "introduced, making stage3 and stage4 no longer byte-for-byte identical."
6766diff stage3-release/bin/zig stage4-release/bin/zig
ci/powerpc64le-linux-release.sh-1
......@@ -63,7 +63,6 @@ stage3-release/bin/zig build \
6363 -Duse-zig-libcxx \
6464 -Dversion-string="$(stage3-release/bin/zig version)"
6565
66# diff returns an error code if the files differ.
6766echo "If the following command fails, it means nondeterminism has been"
6867echo "introduced, making stage3 and stage4 no longer byte-for-byte identical."
6968diff stage3-release/bin/zig stage4-release/bin/zig
ci/s390x-linux-release.sh-1
......@@ -62,7 +62,6 @@ stage3-release/bin/zig build \
6262 -Duse-zig-libcxx \
6363 -Dversion-string="$(stage3-release/bin/zig version)"
6464
65# diff returns an error code if the files differ.
6665echo "If the following command fails, it means nondeterminism has been"
6766echo "introduced, making stage3 and stage4 no longer byte-for-byte identical."
6867diff stage3-release/bin/zig stage4-release/bin/zig
ci/x86_64-freebsd-release.sh-1
......@@ -69,7 +69,6 @@ stage3-release/bin/zig build \
6969 -Duse-zig-libcxx \
7070 -Dversion-string="$(stage3-release/bin/zig version)"
7171
72# diff returns an error code if the files differ.
7372echo "If the following command fails, it means nondeterminism has been"
7473echo "introduced, making stage3 and stage4 no longer byte-for-byte identical."
7574diff stage3-release/bin/zig stage4-release/bin/zig
ci/x86_64-linux-debug-llvm.sh+1
......@@ -49,6 +49,7 @@ stage3-debug/bin/zig build \
4949 -Dno-lib
5050
5151stage3-debug/bin/zig build test docs \
52 --maker-opt=Debug \
5253 --maxrss ${ZSF_MAX_RSS:-0} \
5354 -Dlldb=$HOME/deps/lldb-zig/Debug-e0a42bb34/bin/lldb \
5455 -Dlibc-test-path=$HOME/deps/libc-test-f2bac77 \
ci/x86_64-linux-release.sh-1
......@@ -85,7 +85,6 @@ stage3-release/bin/zig build \
8585 -Duse-zig-libcxx \
8686 -Dversion-string="$(stage3-release/bin/zig version)"
8787
88# diff returns an error code if the files differ.
8988echo "If the following command fails, it means nondeterminism has been"
9089echo "introduced, making stage3 and stage4 no longer byte-for-byte identical."
9190diff stage3-release/bin/zig stage4-release/bin/zig
ci/x86_64-netbsd-release.sh-1
......@@ -63,7 +63,6 @@ stage3-release/bin/zig build \
6363 -Duse-zig-libcxx \
6464 -Dversion-string="$(stage3-release/bin/zig version)"
6565
66# diff returns an error code if the files differ.
6766echo "If the following command fails, it means nondeterminism has been"
6867echo "introduced, making stage3 and stage4 no longer byte-for-byte identical."
6968diff stage3-release/bin/zig stage4-release/bin/zig
ci/x86_64-openbsd-release.sh-1
......@@ -64,7 +64,6 @@ stage3-release/bin/zig build \
6464 -Duse-zig-libcxx \
6565 -Dversion-string="$(stage3-release/bin/zig version)"
6666
67# diff returns an error code if the files differ.
6867echo "If the following command fails, it means nondeterminism has been"
6968echo "introduced, making stage3 and stage4 no longer byte-for-byte identical."
7069diff stage3-release/bin/zig stage4-release/bin/zig
lib/compiler/Maker.zig created+2051
......@@ -0,0 +1,2051 @@
1const Maker = @This();
2const builtin = @import("builtin");
3
4const std = @import("std");
5const Allocator = std.mem.Allocator;
6const Cache = std.Build.Cache;
7const Configuration = std.Build.Configuration;
8const File = std.Io.File;
9const Io = std.Io;
10const Dir = std.Io.Dir;
11const Path = std.Build.Cache.Path;
12const Writer = std.Io.Writer;
13const assert = std.debug.assert;
14const fatal = std.process.fatal;
15const fmt = std.fmt;
16const log = std.log;
17const mem = std.mem;
18const process = std.process;
19
20const Fuzz = @import("Maker/Fuzz.zig");
21const Graph = @import("Maker/Graph.zig");
22const Step = @import("Maker/Step.zig");
23const Watch = @import("Maker/Watch.zig");
24const WebServer = @import("Maker/WebServer.zig");
25const ScannedConfig = @import("Maker/ScannedConfig.zig");
26const PkgConfig = @import("Maker/PkgConfig.zig");
27
28pub const std_options: std.Options = .{
29 .side_channels_mitigations = .none,
30 .http_disable_tls = true,
31};
32
33gpa: Allocator,
34graph: *Graph,
35install_paths: InstallPaths,
36scanned_config: *const ScannedConfig,
37steps: []Step,
38generated_files: []Path,
39run_args: ?[]const []const u8,
40
41available_rss: usize,
42max_rss_is_default: bool,
43max_rss_mutex: Io.Mutex,
44skip_oom_steps: bool,
45unit_test_timeout_ns: ?u64,
46watch: bool,
47web_server: if (!builtin.single_threaded) ?WebServer else ?noreturn,
48/// Allocated into `gpa`.
49memory_blocked_steps: std.ArrayList(Configuration.Step.Index),
50/// Allocated into `gpa`.
51step_stack: std.AutoArrayHashMapUnmanaged(Configuration.Step.Index, void),
52pkg_config: PkgConfig,
53
54error_style: ErrorStyle,
55multiline_errors: MultilineErrors,
56summary: Summary,
57
58pub fn main(init: process.Init.Minimal) !void {
59 // The build runner is often short-lived, but thanks to `--watch` and `--webui`, that's not
60 // always the case. So, we do need a true gpa for some things.
61 var safe_gpa_state: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{});
62 defer _ = safe_gpa_state.deinit();
63 const gpa = safe_gpa_state.allocator();
64
65 var threaded: std.Io.Threaded = .init(gpa, .{
66 .environ = init.environ,
67 .argv0 = .init(init.args),
68 });
69 defer threaded.deinit();
70 const io = threaded.io();
71
72 // ...but we'll back our arena by `std.heap.page_allocator` for efficiency.
73 var arena_instance: std.heap.ArenaAllocator = .init(std.heap.page_allocator);
74 defer arena_instance.deinit();
75 defer if (debugMakerLeaks()) log.debug("used {Bi} of arena", .{arena_instance.queryCapacity()});
76 const arena = arena_instance.allocator();
77
78 const args = try init.args.toSlice(arena);
79
80 // skip my own exe name
81 var arg_idx: usize = 1;
82
83 const zig_exe = expectArgOrFatal(args, &arg_idx, "--zig");
84 const zig_lib_dir = expectArgOrFatal(args, &arg_idx, "--zig-lib-dir");
85 const build_root = expectArgOrFatal(args, &arg_idx, "--build-root");
86 const local_cache_root = expectArgOrFatal(args, &arg_idx, "--local-cache");
87 const global_cache_root = expectArgOrFatal(args, &arg_idx, "--global-cache");
88 const configure_path = expectArgOrFatal(args, &arg_idx, "--configuration");
89
90 const cwd: Dir = .cwd();
91
92 const zig_lib_directory: Cache.Directory = .{
93 .path = zig_lib_dir,
94 .handle = try cwd.openDir(io, zig_lib_dir, .{}),
95 };
96
97 const build_root_directory: Cache.Directory = .{
98 .path = build_root,
99 .handle = try cwd.openDir(io, build_root, .{}),
100 };
101
102 const local_cache_directory: Cache.Directory = .{
103 .path = local_cache_root,
104 .handle = try cwd.createDirPathOpen(io, local_cache_root, .{}),
105 };
106
107 const global_cache_directory: Cache.Directory = .{
108 .path = global_cache_root,
109 .handle = try cwd.createDirPathOpen(io, global_cache_root, .{}),
110 };
111
112 var graph: Graph = .{
113 .io = io,
114 .arena = arena,
115 .cache = .{
116 .io = io,
117 .gpa = gpa,
118 .manifest_dir = try local_cache_directory.handle.createDirPathOpen(io, "h", .{}),
119 .cwd = try process.currentPathAlloc(io, arena),
120 },
121 .zig_exe = zig_exe,
122 .environ_map = try init.environ.createMap(arena),
123 .global_cache_root = global_cache_directory,
124 .local_cache_root = local_cache_directory,
125 .zig_lib_directory = zig_lib_directory,
126 .build_root_directory = build_root_directory,
127 };
128
129 graph.cache.addPrefix(.{ .path = null, .handle = cwd });
130 graph.cache.addPrefix(build_root_directory);
131 graph.cache.addPrefix(local_cache_directory);
132 graph.cache.addPrefix(global_cache_directory);
133 graph.cache.hash.addBytes(builtin.zig_version_string);
134
135 var step_names: std.ArrayList([]const u8) = .empty;
136 var help_menu = false;
137 var steps_menu = false;
138 var print_configuration = false;
139 var override_install_prefix: ?[]const u8 = null;
140 var override_lib_dir: ?[]const u8 = null;
141 var override_bin_dir: ?[]const u8 = null;
142 var override_include_dir: ?[]const u8 = null;
143 var error_style: ErrorStyle = .verbose;
144 var multiline_errors: MultilineErrors = .indent;
145 var summary: ?Summary = null;
146 var max_rss: u64 = 0;
147 var skip_oom_steps = false;
148 var test_timeout_ns: ?u64 = null;
149 var color: Color = .auto;
150 var watch = false;
151 var fuzz: ?Fuzz.Mode = null;
152 var debounce_interval_ms: u16 = 50;
153 var webui_listen: ?Io.net.IpAddress = null;
154 var debug_pkg_config = false;
155 var run_args: ?[]const []const u8 = null;
156
157 if (std.zig.EnvVar.ZIG_BUILD_ERROR_STYLE.get(&graph.environ_map)) |str| {
158 if (std.meta.stringToEnum(ErrorStyle, str)) |style| {
159 error_style = style;
160 }
161 }
162
163 if (std.zig.EnvVar.ZIG_BUILD_MULTILINE_ERRORS.get(&graph.environ_map)) |str| {
164 if (std.meta.stringToEnum(MultilineErrors, str)) |style| {
165 multiline_errors = style;
166 }
167 }
168
169 while (nextArg(args, &arg_idx)) |arg| {
170 if (mem.startsWith(u8, arg, "-")) {
171 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
172 help_menu = true;
173 } else if (mem.eql(u8, arg, "-l") or mem.eql(u8, arg, "--list-steps")) {
174 steps_menu = true;
175 } else if (mem.eql(u8, arg, "--print-configuration")) {
176 print_configuration = true;
177 } else if (mem.eql(u8, arg, "-p") or mem.eql(u8, arg, "--prefix")) {
178 override_install_prefix = nextArgOrFatal(args, &arg_idx);
179 } else if (mem.eql(u8, arg, "--prefix-lib-dir")) {
180 override_lib_dir = nextArgOrFatal(args, &arg_idx);
181 } else if (mem.eql(u8, arg, "--prefix-exe-dir")) {
182 override_bin_dir = nextArgOrFatal(args, &arg_idx);
183 } else if (mem.eql(u8, arg, "--prefix-include-dir")) {
184 override_include_dir = nextArgOrFatal(args, &arg_idx);
185 } else if (mem.eql(u8, arg, "--sysroot")) {
186 graph.sysroot = nextArgOrFatal(args, &arg_idx);
187 } else if (mem.eql(u8, arg, "--maxrss")) {
188 const max_rss_text = nextArgOrFatal(args, &arg_idx);
189 max_rss = std.fmt.parseIntSizeSuffix(max_rss_text, 10) catch |err|
190 fatal("invalid byte size {q}: {t}", .{ max_rss_text, err });
191 } else if (mem.eql(u8, arg, "--skip-oom-steps")) {
192 skip_oom_steps = true;
193 } else if (mem.eql(u8, arg, "--test-timeout")) {
194 const units: []const struct { []const u8, u64 } = &.{
195 .{ "ns", 1 },
196 .{ "nanosecond", 1 },
197 .{ "us", std.time.ns_per_us },
198 .{ "microsecond", std.time.ns_per_us },
199 .{ "ms", std.time.ns_per_ms },
200 .{ "millisecond", std.time.ns_per_ms },
201 .{ "s", std.time.ns_per_s },
202 .{ "second", std.time.ns_per_s },
203 .{ "m", std.time.ns_per_min },
204 .{ "minute", std.time.ns_per_min },
205 .{ "h", std.time.ns_per_hour },
206 .{ "hour", std.time.ns_per_hour },
207 };
208 const timeout_str = nextArgOrFatal(args, &arg_idx);
209 const num_end_idx = std.mem.findLastNone(u8, timeout_str, "abcdefghijklmnopqrstuvwxyz") orelse fatal(
210 "invalid timeout {q}: expected unit (ns, us, ms, s, m, h)",
211 .{timeout_str},
212 );
213 const num_str = timeout_str[0 .. num_end_idx + 1];
214 const unit_str = timeout_str[num_end_idx + 1 ..];
215 const unit_factor: f64 = for (units) |unit_and_factor| {
216 if (std.mem.eql(u8, unit_str, unit_and_factor[0])) {
217 break @floatFromInt(unit_and_factor[1]);
218 }
219 } else fatal(
220 "invalid timeout {q}: invalid unit {q} (expected ns, us, ms, s, m, h)",
221 .{ timeout_str, unit_str },
222 );
223 const num_parsed = std.fmt.parseFloat(f64, num_str) catch |err| fatal(
224 "invalid timeout {q}: invalid number {q} ({t})",
225 .{ timeout_str, num_str, err },
226 );
227 test_timeout_ns = std.math.lossyCast(u64, unit_factor * num_parsed);
228 } else if (mem.eql(u8, arg, "--search-prefix")) {
229 try graph.search_prefixes.append(arena, nextArgOrFatal(args, &arg_idx));
230 } else if (mem.eql(u8, arg, "--libc")) {
231 graph.libc_file = nextArgOrFatal(args, &arg_idx);
232 } else if (mem.eql(u8, arg, "--color")) {
233 const next_arg = nextArg(args, &arg_idx) orelse
234 fatalWithHint("expected [auto|on|off] after {q}", .{arg});
235 color = std.meta.stringToEnum(Color, next_arg) orelse {
236 fatalWithHint("expected [auto|on|off] after {q}, found {q}", .{
237 arg, next_arg,
238 });
239 };
240 } else if (mem.eql(u8, arg, "--error-style")) {
241 const next_arg = nextArg(args, &arg_idx) orelse
242 fatalWithHint("expected style after {q}", .{arg});
243 error_style = std.meta.stringToEnum(ErrorStyle, next_arg) orelse {
244 fatalWithHint("expected style after {q}, found {q}", .{ arg, next_arg });
245 };
246 } else if (mem.eql(u8, arg, "--multiline-errors")) {
247 const next_arg = nextArg(args, &arg_idx) orelse
248 fatalWithHint("expected style after {q}", .{arg});
249 multiline_errors = std.meta.stringToEnum(MultilineErrors, next_arg) orelse {
250 fatalWithHint("expected style after {q}, found {q}", .{ arg, next_arg });
251 };
252 } else if (mem.eql(u8, arg, "--summary")) {
253 const next_arg = nextArg(args, &arg_idx) orelse
254 fatalWithHint("expected [all|new|failures|line|none] after {q}", .{arg});
255 summary = std.meta.stringToEnum(Summary, next_arg) orelse {
256 fatalWithHint("expected [all|new|failures|line|none] after {q}, found {q}", .{
257 arg, next_arg,
258 });
259 };
260 } else if (mem.eql(u8, arg, "--seed")) {
261 const next_arg = nextArg(args, &arg_idx) orelse
262 fatalWithHint("expected u32 after {q}", .{arg});
263 graph.random_seed = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err| {
264 fatal("unable to parse seed {q} as unsigned 32-bit integer: {t}", .{ next_arg, err });
265 };
266 } else if (mem.eql(u8, arg, "--build-id")) {
267 graph.build_id = .fast;
268 } else if (mem.cutPrefix(u8, arg, "--build-id=")) |style| {
269 graph.build_id = std.zig.BuildId.parse(style) catch |err|
270 fatal("unable to parse --build-id style {q}: {t}", .{ style, err });
271 } else if (mem.eql(u8, arg, "--debounce")) {
272 const next_arg = nextArg(args, &arg_idx) orelse
273 fatalWithHint("expected u16 after {q}", .{arg});
274 debounce_interval_ms = std.fmt.parseUnsigned(u16, next_arg, 0) catch |err| {
275 fatal("unable to parse debounce interval {q} as unsigned 16-bit integer: {t}", .{
276 next_arg, err,
277 });
278 };
279 } else if (mem.eql(u8, arg, "--webui")) {
280 if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) };
281 } else if (mem.startsWith(u8, arg, "--webui=")) {
282 const addr_str = arg["--webui=".len..];
283 if (std.mem.eql(u8, addr_str, "-")) fatal("web interface cannot listen on stdio", .{});
284 webui_listen = Io.net.IpAddress.parseLiteral(addr_str) catch |err| {
285 fatal("invalid web UI address {q}: {t}", .{ addr_str, err });
286 };
287 } else if (mem.eql(u8, arg, "--debug-log")) {
288 const next_arg = nextArgOrFatal(args, &arg_idx);
289 try graph.debug_log_scopes.append(arena, next_arg);
290 } else if (mem.eql(u8, arg, "--debug-compile-errors")) {
291 graph.debug_compile_errors = true;
292 } else if (mem.eql(u8, arg, "--debug-incremental")) {
293 graph.debug_incremental = true;
294 } else if (mem.eql(u8, arg, "--debug-pkg-config")) {
295 debug_pkg_config = true;
296 } else if (mem.eql(u8, arg, "--debug-rt")) {
297 graph.debug_compiler_runtime_libs = .Debug;
298 } else if (mem.cutPrefix(u8, arg, "--debug-rt=")) |rest| {
299 graph.debug_compiler_runtime_libs = std.meta.stringToEnum(std.builtin.OptimizeMode, rest) orelse
300 fatal("unrecognized optimization mode: {s}", .{rest});
301 } else if (is_debug_mode and mem.eql(u8, arg, "--debug-maker-leaks")) {
302 debug_maker_leaks = true;
303 } else if (mem.eql(u8, arg, "--libc-runtimes") or mem.eql(u8, arg, "--glibc-runtimes")) {
304 // --glibc-runtimes was the old name of the flag; kept for compatibility for now.
305 graph.libc_runtimes_dir = nextArgOrFatal(args, &arg_idx);
306 } else if (mem.eql(u8, arg, "--verbose")) {
307 graph.verbose = true;
308 } else if (mem.eql(u8, arg, "--verbose-air")) {
309 graph.verbose_air = true;
310 } else if (mem.eql(u8, arg, "--verbose-cc")) {
311 graph.verbose_cc = true;
312 } else if (mem.eql(u8, arg, "--verbose-llvm-ir")) {
313 graph.verbose_llvm_ir = true;
314 } else if (mem.eql(u8, arg, "--watch")) {
315 watch = true;
316 } else if (mem.eql(u8, arg, "--time-report")) {
317 graph.time_report = true;
318 if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) };
319 } else if (mem.eql(u8, arg, "--fuzz")) {
320 fuzz = .{ .forever = undefined };
321 graph.fuzzing = true;
322 if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) };
323 } else if (mem.startsWith(u8, arg, "--fuzz=")) {
324 const value = arg["--fuzz=".len..];
325 if (value.len == 0) fatal("missing argument to --fuzz", .{});
326
327 const unit: u8 = value[value.len - 1];
328 const digits = switch (unit) {
329 '0'...'9' => value,
330 'K', 'M', 'G' => value[0 .. value.len - 1],
331 else => fatal(
332 "invalid argument to --fuzz, expected a positive number optionally suffixed by one of: [KMG]",
333 .{},
334 ),
335 };
336
337 const amount = std.fmt.parseInt(u64, digits, 10) catch {
338 fatal(
339 "invalid argument to --fuzz, expected a positive number optionally suffixed by one of: [KMG]",
340 .{},
341 );
342 };
343
344 const normalized_amount = std.math.mul(u64, amount, switch (unit) {
345 else => unreachable,
346 '0'...'9' => 1,
347 'K' => 1000,
348 'M' => 1_000_000,
349 'G' => 1_000_000_000,
350 }) catch fatal("fuzzing limit amount overflows u64", .{});
351
352 fuzz = .{
353 .limit = .{
354 .amount = normalized_amount,
355 },
356 };
357 graph.fuzzing = true;
358 } else if (mem.eql(u8, arg, "-fincremental")) {
359 graph.incremental = true;
360 } else if (mem.eql(u8, arg, "-fno-incremental")) {
361 graph.incremental = false;
362 } else if (mem.eql(u8, arg, "-fwine")) {
363 graph.enable_wine = true;
364 } else if (mem.eql(u8, arg, "-fno-wine")) {
365 graph.enable_wine = false;
366 } else if (mem.eql(u8, arg, "-fqemu")) {
367 graph.enable_qemu = true;
368 } else if (mem.eql(u8, arg, "-fno-qemu")) {
369 graph.enable_qemu = false;
370 } else if (mem.eql(u8, arg, "-fwasmtime")) {
371 graph.enable_wasmtime = true;
372 } else if (mem.eql(u8, arg, "-fno-wasmtime")) {
373 graph.enable_wasmtime = false;
374 } else if (mem.eql(u8, arg, "-frosetta")) {
375 graph.enable_rosetta = true;
376 } else if (mem.eql(u8, arg, "-fno-rosetta")) {
377 graph.enable_rosetta = false;
378 } else if (mem.eql(u8, arg, "-fdarling")) {
379 graph.enable_darling = true;
380 } else if (mem.eql(u8, arg, "-fno-darling")) {
381 graph.enable_darling = false;
382 } else if (mem.eql(u8, arg, "-fallow-so-scripts")) {
383 graph.allow_so_scripts = true;
384 } else if (mem.eql(u8, arg, "-fno-allow-so-scripts")) {
385 graph.allow_so_scripts = false;
386 } else if (mem.eql(u8, arg, "-freference-trace")) {
387 graph.reference_trace = 256;
388 } else if (mem.cutPrefix(u8, arg, "-freference-trace=")) |num| {
389 graph.reference_trace = std.fmt.parseUnsigned(u32, num, 10) catch |err|
390 fatal("unable to parse reference_trace count {q}: {t}", .{ num, err });
391 } else if (mem.eql(u8, arg, "-fno-reference-trace")) {
392 graph.reference_trace = null;
393 } else if (mem.eql(u8, arg, "--error-limit")) {
394 const next_arg = nextArgOrFatal(args, &arg_idx);
395 graph.error_limit = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err|
396 fatal("unable to parse error limit {q}: {t}", .{ next_arg, err });
397 } else if (mem.cutPrefix(u8, arg, "-j")) |text| {
398 const n = std.fmt.parseUnsigned(u32, text, 10) catch |err|
399 fatal("unable to parse jobs count {q}: {t}", .{ text, err });
400 if (n < 1) fatal("number of jobs must be at least 1", .{});
401 threaded.setAsyncLimit(.limited(n));
402 graph.max_jobs = n;
403 } else if (mem.eql(u8, arg, "--")) {
404 run_args = argsRest(args, arg_idx);
405 break;
406 } else {
407 fatalWithHint("unrecognized argument: {s}", .{arg});
408 }
409 } else {
410 try step_names.append(arena, arg);
411 }
412 }
413
414 const NO_COLOR = std.zig.EnvVar.NO_COLOR.isSet(&graph.environ_map);
415 const CLICOLOR_FORCE = std.zig.EnvVar.CLICOLOR_FORCE.isSet(&graph.environ_map);
416
417 graph.stderr_mode = switch (color) {
418 .auto => try .detect(io, .stderr(), NO_COLOR, CLICOLOR_FORCE),
419 .on => .escape_codes,
420 .off => .no_color,
421 };
422
423 const scanned_config: ScannedConfig = sc: {
424 const configuration = c: {
425 var file = cwd.openFile(io, configure_path, .{}) catch |err|
426 fatal("failed to open configuration file {s}: {t}", .{ configure_path, err });
427 defer file.close(io);
428 break :c Configuration.loadFile(arena, io, file) catch |err|
429 fatal("failed to load configuration file {s}: {t}", .{ configure_path, err });
430 };
431 // Technically if the configuration is marked as poisoned, we could
432 // already delete the file now, but we leave it around in case the
433 // maker process fails or crashes and it's helpful to be able to repeat
434 // execution of the command line or otherwise inspect the configuration file.
435 const c = &configuration;
436 var top_level_steps: std.StringArrayHashMapUnmanaged(Configuration.Step.Index) = .empty;
437 for (configuration.steps, 0..) |*conf_step, step_index_usize| {
438 if (conf_step.owner != .root) continue;
439 const step_index: Configuration.Step.Index = @enumFromInt(step_index_usize);
440 const flags = conf_step.flags(c);
441 switch (flags.tag) {
442 .top_level => {
443 const name = step_index.ptr(c).name.slice(c);
444 try top_level_steps.put(arena, name, step_index);
445 },
446 else => {},
447 }
448 }
449 for (c.search_prefixes) |search_prefix| {
450 try graph.search_prefixes.append(arena, search_prefix.slice(c));
451 }
452 break :sc .{
453 .configuration = configuration,
454 .top_level_steps = top_level_steps,
455 .path = configure_path,
456 };
457 };
458
459 if (help_menu) {
460 var w = initStdoutWriter(io);
461 scanned_config.printUsage(&graph, w) catch |err| switch (err) {
462 error.WriteFailed => return stdout_writer_allocation.err.?,
463 else => |e| return e,
464 };
465 w.flush() catch return stdout_writer_allocation.err.?;
466 return cleanExit(io, &scanned_config);
467 } else if (steps_menu) {
468 var w = initStdoutWriter(io);
469 scanned_config.printSteps(&graph, w) catch |err| switch (err) {
470 error.WriteFailed => return stdout_writer_allocation.err.?,
471 else => |e| return e,
472 };
473 w.flush() catch return stdout_writer_allocation.err.?;
474 return cleanExit(io, &scanned_config);
475 } else if (print_configuration) {
476 var w = initStdoutWriter(io);
477 scanned_config.print(w) catch return stdout_writer_allocation.err.?;
478 w.flush() catch return stdout_writer_allocation.err.?;
479 return cleanExit(io, &scanned_config);
480 }
481
482 if (webui_listen != null) {
483 if (watch) fatal("using '--webui' and '--watch' together is not yet supported; consider omitting '--watch' in favour of the web UI \"Rebuild\" button", .{});
484 if (builtin.single_threaded) fatal("'--webui' is not yet supported on single-threaded hosts", .{});
485 }
486
487 const main_progress_node = std.Progress.start(io, .{
488 .disable_printing = (color == .off),
489 });
490 defer main_progress_node.end();
491
492 const install_prefix_path: Path = if (graph.environ_map.get("DESTDIR")) |dest_dir| .{
493 .root_dir = .cwd(),
494 .sub_path = try Dir.path.join(arena, &.{ dest_dir, override_install_prefix orelse "/usr" }),
495 } else if (override_install_prefix) |cwd_relative| .{
496 .root_dir = .cwd(),
497 .sub_path = cwd_relative,
498 } else .{
499 .root_dir = build_root_directory,
500 .sub_path = "zig-out",
501 };
502
503 const install_lib_path: Path = if (override_lib_dir) |cwd_relative| .{
504 .root_dir = .cwd(),
505 .sub_path = cwd_relative,
506 } else try install_prefix_path.join(arena, "lib");
507
508 const install_bin_path: Path = if (override_bin_dir) |cwd_relative| .{
509 .root_dir = .cwd(),
510 .sub_path = cwd_relative,
511 } else try install_prefix_path.join(arena, "bin");
512
513 const install_include_path: Path = if (override_include_dir) |cwd_relative| .{
514 .root_dir = .cwd(),
515 .sub_path = cwd_relative,
516 } else try install_prefix_path.join(arena, "include");
517
518 var maker: Maker = .{
519 .gpa = gpa,
520 .graph = &graph,
521 .scanned_config = &scanned_config,
522 .install_paths = .{
523 .prefix = install_prefix_path,
524 .lib = install_lib_path,
525 .bin = install_bin_path,
526 .include = install_include_path,
527 },
528
529 .steps = try arena.alloc(Step, scanned_config.configuration.steps.len),
530 .generated_files = try arena.alloc(Path, scanned_config.configuration.generated_files_len),
531 .run_args = run_args,
532
533 .available_rss = max_rss,
534 .max_rss_is_default = false,
535 .max_rss_mutex = .init,
536 .skip_oom_steps = skip_oom_steps,
537 .unit_test_timeout_ns = test_timeout_ns,
538
539 .watch = watch,
540 .web_server = undefined, // set after `prepare`
541 .memory_blocked_steps = .empty,
542 .step_stack = .empty,
543 .pkg_config = .{ .debug = debug_pkg_config },
544
545 .error_style = error_style,
546 .multiline_errors = multiline_errors,
547 .summary = summary orelse if (watch or webui_listen != null) .line else .failures,
548 };
549 defer {
550 maker.memory_blocked_steps.deinit(gpa);
551 maker.step_stack.deinit(gpa);
552 }
553
554 if (maker.available_rss == 0) {
555 maker.available_rss = process.totalSystemMemory() catch std.math.maxInt(u64);
556 maker.max_rss_is_default = true;
557 }
558
559 maker.prepare(step_names.items) catch |err| switch (err) {
560 error.DependencyLoopDetected, error.InsufficientMemory => {
561 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};
562 process.exit(1);
563 },
564 else => |e| return e,
565 };
566
567 var w: Watch = w: {
568 if (!watch) break :w undefined;
569 if (!Watch.have_impl) fatal("--watch not yet implemented for {t}", .{builtin.os.tag});
570 break :w try .init(&maker);
571 };
572
573 const now = Io.Clock.Timestamp.now(io, .awake);
574
575 maker.web_server = if (webui_listen) |listen_address| ws: {
576 if (builtin.single_threaded) unreachable; // `fatal` above
577 break :ws .init(.{
578 .maker = &maker,
579 .root_prog_node = main_progress_node,
580 .listen_address = listen_address,
581 .base_timestamp = now,
582 });
583 } else null;
584
585 if (maker.web_server) |*ws| {
586 ws.start() catch |err| fatal("failed to start web server: {t}", .{err});
587 }
588
589 rebuild: while (true) : (if (maker.error_style.clearOnUpdate()) {
590 const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode);
591 defer io.unlockStderr();
592 stderr.file_writer.interface.writeAll("\x1B[2J\x1B[3J\x1B[H") catch |err| switch (err) {
593 error.WriteFailed => return stderr.file_writer.err.?,
594 };
595 }) {
596 if (maker.web_server) |*ws| ws.startBuild();
597
598 try maker.makeStepNames(step_names.items, main_progress_node, fuzz);
599
600 if (maker.web_server) |*web_server| {
601 if (fuzz) |mode| if (mode != .forever) fatal(
602 "error: limited fuzzing is not implemented yet for --webui",
603 .{},
604 );
605
606 web_server.finishBuild(.{ .fuzz = fuzz != null });
607 }
608
609 if (maker.web_server) |*web_server| {
610 const c = &scanned_config.configuration;
611 assert(!watch); // fatal error after CLI parsing
612 while (true) switch (try web_server.wait()) {
613 .rebuild => {
614 for (maker.step_stack.keys()) |step_index| {
615 const step = maker.stepByIndex(step_index);
616 step.state = .precheck_done;
617 const deps = step_index.ptr(c).deps.slice(c);
618 step.pending_deps = @intCast(deps.len);
619 step.reset(&maker);
620 }
621 continue :rebuild;
622 },
623 };
624 }
625
626 if (!maker.watch) return;
627
628 // Comptime-known guard to prevent including the logic below when `!Watch.have_impl`.
629 if (!Watch.have_impl) unreachable;
630
631 try w.update(maker.step_stack.keys());
632
633 // Wait until a file system notification arrives. Read all such events
634 // until the buffer is empty. Then wait for a debounce interval, resetting
635 // if any more events come in. After the debounce interval has passed,
636 // trigger a rebuild on all steps with modified inputs, as well as their
637 // recursive dependants.
638 var caption_buf: [std.Progress.Node.max_name_len]u8 = undefined;
639 const caption = std.fmt.bufPrint(&caption_buf, "watching {d} directories, {d} processes", .{
640 w.dir_count, countSubProcesses(&maker),
641 }) catch &caption_buf;
642 var debouncing_node = main_progress_node.start(caption, 0);
643 var in_debounce = false;
644 while (true) switch (try w.wait(if (in_debounce) .{ .ms = debounce_interval_ms } else .none)) {
645 .timeout => {
646 assert(in_debounce);
647 debouncing_node.end();
648 markFailedStepsDirty(&maker);
649 continue :rebuild;
650 },
651 .dirty => if (!in_debounce) {
652 in_debounce = true;
653 debouncing_node.end();
654 debouncing_node = main_progress_node.start("Debouncing (Change Detected)", 0);
655 },
656 .clean => {},
657 };
658 }
659}
660
661fn markFailedStepsDirty(maker: *Maker) void {
662 const all_steps = maker.step_stack.keys();
663
664 for (all_steps) |step_index| {
665 const step = maker.stepByIndex(step_index);
666 switch (step.state) {
667 .dependency_failure, .failure, .skipped => _ = maker.invalidateResult(step),
668 else => continue,
669 }
670 }
671 // Now that all dirty steps have been found, the remaining steps that
672 // succeeded from last run shall be marked "cached".
673 for (all_steps) |step_index| {
674 const step = maker.stepByIndex(step_index);
675 switch (step.state) {
676 .success => step.result_cached = true,
677 else => continue,
678 }
679 }
680}
681
682fn countSubProcesses(maker: *Maker) usize {
683 const all_steps = maker.step_stack.keys();
684 var count: usize = 0;
685 for (all_steps) |step_index| {
686 const s = maker.stepByIndex(step_index);
687 count += @intFromBool(s.getZigProcess() != null);
688 }
689 return count;
690}
691
692const InstallPaths = struct {
693 prefix: Path,
694 lib: Path,
695 bin: Path,
696 include: Path,
697};
698
699pub fn stepByIndex(maker: *const Maker, i: Configuration.Step.Index) *Step {
700 return &maker.steps[@intFromEnum(i)];
701}
702
703fn prepare(maker: *Maker, step_names: []const []const u8) !void {
704 const gpa = maker.gpa;
705 const graph = maker.graph;
706 const arena = graph.arena;
707 const seed: u32 = graph.random_seed;
708 const step_stack = &maker.step_stack;
709 const c = &maker.scanned_config.configuration;
710
711 for (maker.steps, 0..) |*step, step_index_usize| {
712 const step_index: Configuration.Step.Index = @enumFromInt(step_index_usize);
713 step.* = .{ .extended = .init(step_index.ptr(c).flags(c).tag) };
714 }
715
716 if (step_names.len == 0) {
717 try step_stack.put(gpa, c.default_step, {});
718 } else {
719 try step_stack.ensureUnusedCapacity(gpa, step_names.len);
720 for (0..step_names.len) |i| {
721 const step_name = step_names[step_names.len - i - 1];
722 const s = maker.scanned_config.top_level_steps.get(step_name) orelse {
723 log.info("to list available steps: zig build -l", .{});
724 fatal("no such step: {s}", .{step_name});
725 };
726 step_stack.putAssumeCapacity(s, {});
727 }
728 }
729
730 const starting_steps = try arena.dupe(Configuration.Step.Index, step_stack.keys());
731
732 var rng = std.Random.DefaultPrng.init(seed);
733 const rand = rng.random();
734 rand.shuffle(Configuration.Step.Index, starting_steps);
735
736 for (starting_steps) |s| {
737 try constructGraphAndCheckForDependencyLoop(maker, s, &maker.step_stack, rand);
738 }
739
740 {
741 // Check that we have enough memory to complete the build.
742 var any_problems = false;
743 var max_needed: usize = 0;
744 for (step_stack.keys()) |step_index| {
745 const make_step = maker.stepByIndex(step_index);
746 const conf_step = step_index.ptr(c);
747 const max_rss = conf_step.max_rss.toBytes();
748 if (max_rss == 0) continue;
749 max_needed = @max(max_needed, max_rss);
750 if (max_rss > maker.available_rss) {
751 if (maker.skip_oom_steps) {
752 make_step.state = .skipped_oom;
753 for (make_step.dependants.items) |dependant| {
754 maker.stepByIndex(dependant).pending_deps -= 1;
755 }
756 } else {
757 log.err("{s}{s}: this step declares an upper bound of {d} bytes of memory, exceeding the available {d} bytes of memory", .{
758 conf_step.owner.depPrefixSlice(c),
759 conf_step.name.slice(c),
760 max_rss,
761 maker.available_rss,
762 });
763 any_problems = true;
764 }
765 }
766 }
767 if (any_problems) {
768 if (maker.max_rss_is_default) {
769 std.log.info("use --maxrss {d} to proceed, risking system memory exhaustion", .{
770 max_needed,
771 });
772 }
773 return error.InsufficientMemory;
774 }
775 }
776}
777
778fn makeStepNames(
779 maker: *Maker,
780 step_names: []const []const u8,
781 parent_prog_node: std.Progress.Node,
782 fuzz: ?Fuzz.Mode,
783) !void {
784 const graph = maker.graph;
785 const gpa = maker.gpa;
786 const io = graph.io;
787 const step_stack = &maker.step_stack;
788 const top_level_steps = &maker.scanned_config.top_level_steps;
789 const c = &maker.scanned_config.configuration;
790
791 {
792 // Collect the initial set of tasks (those with no outstanding dependencies) into a buffer,
793 // then spawn them. The buffer is so that we don't race with `makeStep` and end up thinking
794 // a step is initial when it actually became ready due to an earlier initial step.
795 var initial_set: std.ArrayList(Configuration.Step.Index) = .empty;
796 defer initial_set.deinit(gpa);
797 try initial_set.ensureUnusedCapacity(gpa, step_stack.count());
798 for (step_stack.keys()) |step_index| {
799 const s = maker.stepByIndex(step_index);
800 if (s.state == .precheck_done and s.pending_deps == 0) {
801 initial_set.appendAssumeCapacity(step_index);
802 }
803 }
804
805 const step_prog = parent_prog_node.start("steps", step_stack.count());
806 defer step_prog.end();
807
808 var group: Io.Group = .init;
809 defer group.cancel(io);
810 // Start working on all of the initial steps...
811 for (initial_set.items) |step_index| try stepReady(maker, &group, step_index, step_prog);
812 // ...and `makeStep` will trigger every other step when their last dependency finishes.
813 try group.await(io);
814 }
815
816 assert(maker.memory_blocked_steps.items.len == 0);
817
818 var test_pass_count: usize = 0;
819 var test_skip_count: usize = 0;
820 var test_fail_count: usize = 0;
821 var test_crash_count: usize = 0;
822 var test_timeout_count: usize = 0;
823
824 var test_count: usize = 0;
825
826 var success_count: usize = 0;
827 var skipped_count: usize = 0;
828 var failure_count: usize = 0;
829 var pending_count: usize = 0;
830 var total_compile_errors: usize = 0;
831
832 var cleanup_task = io.async(cleanTmpFiles, .{ maker, step_stack.keys() });
833 defer cleanup_task.await(io);
834
835 for (step_stack.keys()) |step_index| {
836 const make_step = maker.stepByIndex(step_index);
837 test_pass_count += make_step.test_results.passCount();
838 test_skip_count += make_step.test_results.skip_count;
839 test_fail_count += make_step.test_results.fail_count;
840 test_crash_count += make_step.test_results.crash_count;
841 test_timeout_count += make_step.test_results.timeout_count;
842
843 test_count += make_step.test_results.test_count;
844
845 switch (make_step.state) {
846 .precheck_unstarted => unreachable,
847 .precheck_started => unreachable,
848 .precheck_done => unreachable,
849 .dependency_failure => pending_count += 1,
850 .success => success_count += 1,
851 .skipped, .skipped_oom => skipped_count += 1,
852 .failure => {
853 failure_count += 1;
854 const compile_errors_len = make_step.result_error_bundle.errorMessageCount();
855 if (compile_errors_len > 0) {
856 total_compile_errors += compile_errors_len;
857 }
858 },
859 }
860 }
861
862 if (fuzz) |mode| blk: {
863 switch (builtin.os.tag) {
864 // Current implementation depends on two things that need to be ported to Windows:
865 // * Memory-mapping to share data between the fuzzer and build runner.
866 // * COFF/PE support added to `std.debug.Info` (it needs a batching API for resolving
867 // many addresses to source locations).
868 .windows => fatal("--fuzz not yet implemented for {t}", .{builtin.os.tag}),
869 else => {},
870 }
871 if (@bitSizeOf(usize) != 64) {
872 // Current implementation depends on posix.mmap()'s second parameter, `length: usize`,
873 // being compatible with file system's u64 return value. This is not the case
874 // on 32-bit platforms.
875 // Affects or affected by issues #5185, #22523, and #22464.
876 fatal("--fuzz not yet implemented on {d}-bit platforms", .{@bitSizeOf(usize)});
877 }
878
879 switch (mode) {
880 .forever => break :blk,
881 .limit => {},
882 }
883
884 assert(mode == .limit);
885 var f = Fuzz.init(maker, step_stack.keys(), parent_prog_node, mode) catch |err|
886 fatal("failed to start fuzzer: {t}", .{err});
887 defer f.deinit();
888
889 f.start();
890 try f.waitAndPrintReport();
891 }
892
893 // Every test has a state
894 assert(test_pass_count + test_skip_count + test_fail_count + test_crash_count + test_timeout_count == test_count);
895
896 if (failure_count == 0) {
897 std.Progress.setStatus(.success);
898 } else {
899 std.Progress.setStatus(.failure);
900 }
901
902 summary: {
903 switch (maker.summary) {
904 .all, .new, .line => {},
905 .failures => if (failure_count == 0) break :summary,
906 .none => break :summary,
907 }
908
909 const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode);
910 defer io.unlockStderr();
911 const t = stderr.terminal();
912 const w = &stderr.file_writer.interface;
913
914 const total_count = success_count + failure_count + pending_count + skipped_count;
915 t.setColor(.cyan) catch {};
916 t.setColor(.bold) catch {};
917 w.writeAll("Build Summary: ") catch {};
918 t.setColor(.reset) catch {};
919 w.print("{d}/{d} steps succeeded", .{ success_count, total_count }) catch {};
920 {
921 t.setColor(.dim) catch {};
922 var first = true;
923 if (skipped_count > 0) {
924 w.print("{s}{d} skipped", .{ if (first) " (" else ", ", skipped_count }) catch {};
925 first = false;
926 }
927 if (failure_count > 0) {
928 w.print("{s}{d} failed", .{ if (first) " (" else ", ", failure_count }) catch {};
929 first = false;
930 }
931 if (!first) w.writeByte(')') catch {};
932 t.setColor(.reset) catch {};
933 }
934
935 if (test_count > 0) {
936 w.print("; {d}/{d} tests passed", .{ test_pass_count, test_count }) catch {};
937 t.setColor(.dim) catch {};
938 var first = true;
939 if (test_skip_count > 0) {
940 w.print("{s}{d} skipped", .{ if (first) " (" else ", ", test_skip_count }) catch {};
941 first = false;
942 }
943 if (test_fail_count > 0) {
944 w.print("{s}{d} failed", .{ if (first) " (" else ", ", test_fail_count }) catch {};
945 first = false;
946 }
947 if (test_crash_count > 0) {
948 w.print("{s}{d} crashed", .{ if (first) " (" else ", ", test_crash_count }) catch {};
949 first = false;
950 }
951 if (test_timeout_count > 0) {
952 w.print("{s}{d} timed out", .{ if (first) " (" else ", ", test_timeout_count }) catch {};
953 first = false;
954 }
955 if (!first) w.writeByte(')') catch {};
956 t.setColor(.reset) catch {};
957 }
958
959 w.writeAll("\n") catch {};
960
961 if (maker.summary == .line) break :summary;
962
963 // Print a fancy tree with build results.
964 var step_stack_copy = try step_stack.clone(gpa);
965 defer step_stack_copy.deinit(gpa);
966
967 var print_node: PrintNode = .{ .parent = null };
968 if (step_names.len == 0) {
969 print_node.last = true;
970 printTreeStep(maker, c.default_step, t, &print_node, &step_stack_copy) catch |err| switch (err) {
971 error.Canceled => |e| return e,
972 else => {},
973 };
974 } else {
975 const last_index = if (maker.summary == .all) top_level_steps.count() else blk: {
976 var i: usize = step_names.len;
977 while (i > 0) {
978 i -= 1;
979 const step_index = top_level_steps.get(step_names[i]).?;
980 const step = maker.stepByIndex(step_index);
981 const found = switch (maker.summary) {
982 .all, .line, .none => unreachable,
983 .failures => step.state != .success,
984 .new => !step.result_cached,
985 };
986 if (found) break :blk i;
987 }
988 break :blk top_level_steps.count();
989 };
990 for (step_names, 0..) |step_name, i| {
991 const step_index = top_level_steps.get(step_name).?;
992 print_node.last = i + 1 == last_index;
993 printTreeStep(maker, step_index, t, &print_node, &step_stack_copy) catch |err| switch (err) {
994 error.Canceled => |e| return e,
995 else => {},
996 };
997 }
998 }
999 w.writeByte('\n') catch {};
1000 }
1001
1002 if (maker.watch or maker.web_server != null) return;
1003
1004 const code: u8 = code: {
1005 if (failure_count == 0) break :code 0; // success
1006 if (maker.error_style.verboseContext()) break :code 1; // failure; print build command
1007 break :code 2; // failure; do not print build command
1008 };
1009 if (code == 0) {
1010 removePoisonedConfiguration(io, maker.scanned_config);
1011 if (debugMakerLeaks()) return deinit(maker);
1012 }
1013 cleanup_task.await(io); // There is a defer above but an exit below.
1014 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};
1015 process.exit(code);
1016}
1017
1018fn deinit(maker: *Maker) void {
1019 const gpa = maker.gpa;
1020 for (maker.steps) |*step| {
1021 step.clearFailedCommand(gpa);
1022 step.clearErrorBundle(gpa);
1023 step.inputs.deinit(gpa);
1024 }
1025}
1026
1027fn stepReady(
1028 maker: *Maker,
1029 group: *Io.Group,
1030 step_index: Configuration.Step.Index,
1031 root_prog_node: std.Progress.Node,
1032) Io.Cancelable!void {
1033 const graph = maker.graph;
1034 const io = graph.io;
1035 const c = &maker.scanned_config.configuration;
1036 const max_rss = step_index.ptr(c).max_rss.toBytes();
1037 if (max_rss != 0) {
1038 try maker.max_rss_mutex.lock(io);
1039 defer maker.max_rss_mutex.unlock(io);
1040 if (maker.available_rss < max_rss) {
1041 // Running this step right now could possibly exceed the allotted RSS.
1042 maker.memory_blocked_steps.append(maker.gpa, step_index) catch
1043 @panic("TODO eliminate memory allocation here");
1044 return;
1045 }
1046 maker.available_rss -= max_rss;
1047 }
1048 group.async(io, makeStep, .{ maker, group, step_index, root_prog_node });
1049}
1050
1051/// Runs the "make" function of the single step `s`, updates its state, and then spawns newly-ready
1052/// dependant steps in `group`. If `s` makes an RSS claim (i.e. `s.max_rss != 0`), the caller must
1053/// have already subtracted this value from `maker.available_rss`. This function will release the RSS
1054/// claim (i.e. add `s.max_rss` back into `maker.available_rss`) and queue any viable memory-blocked
1055/// steps after "make" completes for `s`.
1056fn makeStep(
1057 maker: *Maker,
1058 group: *Io.Group,
1059 step_index: Configuration.Step.Index,
1060 root_prog_node: std.Progress.Node,
1061) Io.Cancelable!void {
1062 const graph = maker.graph;
1063 const io = graph.io;
1064 const gpa = maker.gpa;
1065 const c = &maker.scanned_config.configuration;
1066 const conf_step = step_index.ptr(c);
1067 const step_name = conf_step.name.slice(c);
1068 const deps = conf_step.deps.slice(c);
1069 const make_step = maker.stepByIndex(step_index);
1070
1071 {
1072 const step_prog_node = root_prog_node.start(step_name, 0);
1073 defer step_prog_node.end();
1074
1075 if (maker.web_server) |*ws| ws.updateStepStatus(step_index, .wip);
1076
1077 const new_state: Step.State = for (deps) |dep_index| {
1078 const dep_make_step = maker.stepByIndex(dep_index);
1079 switch (@atomicLoad(Step.State, &dep_make_step.state, .monotonic)) {
1080 .precheck_unstarted => unreachable,
1081 .precheck_started => unreachable,
1082 .precheck_done => unreachable,
1083
1084 .failure,
1085 .dependency_failure,
1086 .skipped_oom,
1087 => break .dependency_failure,
1088
1089 .success, .skipped => {},
1090 }
1091 } else if (Step.make(step_index, maker, step_prog_node)) state: {
1092 break :state .success;
1093 } else |err| switch (err) {
1094 error.MakeFailed => .failure,
1095 error.MakeSkipped => .skipped,
1096 error.Canceled => |e| return e,
1097 };
1098
1099 @atomicStore(Step.State, &make_step.state, new_state, .monotonic);
1100
1101 switch (new_state) {
1102 .precheck_unstarted => unreachable,
1103 .precheck_started => unreachable,
1104 .precheck_done => unreachable,
1105
1106 .failure,
1107 .dependency_failure,
1108 .skipped_oom,
1109 => {
1110 if (maker.web_server) |*ws| ws.updateStepStatus(step_index, .failure);
1111 std.Progress.setStatus(.failure_working);
1112 },
1113
1114 .success,
1115 .skipped,
1116 => {
1117 if (maker.web_server) |*ws| ws.updateStepStatus(step_index, .success);
1118 },
1119 }
1120 }
1121
1122 // No matter the result, we want to display error/warning messages.
1123 if (make_step.result_error_bundle.errorMessageCount() > 0 or
1124 make_step.result_error_msgs.items.len > 0 or
1125 make_step.result_stderr.len > 0)
1126 {
1127 const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode);
1128 defer io.unlockStderr();
1129 printErrorMessages(maker, step_index, .{}, stderr.terminal(), maker.error_style, maker.multiline_errors) catch |err| switch (err) {
1130 error.Canceled => |e| return e,
1131 error.WriteFailed => switch (stderr.file_writer.err.?) {
1132 error.Canceled => |e| return e,
1133 else => {},
1134 },
1135 else => {},
1136 };
1137 }
1138
1139 const max_rss = conf_step.max_rss.toBytes();
1140 if (max_rss != 0) {
1141 var dispatch_set: std.ArrayList(Configuration.Step.Index) = .empty;
1142 defer dispatch_set.deinit(gpa);
1143
1144 // Release our RSS claim and kick off some blocked steps if possible. We use `dispatch_set`
1145 // as a staging buffer to avoid recursing into `makeStep` while `maker.max_rss_mutex` is held.
1146 {
1147 try maker.max_rss_mutex.lock(io);
1148 defer maker.max_rss_mutex.unlock(io);
1149 maker.available_rss += max_rss;
1150 dispatch_set.ensureUnusedCapacity(gpa, maker.memory_blocked_steps.items.len) catch
1151 @panic("TODO eliminate memory allocation here");
1152 while (maker.memory_blocked_steps.getLast()) |candidate_index| {
1153 const candidate_max_rss = candidate_index.ptr(c).max_rss.toBytes();
1154 if (maker.available_rss < candidate_max_rss) break;
1155 assert(maker.memory_blocked_steps.pop() == candidate_index);
1156 dispatch_set.appendAssumeCapacity(candidate_index);
1157 }
1158 }
1159 for (dispatch_set.items) |candidate| {
1160 group.async(io, makeStep, .{ maker, group, candidate, root_prog_node });
1161 }
1162 }
1163
1164 for (make_step.dependants.items) |dependant_index| {
1165 const dependant = maker.stepByIndex(dependant_index);
1166 // `.acq_rel` synchronizes with itself to ensure all dependencies' final states are visible when this hits 0.
1167 if (@atomicRmw(u32, &dependant.pending_deps, .Sub, 1, .acq_rel) == 1) {
1168 try stepReady(maker, group, dependant_index, root_prog_node);
1169 }
1170 }
1171}
1172
1173fn printTreeStep(
1174 maker: *Maker,
1175 step_index: Configuration.Step.Index,
1176 stderr: Io.Terminal,
1177 parent_node: *PrintNode,
1178 step_stack: *std.AutoArrayHashMapUnmanaged(Configuration.Step.Index, void),
1179) !void {
1180 const writer = stderr.writer;
1181 const first = step_stack.swapRemove(step_index);
1182 const summary = maker.summary;
1183 const c = &maker.scanned_config.configuration;
1184 const conf_step = step_index.ptr(c);
1185 const make_step = maker.stepByIndex(step_index);
1186 const skip = switch (summary) {
1187 .none, .line => unreachable,
1188 .all => false,
1189 .new => make_step.result_cached,
1190 .failures => make_step.state == .success,
1191 };
1192 if (skip) return;
1193 try printPrefix(parent_node, stderr);
1194
1195 if (parent_node.parent != null) {
1196 if (parent_node.last) {
1197 try printChildNodePrefix(stderr);
1198 } else {
1199 try writer.writeAll(switch (stderr.mode) {
1200 .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─
1201 else => "+- ",
1202 });
1203 }
1204 }
1205
1206 if (!first) try stderr.setColor(.dim);
1207
1208 // dep_prefix omitted here because it is redundant with the tree.
1209 try writer.writeAll(conf_step.name.slice(c));
1210
1211 const deps = conf_step.deps.slice(c);
1212
1213 if (first) {
1214 try printStepStatus(maker, step_index, stderr);
1215
1216 const last_index = if (summary == .all) deps.len -| 1 else blk: {
1217 var i: usize = deps.len;
1218 while (i > 0) {
1219 i -= 1;
1220
1221 const dep_index = deps[i];
1222 const dep = maker.stepByIndex(dep_index);
1223 const found = switch (summary) {
1224 .all, .line, .none => unreachable,
1225 .failures => dep.state != .success,
1226 .new => !dep.result_cached,
1227 };
1228 if (found) break :blk i;
1229 }
1230 break :blk deps.len -| 1;
1231 };
1232 for (deps, 0..) |dep, i| {
1233 var print_node: PrintNode = .{
1234 .parent = parent_node,
1235 .last = i == last_index,
1236 };
1237 try printTreeStep(maker, dep, stderr, &print_node, step_stack);
1238 }
1239 } else {
1240 if (deps.len == 0) {
1241 try writer.writeAll(" (reused)\n");
1242 } else {
1243 try writer.print(" (+{d} more reused dependencies)\n", .{deps.len});
1244 }
1245 try stderr.setColor(.reset);
1246 }
1247}
1248
1249fn printStepStatus(maker: *Maker, step_index: Configuration.Step.Index, stderr: Io.Terminal) !void {
1250 const s = maker.stepByIndex(step_index);
1251 const writer = stderr.writer;
1252 switch (s.state) {
1253 .precheck_unstarted => unreachable,
1254 .precheck_started => unreachable,
1255 .precheck_done => unreachable,
1256
1257 .dependency_failure => {
1258 try stderr.setColor(.dim);
1259 try writer.writeAll(" transitive failure\n");
1260 try stderr.setColor(.reset);
1261 },
1262
1263 .success => {
1264 try stderr.setColor(.green);
1265 if (s.result_cached) {
1266 try writer.writeAll(" cached");
1267 } else if (s.test_results.test_count > 0) {
1268 const pass_count = s.test_results.passCount();
1269 assert(s.test_results.test_count == pass_count + s.test_results.skip_count);
1270 try writer.print(" {d} pass", .{pass_count});
1271 if (s.test_results.skip_count > 0) {
1272 try stderr.setColor(.reset);
1273 try writer.writeAll(", ");
1274 try stderr.setColor(.yellow);
1275 try writer.print("{d} skip", .{s.test_results.skip_count});
1276 }
1277 try stderr.setColor(.reset);
1278 try writer.print(" ({d} total)", .{s.test_results.test_count});
1279 } else {
1280 try writer.writeAll(" success");
1281 }
1282 try stderr.setColor(.reset);
1283 if (s.result_duration_ns) |ns| {
1284 try stderr.setColor(.dim);
1285 if (ns >= std.time.ns_per_min) {
1286 try writer.print(" {d}m", .{ns / std.time.ns_per_min});
1287 } else if (ns >= std.time.ns_per_s) {
1288 try writer.print(" {d}s", .{ns / std.time.ns_per_s});
1289 } else if (ns >= std.time.ns_per_ms) {
1290 try writer.print(" {d}ms", .{ns / std.time.ns_per_ms});
1291 } else if (ns >= std.time.ns_per_us) {
1292 try writer.print(" {d}us", .{ns / std.time.ns_per_us});
1293 } else {
1294 try writer.print(" {d}ns", .{ns});
1295 }
1296 try stderr.setColor(.reset);
1297 }
1298 if (s.result_peak_rss != 0) {
1299 const rss = s.result_peak_rss;
1300 try stderr.setColor(.dim);
1301 if (rss >= 1000_000_000) {
1302 try writer.print(" MaxRSS:{d}G", .{rss / 1000_000_000});
1303 } else if (rss >= 1000_000) {
1304 try writer.print(" MaxRSS:{d}M", .{rss / 1000_000});
1305 } else if (rss >= 1000) {
1306 try writer.print(" MaxRSS:{d}K", .{rss / 1000});
1307 } else {
1308 try writer.print(" MaxRSS:{d}B", .{rss});
1309 }
1310 try stderr.setColor(.reset);
1311 }
1312 try writer.writeAll("\n");
1313 },
1314 .skipped => {
1315 try stderr.setColor(.yellow);
1316 try writer.writeAll(" skipped\n");
1317 try stderr.setColor(.reset);
1318 },
1319 .skipped_oom => {
1320 const c = &maker.scanned_config.configuration;
1321 const max_rss = step_index.ptr(c).max_rss.toBytes();
1322 try stderr.setColor(.yellow);
1323 try writer.writeAll(" skipped (not enough memory)");
1324 try stderr.setColor(.dim);
1325 try writer.print(" upper bound of {d} exceeded runner limit ({d})\n", .{
1326 max_rss, maker.available_rss,
1327 });
1328 try stderr.setColor(.reset);
1329 },
1330 .failure => {
1331 try printStepFailure(maker, step_index, stderr, false);
1332 try stderr.setColor(.reset);
1333 },
1334 }
1335}
1336
1337fn printStepFailure(
1338 maker: *Maker,
1339 step_index: Configuration.Step.Index,
1340 stderr: Io.Terminal,
1341 dim: bool,
1342) !void {
1343 const w = stderr.writer;
1344 const s = maker.stepByIndex(step_index);
1345 if (s.result_error_bundle.errorMessageCount() > 0) {
1346 try stderr.setColor(.red);
1347 try w.print(" {d} errors\n", .{
1348 s.result_error_bundle.errorMessageCount(),
1349 });
1350 } else if (!s.test_results.isSuccess()) {
1351 // These first values include all of the test "statuses". Every test is either passsed,
1352 // skipped, failed, crashed, or timed out.
1353 try stderr.setColor(.green);
1354 try w.print(" {d} pass", .{s.test_results.passCount()});
1355 try stderr.setColor(.reset);
1356 if (dim) try stderr.setColor(.dim);
1357 if (s.test_results.skip_count > 0) {
1358 try w.writeAll(", ");
1359 try stderr.setColor(.yellow);
1360 try w.print("{d} skip", .{s.test_results.skip_count});
1361 try stderr.setColor(.reset);
1362 if (dim) try stderr.setColor(.dim);
1363 }
1364 if (s.test_results.fail_count > 0) {
1365 try w.writeAll(", ");
1366 try stderr.setColor(.red);
1367 try w.print("{d} fail", .{s.test_results.fail_count});
1368 try stderr.setColor(.reset);
1369 if (dim) try stderr.setColor(.dim);
1370 }
1371 if (s.test_results.crash_count > 0) {
1372 try w.writeAll(", ");
1373 try stderr.setColor(.red);
1374 try w.print("{d} crash", .{s.test_results.crash_count});
1375 try stderr.setColor(.reset);
1376 if (dim) try stderr.setColor(.dim);
1377 }
1378 if (s.test_results.timeout_count > 0) {
1379 try w.writeAll(", ");
1380 try stderr.setColor(.red);
1381 try w.print("{d} timeout", .{s.test_results.timeout_count});
1382 try stderr.setColor(.reset);
1383 if (dim) try stderr.setColor(.dim);
1384 }
1385 try w.print(" ({d} total)", .{s.test_results.test_count});
1386
1387 // Memory leaks are intentionally written after the total, because is isn't a test *status*,
1388 // but just a flag that any tests -- even passed ones -- can have. We also use a different
1389 // separator, so it looks like:
1390 // 2 pass, 1 skip, 2 fail (5 total); 2 leaks
1391 if (s.test_results.leak_count > 0) {
1392 try w.writeAll("; ");
1393 try stderr.setColor(.red);
1394 try w.print("{d} leaks", .{s.test_results.leak_count});
1395 try stderr.setColor(.reset);
1396 if (dim) try stderr.setColor(.dim);
1397 }
1398
1399 // It's usually not helpful to know how many error logs there were because they tend to
1400 // just come with other errors (e.g. crashes and leaks print stack traces, and clean
1401 // failures print error traces). So only mention them if they're the only thing causing
1402 // the failure.
1403 const show_err_logs: bool = show: {
1404 var alt_results = s.test_results;
1405 alt_results.log_err_count = 0;
1406 break :show alt_results.isSuccess();
1407 };
1408 if (show_err_logs) {
1409 try w.writeAll("; ");
1410 try stderr.setColor(.red);
1411 try w.print("{d} error logs", .{s.test_results.log_err_count});
1412 try stderr.setColor(.reset);
1413 if (dim) try stderr.setColor(.dim);
1414 }
1415
1416 try w.writeAll("\n");
1417 } else if (s.result_error_msgs.items.len > 0) {
1418 try stderr.setColor(.red);
1419 try w.writeAll(" failure\n");
1420 } else {
1421 assert(s.result_stderr.len > 0);
1422 try stderr.setColor(.red);
1423 try w.writeAll(" w\n");
1424 }
1425}
1426
1427const PrintNode = struct {
1428 parent: ?*PrintNode,
1429 last: bool = false,
1430};
1431
1432fn printPrefix(node: *PrintNode, stderr: Io.Terminal) !void {
1433 const parent = node.parent orelse return;
1434 const writer = stderr.writer;
1435 if (parent.parent == null) return;
1436 try printPrefix(parent, stderr);
1437 if (parent.last) {
1438 try writer.writeAll(" ");
1439 } else {
1440 try writer.writeAll(switch (stderr.mode) {
1441 .escape_codes => "\x1B\x28\x30\x78\x1B\x28\x42 ", // │
1442 else => "| ",
1443 });
1444 }
1445}
1446
1447fn printChildNodePrefix(stderr: Io.Terminal) !void {
1448 try stderr.writer.writeAll(switch (stderr.mode) {
1449 .escape_codes => "\x1B\x28\x30\x6d\x71\x1B\x28\x42 ", // └─
1450 else => "+- ",
1451 });
1452}
1453
1454/// Traverse the dependency graph depth-first and make it undirected by having
1455/// steps know their dependants (they only know dependencies at start).
1456/// Along the way, check that there is no dependency loop, and record the steps
1457/// in traversal order in `step_stack`.
1458/// Each step has its dependencies traversed in random order, this accomplishes
1459/// two things:
1460/// - `step_stack` will be in randomized-depth-first order, so the build runner
1461/// spawns initial steps in a random order
1462/// - each step's `dependants` list is also filled in a random order, so that
1463/// when it finishes executing in `makeStep`, it spawns next steps to run in
1464/// random order
1465fn constructGraphAndCheckForDependencyLoop(
1466 maker: *Maker,
1467 step_index: Configuration.Step.Index,
1468 step_stack: *std.AutoArrayHashMapUnmanaged(Configuration.Step.Index, void),
1469 rand: std.Random,
1470) error{ DependencyLoopDetected, OutOfMemory }!void {
1471 const c = &maker.scanned_config.configuration;
1472 const gpa = maker.gpa;
1473 const arena = maker.graph.arena;
1474 const make_step = maker.stepByIndex(step_index);
1475 switch (make_step.state) {
1476 .precheck_started => {
1477 log.err("dependency loop detected: {s}", .{step_index.ptr(c).name.slice(c)});
1478 return error.DependencyLoopDetected;
1479 },
1480 .precheck_unstarted => {
1481 make_step.state = .precheck_started;
1482
1483 const step = step_index.ptr(c);
1484 const dependencies = step.deps.slice(c);
1485 try step_stack.ensureUnusedCapacity(gpa, dependencies.len);
1486
1487 // We dupe to avoid shuffling the steps in the summary, it depends
1488 // on dependencies' order.
1489 const deps = try gpa.dupe(Configuration.Step.Index, dependencies);
1490 defer gpa.free(deps);
1491
1492 rand.shuffle(Configuration.Step.Index, deps);
1493
1494 for (deps) |dep| {
1495 const dep_step = maker.stepByIndex(dep);
1496 try step_stack.put(gpa, dep, {});
1497 try dep_step.dependants.append(arena, step_index);
1498 constructGraphAndCheckForDependencyLoop(maker, dep, step_stack, rand) catch |err| switch (err) {
1499 error.DependencyLoopDetected => {
1500 log.info("needed by: {s}", .{step_index.ptr(c).name.slice(c)});
1501 return err;
1502 },
1503 else => return err,
1504 };
1505 }
1506
1507 make_step.state = .precheck_done;
1508 make_step.pending_deps = @intCast(dependencies.len);
1509 },
1510 .precheck_done => {},
1511
1512 // These don't happen until we actually run the step graph.
1513 .dependency_failure => unreachable,
1514 .success => unreachable,
1515 .failure => unreachable,
1516 .skipped => unreachable,
1517 .skipped_oom => unreachable,
1518 }
1519}
1520
1521/// When file watching, prepares the step for being re-evaluated. Returns
1522/// `true` if the step was newly invalidated, `false` if it was already
1523/// invalidated.
1524pub fn invalidateResult(maker: *Maker, step: *Step) bool {
1525 if (step.state == .precheck_done) return false;
1526 assert(step.pending_deps == 0);
1527 step.state = .precheck_done;
1528 step.reset(maker);
1529 for (step.dependants.items) |dependant_index| {
1530 const dependant = maker.stepByIndex(dependant_index);
1531 _ = invalidateResult(maker, dependant);
1532 dependant.pending_deps += 1;
1533 }
1534 return true;
1535}
1536
1537pub fn printErrorMessages(
1538 maker: *Maker,
1539 failing_step_index: Configuration.Step.Index,
1540 options: std.zig.ErrorBundle.RenderOptions,
1541 stderr: Io.Terminal,
1542 error_style: ErrorStyle,
1543 multiline_errors: MultilineErrors,
1544) !void {
1545 const c = &maker.scanned_config.configuration;
1546 const gpa = maker.gpa;
1547 const writer = stderr.writer;
1548 if (error_style.verboseContext()) {
1549 // Provide context for where these error messages are coming from by
1550 // printing the corresponding Step subtree.
1551 var step_stack: std.ArrayList(Configuration.Step.Index) = .empty;
1552 defer step_stack.deinit(gpa);
1553 try step_stack.append(gpa, failing_step_index);
1554 while (true) {
1555 const last_step = maker.stepByIndex(step_stack.items[step_stack.items.len - 1]);
1556 if (last_step.dependants.items.len == 0) break;
1557 try step_stack.append(gpa, last_step.dependants.items[0]);
1558 }
1559
1560 // Now, `step_stack` has the subtree that we want to print, in reverse order.
1561 try stderr.setColor(.dim);
1562 var indent: usize = 0;
1563 while (step_stack.pop()) |step_index| : (indent += 1) {
1564 if (indent > 0) {
1565 try writer.splatByteAll(' ', (indent - 1) * 3);
1566 try printChildNodePrefix(stderr);
1567 }
1568
1569 try writer.writeAll(step_index.ptr(c).name.slice(c));
1570
1571 if (step_index == failing_step_index) {
1572 try printStepFailure(maker, step_index, stderr, true);
1573 } else {
1574 try writer.writeAll("\n");
1575 }
1576 }
1577 try stderr.setColor(.reset);
1578 } else {
1579 // Just print the failing step itself.
1580 try stderr.setColor(.dim);
1581 try writer.writeAll(failing_step_index.ptr(c).name.slice(c));
1582 try printStepFailure(maker, failing_step_index, stderr, true);
1583 try stderr.setColor(.reset);
1584 }
1585
1586 const failing_step = maker.stepByIndex(failing_step_index);
1587
1588 if (failing_step.result_stderr.len > 0) {
1589 try writer.writeAll(failing_step.result_stderr);
1590 if (!mem.endsWith(u8, failing_step.result_stderr, "\n")) {
1591 try writer.writeAll("\n");
1592 }
1593 }
1594
1595 try failing_step.result_error_bundle.renderToTerminal(options, stderr);
1596
1597 for (failing_step.result_error_msgs.items) |msg| {
1598 try stderr.setColor(.red);
1599 try writer.writeAll("error:");
1600 try stderr.setColor(.reset);
1601 if (std.mem.indexOfScalar(u8, msg, '\n') == null) {
1602 try writer.print(" {s}\n", .{msg});
1603 } else switch (multiline_errors) {
1604 .indent => {
1605 var it = std.mem.splitScalar(u8, msg, '\n');
1606 try writer.print(" {s}\n", .{it.first()});
1607 while (it.next()) |line| {
1608 try writer.print(" {s}\n", .{line});
1609 }
1610 },
1611 .newline => try writer.print("\n{s}\n", .{msg}),
1612 .none => try writer.print(" {s}\n", .{msg}),
1613 }
1614 }
1615
1616 if (error_style.verboseContext()) {
1617 if (failing_step.result_failed_command) |cmd_str| {
1618 try stderr.setColor(.red);
1619 try writer.writeAll("failed command: ");
1620 try stderr.setColor(.reset);
1621 try writer.writeAll(cmd_str);
1622 try writer.writeByte('\n');
1623 }
1624 }
1625
1626 if (failing_step.result_oom) {
1627 try stderr.setColor(.red);
1628 try writer.writeAll("error information missing due to allocation failure");
1629 try stderr.setColor(.reset);
1630 try writer.writeByte('\n');
1631 }
1632
1633 try writer.writeByte('\n');
1634}
1635
1636fn nextArg(args: []const [:0]const u8, idx: *usize) ?[:0]const u8 {
1637 if (idx.* >= args.len) return null;
1638 defer idx.* += 1;
1639 return args[idx.*];
1640}
1641
1642fn nextArgOrFatal(args: []const [:0]const u8, idx: *usize) [:0]const u8 {
1643 return nextArg(args, idx) orelse {
1644 fatalWithHint("expected argument after {q}", .{args[idx.* - 1]});
1645 };
1646}
1647
1648fn expectArgOrFatal(args: []const [:0]const u8, index_ptr: *usize, first: []const u8) []const u8 {
1649 const next_arg = nextArg(args, index_ptr) orelse fatal("missing {q} argument", .{first});
1650 if (!mem.eql(u8, first, next_arg)) fatal("expected {q} instead of {q}", .{ first, next_arg });
1651 const arg = nextArg(args, index_ptr) orelse fatal("expected argument after {q}", .{first});
1652 return arg;
1653}
1654
1655fn argsRest(args: []const [:0]const u8, idx: usize) ?[]const [:0]const u8 {
1656 if (idx >= args.len) return null;
1657 return args[idx..];
1658}
1659
1660const Color = std.zig.Color;
1661const ErrorStyle = enum {
1662 verbose,
1663 minimal,
1664 verbose_clear,
1665 minimal_clear,
1666 fn verboseContext(s: ErrorStyle) bool {
1667 return switch (s) {
1668 .verbose, .verbose_clear => true,
1669 .minimal, .minimal_clear => false,
1670 };
1671 }
1672 fn clearOnUpdate(s: ErrorStyle) bool {
1673 return switch (s) {
1674 .verbose, .minimal => false,
1675 .verbose_clear, .minimal_clear => true,
1676 };
1677 }
1678};
1679const MultilineErrors = enum { indent, newline, none };
1680const Summary = enum { all, new, failures, line, none };
1681
1682fn fatalWithHint(comptime f: []const u8, args: anytype) noreturn {
1683 log.info("to access the help menu: zig build -h", .{});
1684 fatal(f, args);
1685}
1686
1687fn cleanTmpFiles(maker: *Maker, steps: []const Configuration.Step.Index) void {
1688 const graph = maker.graph;
1689 const io = graph.io;
1690 const conf = &maker.scanned_config.configuration;
1691
1692 for (steps) |step_index| {
1693 const conf_step = step_index.ptr(conf);
1694 const wf = conf_step.extended.cast(conf, Configuration.Step.WriteFile) orelse continue;
1695 if (wf.flags.mode != .tmp) continue;
1696 const step = maker.stepByIndex(step_index);
1697 if (step.state != .success) continue;
1698 const tmp_path = generatedPath(maker, wf.generated_directory).*;
1699 tmp_path.root_dir.handle.deleteTree(io, tmp_path.subPathOrDot()) catch |err|
1700 log.warn("failed to delete temporary path {f}: {t}", .{ tmp_path, err });
1701 }
1702}
1703
1704var stdio_buffer_allocation: [256]u8 = undefined;
1705var stdout_writer_allocation: Io.File.Writer = undefined;
1706
1707fn initStdoutWriter(io: Io) *Writer {
1708 stdout_writer_allocation = Io.File.stdout().writerStreaming(io, &stdio_buffer_allocation);
1709 return &stdout_writer_allocation.interface;
1710}
1711
1712/// `asking_step` is only used for debugging purposes; it's the step being run
1713/// that is asking for the path.
1714pub fn resolveLazyPath(
1715 maker: *const Maker,
1716 arena: Allocator,
1717 lazy_path: Configuration.LazyPath,
1718 asking_step_index: Configuration.Step.Index,
1719) error{ OutOfMemory, MakeFailed }!Path {
1720 const c = &maker.scanned_config.configuration;
1721 return switch (lazy_path) {
1722 .source_path => |sp| try packagePath(maker, arena, sp.owner, sp.sub_path.slice(c)),
1723 .relative => |relative| relativePath(maker, arena, relative),
1724 .generated => |gen| {
1725 const base = generatedPath(maker, gen.index).*;
1726 var file_path = base;
1727 for (0..gen.flags.up) |_| {
1728 file_path.sub_path = Dir.path.dirname(file_path.sub_path) orelse {
1729 const s = stepByIndex(maker, asking_step_index);
1730 return s.fail(maker, "invalid LazyPath traversal: up {d} times from {f}", .{
1731 gen.flags.up, base,
1732 });
1733 };
1734 }
1735 return file_path.join(arena, gen.sub_path.slice(c));
1736 },
1737 };
1738}
1739
1740pub fn resolveLazyPathIndex(
1741 maker: *const Maker,
1742 arena: Allocator,
1743 lazy_path_index: Configuration.LazyPath.Index,
1744 asking_step_index: Configuration.Step.Index,
1745) error{ OutOfMemory, MakeFailed }!Path {
1746 const c = &maker.scanned_config.configuration;
1747 return resolveLazyPath(maker, arena, lazy_path_index.get(c), asking_step_index);
1748}
1749
1750/// `resolveLazyPath` is preferred, but this can be necessary when passing Path
1751/// objects to child processes.
1752pub fn resolveLazyPathAbs(
1753 maker: *const Maker,
1754 arena: Allocator,
1755 lazy_path: Configuration.LazyPath,
1756 asking_step_index: Configuration.Step.Index,
1757) error{ OutOfMemory, MakeFailed }![]const u8 {
1758 const p = try resolveLazyPath(maker, arena, lazy_path, asking_step_index);
1759 const root_dir_path = p.root_dir.path orelse return p.subPathOrDot();
1760 if (p.sub_path.len == 0) return root_dir_path;
1761 return Dir.path.join(arena, &.{ root_dir_path, p.sub_path });
1762}
1763
1764/// `resolveLazyPath` is preferred, but this can be necessary when passing Path
1765/// objects to child processes.
1766pub fn resolveLazyPathIndexAbs(
1767 maker: *const Maker,
1768 arena: Allocator,
1769 lazy_path_index: Configuration.LazyPath.Index,
1770 asking_step_index: Configuration.Step.Index,
1771) error{ OutOfMemory, MakeFailed }![]const u8 {
1772 const c = &maker.scanned_config.configuration;
1773 return resolveLazyPathAbs(maker, arena, lazy_path_index.get(c), asking_step_index);
1774}
1775
1776pub fn generatedPath(maker: *const Maker, index: Configuration.GeneratedFileIndex) *Path {
1777 return &maker.generated_files[@intFromEnum(index)];
1778}
1779
1780pub fn packagePath(
1781 maker: *const Maker,
1782 arena: Allocator,
1783 package_index: Configuration.Package.Index,
1784 sub_path: []const u8,
1785) Allocator.Error!Path {
1786 const c = &maker.scanned_config.configuration;
1787 const graph = maker.graph;
1788 const package = package_index.get(c) orelse return .{
1789 .root_dir = graph.build_root_directory,
1790 .sub_path = sub_path,
1791 };
1792 // Currently, neither configurer nor Maker is aware of the standard zig
1793 // package path, and the root path is stored as a bare string rather than
1794 // relative to a known base directory. Without changing that, we must
1795 // construct a cwd relative path here.
1796 return .{
1797 .root_dir = .cwd(),
1798 .sub_path = try Dir.path.join(arena, &.{ package.root_path.slice(c), sub_path }),
1799 };
1800}
1801
1802pub fn relativePath(maker: *const Maker, arena: Allocator, relative: Configuration.LazyPath.Relative) Allocator.Error!Path {
1803 const graph = maker.graph;
1804 const c = &maker.scanned_config.configuration;
1805 const sub_path = relative.sub_path.slice(c);
1806 return switch (relative.flags.base) {
1807 .cwd => .{
1808 .root_dir = .cwd(),
1809 .sub_path = sub_path,
1810 },
1811 .local_cache => .{
1812 .root_dir = graph.local_cache_root,
1813 .sub_path = sub_path,
1814 },
1815 .global_cache => .{
1816 .root_dir = graph.global_cache_root,
1817 .sub_path = sub_path,
1818 },
1819 .build_root => .{
1820 .root_dir = graph.build_root_directory,
1821 .sub_path = sub_path,
1822 },
1823 .zig_exe => .{
1824 .root_dir = .cwd(),
1825 .sub_path = if (sub_path.len == 0)
1826 graph.zig_exe
1827 else
1828 try Io.Dir.path.join(arena, &.{ graph.zig_exe, sub_path }),
1829 },
1830 .zig_lib => .{
1831 .root_dir = graph.zig_lib_directory,
1832 .sub_path = sub_path,
1833 },
1834 .install_prefix => maker.install_paths.prefix,
1835 .install_lib => maker.install_paths.lib,
1836 .install_bin => maker.install_paths.bin,
1837 .install_include => maker.install_paths.include,
1838 };
1839}
1840
1841pub fn resolveInstallDir(
1842 maker: *Maker,
1843 arena: Allocator,
1844 dest_dir: Configuration.InstallDestDir,
1845) Allocator.Error!Path {
1846 const c = &maker.scanned_config.configuration;
1847 return switch (dest_dir.unpack().?) {
1848 .prefix => maker.install_paths.prefix,
1849 .lib => maker.install_paths.lib,
1850 .bin => maker.install_paths.bin,
1851 .header => maker.install_paths.include,
1852 .sub_path => |s| try maker.install_paths.prefix.join(arena, s.slice(c)),
1853 };
1854}
1855
1856pub fn installLazyPathSub(
1857 maker: *Maker,
1858 arena: Allocator,
1859 source: Configuration.LazyPath.Index,
1860 dest_dir: Configuration.InstallDestDir,
1861 sub_path: []const u8,
1862 asking_step_index: Configuration.Step.Index,
1863) !Dir.PrevStatus {
1864 const src_path = try resolveLazyPathIndex(maker, arena, source, asking_step_index);
1865 const dest_dir_path = try resolveInstallDir(maker, arena, dest_dir);
1866 const dest_path = try dest_dir_path.join(arena, sub_path);
1867 return installPath(maker, arena, src_path, dest_path, asking_step_index);
1868}
1869
1870pub fn installLazyPath(
1871 maker: *Maker,
1872 arena: Allocator,
1873 source: Configuration.LazyPath.Index,
1874 dest_dir: Configuration.InstallDestDir,
1875 asking_step_index: Configuration.Step.Index,
1876) !Dir.PrevStatus {
1877 const src_path = try resolveLazyPathIndex(maker, arena, source, asking_step_index);
1878 const dest_dir_path = try resolveInstallDir(maker, arena, dest_dir);
1879 const dest_path = try dest_dir_path.join(arena, src_path.basename());
1880 return installPath(maker, arena, src_path, dest_path, asking_step_index);
1881}
1882
1883pub fn installGenerated(
1884 maker: *Maker,
1885 arena: Allocator,
1886 source: Configuration.GeneratedFileIndex,
1887 dest_dir: Configuration.InstallDestDir,
1888 asking_step_index: Configuration.Step.Index,
1889) !Dir.PrevStatus {
1890 const src_path = generatedPath(maker, source).*;
1891 const dest_dir_path = try resolveInstallDir(maker, arena, dest_dir);
1892 const dest_path = try dest_dir_path.join(arena, src_path.basename());
1893 return installPath(maker, arena, src_path, dest_path, asking_step_index);
1894}
1895
1896pub fn truncatePath(
1897 maker: *Maker,
1898 arena: Allocator,
1899 dest_path: Path,
1900 asking_step_index: Configuration.Step.Index,
1901) Step.ExtendedMakeError!void {
1902 const graph = maker.graph;
1903 const io = graph.io;
1904 if (graph.verbose) try graph.handleVerbose(null, null, &.{
1905 "truncate", try dest_path.toString(arena),
1906 });
1907 const err = e: {
1908 var file = f: {
1909 break :f dest_path.root_dir.handle.createFile(io, dest_path.sub_path, .{}) catch |err| switch (err) {
1910 error.FileNotFound => {
1911 const parent_path = dest_path.dirname() orelse break :e err;
1912 parent_path.root_dir.handle.createDirPath(io, parent_path.sub_path) catch |in| switch (in) {
1913 error.Canceled => |e| return e,
1914 else => |e| {
1915 const s = stepByIndex(maker, asking_step_index);
1916 return s.fail(maker, "failed creating directory {f}: {t}", .{ parent_path, e });
1917 },
1918 };
1919 break :f dest_path.root_dir.handle.createFile(io, dest_path.sub_path, .{}) catch |in| break :e in;
1920 },
1921 error.Canceled => |e| return e,
1922 else => |e| break :e e,
1923 };
1924 };
1925 file.close(io);
1926 return;
1927 };
1928 const s = stepByIndex(maker, asking_step_index);
1929 return s.fail(maker, "failed truncating file {f}: {t}", .{ dest_path, err });
1930}
1931
1932pub fn installPath(
1933 maker: *Maker,
1934 arena: Allocator,
1935 src_path: Path,
1936 dest_path: Path,
1937 asking_step_index: Configuration.Step.Index,
1938) Step.ExtendedMakeError!Dir.PrevStatus {
1939 const graph = maker.graph;
1940 const io = graph.io;
1941 if (graph.verbose) try graph.handleVerbose(null, null, &.{
1942 "install", "-C", try src_path.toString(arena), try dest_path.toString(arena),
1943 });
1944 return Dir.updateFile(
1945 src_path.root_dir.handle,
1946 io,
1947 src_path.sub_path,
1948 dest_path.root_dir.handle,
1949 dest_path.sub_path,
1950 .{},
1951 ) catch |err| {
1952 const s = stepByIndex(maker, asking_step_index);
1953 return s.fail(maker, "failed updating file from {f} to {f}: {t}", .{ src_path, dest_path, err });
1954 };
1955}
1956
1957/// Wrapper around `Dir.createDirPathStatus` that handles verbose and error output.
1958pub fn installDir(
1959 maker: *Maker,
1960 arena: Allocator,
1961 dest_path: Path,
1962 asking_step_index: Configuration.Step.Index,
1963) Step.ExtendedMakeError!Dir.CreatePathStatus {
1964 const graph = maker.graph;
1965 const io = graph.io;
1966 if (graph.verbose) try graph.handleVerbose(null, null, &.{
1967 "install", "-d", try dest_path.toString(arena),
1968 });
1969 return dest_path.root_dir.handle.createDirPathStatus(io, dest_path.sub_path, .default_dir) catch |err| {
1970 const s = stepByIndex(maker, asking_step_index);
1971 return s.fail(maker, "failed creating dir {f}: {t}", .{ dest_path, err });
1972 };
1973}
1974
1975pub fn installSymLinks(
1976 maker: *Maker,
1977 arena: Allocator,
1978 output_path: Path,
1979 compile_step_index: Configuration.Step.Index,
1980 asking_step_index: Configuration.Step.Index,
1981) !void {
1982 const c = &maker.scanned_config.configuration;
1983 const conf_step = compile_step_index.ptr(c);
1984 const conf_comp = conf_step.extended.get(c.extra).compile;
1985 const root_module = conf_comp.root_module.get(c);
1986 const target = root_module.resolved_target.get(c).?.result.get(c);
1987 const os_tag = target.flags.os_tag.unwrap().?;
1988
1989 assert(conf_comp.flags3.kind == .lib);
1990 assert(conf_comp.flags2.linkage == .dynamic);
1991 assert(os_tag != .windows);
1992
1993 const version = std.SemanticVersion.parse(conf_comp.version.value.?.slice(c)) catch unreachable;
1994 const name = conf_comp.root_name.slice(c);
1995
1996 const filename_major_only, const filename_name_only = if (os_tag.isDarwin()) .{
1997 try std.fmt.allocPrint(arena, "lib{s}.{d}.dylib", .{ name, version.major }),
1998 try std.fmt.allocPrint(arena, "lib{s}.dylib", .{name}),
1999 } else .{
2000 try std.fmt.allocPrint(arena, "lib{s}.so.{d}", .{ name, version.major }),
2001 try std.fmt.allocPrint(arena, "lib{s}.so", .{name}),
2002 };
2003
2004 return installSymLinksInner(maker, arena, output_path, asking_step_index, filename_major_only, filename_name_only);
2005}
2006
2007fn installSymLinksInner(
2008 maker: *Maker,
2009 arena: Allocator,
2010 output_path: Path,
2011 asking_step_index: Configuration.Step.Index,
2012 filename_major_only: []const u8,
2013 filename_name_only: []const u8,
2014) !void {
2015 const io = maker.graph.io;
2016 const step = stepByIndex(maker, asking_step_index);
2017 const out_basename = Io.Dir.path.basename(output_path.sub_path);
2018
2019 const out_dir = output_path.dirname().?;
2020 const major_only_path = try out_dir.join(arena, filename_major_only);
2021 const name_only_path = try out_dir.join(arena, filename_name_only);
2022
2023 // libfoo.so.1 to libfoo.so.1.2.3
2024 major_only_path.root_dir.handle.symLinkAtomic(io, out_basename, major_only_path.sub_path, .{}) catch |err|
2025 return step.fail(maker, "failed symlinking {f} to {s}: {t}", .{ output_path, out_basename, err });
2026
2027 // libfoo.so to libfoo.so.1
2028 name_only_path.root_dir.handle.symLinkAtomic(io, filename_major_only, name_only_path.sub_path, .{}) catch |err|
2029 return step.fail(maker, "failed symlinking {f} to {s}: {t}", .{ name_only_path, filename_major_only, err });
2030}
2031
2032fn cleanExit(io: Io, scanned_config: *const ScannedConfig) void {
2033 removePoisonedConfiguration(io, scanned_config);
2034 return process.cleanExit(io);
2035}
2036
2037fn removePoisonedConfiguration(io: Io, scanned_config: *const ScannedConfig) void {
2038 if (scanned_config.configuration.poisoned) {
2039 // This configuration file was good for only 1 invocation of the maker
2040 // process. Delete it to save space on disk.
2041 Io.Dir.cwd().deleteFile(io, scanned_config.path) catch |err|
2042 log.warn("failed deleting poisoned configuration file {s}: {t}", .{ scanned_config.path, err });
2043 }
2044}
2045
2046const is_debug_mode = builtin.mode == .Debug;
2047var debug_maker_leaks: bool = false;
2048inline fn debugMakerLeaks() bool {
2049 if (!is_debug_mode) return false;
2050 return debug_maker_leaks;
2051}
lib/compiler/Maker/Fuzz.zig created+685
......@@ -0,0 +1,685 @@
1const Fuzz = @This();
2
3const std = @import("std");
4const Allocator = std.mem.Allocator;
5const Build = std.Build;
6const Cache = std.Build.Cache;
7const Coverage = std.debug.Coverage;
8const Configuration = std.Build.Configuration;
9const Io = std.Io;
10const abi = std.Build.abi.fuzz;
11const assert = std.debug.assert;
12const fatal = std.process.fatal;
13const log = std.log;
14
15const Maker = @import("../Maker.zig");
16const WebServer = @import("WebServer.zig");
17
18maker: *Maker,
19mode: Mode,
20
21/// Allocated into `gpa`.
22run_steps: []const Configuration.Step.Index,
23
24group: Io.Group,
25root_prog_node: std.Progress.Node,
26prog_node: std.Progress.Node,
27
28/// Protects `coverage_files`.
29coverage_mutex: Io.Mutex,
30coverage_files: std.AutoArrayHashMapUnmanaged(u64, CoverageMap),
31
32queue_mutex: Io.Mutex,
33queue_cond: Io.Condition,
34msg_queue: std.ArrayList(Msg),
35
36pub const Mode = union(enum) {
37 forever: struct { ws: *WebServer },
38 limit: Limited,
39
40 pub const Limited = struct {
41 amount: u64,
42 };
43};
44
45const Msg = union(enum) {
46 coverage: struct {
47 id: u64,
48 cumulative: struct {
49 runs: u64,
50 unique: u64,
51 coverage: u64,
52 },
53 run: Configuration.Step.Index,
54 },
55 entry_point: struct {
56 coverage_id: u64,
57 addr: u64,
58 },
59};
60
61const CoverageMap = struct {
62 mapped_memory: []align(std.heap.page_size_min) const u8,
63 coverage: Coverage,
64 source_locations: []Coverage.SourceLocation,
65 /// Elements are indexes into `source_locations` pointing to the unit tests that are being fuzz tested.
66 entry_points: std.ArrayList(u32),
67 start_timestamp: i64,
68 start_n_runs: u64,
69
70 fn deinit(cm: *CoverageMap, gpa: Allocator) void {
71 std.posix.munmap(cm.mapped_memory);
72 cm.coverage.deinit(gpa);
73 cm.* = undefined;
74 }
75};
76
77pub fn init(
78 maker: *Maker,
79 all_steps: []const Configuration.Step.Index,
80 root_prog_node: std.Progress.Node,
81 mode: Mode,
82) error{ OutOfMemory, Canceled }!Fuzz {
83 const graph = maker.graph;
84 const gpa = graph.cache.gpa;
85 const io = graph.io;
86 const conf = &maker.scanned_config.configuration;
87
88 const run_steps: []const Configuration.Step.Index = steps: {
89 var steps: std.ArrayList(Configuration.Step.Index) = .empty;
90 defer steps.deinit(gpa);
91 const rebuild_node = root_prog_node.start("Rebuilding Unit Tests", 0);
92 defer rebuild_node.end();
93 var rebuild_group: Io.Group = .init;
94 defer rebuild_group.cancel(io);
95
96 for (all_steps) |step_index| {
97 const conf_run = step_index.ptr(conf).extended.cast(conf, Configuration.Step.Run) orelse continue;
98 if (conf_run.producer.value == null) continue;
99 const run = &maker.stepByIndex(step_index).extended.run;
100 if (run.fuzz_tests.items.len == 0) continue;
101 try steps.append(gpa, step_index);
102 rebuild_group.async(io, rebuildTestsWorkerRun, .{ maker, step_index, rebuild_node });
103 }
104
105 if (steps.items.len == 0) fatal("no fuzz tests found", .{});
106 rebuild_node.setEstimatedTotalItems(steps.items.len);
107 const run_steps = try gpa.dupe(Configuration.Step.Index, steps.items);
108 try rebuild_group.await(io);
109 break :steps run_steps;
110 };
111 errdefer gpa.free(run_steps);
112
113 for (run_steps) |run_index| {
114 const run = &maker.stepByIndex(run_index).extended.run;
115 assert(run.fuzz_tests.items.len > 0);
116 if (run.rebuilt_executable == null)
117 fatal("one or more unit tests failed to be rebuilt in fuzz mode", .{});
118 }
119
120 return .{
121 .maker = maker,
122 .mode = mode,
123 .run_steps = run_steps,
124 .group = .init,
125 .root_prog_node = root_prog_node,
126 .prog_node = .none,
127 .coverage_files = .empty,
128 .coverage_mutex = .init,
129 .queue_mutex = .init,
130 .queue_cond = .init,
131 .msg_queue = .empty,
132 };
133}
134
135pub fn start(fuzz: *Fuzz) void {
136 const maker = fuzz.maker;
137 const graph = maker.graph;
138 const io = graph.io;
139
140 fuzz.prog_node = fuzz.root_prog_node.start("Fuzzing", 0);
141
142 if (fuzz.mode == .forever) {
143 // For polling messages and sending updates to subscribers.
144 fuzz.group.concurrent(io, coverageRun, .{fuzz}) catch |err|
145 fatal("unable to spawn coverage task: {t}", .{err});
146 }
147
148 for (fuzz.run_steps) |run_index| {
149 const run = &maker.stepByIndex(run_index).extended.run;
150 assert(run.rebuilt_executable != null);
151 fuzz.group.async(io, fuzzWorkerRun, .{ fuzz, run_index });
152 }
153}
154
155pub fn deinit(fuzz: *Fuzz) void {
156 const maker = fuzz.maker;
157 const graph = maker.graph;
158 const io = graph.io;
159 const gpa = maker.gpa;
160
161 fuzz.group.cancel(io);
162 fuzz.prog_node.end();
163 gpa.free(fuzz.run_steps);
164}
165
166fn rebuildTestsWorkerRun(
167 maker: *Maker,
168 run_index: Configuration.Step.Index,
169 parent_prog_node: std.Progress.Node,
170) void {
171 rebuildTestsWorkerRunFallible(maker, run_index, parent_prog_node) catch |err| {
172 const conf = &maker.scanned_config.configuration;
173 const conf_run = run_index.ptr(conf).extended.cast(conf, Configuration.Step.Run).?;
174 const comp_index = conf_run.producer.value.?;
175 const step_name = comp_index.ptr(conf).name.slice(conf);
176 log.err("step {s}: failed to rebuild in fuzz mode: {t}", .{ step_name, err });
177 };
178}
179
180fn rebuildTestsWorkerRunFallible(
181 maker: *Maker,
182 run_index: Configuration.Step.Index,
183 parent_prog_node: std.Progress.Node,
184) !void {
185 const graph = maker.graph;
186 const io = graph.io;
187 const gpa = maker.gpa;
188 const conf = &maker.scanned_config.configuration;
189 const run = &maker.stepByIndex(run_index).extended.run;
190 const conf_run = run_index.ptr(conf).extended.cast(conf, Configuration.Step.Run).?;
191 const comp_index = conf_run.producer.value.?;
192 const comp_step = maker.stepByIndex(comp_index);
193 const comp = &comp_step.extended.compile;
194 const conf_comp_step = comp_index.ptr(conf);
195 const conf_comp = conf_comp_step.extended.cast(conf, Configuration.Step.Compile).?;
196 const root_module = conf_comp.root_module.get(conf);
197 const target = root_module.resolved_target.get(conf).?.result.get(conf);
198
199 const prog_node = parent_prog_node.start(conf_comp_step.name.slice(conf), 0);
200 defer prog_node.end();
201
202 const result = comp.rebuildInFuzzMode(maker, comp_index, prog_node);
203
204 const show_compile_errors = comp_step.result_error_bundle.errorMessageCount() > 0;
205 const show_error_msgs = comp_step.result_error_msgs.items.len > 0;
206 const show_stderr = comp_step.result_stderr.len > 0;
207
208 if (show_error_msgs or show_compile_errors or show_stderr) {
209 var buf: [256]u8 = undefined;
210 const stderr = try io.lockStderr(&buf, graph.stderr_mode);
211 defer io.unlockStderr();
212 maker.printErrorMessages(comp_index, .{}, stderr.terminal(), .verbose, .indent) catch {};
213 }
214
215 const rebuilt_bin_path = result catch |err| switch (err) {
216 error.MakeFailed => return,
217 else => |other| return other,
218 };
219 const compile_filename = try std.zig.binNameAlloc(gpa, .{
220 .root_name = conf_comp.root_name.slice(conf),
221 .cpu_arch = target.flags.cpu_arch.unwrap().?,
222 .os_tag = target.flags.os_tag.unwrap().?,
223 .ofmt = target.flags.object_format.unwrap().?,
224 .abi = target.flags.abi.unwrap().?,
225 .output_mode = switch (conf_comp.flags3.kind) {
226 .lib => .Lib,
227 .obj, .test_obj => .Obj,
228 .exe, .@"test" => .Exe,
229 },
230 .link_mode = conf_comp.flags2.linkage.unwrap(),
231 .version = if (conf_comp.version.value) |v|
232 std.SemanticVersion.parse(v.slice(conf)) catch unreachable
233 else
234 null,
235 });
236 defer gpa.free(compile_filename);
237
238 run.rebuilt_executable = try rebuilt_bin_path.join(gpa, compile_filename);
239}
240
241fn fuzzWorkerRun(fuzz: *Fuzz, run_index: Configuration.Step.Index) void {
242 const maker = fuzz.maker;
243 const graph = maker.graph;
244 const io = graph.io;
245 const conf = &maker.scanned_config.configuration;
246 const run = &maker.stepByIndex(run_index).extended.run;
247
248 run.rerunInFuzzMode(run_index, fuzz, fuzz.prog_node) catch |err| switch (err) {
249 error.MakeFailed => {
250 var buf: [256]u8 = undefined;
251 const stderr = io.lockStderr(&buf, graph.stderr_mode) catch |e| switch (e) {
252 error.Canceled => return,
253 };
254 defer io.unlockStderr();
255 maker.printErrorMessages(run_index, .{}, stderr.terminal(), .verbose, .indent) catch {};
256 return;
257 },
258 else => {
259 const step_name = run_index.ptr(conf).name.slice(conf);
260 log.err("step {s}: failed to rerun in fuzz mode: {t}", .{ step_name, err });
261 return;
262 },
263 };
264}
265
266pub fn serveSourcesTar(fuzz: *Fuzz, req: *std.http.Server.Request) !void {
267 assert(fuzz.mode == .forever);
268 const maker = fuzz.maker;
269 const gpa = maker.gpa;
270 const conf = &maker.scanned_config.configuration;
271
272 var arena_state: std.heap.ArenaAllocator = .init(gpa);
273 defer arena_state.deinit();
274 const arena = arena_state.allocator();
275
276 const DedupTable = std.ArrayHashMapUnmanaged(Build.Cache.Path, void, Build.Cache.Path.TableAdapter, false);
277 var dedup_table: DedupTable = .empty;
278 defer dedup_table.deinit(gpa);
279
280 for (fuzz.run_steps) |run_index| {
281 const conf_run = run_index.ptr(conf).extended.cast(conf, Configuration.Step.Run) orelse continue;
282 const comp_index = conf_run.producer.value.?;
283 const comp_step = maker.stepByIndex(comp_index);
284 const compile_inputs = comp_step.inputs.table;
285 for (compile_inputs.keys(), compile_inputs.values()) |dir_path, *file_list| {
286 try dedup_table.ensureUnusedCapacity(gpa, file_list.items.len);
287 for (file_list.items) |sub_path| {
288 if (!std.mem.endsWith(u8, sub_path, ".zig")) continue;
289 const joined_path = try dir_path.join(arena, sub_path);
290 dedup_table.putAssumeCapacity(joined_path, {});
291 }
292 }
293 }
294
295 const deduped_paths = dedup_table.keys();
296 const SortContext = struct {
297 pub fn lessThan(this: @This(), lhs: Build.Cache.Path, rhs: Build.Cache.Path) bool {
298 _ = this;
299 return switch (std.mem.order(u8, lhs.root_dir.path orelse ".", rhs.root_dir.path orelse ".")) {
300 .lt => true,
301 .gt => false,
302 .eq => std.mem.lessThan(u8, lhs.sub_path, rhs.sub_path),
303 };
304 }
305 };
306 std.mem.sortUnstable(Build.Cache.Path, deduped_paths, SortContext{}, SortContext.lessThan);
307 return fuzz.mode.forever.ws.serveTarFile(req, deduped_paths);
308}
309
310pub const Previous = struct {
311 unique_runs: usize,
312 entry_points: usize,
313 sent_source_index: bool,
314 pub const init: Previous = .{
315 .unique_runs = 0,
316 .entry_points = 0,
317 .sent_source_index = false,
318 };
319};
320pub fn sendUpdate(
321 fuzz: *Fuzz,
322 socket: *std.http.Server.WebSocket,
323 prev: *Previous,
324) !void {
325 const maker = fuzz.maker;
326 const graph = maker.graph;
327 const io = graph.io;
328
329 try fuzz.coverage_mutex.lock(io);
330 defer fuzz.coverage_mutex.unlock(io);
331
332 const coverage_maps = fuzz.coverage_files.values();
333 if (coverage_maps.len == 0) return;
334 // TODO: handle multiple fuzz steps in the WebSocket packets
335 const coverage_map = &coverage_maps[0];
336 const cov_header: *const abi.SeenPcsHeader = @ptrCast(coverage_map.mapped_memory[0..@sizeOf(abi.SeenPcsHeader)]);
337 // TODO: this isn't sound! We need to do volatile reads of these bits rather than handing the
338 // buffer off to the kernel, because we might race with the fuzzer process[es]. This brings the
339 // whole mmap strategy into question. Incidentally, I wonder if post-writergate we could pass
340 // this data straight to the socket with sendfile...
341 const seen_pcs = cov_header.seenBits();
342 const n_runs = @atomicLoad(usize, &cov_header.n_runs, .monotonic);
343 const unique_runs = @atomicLoad(usize, &cov_header.unique_runs, .monotonic);
344 {
345 if (!prev.sent_source_index) {
346 prev.sent_source_index = true;
347 // We need to send initial context.
348 const header: abi.SourceIndexHeader = .{
349 .directories_len = @intCast(coverage_map.coverage.directories.entries.len),
350 .files_len = @intCast(coverage_map.coverage.files.entries.len),
351 .source_locations_len = @intCast(coverage_map.source_locations.len),
352 .string_bytes_len = @intCast(coverage_map.coverage.string_bytes.items.len),
353 .start_timestamp = coverage_map.start_timestamp,
354 .start_n_runs = coverage_map.start_n_runs,
355 };
356 var iovecs: [5][]const u8 = .{
357 @ptrCast(&header),
358 @ptrCast(coverage_map.coverage.directories.keys()),
359 @ptrCast(coverage_map.coverage.files.keys()),
360 @ptrCast(coverage_map.source_locations),
361 coverage_map.coverage.string_bytes.items,
362 };
363 try socket.writeMessageVec(&iovecs, .binary);
364 }
365
366 const header: abi.CoverageUpdateHeader = .{
367 .n_runs = n_runs,
368 .unique_runs = unique_runs,
369 };
370 var iovecs: [2][]const u8 = .{
371 @ptrCast(&header),
372 @ptrCast(seen_pcs),
373 };
374 try socket.writeMessageVec(&iovecs, .binary);
375
376 prev.unique_runs = unique_runs;
377 }
378
379 if (prev.entry_points != coverage_map.entry_points.items.len) {
380 const header: abi.EntryPointHeader = .init(@intCast(coverage_map.entry_points.items.len));
381 var iovecs: [2][]const u8 = .{
382 @ptrCast(&header),
383 @ptrCast(coverage_map.entry_points.items),
384 };
385 try socket.writeMessageVec(&iovecs, .binary);
386
387 prev.entry_points = coverage_map.entry_points.items.len;
388 }
389}
390
391fn coverageRun(fuzz: *Fuzz) void {
392 coverageRunCancelable(fuzz) catch |err| switch (err) {
393 error.Canceled => return,
394 };
395}
396
397fn coverageRunCancelable(fuzz: *Fuzz) Io.Cancelable!void {
398 const maker = fuzz.maker;
399 const graph = maker.graph;
400 const io = graph.io;
401
402 try fuzz.queue_mutex.lock(io);
403 defer fuzz.queue_mutex.unlock(io);
404
405 while (true) {
406 try fuzz.queue_cond.wait(io, &fuzz.queue_mutex);
407 for (fuzz.msg_queue.items) |msg| switch (msg) {
408 .coverage => |coverage| prepareTables(fuzz, coverage.run, coverage.id) catch |err| switch (err) {
409 error.AlreadyReported => continue,
410 error.Canceled => return,
411 else => |e| log.err("failed to prepare code coverage tables: {t}", .{e}),
412 },
413 .entry_point => |entry_point| addEntryPoint(fuzz, entry_point.coverage_id, entry_point.addr) catch |err| switch (err) {
414 error.AlreadyReported => continue,
415 error.Canceled => return,
416 else => |e| log.err("failed to prepare code coverage tables: {t}", .{e}),
417 },
418 };
419 fuzz.msg_queue.clearRetainingCapacity();
420 }
421}
422fn prepareTables(fuzz: *Fuzz, run_index: Configuration.Step.Index, coverage_id: u64) error{ OutOfMemory, AlreadyReported, Canceled }!void {
423 assert(fuzz.mode == .forever);
424 const ws = fuzz.mode.forever.ws;
425 const maker = fuzz.maker;
426 const graph = maker.graph;
427 const io = graph.io;
428 const gpa = maker.gpa;
429 const conf = &maker.scanned_config.configuration;
430 const cache_root = graph.local_cache_root;
431
432 try fuzz.coverage_mutex.lock(io);
433 defer fuzz.coverage_mutex.unlock(io);
434
435 const gop = try fuzz.coverage_files.getOrPut(gpa, coverage_id);
436 if (gop.found_existing) {
437 // We are fuzzing the same executable with multiple threads.
438 // Perhaps the same unit test; perhaps a different one. In any
439 // case, since the coverage file is the same, we only have to
440 // notice changes to that one file in order to learn coverage for
441 // this particular executable.
442 return;
443 }
444 errdefer _ = fuzz.coverage_files.pop();
445
446 gop.value_ptr.* = .{
447 .coverage = std.debug.Coverage.init,
448 .mapped_memory = undefined, // populated below
449 .source_locations = undefined, // populated below
450 .entry_points = .empty,
451 .start_timestamp = ws.now(),
452 .start_n_runs = undefined, // populated below
453 };
454 errdefer gop.value_ptr.coverage.deinit(gpa);
455
456 const run_step = maker.stepByIndex(run_index);
457 const conf_run_step = run_index.ptr(conf);
458 const conf_run = conf_run_step.extended.cast(conf, Configuration.Step.Run).?;
459 const comp_index = conf_run.producer.value.?;
460 const conf_comp_step = comp_index.ptr(conf);
461 const conf_comp = conf_comp_step.extended.cast(conf, Configuration.Step.Compile).?;
462 const rebuilt_exe_path = run_step.extended.run.rebuilt_executable.?;
463 const root_module = conf_comp.root_module.get(conf);
464 const target = root_module.resolved_target.get(conf).?.result.get(conf);
465
466 var debug_info = std.debug.Info.load(
467 gpa,
468 io,
469 rebuilt_exe_path,
470 &gop.value_ptr.coverage,
471 target.flags.object_format.unwrap().?,
472 target.flags.cpu_arch.unwrap().?,
473 ) catch |err| {
474 log.err("step {s}: failed to load debug information for {f}: {t}", .{
475 conf_run_step.name.slice(conf), rebuilt_exe_path, err,
476 });
477 return error.AlreadyReported;
478 };
479 defer debug_info.deinit(gpa);
480
481 const coverage_file_path: Build.Cache.Path = .{
482 .root_dir = cache_root,
483 .sub_path = "v/" ++ std.fmt.hex(coverage_id),
484 };
485 var coverage_file = coverage_file_path.root_dir.handle.openFile(io, coverage_file_path.sub_path, .{}) catch |err| {
486 log.err("step {s}: failed to load coverage file {f}: {t}", .{
487 conf_run_step.name.slice(conf), coverage_file_path, err,
488 });
489 return error.AlreadyReported;
490 };
491 defer coverage_file.close(io);
492
493 const file_size = coverage_file.length(io) catch |err| {
494 log.err("unable to check len of coverage file {f}: {t}", .{ coverage_file_path, err });
495 return error.AlreadyReported;
496 };
497
498 const mapped_memory = std.posix.mmap(
499 null,
500 file_size,
501 .{ .READ = true },
502 .{ .TYPE = .SHARED },
503 coverage_file.handle,
504 0,
505 ) catch |err| {
506 log.err("failed to map coverage file {f}: {t}", .{ coverage_file_path, err });
507 return error.AlreadyReported;
508 };
509 gop.value_ptr.mapped_memory = mapped_memory;
510
511 const header: *const abi.SeenPcsHeader = @ptrCast(mapped_memory[0..@sizeOf(abi.SeenPcsHeader)]);
512 const pcs = header.pcAddrs();
513 const source_locations = try gpa.alloc(Coverage.SourceLocation, pcs.len);
514 errdefer gpa.free(source_locations);
515
516 // Unfortunately the PCs array that LLVM gives us from the 8-bit PC
517 // counters feature is not sorted.
518 var sorted_pcs: std.MultiArrayList(struct { pc: u64, index: u32, sl: Coverage.SourceLocation }) = .empty;
519 defer sorted_pcs.deinit(gpa);
520 try sorted_pcs.resize(gpa, pcs.len);
521 @memcpy(sorted_pcs.items(.pc), pcs);
522 for (sorted_pcs.items(.index), 0..) |*v, i| v.* = @intCast(i);
523 sorted_pcs.sortUnstable(struct {
524 addrs: []const u64,
525
526 pub fn lessThan(ctx: @This(), a_index: usize, b_index: usize) bool {
527 return ctx.addrs[a_index] < ctx.addrs[b_index];
528 }
529 }{ .addrs = sorted_pcs.items(.pc) });
530
531 debug_info.resolveAddresses(gpa, io, sorted_pcs.items(.pc), sorted_pcs.items(.sl)) catch |err| {
532 log.err("failed to resolve addresses to source locations: {t}", .{err});
533 return error.AlreadyReported;
534 };
535
536 for (sorted_pcs.items(.index), sorted_pcs.items(.sl)) |i, sl| source_locations[i] = sl;
537 gop.value_ptr.source_locations = source_locations;
538 gop.value_ptr.start_n_runs = header.n_runs;
539
540 ws.notifyUpdate();
541}
542
543fn addEntryPoint(fuzz: *Fuzz, coverage_id: u64, addr: u64) error{ AlreadyReported, OutOfMemory, Canceled }!void {
544 const maker = fuzz.maker;
545 const graph = maker.graph;
546 const io = graph.io;
547 const gpa = maker.gpa;
548
549 try fuzz.coverage_mutex.lock(io);
550 defer fuzz.coverage_mutex.unlock(io);
551
552 const coverage_map = fuzz.coverage_files.getPtr(coverage_id).?;
553 const header: *const abi.SeenPcsHeader = @ptrCast(coverage_map.mapped_memory[0..@sizeOf(abi.SeenPcsHeader)]);
554 const pcs = header.pcAddrs();
555
556 // Since this pcs list is unsorted, we must linear scan for the best index.
557 const index = i: {
558 var best: usize = 0;
559 for (pcs[1..], 1..) |elem_addr, i| {
560 if (elem_addr == addr) break :i i;
561 if (elem_addr > addr) continue;
562 if (elem_addr > pcs[best]) best = i;
563 }
564 break :i best;
565 };
566 if (index >= pcs.len) {
567 log.err("unable to find unit test entry address 0x{x} in source locations (range: 0x{x} to 0x{x})", .{
568 addr, pcs[0], pcs[pcs.len - 1],
569 });
570 return error.AlreadyReported;
571 }
572 if (false) {
573 const sl = coverage_map.source_locations[index];
574 const file_name = coverage_map.coverage.stringAt(coverage_map.coverage.fileAt(sl.file).basename);
575 if (pcs.len == 1) {
576 log.debug("server found entry point for 0x{x} at {s}:{d}:{d} - index 0 (final)", .{
577 addr, file_name, sl.line, sl.column,
578 });
579 } else if (index == 0) {
580 log.debug("server found entry point for 0x{x} at {s}:{d}:{d} - index 0 before {x}", .{
581 addr, file_name, sl.line, sl.column, pcs[index + 1],
582 });
583 } else if (index == pcs.len - 1) {
584 log.debug("server found entry point for 0x{x} at {s}:{d}:{d} - index {d} (final) after {x}", .{
585 addr, file_name, sl.line, sl.column, index, pcs[index - 1],
586 });
587 } else {
588 log.debug("server found entry point for 0x{x} at {s}:{d}:{d} - index {d} between {x} and {x}", .{
589 addr, file_name, sl.line, sl.column, index, pcs[index - 1], pcs[index + 1],
590 });
591 }
592 }
593 try coverage_map.entry_points.append(gpa, @intCast(index));
594}
595
596pub fn waitAndPrintReport(fuzz: *Fuzz) Io.Cancelable!void {
597 assert(fuzz.mode == .limit);
598 const maker = fuzz.maker;
599 const graph = maker.graph;
600 const io = graph.io;
601 const cache_root = graph.local_cache_root;
602 const conf = &maker.scanned_config.configuration;
603
604 try fuzz.group.await(io);
605 fuzz.group = .init;
606
607 std.debug.print("======= FUZZING REPORT =======\n", .{});
608 for (fuzz.msg_queue.items) |msg| {
609 if (msg != .coverage) continue;
610
611 const cov = msg.coverage;
612 const run_step_name = cov.run.ptr(conf).name.slice(conf);
613 const run = &maker.stepByIndex(cov.run).extended.run;
614 const coverage_file_path: std.Build.Cache.Path = .{
615 .root_dir = cache_root,
616 .sub_path = "v/" ++ std.fmt.hex(cov.id),
617 };
618 var coverage_file = coverage_file_path.root_dir.handle.openFile(io, coverage_file_path.sub_path, .{}) catch |err| {
619 fatal("step {s}: failed to load coverage file {f}: {t}", .{
620 run_step_name, coverage_file_path, err,
621 });
622 };
623 defer coverage_file.close(io);
624
625 const fuzz_abi = std.Build.abi.fuzz;
626 var rbuf: [0x1000]u8 = undefined;
627 var r = coverage_file.reader(io, &rbuf);
628
629 var header: fuzz_abi.SeenPcsHeader = undefined;
630 r.interface.readSliceAll(std.mem.asBytes(&header)) catch |err| {
631 fatal("step {s}: failed to read from coverage file {f}: {t}", .{
632 run_step_name, coverage_file_path, err,
633 });
634 };
635
636 if (header.pcs_len == 0) {
637 fatal("step {s}: corrupted coverage file {f}: pcs_len was zero", .{
638 run_step_name, coverage_file_path,
639 });
640 }
641
642 var seen_count: usize = 0;
643 const chunk_count = fuzz_abi.SeenPcsHeader.seenElemsLen(header.pcs_len);
644 for (0..chunk_count) |_| {
645 const seen = r.interface.takeInt(usize, .little) catch |err| {
646 fatal("step {s}: failed to read from coverage file {f}: {t}", .{
647 run_step_name, coverage_file_path, err,
648 });
649 };
650 seen_count += @popCount(seen);
651 }
652
653 const seen_f: f64 = @floatFromInt(seen_count);
654 const total_f: f64 = @floatFromInt(header.pcs_len);
655 const ratio = seen_f / total_f;
656 std.debug.print(
657 \\Step: {s}
658 \\Fuzz test: "{s}" ({x})
659 \\Runs: {} -> {}
660 \\Unique runs: {} -> {}
661 \\Coverage: {}/{} -> {}/{} ({:.02}%)
662 \\
663 , .{
664 run_step_name,
665 run.fuzz_tests.items[0],
666 cov.id,
667 cov.cumulative.runs,
668 header.n_runs,
669 cov.cumulative.unique,
670 header.unique_runs,
671 cov.cumulative.coverage,
672 header.pcs_len,
673 seen_count,
674 header.pcs_len,
675 ratio * 100,
676 });
677
678 std.debug.print("------------------------------\n", .{});
679 }
680 std.debug.print(
681 \\Values are accumulated across multiple runs when preserving the cache.
682 \\==============================
683 \\
684 , .{});
685}
lib/compiler/Maker/Graph.zig created+85
......@@ -0,0 +1,85 @@
1//! Shared maker state among all steps.
2const Graph = @This();
3
4const std = @import("std");
5const Io = std.Io;
6const Allocator = std.mem.Allocator;
7const Configuration = std.Build.Configuration;
8const Path = std.Build.Cache.Path;
9const Directory = std.Build.Cache.Directory;
10
11io: Io,
12/// Process lifetime.
13arena: Allocator,
14cache: std.Build.Cache,
15zig_exe: []const u8,
16environ_map: std.process.Environ.Map,
17global_cache_root: Directory,
18local_cache_root: Directory,
19zig_lib_directory: Directory,
20build_root_directory: Directory,
21
22debug_compiler_runtime_libs: ?std.builtin.OptimizeMode = null,
23incremental: ?bool = null,
24random_seed: u32 = 0,
25allow_so_scripts: ?bool = null,
26time_report: bool = false,
27/// Similar to the `Io.Terminal.Mode` returned by `Io.lockStderr`, but also
28/// respects the '--color' flag.
29stderr_mode: ?Io.Terminal.Mode = null,
30reference_trace: ?u32 = null,
31debug_log_scopes: std.ArrayList([]const u8) = .empty,
32debug_compile_errors: bool = false,
33debug_incremental: bool = false,
34fuzzing: bool = false,
35verbose: bool = false,
36verbose_air: bool = false,
37verbose_cc: bool = false,
38verbose_link: bool = false,
39verbose_llvm_cpu_features: bool = false,
40verbose_llvm_ir: bool = false,
41libc_file: ?[]const u8 = null,
42/// What does this do? Nobody bothered to document it, and I think it's a
43/// smelly option. So unless somebody deletes these passive aggressive comments
44/// and replaces them with actual documentation, I'm going to delete this
45/// option from the build system in a future release. In other words, this is
46/// deprecated due to lack of test coverage, lack of documentation, and a hunch
47/// that it's a bad option that should be avoided.
48sysroot: ?[]const u8 = null,
49search_prefixes: std.ArrayList([]const u8) = .empty,
50build_id: ?std.zig.BuildId = null,
51error_limit: ?u32 = null,
52/// Steps should use `io` to limit the number of jobs, however in the case of
53/// a single step spawning a fixed number of processes this can be used.
54max_jobs: ?u32 = null,
55
56/// After following the steps in https://codeberg.org/ziglang/infra/src/branch/master/libc-update/glibc.md,
57/// this will be the directory $glibc-build-dir/install/glibcs
58/// Given the example of the aarch64 target, this is the directory
59/// that contains the path `aarch64-linux-gnu/lib/ld-linux-aarch64.so.1`.
60/// Also works for dynamic musl.
61libc_runtimes_dir: ?[]const u8 = null,
62enable_wine: bool = false,
63enable_qemu: bool = false,
64enable_wasmtime: bool = false,
65enable_darling: bool = false,
66enable_rosetta: bool = false,
67
68/// Intention of verbose is to print all sub-process command lines to stderr
69/// before spawning them.
70pub fn handleVerbose(
71 graph: *const Graph,
72 cwd: ?[]const u8,
73 opt_env: ?*const std.process.Environ.Map,
74 argv: []const []const u8,
75) error{OutOfMemory}!void {
76 if (!graph.verbose) return;
77 const arena = graph.arena;
78 const text = try std.zig.allocPrintCmd(arena, argv, .{
79 .cwd = cwd,
80 .parent_env = &graph.environ_map,
81 .child_env = opt_env,
82 });
83 defer arena.free(text);
84 std.log.scoped(.verbose).info("{s}", .{text});
85}
lib/compiler/Maker/PkgConfig.zig created+114
......@@ -0,0 +1,114 @@
1const std = @import("std");
2const Io = std.Io;
3const mem = std.mem;
4const assert = std.debug.assert;
5
6const Maker = @import("../Maker.zig");
7const Step = @import("Step.zig");
8const Graph = @import("Graph.zig");
9
10mutex: Io.Mutex = .init,
11pkgs: ?std.zig.PkgConfig = null,
12debug: bool = false,
13
14pub const RunError = error{
15 PackageNotFound,
16 PkgConfigUnavailable,
17} || Step.ExtendedMakeError;
18
19pub const Result = std.zig.PkgConfig.Parsed;
20
21/// Run pkg-config for the given library name and parse the output, returning the arguments
22/// that should be passed to zig to link the given library.
23pub fn run(
24 maker: *Maker,
25 step: *Step,
26 progress_node: std.Progress.Node,
27 lib_name: []const u8,
28 /// If true, reports failure error messages on step rather than returning
29 /// error.PackageNotFound or error.PkgConfigUnavailable,
30 force: bool,
31) RunError!Result {
32 const pc = &maker.pkg_config;
33 const graph = maker.graph;
34 const arena = graph.arena; // TODO don't leak into process arena
35
36 const pkg_config_exe = getExe(graph);
37 const pkgs = try getPkgs(maker, step, progress_node, force);
38 const found_index = pkgs.find(lib_name) orelse {
39 if (force) return step.fail(maker, "{s}: package not found: {s}", .{ pkg_config_exe, lib_name });
40 return error.PackageNotFound;
41 };
42 const pkg = pkgs.all[found_index];
43
44 const stdout = try captureChildProcess(maker, step, .{
45 .argv = &.{ pkg_config_exe, pkg.name, "--cflags", "--libs" },
46 .progress_node = progress_node,
47 .allow_failure = !force,
48 });
49
50 const parsed = std.zig.PkgConfig.parse(arena, stdout) catch |err| switch (err) {
51 error.InvalidPkgConfigOutput => {
52 if (force) return step.fail(maker, "{s} package {s} invalid output: {s}", .{
53 pkg_config_exe, lib_name, stdout,
54 });
55 return error.PkgConfigUnavailable;
56 },
57 else => |e| return e,
58 };
59 if (force or pc.debug) {
60 for (parsed.unknown_flags) |unknown_flag| {
61 return step.fail(maker, "{s} package {s} unknown flag: {s}", .{ pkg_config_exe, lib_name, unknown_flag });
62 }
63 }
64
65 return parsed;
66}
67
68fn getExe(graph: *const Graph) []const u8 {
69 return std.zig.PkgConfig.exe(&graph.environ_map);
70}
71
72fn getPkgs(maker: *Maker, step: *Step, progress_node: std.Progress.Node, force: bool) RunError!std.zig.PkgConfig {
73 const graph = maker.graph;
74 const arena = graph.arena; // TODO don't leak into process arena
75 const io = graph.io;
76 const pc = &maker.pkg_config;
77
78 try pc.mutex.lock(io);
79 defer pc.mutex.unlock(io);
80
81 if (pc.pkgs) |pkgs| return pkgs;
82
83 const pkg_config_exe = getExe(graph);
84 const stdout = try captureChildProcess(maker, step, .{
85 .argv = &.{ pkg_config_exe, "--list-all" },
86 .progress_node = progress_node,
87 .allow_failure = !force,
88 });
89
90 var diagnostic: std.zig.PkgConfig.Diagnostic = undefined;
91 const result = std.zig.PkgConfig.init(arena, stdout, &diagnostic) catch |err| switch (err) {
92 error.InvalidPkgConfigOutput => {
93 if (force) return step.fail(maker, "{s}: invalid line({d}): {s}", .{
94 pkg_config_exe, diagnostic.invalid_line_index + 1, diagnostic.invalid_line,
95 });
96 return error.PkgConfigUnavailable;
97 },
98 else => |e| return e,
99 };
100
101 pc.pkgs = result;
102 return result;
103}
104
105fn captureChildProcess(maker: *Maker, step: *Step, options: Step.CaptureChildProcessOptions) ![]const u8 {
106 const captured = step.captureChildProcess(maker, options) catch |err| switch (err) {
107 error.FileNotFound => return error.PkgConfigUnavailable,
108 else => |e| return e,
109 };
110 assert(step.result_failed_command != null);
111 if (captured.term.success()) return captured.stdout;
112 if (!options.allow_failure) return step.fail(maker, "{s} {f}", .{ options.argv[0], captured.term });
113 return error.PkgConfigUnavailable;
114}
lib/compiler/Maker/ScannedConfig.zig created+370
......@@ -0,0 +1,370 @@
1const ScannedConfig = @This();
2
3const std = @import("std");
4const Configuration = std.Build.Configuration;
5const Writer = std.Io.Writer;
6const Serializer = std.zon.Serializer;
7
8const Graph = @import("Graph.zig");
9
10configuration: Configuration,
11top_level_steps: std.StringArrayHashMapUnmanaged(Configuration.Step.Index),
12path: []const u8,
13
14pub fn print(sc: *const ScannedConfig, w: *Writer) Writer.Error!void {
15 std.log.err("TODO also print paths", .{});
16 std.log.err("TODO also print unlazy deps", .{});
17 std.log.err("TODO also print system integrations", .{});
18 std.log.err("TODO also print available options", .{});
19 const c = &sc.configuration;
20 var serializer: Serializer = .{ .writer = w };
21 var s = try serializer.beginStruct(.{});
22
23 {
24 var tf = try s.beginTupleField("search_prefixes", .{});
25 for (c.search_prefixes) |string| try tf.field(string.slice(c), .{});
26 try tf.end();
27 }
28
29 try s.field("default_step", @intFromEnum(c.default_step), .{});
30 {
31 var sf = try s.beginStructField("top_level_steps", .{});
32 for (sc.top_level_steps.keys(), sc.top_level_steps.values()) |name, step| {
33 try sf.field(name, @intFromEnum(step), .{});
34 }
35 try sf.end();
36 }
37
38 {
39 var tf = try s.beginTupleField("steps", .{});
40 for (c.steps) |step| {
41 var step_field = try tf.beginStructField(.{});
42 try printStruct(sc, &step_field, Configuration.Step, step);
43 try step_field.end();
44 }
45 try tf.end();
46 }
47
48 try s.end();
49}
50
51fn printStruct(sc: *const ScannedConfig, s: *Serializer.Struct, comptime S: type, v: S) !void {
52 inline for (@typeInfo(S).@"struct".fields) |field| {
53 try s.fieldPrefix(field.name);
54 try printValue(sc, s.container.serializer, field.type, @field(v, field.name));
55 }
56}
57
58fn printValue(sc: *const ScannedConfig, s: *Serializer, comptime Field: type, field_value: Field) !void {
59 const c = &sc.configuration;
60 switch (Field) {
61 Configuration.String => {
62 try s.value(field_value.slice(c), .{});
63 },
64 Configuration.Deps.Index => {
65 try printValue(sc, s, []const Configuration.Step.Index, field_value.get(c).steps.slice);
66 },
67 Configuration.MaxRss => {
68 try s.value(field_value.toBytes(), .{});
69 },
70 Configuration.Step.Run.Arg.Index => {
71 var sub_struct = try s.beginStruct(.{});
72 try printStruct(sc, &sub_struct, Configuration.Step.Run.Arg, field_value.get(c));
73 try sub_struct.end();
74 },
75 Configuration.Step.ObjCopy.UpdateSection.Flags => {
76 var sub_struct = try s.beginStruct(.{});
77 try printStruct(sc, &sub_struct, Field, field_value);
78 try sub_struct.end();
79 },
80 Configuration.LazyPath.Index => {
81 switch (field_value.get(c)) {
82 inline else => |u| {
83 var sub_struct = try s.beginStruct(.{});
84 try printStruct(sc, &sub_struct, @TypeOf(u), u);
85 try sub_struct.end();
86 },
87 }
88 },
89 else => switch (@typeInfo(Field)) {
90 .int => try s.int(field_value),
91 .pointer => |info| switch (info.size) {
92 .slice => {
93 var slice_field = try s.beginTuple(.{});
94 for (field_value) |elem| {
95 try slice_field.fieldPrefix();
96 try printValue(sc, s, info.child, elem);
97 }
98 try slice_field.end();
99 },
100 else => comptime unreachable,
101 },
102 .@"enum" => {
103 if (@hasDecl(Field, "storage")) switch (Field.storage) {
104 .extended => {
105 var sub_struct = try s.beginStruct(.{});
106 switch (field_value.get(c.extra)) {
107 inline else => |u| {
108 try printStruct(sc, &sub_struct, @TypeOf(u), u);
109 },
110 }
111 try sub_struct.end();
112 },
113 .flag_optional => comptime unreachable,
114 .flag_length_prefixed_list => comptime unreachable,
115 .enum_optional => comptime unreachable,
116 .union_list => comptime unreachable,
117 .length_prefixed_list => comptime unreachable,
118 .flag_list => comptime unreachable,
119 .flag_union => comptime unreachable,
120 .multi_list => comptime unreachable,
121 } else if (std.enums.tagName(Field, field_value)) |name| {
122 try s.ident(name);
123 } else {
124 try s.int(@intFromEnum(field_value));
125 }
126 },
127 .@"struct" => |info| switch (info.layout) {
128 .@"packed" => {
129 try s.value(field_value, .{});
130 },
131 .@"extern" => {
132 var sub_struct = try s.beginStruct(.{});
133 try printStruct(sc, &sub_struct, Field, field_value);
134 try sub_struct.end();
135 },
136 .auto => switch (Field.storage) {
137 .flag_optional, .enum_optional => {
138 if (field_value.value) |some| {
139 try printValue(sc, s, Field.Value, some);
140 } else {
141 try s.value(null, .{});
142 }
143 },
144 .length_prefixed_list, .flag_length_prefixed_list, .flag_list => {
145 try printValue(sc, s, @TypeOf(field_value.slice), field_value.slice);
146 },
147 .extended => @compileError("TODO"),
148 .union_list => {
149 var slice_field = try s.beginTuple(.{});
150 for (field_value.slice(c.extra), 0..) |elem, i| switch (field_value.tag(c.extra, i)) {
151 inline else => |tag| {
152 var sub_struct = try s.beginStruct(.{});
153 try sub_struct.fieldPrefix(@tagName(tag));
154 try printValue(sc, s, @FieldType(Field.Union, @tagName(tag)), @enumFromInt(elem));
155 try sub_struct.end();
156 },
157 };
158 try slice_field.end();
159 },
160 .flag_union => try printValue(sc, s, Field.Union, field_value.u),
161 .multi_list => @compileError("TODO"),
162 },
163 },
164 .@"union" => {
165 try printTaggedUnion(sc, s, field_value);
166 },
167 else => @compileError("not implemented: " ++ @typeName(Field)),
168 },
169 }
170}
171
172fn printTaggedUnion(sc: *const ScannedConfig, s: *Serializer, value: anytype) !void {
173 switch (value) {
174 inline else => |u, tag| {
175 if (@TypeOf(u) == void) {
176 try s.ident(@tagName(tag));
177 } else {
178 var sub_struct = try s.beginStruct(.{});
179 try sub_struct.fieldPrefix(@tagName(tag));
180 try printValue(sc, s, @TypeOf(u), u);
181 try sub_struct.end();
182 }
183 },
184 }
185}
186
187pub fn printSteps(sc: *const ScannedConfig, graph: *Graph, w: *Writer) !void {
188 const arena = graph.arena;
189 const c = &sc.configuration;
190 for (sc.top_level_steps.keys(), sc.top_level_steps.values()) |name, step_index| {
191 const step = step_index.ptr(c);
192 const decorated_name = if (step_index == c.default_step)
193 try std.fmt.allocPrint(arena, "{s} (default)", .{name})
194 else
195 name;
196 const top_level = step.extended.get(c.extra).top_level;
197 const description = top_level.description.slice(c);
198 try w.print(" {s:<28} {s}\n", .{ decorated_name, description });
199 }
200}
201
202pub fn printUsage(sc: *const ScannedConfig, graph: *Graph, w: *Writer) !void {
203 const arena = graph.arena;
204
205 try w.print(
206 \\Usage: {s} build [steps] [options]
207 \\
208 \\Steps:
209 \\
210 , .{graph.zig_exe});
211 try printSteps(sc, graph, w);
212 try w.writeAll(
213 \\
214 \\Project-Specific Options:
215 \\
216 );
217
218 const available_options = sc.configuration.available_options;
219 if (available_options.len == 0) {
220 try w.print(" (none)\n", .{});
221 } else {
222 for (available_options) |option| {
223 const name = option.name.slice(&sc.configuration);
224 const description = option.description.slice(&sc.configuration);
225 const help = try std.fmt.allocPrint(arena, " -D{s}=[{t}]", .{ name, option.type });
226 try w.print("{s:<30} {s}\n", .{ help, description });
227 if (option.enum_options.slice(&sc.configuration)) |enum_options| {
228 const padding: [33]u8 = @splat(' ');
229 try w.writeAll(padding ++ "Supported Values:\n");
230 for (enum_options) |enum_option_index| {
231 const enum_option = enum_option_index.slice(&sc.configuration);
232 try w.print(padding ++ " {s}\n", .{enum_option});
233 }
234 }
235 }
236 }
237
238 try w.writeAll(
239 \\
240 \\System Integration Options:
241 \\ --search-prefix [path] Add a path to look for binaries, libraries, headers
242 \\ --sysroot [path] Set the system root directory (usually /)
243 \\ --libc [file] Provide a file which specifies libc paths
244 \\
245 \\ --system [pkgdir] Disable package fetching; enable all integrations
246 \\ -fsys=[name] Enable a system integration
247 \\ -fno-sys=[name] Disable a system integration
248 \\
249 \\ -fdarling, -fno-darling Integration with system-installed Darling to
250 \\ execute macOS programs on Linux hosts
251 \\ (default: no)
252 \\ -fqemu, -fno-qemu Integration with system-installed QEMU to execute
253 \\ foreign-architecture programs on Linux hosts
254 \\ (default: no)
255 \\ --libc-runtimes [path] Enhances QEMU integration by providing dynamic libc
256 \\ (e.g. glibc or musl) built for multiple foreign
257 \\ architectures, allowing execution of non-native
258 \\ programs that link with libc.
259 \\ -frosetta, -fno-rosetta Rely on Rosetta to execute x86_64 programs on
260 \\ ARM64 macOS hosts. (default: no)
261 \\ -fwasmtime, -fno-wasmtime Integration with system-installed wasmtime to
262 \\ execute WASI binaries. (default: no)
263 \\ -fwine, -fno-wine Integration with system-installed Wine to execute
264 \\ Windows programs on Linux hosts. (default: no)
265 \\
266 \\ Available System Integrations: Enabled:
267 \\
268 );
269 if (sc.configuration.system_integrations.len == 0) {
270 try w.writeAll(" (none) -\n");
271 } else {
272 for (sc.configuration.system_integrations) |system_integration| {
273 const name = system_integration.name.slice(&sc.configuration);
274 const status = switch (system_integration.status) {
275 .disabled => "no",
276 .enabled => "yes",
277 };
278 try w.print(" {s:<43} {s}\n", .{ name, status });
279 }
280 }
281
282 try w.writeAll(
283 \\
284 \\General Options:
285 \\ -h, --help Print this help to stdout and exit
286 \\ -l, --list-steps Print available steps to stdout and exit
287 \\
288 \\ -p, --prefix [path] Where to install files (default: zig-out)
289 \\ --prefix-lib-dir [path] Where to install libraries
290 \\ --prefix-exe-dir [path] Where to install executables
291 \\ --prefix-include-dir [path] Where to install C header files
292 \\ --release[=mode] Request release mode, optionally specifying a
293 \\ preferred optimization mode: fast, safe, small
294 \\
295 \\ --verbose Print commands before executing them
296 \\ --color [auto|off|on] Enable or disable colored error messages
297 \\ --error-style [style] Control how build errors are printed
298 \\ verbose (Default) Report errors with full context
299 \\ minimal Report errors after summary, excluding context like command lines
300 \\ verbose_clear Like 'verbose', but clear the terminal at the start of each update
301 \\ minimal_clear Like 'minimal', but clear the terminal at the start of each update
302 \\ --multiline-errors [style] Control how multi-line error messages are printed
303 \\ indent (Default) Indent non-initial lines to align with initial line
304 \\ newline Include a leading newline so that the error message is on its own lines
305 \\ none Print as usual so the first line is misaligned
306 \\ --summary [mode] Control the printing of the build summary
307 \\ all Print the build summary in its entirety
308 \\ new Omit cached steps
309 \\ failures (Default if short-lived) Only print failed steps
310 \\ line (Default if long-lived) Only print the single-line summary
311 \\ none Do not print the build summary
312 \\ -j<N> Limit concurrent jobs (default is to use all CPU cores)
313 \\ --maxrss <bytes> Limit memory usage (default is to use available memory)
314 \\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss
315 \\ --test-timeout <timeout> Limit execution time of unit tests, terminating if exceeded.
316 \\ The timeout must include a unit: ns, us, ms, s, m, h
317 \\ --watch Continuously rebuild when source files are modified
318 \\ --debounce <ms> Delay before rebuilding after changed file detected
319 \\ --webui[=ip] Enable the web interface on the given IP address
320 \\ --fuzz[=limit] Continuously search for unit test failures with an optional
321 \\ limit to the max number of iterations. The argument supports
322 \\ an optional 'K', 'M', or 'G' suffix (e.g. '10K'). Implies
323 \\ '--webui' when no limit is specified.
324 \\ --time-report Force full rebuild and provide detailed information on
325 \\ compilation time of Zig source code (implies '--webui')
326 \\ -fincremental Enable incremental compilation
327 \\ -fno-incremental Disable incremental compilation
328 \\
329 \\Package Management Options:
330 \\ --fetch[=mode] Fetch dependency tree (optionally choose laziness) and exit
331 \\ needed (Default) Lazy dependencies are fetched as needed
332 \\ all Lazy dependencies are always fetched
333 \\ --fork=[path], --fork [path] Override one or more projects from dependency tree
334 \\
335 \\Advanced Options:
336 \\ -freference-trace[=num] How many lines of reference trace should be shown per compile error
337 \\ -fno-reference-trace Disable reference trace
338 \\ -fallow-so-scripts Allows .so files to be GNU ld scripts
339 \\ -fno-allow-so-scripts (default) .so files must be ELF files
340 \\ --error-limit [num] Set the maximum amount of distinct error values
341 \\ --build-file [file] Override path to build.zig
342 \\ --cache-dir [path] Override path to local Zig cache directory
343 \\ --global-cache-dir [path] Override path to global Zig cache directory
344 \\ --zig-lib-dir [arg] Override path to Zig lib directory
345 \\ --seed [integer] For shuffling dependency traversal order (default: random)
346 \\ --cache-poison[=mode] Override configuration caching behavior
347 \\ pure (default) Avoid false positive cache hits
348 \\ poisoned Don't cache the configuration
349 \\ disallowed Panics when cache would be poisoned
350 \\ ignored A little poison never hurt anybody
351 \\ --print-configuration Render configuration as .zon to stdout
352 \\ --build-id[=style] At a minor link-time expense, embeds a build ID in binaries
353 \\ fast 8-byte non-cryptographic hash (COFF, ELF, WASM)
354 \\ sha1, tree 20-byte cryptographic hash (ELF, WASM)
355 \\ md5 16-byte cryptographic hash (ELF)
356 \\ uuid 16-byte random UUID (ELF, WASM)
357 \\ 0x[hexstring] Constant ID, maximum 32 bytes (ELF, WASM)
358 \\ none (default) No build ID
359 \\ --debug-log [scope] Enable debugging the compiler
360 \\ --debug-pkg-config Fail if unknown pkg-config flags encountered
361 \\ --maker-opt=[mode] Change maker executable optimization mode (default: ReleaseSafe)
362 \\ --verbose-link Enable compiler debug output for linking
363 \\ --verbose-air Enable compiler debug output for Zig AIR
364 \\ --verbose-llvm-ir Enable compiler debug output for LLVM IR
365 \\ --verbose-cimport Enable compiler debug output for C imports
366 \\ --verbose-cc Enable compiler debug output for C compilation
367 \\ --verbose-llvm-cpu-features Enable compiler debug output for LLVM CPU features
368 \\
369 );
370}
lib/compiler/Maker/Step.zig created+863
......@@ -0,0 +1,863 @@
1//! The *mutable* state that `Maker` needs in order to process one node from
2//! the build graph.
3const Step = @This();
4
5const builtin = @import("builtin");
6
7const std = @import("std");
8const Allocator = std.mem.Allocator;
9const Cache = std.Build.Cache;
10const Io = std.Io;
11const Dir = std.Io.Dir;
12const LazyPath = std.Build.Configuration.LazyPath;
13const Package = std.Build.Configuration.Package;
14const Path = std.Build.Cache.Path;
15const Configuration = std.Build.Configuration;
16const assert = std.debug.assert;
17
18const WebServer = @import("WebServer.zig");
19const Maker = @import("../Maker.zig");
20
21pub const CheckFile = @import("Step/CheckFile.zig");
22pub const Compile = @import("Step/Compile.zig");
23pub const ConfigHeader = @import("Step/ConfigHeader.zig");
24pub const FindProgram = @import("Step/FindProgram.zig");
25pub const Fmt = @import("Step/Fmt.zig");
26pub const InstallArtifact = @import("Step/InstallArtifact.zig");
27pub const InstallDir = @import("Step/InstallDir.zig");
28pub const InstallFile = @import("Step/InstallFile.zig");
29pub const ObjCopy = @import("Step/ObjCopy.zig");
30pub const Options = @import("Step/Options.zig");
31pub const Run = @import("Step/Run.zig");
32pub const TranslateC = @import("Step/TranslateC.zig");
33pub const UpdateSourceFiles = @import("Step/UpdateSourceFiles.zig");
34pub const WriteFile = @import("Step/WriteFile.zig");
35
36/// Avoid false sharing.
37_: void align(std.atomic.cache_line) = {},
38
39/// Extra data for specific types of steps.
40extended: Extended,
41
42/// This field is atomically accessed multi-threaded.
43state: State = .precheck_unstarted,
44
45dependants: std.ArrayList(Configuration.Step.Index) = .empty,
46/// Collects the set of files that retrigger this step to run.
47///
48/// This is used by the build system's implementation of `--watch` but it can
49/// also be potentially useful for IDEs to know what effects editing a
50/// particular file has.
51///
52/// Populated within `make`. Implementation may choose to clear and repopulate,
53/// retain previous value, or update.
54inputs: Inputs = .init,
55pending_deps: u32 = undefined,
56
57result_error_msgs: std.ArrayList([]const u8) = .empty,
58result_error_bundle: std.zig.ErrorBundle = .empty,
59result_stderr: []const u8 = "",
60result_cached: bool = false,
61/// Indicates error information is missing due to allocation failure.
62result_oom: bool = false,
63result_duration_ns: ?u64 = null,
64/// 0 means unavailable or not reported.
65result_peak_rss: usize = 0,
66/// If the step is failed and this field is populated, this is the command which failed.
67/// This field may be populated even if the step succeeded.
68/// Memory owned by `Maker.gpa`.
69result_failed_command: ?[]const u8 = null,
70test_results: TestResults = .{},
71
72comptime {
73 // Common cache line size is 128. This check prevents accidentally crossing
74 // an additional cache line. In the future it might be nice to try to fit
75 // this struct in 128 bytes or less.
76 if (std.atomic.cache_line <= 128) assert(@sizeOf(@This()) <= 128 * 3);
77}
78
79pub const Extended = union(enum) {
80 check_file: CheckFile,
81 compile: Compile,
82 config_header: ConfigHeader,
83 fail: Fail,
84 find_program: FindProgram,
85 fmt: Fmt,
86 install_artifact: InstallArtifact,
87 install_dir: InstallDir,
88 install_file: InstallFile,
89 obj_copy: ObjCopy,
90 options: Options,
91 run: Run,
92 top_level: TopLevel,
93 translate_c: TranslateC,
94 update_source_files: UpdateSourceFiles,
95 write_file: WriteFile,
96
97 pub fn init(tag: Configuration.Step.Tag) Extended {
98 return switch (tag) {
99 .check_file => .{ .check_file = .{} },
100 .compile => .{ .compile = .{} },
101 .config_header => .{ .config_header = .{} },
102 .fail => .{ .fail = .{} },
103 .find_program => .{ .find_program = .{} },
104 .fmt => .{ .fmt = .{} },
105 .install_artifact => .{ .install_artifact = .{} },
106 .install_dir => .{ .install_dir = .{} },
107 .install_file => .{ .install_file = .{} },
108 .obj_copy => .{ .obj_copy = .{} },
109 .options => .{ .options = .{} },
110 .run => .{ .run = .{} },
111 .top_level => .{ .top_level = .{} },
112 .translate_c => .{ .translate_c = .{} },
113 .update_source_files => .{ .update_source_files = .{} },
114 .write_file => .{ .write_file = .{} },
115 };
116 }
117
118 pub const TopLevel = struct {
119 pub fn make(
120 top_level: *TopLevel,
121 step_index: Configuration.Step.Index,
122 maker: *Maker,
123 progress_node: std.Progress.Node,
124 ) Step.ExtendedMakeError!void {
125 _ = top_level;
126 _ = step_index;
127 _ = maker;
128 _ = progress_node;
129 }
130 };
131
132 pub const Fail = struct {
133 pub fn make(
134 this: *@This(),
135 step_index: Configuration.Step.Index,
136 maker: *Maker,
137 progress_node: std.Progress.Node,
138 ) Step.ExtendedMakeError!void {
139 _ = this;
140 _ = progress_node;
141 const graph = maker.graph;
142 const arena = graph.arena; // TODO don't leak into the process arena
143 const conf = &maker.scanned_config.configuration;
144 const step = maker.stepByIndex(step_index);
145 const conf_step = step_index.ptr(conf);
146 const conf_fail = conf_step.extended.get(conf.extra).fail;
147
148 try step.result_error_msgs.append(arena, conf_fail.msg.slice(conf));
149 return error.MakeFailed;
150 }
151 };
152};
153
154pub const State = enum {
155 precheck_unstarted,
156 precheck_started,
157 /// This is also used to indicate "dirty" steps that have been modified
158 /// after a previous build completed, in which case, the step may or may
159 /// not have been completed before. Either way, one or more of its direct
160 /// file system inputs have been modified, meaning that the step needs to
161 /// be re-evaluated.
162 precheck_done,
163 dependency_failure,
164 success,
165 failure,
166 /// This state indicates that the step did not complete, however, it also did not fail,
167 /// and it is safe to continue executing its dependencies.
168 skipped,
169 /// This step was skipped because it specified a max_rss that exceeded the runner's maximum.
170 /// It is not safe to run its dependencies.
171 skipped_oom,
172};
173
174pub const Inputs = struct {
175 table: Table,
176
177 pub const init: Inputs = .{
178 .table = .{},
179 };
180
181 pub const Table = std.ArrayHashMapUnmanaged(Path, Files, Path.TableAdapter, false);
182 /// The special file name "." means any changes inside the directory.
183 pub const Files = std.ArrayList([]const u8);
184
185 pub fn populated(inputs: *Inputs) bool {
186 return inputs.table.count() != 0;
187 }
188
189 pub fn clear(inputs: *Inputs, gpa: Allocator) void {
190 for (inputs.table.values()) |*files| files.deinit(gpa);
191 inputs.table.clearRetainingCapacity();
192 }
193
194 pub fn deinit(inputs: *Inputs, gpa: Allocator) void {
195 clear(inputs, gpa);
196 inputs.table.deinit(gpa);
197 }
198};
199
200pub const TestResults = struct {
201 /// The total number of tests in the step. Every test has a "status" from the following:
202 /// * passed
203 /// * skipped
204 /// * failed cleanly
205 /// * crashed
206 /// * timed out
207 test_count: u32 = 0,
208
209 /// The number of tests which were skipped (`error.SkipZigTest`).
210 skip_count: u32 = 0,
211 /// The number of tests which failed cleanly.
212 fail_count: u32 = 0,
213 /// The number of tests which terminated unexpectedly, i.e. crashed.
214 crash_count: u32 = 0,
215 /// The number of tests which timed out.
216 timeout_count: u32 = 0,
217
218 /// The number of detected memory leaks. The associated test may still have passed; indeed, *all*
219 /// individual tests may have passed. However, the step as a whole fails if any test has leaks.
220 leak_count: u32 = 0,
221 /// The number of detected error logs. The associated test may still have passed; indeed, *all*
222 /// individual tests may have passed. However, the step as a whole fails if any test logs errors.
223 log_err_count: u32 = 0,
224
225 pub fn isSuccess(tr: TestResults) bool {
226 // all steps are success or skip
227 return tr.fail_count == 0 and
228 tr.crash_count == 0 and
229 tr.timeout_count == 0 and
230 // no (otherwise successful) step leaked memory or logged errors
231 tr.leak_count == 0 and
232 tr.log_err_count == 0;
233 }
234
235 /// Computes the number of tests which passed from the other values.
236 pub fn passCount(tr: TestResults) u32 {
237 return tr.test_count - tr.skip_count - tr.fail_count - tr.crash_count - tr.timeout_count;
238 }
239};
240
241pub const MakeError = error{
242 /// Indicates the error is already reported.
243 MakeFailed,
244 MakeSkipped,
245} || Io.Cancelable;
246
247pub const ExtendedMakeError = MakeError || Allocator.Error;
248
249pub fn make(
250 step_index: Configuration.Step.Index,
251 maker: *Maker,
252 progress_node: std.Progress.Node,
253) MakeError!void {
254 const graph = maker.graph;
255 const arena = graph.arena; // TODO don't leak into the process arena
256 const io = graph.io;
257 const c = &maker.scanned_config.configuration;
258 const conf_step = step_index.ptr(c);
259 const s = maker.stepByIndex(step_index);
260
261 var start_ts: ?Io.Timestamp = t: {
262 if (!graph.time_report) break :t null;
263 const flags = conf_step.flags(c);
264 switch (flags.tag) {
265 .compile => break :t null,
266 .run => {
267 const run_flags: Configuration.Step.Run.Flags = @bitCast(flags);
268 if (run_flags.stdio == .zig_test) break :t null;
269 },
270 else => {},
271 }
272 break :t Io.Clock.awake.now(io);
273 };
274 const make_result = switch (s.extended) {
275 inline else => |*extended| extended.make(step_index, maker, progress_node),
276 };
277 if (start_ts) |*ts| {
278 const duration = ts.untilNow(io, .awake);
279 maker.web_server.?.updateTimeReportGeneric(step_index, duration);
280 }
281
282 make_result catch |err| switch (err) {
283 error.MakeFailed, error.MakeSkipped => |e| return e,
284 error.OutOfMemory => {
285 s.result_oom = true;
286 return error.MakeFailed;
287 },
288 error.Canceled => |e| return e,
289 };
290
291 if (!s.test_results.isSuccess()) {
292 return error.MakeFailed;
293 }
294
295 const max_rss = conf_step.max_rss.toBytes();
296 if (max_rss != 0 and s.result_peak_rss > max_rss) {
297 if (std.fmt.allocPrint(
298 arena,
299 "memory usage peaked at {0B:.2} ({0d} bytes), exceeding the declared upper bound of {1B:.2} ({1d} bytes)",
300 .{ s.result_peak_rss, max_rss },
301 )) |msg| {
302 s.oomWrap(s.result_error_msgs.append(arena, msg));
303 } else |_| s.result_oom = true;
304 }
305}
306
307/// Prepares the step for being re-evaluated.
308pub fn reset(step: *Step, maker: *Maker) void {
309 assert(step.state == .precheck_done);
310 const gpa = maker.gpa;
311
312 clearFailedCommand(step, gpa);
313
314 step.result_error_msgs.clearRetainingCapacity();
315 step.result_stderr = "";
316 step.result_cached = false;
317 step.result_duration_ns = null;
318 step.result_peak_rss = 0;
319 step.test_results = .{};
320 clearWatchInputs(step, maker);
321 clearErrorBundle(step, gpa);
322}
323
324pub const CaptureChildProcessError = error{
325 FileNotFound,
326} || ExtendedMakeError;
327
328pub const CaptureChildProcessOptions = struct {
329 argv: []const []const u8,
330 progress_node: std.Progress.Node = .none,
331 environ_map: ?*const std.process.Environ.Map = null,
332 allow_failure: bool = false,
333};
334
335/// Populates `s.result_failed_command`.
336pub fn captureChildProcess(s: *Step, maker: *Maker, options: CaptureChildProcessOptions) !std.process.RunResult {
337 const gpa = maker.gpa;
338 const graph = maker.graph;
339 const arena = graph.arena; // TODO stop leaking into process arena
340 const io = graph.io;
341
342 clearFailedCommand(s, gpa);
343 s.result_failed_command = try std.zig.allocPrintCmd(gpa, options.argv, .{});
344
345 try handleChildProcUnsupported(s, maker);
346 try graph.handleVerbose(null, null, options.argv);
347
348 const result = std.process.run(arena, io, .{
349 .argv = options.argv,
350 .environ_map = options.environ_map orelse &graph.environ_map,
351 .progress_node = options.progress_node,
352 }) catch |err| {
353 switch (err) {
354 error.OutOfMemory, error.Canceled => |e| return e,
355 error.FileNotFound => |e| if (options.allow_failure) return e,
356 else => {},
357 }
358 return s.fail(maker, "failed to run {s}: {t}", .{ options.argv[0], err });
359 };
360
361 if (result.stderr.len > 0) try s.result_error_msgs.append(arena, result.stderr);
362
363 return result;
364}
365
366pub fn clearErrorBundle(s: *Step, gpa: Allocator) void {
367 s.result_error_bundle.deinit(gpa);
368 s.result_error_bundle = .empty;
369}
370
371pub fn clearFailedCommand(s: *Step, gpa: Allocator) void {
372 if (s.result_failed_command) |cmd| {
373 gpa.free(cmd);
374 s.result_failed_command = null;
375 }
376}
377
378pub const FailError = error{ OutOfMemory, MakeFailed };
379
380pub fn fail(step: *Step, maker: *const Maker, comptime fmt: []const u8, args: anytype) FailError {
381 try step.addError(maker, fmt, args);
382 return error.MakeFailed;
383}
384
385pub fn addError(step: *Step, maker: *const Maker, comptime fmt: []const u8, args: anytype) error{OutOfMemory}!void {
386 const graph = maker.graph;
387 const arena = graph.arena; // TODO don't leak into the process_arena
388 const msg = try std.fmt.allocPrint(arena, fmt, args);
389 try step.result_error_msgs.append(arena, msg);
390}
391
392pub const ZigProcess = struct {
393 child: std.process.Child,
394 multi_reader_buffer: Io.File.MultiReader.Buffer(2),
395 multi_reader: Io.File.MultiReader,
396 progress_ipc_index: ?if (std.Progress.have_ipc) std.Progress.Ipc.Index else noreturn,
397
398 pub const StreamEnum = enum { stdout, stderr };
399
400 pub fn saveState(zp: *ZigProcess, prog_node: std.Progress.Node) void {
401 zp.progress_ipc_index = if (std.Progress.have_ipc) prog_node.takeIpcIndex() else null;
402 }
403
404 pub fn deinit(zp: *ZigProcess, io: Io) void {
405 zp.child.kill(io);
406 zp.multi_reader.deinit();
407 zp.* = undefined;
408 }
409};
410
411/// Assumes that argv contains `--listen=-` and that the process being spawned
412/// is the zig compiler - the same version that compiled the build runner.
413/// Populates `s.result_failed_command`.
414pub fn evalZigProcess(
415 step_index: Configuration.Step.Index,
416 maker: *Maker,
417 argv: []const []const u8,
418 prog_node: std.Progress.Node,
419 watch: bool,
420) (Step.ExtendedMakeError || error{NeedCompileErrorCheck})!?Path {
421 const s = maker.stepByIndex(step_index);
422 const gpa = maker.gpa;
423 const graph = maker.graph;
424 const io = graph.io;
425
426 // If an error occurs, it's happened in this command:
427 clearFailedCommand(s, gpa);
428 s.result_failed_command = try std.zig.allocPrintCmd(gpa, argv, .{});
429
430 if (s.getZigProcess()) |zp| update: {
431 assert(watch);
432 if (zp.progress_ipc_index) |ipc_index| prog_node.setIpcIndex(ipc_index);
433 zp.progress_ipc_index = null;
434 var exited = false;
435 defer if (exited) {
436 s.extended.compile.zig_process = null;
437 zp.deinit(io);
438 gpa.destroy(zp);
439 } else zp.saveState(prog_node);
440 const result = zigProcessUpdate(step_index, maker, zp, watch) catch |err| switch (err) {
441 error.BrokenPipe, error.EndOfStream => |reason| {
442 // Process restart required.
443 std.log.info("{s} restart required: {t}", .{ argv[0], reason });
444 _ = zp.child.wait(io) catch |e| return s.fail(maker, "unable to wait for {s}: {t}", .{ argv[0], e });
445 exited = true;
446 break :update;
447 },
448 error.OutOfMemory, error.Canceled, error.MakeFailed => |e| return e,
449 else => |e| return s.fail(maker, "zig child process monitoring failed: {t}", .{e}),
450 };
451
452 if (s.result_error_bundle.errorMessageCount() > 0)
453 return s.fail(maker, "{d} compilation errors", .{s.result_error_bundle.errorMessageCount()});
454
455 if (s.result_error_msgs.items.len > 0 and result == null) {
456 // Crash detected.
457 const term = zp.child.wait(io) catch |e| {
458 return s.fail(maker, "unable to wait for {s}: {t}", .{ argv[0], e });
459 };
460 s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0;
461 exited = true;
462 try handleChildProcessTerm(s, maker, term);
463 return error.MakeFailed;
464 }
465
466 return result;
467 }
468 assert(argv.len != 0);
469
470 try handleChildProcUnsupported(s, maker);
471 try graph.handleVerbose(null, null, argv);
472
473 const zp = try gpa.create(ZigProcess);
474 defer if (!watch) gpa.destroy(zp);
475
476 zp.child = std.process.spawn(io, .{
477 .argv = argv,
478 .environ_map = &graph.environ_map,
479 .stdin = .pipe,
480 .stdout = .pipe,
481 .stderr = .pipe,
482 .request_resource_usage_statistics = true,
483 .progress_node = prog_node,
484 }) catch |err| return s.fail(maker, "failed to spawn zig compiler {s}: {t}", .{ argv[0], err });
485
486 zp.multi_reader.init(gpa, io, zp.multi_reader_buffer.toStreams(), &.{
487 zp.child.stdout.?, zp.child.stderr.?,
488 });
489 if (watch) s.extended.compile.zig_process = zp;
490 defer if (!watch) zp.deinit(io);
491
492 const result = result: {
493 defer if (watch) zp.saveState(prog_node);
494 break :result zigProcessUpdate(step_index, maker, zp, watch) catch |err| switch (err) {
495 error.OutOfMemory, error.Canceled, error.MakeFailed => |e| return e,
496 else => |e| return s.fail(maker, "zig child process monitoring failed: {t}", .{e}),
497 };
498 };
499
500 if (!watch) {
501 // Send EOF to stdin.
502 zp.child.stdin.?.close(io);
503 zp.child.stdin = null;
504
505 const term = zp.child.wait(io) catch |err| {
506 return s.fail(maker, "unable to wait for {s}: {t}", .{ argv[0], err });
507 };
508 s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0;
509
510 // Special handling for compile step that is expecting compile errors.
511 const conf = &maker.scanned_config.configuration;
512 if (term == .exited) switch (step_index.ptr(conf).extended.get(conf.extra)) {
513 .compile => |compile| if (compile.flags4.expect_errors != .none) {
514 // Note that the exit code may be 0 in this case due to the
515 // compiler server protocol.
516 return error.NeedCompileErrorCheck;
517 },
518 else => {},
519 };
520 try handleChildProcessTerm(s, maker, term);
521 }
522
523 if (s.result_error_bundle.errorMessageCount() > 0) {
524 return s.fail(maker, "{d} compilation errors", .{s.result_error_bundle.errorMessageCount()});
525 }
526
527 return result;
528}
529
530fn zigProcessUpdate(step_index: Configuration.Step.Index, maker: *Maker, zp: *ZigProcess, watch: bool) !?Path {
531 const s = maker.stepByIndex(step_index);
532 const gpa = maker.gpa;
533 const graph = maker.graph;
534 const arena = graph.arena; // TODO don't leak into the process arena
535 const io = graph.io;
536
537 const start_ts = Io.Clock.awake.now(io);
538
539 try sendMessage(io, zp.child.stdin.?, .update);
540 if (!watch) try sendMessage(io, zp.child.stdin.?, .exit);
541
542 var result: ?Path = null;
543 var eos_err: error{EndOfStream}!void = {};
544
545 const stdout = zp.multi_reader.fileReader(0);
546
547 while (true) {
548 const Header = std.zig.Server.Message.Header;
549 const header = stdout.interface.takeStruct(Header, .little) catch |err| switch (err) {
550 error.EndOfStream => break,
551 error.ReadFailed => return stdout.err.?,
552 };
553 const body = stdout.interface.take(header.bytes_len) catch |err| switch (err) {
554 error.EndOfStream => |e| {
555 // Better to report the crash with stderr below, but we set
556 // this in case the child exits successfully while violating
557 // this protocol.
558 eos_err = e;
559 break;
560 },
561 error.ReadFailed => return stdout.err.?,
562 };
563 switch (header.tag) {
564 .zig_version => {
565 if (!std.mem.eql(u8, builtin.zig_version_string, body)) {
566 return s.fail(
567 maker,
568 "zig version mismatch build runner vs compiler: '{s}' vs '{s}'",
569 .{ builtin.zig_version_string, body },
570 );
571 }
572 },
573 .error_bundle => {
574 s.result_error_bundle = try std.zig.Server.allocErrorBundle(gpa, body);
575 // This message indicates the end of the update.
576 if (watch) break;
577 },
578 .emit_digest => {
579 const EmitDigest = std.zig.Server.Message.EmitDigest;
580 const emit_digest: *align(1) const EmitDigest = @ptrCast(body);
581 s.result_cached = emit_digest.flags.cache_hit;
582 const digest = body[@sizeOf(EmitDigest)..][0..Cache.bin_digest_len];
583 result = .{
584 .root_dir = graph.local_cache_root,
585 .sub_path = try arena.dupe(u8, "o" ++ Dir.path.sep_str ++ Cache.binToHex(digest.*)),
586 };
587 },
588 .file_system_inputs => {
589 clearWatchInputs(s, maker);
590 const conf = &maker.scanned_config.configuration;
591 const conf_step = step_index.ptr(conf);
592 var it = std.mem.splitScalar(u8, body, 0);
593 while (it.next()) |prefixed_path| {
594 const prefix_index: std.zig.Server.Message.PathPrefix = @enumFromInt(prefixed_path[0] - 1);
595 const sub_path = try arena.dupe(u8, prefixed_path[1..]);
596 const sub_path_dirname = Dir.path.dirname(sub_path) orelse "";
597 switch (prefix_index) {
598 .cwd => {
599 const path: Path = .{
600 .root_dir = .cwd(),
601 .sub_path = sub_path_dirname,
602 };
603 try addWatchInputFromPath(s, maker, path, Dir.path.basename(sub_path));
604 },
605 .zig_lib => zl: {
606 switch (conf_step.extended.get(conf.extra)) {
607 .compile => |compile| if (compile.zig_lib_dir.value) |zig_lib_dir| {
608 const resolved = try maker.resolveLazyPathIndex(arena, zig_lib_dir, step_index);
609 const appended = try resolved.join(arena, sub_path);
610 try addWatchInputPath(s, maker, appended);
611 break :zl;
612 },
613 else => {},
614 }
615 const path: Path = .{
616 .root_dir = graph.zig_lib_directory,
617 .sub_path = sub_path_dirname,
618 };
619 try addWatchInputFromPath(s, maker, path, Dir.path.basename(sub_path));
620 },
621 .local_cache => {
622 const path: Path = .{
623 .root_dir = graph.local_cache_root,
624 .sub_path = sub_path_dirname,
625 };
626 try addWatchInputFromPath(s, maker, path, Dir.path.basename(sub_path));
627 },
628 .global_cache => {
629 const path: Path = .{
630 .root_dir = graph.global_cache_root,
631 .sub_path = sub_path_dirname,
632 };
633 try addWatchInputFromPath(s, maker, path, Dir.path.basename(sub_path));
634 },
635 }
636 }
637 },
638 .time_report => if (maker.web_server) |*ws| {
639 const TimeReport = std.zig.Server.Message.TimeReport;
640 const tr: *align(1) const TimeReport = @ptrCast(body[0..@sizeOf(TimeReport)]);
641 ws.updateTimeReportCompile(.{
642 .compile_step = step_index,
643 .use_llvm = tr.flags.use_llvm,
644 .stats = tr.stats,
645 .ns_total = @intCast(start_ts.untilNow(io, .awake).toNanoseconds()),
646 .llvm_pass_timings_len = tr.llvm_pass_timings_len,
647 .files_len = tr.files_len,
648 .decls_len = tr.decls_len,
649 .trailing = body[@sizeOf(TimeReport)..],
650 });
651 },
652 else => {}, // ignore other messages
653 }
654 }
655
656 s.result_duration_ns = @intCast(start_ts.untilNow(io, .awake).toNanoseconds());
657
658 const stderr_contents = zp.multi_reader.reader(1).buffered();
659 if (stderr_contents.len > 0) {
660 try s.result_error_msgs.append(arena, try arena.dupe(u8, stderr_contents));
661 }
662
663 try eos_err;
664
665 return result;
666}
667
668pub fn getZigProcess(s: *Step) ?*ZigProcess {
669 return switch (s.extended) {
670 .compile => |*compile| compile.zig_process,
671 else => null,
672 };
673}
674
675fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void {
676 const header: std.zig.Client.Message.Header = .{
677 .tag = tag,
678 .bytes_len = 0,
679 };
680 var w = file.writer(io, &.{});
681 w.interface.writeStruct(header, .little) catch |err| switch (err) {
682 error.WriteFailed => return w.err.?,
683 };
684}
685
686/// Asserts that the caller has already populated `s.result_failed_command`.
687pub inline fn handleChildProcUnsupported(s: *Step, maker: *Maker) FailError!void {
688 assert(s.result_failed_command != null);
689 if (!std.process.can_spawn)
690 return s.fail(maker, "unable to spawn process: host cannot spawn child processes", .{});
691}
692
693/// Asserts that the caller has already populated `s.result_failed_command`.
694pub fn handleChildProcessTerm(s: *Step, maker: *Maker, term: std.process.Child.Term) FailError!void {
695 assert(s.result_failed_command != null);
696 if (!term.success()) return s.fail(maker, "process {f}", .{term});
697}
698
699/// Prefer `cacheHitAndWatch` unless you already added watch inputs
700/// separately from using the cache system.
701pub fn cacheHit(s: *Step, maker: *Maker, man: *Cache.Manifest) !bool {
702 s.result_cached = man.hit() catch |err| return failWithCacheError(s, maker, man, err);
703 return s.result_cached;
704}
705
706/// Clears previous watch inputs, if any, and then populates watch inputs from
707/// the full set of files picked up by the cache manifest.
708///
709/// Must be accompanied with `writeManifestAndWatch`.
710pub fn cacheHitAndWatch(s: *Step, maker: *Maker, man: *Cache.Manifest) !bool {
711 const is_hit = man.hit() catch |err| return failWithCacheError(s, maker, man, err);
712 s.result_cached = is_hit;
713 // The above call to hit() populates the manifest with files, so in case of
714 // a hit, we need to populate watch inputs.
715 if (is_hit) try setWatchInputsFromManifest(s, maker, man);
716 return is_hit;
717}
718
719fn failWithCacheError(
720 s: *Step,
721 maker: *Maker,
722 man: *const Cache.Manifest,
723 err: Cache.Manifest.HitError,
724) error{ OutOfMemory, Canceled, MakeFailed } {
725 switch (err) {
726 error.CacheCheckFailed => switch (man.diagnostic) {
727 .none => unreachable,
728 .manifest_create, .manifest_read, .manifest_lock => |e| return s.fail(maker, "failed checking cache: {t} {t}", .{
729 man.diagnostic, e,
730 }),
731 .file_open, .file_stat, .file_read, .file_hash => |op| {
732 const pp = man.files.keys()[op.file_index].prefixed_path;
733 const prefix = man.cache.prefixes()[pp.prefix].path orelse "";
734 return s.fail(maker, "failed checking cache: {s}{c}{s} {t} {t}", .{
735 prefix, Dir.path.sep, pp.sub_path, man.diagnostic, op.err,
736 });
737 },
738 },
739 error.OutOfMemory, error.Canceled => |e| return e,
740 error.InvalidFormat => return s.fail(maker, "failed checking cache: invalid manifest file format", .{}),
741 }
742}
743
744/// Prefer `writeManifestAndWatch` unless you already added watch inputs
745/// separately from using the cache system.
746pub fn writeManifest(s: *Step, maker: *Maker, man: *Cache.Manifest) !void {
747 if (s.test_results.isSuccess()) {
748 man.writeManifest() catch |err| switch (err) {
749 error.Canceled => |e| return e,
750 else => |e| try s.addError(maker, "failed writing cache manifest: {t}", .{e}),
751 };
752 }
753}
754
755/// Clears previous watch inputs, if any, and then populates watch inputs from
756/// the full set of files picked up by the cache manifest.
757///
758/// Must be accompanied with `cacheHitAndWatch`.
759pub fn writeManifestAndWatch(s: *Step, maker: *Maker, man: *Cache.Manifest) !void {
760 try writeManifest(s, maker, man);
761 try setWatchInputsFromManifest(s, maker, man);
762}
763
764fn setWatchInputsFromManifest(s: *Step, maker: *Maker, man: *Cache.Manifest) !void {
765 const graph = maker.graph;
766 const arena = graph.arena; // TODO don't leak into process arena
767 const prefixes = man.cache.prefixes();
768 clearWatchInputs(s, maker);
769 for (man.files.keys()) |file| {
770 // The file path data is freed when the cache manifest is cleaned up at the end of `make`.
771 const sub_path = try arena.dupe(u8, file.prefixed_path.sub_path);
772 try addWatchInputFromPath(s, maker, .{
773 .root_dir = prefixes[file.prefixed_path.prefix],
774 .sub_path = Dir.path.dirname(sub_path) orelse "",
775 }, Dir.path.basename(sub_path));
776 }
777}
778
779/// For steps that have a single input that never changes when re-running `make`.
780pub fn singleUnchangingWatchInput(step: *Step, maker: *Maker, arena: Allocator, lazy_path: LazyPath) Allocator.Error!void {
781 if (!step.inputs.populated()) try step.addWatchInput(maker, arena, lazy_path);
782}
783
784pub fn clearWatchInputs(step: *Step, maker: *Maker) void {
785 step.inputs.clear(maker.gpa);
786}
787
788/// Places a *file* dependency on the path.
789pub fn addWatchInput(step: *Step, maker: *Maker, arena: Allocator, lazy_file: LazyPath) Allocator.Error!void {
790 const conf = &maker.scanned_config.configuration;
791 switch (lazy_file) {
792 .source_path => |source_path| {
793 const sub_path = source_path.sub_path.slice(conf);
794 const pkg_path = try maker.packagePath(arena, source_path.owner, sub_path);
795 try addWatchInputPath(step, maker, pkg_path);
796 },
797 .relative => |relative| {
798 const resolved_path = try maker.relativePath(arena, relative);
799 try addWatchInputPath(step, maker, resolved_path);
800 },
801 // Nothing to watch because this dependency edge is modeled instead via `dependants`.
802 .generated => {},
803 }
804}
805
806/// Any changes inside the directory will trigger invalidation.
807///
808/// See also `addDirectoryWatchInputFromPath` which takes a `Path` instead.
809///
810/// Paths derived from this directory should also be manually added via
811/// `addDirectoryWatchInputFromPath` if and only if this function returns
812/// `true`.
813pub fn addDirectoryWatchInput(step: *Step, maker: *Maker, lazy_directory: LazyPath) Allocator.Error!bool {
814 const graph = maker.graph;
815 const arena = graph.arena; // TODO don't leak into the process arena
816 switch (lazy_directory) {
817 .source_path => |source_path| {
818 const conf = &maker.scanned_config.configuration;
819 const sub_path = source_path.sub_path.slice(conf);
820 const pkg_path = try maker.packagePath(arena, source_path.owner, sub_path);
821 try addDirectoryWatchInputFromPath(step, maker, pkg_path);
822 },
823 .relative => |relative| {
824 const resolved_path = try maker.relativePath(arena, relative);
825 try addDirectoryWatchInputFromPath(step, maker, resolved_path);
826 },
827 // Nothing to watch because this dependency edge is modeled instead via `dependants`.
828 .generated => return false,
829 }
830 return true;
831}
832
833/// Any changes inside the directory will trigger invalidation.
834///
835/// See also `addDirectoryWatchInput` which takes a `LazyPath` instead.
836///
837/// This function should only be called when it has been verified that the
838/// dependency on `path` is not already accounted for by a `Step` dependency.
839/// In other words, before calling this function, first check that the
840/// `LazyPath` which this `path` is derived from is not `generated`.
841pub fn addDirectoryWatchInputFromPath(step: *Step, maker: *Maker, path: Path) !void {
842 return addWatchInputFromPath(step, maker, path, ".");
843}
844
845fn addWatchInputPath(step: *Step, maker: *Maker, path: Path) Allocator.Error!void {
846 return addWatchInputFromPath(step, maker, .{
847 .root_dir = path.root_dir,
848 .sub_path = Dir.path.dirname(path.sub_path) orelse "",
849 }, Dir.path.basename(path.sub_path));
850}
851
852fn addWatchInputFromPath(step: *Step, maker: *Maker, directory: Path, basename: []const u8) Allocator.Error!void {
853 const gpa = maker.gpa;
854 const gop = try step.inputs.table.getOrPut(gpa, directory);
855 if (!gop.found_existing) gop.value_ptr.* = .empty;
856 try gop.value_ptr.append(gpa, basename);
857}
858
859fn oomWrap(s: *Step, result: error{OutOfMemory}!void) void {
860 result catch {
861 s.result_oom = true;
862 };
863}
lib/compiler/Maker/Step/CheckFile.zig created+63
......@@ -0,0 +1,63 @@
1const CheckFile = @This();
2
3const std = @import("std");
4const Io = std.Io;
5const Configuration = std.Build.Configuration;
6
7const Step = @import("../Step.zig");
8const Maker = @import("../../Maker.zig");
9
10pub fn make(
11 check_file: *CheckFile,
12 step_index: Configuration.Step.Index,
13 maker: *Maker,
14 progress_node: std.Progress.Node,
15) Step.ExtendedMakeError!void {
16 _ = check_file;
17 _ = progress_node;
18 const graph = maker.graph;
19 const arena = maker.graph.arena; // TODO don't leak into process arena
20 const io = graph.io;
21 const step = maker.stepByIndex(step_index);
22 const conf = &maker.scanned_config.configuration;
23 const conf_step = step_index.ptr(conf);
24 const conf_cf = conf_step.extended.get(conf.extra).check_file;
25 const lazy_path = conf_cf.file.get(conf);
26
27 try step.singleUnchangingWatchInput(maker, arena, lazy_path);
28
29 const src_path = try maker.resolveLazyPath(arena, lazy_path, step_index);
30 const limit: Io.Limit = if (conf_cf.max_bytes.value) |x| .limited(x) else .unlimited;
31
32 const contents = src_path.root_dir.handle.readFileAlloc(io, src_path.sub_path, arena, limit) catch |err|
33 return step.fail(maker, "failed to read {f}: {t}", .{ src_path, err });
34
35 for (conf_cf.expected_matches.slice) |expected_match_index| {
36 const expected_match = expected_match_index.slice(conf);
37 if (std.mem.find(u8, contents, expected_match) == null) {
38 return step.fail(maker,
39 \\
40 \\========= expected to find: ===================
41 \\{s}
42 \\========= but file does not contain it: =======
43 \\{s}
44 \\===============================================
45 , .{ expected_match, contents });
46 }
47 }
48
49 if (conf_cf.expected_exact.value) |expected_exact_index| {
50 const expected_exact = expected_exact_index.slice(conf);
51 if (!std.mem.eql(u8, expected_exact, contents)) {
52 return step.fail(maker,
53 \\
54 \\========= expected: =====================
55 \\{s}
56 \\========= but found: ====================
57 \\{s}
58 \\========= from the following file: ======
59 \\{f}
60 , .{ expected_exact, contents, src_path });
61 }
62 }
63}
lib/compiler/Maker/Step/Compile.zig created+1390
......@@ -0,0 +1,1390 @@
1const Compile = @This();
2
3const std = @import("std");
4const Allocator = std.mem.Allocator;
5const mem = std.mem;
6const Configuration = std.Build.Configuration;
7const Dir = std.Io.Dir;
8const Path = std.Build.Cache.Path;
9const Module = std.Build.Configuration.Module;
10const Io = std.Io;
11const Sha256 = std.crypto.hash.sha2.Sha256;
12const assert = std.debug.assert;
13const allocPrint = std.fmt.allocPrint;
14
15const Step = @import("../Step.zig");
16const Maker = @import("../../Maker.zig");
17const PkgConfig = @import("../PkgConfig.zig");
18
19/// Populated when there is compiler process that lives across multiple calls
20/// to `make`.
21zig_process: ?*Step.ZigProcess = null,
22/// Populated by InstallArtifact.
23installed_path: ?Path = null,
24/// Populated by `make`, used by `Run`.
25is_linking_libc: bool = false,
26
27pub fn make(
28 compile: *Compile,
29 compile_index: Configuration.Step.Index,
30 maker: *Maker,
31 progress_node: std.Progress.Node,
32) Step.ExtendedMakeError!void {
33 const graph = maker.graph;
34 const gpa = maker.gpa;
35 const conf = &maker.scanned_config.configuration;
36 const conf_step = compile_index.ptr(conf);
37 const conf_comp = conf_step.extended.get(conf.extra).compile;
38
39 var arena_allocator: std.heap.ArenaAllocator = .init(gpa);
40 defer arena_allocator.deinit();
41 const arena = arena_allocator.allocator();
42
43 var argv: std.ArrayList([]const u8) = .empty;
44 defer argv.deinit(gpa);
45
46 try lowerZigArgs(arena, compile, compile_index, maker, progress_node, &argv, false);
47
48 const maybe_output_dir = Step.evalZigProcess(
49 compile_index,
50 maker,
51 argv.items,
52 progress_node,
53 (graph.incremental == true) and (maker.watch or maker.web_server != null),
54 ) catch |err| switch (err) {
55 error.NeedCompileErrorCheck => {
56 try checkCompileErrors(arena, maker, compile_index);
57 return;
58 },
59 else => |e| return e,
60 };
61
62 const root_module = conf_comp.root_module.get(conf);
63 const target = root_module.resolved_target.get(conf).?.result.get(conf);
64
65 // Update generated files
66 if (maybe_output_dir) |output_dir| {
67 if (conf_comp.emit_directory.value) |gf| maker.generatedPath(gf).* = output_dir;
68 try updateGeneratedFile(maker, arena, &conf_comp, output_dir, &target, conf_comp.generated_bin.value, .bin);
69 try updateGeneratedFile(maker, arena, &conf_comp, output_dir, &target, conf_comp.generated_pdb.value, .pdb);
70 try updateGeneratedFile(maker, arena, &conf_comp, output_dir, &target, conf_comp.generated_implib.value, .implib);
71 try updateGeneratedFile(maker, arena, &conf_comp, output_dir, &target, conf_comp.generated_h.value, .h);
72 try updateGeneratedFile(maker, arena, &conf_comp, output_dir, &target, conf_comp.generated_docs.value, .docs);
73 try updateGeneratedFile(maker, arena, &conf_comp, output_dir, &target, conf_comp.generated_asm.value, .@"asm");
74 try updateGeneratedFile(maker, arena, &conf_comp, output_dir, &target, conf_comp.generated_llvm_ir.value, .llvm_ir);
75 try updateGeneratedFile(maker, arena, &conf_comp, output_dir, &target, conf_comp.generated_llvm_bc.value, .llvm_bc);
76 }
77
78 if (conf_comp.flags3.kind == .lib and conf_comp.flags2.linkage == .dynamic and
79 conf_comp.version.value != null and target.flags.os_tag != .windows)
80 {
81 if (conf_comp.generated_bin.value) |generated_bin| {
82 const full_dest_path = maker.generatedPath(generated_bin).*;
83 try maker.installSymLinks(arena, full_dest_path, compile_index, compile_index);
84 }
85 }
86}
87
88fn updateGeneratedFile(
89 maker: *Maker,
90 arena: Allocator,
91 conf_comp: *const Configuration.Step.Compile,
92 out_path: std.Build.Cache.Path,
93 target: *const Configuration.TargetQuery,
94 opt_gf: ?Configuration.GeneratedFileIndex,
95 ea: std.zig.EmitArtifact,
96) Allocator.Error!void {
97 const gf = opt_gf orelse return;
98 const graph = maker.graph;
99 const conf = &maker.scanned_config.configuration;
100 const name = try ea.cacheName(arena, .{
101 .root_name = conf_comp.root_name.slice(conf),
102 .cpu_arch = target.flags.cpu_arch.unwrap().?,
103 .os_tag = target.flags.os_tag.unwrap().?,
104 .ofmt = target.flags.object_format.unwrap().?,
105 .abi = target.flags.abi.unwrap().?,
106 .output_mode = switch (conf_comp.flags3.kind) {
107 .lib => .Lib,
108 .obj, .test_obj => .Obj,
109 .exe, .@"test" => .Exe,
110 },
111 .link_mode = conf_comp.flags2.linkage.unwrap(),
112 .version = if (conf_comp.version.value) |v|
113 std.SemanticVersion.parse(v.slice(conf)) catch unreachable
114 else
115 null,
116 });
117 maker.generatedPath(gf).* = try out_path.join(graph.arena, name);
118}
119
120/// List of importable modules in a compilation's module graph, including
121/// the root module. The root module is guaranteed to be first.
122const ModuleList = std.AutoArrayHashMapUnmanaged(Configuration.Module.Index, Configuration.String);
123/// Keyed on the first key in the module list.
124pub const ModuleGraph = std.ArrayHashMapUnmanaged(ModuleList, void, ModuleListContext, false);
125
126const ModuleListContext = struct {
127 pub fn eql(ctx: @This(), a: ModuleList, b: ModuleList) bool {
128 _ = ctx;
129 return a.keys()[0] == b.keys()[0];
130 }
131
132 pub fn hash(ctx: @This(), key: ModuleList) u32 {
133 _ = ctx;
134 return std.hash.int(@intFromEnum(key.keys()[0]));
135 }
136
137 const Adapter = struct {
138 pub fn eql(ctx: @This(), a: Configuration.Module.Index, b: ModuleList, b_index: usize) bool {
139 _ = ctx;
140 _ = b_index;
141 return a == b.keys()[0];
142 }
143
144 pub fn hash(ctx: @This(), key: Configuration.Module.Index) u32 {
145 _ = ctx;
146 return std.hash.int(@intFromEnum(key));
147 }
148 };
149};
150
151fn lowerZigArgs(
152 arena: Allocator,
153 compile: *Compile,
154 compile_index: Configuration.Step.Index,
155 maker: *Maker,
156 progress_node: std.Progress.Node,
157 zig_args: *std.ArrayList([]const u8),
158 fuzz: bool,
159) Step.ExtendedMakeError!void {
160 const step = maker.stepByIndex(compile_index);
161 const graph = maker.graph;
162 const gpa = maker.gpa;
163 const conf = &maker.scanned_config.configuration;
164 const conf_step = compile_index.ptr(conf);
165 const conf_comp = conf_step.extended.get(conf.extra).compile;
166 const root_module_target = conf_comp.rootModuleTarget(conf);
167
168 try zig_args.append(gpa, graph.zig_exe);
169
170 const cmd = switch (conf_comp.flags3.kind) {
171 .lib => "build-lib",
172 .exe => "build-exe",
173 .obj => "build-obj",
174 .@"test" => "test",
175 .test_obj => "test-obj",
176 };
177 try zig_args.append(gpa, cmd);
178
179 if (graph.reference_trace) |some| {
180 try zig_args.append(gpa, try allocPrint(arena, "-freference-trace={d}", .{some}));
181 }
182 try addFlag(gpa, zig_args, "allow-so-scripts", conf_comp.flags2.allow_so_scripts.toBool() orelse graph.allow_so_scripts);
183
184 try addFlag(gpa, zig_args, "llvm", conf_comp.flags2.use_llvm.toBool());
185 try addFlag(gpa, zig_args, "lld", conf_comp.flags2.use_lld.toBool());
186 try addFlag(gpa, zig_args, "new-linker", conf_comp.flags2.use_new_linker.toBool());
187
188 const root_module = conf_comp.root_module.get(conf);
189
190 if (root_module.resolved_target.get(conf).?.query.unwrap()) |query| {
191 if (query.get(conf).flags.object_format.unwrap()) |ofmt| {
192 try zig_args.append(gpa, try allocPrint(arena, "-ofmt={t}", .{ofmt}));
193 }
194 }
195
196 switch (conf_comp.flags3.entry) {
197 .default => {},
198 .disabled => try zig_args.append(gpa, "-fno-entry"),
199 .enabled => try zig_args.append(gpa, "-fentry"),
200 .symbol_name => {
201 const symbol_name = conf_comp.entry.value.?.slice(conf);
202 try zig_args.append(gpa, try allocPrint(arena, "-fentry={s}", .{symbol_name}));
203 },
204 }
205
206 for (conf_comp.force_undefined_symbols.slice) |symbol_name| {
207 try zig_args.appendSlice(gpa, &.{ "--force_undefined", symbol_name.slice(conf) });
208 }
209
210 if (conf_comp.stack_size.value) |stack_size| {
211 try zig_args.appendSlice(gpa, &.{ "--stack", try allocPrint(arena, "{d}", .{stack_size}) });
212 }
213
214 try addBool(gpa, zig_args, "-ffuzz", fuzz);
215
216 {
217 var is_linking_libc = conf_comp.flags3.is_linking_libc;
218 var is_linking_libcpp = conf_comp.flags3.is_linking_libcpp;
219
220 // Stores system libraries that have already been seen for at least one
221 // module, along with any C compiler arguments that need to be passed
222 // to the compiler for each module individually as reported by
223 // pkg-config.
224 var seen_system_libs: std.AutoArrayHashMapUnmanaged(Configuration.String, []const []const u8) = .empty;
225 var frameworks: std.AutoArrayHashMapUnmanaged(Configuration.String, Configuration.Module.Framework.Flags) = .empty;
226 var module_graph: ModuleGraph = .empty;
227
228 var prev_has_cflags = false;
229 var prev_has_rcflags = false;
230 var prev_search_strategy: Configuration.SystemLib.SearchStrategy = .paths_first;
231 var prev_preferred_link_mode: std.builtin.LinkMode = .dynamic;
232 // Track the number of positional arguments so that a nice error can be
233 // emitted if there is nothing to link.
234 var total_linker_objects: usize = @intFromBool(root_module.root_source_file != .none);
235
236 // Fully recursive iteration including dynamic libraries to detect
237 // libc and libc++ linkage.
238 for (try getCompileDependencies(arena, &module_graph, conf, compile_index, true)) |some_compile_index| {
239 const some_compile = some_compile_index.ptr(conf).extended.get(conf.extra).compile;
240 const modules = try getModuleList(arena, &module_graph, some_compile.root_module, conf);
241 for (modules.keys()) |mod_index| {
242 const mod = mod_index.get(conf);
243 is_linking_libc = is_linking_libc or mod.flags2.link_libc == .true;
244 is_linking_libcpp = is_linking_libcpp or mod.flags2.link_libcpp == .true;
245 }
246 }
247
248 var cli_named_modules = try CliNamedModules.init(arena, &module_graph, compile_index, maker);
249
250 // For this loop, don't chase dynamic libraries because their link
251 // objects are already linked.
252 for (try getCompileDependencies(arena, &module_graph, conf, compile_index, false)) |dep_compile_index| {
253 const dep_compile = dep_compile_index.ptr(conf).extended.get(conf.extra).compile;
254 const modules = try getModuleList(arena, &module_graph, dep_compile.root_module, conf);
255 for (modules.keys()) |mod_index| {
256 const mod = mod_index.get(conf);
257 // While walking transitive dependencies, if a given link object is
258 // already included in a library, it should not redundantly be
259 // placed on the linker line of the dependee.
260 const my_responsibility = dep_compile_index == compile_index;
261 const already_linked = !my_responsibility and dep_compile.isDynamicLibrary();
262
263 // Inherit dependencies on darwin frameworks.
264 if (!already_linked) {
265 for (mod.frameworks.slice) |framework| {
266 try frameworks.put(arena, framework.name, framework.flags);
267 }
268 }
269
270 // Inherit dependencies on system libraries and static libraries.
271 for (0..mod.link_objects.len) |lo_i| switch (mod.link_objects.get(conf.extra, lo_i)) {
272 .static_path => |static_path| {
273 if (my_responsibility) {
274 try zig_args.append(gpa, try maker.resolveLazyPathIndexAbs(arena, static_path, compile_index));
275 total_linker_objects += 1;
276 }
277 },
278 .system_lib => |system_lib_index| {
279 const system_lib = system_lib_index.get(conf);
280 const system_lib_name = system_lib.name.slice(conf);
281 const system_lib_gop = try seen_system_libs.getOrPut(arena, system_lib.name);
282 if (system_lib_gop.found_existing) {
283 try zig_args.appendSlice(gpa, system_lib_gop.value_ptr.*);
284 continue;
285 } else {
286 system_lib_gop.value_ptr.* = &.{};
287 }
288
289 if (already_linked)
290 continue;
291
292 if ((system_lib.flags.search_strategy != prev_search_strategy or
293 system_lib.flags.preferred_link_mode != prev_preferred_link_mode) and
294 conf_comp.flags2.linkage != .static)
295 {
296 try zig_args.ensureUnusedCapacity(gpa, 1);
297 switch (system_lib.flags.search_strategy) {
298 .no_fallback => switch (system_lib.flags.preferred_link_mode) {
299 .dynamic => zig_args.appendAssumeCapacity("-search_dylibs_only"),
300 .static => zig_args.appendAssumeCapacity("-search_static_only"),
301 },
302 .paths_first => switch (system_lib.flags.preferred_link_mode) {
303 .dynamic => zig_args.appendAssumeCapacity("-search_paths_first"),
304 .static => zig_args.appendAssumeCapacity("-search_paths_first_static"),
305 },
306 .mode_first => switch (system_lib.flags.preferred_link_mode) {
307 .dynamic => zig_args.appendAssumeCapacity("-search_dylibs_first"),
308 .static => zig_args.appendAssumeCapacity("-search_static_first"),
309 },
310 }
311 prev_search_strategy = system_lib.flags.search_strategy;
312 prev_preferred_link_mode = system_lib.flags.preferred_link_mode;
313 }
314
315 const prefix: []const u8 = prefix: {
316 if (system_lib.flags.needed) break :prefix "-needed-l";
317 if (system_lib.flags.weak) break :prefix "-weak-l";
318 break :prefix "-l";
319 };
320 l: {
321 pc: {
322 const force = switch (system_lib.flags.use_pkg_config) {
323 .no => break :pc,
324 .yes => false,
325 .force => true,
326 };
327
328 const pkg_conf_node = progress_node.start("pkg-config", 0);
329 defer pkg_conf_node.end();
330
331 if (PkgConfig.run(maker, step, pkg_conf_node, system_lib_name, force)) |result| {
332 try zig_args.appendSlice(gpa, result.cflags);
333 try zig_args.appendSlice(gpa, result.libs);
334 try seen_system_libs.put(arena, system_lib.name, result.cflags);
335 break :l;
336 } else |err| switch (err) {
337 error.PkgConfigUnavailable,
338 error.PackageNotFound,
339 => {
340 // pkg-config failed, so fall back to linking the library by name directly.
341 assert(!force);
342 break :pc;
343 },
344 else => |e| return e,
345 }
346 }
347 try zig_args.append(gpa, try allocPrint(arena, "{s}{s}", .{
348 prefix, system_lib_name,
349 }));
350 }
351 },
352 .other_step => |other_step_index| {
353 const other = other_step_index.ptr(conf);
354 const other_compile = other.extended.get(conf.extra).compile;
355 switch (other_compile.flags3.kind) {
356 .exe => return step.fail(maker, "cannot link with an executable build artifact", .{}),
357 .@"test" => return step.fail(maker, "cannot link with a test", .{}),
358 .obj, .test_obj => {
359 const included_in_lib_or_obj = switch (dep_compile.flags3.kind) {
360 .lib, .obj, .test_obj => !my_responsibility,
361 else => false,
362 };
363 if (!already_linked and !included_in_lib_or_obj) {
364 try zig_args.append(gpa, try maker.resolveLazyPathAbs(
365 arena,
366 .{ .generated = .{ .index = other_compile.generated_bin.value.? } },
367 compile_index,
368 ));
369 total_linker_objects += 1;
370 }
371 },
372 .lib => l: {
373 const other_produces_implib = other_compile.producesImplib(conf);
374 const other_is_static = other_produces_implib or other_compile.isStaticLibrary();
375
376 if (conf_comp.isStaticLibrary() and other_is_static) {
377 // Avoid putting a static library inside a static library.
378 break :l;
379 }
380
381 // For DLLs, we must link against the implib.
382 // For everything else, we directly link
383 // against the library file.
384 const full_path_lib = try maker.resolveLazyPathAbs(
385 arena,
386 .{ .generated = .{
387 .index = if (other_produces_implib)
388 other_compile.generated_implib.value.?
389 else
390 other_compile.generated_bin.value.?,
391 } },
392 compile_index,
393 );
394
395 try zig_args.append(gpa, full_path_lib);
396 total_linker_objects += 1;
397
398 if (other_compile.flags2.linkage == .dynamic and
399 root_module_target.flags.os_tag != .windows)
400 {
401 if (Dir.path.dirname(full_path_lib)) |dirname| {
402 try zig_args.appendSlice(gpa, &.{ "-rpath", dirname });
403 }
404 }
405 },
406 }
407 },
408 .assembly_file => |asm_file| l: {
409 if (!my_responsibility) break :l;
410
411 if (prev_has_cflags) {
412 try zig_args.appendSlice(gpa, &.{ "-cflags", "--" });
413 prev_has_cflags = false;
414 }
415 try zig_args.append(gpa, try maker.resolveLazyPathIndexAbs(arena, asm_file, compile_index));
416 total_linker_objects += 1;
417 },
418
419 .c_source_file => |c_source_file_index| l: {
420 if (!my_responsibility) break :l;
421
422 const c_source_file = c_source_file_index.get(conf);
423
424 if (prev_has_cflags or c_source_file.args.slice.len != 0) {
425 try zig_args.ensureUnusedCapacity(gpa, 2 + c_source_file.args.slice.len);
426 zig_args.appendAssumeCapacity("-cflags");
427 for (c_source_file.args.slice) |arg| {
428 zig_args.appendAssumeCapacity(arg.slice(conf));
429 }
430 zig_args.appendAssumeCapacity("--");
431 }
432 prev_has_cflags = (c_source_file.args.slice.len != 0);
433
434 if (c_source_file.flags.lang.get()) |lang|
435 (try zig_args.addManyAsArray(gpa, 2)).* = .{ "-x", lang.clangIdentifier() };
436
437 try zig_args.append(gpa, try maker.resolveLazyPathIndexAbs(arena, c_source_file.file, compile_index));
438
439 if (c_source_file.flags.lang != .default)
440 (try zig_args.addManyAsArray(gpa, 2)).* = .{ "-x", "none" };
441
442 total_linker_objects += 1;
443 },
444
445 .c_source_files => |c_source_files_index| l: {
446 if (!my_responsibility) break :l;
447
448 const c_source_files = c_source_files_index.get(conf);
449
450 if (prev_has_cflags or c_source_files.args.slice.len != 0) {
451 try zig_args.ensureUnusedCapacity(gpa, 2 + c_source_files.args.slice.len);
452 zig_args.appendAssumeCapacity("-cflags");
453 for (c_source_files.args.slice) |arg| {
454 zig_args.appendAssumeCapacity(arg.slice(conf));
455 }
456 zig_args.appendAssumeCapacity("--");
457 }
458 prev_has_cflags = (c_source_files.args.slice.len != 0);
459
460 if (c_source_files.flags.lang.get()) |lang|
461 (try zig_args.addManyAsArray(gpa, 2)).* = .{ "-x", lang.clangIdentifier() };
462
463 const root_path = try maker.resolveLazyPathIndexAbs(arena, c_source_files.root, compile_index);
464 try zig_args.ensureUnusedCapacity(gpa, c_source_files.sub_paths.slice.len);
465 for (c_source_files.sub_paths.slice) |sub_path| {
466 zig_args.appendAssumeCapacity(try Dir.path.join(arena, &.{
467 root_path, sub_path.slice(conf),
468 }));
469 }
470
471 if (c_source_files.flags.lang != .default)
472 (try zig_args.addManyAsArray(gpa, 2)).* = .{ "-x", "none" };
473
474 total_linker_objects += c_source_files.sub_paths.slice.len;
475 },
476
477 .win32_resource_file => |rc_source_file_index| l: {
478 if (!my_responsibility) break :l;
479
480 const rc_source_file = rc_source_file_index.get(conf);
481
482 if (rc_source_file.args.slice.len == 0 and rc_source_file.include_paths.slice.len == 0) {
483 if (prev_has_rcflags) {
484 (try zig_args.addManyAsArray(gpa, 2)).* = .{ "-rcflags", "--" };
485 prev_has_rcflags = false;
486 }
487 } else {
488 try zig_args.ensureUnusedCapacity(gpa, 1 + rc_source_file.args.slice.len);
489 zig_args.appendAssumeCapacity("-rcflags");
490 for (rc_source_file.args.slice) |arg| {
491 zig_args.appendAssumeCapacity(arg.slice(conf));
492 }
493 try zig_args.ensureUnusedCapacity(gpa, 1 + 2 * rc_source_file.include_paths.slice.len);
494 for (rc_source_file.include_paths.slice) |include_path| {
495 zig_args.appendAssumeCapacity("/I");
496 zig_args.appendAssumeCapacity(try maker.resolveLazyPathIndexAbs(arena, include_path, compile_index));
497 }
498 zig_args.appendAssumeCapacity("--");
499 prev_has_rcflags = true;
500 }
501 try zig_args.append(gpa, try maker.resolveLazyPathIndexAbs(arena, rc_source_file.file, compile_index));
502 total_linker_objects += 1;
503 },
504 };
505
506 // We need to emit the --mod argument here so that the above link objects
507 // have the correct parent module, but only if the module is part of
508 // this compilation.
509 if (!my_responsibility) continue;
510 if (cli_named_modules.modules.getIndex(mod_index)) |module_cli_index| {
511 const module_cli_name = cli_named_modules.names.keys()[module_cli_index];
512 const module_index = cli_named_modules.modules.keys()[module_cli_index];
513 try appendModuleFlags(arena, module_index, zig_args, compile_index, maker);
514
515 const imports = mod.import_table.get(conf).imports.mal;
516
517 // --dep arguments
518 try zig_args.ensureUnusedCapacity(gpa, imports.len * 2);
519 for (imports.items(.name), imports.items(.module)) |name, import| {
520 const import_index = cli_named_modules.modules.getIndex(import).?;
521 const import_cli_name = cli_named_modules.names.keys()[import_index];
522 zig_args.appendAssumeCapacity("--dep");
523 const name_slice = name.slice(conf);
524 if (mem.eql(u8, import_cli_name, name_slice)) {
525 zig_args.appendAssumeCapacity(import_cli_name);
526 } else {
527 zig_args.appendAssumeCapacity(try allocPrint(arena, "{s}={s}", .{
528 name_slice, import_cli_name,
529 }));
530 }
531 }
532
533 // When the CLI sees a -M argument, it determines whether it
534 // implies the existence of a Zig compilation unit based on
535 // whether there is a root source file. If there is no root
536 // source file, then this is not a zig compilation unit - it is
537 // perhaps a set of linker objects, or C source files instead.
538 // Linker objects are added to the CLI globally, while C source
539 // files must have a module parent.
540 try zig_args.ensureUnusedCapacity(gpa, 1);
541 if (mod.root_source_file.unwrap()) |lp| {
542 const src = try maker.resolveLazyPathIndexAbs(arena, lp, compile_index);
543 zig_args.appendAssumeCapacity(try allocPrint(arena, "-M{s}={s}", .{ module_cli_name, src }));
544 } else if (moduleNeedsCliArg(&mod, conf)) {
545 zig_args.appendAssumeCapacity(try allocPrint(arena, "-M{s}", .{module_cli_name}));
546 }
547 }
548 }
549 }
550
551 if (total_linker_objects == 0) {
552 return step.fail(maker, "the linker needs one or more objects to link", .{});
553 }
554
555 for (frameworks.keys(), frameworks.values()) |name, info| {
556 try zig_args.ensureUnusedCapacity(gpa, 2);
557 if (info.needed) {
558 zig_args.appendAssumeCapacity("-needed_framework");
559 } else if (info.weak) {
560 zig_args.appendAssumeCapacity("-weak_framework");
561 } else {
562 zig_args.appendAssumeCapacity("-framework");
563 }
564 zig_args.appendAssumeCapacity(name.slice(conf));
565 }
566
567 try zig_args.ensureUnusedCapacity(gpa, 2);
568 if (is_linking_libcpp) zig_args.appendAssumeCapacity("-lc++");
569 if (is_linking_libc) zig_args.appendAssumeCapacity("-lc");
570
571 compile.is_linking_libc = is_linking_libc;
572 }
573
574 if (conf_comp.win32_manifest.value) |manifest_file| {
575 try zig_args.append(gpa, try maker.resolveLazyPathIndexAbs(arena, manifest_file, compile_index));
576 }
577
578 if (conf_comp.win32_module_definition.value) |module_file| {
579 try zig_args.append(gpa, try maker.resolveLazyPathIndexAbs(arena, module_file, compile_index));
580 }
581
582 if (conf_comp.image_base.value) |image_base| {
583 (try zig_args.addManyAsArray(gpa, 2)).* = .{
584 "--image-base", try allocPrint(arena, "0x{x}", .{image_base}),
585 };
586 }
587
588 for (conf_comp.filters.slice) |filter| {
589 (try zig_args.addManyAsArray(gpa, 2)).* = .{ "--test-filter", filter.slice(conf) };
590 }
591
592 switch (conf_comp.test_runner.u) {
593 .default => {},
594 .simple, .server => |lp| (try zig_args.addManyAsArray(gpa, 2)).* = .{
595 "--test-runner", try maker.resolveLazyPathIndexAbs(arena, lp, compile_index),
596 },
597 }
598
599 for (graph.debug_log_scopes.items) |log_scope| {
600 (try zig_args.addManyAsArray(gpa, 2)).* = .{ "--debug-log", log_scope };
601 }
602
603 try addBool(gpa, zig_args, "--debug-compile-errors", graph.debug_compile_errors);
604 try addBool(gpa, zig_args, "--debug-incremental", graph.debug_incremental);
605 try addBool(gpa, zig_args, "--verbose-air", graph.verbose_air);
606 try addBool(gpa, zig_args, "--verbose-llvm-ir", graph.verbose_llvm_ir);
607 try addBool(gpa, zig_args, "--verbose-link", graph.verbose_link or conf_comp.flags.verbose_link);
608 try addBool(gpa, zig_args, "--verbose-cc", graph.verbose_cc or conf_comp.flags.verbose_cc);
609 try addBool(gpa, zig_args, "--verbose-llvm-cpu-features", graph.verbose_llvm_cpu_features);
610 try addBool(gpa, zig_args, "--time-report", graph.time_report);
611
612 if (conf_comp.generated_bin.value == null) try zig_args.append(gpa, "-fno-emit-bin");
613 if (conf_comp.generated_asm.value != null) try zig_args.append(gpa, "-femit-asm");
614 if (conf_comp.generated_docs.value != null) try zig_args.append(gpa, "-femit-docs");
615 if (conf_comp.generated_implib.value != null) try zig_args.append(gpa, "-femit-implib");
616 if (conf_comp.generated_llvm_bc.value != null) try zig_args.append(gpa, "-femit-llvm-bc");
617 if (conf_comp.generated_llvm_ir.value != null) try zig_args.append(gpa, "-femit-llvm-ir");
618 if (conf_comp.generated_h.value != null) try zig_args.append(gpa, "-femit-h");
619
620 try addFlag(gpa, zig_args, "formatted-panics", conf_comp.flags2.formatted_panics.toBool());
621
622 switch (conf_comp.flags3.compress_debug_sections) {
623 .none => {},
624 .zlib => try zig_args.append(gpa, "--compress-debug-sections=zlib"),
625 .zstd => try zig_args.append(gpa, "--compress-debug-sections=zstd"),
626 }
627
628 try addBool(gpa, zig_args, "--eh-frame-hdr", conf_comp.flags.link_eh_frame_hdr);
629 try addBool(gpa, zig_args, "--emit-relocs", conf_comp.flags.link_emit_relocs);
630 try addBool(gpa, zig_args, "-ffunction-sections", conf_comp.flags.link_function_sections);
631 try addBool(gpa, zig_args, "-fdata-sections", conf_comp.flags.link_data_sections);
632
633 if (conf_comp.flags2.link_gc_sections.toBool()) |x|
634 try zig_args.append(gpa, if (x) "--gc-sections" else "--no-gc-sections");
635
636 if (!conf_comp.flags.linker_dynamicbase)
637 try zig_args.append(gpa, "--no-dynamicbase");
638
639 try addFlag(gpa, zig_args, "allow-shlib-undefined", conf_comp.flags2.linker_allow_shlib_undefined.toBool());
640 if (conf_comp.flags.link_z_notext) (try zig_args.addManyAsArray(gpa, 2)).* = .{ "-z", "notext" };
641 if (!conf_comp.flags.link_z_relro) (try zig_args.addManyAsArray(gpa, 2)).* = .{ "-z", "norelro" };
642 if (conf_comp.flags.link_z_lazy) (try zig_args.addManyAsArray(gpa, 2)).* = .{ "-z", "lazy" };
643 if (conf_comp.link_z_common_page_size.value) |size| (try zig_args.addManyAsArray(gpa, 2)).* = .{
644 "-z", try allocPrint(arena, "common-page-size={d}", .{size}),
645 };
646 if (conf_comp.link_z_max_page_size.value) |size| (try zig_args.addManyAsArray(gpa, 2)).* = .{
647 "-z", try allocPrint(arena, "max-page-size={d}", .{size}),
648 };
649 if (conf_comp.flags.link_z_defs) (try zig_args.addManyAsArray(gpa, 2)).* = .{ "-z", "defs" };
650
651 try zig_args.ensureUnusedCapacity(gpa, 2);
652 if (conf_comp.libc_file.value) |libc_file| {
653 zig_args.appendAssumeCapacity("--libc");
654 zig_args.appendAssumeCapacity(try maker.resolveLazyPathIndexAbs(arena, libc_file, compile_index));
655 } else if (graph.libc_file) |libc_file| {
656 zig_args.appendAssumeCapacity("--libc");
657 zig_args.appendAssumeCapacity(libc_file);
658 }
659
660 (try zig_args.addManyAsArray(gpa, 4)).* = .{
661 "--cache-dir", graph.local_cache_root.path orelse ".",
662 "--global-cache-dir", graph.global_cache_root.path orelse ".",
663 };
664
665 try zig_args.ensureUnusedCapacity(gpa, 1);
666 if (graph.debug_compiler_runtime_libs) |mode| switch (mode) {
667 .Debug => zig_args.appendAssumeCapacity("--debug-rt"),
668 else => zig_args.appendAssumeCapacity(try allocPrint(arena, "--debug-rt={t}", .{mode})),
669 };
670
671 {
672 try zig_args.ensureUnusedCapacity(gpa, 7);
673
674 zig_args.addManyAsArrayAssumeCapacity(2).* = .{ "--name", conf_comp.root_name.slice(conf) };
675
676 switch (conf_comp.flags2.linkage) {
677 .dynamic => zig_args.appendAssumeCapacity("-dynamic"),
678 .static => zig_args.appendAssumeCapacity("-static"),
679 .default => {},
680 }
681
682 if (conf_comp.flags3.kind == .lib and conf_comp.flags2.linkage == .dynamic) {
683 if (conf_comp.version.value) |version| zig_args.addManyAsArrayAssumeCapacity(2).* = .{
684 "--version", version.slice(conf),
685 };
686
687 const os_tag = root_module_target.flags.os_tag.unwrap().?;
688 if (os_tag.isDarwin()) {
689 const abi = root_module_target.flags.abi.unwrap().?;
690 zig_args.addManyAsArrayAssumeCapacity(2).* = .{
691 "-install_name",
692 if (conf_comp.install_name.value) |s| s.slice(conf) else try allocPrint(
693 arena,
694 "@rpath/{s}{s}{s}",
695 .{
696 os_tag.libPrefix(abi),
697 conf_comp.root_name.slice(conf),
698 os_tag.dynamicLibSuffix(),
699 },
700 ),
701 };
702 }
703 }
704 }
705
706 if (conf_comp.entitlements.value) |entitlements| {
707 (try zig_args.addManyAsArray(gpa, 2)).* = .{
708 "--entitlements", try maker.resolveLazyPathIndexAbs(arena, entitlements, compile_index),
709 };
710 }
711 if (conf_comp.pagezero_size.value) |pagezero_size| {
712 (try zig_args.addManyAsArray(gpa, 2)).* = .{
713 "-pagezero_size", try allocPrint(arena, "{x}", .{pagezero_size}),
714 };
715 }
716 if (conf_comp.headerpad_size.value) |headerpad_size| {
717 (try zig_args.addManyAsArray(gpa, 2)).* = .{
718 "-headerpad", try allocPrint(arena, "{x}", .{headerpad_size}),
719 };
720 }
721 try addBool(gpa, zig_args, "-headerpad_max_install_names", conf_comp.flags.headerpad_max_install_names);
722 try addBool(gpa, zig_args, "-dead_strip_dylibs", conf_comp.flags.dead_strip_dylibs);
723 try addBool(gpa, zig_args, "-ObjC", conf_comp.flags.force_load_objc);
724 try addBool(gpa, zig_args, "--discard-all", conf_comp.flags.discard_local_symbols);
725
726 try addFlag(gpa, zig_args, "compiler-rt", conf_comp.flags2.bundle_compiler_rt.toBool());
727 try addFlag(gpa, zig_args, "ubsan-rt", conf_comp.flags2.bundle_ubsan_rt.toBool());
728 try addFlag(gpa, zig_args, "dll-export-fns", conf_comp.flags2.dll_export_fns.toBool());
729
730 try addBool(gpa, zig_args, "-rdynamic", conf_comp.flags.rdynamic);
731 try addBool(gpa, zig_args, "--import-memory", conf_comp.flags.import_memory);
732 try addBool(gpa, zig_args, "--export-memory", conf_comp.flags.export_memory);
733 try addBool(gpa, zig_args, "--import-symbols", conf_comp.flags.import_symbols);
734 try addBool(gpa, zig_args, "--import-table", conf_comp.flags.import_table);
735 try addBool(gpa, zig_args, "--export-table", conf_comp.flags.export_table);
736 try addBool(gpa, zig_args, "--shared-memory", conf_comp.flags.shared_memory);
737
738 {
739 try zig_args.ensureUnusedCapacity(gpa, 4);
740 if (conf_comp.initial_memory.value) |initial_memory| {
741 zig_args.appendAssumeCapacity(try allocPrint(arena, "--initial-memory={d}", .{initial_memory}));
742 }
743 if (conf_comp.max_memory.value) |max_memory| {
744 zig_args.appendAssumeCapacity(try allocPrint(arena, "--max-memory={d}", .{max_memory}));
745 }
746 if (conf_comp.global_base.value) |global_base| {
747 zig_args.appendAssumeCapacity(try allocPrint(arena, "--global-base={d}", .{global_base}));
748 }
749 switch (conf_comp.flags3.wasi_exec_model) {
750 .default => {},
751 .command => zig_args.appendAssumeCapacity("-mexec-model=command"),
752 .reactor => zig_args.appendAssumeCapacity("-mexec-model=reactor"),
753 }
754 }
755
756 if (conf_comp.linker_script.value) |linker_script| (try zig_args.addManyAsArray(gpa, 2)).* = .{
757 "--script", try maker.resolveLazyPathIndexAbs(arena, linker_script, compile_index),
758 };
759 if (conf_comp.version_script.value) |version_script| (try zig_args.addManyAsArray(gpa, 2)).* = .{
760 "--version-script", try maker.resolveLazyPathIndexAbs(arena, version_script, compile_index),
761 };
762 if (conf_comp.flags2.linker_allow_undefined_version.toBool()) |x| {
763 try zig_args.append(gpa, if (x) "--undefined-version" else "--no-undefined-version");
764 }
765
766 if (conf_comp.flags2.linker_enable_new_dtags.toBool()) |enabled| {
767 try zig_args.append(gpa, if (enabled) "--enable-new-dtags" else "--disable-new-dtags");
768 }
769
770 if (conf_comp.flags3.kind == .@"test" and conf_comp.exec_cmd_args.slice.len != 0) {
771 for (conf_comp.exec_cmd_args.slice) |cmd_arg| {
772 try zig_args.ensureUnusedCapacity(gpa, 2);
773 if (cmd_arg.slice(conf)) |arg| {
774 zig_args.appendAssumeCapacity("--test-cmd");
775 zig_args.appendAssumeCapacity(arg);
776 } else {
777 zig_args.appendAssumeCapacity("--test-cmd-bin");
778 }
779 }
780 }
781
782 if (graph.sysroot) |sysroot| try zig_args.appendSlice(gpa, &.{ "--sysroot", sysroot });
783
784 // -I and -L arguments that appear after the last --mod argument apply to all modules.
785 const cwd: Io.Dir = .cwd();
786 const io = graph.io;
787
788 for (graph.search_prefixes.items) |search_prefix| {
789 var prefix_dir = cwd.openDir(io, search_prefix, .{}) catch |err| {
790 return step.fail(maker, "unable to open prefix directory '{s}': {t}", .{ search_prefix, err });
791 };
792 defer prefix_dir.close(io);
793
794 // Avoid passing -L and -I flags for nonexistent directories.
795 // This prevents a warning, that should probably be upgraded to an error in Zig's
796 // CLI parsing code, when the linker sees an -L directory that does not exist.
797
798 if (prefix_dir.access(io, "lib", .{})) |_| {
799 try zig_args.appendSlice(gpa, &.{
800 "-L", try Dir.path.join(arena, &.{ search_prefix, "lib" }),
801 });
802 } else |err| switch (err) {
803 error.FileNotFound => {},
804 else => |e| return step.fail(maker, "unable to access '{s}/lib' directory: {t}", .{ search_prefix, e }),
805 }
806
807 if (prefix_dir.access(io, "include", .{})) |_| {
808 try zig_args.appendSlice(gpa, &.{
809 "-I", try Dir.path.join(arena, &.{ search_prefix, "include" }),
810 });
811 } else |err| switch (err) {
812 error.FileNotFound => {},
813 else => |e| return step.fail(maker, "unable to access '{s}/include' directory: {t}", .{ search_prefix, e }),
814 }
815 }
816
817 if (conf_comp.flags3.rc_includes != .any) (try zig_args.addManyAsArray(gpa, 2)).* = .{
818 "-rcincludes", @tagName(conf_comp.flags3.rc_includes),
819 };
820
821 try addFlag(gpa, zig_args, "each-lib-rpath", conf_comp.flags2.each_lib_rpath.toBool());
822
823 if (conf_comp.flags3.build_id.unwrap(conf_comp.build_id.value, conf) orelse graph.build_id) |build_id| {
824 try zig_args.append(gpa, switch (build_id) {
825 .hexstring => |hs| try allocPrint(arena, "--build-id=0x{x}", .{hs.toSlice()}),
826 .none, .fast, .uuid, .sha1, .md5 => try allocPrint(arena, "--build-id={t}", .{build_id}),
827 });
828 }
829
830 const opt_zig_lib_dir: ?[]const u8 = if (conf_comp.zig_lib_dir.value) |dir|
831 try maker.resolveLazyPathIndexAbs(arena, dir, compile_index)
832 else if (graph.zig_lib_directory.path) |_|
833 try allocPrint(arena, "{f}", .{graph.zig_lib_directory})
834 else
835 null;
836
837 if (opt_zig_lib_dir) |zig_lib_dir| (try zig_args.addManyAsArray(gpa, 2)).* = .{
838 "--zig-lib-dir", zig_lib_dir,
839 };
840
841 try addFlag(gpa, zig_args, "PIE", conf_comp.flags2.pie.toBool());
842
843 try zig_args.ensureUnusedCapacity(gpa, 1);
844 switch (conf_comp.flags3.lto) {
845 .full => zig_args.appendAssumeCapacity("-flto=full"),
846 .thin => zig_args.appendAssumeCapacity("-flto=thin"),
847 .none => zig_args.appendAssumeCapacity("-fno-lto"),
848 .default => {},
849 }
850
851 try addFlag(gpa, zig_args, "sanitize-coverage-trace-pc-guard", conf_comp.flags2.sanitize_coverage_trace_pc_guard.toBool());
852
853 switch (conf_comp.flags3.subsystem) {
854 .default => {},
855 else => |t| (try zig_args.addManyAsArray(gpa, 2)).* = .{ "--subsystem", @tagName(t) },
856 }
857
858 try addBool(gpa, zig_args, "-municode", conf_comp.flags.mingw_unicode_entry_point);
859
860 if (conf_comp.error_limit.value orelse graph.error_limit) |err_limit| (try zig_args.addManyAsArray(gpa, 2)).* = .{
861 "--error-limit", try allocPrint(arena, "{d}", .{err_limit}),
862 };
863
864 try addFlag(gpa, zig_args, "incremental", graph.incremental);
865
866 try zig_args.append(gpa, "--listen=-");
867
868 // Windows has an argument length limit of 32,766 characters, macOS 262,144 and Linux
869 // 2,097,152. If our args exceed 30 KiB, we instead write them to a "response file" and
870 // pass that to zig, e.g. via 'zig build-lib @args.rsp'
871 // See @file syntax here: https://gcc.gnu.org/onlinedocs/gcc/Overall-Options.html
872 var args_length: usize = 0;
873 for (zig_args.items) |arg| {
874 args_length += arg.len + 1; // +1 to account for null terminator
875 }
876 if (args_length >= 30 * 1024) {
877 const local_cache_root = graph.local_cache_root;
878 const args_path: Path = .{ .root_dir = local_cache_root, .sub_path = "args" };
879 args_path.root_dir.handle.createDirPath(io, args_path.sub_path) catch |err|
880 return step.fail(maker, "failed creating directory {f}: {t}", .{ args_path, err });
881
882 const args_to_escape = zig_args.items[2..];
883 var escaped_args = try std.array_list.Managed([]const u8).initCapacity(arena, args_to_escape.len);
884 arg_blk: for (args_to_escape) |arg| {
885 for (arg, 0..) |c, arg_idx| {
886 if (c == '\\' or c == '"') {
887 // Slow path for arguments that need to be escaped. We'll need to allocate and copy
888 var escaped: std.ArrayList(u8) = .empty;
889 try escaped.ensureTotalCapacityPrecise(arena, arg.len + 1);
890 try escaped.appendSlice(arena, arg[0..arg_idx]);
891 for (arg[arg_idx..]) |to_escape| {
892 if (to_escape == '\\' or to_escape == '"') try escaped.append(arena, '\\');
893 try escaped.append(arena, to_escape);
894 }
895 escaped_args.appendAssumeCapacity(escaped.items);
896 continue :arg_blk;
897 }
898 }
899 escaped_args.appendAssumeCapacity(arg); // no escaping needed so just use original argument
900 }
901
902 // Write the args to zig-cache/args/<SHA256 hash of args> to avoid conflicts with
903 // other zig build commands running in parallel.
904 const partially_quoted = try mem.join(arena, "\" \"", escaped_args.items);
905 const args = try mem.concat(arena, u8, &[_][]const u8{ "\"", partially_quoted, "\"" });
906
907 var args_hash: [Sha256.digest_length]u8 = undefined;
908 Sha256.hash(args, &args_hash, .{});
909 var args_hex_hash: [Sha256.digest_length * 2]u8 = undefined;
910 _ = std.fmt.bufPrint(&args_hex_hash, "{x}", .{&args_hash}) catch unreachable;
911
912 const args_file = "args" ++ Dir.path.sep_str ++ args_hex_hash;
913 local_cache_root.handle.access(io, args_file, .{}) catch {
914 var af = local_cache_root.handle.createFileAtomic(io, args_file, .{
915 .replace = false,
916 .make_path = true,
917 }) catch |e| return step.fail(maker, "failed creating tmp args file {f}{s}: {t}", .{
918 local_cache_root, args_file, e,
919 });
920 defer af.deinit(io);
921
922 af.file.writeStreamingAll(io, args) catch |e| {
923 return step.fail(maker, "failed writing args data to tmp file {f}{s}: {t}", .{
924 local_cache_root, args_file, e,
925 });
926 };
927 // Note we can't clean up this file, not even after build
928 // success, because that might interfere with another build
929 // process that needs the same file.
930 af.link(io) catch |e| switch (e) {
931 error.PathAlreadyExists => {
932 // The args file was created by another concurrent build process.
933 },
934 else => |other_err| return step.fail(maker, "failed linking tmp file {f}{s}: {t}", .{
935 local_cache_root, args_file, other_err,
936 }),
937 };
938 };
939
940 const resolved_args_file = try mem.concat(arena, u8, &.{
941 "@", try local_cache_root.join(arena, &.{args_file}),
942 });
943
944 zig_args.shrinkRetainingCapacity(2);
945 try zig_args.append(gpa, resolved_args_file);
946 }
947}
948
949pub fn rebuildInFuzzMode(
950 compile: *Compile,
951 maker: *Maker,
952 compile_index: Configuration.Step.Index,
953 progress_node: std.Progress.Node,
954) !Path {
955 const gpa = maker.gpa;
956 const step = maker.stepByIndex(compile_index);
957
958 var arena_allocator: std.heap.ArenaAllocator = .init(gpa);
959 defer arena_allocator.deinit();
960 const arena = arena_allocator.allocator();
961
962 step.result_error_msgs.clearRetainingCapacity();
963 step.result_stderr = "";
964
965 step.result_error_bundle.deinit(gpa);
966 step.result_error_bundle = std.zig.ErrorBundle.empty;
967
968 step.clearFailedCommand(gpa);
969
970 var argv: std.ArrayList([]const u8) = .empty;
971 defer argv.deinit(gpa);
972
973 try lowerZigArgs(arena, compile, compile_index, maker, progress_node, &argv, true);
974 const maybe_output_bin_path = try Step.evalZigProcess(compile_index, maker, argv.items, progress_node, false);
975 return maybe_output_bin_path.?;
976}
977
978fn addBool(gpa: Allocator, args: *std.ArrayList([]const u8), arg: []const u8, opt: bool) !void {
979 if (opt) try args.append(gpa, arg);
980}
981
982fn addFlag(gpa: Allocator, args: *std.ArrayList([]const u8), comptime name: []const u8, opt: ?bool) !void {
983 const cond = opt orelse return;
984 try args.append(gpa, if (cond) "-f" ++ name else "-fno-" ++ name);
985}
986
987fn checkCompileErrors(arena: Allocator, maker: *Maker, step_index: Configuration.Step.Index) Step.ExtendedMakeError!void {
988 const step = maker.stepByIndex(step_index);
989 const conf = &maker.scanned_config.configuration;
990 const conf_step = step_index.ptr(conf);
991 const conf_comp = conf_step.extended.get(conf.extra).compile;
992
993 // Clear this field so that it does not get printed by the build runner.
994 var actual_eb = step.result_error_bundle;
995 step.result_error_bundle = .empty;
996 defer actual_eb.deinit(maker.gpa);
997
998 const actual_errors = ae: {
999 var aw: std.Io.Writer.Allocating = .init(arena);
1000 defer aw.deinit();
1001 actual_eb.renderToWriter(.{
1002 .include_reference_trace = false,
1003 .include_source_line = false,
1004 }, &aw.writer) catch |err| switch (err) {
1005 error.WriteFailed => return error.OutOfMemory,
1006 };
1007 break :ae try aw.toOwnedSlice();
1008 };
1009
1010 // Render the expected lines into a string that we can compare verbatim.
1011 var expected_generated: std.ArrayList(u8) = .empty;
1012 var actual_line_it = mem.splitScalar(u8, actual_errors, '\n');
1013
1014 switch (conf_comp.expect_errors.u) {
1015 .none => unreachable,
1016 .starts_with => |expect_starts_with_string| {
1017 const expect_starts_with = expect_starts_with_string.slice(conf);
1018 if (mem.startsWith(u8, actual_errors, expect_starts_with)) return;
1019 return step.fail(maker,
1020 \\
1021 \\========= should start with: ============
1022 \\{s}
1023 \\========= but not found: ================
1024 \\{s}
1025 \\=========================================
1026 , .{ expect_starts_with, actual_errors });
1027 },
1028 .contains => |expect_line_string| {
1029 const expect_line = expect_line_string.slice(conf);
1030 while (actual_line_it.next()) |actual_line| {
1031 if (!matchCompileError(actual_line, expect_line)) continue;
1032 return;
1033 }
1034
1035 return step.fail(maker,
1036 \\
1037 \\========= should contain: ===============
1038 \\{s}
1039 \\========= but not found: ================
1040 \\{s}
1041 \\=========================================
1042 , .{ expect_line, actual_errors });
1043 },
1044 .stderr_contains => |expect_line_string| {
1045 const expect_line = expect_line_string.slice(conf);
1046 const actual_stderr: []const u8 = if (step.result_error_msgs.items.len > 0)
1047 step.result_error_msgs.items[0]
1048 else
1049 &.{};
1050 step.result_error_msgs.clearRetainingCapacity();
1051
1052 var stderr_line_it = mem.splitScalar(u8, actual_stderr, '\n');
1053
1054 while (stderr_line_it.next()) |actual_line| {
1055 if (!matchCompileError(actual_line, expect_line)) continue;
1056 return;
1057 }
1058
1059 return step.fail(maker,
1060 \\
1061 \\========= should contain: ===============
1062 \\{s}
1063 \\========= but not found: ================
1064 \\{s}
1065 \\=========================================
1066 , .{ expect_line, actual_stderr });
1067 },
1068 .exact => |expect_lines| {
1069 for (expect_lines.slice) |expect_line_string| {
1070 const expect_line = expect_line_string.slice(conf);
1071 const actual_line = actual_line_it.next() orelse {
1072 try expected_generated.appendSlice(arena, expect_line);
1073 try expected_generated.append(arena, '\n');
1074 continue;
1075 };
1076 if (matchCompileError(actual_line, expect_line)) {
1077 try expected_generated.appendSlice(arena, actual_line);
1078 try expected_generated.append(arena, '\n');
1079 continue;
1080 }
1081 try expected_generated.appendSlice(arena, expect_line);
1082 try expected_generated.append(arena, '\n');
1083 }
1084
1085 if (mem.eql(u8, expected_generated.items, actual_errors)) return;
1086
1087 return step.fail(maker,
1088 \\
1089 \\========= expected: =====================
1090 \\{s}
1091 \\========= but found: ====================
1092 \\{s}
1093 \\=========================================
1094 , .{ expected_generated.items, actual_errors });
1095 },
1096 }
1097}
1098
1099fn matchCompileError(actual: []const u8, expected: []const u8) bool {
1100 if (mem.endsWith(u8, actual, expected)) return true;
1101 if (mem.startsWith(u8, expected, ":?:?: ")) {
1102 if (mem.endsWith(u8, actual, expected[":?:?: ".len..])) return true;
1103 }
1104 // We scan for /?/ in expected line and if there is a match, we match everything
1105 // up to and after /?/.
1106 const expected_trim = mem.trim(u8, expected, " ");
1107 if (mem.find(u8, expected_trim, "/?/")) |index| {
1108 const actual_trim = mem.trim(u8, actual, " ");
1109 const lhs = expected_trim[0..index];
1110 const rhs = expected_trim[index + "/?/".len ..];
1111 if (mem.startsWith(u8, actual_trim, lhs) and mem.endsWith(u8, actual_trim, rhs)) return true;
1112 }
1113 return false;
1114}
1115
1116fn moduleNeedsCliArg(mod: *const Configuration.Module, conf: *const Configuration) bool {
1117 return for (0..mod.link_objects.len) |i| switch (mod.link_objects.tag(conf.extra, i)) {
1118 .c_source_file, .c_source_files, .assembly_file, .win32_resource_file => break true,
1119 else => continue,
1120 } else false;
1121}
1122
1123const CliNamedModules = struct {
1124 modules: std.AutoArrayHashMapUnmanaged(Configuration.Module.Index, void),
1125 names: std.StringArrayHashMapUnmanaged(void),
1126
1127 /// Traverse the whole dependency graph and give every module a unique
1128 /// name, ideally one named after what it's called somewhere in the graph.
1129 /// It will help here to have both a mapping from module to name and a set
1130 /// of all the currently-used names.
1131 fn init(
1132 arena: Allocator,
1133 module_graph: *ModuleGraph,
1134 compile_index: Configuration.Step.Index,
1135 maker: *const Maker,
1136 ) Allocator.Error!CliNamedModules {
1137 const conf = &maker.scanned_config.configuration;
1138 const conf_compile = compile_index.ptr(conf).extended.get(conf.extra).compile;
1139
1140 var result: CliNamedModules = .{
1141 .modules = .{},
1142 .names = .{},
1143 };
1144 const modules = try getModuleList(arena, module_graph, conf_compile.root_module, conf);
1145 {
1146 assert(conf_compile.root_module == modules.keys()[0]);
1147 try result.modules.put(arena, conf_compile.root_module, {});
1148 try result.names.put(arena, "root", {});
1149 }
1150 for (modules.keys()[1..], modules.values()[1..]) |mod, orig_name| {
1151 const orig_name_slice = orig_name.slice(conf);
1152 var name: []const u8 = orig_name_slice;
1153 var n: usize = 0;
1154 while (true) {
1155 const gop = try result.names.getOrPut(arena, name);
1156 if (!gop.found_existing) {
1157 try result.modules.putNoClobber(arena, mod, {});
1158 break;
1159 }
1160 name = try allocPrint(arena, "{s}{d}", .{ orig_name_slice, n });
1161 n += 1;
1162 }
1163 }
1164 return result;
1165 }
1166};
1167
1168pub fn getCompileDependencies(
1169 arena: Allocator,
1170 module_graph: *ModuleGraph,
1171 conf: *const Configuration,
1172 start: Configuration.Step.Index,
1173 chase_dynamic: bool,
1174) ![]const Configuration.Step.Index {
1175 var compiles: std.AutoArrayHashMapUnmanaged(Configuration.Step.Index, void) = .empty;
1176 var compiles_i: usize = 0;
1177
1178 try compiles.putNoClobber(arena, start, {});
1179
1180 while (compiles_i < compiles.count()) : (compiles_i += 1) {
1181 const step = compiles.keys()[compiles_i].ptr(conf);
1182 const compile = step.extended.get(conf.extra).compile;
1183 const modules = try getModuleList(arena, module_graph, compile.root_module, conf);
1184
1185 for (modules.keys()) |mod_index| {
1186 const mod = mod_index.get(conf);
1187 for (0..mod.link_objects.len) |i| {
1188 switch (mod.link_objects.get(conf.extra, i)) {
1189 .other_step => |other_compile_index| {
1190 const other_compile = other_compile_index.ptr(conf).extended.get(conf.extra).compile;
1191 if (!chase_dynamic and other_compile.isDynamicLibrary()) continue;
1192 try compiles.put(arena, other_compile_index, {});
1193 },
1194 else => {},
1195 }
1196 }
1197 }
1198 }
1199
1200 return compiles.keys();
1201}
1202
1203/// Returned pointer expires upon next call to `getModuleList`.
1204fn getModuleList(
1205 arena: Allocator,
1206 module_graph: *ModuleGraph,
1207 root_module: Configuration.Module.Index,
1208 conf: *const Configuration,
1209) !*ModuleList {
1210 const gop = try module_graph.getOrPutAdapted(arena, root_module, @as(ModuleListContext.Adapter, .{}));
1211 const modules = gop.key_ptr;
1212
1213 if (gop.found_existing) return modules;
1214 modules.* = .empty;
1215 try modules.putNoClobber(arena, root_module, .root);
1216
1217 var i: usize = 0;
1218
1219 while (i < modules.entries.len) : (i += 1) {
1220 const dep_index = modules.keys()[i];
1221 const dep = dep_index.get(conf);
1222 const imports = dep.import_table.get(conf).imports;
1223 try modules.ensureUnusedCapacity(arena, imports.mal.len);
1224 for (imports.mal.items(.name), imports.mal.items(.module)) |import_name, other_mod|
1225 modules.putAssumeCapacity(other_mod, import_name);
1226 }
1227
1228 return modules;
1229}
1230
1231fn appendModuleFlags(
1232 arena: Allocator,
1233 module_index: Configuration.Module.Index,
1234 zig_args: *std.ArrayList([]const u8),
1235 asking_step: Configuration.Step.Index,
1236 maker: *const Maker,
1237) !void {
1238 const gpa = maker.gpa;
1239 const conf = &maker.scanned_config.configuration;
1240 const m = module_index.get(conf);
1241
1242 try addFlag(gpa, zig_args, "strip", m.flags.strip.toBool());
1243 try addFlag(gpa, zig_args, "single-threaded", m.flags.single_threaded.toBool());
1244 try addFlag(gpa, zig_args, "stack-check", m.flags.stack_check.toBool());
1245 try addFlag(gpa, zig_args, "stack-protector", m.flags.stack_protector.toBool());
1246 try addFlag(gpa, zig_args, "omit-frame-pointer", m.flags2.omit_frame_pointer.toBool());
1247 try addFlag(gpa, zig_args, "error-tracing", m.flags2.error_tracing.toBool());
1248 try addFlag(gpa, zig_args, "sanitize-thread", m.flags.sanitize_thread.toBool());
1249 try addFlag(gpa, zig_args, "fuzz", m.flags.fuzz.toBool());
1250 try addFlag(gpa, zig_args, "valgrind", m.flags2.valgrind.toBool());
1251 try addFlag(gpa, zig_args, "PIC", m.flags2.pic.toBool());
1252 try addFlag(gpa, zig_args, "red-zone", m.flags2.red_zone.toBool());
1253 try addFlag(gpa, zig_args, "no-builtin", m.flags2.no_builtin.toBool());
1254
1255 {
1256 try zig_args.ensureUnusedCapacity(gpa, 6);
1257
1258 switch (m.flags.sanitize_c) {
1259 .off => zig_args.appendAssumeCapacity("-fno-sanitize-c"),
1260 .trap => zig_args.appendAssumeCapacity("-fsanitize-c=trap"),
1261 .full => zig_args.appendAssumeCapacity("-fsanitize-c=full"),
1262 .default => {},
1263 }
1264
1265 switch (m.flags.dwarf_format) {
1266 .@"32" => zig_args.appendAssumeCapacity("-gdwarf32"),
1267 .@"64" => zig_args.appendAssumeCapacity("-gdwarf64"),
1268 .default => {},
1269 }
1270
1271 switch (m.flags.unwind_tables) {
1272 .none => zig_args.appendAssumeCapacity("-fno-unwind-tables"),
1273 .sync => zig_args.appendAssumeCapacity("-funwind-tables"),
1274 .async => zig_args.appendAssumeCapacity("-fasync-unwind-tables"),
1275 .default => {},
1276 }
1277
1278 switch (m.flags.optimize) {
1279 .debug => zig_args.appendAssumeCapacity("-ODebug"),
1280 .safe => zig_args.appendAssumeCapacity("-OReleaseSafe"),
1281 .fast => zig_args.appendAssumeCapacity("-OReleaseFast"),
1282 .small => zig_args.appendAssumeCapacity("-OReleaseSmall"),
1283 .default => {},
1284 }
1285
1286 if (m.flags.code_model != .default) {
1287 zig_args.appendAssumeCapacity("-mcmodel");
1288 zig_args.appendAssumeCapacity(@tagName(m.flags.code_model));
1289 }
1290 }
1291
1292 if (m.resolved_target.get(conf)) |resolved_target| {
1293 // Communicate the query via CLI since it's more compact.
1294 if (resolved_target.query.get(conf)) |compact_query| {
1295 try zig_args.ensureUnusedCapacity(gpa, 6);
1296
1297 const query = compact_query.unwrap(conf);
1298
1299 zig_args.appendAssumeCapacity("-target");
1300 zig_args.appendAssumeCapacity(try query.zigTriple(arena));
1301
1302 zig_args.appendAssumeCapacity("-mcpu");
1303 zig_args.appendAssumeCapacity(try query.serializeCpuAlloc(arena));
1304
1305 if (query.dynamic_linker) |*dynamic_linker| {
1306 if (dynamic_linker.get()) |dynamic_linker_path| {
1307 zig_args.appendAssumeCapacity("--dynamic-linker");
1308 zig_args.appendAssumeCapacity(dynamic_linker_path);
1309 } else {
1310 zig_args.appendAssumeCapacity("--no-dynamic-linker");
1311 }
1312 }
1313 }
1314 }
1315
1316 for (m.export_symbol_names.slice) |symbol_name| {
1317 try zig_args.append(gpa, try allocPrint(arena, "--export={s}", .{symbol_name.slice(conf)}));
1318 }
1319
1320 try zig_args.ensureUnusedCapacity(gpa, 2 * m.include_dirs.len);
1321 for (0..m.include_dirs.len) |i|
1322 try appendIncludeDirFlags(arena, m.include_dirs.get(conf.extra, i), zig_args, asking_step, maker);
1323
1324 try zig_args.ensureUnusedCapacity(gpa, m.c_macros.slice.len);
1325 for (m.c_macros.slice) |c_macro|
1326 zig_args.appendAssumeCapacity(c_macro.slice(conf));
1327
1328 try zig_args.ensureUnusedCapacity(gpa, 2 * m.lib_paths.slice.len);
1329 for (m.lib_paths.slice) |lib_path| {
1330 zig_args.appendAssumeCapacity("-L");
1331 zig_args.appendAssumeCapacity(try maker.resolveLazyPathIndexAbs(arena, lib_path, asking_step));
1332 }
1333
1334 try zig_args.ensureUnusedCapacity(gpa, 2 * m.rpaths.len);
1335 for (0..m.rpaths.len) |i| switch (m.rpaths.get(conf.extra, i)) {
1336 .lazy_path => |lp| {
1337 zig_args.appendAssumeCapacity("-rpath");
1338 zig_args.appendAssumeCapacity(try maker.resolveLazyPathIndexAbs(arena, lp, asking_step));
1339 },
1340 .special => |string| {
1341 zig_args.appendAssumeCapacity("-rpath");
1342 zig_args.appendAssumeCapacity(string.slice(conf));
1343 },
1344 };
1345}
1346
1347/// Assumes unused capacity for at least 2 items.
1348pub fn appendIncludeDirFlags(
1349 arena: Allocator,
1350 include_dir: Configuration.Module.IncludeDir,
1351 zig_args: *std.ArrayList([]const u8),
1352 asking_step: Configuration.Step.Index,
1353 maker: *const Maker,
1354) !void {
1355 const conf = &maker.scanned_config.configuration;
1356
1357 switch (include_dir) {
1358 .path => |lp| {
1359 zig_args.appendAssumeCapacity("-I");
1360 zig_args.appendAssumeCapacity(try maker.resolveLazyPathIndexAbs(arena, lp, asking_step));
1361 },
1362 .path_system => |lp| {
1363 zig_args.appendAssumeCapacity("-isystem");
1364 zig_args.appendAssumeCapacity(try maker.resolveLazyPathIndexAbs(arena, lp, asking_step));
1365 },
1366 .path_after => |lp| {
1367 zig_args.appendAssumeCapacity("-idirafter");
1368 zig_args.appendAssumeCapacity(try maker.resolveLazyPathIndexAbs(arena, lp, asking_step));
1369 },
1370 .framework_path => |lp| {
1371 zig_args.appendAssumeCapacity("-F");
1372 zig_args.appendAssumeCapacity(try maker.resolveLazyPathIndexAbs(arena, lp, asking_step));
1373 },
1374 .framework_path_system => |lp| {
1375 zig_args.appendAssumeCapacity("-iframework");
1376 zig_args.appendAssumeCapacity(try maker.resolveLazyPathIndexAbs(arena, lp, asking_step));
1377 },
1378 .config_header_step => |ch_index| {
1379 const conf_ch = ch_index.ptr(conf).extended.get(conf.extra).config_header;
1380 const path = maker.generatedPath(conf_ch.generated_dir).*;
1381 zig_args.appendAssumeCapacity("-I");
1382 zig_args.appendAssumeCapacity(try path.toString(arena));
1383 },
1384 .embed_path => |lazy_path| {
1385 zig_args.appendAssumeCapacity(try allocPrint(arena, "--embed-dir={f}", .{
1386 try maker.resolveLazyPathIndex(arena, lazy_path, asking_step),
1387 }));
1388 },
1389 }
1390}
lib/compiler/Maker/Step/ConfigHeader.zig created+610
......@@ -0,0 +1,610 @@
1const ConfigHeader = @This();
2
3const std = @import("std");
4const Io = std.Io;
5const Configuration = std.Build.Configuration;
6const Writer = std.Io.Writer;
7const Path = std.Build.Cache.Path;
8const Allocator = std.mem.Allocator;
9
10const Step = @import("../Step.zig");
11const Maker = @import("../../Maker.zig");
12
13const header_text = "This file was generated by ConfigHeader using the Zig Build System.";
14const c_generated_line = "/* " ++ header_text ++ " */\n";
15const asm_generated_line = "; " ++ header_text ++ "\n";
16
17/// Table value is whether the value is used.
18const ValueMap = std.array_hash_map.String(bool);
19const Value = Configuration.Step.ConfigHeader.Value;
20
21pub fn make(
22 config_header: *ConfigHeader,
23 step_index: Configuration.Step.Index,
24 maker: *Maker,
25 progress_node: std.Progress.Node,
26) Step.ExtendedMakeError!void {
27 _ = config_header;
28 _ = progress_node;
29 const graph = maker.graph;
30 const step = maker.stepByIndex(step_index);
31 const io = graph.io;
32 const arena = graph.arena; // TODO don't leak into the process arena
33 const conf = &maker.scanned_config.configuration;
34 const conf_step = step_index.ptr(conf);
35 const conf_ch = conf_step.extended.get(conf.extra).config_header;
36 const cache_root = graph.local_cache_root;
37
38 const input_size_limit: Io.Limit = if (conf_ch.input_size_limit.value) |x| .limited64(x) else .unlimited;
39 const include_guard_override: ?[]const u8 = if (conf_ch.include_guard.value) |s| s.slice(conf) else null;
40 const include_path: []const u8 = conf_ch.include_path.slice(conf);
41 const template_file = if (conf_ch.template_file.value) |lp|
42 try maker.resolveLazyPathIndex(arena, lp, step_index)
43 else
44 null;
45 const value_pairs = conf_ch.values.slice;
46
47 if (conf_ch.template_file.value) |lp| try step.singleUnchangingWatchInput(maker, arena, lp.get(conf));
48
49 var value_map: ValueMap = .empty;
50 try value_map.ensureTotalCapacity(arena, value_pairs.len);
51 for (value_pairs) |pair| value_map.putAssumeCapacityNoClobber(pair.key.slice(conf), false);
52
53 var man = graph.cache.obtain();
54 defer man.deinit();
55
56 // Random bytes to make ConfigHeader unique. Refresh this with new
57 // random bytes when ConfigHeader implementation is modified in a
58 // non-backwards-compatible way.
59 man.hash.add(@as(u32, 0xdef08d23));
60 man.hash.add(@as(u32, @bitCast(conf_ch.flags)));
61 man.hash.addBytes(include_path);
62 man.hash.addOptionalBytes(include_guard_override);
63
64 var aw: Writer.Allocating = .init(arena);
65 defer aw.deinit();
66
67 switch (conf_ch.flags.style) {
68 .autoconf_undef => {
69 const tf = template_file.?;
70 const contents = tf.root_dir.handle.readFileAlloc(io, tf.sub_path, arena, input_size_limit) catch |err|
71 return step.fail(maker, "unable to read autoconf input file {f}: {t}", .{ tf, err });
72 renderAutoConfUndef(maker, step, contents, &aw.writer, value_pairs, &value_map, tf) catch |err| switch (err) {
73 error.WriteFailed => return error.OutOfMemory,
74 else => |e| return e,
75 };
76 },
77 .autoconf_at => {
78 const tf = template_file.?;
79 const contents = tf.root_dir.handle.readFileAlloc(io, tf.sub_path, arena, input_size_limit) catch |err|
80 return step.fail(maker, "unable to read autoconf input file {f}: {t}", .{ tf, err });
81 renderAutoconfAt(maker, step, contents, &aw, value_pairs, &value_map, tf) catch |err| switch (err) {
82 error.WriteFailed => return error.OutOfMemory,
83 else => |e| return e,
84 };
85 },
86 .cmake => {
87 const tf = template_file.?;
88 const contents = tf.root_dir.handle.readFileAlloc(io, tf.sub_path, arena, input_size_limit) catch |err|
89 return step.fail(maker, "unable to read cmake input file {f}: {t}", .{ tf, err });
90 renderCmake(arena, maker, step, contents, &aw.writer, value_pairs, &value_map, tf) catch |err| switch (err) {
91 error.WriteFailed => return error.OutOfMemory,
92 else => |e| return e,
93 };
94 },
95 .blank => {
96 renderBlank(conf, &aw.writer, value_pairs, &value_map, include_path, include_guard_override) catch |err| switch (err) {
97 error.WriteFailed => return error.OutOfMemory,
98 else => |e| return e,
99 };
100 },
101 .nasm => {
102 renderNasm(conf, &aw.writer, value_pairs, &value_map) catch |err| switch (err) {
103 error.WriteFailed => return error.OutOfMemory,
104 else => |e| return e,
105 };
106 },
107 }
108
109 const output = aw.written();
110 man.hash.addBytes(output);
111
112 if (try step.cacheHit(maker, &man)) {
113 const digest = man.final();
114 maker.generatedPath(conf_ch.generated_dir).* = .{
115 .root_dir = cache_root,
116 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }),
117 };
118 return;
119 }
120
121 const digest = man.final();
122
123 // If output_path has directory parts, deal with them. Example:
124 // output_dir is zig-cache/o/HASH
125 // output_path is libavutil/avconfig.h
126 // We want to open directory zig-cache/o/HASH/libavutil/
127 // but keep output_dir as zig-cache/o/HASH for -I include
128 const out_path: Path = .{
129 .root_dir = cache_root,
130 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, conf_ch.include_path.slice(conf) }),
131 };
132 const out_path_dirname = out_path.dirname().?;
133
134 out_path_dirname.root_dir.handle.createDirPath(io, out_path_dirname.sub_path) catch |err|
135 return step.fail(maker, "unable to make path {f}: {t}", .{ out_path_dirname, err });
136
137 out_path.root_dir.handle.writeFile(io, .{ .sub_path = out_path.sub_path, .data = output }) catch |err|
138 return step.fail(maker, "unable to write file {f}: {t}", .{ out_path, err });
139
140 maker.generatedPath(conf_ch.generated_dir).* = .{
141 .root_dir = cache_root,
142 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }),
143 };
144
145 try step.writeManifest(maker, &man);
146}
147
148fn ensureAllValuesUsed(
149 maker: *Maker,
150 step: *Step,
151 value_map: *const ValueMap,
152 src_path: Path,
153) Step.ExtendedMakeError!void {
154 var any_errors = false;
155 for (value_map.keys(), value_map.values()) |name, used| {
156 if (used) continue;
157 try step.addError(maker, "{f}: config header value unused: {s}", .{ src_path, name });
158 any_errors = true;
159 }
160 if (any_errors) return error.MakeFailed;
161}
162
163fn renderAutoConfUndef(
164 maker: *Maker,
165 step: *Step,
166 contents: []const u8,
167 w: *Writer,
168 value_pairs: []const Value.Pair,
169 value_map: *ValueMap,
170 src_path: Path,
171) !void {
172 const conf = &maker.scanned_config.configuration;
173
174 try w.writeAll(c_generated_line);
175
176 var any_errors = false;
177 var line_index: u32 = 0;
178 var line_it = std.mem.splitScalar(u8, contents, '\n');
179 while (line_it.next()) |line| : (line_index += 1) {
180 if (!std.mem.startsWith(u8, line, "#")) {
181 try w.writeAll(line);
182 try w.writeByte('\n');
183 continue;
184 }
185 var it = std.mem.tokenizeAny(u8, line[1..], " \t\r");
186 const undef = it.next().?;
187 if (!std.mem.eql(u8, undef, "undef")) {
188 try w.writeAll(line);
189 try w.writeByte('\n');
190 continue;
191 }
192 const name = it.next().?;
193 const index = value_map.getIndex(name) orelse {
194 try step.addError(maker, "{f}:{d}: unspecified config header value: {s}", .{
195 src_path, line_index + 1, name,
196 });
197 any_errors = true;
198 continue;
199 };
200 value_map.values()[index] = true; // Set to used.
201 try renderValueC(conf, w, name, value_pairs[index].index);
202 }
203
204 try ensureAllValuesUsed(maker, step, value_map, src_path);
205 if (any_errors) return error.MakeFailed;
206}
207
208fn renderAutoconfAt(
209 maker: *Maker,
210 step: *Step,
211 contents: []const u8,
212 aw: *Writer.Allocating,
213 value_pairs: []const Value.Pair,
214 value_map: *const ValueMap,
215 src_path: Path,
216) !void {
217 const w = &aw.writer;
218 const conf = &maker.scanned_config.configuration;
219
220 try w.writeAll(c_generated_line);
221
222 var any_errors = false;
223 var line_index: u32 = 0;
224 var line_it = std.mem.splitScalar(u8, contents, '\n');
225 while (line_it.next()) |line| : (line_index += 1) {
226 const last_line = line_it.index == line_it.buffer.len;
227
228 const old_len = aw.written().len;
229 expandVariablesAutoconfAt(w, line, conf, value_pairs, value_map) catch |err| switch (err) {
230 error.MissingValue => {
231 const name = aw.written()[old_len..];
232 defer aw.shrinkRetainingCapacity(old_len);
233 try step.addError(maker, "{f}:{d}: error: unspecified config header value: {s}", .{
234 src_path, line_index + 1, name,
235 });
236 any_errors = true;
237 continue;
238 },
239 else => {
240 try step.addError(maker, "{f}:{d}: unable to substitute variable: error: {t}", .{
241 src_path, line_index + 1, err,
242 });
243 any_errors = true;
244 continue;
245 },
246 };
247 if (!last_line) try w.writeByte('\n');
248 }
249
250 try ensureAllValuesUsed(maker, step, value_map, src_path);
251 if (any_errors) return error.MakeFailed;
252}
253
254fn renderCmake(
255 arena: Allocator,
256 maker: *Maker,
257 step: *Step,
258 contents: []const u8,
259 w: *Writer,
260 value_pairs: []const Value.Pair,
261 value_map: *ValueMap,
262 src_path: Path,
263) !void {
264 const conf = &maker.scanned_config.configuration;
265
266 try w.writeAll(c_generated_line);
267
268 var any_errors = false;
269 var line_index: u32 = 0;
270 var line_it = std.mem.splitScalar(u8, contents, '\n');
271 while (line_it.next()) |raw_line| : (line_index += 1) {
272 const last_line = line_it.index == line_it.buffer.len;
273
274 const line = expandVariablesCmake(arena, raw_line, conf, value_pairs, value_map) catch |err| switch (err) {
275 error.InvalidCharacter => {
276 try step.addError(maker, "{f}:{d}: invalid character in a variable name", .{
277 src_path, line_index + 1,
278 });
279 any_errors = true;
280 continue;
281 },
282 else => {
283 try step.addError(maker, "{f}:{d}: failed substituting variable: {t}", .{
284 src_path, line_index + 1, err,
285 });
286 any_errors = true;
287 continue;
288 },
289 };
290
291 const line_start = std.mem.findNone(u8, line, " \t\r") orelse {
292 try w.writeAll(line);
293 if (!last_line) try w.writeByte('\n');
294 continue;
295 };
296 const whitespace_prefix = line[0..line_start];
297 const trimmed_line = line[line_start..];
298
299 if (!std.mem.startsWith(u8, trimmed_line, "#")) {
300 try w.writeAll(line);
301 if (!last_line) try w.writeByte('\n');
302 continue;
303 }
304
305 var it = std.mem.tokenizeAny(u8, trimmed_line[1..], " \t\r");
306 const cmakedefine = it.next().?;
307
308 const booldefine = if (std.mem.eql(u8, cmakedefine, "cmakedefine01"))
309 true
310 else if (std.mem.eql(u8, cmakedefine, "cmakedefine"))
311 false
312 else {
313 try w.writeAll(line);
314 if (!last_line) try w.writeByte('\n');
315 continue;
316 };
317
318 const name = it.next() orelse {
319 try step.addError(maker, "{f}:{d}: error: missing define name", .{ src_path, line_index + 1 });
320 any_errors = true;
321 continue;
322 };
323 const orig_value: Value.Index = v: {
324 const index = value_map.getIndex(name) orelse break :v if (booldefine) .int_0 else .undef;
325 value_map.values()[index] = true; // Mark as used.
326 break :v value_pairs[index].index;
327 };
328 const value = switch (orig_value.unpack(conf)) {
329 .bool => |b| if (!b) .undef else orig_value,
330 inline .i64, .u64 => |i| if (i == 0) .undef else orig_value,
331 .string => |s| if (s.len == 0) .undef else orig_value,
332 else => orig_value,
333 };
334
335 try w.writeAll(whitespace_prefix);
336
337 if (booldefine) {
338 try renderValueCBool(w, name, switch (value.unpack(conf)) {
339 .undef, .defined => false,
340 .bool => |b| b,
341 inline .u64, .i64 => |i| i != 0,
342 .string => |s| s.len != 0,
343 .ident => false,
344 });
345 } else if (value != .undef) {
346 try renderValueCIdent(w, name, it.rest());
347 } else {
348 try renderValueC(conf, w, name, value);
349 }
350 }
351
352 try ensureAllValuesUsed(maker, step, value_map, src_path);
353 if (any_errors) return error.MakeFailed;
354}
355
356fn renderBlank(
357 conf: *const Configuration,
358 w: *Writer,
359 value_pairs: []const Value.Pair,
360 value_map: *const ValueMap,
361 include_path: []const u8,
362 include_guard_override: ?[]const u8,
363) !void {
364 try w.writeAll(c_generated_line);
365
366 const include_guard_fmt: IncludeGuardFmt = .{
367 .include_path = include_path,
368 .override = include_guard_override,
369 };
370
371 try w.print(
372 \\#ifndef {[0]f}
373 \\#define {[0]f}
374 \\
375 , .{include_guard_fmt});
376
377 for (value_map.keys(), value_pairs) |name, pair| try renderValueC(conf, w, name, pair.index);
378
379 try w.print(
380 \\#endif /* {f} */
381 \\
382 , .{include_guard_fmt});
383}
384
385const IncludeGuardFmt = struct {
386 include_path: []const u8,
387 override: ?[]const u8,
388
389 pub fn format(this: @This(), w: *Writer) Writer.Error!void {
390 if (this.override) |s| return w.writeAll(s);
391 for (this.include_path) |byte| switch (byte) {
392 'a'...'z' => try w.writeByte(byte - 'a' + 'A'),
393 'A'...'Z', '0'...'9' => continue,
394 else => try w.writeByte('_'),
395 };
396 }
397};
398
399fn renderNasm(
400 conf: *const Configuration,
401 w: *Writer,
402 value_pairs: []const Value.Pair,
403 value_map: *const ValueMap,
404) !void {
405 try w.writeAll(asm_generated_line);
406 for (value_map.keys(), value_pairs) |name, pair| try renderValueNasm(conf, w, name, pair.index);
407}
408
409fn renderValueC(conf: *const Configuration, w: *Writer, name: []const u8, value: Value.Index) !void {
410 switch (value.unpack(conf)) {
411 .undef => try w.print("/* #undef {s} */\n", .{name}),
412 .defined => try w.print("#define {s}\n", .{name}),
413 .bool => |b| return renderValueCBool(w, name, b),
414 inline .u64, .i64 => |i| try w.print("#define {s} {d}\n", .{ name, i }),
415 .ident => |ident| return renderValueCIdent(w, name, ident),
416 .string => |string| try w.print("#define {s} \"{f}\"\n", .{ name, std.zig.fmtString(string) }),
417 }
418}
419
420fn renderValueCIdent(w: *Writer, name: []const u8, ident: []const u8) Writer.Error!void {
421 return w.print("#define {s} {s}\n", .{ name, ident });
422}
423
424fn renderValueCBool(w: *Writer, name: []const u8, b: bool) Writer.Error!void {
425 return w.print("#define {s} {c}\n", .{ name, @as(u8, '0') + @intFromBool(b) });
426}
427
428fn renderValueNasm(conf: *const Configuration, w: *Writer, name: []const u8, value: Value.Index) !void {
429 switch (value.unpack(conf)) {
430 .undef => try w.print("; %undef {s}\n", .{name}),
431 .defined => try w.print("%define {s}\n", .{name}),
432 .bool => |b| try w.print("%define {s} {c}\n", .{ name, @as(u8, '0') + @intFromBool(b) }),
433 inline .u64, .i64 => |i| try w.print("%define {s} {d}\n", .{ name, i }),
434 .ident => |ident| try w.print("%define {s} {s}\n", .{ name, ident }),
435 .string => |string| try w.print("%define {s} \"{f}\"\n", .{ name, std.zig.fmtString(string) }),
436 }
437}
438
439fn expandVariablesAutoconfAt(
440 w: *Writer,
441 contents: []const u8,
442 conf: *const Configuration,
443 value_pairs: []const Value.Pair,
444 value_map: *const ValueMap,
445) !void {
446 const valid_varname_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_";
447
448 var curr: usize = 0;
449 var source_offset: usize = 0;
450 while (curr < contents.len) : (curr += 1) {
451 if (contents[curr] != '@') continue;
452 if (std.mem.findScalarPos(u8, contents, curr + 1, '@')) |close_pos| {
453 if (close_pos == curr + 1) {
454 // closed immediately, preserve as a literal
455 continue;
456 }
457 const valid_varname_end = std.mem.findNonePos(u8, contents, curr + 1, valid_varname_chars) orelse 0;
458 if (valid_varname_end != close_pos) {
459 // contains invalid characters, preserve as a literal
460 continue;
461 }
462
463 const key = contents[curr + 1 .. close_pos];
464 const index = value_map.getIndex(key) orelse {
465 // Report the missing key to the caller.
466 try w.writeAll(key);
467 return error.MissingValue;
468 };
469 const value = value_pairs[index].index;
470 value_map.values()[index] = true; // Mark as used.
471 try w.writeAll(contents[source_offset..curr]);
472 switch (value.unpack(conf)) {
473 .undef, .defined => {},
474 .bool => |b| try w.writeByte(@as(u8, '0') + @intFromBool(b)),
475 inline .u64, .i64 => |i| try w.print("{d}", .{i}),
476 .ident, .string => |s| try w.writeAll(s),
477 }
478
479 curr = close_pos;
480 source_offset = close_pos + 1;
481 }
482 }
483
484 try w.writeAll(contents[source_offset..]);
485}
486
487fn expandVariablesCmake(
488 arena: Allocator,
489 contents: []const u8,
490 conf: *const Configuration,
491 value_pairs: []const Value.Pair,
492 value_map: *const ValueMap,
493) ![]const u8 {
494 var result: std.ArrayList(u8) = .empty;
495
496 const valid_varname_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/_.+-";
497 const open_var = "${";
498
499 var curr: usize = 0;
500 var source_offset: usize = 0;
501 const Position = struct {
502 source: usize,
503 target: usize,
504 };
505 var var_stack: std.ArrayList(Position) = .empty;
506 loop: while (curr < contents.len) : (curr += 1) {
507 switch (contents[curr]) {
508 '@' => blk: {
509 if (std.mem.findScalarPos(u8, contents, curr + 1, '@')) |close_pos| {
510 if (close_pos == curr + 1) {
511 // closed immediately, preserve as a literal
512 break :blk;
513 }
514 const valid_varname_end = std.mem.findNonePos(u8, contents, curr + 1, valid_varname_chars) orelse 0;
515 if (valid_varname_end != close_pos) {
516 // contains invalid characters, preserve as a literal
517 break :blk;
518 }
519
520 const key = contents[curr + 1 .. close_pos];
521 const index = value_map.getIndex(key) orelse return error.MissingValue;
522 value_map.values()[index] = true; // Mark as used.
523 const value = value_pairs[index].index;
524 const missing = contents[source_offset..curr];
525 try result.appendSlice(arena, missing);
526 switch (value.unpack(conf)) {
527 .undef, .defined => {},
528 .bool => |b| try result.append(arena, if (b) '1' else '0'),
529 inline .i64, .u64 => |i| try result.print(arena, "{d}", .{i}),
530 .ident, .string => |s| try result.appendSlice(arena, s),
531 }
532
533 curr = close_pos;
534 source_offset = close_pos + 1;
535
536 continue :loop;
537 }
538 },
539 '$' => blk: {
540 const next = curr + 1;
541 if (next == contents.len or contents[next] != '{') {
542 // no open bracket detected, preserve as a literal
543 break :blk;
544 }
545 const missing = contents[source_offset..curr];
546 try result.appendSlice(arena, missing);
547 try result.appendSlice(arena, open_var);
548
549 source_offset = curr + open_var.len;
550 curr = next;
551 try var_stack.append(arena, .{
552 .source = curr,
553 .target = result.items.len - open_var.len,
554 });
555
556 continue :loop;
557 },
558 '}' => blk: {
559 if (var_stack.items.len == 0) {
560 // no open bracket, preserve as a literal
561 break :blk;
562 }
563 const open_pos = var_stack.pop().?;
564 if (source_offset == open_pos.source) {
565 source_offset += open_var.len;
566 }
567 const missing = contents[source_offset..curr];
568 try result.appendSlice(arena, missing);
569
570 const key_start = open_pos.target + open_var.len;
571 const key = result.items[key_start..];
572 if (key.len == 0) {
573 return error.MissingKey;
574 }
575 const index = value_map.getIndex(key) orelse return error.MissingValue;
576 value_map.values()[index] = true; // Mark as used.
577 const value = value_pairs[index].index;
578 result.shrinkRetainingCapacity(result.items.len - key.len - open_var.len);
579 switch (value.unpack(conf)) {
580 .undef, .defined => {},
581 .bool => |b| try result.append(arena, if (b) '1' else '0'),
582 inline .i64, .u64 => |i| try result.print(arena, "{d}", .{i}),
583 .ident, .string => |s| try result.appendSlice(arena, s),
584 }
585
586 source_offset = curr + 1;
587
588 continue :loop;
589 },
590 '\\' => {
591 // backslash is not considered a special character
592 continue :loop;
593 },
594 else => {},
595 }
596
597 if (var_stack.items.len > 0 and std.mem.findScalar(u8, valid_varname_chars, contents[curr]) == null) {
598 return error.InvalidCharacter;
599 }
600 }
601
602 if (source_offset != contents.len) {
603 const missing = contents[source_offset..];
604 try result.appendSlice(arena, missing);
605 }
606
607 try result.shrinkToLen(arena);
608
609 return result.toOwnedSliceAssert();
610}
lib/compiler/Maker/Step/FindProgram.zig created+120
......@@ -0,0 +1,120 @@
1const FindProgram = @This();
2const builtin = @import("builtin");
3
4const std = @import("std");
5const Io = std.Io;
6const Configuration = std.Build.Configuration;
7const assert = std.debug.assert;
8
9const Step = @import("../Step.zig");
10const Maker = @import("../../Maker.zig");
11
12pub fn make(
13 find_program: *FindProgram,
14 step_index: Configuration.Step.Index,
15 maker: *Maker,
16 progress_node: std.Progress.Node,
17) Step.ExtendedMakeError!void {
18 _ = find_program;
19 _ = progress_node;
20 const graph = maker.graph;
21 const step = maker.stepByIndex(step_index);
22 const arena = graph.arena; // TODO don't leak into the process arena
23 const conf = &maker.scanned_config.configuration;
24 const conf_step = step_index.ptr(conf);
25 const conf_fp = conf_step.extended.get(conf.extra).find_program;
26 const found_path = conf_fp.found_path;
27 const names = conf_fp.names.slice(conf);
28
29 // In case we fail at the end.
30 var err_msg: std.ArrayList(u8) = .empty;
31 try err_msg.appendSlice(arena, "program not found. searched paths:\n");
32
33 for (names) |name_index| {
34 const name = name_index.slice(conf);
35
36 if (Io.Dir.path.isAbsolute(name)) {
37 if (try checkCandidate(maker, step, found_path, &err_msg, name)) return;
38
39 continue;
40 }
41
42 for (graph.search_prefixes.items) |search_prefix| {
43 const full_path = try Io.Dir.path.join(arena, &.{ search_prefix, "bin", name });
44
45 if (try checkCandidate(maker, step, found_path, &err_msg, full_path)) return;
46 }
47 }
48
49 if (graph.environ_map.get("PATH")) |PATH| {
50 for (names) |name_index| {
51 const name = name_index.slice(conf);
52
53 var it = std.mem.tokenizeScalar(u8, PATH, Io.Dir.path.delimiter);
54 while (it.next()) |p| {
55 const full_path = try Io.Dir.path.join(arena, &.{ p, name });
56
57 if (try checkCandidate(maker, step, found_path, &err_msg, full_path)) return;
58 }
59 }
60 }
61
62 assert(err_msg.items[err_msg.items.len - 1] == '\n');
63 const chopped = err_msg.items[0 .. err_msg.items.len - 1];
64 try step.result_error_msgs.append(arena, chopped);
65 return error.MakeFailed;
66}
67
68fn checkCandidate(
69 maker: *Maker,
70 step: *Step,
71 found_path: Configuration.GeneratedFileIndex,
72 err_msg: *std.ArrayList(u8),
73 full_path: []const u8,
74) !bool {
75 const graph = maker.graph;
76 const arena = graph.arena; // TODO don't leak into process arena
77 const io = graph.io;
78
79 if (Io.Dir.cwd().access(io, full_path, .{ .execute = true })) |_| {
80 maker.generatedPath(found_path).* = .initCwd(full_path);
81 return true;
82 } else |err| switch (err) {
83 error.Canceled => |e| return e,
84 error.FileNotFound, error.AccessDenied, error.PermissionDenied => |e| {
85 try err_msg.print(arena, "{t} {s}\n", .{ e, full_path });
86 },
87 else => |e| return step.fail(maker, "failed accessing {s}: {t}", .{ full_path, e }),
88 }
89
90 if (builtin.os.tag == .windows) {
91 if (graph.environ_map.get("PATHEXT")) |PATHEXT| {
92 var it = std.mem.tokenizeScalar(u8, PATHEXT, Io.Dir.path.delimiter);
93 while (it.next()) |ext| {
94 if (!supportedWindowsProgramExtension(ext)) continue;
95
96 const extended_path = try std.mem.concat(arena, u8, &.{ full_path, ext });
97
98 if (Io.Dir.cwd().access(io, extended_path, .{ .execute = true })) |_| {
99 maker.generatedPath(found_path).* = .initCwd(extended_path);
100 return true;
101 } else |err| switch (err) {
102 error.Canceled => |e| return e,
103 error.FileNotFound, error.AccessDenied, error.PermissionDenied => |e| {
104 try err_msg.print(arena, "{t} {s}\n", .{ e, extended_path });
105 },
106 else => |e| return step.fail(maker, "failed accessing {s}: {t}", .{ extended_path, e }),
107 }
108 }
109 }
110 }
111
112 return false;
113}
114
115fn supportedWindowsProgramExtension(ext: []const u8) bool {
116 inline for (@typeInfo(std.process.WindowsExtension).@"enum".fields) |field| {
117 if (std.ascii.eqlIgnoreCase(ext, "." ++ field.name)) return true;
118 }
119 return false;
120}
lib/compiler/Maker/Step/Fmt.zig created+65
......@@ -0,0 +1,65 @@
1const Fmt = @This();
2
3const std = @import("std");
4const Configuration = std.Build.Configuration;
5
6const Step = @import("../Step.zig");
7const Maker = @import("../../Maker.zig");
8
9/// Persisted to reuse memory on subsequent calls to `make`.
10argv: std.ArrayList([]const u8) = .empty,
11
12pub fn make(
13 fmt: *Fmt,
14 step_index: Configuration.Step.Index,
15 maker: *Maker,
16 progress_node: std.Progress.Node,
17) Step.ExtendedMakeError!void {
18 const graph = maker.graph;
19 const step = maker.stepByIndex(step_index);
20 const gpa = maker.gpa;
21 const arena = graph.arena; // TODO don't leak into the process arena
22 const argv = &fmt.argv;
23 const conf = &maker.scanned_config.configuration;
24 const conf_step = step_index.ptr(conf);
25 const conf_fmt = conf_step.extended.get(conf.extra).fmt;
26 const paths = conf_fmt.paths.slice;
27 const exclude_paths = conf_fmt.exclude_paths.slice;
28
29 argv.clearRetainingCapacity();
30 try argv.ensureUnusedCapacity(gpa, 2 + 1 + paths.len + 2 * exclude_paths.len);
31
32 argv.appendAssumeCapacity(graph.zig_exe);
33 argv.appendAssumeCapacity("fmt");
34
35 if (conf_fmt.flags.check)
36 argv.appendAssumeCapacity("--check");
37
38 for (paths) |lp|
39 argv.appendAssumeCapacity(try maker.resolveLazyPathIndexAbs(arena, lp, step_index));
40
41 for (exclude_paths) |lp| {
42 argv.appendAssumeCapacity("--exclude");
43 argv.appendAssumeCapacity(try maker.resolveLazyPathIndexAbs(arena, lp, step_index));
44 }
45
46 const run_result = step.captureChildProcess(maker, .{
47 .progress_node = progress_node,
48 .argv = argv.items,
49 .allow_failure = false,
50 }) catch |err| switch (err) {
51 error.FileNotFound => unreachable,
52 else => |e| return e,
53 };
54
55 if (conf_fmt.flags.check) switch (run_result.term) {
56 .exited => |code| if (code != 0 and run_result.stdout.len != 0) {
57 var it = std.mem.tokenizeScalar(u8, run_result.stdout, '\n');
58 while (it.next()) |bad_file_name| {
59 try step.addError(maker, "{s}: non-conforming formatting", .{bad_file_name});
60 }
61 },
62 else => {},
63 };
64 try step.handleChildProcessTerm(maker, run_result.term);
65}
lib/compiler/Maker/Step/InstallArtifact.zig created+141
......@@ -0,0 +1,141 @@
1const InstallArtifact = @This();
2
3const std = @import("std");
4const Io = std.Io;
5const Configuration = std.Build.Configuration;
6const assert = std.debug.assert;
7
8const Step = @import("../Step.zig");
9const Maker = @import("../../Maker.zig");
10
11pub fn make(
12 install_artifact: *InstallArtifact,
13 step_index: Configuration.Step.Index,
14 maker: *Maker,
15 progress_node: std.Progress.Node,
16) Step.ExtendedMakeError!void {
17 _ = install_artifact;
18 _ = progress_node;
19 const step = maker.stepByIndex(step_index);
20 const conf = &maker.scanned_config.configuration;
21 const graph = maker.graph;
22 const gpa = maker.gpa;
23 const arena = graph.arena; // TODO don't leak into process arena
24 const io = graph.io;
25 const conf_step = step_index.ptr(conf);
26 const conf_ia = conf_step.extended.get(conf.extra).install_artifact;
27 const compile_step_index = conf_step.deps.get(conf).steps.slice[0];
28 const conf_comp_step = compile_step_index.ptr(conf);
29 const conf_comp = conf_comp_step.extended.get(conf.extra).compile;
30 const root_module = conf_comp.root_module.get(conf);
31 const target = root_module.resolved_target.get(conf).?.result.get(conf);
32
33 var all_cached = true;
34
35 if (conf_ia.bin_dir.value) |bin_dir| {
36 if (conf_comp.generated_bin.value) |generated_bin| {
37 const bin_sub_path = if (conf_ia.bin_sub_path.value) |s| s.slice(conf) else try std.zig.binNameAlloc(arena, .{
38 .root_name = conf_comp.root_name.slice(conf),
39 .cpu_arch = target.flags.cpu_arch.unwrap().?,
40 .os_tag = target.flags.os_tag.unwrap().?,
41 .ofmt = target.flags.object_format.unwrap().?,
42 .abi = target.flags.abi.unwrap().?,
43 .output_mode = conf_comp.flags3.kind.toOutputMode(),
44 .link_mode = conf_comp.flags2.linkage.unwrap(),
45 .version = v: {
46 const string = conf_comp.version.value orelse break :v null;
47 const slice = string.slice(conf);
48 break :v std.SemanticVersion.parse(slice) catch @panic("bad semver string");
49 },
50 });
51 const dest_dir = try maker.resolveInstallDir(arena, bin_dir);
52 const dest_path = try dest_dir.join(arena, bin_sub_path);
53 const src_path = maker.generatedPath(generated_bin).*;
54 const p = try maker.installPath(arena, src_path, dest_path, step_index);
55 all_cached = all_cached and p == .fresh;
56
57 if (conf_ia.flags.dylib_symlinks)
58 try maker.installSymLinks(arena, dest_path, compile_step_index, step_index);
59
60 const make_comp_step = maker.stepByIndex(compile_step_index);
61 const make_comp = &make_comp_step.extended.compile;
62 make_comp.installed_path = dest_path;
63 }
64 }
65
66 if (conf_ia.implib_dir.value) |implib_dir| {
67 if (conf_comp.generated_implib.value) |generated_implib| {
68 const p = try maker.installGenerated(arena, generated_implib, implib_dir, step_index);
69 all_cached = all_cached and p == .fresh;
70 }
71 }
72
73 if (conf_ia.pdb_dir.value) |pdb_dir| {
74 if (conf_comp.generated_pdb.value) |generated_pdb| {
75 const p = try maker.installGenerated(arena, generated_pdb, pdb_dir, step_index);
76 all_cached = all_cached and p == .fresh;
77 }
78 }
79
80 if (conf_ia.h_dir.value) |h_dir| {
81 const h_prefix = try maker.resolveInstallDir(arena, h_dir);
82
83 if (conf_comp.generated_h.value) |generated_h| {
84 const p = try maker.installGenerated(arena, generated_h, h_dir, step_index);
85 all_cached = all_cached and p == .fresh;
86 }
87
88 for (conf_comp.installed_headers.slice) |installation| switch (installation.get(conf.extra)) {
89 .file => |file| {
90 const src_path = try maker.resolveLazyPathIndex(arena, file.source, step_index);
91 const dest_path = try h_prefix.join(arena, file.dest_sub_path.slice(conf));
92 const p = try maker.installPath(arena, src_path, dest_path, step_index);
93 all_cached = all_cached and p == .fresh;
94 },
95 .directory => |dir| {
96 const src_dir_path = try maker.resolveLazyPathIndex(arena, dir.source, step_index);
97 const full_h_prefix = try h_prefix.join(arena, dir.dest_sub_path.slice(conf));
98
99 var src_dir = src_dir_path.root_dir.handle.openDir(io, src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| {
100 return step.fail(maker, "unable to open source directory {f}: {t}", .{ src_dir_path, err });
101 };
102 defer src_dir.close(io);
103
104 var it = try src_dir.walk(gpa);
105 defer it.deinit();
106 next_entry: while (it.next(io) catch |err| switch (err) {
107 error.Canceled, error.OutOfMemory => |e| return e,
108 else => |e| return step.fail(maker, "failed to iterate directory {f}: {t}", .{ src_dir_path, e }),
109 }) |entry| {
110 for (dir.exclude_extensions.slice) |ext| {
111 if (std.mem.endsWith(u8, entry.path, ext.slice(conf))) continue :next_entry;
112 }
113 if (dir.flags.include_extensions) {
114 for (dir.include_extensions.slice) |inc| {
115 if (std.mem.endsWith(u8, entry.path, inc.slice(conf))) break;
116 } else {
117 continue :next_entry;
118 }
119 }
120
121 const full_dest_path = try full_h_prefix.join(arena, entry.path);
122 switch (entry.kind) {
123 .directory => {
124 const p = try maker.installDir(arena, full_dest_path, step_index);
125 all_cached = all_cached and p == .existed;
126 },
127 .file => {
128 const entry_dir_path = try maker.resolveLazyPathIndex(arena, dir.source, step_index);
129 const entry_path = try entry_dir_path.join(arena, entry.path);
130 const p = try maker.installPath(arena, entry_path, full_dest_path, step_index);
131 all_cached = all_cached and p == .fresh;
132 },
133 else => continue,
134 }
135 }
136 },
137 };
138 }
139
140 step.result_cached = all_cached;
141}
lib/compiler/Maker/Step/InstallDir.zig created+99
......@@ -0,0 +1,99 @@
1const InstallDir = @This();
2
3const std = @import("std");
4const Io = std.Io;
5const log = std.log;
6const Configuration = std.Build.Configuration;
7const endsWith = std.mem.endsWith;
8
9const Step = @import("../Step.zig");
10const Maker = @import("../../Maker.zig");
11
12pub fn make(
13 install_dir: *InstallDir,
14 step_index: Configuration.Step.Index,
15 maker: *Maker,
16 progress_node: std.Progress.Node,
17) Step.ExtendedMakeError!void {
18 _ = install_dir;
19 const graph = maker.graph;
20 const gpa = maker.gpa;
21 const arena = maker.graph.arena; // TODO don't leak into process arena
22 const io = graph.io;
23 const step = maker.stepByIndex(step_index);
24 const conf = &maker.scanned_config.configuration;
25 const conf_step = step_index.ptr(conf);
26 const conf_id = conf_step.extended.get(conf.extra).install_dir;
27
28 step.clearWatchInputs(maker);
29
30 const dest_parent_path = try maker.resolveInstallDir(arena, conf_id.dest_dir);
31 const dest_prefix = if (conf_id.dest_sub_path.value) |s|
32 try dest_parent_path.join(arena, s.slice(conf))
33 else
34 dest_parent_path;
35 const src_dir_lazy_path = conf_id.source_dir.get(conf);
36 const src_dir_path = try maker.resolveLazyPath(arena, src_dir_lazy_path, step_index);
37 const need_derived_inputs = try step.addDirectoryWatchInput(maker, src_dir_lazy_path);
38
39 var src_dir = src_dir_path.root_dir.handle.openDir(
40 io,
41 src_dir_path.subPathOrDot(),
42 .{ .iterate = true },
43 ) catch |err| return step.fail(maker, "failed opening source directory {f}: {t}", .{ src_dir_path, err });
44 defer src_dir.close(io);
45
46 const exclude_extensions = conf_id.exclude_extensions.slice;
47 const include_extensions: ?[]const Configuration.String = if (conf_id.flags.include_extensions_active)
48 conf_id.include_extensions.slice
49 else
50 null;
51 const blank_extensions = conf_id.blank_extensions.slice;
52
53 var all_cached = true;
54 var it = try src_dir.walk(gpa);
55 defer it.deinit();
56 next_entry: while (it.next(io) catch |err| switch (err) {
57 error.Canceled, error.OutOfMemory => |e| return e,
58 else => |e| return step.fail(maker, "failed iterating dir {f}: {t}", .{ src_dir_path, e }),
59 }) |entry| {
60 for (exclude_extensions) |ext| {
61 if (endsWith(u8, entry.path, ext.slice(conf))) continue :next_entry;
62 }
63 if (include_extensions) |includes| {
64 for (includes) |inc| {
65 if (endsWith(u8, entry.path, inc.slice(conf))) break;
66 } else {
67 continue :next_entry;
68 }
69 }
70
71 const dest_path = try dest_prefix.join(arena, entry.path);
72 switch (entry.kind) {
73 .directory => {
74 if (need_derived_inputs) {
75 const entry_path = try src_dir_path.join(arena, entry.path);
76 try step.addDirectoryWatchInputFromPath(maker, entry_path);
77 }
78 const p = try maker.installDir(arena, dest_path, step_index);
79 all_cached = all_cached and p == .existed;
80 },
81 .file => {
82 for (blank_extensions) |ext| {
83 if (endsWith(u8, entry.path, ext.slice(conf))) {
84 try maker.truncatePath(arena, dest_path, step_index);
85 continue :next_entry;
86 }
87 }
88
89 const entry_path = try src_dir_path.join(arena, entry.path);
90 const p = try maker.installPath(arena, entry_path, dest_path, step_index);
91 all_cached = all_cached and p == .fresh;
92 progress_node.completeOne();
93 },
94 else => continue,
95 }
96 }
97
98 step.result_cached = all_cached;
99}
lib/compiler/Maker/Step/InstallFile.zig created+26
......@@ -0,0 +1,26 @@
1const InstallFile = @This();
2
3const std = @import("std");
4const Configuration = std.Build.Configuration;
5
6const Step = @import("../Step.zig");
7const Maker = @import("../../Maker.zig");
8
9pub fn make(
10 install_file: *InstallFile,
11 step_index: Configuration.Step.Index,
12 maker: *Maker,
13 progress_node: std.Progress.Node,
14) Step.ExtendedMakeError!void {
15 _ = install_file;
16 _ = progress_node;
17 const arena = maker.graph.arena; // TODO don't leak into process arena
18 const step = maker.stepByIndex(step_index);
19 const conf = &maker.scanned_config.configuration;
20 const conf_step = step_index.ptr(conf);
21 const conf_if = conf_step.extended.get(conf.extra).install_file;
22
23 try step.singleUnchangingWatchInput(maker, arena, conf_if.source.get(conf));
24 const p = try maker.installLazyPathSub(arena, conf_if.source, conf_if.dest_dir, conf_if.dest_sub_path.slice(conf), step_index);
25 step.result_cached = p == .fresh;
26}
lib/compiler/Maker/Step/ObjCopy.zig created+173
......@@ -0,0 +1,173 @@
1const ObjCopy = @This();
2
3const std = @import("std");
4const Io = std.Io;
5const Path = std.Build.Cache.Path;
6const allocPrint = std.fmt.allocPrint;
7const Configuration = std.Build.Configuration;
8
9const Step = @import("../Step.zig");
10const Maker = @import("../../Maker.zig");
11
12pub fn make(
13 obj_copy: *ObjCopy,
14 step_index: Configuration.Step.Index,
15 maker: *Maker,
16 progress_node: std.Progress.Node,
17) Step.ExtendedMakeError!void {
18 _ = obj_copy;
19 const graph = maker.graph;
20 const arena = maker.graph.arena; // TODO don't leak into process arena
21 const io = graph.io;
22 const step = maker.stepByIndex(step_index);
23 const conf = &maker.scanned_config.configuration;
24 const conf_step = step_index.ptr(conf);
25 const conf_oc = conf_step.extended.get(conf.extra).obj_copy;
26 const cache_root = graph.local_cache_root;
27 const input_lazy_path = conf_oc.input_file.get(conf);
28 const only_section: ?[]const u8 = if (conf_oc.only_section.value) |s| s.slice(conf) else null;
29 const opt_basename: ?[]const u8 = if (conf_oc.basename.value) |s| s.slice(conf) else null;
30 const opt_debug_basename: ?[]const u8 = if (conf_oc.debug_basename.value) |s| s.slice(conf) else null;
31
32 try step.singleUnchangingWatchInput(maker, arena, input_lazy_path);
33
34 var man = graph.cache.obtain();
35 defer man.deinit();
36
37 const input_path = try maker.resolveLazyPath(arena, input_lazy_path, step_index);
38 _ = try man.addFilePath(input_path, null);
39 man.hash.addOptionalBytes(only_section);
40 man.hash.addOptionalBytes(opt_basename);
41 man.hash.addOptionalBytes(opt_debug_basename);
42 man.hash.addOptional(conf_oc.pad_to.value);
43 man.hash.add(conf_oc.flags.format);
44 man.hash.add(conf_oc.flags.compress_debug);
45 man.hash.add(conf_oc.flags.strip);
46 man.hash.add(conf_oc.debug_file.value != null);
47
48 const basename = opt_basename orelse Io.Dir.path.basename(input_path.sub_path);
49
50 if (try step.cacheHit(maker, &man)) {
51 // Cache hit, skip subprocess execution.
52 const digest = man.final();
53 maker.generatedPath(conf_oc.output_file).* = .{
54 .root_dir = cache_root,
55 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, basename }),
56 };
57 if (conf_oc.debug_file.value) |debug_file| {
58 const debug_basename = opt_debug_basename orelse try allocPrint(arena, "{s}.debug", .{
59 Io.Dir.path.basename(input_path.sub_path),
60 });
61 maker.generatedPath(debug_file).* = .{
62 .root_dir = cache_root,
63 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, debug_basename }),
64 };
65 }
66 return;
67 }
68
69 // We don't find out more input files while executing objcopy so we can
70 // already obtain the digest and use it directly as the output path.
71 const digest = man.final();
72 const dest_path: Path = .{
73 .root_dir = cache_root,
74 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, basename }),
75 };
76 const dest_dirname = dest_path.dirname().?;
77 dest_dirname.root_dir.handle.createDirPath(io, dest_dirname.sub_path) catch |err|
78 return step.fail(maker, "failed to create path {f}: {t}", .{ dest_dirname, err });
79
80 var argv: std.ArrayList([]const u8) = .empty;
81 try argv.ensureUnusedCapacity(arena, 11);
82
83 argv.addManyAsArrayAssumeCapacity(2).* = .{ graph.zig_exe, "objcopy" };
84
85 if (only_section) |s| argv.addManyAsArrayAssumeCapacity(2).* = .{ "-j", s };
86
87 switch (conf_oc.flags.strip) {
88 .none => {},
89 .debug => argv.appendAssumeCapacity("--strip-debug"),
90 .debug_and_symbols => argv.appendAssumeCapacity("--strip-all"),
91 }
92
93 if (conf_oc.pad_to.value) |pad_to| {
94 argv.addManyAsArrayAssumeCapacity(2).* = .{
95 "--pad-to", try allocPrint(arena, "{d}", .{pad_to}),
96 };
97 }
98
99 switch (conf_oc.flags.format) {
100 .default => {},
101 else => |t| argv.addManyAsArrayAssumeCapacity(2).* = .{ "-O", @tagName(t) },
102 }
103
104 if (conf_oc.flags.compress_debug)
105 argv.appendAssumeCapacity("--compress-debug-sections");
106
107 if (conf_oc.debug_file.value) |debug_file| {
108 const debug_basename = opt_debug_basename orelse try allocPrint(arena, "{s}.debug", .{
109 Io.Dir.path.basename(input_path.sub_path),
110 });
111 const debug_dest_path: Path = .{
112 .root_dir = cache_root,
113 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, debug_basename }),
114 };
115 argv.appendAssumeCapacity(try allocPrint(arena, "--extract-to={f}", .{debug_dest_path}));
116 maker.generatedPath(debug_file).* = debug_dest_path;
117 }
118
119 try argv.ensureUnusedCapacity(arena, conf_oc.add_section.slice.len * 2);
120
121 for (conf_oc.add_section.slice) |section| {
122 argv.appendAssumeCapacity("--add-section");
123 argv.appendAssumeCapacity(try allocPrint(arena, "{s}={f}", .{
124 section.section_name.slice(conf),
125 try maker.resolveLazyPathIndex(arena, section.file_path, step_index),
126 }));
127 }
128
129 for (conf_oc.update_section.slice) |update| {
130 const name = update.section_name.slice(conf);
131
132 try argv.ensureUnusedCapacity(arena, 4);
133
134 if (update.flags.alignment.toBytes()) |a| {
135 argv.appendAssumeCapacity("--set-section-alignment");
136 argv.appendAssumeCapacity(try allocPrint(arena, "{s}={d}", .{ name, a }));
137 }
138
139 const f = update.flags.section_flags;
140 if (f != Configuration.Step.ObjCopy.SectionFlags.default) {
141 // trailing comma is allowed
142 argv.appendAssumeCapacity("--set-section-flags");
143 argv.appendAssumeCapacity(try allocPrint(arena, "{s}={s}{s}{s}{s}{s}{s}{s}{s}{s}", .{
144 name,
145 if (f.alloc) "alloc," else "",
146 if (f.contents) "contents," else "",
147 if (f.load) "load," else "",
148 if (f.readonly) "readonly," else "",
149 if (f.code) "code," else "",
150 if (f.exclude) "exclude," else "",
151 if (f.large) "large," else "",
152 if (f.merge) "merge," else "",
153 if (f.strings) "strings," else "",
154 }));
155 }
156 }
157
158 argv.appendAssumeCapacity(try allocPrint(arena, "{f}", .{input_path}));
159 argv.appendAssumeCapacity(try allocPrint(arena, "{f}", .{dest_path}));
160
161 argv.appendAssumeCapacity("--listen=-");
162 _ = Step.evalZigProcess(step_index, maker, argv.items, progress_node, false) catch |err| switch (err) {
163 error.NeedCompileErrorCheck => unreachable,
164 else => |e| return e,
165 };
166
167 maker.generatedPath(conf_oc.output_file).* = dest_path;
168
169 step.writeManifest(maker, &man) catch |err| switch (err) {
170 error.Canceled => |e| return e,
171 else => |e| try step.addError(maker, "failed writing cache manifest: {t}", .{e}),
172 };
173}
lib/compiler/Maker/Step/Options.zig created+104
......@@ -0,0 +1,104 @@
1const Options = @This();
2
3const std = @import("std");
4const Io = std.Io;
5const Configuration = std.Build.Configuration;
6const Cache = std.Build.Cache;
7
8const Step = @import("../Step.zig");
9const Maker = @import("../../Maker.zig");
10
11pub fn make(
12 options: *Options,
13 step_index: Configuration.Step.Index,
14 maker: *Maker,
15 progress_node: std.Progress.Node,
16) Step.ExtendedMakeError!void {
17 _ = options;
18
19 // This step completes so quickly that no progress reporting is necessary.
20 _ = progress_node;
21
22 const graph = maker.graph;
23 const step = maker.stepByIndex(step_index);
24 const io = graph.io;
25 const cache_root = graph.local_cache_root;
26 const arena = graph.arena; // TODO don't leak into the process arena
27 const conf = &maker.scanned_config.configuration;
28 const conf_step = step_index.ptr(conf);
29 const conf_options = conf_step.extended.get(conf.extra).options;
30 const contents = conf_options.contents.slice(conf);
31
32 // This step operates under the assumption that all contents of the
33 // generated zig file are observable by dependant steps, as well as the
34 // contents of files added via Options.Arg.
35
36 step.clearWatchInputs(maker);
37
38 var man = graph.cache.obtain();
39 defer man.deinit();
40
41 var args_bytes: std.ArrayList(u8) = .empty;
42
43 for (conf_options.args.slice) |arg| {
44 const name = arg.name.slice(conf);
45 const lazy_path = arg.path.get(conf);
46 try step.addWatchInput(maker, arena, lazy_path);
47 const arg_path = try maker.resolveLazyPath(arena, lazy_path, step_index);
48 _ = try man.addFilePath(arg_path, null);
49 try args_bytes.print(arena, "pub const {f}: []const u8 = \"{f}\";\n", .{
50 std.zig.fmtId(name), arg_path.fmtEscapeString(),
51 });
52 }
53
54 man.hash.addBytes(contents);
55 man.hash.addBytes(args_bytes.items);
56
57 const basename = "options.zig";
58
59 if (try step.cacheHitAndWatch(maker, &man)) {
60 const digest = man.final();
61 maker.generatedPath(conf_options.generated_file).* = .{
62 .root_dir = cache_root,
63 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, basename }),
64 };
65 step.result_cached = true;
66 return;
67 }
68
69 const digest = man.final();
70 const out_path: Cache.Path = .{
71 .root_dir = cache_root,
72 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, basename }),
73 };
74
75 var file: Io.File = out_path.root_dir.handle.createFile(io, out_path.sub_path, .{}) catch |err| switch (err) {
76 error.Canceled => |e| return e,
77 error.FileNotFound => f: {
78 out_path.root_dir.handle.createDirPath(io, Io.Dir.path.dirname(out_path.sub_path).?) catch |inner| switch (inner) {
79 error.Canceled => |e| return e,
80 else => |e| return step.fail(maker, "failed to create {f}: {t}", .{ out_path, e }),
81 };
82 break :f out_path.root_dir.handle.createFile(io, out_path.sub_path, .{}) catch |inner| switch (inner) {
83 error.Canceled => |e| return e,
84 else => |e| return step.fail(maker, "failed to create {f}: {t}", .{ out_path, e }),
85 };
86 },
87 else => |e| return step.fail(maker, "failed to create {f}: {t}", .{ out_path, e }),
88 };
89 defer file.close(io);
90
91 // No buffer because we already have all contents buffered.
92 var file_writer = file.writer(io, &.{});
93 var data: [2][]const u8 = .{ contents, args_bytes.items };
94 file_writer.interface.writeVecAll(&data) catch |write_err| switch (write_err) {
95 error.WriteFailed => switch (file_writer.err.?) {
96 error.Canceled => |e| return e,
97 else => |e| return step.fail(maker, "failed to write to {f}: {t}", .{ out_path, e }),
98 },
99 };
100
101 try step.writeManifestAndWatch(maker, &man);
102
103 maker.generatedPath(conf_options.generated_file).* = out_path;
104}
lib/compiler/Maker/Step/Run.zig created+2372
......@@ -0,0 +1,2372 @@
1const Run = @This();
2
3const builtin = @import("builtin");
4
5const std = @import("std");
6const Cache = std.Build.Cache;
7const Configuration = std.Build.Configuration;
8const Dir = std.Io.Dir;
9const EnvMap = std.process.Environ.Map;
10const Io = std.Io;
11const Path = std.Build.Cache.Path;
12const assert = std.debug.assert;
13const mem = std.mem;
14const process = std.process;
15const allocPrint = std.fmt.allocPrint;
16const Allocator = std.mem.Allocator;
17
18const Step = @import("../Step.zig");
19const Maker = @import("../../Maker.zig");
20const Fuzz = @import("../../Maker/Fuzz.zig");
21
22/// If this is a Zig unit test binary, this tracks the names of the unit
23/// tests that are also fuzz tests. Indexes cannot be used as they may
24/// change between reruns.
25fuzz_tests: std.ArrayList([]const u8) = .empty,
26cached_test_metadata: ?CachedTestMetadata = null,
27
28/// Populated during the fuzz phase if this run step corresponds to a unit test
29/// executable that contains fuzz tests.
30rebuilt_executable: ?Path = null,
31
32pub fn make(
33 run: *Run,
34 run_index: Configuration.Step.Index,
35 maker: *Maker,
36 progress_node: std.Progress.Node,
37) Step.ExtendedMakeError!void {
38 const graph = maker.graph;
39 const gpa = maker.gpa;
40 const step = maker.stepByIndex(run_index);
41 const io = graph.io;
42 const conf = &maker.scanned_config.configuration;
43 const conf_step = run_index.ptr(conf);
44 const conf_run = conf_step.extended.get(conf.extra).run;
45 const cache_root = graph.local_cache_root;
46
47 var arena_allocator: std.heap.ArenaAllocator = .init(gpa);
48 defer arena_allocator.deinit();
49 const arena = arena_allocator.allocator();
50
51 var argv_list: std.ArrayList([]const u8) = .empty;
52 defer argv_list.deinit(gpa);
53
54 var output_placeholders: std.ArrayList(IndexedOutput) = .empty;
55 defer output_placeholders.deinit(gpa);
56
57 var man = graph.cache.obtain();
58 defer man.deinit();
59
60 if (conf_run.environ_map.value) |environ_map_index| {
61 const environ_map = environ_map_index.get(conf);
62 for (environ_map.keys.slice(conf), environ_map.values.slice(conf)) |key, value| {
63 man.hash.addBytesZ(key.slice(conf));
64 man.hash.addBytesZ(value.slice(conf));
65 }
66 }
67
68 man.hash.add(graph.fuzzing);
69 man.hash.add(conf_run.flags.color);
70 man.hash.add(conf_run.flags.disable_zig_progress);
71
72 var any_dep_files = false;
73 var any_output_args = false;
74 var any_cli_positionals = false;
75
76 for (conf_run.args.slice) |arg_index| {
77 const arg = arg_index.get(conf);
78 try argv_list.ensureUnusedCapacity(gpa, 1);
79 switch (arg.flags.tag) {
80 .string => {
81 const prefix = arg.prefix.value.?.slice(conf);
82 argv_list.appendAssumeCapacity(prefix);
83 man.hash.addBytesZ(prefix);
84 },
85 .path_file => {
86 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
87 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";
88 const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index);
89 argv_list.appendAssumeCapacity(try mem.concat(arena, u8, &.{
90 prefix, try convertPathArg(arena, run_index, maker, file_path), suffix,
91 }));
92 man.hash.addBytesZ(prefix);
93 man.hash.addBytesZ(suffix);
94 _ = try man.addFilePath(file_path, null);
95 },
96 .path_directory => {
97 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
98 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";
99 const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index);
100 const resolved_arg = try mem.concat(arena, u8, &.{
101 prefix, try convertPathArg(arena, run_index, maker, file_path), suffix,
102 });
103 argv_list.appendAssumeCapacity(resolved_arg);
104 man.hash.addBytes(resolved_arg);
105 },
106 .file_content => {
107 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
108 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";
109 const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index);
110
111 var result: std.Io.Writer.Allocating = .init(arena);
112 result.writer.writeAll(prefix) catch return error.OutOfMemory;
113
114 const file = file_path.root_dir.handle.openFile(io, file_path.sub_path, .{}) catch |err|
115 return step.fail(maker, "unable to open input file {f}: {t}", .{ file_path, err });
116 defer file.close(io);
117
118 var file_reader = file.reader(io, &.{});
119 _ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) {
120 error.ReadFailed => switch (file_reader.err.?) {
121 error.Canceled => |e| return e,
122 else => |e| return step.fail(maker, "failed to read from {f}: {t}", .{ file_path, e }),
123 },
124 error.WriteFailed => return error.OutOfMemory,
125 };
126 result.writer.writeAll(suffix) catch return error.OutOfMemory;
127
128 argv_list.appendAssumeCapacity(result.written());
129 man.hash.addBytesZ(prefix);
130 man.hash.addBytesZ(suffix);
131 _ = try man.addFilePath(file_path, null);
132 },
133 .artifact => {
134 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
135 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";
136 const producer_index = arg.producer.value.?;
137 const producer_step = producer_index.ptr(conf);
138 const producer = producer_step.extended.get(conf.extra).compile;
139 const producer_make_comp_step = maker.stepByIndex(producer_index);
140 const producer_make_comp = &producer_make_comp_step.extended.compile;
141
142 const file_path = producer_make_comp.installed_path orelse maker.generatedPath(producer.generated_bin.value.?).*;
143
144 argv_list.appendAssumeCapacity(try mem.concat(arena, u8, &.{
145 prefix, try convertPathArg(arena, run_index, maker, file_path), suffix,
146 }));
147
148 _ = try man.addFilePath(file_path, null);
149 },
150 .output_file, .output_directory => {
151 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
152 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";
153 const basename = arg.basename.value.?.slice(conf);
154
155 man.hash.addBytesZ(prefix);
156 man.hash.addBytesZ(basename);
157 man.hash.addBytesZ(suffix);
158 man.hash.add(arg.flags.dep_file);
159
160 any_dep_files = any_dep_files or arg.flags.dep_file;
161 any_output_args = true;
162
163 // Add a placeholder into the argument list because we need the
164 // manifest hash to be updated with all arguments before the
165 // object directory is computed.
166 try output_placeholders.append(gpa, .{
167 .index = @intCast(argv_list.items.len),
168 .arg_index = arg_index,
169 });
170 argv_list.items.len += 1;
171 },
172 .passthru => {
173 any_cli_positionals = true;
174 if (maker.run_args) |run_args| {
175 try argv_list.appendSlice(gpa, run_args);
176 man.hash.addListOfBytes(run_args);
177 }
178 },
179 }
180 }
181
182 man.hash.add(conf_run.flags.test_runner_mode);
183 if (conf_run.flags.test_runner_mode) {
184 const cache_dir_string = try convertPathArg(arena, run_index, maker, .{ .root_dir = cache_root });
185
186 try argv_list.ensureUnusedCapacity(gpa, 3);
187 argv_list.appendAssumeCapacity(try allocPrint(arena, "--cache-dir={s}", .{cache_dir_string}));
188 argv_list.appendAssumeCapacity(try allocPrint(arena, "--seed=0x{x}", .{graph.random_seed}));
189 argv_list.appendAssumeCapacity("--listen=-");
190 }
191
192 switch (conf_run.stdin.u) {
193 .bytes => |bytes| {
194 man.hash.addBytes(bytes.slice(conf));
195 },
196 .lazy_path => |lazy_path| {
197 const file_path = try maker.resolveLazyPathIndex(arena, lazy_path, run_index);
198 _ = try man.addFilePath(file_path, null);
199 },
200 .none => {},
201 }
202
203 if (conf_run.captured_stdout.value) |captured| {
204 man.hash.addBytes(captured.basename.slice(conf));
205 man.hash.add(conf_run.flags.stdout_trim_whitespace);
206 }
207
208 if (conf_run.captured_stderr.value) |captured| {
209 man.hash.addBytes(captured.basename.slice(conf));
210 man.hash.add(conf_run.flags.stderr_trim_whitespace);
211 }
212
213 switch (conf_run.flags.stdio) {
214 .infer_from_args, .inherit, .zig_test => {},
215 .check => {
216 man.hash.addBytes(if (conf_run.expect_stderr_exact.value) |bytes| bytes.slice(conf) else "");
217 man.hash.addBytes(if (conf_run.expect_stdout_exact.value) |bytes| bytes.slice(conf) else "");
218 for (conf_run.expect_stderr_match.slice) |bytes| man.hash.addBytes(bytes.slice(conf));
219 for (conf_run.expect_stdout_match.slice) |bytes| man.hash.addBytes(bytes.slice(conf));
220 man.hash.add(conf_run.flags2.expect_term_status);
221 man.hash.addOptional(conf_run.expect_term_value.value);
222 },
223 }
224
225 for (conf_run.file_inputs.slice) |lazy_path| {
226 const file_path = try maker.resolveLazyPathIndex(arena, lazy_path, run_index);
227 _ = try man.addFilePath(file_path, null);
228 }
229
230 if (conf_run.cwd.value) |lazy_path| {
231 const cwd_path = try maker.resolveLazyPathIndex(arena, lazy_path, run_index);
232 _ = man.hash.addBytes(try cwd_path.toString(arena));
233 }
234
235 // Whether the Run step has side effects *other than* updating the output arguments.
236 // When fuzzing we need to always run the test runner to populate fuzz_tests.
237 const has_side_effects = graph.fuzzing or conf_run.flags.has_side_effects or any_cli_positionals or
238 switch (conf_run.flags.stdio) {
239 .infer_from_args => !any_output_args and
240 conf_run.captured_stdout.value == null and
241 conf_run.captured_stderr.value == null,
242 .inherit => true,
243 .check, .zig_test => false,
244 };
245
246 if (!has_side_effects and try step.cacheHitAndWatch(maker, &man)) {
247 // Cache hit; skip running command.
248 const digest = man.final();
249 try populateGeneratedStdIo(maker, &conf_run, cache_root, &digest);
250 try populateGeneratedPaths(maker, output_placeholders.items, cache_root, &digest);
251 step.result_cached = true;
252 return;
253 }
254
255 if (!any_dep_files) {
256 // We already know the final output paths; use them directly.
257 const digest = if (has_side_effects) man.hash.final() else man.final();
258 const output_dir_path = "o" ++ Dir.path.sep_str ++ &digest;
259 try populateGeneratedStdIo(maker, &conf_run, cache_root, &digest);
260 try populateGeneratedPathsCreateDirs(arena, run_index, maker, output_dir_path, output_placeholders.items, argv_list.items);
261 try runCommand(arena, run, run_index, maker, progress_node, argv_list.items, has_side_effects, output_dir_path, null);
262 if (!has_side_effects) try step.writeManifestAndWatch(maker, &man);
263 return;
264 }
265
266 // We do not know the final output paths yet; use temporary directory to run the command.
267 var rand_int: u64 = undefined;
268 io.random(@ptrCast(&rand_int));
269 const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);
270
271 try populateGeneratedPathsCreateDirs(arena, run_index, maker, tmp_dir_path, output_placeholders.items, argv_list.items);
272 try runCommand(arena, run, run_index, maker, progress_node, argv_list.items, has_side_effects, tmp_dir_path, null);
273
274 for (output_placeholders.items) |placeholder| {
275 const arg = placeholder.arg_index.get(conf);
276 switch (arg.flags.tag) {
277 .output_file => if (arg.flags.dep_file) {
278 const generated_path = maker.generatedPath(arg.generated.value.?).*;
279 const result = if (has_side_effects)
280 man.addDepFile(generated_path.root_dir.handle, generated_path.sub_path)
281 else
282 man.addDepFilePost(generated_path.root_dir.handle, generated_path.sub_path);
283 result catch |err| switch (err) {
284 error.OutOfMemory, error.Canceled => |e| return e,
285 else => |e| return step.fail(maker, "failed adding to cache the file {f}: {t}", .{
286 generated_path, e,
287 }),
288 };
289 },
290 .output_directory => continue,
291 else => unreachable,
292 }
293 }
294
295 const digest = if (has_side_effects) man.hash.final() else man.final();
296
297 const any_output = output_placeholders.items.len > 0 or
298 conf_run.captured_stdout.value != null or conf_run.captured_stderr.value != null;
299
300 if (any_output) {
301 // Rename into place.
302 const tmp_path: Path = .{ .root_dir = cache_root, .sub_path = tmp_dir_path };
303 const dst_path: Path = .{ .root_dir = cache_root, .sub_path = "o" ++ Dir.path.sep_str ++ &digest };
304 Dir.rename(
305 tmp_path.root_dir.handle,
306 tmp_path.sub_path,
307 dst_path.root_dir.handle,
308 dst_path.sub_path,
309 io,
310 ) catch |err| switch (err) {
311 error.DirNotEmpty => {
312 dst_path.root_dir.handle.deleteTree(io, dst_path.sub_path) catch |del_err|
313 return step.fail(maker, "failed to remove tree {f}: {t}", .{ dst_path, del_err });
314
315 Dir.rename(
316 tmp_path.root_dir.handle,
317 tmp_path.sub_path,
318 dst_path.root_dir.handle,
319 dst_path.sub_path,
320 io,
321 ) catch |retry_err| return step.fail(maker, "failed to rename directory {f} to {f}: {t}", .{
322 tmp_path, dst_path, retry_err,
323 });
324 },
325 else => return step.fail(maker, "failed to rename directory {f} to {f}: {t}", .{
326 tmp_path, dst_path, err,
327 }),
328 };
329 }
330
331 if (!has_side_effects) try step.writeManifestAndWatch(maker, &man);
332
333 try populateGeneratedStdIo(maker, &conf_run, cache_root, &digest);
334 try populateGeneratedPaths(maker, output_placeholders.items, cache_root, &digest);
335}
336
337/// Reads stdout of a Zig test process until a termination condition is reached:
338/// * A write fails, indicating the child unexpectedly closed stdin
339/// * A test (or a response from the test runner) times out
340/// * The wait fails, indicating the child closed stdout and stderr
341fn waitZigTest(
342 arena: Allocator,
343 run: *Run,
344 run_index: Configuration.Step.Index,
345 maker: *Maker,
346 child: *process.Child,
347 progress_node: std.Progress.Node,
348 multi_reader: *Io.File.MultiReader,
349 opt_metadata: *?TestMetadata,
350 results: *Step.TestResults,
351) !union(enum) {
352 write_failed: anyerror,
353 no_poll: struct {
354 active_test_index: ?u32,
355 ns_elapsed: u64,
356 },
357 timeout: struct {
358 active_test_index: ?u32,
359 ns_elapsed: u64,
360 },
361} {
362 const graph = maker.graph;
363 const gpa = maker.gpa;
364 const io = graph.io;
365 const step = maker.stepByIndex(run_index);
366
367 var sub_prog_node: ?std.Progress.Node = null;
368 defer if (sub_prog_node) |n| n.end();
369
370 if (opt_metadata.*) |*md| {
371 // Previous unit test process died or was killed; we're continuing where it left off
372 requestNextTest(io, child.stdin.?, md, &sub_prog_node) catch |err| return .{ .write_failed = err };
373 } else {
374 // Running unit tests normally
375 run.fuzz_tests.clearRetainingCapacity();
376 sendMessage(io, child.stdin.?, .query_test_metadata) catch |err| return .{ .write_failed = err };
377 }
378
379 var active_test_index: ?u32 = null;
380
381 var last_update: Io.Clock.Timestamp = .now(io, .awake);
382
383 // This timeout is used when we're waiting on the test runner itself rather than a user-specified
384 // test. For instance, if the test runner leaves this much time between us requesting a test to
385 // start and it acknowledging the test starting, we terminate the child and raise an error. This
386 // *should* never happen, but could in theory be caused by some very unlucky IB in a test.
387 const response_timeout: Io.Clock.Duration = t: {
388 const ns = @max(maker.unit_test_timeout_ns orelse 0, 60 * std.time.ns_per_s);
389 break :t .{ .clock = .awake, .raw = .fromNanoseconds(ns) };
390 };
391 const test_timeout: ?Io.Clock.Duration = if (maker.unit_test_timeout_ns) |ns| .{
392 .clock = .awake,
393 .raw = .fromNanoseconds(ns),
394 } else null;
395
396 const stdout = multi_reader.reader(0);
397 const stderr = multi_reader.reader(1);
398 const Header = std.zig.Server.Message.Header;
399
400 while (true) {
401 const timeout: Io.Timeout = t: {
402 const opt_duration = if (active_test_index == null) response_timeout else test_timeout;
403 const duration = opt_duration orelse break :t .none;
404 break :t .{ .deadline = last_update.addDuration(duration) };
405 };
406
407 // This block is exited when `stdout` contains enough bytes for a `Header`.
408 header_ready: {
409 if (stdout.buffered().len >= @sizeOf(Header)) {
410 // We already have one, no need to poll!
411 break :header_ready;
412 }
413
414 multi_reader.fill(64, timeout) catch |err| switch (err) {
415 error.Timeout => return .{ .timeout = .{
416 .active_test_index = active_test_index,
417 .ns_elapsed = @intCast(last_update.untilNow(io).raw.nanoseconds),
418 } },
419 error.EndOfStream => return .{ .no_poll = .{
420 .active_test_index = active_test_index,
421 .ns_elapsed = @intCast(last_update.untilNow(io).raw.nanoseconds),
422 } },
423 else => |e| return e,
424 };
425
426 continue;
427 }
428 // There is definitely a header available now -- read it.
429 const header = stdout.takeStruct(Header, .little) catch unreachable;
430
431 while (stdout.buffered().len < header.bytes_len) {
432 multi_reader.fill(64, timeout) catch |err| switch (err) {
433 error.Timeout => return .{ .timeout = .{
434 .active_test_index = active_test_index,
435 .ns_elapsed = @intCast(last_update.untilNow(io).raw.nanoseconds),
436 } },
437 error.EndOfStream => return .{ .no_poll = .{
438 .active_test_index = active_test_index,
439 .ns_elapsed = @intCast(last_update.untilNow(io).raw.nanoseconds),
440 } },
441 else => |e| return e,
442 };
443 }
444
445 const body = stdout.take(header.bytes_len) catch unreachable;
446 var body_r: std.Io.Reader = .fixed(body);
447 switch (header.tag) {
448 .zig_version => {
449 if (!std.mem.eql(u8, builtin.zig_version_string, body)) return step.fail(
450 maker,
451 "zig version mismatch build runner vs compiler: '{s}' vs '{s}'",
452 .{ builtin.zig_version_string, body },
453 );
454 },
455 .test_metadata => {
456 // `metadata` would only be populated if we'd already seen a `test_metadata`, but we
457 // only request it once (and importantly, we don't re-request it if we kill and
458 // restart the test runner).
459 assert(opt_metadata.* == null);
460
461 const tm_hdr = body_r.takeStruct(std.zig.Server.Message.TestMetadata, .little) catch unreachable;
462 results.test_count = tm_hdr.tests_len;
463
464 const names = try arena.alloc(u32, results.test_count);
465 for (names) |*dest| dest.* = body_r.takeInt(u32, .little) catch unreachable;
466
467 const expected_panic_msgs = try arena.alloc(u32, results.test_count);
468 for (expected_panic_msgs) |*dest| dest.* = body_r.takeInt(u32, .little) catch unreachable;
469
470 const string_bytes = body_r.take(tm_hdr.string_bytes_len) catch unreachable;
471
472 progress_node.setEstimatedTotalItems(names.len);
473 opt_metadata.* = .{
474 .string_bytes = try arena.dupe(u8, string_bytes),
475 .ns_per_test = try arena.alloc(u64, results.test_count),
476 .names = names,
477 .expected_panic_msgs = expected_panic_msgs,
478 .next_index = 0,
479 .prog_node = progress_node,
480 };
481 @memset(opt_metadata.*.?.ns_per_test, std.math.maxInt(u64));
482
483 active_test_index = null;
484 last_update = .now(io, .awake);
485
486 requestNextTest(io, child.stdin.?, &opt_metadata.*.?, &sub_prog_node) catch |err| return .{ .write_failed = err };
487 },
488 .test_started => {
489 active_test_index = opt_metadata.*.?.next_index - 1;
490 last_update = .now(io, .awake);
491 },
492 .test_results => {
493 const md = &opt_metadata.*.?;
494
495 const tr_hdr = body_r.takeStruct(std.zig.Server.Message.TestResults, .little) catch unreachable;
496 assert(tr_hdr.index == active_test_index);
497
498 switch (tr_hdr.flags.status) {
499 .pass => {},
500 .skip => results.skip_count +|= 1,
501 .fail => results.fail_count +|= 1,
502 }
503 const leak_count = tr_hdr.flags.leak_count;
504 const log_err_count = tr_hdr.flags.log_err_count;
505 results.leak_count +|= leak_count;
506 results.log_err_count +|= log_err_count;
507
508 if (tr_hdr.flags.fuzz) try run.fuzz_tests.append(gpa, md.testName(tr_hdr.index));
509
510 if (tr_hdr.flags.status == .fail) {
511 const name = md.testName(tr_hdr.index);
512 const stderr_bytes = std.mem.trim(u8, stderr.buffered(), "\n");
513 stderr.tossBuffered();
514 if (stderr_bytes.len == 0) {
515 try step.addError(maker, "'{s}' failed without output", .{name});
516 } else {
517 try step.addError(maker, "'{s}' failed:\n{s}", .{ name, stderr_bytes });
518 }
519 } else if (leak_count > 0) {
520 const name = md.testName(tr_hdr.index);
521 const stderr_bytes = std.mem.trim(u8, stderr.buffered(), "\n");
522 stderr.tossBuffered();
523 try step.addError(maker, "'{s}' leaked {d} allocations:\n{s}", .{ name, leak_count, stderr_bytes });
524 } else if (log_err_count > 0) {
525 const name = md.testName(tr_hdr.index);
526 const stderr_bytes = std.mem.trim(u8, stderr.buffered(), "\n");
527 stderr.tossBuffered();
528 try step.addError(maker, "'{s}' logged {d} errors:\n{s}", .{ name, log_err_count, stderr_bytes });
529 }
530
531 active_test_index = null;
532
533 const now: Io.Clock.Timestamp = .now(io, .awake);
534 md.ns_per_test[tr_hdr.index] = @intCast(last_update.durationTo(now).raw.nanoseconds);
535 last_update = now;
536
537 requestNextTest(io, child.stdin.?, md, &sub_prog_node) catch |err| return .{ .write_failed = err };
538 },
539 else => {}, // ignore other messages
540 }
541 }
542}
543
544const FuzzTestRunner = struct {
545 run: *Run,
546 run_index: Configuration.Step.Index,
547 ctx: FuzzContext,
548 coverage_id: ?u64,
549
550 instances: []Instance,
551 /// The indexes of this are layed out such that it is effectively an array
552 /// of `[instances.len][3]Io.Operation.Storage` of stdin, stdout, stderr.
553 batch: Io.Batch,
554 /// LIFO. Stream of message bodies trailed by PendingBroadcastFooter.
555 pending_broadcasts: std.ArrayList(u8),
556 broadcast: std.ArrayList(u8),
557 broadcast_undelivered: u32,
558
559 const Instance = struct {
560 child: process.Child,
561 message: std.ArrayListAligned(u8, .@"4"),
562 broadcast_written: usize,
563 stderr: std.ArrayList(u8),
564 stdin_vec: [1][]u8,
565 stdout_vec: [1][]u8,
566 stderr_vec: [1][]u8,
567 progress_node: std.Progress.Node,
568
569 fn messageHeader(instance: *Instance) InHeader {
570 assert(instance.message.items.len >= @sizeOf(InHeader));
571 const header_ptr: *InHeader = @ptrCast(instance.message.items);
572 var header = header_ptr.*;
573 if (std.builtin.Endian.native != .little) {
574 std.mem.byteSwapAllFields(InHeader, &header);
575 }
576 return header;
577 }
578 };
579
580 const PendingBroadcastFooter = struct {
581 from_id: u32,
582 body_len: u32,
583 };
584
585 const InHeader = std.zig.Server.Message.Header;
586 const OutHeader = std.zig.Client.Message.Header;
587
588 const stdin_i = 0;
589 const stdout_i = 1;
590 const stderr_i = 2;
591
592 fn init(
593 run: *Run,
594 run_index: Configuration.Step.Index,
595 ctx: FuzzContext,
596 progress_node: std.Progress.Node,
597 spawn_options: process.SpawnOptions,
598 ) !FuzzTestRunner {
599 const maker = ctx.fuzz.maker;
600 const graph = maker.graph;
601 const gpa = maker.gpa;
602 const io = graph.io;
603
604 const n_instances = switch (ctx.fuzz.mode) {
605 .forever => graph.max_jobs orelse @min(
606 std.Thread.getCpuCount() catch 1,
607 (std.math.maxInt(u32) - 2) / 3,
608 ),
609 .limit => 1,
610 };
611 const instances = try gpa.alloc(Instance, n_instances);
612 errdefer gpa.free(instances);
613 const batch_storage = try gpa.alloc(Io.Operation.Storage, instances.len * 3);
614 errdefer gpa.free(batch_storage);
615
616 @memset(instances, .{
617 .child = undefined,
618 .message = .empty,
619 .broadcast_written = undefined,
620 .stderr = .empty,
621 .stdin_vec = undefined,
622 .stdout_vec = undefined,
623 .stderr_vec = undefined,
624 .progress_node = undefined,
625 });
626 for (0.., instances) |id, *instance| {
627 errdefer for (instances[0..id]) |*spawned| {
628 spawned.child.kill(io);
629 spawned.progress_node.end();
630 };
631 instance.child = try process.spawn(io, spawn_options);
632 instance.progress_node = progress_node.start("starting fuzzer", 0);
633 }
634
635 return .{
636 .run = run,
637 .run_index = run_index,
638 .ctx = ctx,
639 .coverage_id = null,
640
641 .instances = instances,
642 .batch = .init(batch_storage),
643 .pending_broadcasts = .empty,
644 .broadcast = .empty,
645 .broadcast_undelivered = 0,
646 };
647 }
648
649 fn deinit(f: *FuzzTestRunner) void {
650 const maker = f.ctx.fuzz.maker;
651 const run_index = f.run_index;
652
653 const graph = maker.graph;
654 const gpa = maker.gpa;
655 const io = graph.io;
656 const step = maker.stepByIndex(run_index);
657
658 f.batch.cancel(io);
659 gpa.free(f.batch.storage);
660 var total_rss: usize = 0;
661 for (f.instances) |*instance| {
662 instance.child.kill(io);
663 instance.message.deinit(gpa);
664 instance.stderr.deinit(gpa);
665 instance.progress_node.end();
666 total_rss += instance.child.resource_usage_statistics.getMaxRss() orelse 0;
667 }
668 step.result_peak_rss = @max(step.result_peak_rss, total_rss);
669 gpa.free(f.instances);
670 }
671
672 fn startInstances(f: *FuzzTestRunner) !void {
673 const maker = f.ctx.fuzz.maker;
674 const run_index = f.run_index;
675 const run = f.run;
676
677 const graph = maker.graph;
678 const io = graph.io;
679 const step = maker.stepByIndex(run_index);
680
681 for (0.., f.instances) |id, *instance| {
682 const id32: u32 = @intCast(id);
683 (switch (f.ctx.fuzz.mode) {
684 .forever => sendRunFuzzTestMessage(
685 io,
686 instance.child.stdin.?,
687 run.fuzz_tests.items,
688 .forever,
689 id32,
690 ),
691 .limit => |limit| sendRunFuzzTestMessage(
692 io,
693 instance.child.stdin.?,
694 run.fuzz_tests.items,
695 .iterations,
696 limit.amount,
697 ),
698 }) catch |write_err| {
699 // The runner unexpectedly closed stdin, which means it crashed during initialization.
700 // Clean up everything and wait for the child to exit.
701 instance.child.stdin.?.close(io);
702 instance.child.stdin = null;
703 const term = try instance.child.wait(io);
704 return step.fail(
705 maker,
706 "unable to write stdin ({t}); test process unexpectedly {f}",
707 .{ write_err, fmtTerm(term) },
708 );
709 };
710
711 try f.addStdoutRead(id32, @sizeOf(InHeader));
712 try f.addStderrRead(id32);
713 }
714 }
715
716 fn listen(f: *FuzzTestRunner, arena: Allocator) !void {
717 const maker = f.ctx.fuzz.maker;
718 const graph = maker.graph;
719 const io = graph.io;
720
721 while (true) {
722 try f.batch.awaitConcurrent(io, .none);
723 while (f.batch.next()) |completion| {
724 const id = completion.index / 3;
725 const result = completion.result;
726 switch (completion.index % 3) {
727 0 => try f.completeStdinWrite(id, result.file_write_streaming catch |e| switch (e) {
728 // Avoid calling `instanceEos` until EndOfStream is seen with stderr so
729 // that all stderr is collected.
730 error.BrokenPipe => continue,
731 else => |write_e| return write_e,
732 }),
733 1 => try f.completeStdoutRead(id, result.file_read_streaming catch |e| switch (e) {
734 // Avoid calling `instanceEos` until EndOfStream is seen with stderr so
735 // that all stderr is collected.
736 error.EndOfStream => continue,
737 else => |read_e| return read_e,
738 }),
739 2 => try f.completeStderrRead(id, result.file_read_streaming catch |e| switch (e) {
740 error.EndOfStream => return f.instanceEos(arena, id),
741 else => |read_e| return read_e,
742 }),
743 else => unreachable,
744 }
745 }
746 }
747 }
748
749 fn completeStdoutRead(f: *FuzzTestRunner, id: u32, n: usize) !void {
750 const maker = f.ctx.fuzz.maker;
751 const instance = &f.instances[id];
752 const run_index = f.run_index;
753 const run = f.run;
754
755 const graph = maker.graph;
756 const gpa = maker.gpa;
757 const io = graph.io;
758 const step = maker.stepByIndex(run_index);
759
760 instance.message.items.len += n;
761 const total_read = instance.message.items.len;
762 if (total_read < @sizeOf(InHeader)) {
763 try f.addStdoutRead(id, @sizeOf(InHeader));
764 return;
765 }
766
767 const header = instance.messageHeader();
768 const body = instance.message.items[@sizeOf(InHeader)..];
769 if (body.len != header.bytes_len) {
770 try f.addStdoutRead(id, @sizeOf(InHeader) + header.bytes_len);
771 return;
772 }
773
774 switch (header.tag) {
775 .zig_version => {
776 if (!std.mem.eql(u8, builtin.zig_version_string, body)) return step.fail(
777 maker,
778 "zig version mismatch build runner vs compiler: '{s}' vs '{s}'",
779 .{ builtin.zig_version_string, body },
780 );
781 },
782 .coverage_id => {
783 var body_r: Io.Reader = .fixed(body);
784 f.coverage_id = body_r.takeInt(u64, .little) catch unreachable;
785 const cumulative_runs = body_r.takeInt(u64, .little) catch unreachable;
786 const cumulative_unique = body_r.takeInt(u64, .little) catch unreachable;
787 const cumulative_coverage = body_r.takeInt(u64, .little) catch unreachable;
788
789 const fuzz = f.ctx.fuzz;
790 fuzz.queue_mutex.lockUncancelable(io);
791 defer fuzz.queue_mutex.unlock(io);
792 try fuzz.msg_queue.append(gpa, .{ .coverage = .{
793 .id = f.coverage_id.?,
794 .cumulative = .{
795 .runs = cumulative_runs,
796 .unique = cumulative_unique,
797 .coverage = cumulative_coverage,
798 },
799 .run = run_index,
800 } });
801 fuzz.queue_cond.signal(io);
802 },
803 .fuzz_start_addr => {
804 var body_r: Io.Reader = .fixed(body);
805 const fuzz = f.ctx.fuzz;
806 const addr = body_r.takeInt(u64, .little) catch unreachable;
807
808 fuzz.queue_mutex.lockUncancelable(io);
809 defer fuzz.queue_mutex.unlock(io);
810 try fuzz.msg_queue.append(gpa, .{ .entry_point = .{
811 .addr = addr,
812 .coverage_id = f.coverage_id.?,
813 } });
814 fuzz.queue_cond.signal(io);
815 },
816 .fuzz_test_change => {
817 const test_i = std.mem.readInt(u32, body[0..4], .little);
818 instance.progress_node.setName(run.fuzz_tests.items[test_i]);
819 },
820 .broadcast_fuzz_input => {
821 if (f.instances.len == 1) {
822 // No other processes to broadcast to.
823 } else if (f.broadcast_undelivered == 0) {
824 try f.instanceBroadcast(id, body);
825 } else {
826 const footer: PendingBroadcastFooter = .{
827 .from_id = id,
828 .body_len = @intCast(body.len),
829 };
830 // There is another broadcast in progress so add this one to the queue.
831 const size = @sizeOf(PendingBroadcastFooter) + body.len;
832 try f.pending_broadcasts.ensureUnusedCapacity(gpa, size);
833 f.pending_broadcasts.appendSliceAssumeCapacity(body);
834 f.pending_broadcasts.appendSliceAssumeCapacity(@ptrCast(&footer));
835 }
836 },
837 else => {}, // ignore other messages
838 }
839
840 instance.message.clearRetainingCapacity();
841 try f.addStdoutRead(id, @sizeOf(InHeader));
842 }
843
844 fn completeStderrRead(f: *FuzzTestRunner, id: u32, n: usize) !void {
845 const instance = &f.instances[id];
846 instance.stderr.items.len += n;
847 try f.addStderrRead(id);
848 }
849
850 fn completeStdinWrite(f: *FuzzTestRunner, id: u32, n: usize) !void {
851 const instance = &f.instances[id];
852
853 instance.broadcast_written += n;
854 if (instance.broadcast_written == f.broadcast.items.len) {
855 f.broadcast_undelivered -= 1;
856 if (f.broadcast_undelivered == 0) {
857 try f.broadcastComplete();
858 }
859 } else {
860 f.addStdinWrite(id);
861 }
862 }
863
864 fn addStdoutRead(f: *FuzzTestRunner, id: u32, end: usize) !void {
865 const maker = f.ctx.fuzz.maker;
866 const gpa = maker.gpa;
867 const instance = &f.instances[id];
868
869 try instance.message.ensureTotalCapacity(gpa, end);
870 const start = instance.message.items.len;
871 instance.stdout_vec = .{instance.message.allocatedSlice()[start..end]};
872 f.batch.addAt(id * 3 + stdout_i, .{ .file_read_streaming = .{
873 .file = instance.child.stdout.?,
874 .data = &instance.stdout_vec,
875 } });
876 }
877
878 fn addStderrRead(f: *FuzzTestRunner, id: u32) !void {
879 const maker = f.ctx.fuzz.maker;
880 const gpa = maker.gpa;
881 const instance = &f.instances[id];
882
883 try instance.stderr.ensureUnusedCapacity(gpa, 1);
884 instance.stderr_vec = .{instance.stderr.unusedCapacitySlice()};
885 f.batch.addAt(id * 3 + stderr_i, .{ .file_read_streaming = .{
886 .file = instance.child.stderr.?,
887 .data = &instance.stderr_vec,
888 } });
889 }
890
891 fn addStdinWrite(f: *FuzzTestRunner, id: u32) void {
892 const instance = &f.instances[id];
893
894 assert(f.broadcast.items.len != instance.broadcast_written);
895 instance.stdin_vec = .{f.broadcast.items[instance.broadcast_written..]};
896 f.batch.addAt(id * 3 + stdin_i, .{ .file_write_streaming = .{
897 .file = instance.child.stdin.?,
898 .data = &instance.stdin_vec,
899 } });
900 }
901
902 fn instanceEos(f: *FuzzTestRunner, arena: Allocator, id: u32) !void {
903 const maker = f.ctx.fuzz.maker;
904 const instance = &f.instances[id];
905 const run_index = f.run_index;
906
907 const graph = maker.graph;
908 const io = graph.io;
909 const step = maker.stepByIndex(run_index);
910
911 instance.child.stdin.?.close(io);
912 instance.child.stdin = null;
913 const term = try instance.child.wait(io);
914 if (!termMatches(.{ .exited = 0 }, term)) {
915 step.result_stderr = try f.mergedStderr(arena);
916 try f.saveCrash(id, term);
917 return step.fail(maker, "test process unexpectedly {f}", .{fmtTerm(term)});
918 }
919 }
920
921 fn saveCrash(f: *FuzzTestRunner, id: u32, term: process.Child.Term) !void {
922 const fuzz = f.ctx.fuzz;
923 const run_index = f.run_index;
924 const run = f.run;
925
926 const maker = fuzz.maker;
927 const step = maker.stepByIndex(run_index);
928 const graph = maker.graph;
929 const io = graph.io;
930 const cache_root = graph.local_cache_root;
931
932 if (f.coverage_id == null) return;
933
934 // Search for the input file corresponding to the instance
935 const InputHeader = std.Build.abi.fuzz.MmapInputHeader;
936 var in_r_buf: [@sizeOf(InputHeader)]u8 = undefined;
937 var in_r: Io.File.Reader = undefined;
938 var in_f: Io.File = undefined;
939 var in_name_buf: [12]u8 = undefined;
940 var in_name: []const u8 = undefined;
941 var i: u32 = 0;
942 const header: InputHeader = while (true) : ({
943 if (i == std.math.maxInt(u32)) return;
944 i += 1;
945 }) {
946 const name_prefix = "f" ++ Dir.path.sep_str ++ "in";
947 in_name = std.fmt.bufPrint(&in_name_buf, name_prefix ++ "{x}", .{i}) catch unreachable;
948 in_f = cache_root.handle.openFile(io, in_name, .{
949 .lock = .exclusive,
950 .lock_nonblocking = true,
951 }) catch |e| switch (e) {
952 error.FileNotFound => return,
953 error.WouldBlock => continue, // Can not be from
954 // the crashed instance since it is still locked.
955 else => return step.fail(maker, "failed to open file '{f}{s}': {t}", .{
956 cache_root, in_name, e,
957 }),
958 };
959
960 in_r = in_f.readerStreaming(io, &in_r_buf);
961 const header = in_r.interface.takeStruct(InputHeader, .little) catch |e| {
962 in_f.close(io);
963 switch (e) {
964 error.ReadFailed => return step.fail(maker, "failed to read file '{f}{s}': {t}", .{
965 cache_root, in_name, in_r.err.?,
966 }),
967 error.EndOfStream => continue,
968 }
969 };
970
971 if (header.pc_digest == f.coverage_id.? and
972 header.instance_id == id and
973 header.test_i < run.fuzz_tests.items.len)
974 {
975 break header;
976 }
977
978 in_f.close(io);
979 };
980 defer in_f.close(io);
981
982 // Save it to a seperate file
983 const crash_name = "f" ++ Dir.path.sep_str ++ "crash";
984 const out = cache_root.handle.createFile(io, crash_name, .{
985 .lock = .exclusive, // Multiple run steps could have found a crash at the same time
986 }) catch |e| return step.fail(maker, "failed to create file '{f}{s}': {t}", .{
987 cache_root, crash_name, e,
988 });
989 defer out.close(io);
990
991 var out_w_buf: [512]u8 = undefined;
992 var out_w = out.writerStreaming(io, &out_w_buf);
993 _ = out_w.interface.sendFileAll(&in_r, .limited(header.len)) catch |e| switch (e) {
994 error.ReadFailed => return step.fail(maker, "failed to read file '{f}{s}': {t}", .{
995 cache_root, in_name, in_r.err.?,
996 }),
997 error.WriteFailed => return step.fail(maker, "failed to write file '{f}{s}': {t}", .{
998 cache_root, crash_name, out_w.err.?,
999 }),
1000 };
1001
1002 return step.fail(maker, "test '{s}' {f}; input saved to '{f}{s}'", .{
1003 run.fuzz_tests.items[header.test_i],
1004 fmtTerm(term),
1005 cache_root,
1006 crash_name,
1007 });
1008 }
1009
1010 fn instanceBroadcast(f: *FuzzTestRunner, from_id: u32, bytes: []const u8) !void {
1011 assert(f.instances.len > 1);
1012 assert(f.broadcast_undelivered == 0); // no other broadcast is progress
1013 assert(f.broadcast.items.len == 0);
1014 assert(from_id < f.instances.len);
1015
1016 const maker = f.ctx.fuzz.maker;
1017 const gpa = maker.gpa;
1018
1019 var out_header: OutHeader = .{
1020 .tag = .new_fuzz_input,
1021 .bytes_len = @intCast(bytes.len),
1022 };
1023 if (std.builtin.Endian.native != .little) {
1024 std.mem.byteSwapAllFields(OutHeader, &out_header);
1025 }
1026 try f.broadcast.ensureTotalCapacity(gpa, @sizeOf(OutHeader) + bytes.len);
1027 f.broadcast.appendSliceAssumeCapacity(@ptrCast(&out_header));
1028 f.broadcast.appendSliceAssumeCapacity(bytes);
1029
1030 f.broadcast_undelivered = @intCast(f.instances.len - 1);
1031 for (0.., f.instances) |to_id, *instance| {
1032 if (to_id == from_id) continue;
1033 instance.broadcast_written = 0;
1034 f.addStdinWrite(@intCast(to_id));
1035 }
1036 }
1037
1038 fn broadcastComplete(f: *FuzzTestRunner) !void {
1039 assert(f.instances.len > 1);
1040 assert(f.broadcast_undelivered == 0);
1041 f.broadcast.clearRetainingCapacity();
1042
1043 const pending = &f.pending_broadcasts;
1044 if (pending.items.len != 0) {
1045 // Another broadcast is pending; copy it over to `broadcast`
1046
1047 const footer_len = @sizeOf(PendingBroadcastFooter);
1048 const footer_bytes = pending.items[pending.items.len - footer_len ..];
1049 const footer: *align(1) PendingBroadcastFooter = @ptrCast(footer_bytes);
1050 pending.items.len -= footer_len;
1051
1052 const body = pending.items[pending.items.len - footer.body_len ..];
1053 try f.instanceBroadcast(footer.from_id, body);
1054 pending.items.len -= body.len;
1055 }
1056 }
1057
1058 fn mergedStderr(f: *FuzzTestRunner, arena: Allocator) Allocator.Error![]const u8 {
1059 // Collect any available stderr
1060 while (f.batch.next()) |completion| {
1061 if (completion.index % 3 != 2) continue;
1062 const len = completion.result.file_read_streaming catch continue;
1063 f.instances[completion.index / 3].stderr.items.len += len;
1064 }
1065
1066 var stderr_len: usize = 0;
1067 for (f.instances) |*instance| stderr_len += instance.stderr.items.len;
1068 const stderr = try arena.alloc(u8, stderr_len);
1069
1070 stderr_len = 0;
1071 for (f.instances) |*instance| {
1072 @memcpy(stderr[stderr_len..][0..instance.stderr.items.len], instance.stderr.items);
1073 stderr_len += instance.stderr.items.len;
1074 }
1075 return stderr;
1076 }
1077};
1078
1079fn evalFuzzTest(
1080 run: *Run,
1081 run_index: Configuration.Step.Index,
1082 progress_node: std.Progress.Node,
1083 spawn_options: process.SpawnOptions,
1084 fuzz_context: FuzzContext,
1085) !void {
1086 var f: FuzzTestRunner = try .init(run, run_index, fuzz_context, progress_node, spawn_options);
1087 defer f.deinit();
1088 try f.startInstances();
1089 try f.listen(fuzz_context.fuzz.maker.graph.arena);
1090}
1091
1092const StdioPollEnum = enum { stdout, stderr };
1093
1094fn evalZigTest(
1095 arena: Allocator,
1096 run: *Run,
1097 run_index: Configuration.Step.Index,
1098 maker: *Maker,
1099 progress_node: std.Progress.Node,
1100 spawn_options: process.SpawnOptions,
1101 fuzz_context: ?FuzzContext,
1102) !void {
1103 if (fuzz_context != null) {
1104 try evalFuzzTest(run, run_index, progress_node, spawn_options, fuzz_context.?);
1105 return;
1106 }
1107
1108 const graph = maker.graph;
1109 const gpa = maker.gpa;
1110 const io = graph.io;
1111 const step = maker.stepByIndex(run_index);
1112
1113 // We will update this every time a child runs.
1114 step.result_peak_rss = 0;
1115
1116 var test_results: Step.TestResults = .{
1117 .test_count = 0,
1118 .skip_count = 0,
1119 .fail_count = 0,
1120 .crash_count = 0,
1121 .timeout_count = 0,
1122 .leak_count = 0,
1123 .log_err_count = 0,
1124 };
1125 var test_metadata: ?TestMetadata = null;
1126
1127 while (true) {
1128 var child = try process.spawn(io, spawn_options);
1129 var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined;
1130 var multi_reader: Io.File.MultiReader = undefined;
1131 multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ child.stdout.?, child.stderr.? });
1132 var child_killed = false;
1133 defer if (!child_killed) {
1134 child.kill(io);
1135 multi_reader.deinit();
1136 step.result_peak_rss = @max(
1137 step.result_peak_rss,
1138 child.resource_usage_statistics.getMaxRss() orelse 0,
1139 );
1140 };
1141
1142 switch (try waitZigTest(
1143 arena,
1144 run,
1145 run_index,
1146 maker,
1147 &child,
1148 progress_node,
1149 &multi_reader,
1150 &test_metadata,
1151 &test_results,
1152 )) {
1153 .write_failed => |err| {
1154 // The runner unexpectedly closed a stdio pipe, which means a crash. Make sure we've captured
1155 // all available stderr to make our error output as useful as possible.
1156 const stderr_fr = multi_reader.fileReader(1);
1157 while (stderr_fr.interface.fillMore()) |_| {} else |e| switch (e) {
1158 error.ReadFailed => return stderr_fr.err.?,
1159 error.EndOfStream => {},
1160 }
1161 step.result_stderr = try arena.dupe(u8, stderr_fr.interface.buffered());
1162
1163 // Clean up everything and wait for the child to exit.
1164 child.stdin.?.close(io);
1165 child.stdin = null;
1166 multi_reader.deinit();
1167 child_killed = true;
1168 const term = try child.wait(io);
1169 step.result_peak_rss = @max(
1170 step.result_peak_rss,
1171 child.resource_usage_statistics.getMaxRss() orelse 0,
1172 );
1173
1174 // The individual unit test results are irrelevant: the test runner itself broke!
1175 // Fail immediately without populating `s.test_results`.
1176 return step.fail(maker, "unable to write stdin ({t}); test process unexpectedly {f}", .{
1177 err, fmtTerm(term),
1178 });
1179 },
1180 .no_poll => |no_poll| {
1181 // This might be a success (we requested exit and the child dutifully closed stdout) or
1182 // a crash of some kind. Either way, the child will terminate by itself -- wait for it.
1183 const stderr_reader = multi_reader.reader(1);
1184 const stderr_owned = try arena.dupe(u8, stderr_reader.buffered());
1185
1186 // Clean up everything and wait for the child to exit.
1187 child.stdin.?.close(io);
1188 child.stdin = null;
1189 multi_reader.deinit();
1190 child_killed = true;
1191 const term = try child.wait(io);
1192 step.result_peak_rss = @max(
1193 step.result_peak_rss,
1194 child.resource_usage_statistics.getMaxRss() orelse 0,
1195 );
1196
1197 if (no_poll.active_test_index) |test_index| {
1198 // A test was running, so this is definitely a crash. Report it against that
1199 // test, and continue to the next test.
1200 test_metadata.?.ns_per_test[test_index] = no_poll.ns_elapsed;
1201 test_results.crash_count += 1;
1202 try step.addError(maker, "'{s}' {f}{s}{s}", .{
1203 test_metadata.?.testName(test_index),
1204 fmtTerm(term),
1205 if (stderr_owned.len != 0) " with stderr:\n" else "",
1206 std.mem.trim(u8, stderr_owned, "\n"),
1207 });
1208 continue;
1209 }
1210
1211 // Report an error if the child terminated uncleanly or if we were still trying to run more tests.
1212 step.result_stderr = stderr_owned;
1213 const tests_done = test_metadata != null and test_metadata.?.next_index == std.math.maxInt(u32);
1214 if (!tests_done or !termMatches(.{ .exited = 0 }, term)) {
1215 // The individual unit test results are irrelevant: the test runner itself broke!
1216 // Fail immediately without populating `s.test_results`.
1217 return step.fail(maker, "test process unexpectedly {f}", .{fmtTerm(term)});
1218 }
1219
1220 // We're done with all of the tests! Commit the test results and return.
1221 step.test_results = test_results;
1222 if (test_metadata) |tm| {
1223 run.cached_test_metadata = tm.toCachedTestMetadata();
1224 if (maker.web_server) |*ws| {
1225 if (graph.time_report) {
1226 ws.updateTimeReportRunTest(
1227 run_index,
1228 &run.cached_test_metadata.?,
1229 tm.ns_per_test,
1230 );
1231 }
1232 }
1233 }
1234 return;
1235 },
1236 .timeout => |timeout| {
1237 const stderr_reader = multi_reader.reader(1);
1238 const stderr = stderr_reader.buffered();
1239 stderr_reader.tossBuffered();
1240 if (timeout.active_test_index) |test_index| {
1241 // A test was running. Report the timeout against that test, and continue on to
1242 // the next test.
1243 test_metadata.?.ns_per_test[test_index] = timeout.ns_elapsed;
1244 test_results.timeout_count += 1;
1245 try step.addError(maker, "'{s}' timed out after {f}{s}{s}", .{
1246 test_metadata.?.testName(test_index),
1247 Io.Duration{ .nanoseconds = timeout.ns_elapsed },
1248 if (stderr.len != 0) " with stderr:\n" else "",
1249 std.mem.trim(u8, stderr, "\n"),
1250 });
1251 continue;
1252 }
1253 // Just log an error and let the child be killed.
1254 step.result_stderr = try arena.dupe(u8, stderr);
1255 // The individual unit test results in `results` are irrelevant: the test runner
1256 // is broken! Fail immediately without populating `s.test_results`.
1257 return step.fail(maker, "test runner failed to respond for {f}", .{Io.Duration{ .nanoseconds = timeout.ns_elapsed }});
1258 },
1259 }
1260 comptime unreachable;
1261 }
1262}
1263
1264const TestMetadata = struct {
1265 names: []const u32,
1266 ns_per_test: []u64,
1267 expected_panic_msgs: []const u32,
1268 string_bytes: []const u8,
1269 next_index: u32,
1270 prog_node: std.Progress.Node,
1271
1272 fn toCachedTestMetadata(tm: TestMetadata) CachedTestMetadata {
1273 return .{
1274 .names = tm.names,
1275 .string_bytes = tm.string_bytes,
1276 };
1277 }
1278
1279 fn testName(tm: TestMetadata, index: u32) []const u8 {
1280 return tm.toCachedTestMetadata().testName(index);
1281 }
1282};
1283
1284pub const CachedTestMetadata = struct {
1285 names: []const u32,
1286 string_bytes: []const u8,
1287
1288 pub fn testName(tm: CachedTestMetadata, index: u32) []const u8 {
1289 return std.mem.sliceTo(tm.string_bytes[tm.names[index]..], 0);
1290 }
1291};
1292
1293fn requestNextTest(io: Io, in: Io.File, metadata: *TestMetadata, sub_prog_node: *?std.Progress.Node) !void {
1294 while (metadata.next_index < metadata.names.len) {
1295 const i = metadata.next_index;
1296 metadata.next_index += 1;
1297
1298 if (metadata.expected_panic_msgs[i] != 0) continue;
1299
1300 const name = metadata.testName(i);
1301 if (sub_prog_node.*) |n| n.end();
1302 sub_prog_node.* = metadata.prog_node.start(name, 0);
1303
1304 try sendRunTestMessage(io, in, .run_test, i);
1305 return;
1306 } else {
1307 metadata.next_index = std.math.maxInt(u32); // indicate that all tests are done
1308 try sendMessage(io, in, .exit);
1309 }
1310}
1311
1312fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void {
1313 const header: std.zig.Client.Message.Header = .{
1314 .tag = tag,
1315 .bytes_len = 0,
1316 };
1317 var w = file.writerStreaming(io, &.{});
1318 w.interface.writeStruct(header, .little) catch |err| switch (err) {
1319 error.WriteFailed => return w.err.?,
1320 };
1321}
1322
1323fn sendRunTestMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag, index: u32) !void {
1324 const header: std.zig.Client.Message.Header = .{
1325 .tag = tag,
1326 .bytes_len = 4,
1327 };
1328 var w = file.writerStreaming(io, &.{});
1329 w.interface.writeStruct(header, .little) catch |err| switch (err) {
1330 error.WriteFailed => return w.err.?,
1331 };
1332 w.interface.writeInt(u32, index, .little) catch |err| switch (err) {
1333 error.WriteFailed => return w.err.?,
1334 };
1335}
1336
1337fn sendRunFuzzTestMessage(
1338 io: Io,
1339 file: Io.File,
1340 test_names: []const []const u8,
1341 kind: std.Build.abi.fuzz.LimitKind,
1342 amount_or_instance: u64,
1343) !void {
1344 const header: std.zig.Client.Message.Header = .{
1345 .tag = .start_fuzzing,
1346 .bytes_len = 1 + 8 + 4 + count: {
1347 var c: u32 = @intCast(test_names.len * 4);
1348 for (test_names) |name| {
1349 c += @intCast(name.len);
1350 }
1351 break :count c;
1352 },
1353 };
1354 var w = file.writerStreaming(io, &.{});
1355 w.interface.writeStruct(header, .little) catch |err| switch (err) {
1356 error.WriteFailed => return w.err.?,
1357 };
1358 w.interface.writeByte(@intFromEnum(kind)) catch |err| switch (err) {
1359 error.WriteFailed => return w.err.?,
1360 };
1361 w.interface.writeInt(u64, amount_or_instance, .little) catch |err| switch (err) {
1362 error.WriteFailed => return w.err.?,
1363 };
1364 w.interface.writeInt(u32, @intCast(test_names.len), .little) catch |err| switch (err) {
1365 error.WriteFailed => return w.err.?,
1366 };
1367 for (test_names) |test_name| {
1368 w.interface.writeInt(u32, @intCast(test_name.len), .little) catch |err| switch (err) {
1369 error.WriteFailed => return w.err.?,
1370 };
1371 w.interface.writeAll(test_name) catch |err| switch (err) {
1372 error.WriteFailed => return w.err.?,
1373 };
1374 }
1375}
1376
1377/// Uses `arena` to allocate the result.
1378fn evalGeneric(
1379 arena: Allocator,
1380 run_index: Configuration.Step.Index,
1381 maker: *Maker,
1382 spawn_options: process.SpawnOptions,
1383) !EvalGenericResult {
1384 const graph = maker.graph;
1385 const io = graph.io;
1386 const conf = &maker.scanned_config.configuration;
1387 const conf_step = run_index.ptr(conf);
1388 const conf_run = conf_step.extended.get(conf.extra).run;
1389 const step = maker.stepByIndex(run_index);
1390
1391 var child = try process.spawn(io, spawn_options);
1392 defer child.kill(io);
1393
1394 switch (conf_run.stdin.u) {
1395 .bytes => |bytes| {
1396 child.stdin.?.writeStreamingAll(io, bytes.slice(conf)) catch |err| {
1397 return step.fail(maker, "failed to write stdin: {t}", .{err});
1398 };
1399 child.stdin.?.close(io);
1400 child.stdin = null;
1401 },
1402 .lazy_path => |lazy_path| {
1403 const path = try maker.resolveLazyPathIndex(arena, lazy_path, run_index);
1404 const file = path.root_dir.handle.openFile(io, path.subPathOrDot(), .{}) catch |err| {
1405 return step.fail(maker, "failed to open stdin file: {t}", .{err});
1406 };
1407 defer file.close(io);
1408 // TODO https://github.com/ziglang/zig/issues/23955
1409 var read_buffer: [1024]u8 = undefined;
1410 var file_reader = file.reader(io, &read_buffer);
1411 var write_buffer: [1024]u8 = undefined;
1412 var stdin_writer = child.stdin.?.writerStreaming(io, &write_buffer);
1413 _ = stdin_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) {
1414 error.ReadFailed => return step.fail(maker, "failed to read from {f}: {t}", .{
1415 path, file_reader.err.?,
1416 }),
1417 error.WriteFailed => return step.fail(maker, "failed to write to stdin: {t}", .{
1418 stdin_writer.err.?,
1419 }),
1420 };
1421 stdin_writer.interface.flush() catch |err| switch (err) {
1422 error.WriteFailed => return step.fail(maker, "failed to write to stdin: {t}", .{
1423 stdin_writer.err.?,
1424 }),
1425 };
1426 child.stdin.?.close(io);
1427 child.stdin = null;
1428 },
1429 .none => {},
1430 }
1431
1432 var stdout_bytes: ?[]const u8 = null;
1433 var stderr_bytes: ?[]const u8 = null;
1434
1435 if (child.stdout) |stdout| {
1436 if (child.stderr) |stderr| {
1437 var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined;
1438 var multi_reader: Io.File.MultiReader = undefined;
1439 multi_reader.init(arena, io, multi_reader_buffer.toStreams(), &.{ stdout, stderr });
1440
1441 const stdout_reader = multi_reader.reader(0);
1442 const stderr_reader = multi_reader.reader(1);
1443
1444 while (multi_reader.fill(64, .none)) |_| {
1445 if (conf_run.stdio_limit.value) |limit| {
1446 if (stdout_reader.buffered().len > limit)
1447 return error.StdoutStreamTooLong;
1448 if (stderr_reader.buffered().len > limit)
1449 return error.StderrStreamTooLong;
1450 }
1451 } else |err| switch (err) {
1452 error.Timeout => unreachable,
1453 error.EndOfStream => {},
1454 else => |e| return e,
1455 }
1456
1457 try multi_reader.checkAnyError();
1458
1459 stdout_bytes = try multi_reader.toOwnedSlice(0);
1460 stderr_bytes = try multi_reader.toOwnedSlice(1);
1461 } else {
1462 var stdout_reader = stdout.readerStreaming(io, &.{});
1463 const stdio_limit: Io.Limit = if (conf_run.stdio_limit.value) |x| .limited(x) else .unlimited;
1464 stdout_bytes = stdout_reader.interface.allocRemaining(arena, stdio_limit) catch |err| switch (err) {
1465 error.OutOfMemory => |e| return e,
1466 error.ReadFailed => return stdout_reader.err.?,
1467 error.StreamTooLong => return error.StdoutStreamTooLong,
1468 };
1469 }
1470 } else if (child.stderr) |stderr| {
1471 var stderr_reader = stderr.readerStreaming(io, &.{});
1472 const stdio_limit: Io.Limit = if (conf_run.stdio_limit.value) |x| .limited(x) else .unlimited;
1473 stderr_bytes = stderr_reader.interface.allocRemaining(arena, stdio_limit) catch |err| switch (err) {
1474 error.OutOfMemory => |e| return e,
1475 error.ReadFailed => return stderr_reader.err.?,
1476 error.StreamTooLong => return error.StderrStreamTooLong,
1477 };
1478 }
1479
1480 if (stderr_bytes) |bytes| if (bytes.len > 0) {
1481 // Treat stderr as an error message.
1482 const stderr_is_diagnostic = conf_run.captured_stderr.value == null and switch (conf_run.flags.stdio) {
1483 .check => !checksContainStderr(&conf_run),
1484 else => true,
1485 };
1486 if (stderr_is_diagnostic) {
1487 step.result_stderr = bytes;
1488 }
1489 };
1490
1491 step.result_peak_rss = child.resource_usage_statistics.getMaxRss() orelse 0;
1492
1493 return .{
1494 .term = try child.wait(io),
1495 .stdout = stdout_bytes,
1496 .stderr = stderr_bytes,
1497 };
1498}
1499
1500const IndexedOutput = struct {
1501 index: u32,
1502 arg_index: Configuration.Step.Run.Arg.Index,
1503};
1504
1505pub fn rerunInFuzzMode(
1506 run: *Run,
1507 run_index: Configuration.Step.Index,
1508 fuzz: *Fuzz,
1509 prog_node: std.Progress.Node,
1510) !void {
1511 const maker = fuzz.maker;
1512 const graph = maker.graph;
1513 const step = maker.stepByIndex(run_index);
1514 const io = graph.io;
1515 const gpa = maker.gpa;
1516 const conf = &maker.scanned_config.configuration;
1517 const conf_step = run_index.ptr(conf);
1518 const conf_run = conf_step.extended.get(conf.extra).run;
1519 const cache_root = graph.local_cache_root;
1520
1521 var arena_allocator: std.heap.ArenaAllocator = .init(gpa);
1522 defer arena_allocator.deinit();
1523 const arena = arena_allocator.allocator();
1524
1525 var argv_list: std.ArrayList([]const u8) = .empty;
1526 defer argv_list.deinit(gpa);
1527
1528 for (conf_run.args.slice) |arg_index| {
1529 const arg = arg_index.get(conf);
1530 try argv_list.ensureUnusedCapacity(gpa, 1);
1531 switch (arg.flags.tag) {
1532 .string => {
1533 const prefix = arg.prefix.value.?.slice(conf);
1534 argv_list.appendAssumeCapacity(prefix);
1535 },
1536 .path_file => {
1537 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
1538 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";
1539 const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index);
1540 argv_list.appendAssumeCapacity(try mem.concat(arena, u8, &.{
1541 prefix, try convertPathArg(arena, run_index, maker, file_path), suffix,
1542 }));
1543 },
1544 .path_directory => {
1545 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
1546 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";
1547 const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index);
1548 const resolved_arg = try mem.concat(arena, u8, &.{
1549 prefix, try convertPathArg(arena, run_index, maker, file_path), suffix,
1550 });
1551 argv_list.appendAssumeCapacity(resolved_arg);
1552 },
1553 .file_content => {
1554 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
1555 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";
1556 const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index);
1557
1558 var result: std.Io.Writer.Allocating = .init(arena);
1559 result.writer.writeAll(prefix) catch return error.OutOfMemory;
1560
1561 const file = file_path.root_dir.handle.openFile(io, file_path.sub_path, .{}) catch |err|
1562 return step.fail(maker, "unable to open input file {f}: {t}", .{ file_path, err });
1563 defer file.close(io);
1564
1565 var file_reader = file.reader(io, &.{});
1566 _ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) {
1567 error.ReadFailed => switch (file_reader.err.?) {
1568 error.Canceled => |e| return e,
1569 else => |e| return step.fail(maker, "failed to read from {f}: {t}", .{ file_path, e }),
1570 },
1571 error.WriteFailed => return error.OutOfMemory,
1572 };
1573 result.writer.writeAll(suffix) catch return error.OutOfMemory;
1574
1575 argv_list.appendAssumeCapacity(result.written());
1576 },
1577 .artifact => {
1578 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
1579 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";
1580 const producer_index = arg.producer.value.?;
1581 const producer_step = producer_index.ptr(conf);
1582 const producer = producer_step.extended.get(conf.extra).compile;
1583 const producer_make_comp_step = maker.stepByIndex(producer_index);
1584 const producer_make_comp = &producer_make_comp_step.extended.compile;
1585 const file_path: Path = if (producer_index == conf_run.producer.value.?)
1586 run.rebuilt_executable.?
1587 else
1588 producer_make_comp.installed_path orelse
1589 maker.generatedPath(producer.generated_bin.value.?).*;
1590 argv_list.appendAssumeCapacity(try mem.concat(arena, u8, &.{
1591 prefix, try convertPathArg(arena, run_index, maker, file_path), suffix,
1592 }));
1593 },
1594 .output_file => unreachable,
1595 .output_directory => unreachable,
1596 .passthru => unreachable,
1597 }
1598 }
1599
1600 if (conf_run.flags.test_runner_mode) {
1601 const cache_dir_string = try convertPathArg(arena, run_index, maker, .{ .root_dir = cache_root });
1602
1603 try argv_list.ensureUnusedCapacity(gpa, 3);
1604 argv_list.appendAssumeCapacity(try allocPrint(arena, "--cache-dir={s}", .{cache_dir_string}));
1605 argv_list.appendAssumeCapacity(try allocPrint(arena, "--seed=0x{x}", .{graph.random_seed}));
1606 argv_list.appendAssumeCapacity("--listen=-");
1607 }
1608
1609 step.clearFailedCommand(gpa);
1610
1611 const has_side_effects = false;
1612 var rand_int: u64 = undefined;
1613 io.random(@ptrCast(&rand_int));
1614 const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);
1615 try runCommand(arena, run, run_index, maker, prog_node, argv_list.items, has_side_effects, tmp_dir_path, .{
1616 .fuzz = fuzz,
1617 });
1618}
1619
1620fn populateGeneratedPaths(
1621 maker: *Maker,
1622 output_placeholders: []const IndexedOutput,
1623 cache_root: Cache.Directory,
1624 digest: *const Cache.HexDigest,
1625) !void {
1626 const conf = &maker.scanned_config.configuration;
1627 const graph = maker.graph;
1628
1629 for (output_placeholders) |placeholder| {
1630 const arg = placeholder.arg_index.get(conf);
1631 maker.generatedPath(arg.generated.value.?).* = .{
1632 .root_dir = cache_root,
1633 .sub_path = try Dir.path.join(graph.arena, &.{
1634 "o", digest, arg.basename.value.?.slice(conf),
1635 }),
1636 };
1637 }
1638}
1639
1640fn populateGeneratedPathsCreateDirs(
1641 arena: Allocator,
1642 run_index: Configuration.Step.Index,
1643 maker: *Maker,
1644 output_dir_path: []const u8,
1645 output_placeholders: []const IndexedOutput,
1646 argv: [][]const u8,
1647) !void {
1648 const step = maker.stepByIndex(run_index);
1649 const conf = &maker.scanned_config.configuration;
1650 const graph = maker.graph;
1651 const io = graph.io;
1652 const cache_root = graph.local_cache_root;
1653
1654 for (output_placeholders) |placeholder| {
1655 const arg = placeholder.arg_index.get(conf);
1656 const prefix = if (arg.prefix.value) |p| p.slice(conf) else "";
1657 const suffix = if (arg.suffix.value) |p| p.slice(conf) else "";
1658 const basename = arg.basename.value.?.slice(conf);
1659
1660 const generated_path: Path = .{
1661 .root_dir = cache_root,
1662 .sub_path = try Dir.path.join(graph.arena, &.{ output_dir_path, basename }),
1663 };
1664 const create_path: Path = .{
1665 .root_dir = cache_root,
1666 .sub_path = switch (arg.flags.tag) {
1667 .output_file => Dir.path.dirname(generated_path.sub_path).?,
1668 .output_directory => generated_path.sub_path,
1669 else => unreachable,
1670 },
1671 };
1672 create_path.root_dir.handle.createDirPath(io, create_path.sub_path) catch |err|
1673 return step.fail(maker, "unable to make path {f}: {t}", .{ create_path, err });
1674
1675 maker.generatedPath(arg.generated.value.?).* = generated_path;
1676
1677 const arg_output_path = try convertPathArg(arena, run_index, maker, generated_path);
1678 argv[placeholder.index] = try mem.concat(arena, u8, &.{ prefix, arg_output_path, suffix });
1679 }
1680}
1681
1682fn populateGeneratedStdIo(
1683 maker: *Maker,
1684 conf_run: *const Configuration.Step.Run,
1685 cache_root: Cache.Directory,
1686 digest: *const Cache.HexDigest,
1687) !void {
1688 const conf = &maker.scanned_config.configuration;
1689 const graph = maker.graph;
1690
1691 if (conf_run.captured_stdout.value) |captured| {
1692 maker.generatedPath(captured.generated_file).* = .{
1693 .root_dir = cache_root,
1694 .sub_path = try Dir.path.join(graph.arena, &.{
1695 "o", digest, captured.basename.slice(conf),
1696 }),
1697 };
1698 }
1699
1700 if (conf_run.captured_stderr.value) |captured| {
1701 maker.generatedPath(captured.generated_file).* = .{
1702 .root_dir = cache_root,
1703 .sub_path = try Dir.path.join(graph.arena, &.{
1704 "o", digest, captured.basename.slice(conf),
1705 }),
1706 };
1707 }
1708}
1709
1710fn formatTerm(term: ?process.Child.Term, w: *std.Io.Writer) std.Io.Writer.Error!void {
1711 if (term) |t| switch (t) {
1712 .exited => |code| try w.print("exited with code {d}", .{code}),
1713 .signal => |sig| try w.print("terminated with signal {t}", .{sig}),
1714 .stopped => |sig| try w.print("stopped with signal {t}", .{sig}),
1715 .unknown => |code| try w.print("terminated for unknown reason with code {d}", .{code}),
1716 } else {
1717 try w.writeAll("exited with any code");
1718 }
1719}
1720fn fmtTerm(term: ?process.Child.Term) std.fmt.Alt(?process.Child.Term, formatTerm) {
1721 return .{ .data = term };
1722}
1723
1724const FuzzContext = struct {
1725 fuzz: *Fuzz,
1726};
1727
1728fn runCommand(
1729 arena: Allocator,
1730 run: *Run,
1731 run_index: Configuration.Step.Index,
1732 maker: *Maker,
1733 progress_node: std.Progress.Node,
1734 argv: []const []const u8,
1735 has_side_effects: bool,
1736 output_dir_path: []const u8,
1737 fuzz_context: ?FuzzContext,
1738) Step.ExtendedMakeError!void {
1739 const graph = maker.graph;
1740 const gpa = maker.gpa;
1741 const step = maker.stepByIndex(run_index);
1742 const io = graph.io;
1743 const cache_root = graph.local_cache_root;
1744 const conf = &maker.scanned_config.configuration;
1745 const conf_step = run_index.ptr(conf);
1746 const conf_run = conf_step.extended.get(conf.extra).run;
1747
1748 const cwd: process.Child.Cwd = if (conf_run.cwd.value) |lazy_cwd|
1749 .{ .path = try maker.resolveLazyPathIndexAbs(arena, lazy_cwd, run_index) }
1750 else
1751 .inherit;
1752
1753 const allow_skip = switch (conf_run.flags.stdio) {
1754 .check, .zig_test => conf_run.flags.skip_foreign_checks,
1755 else => false,
1756 };
1757
1758 var interp_argv: std.ArrayList([]const u8) = .empty;
1759
1760 var environ_map: std.process.Environ.Map = .init(gpa);
1761 defer environ_map.deinit();
1762
1763 // In either case we add to this mutatable data structure so that we can
1764 // tweak the environment below.
1765 if (conf_run.environ_map.value) |env_map_index| {
1766 const conf_env_map = env_map_index.get(conf);
1767 for (conf_env_map.keys.slice(conf), conf_env_map.values.slice(conf)) |k, v| {
1768 try environ_map.put(k.slice(conf), v.slice(conf));
1769 }
1770 } else {
1771 try environ_map.putAll(&graph.environ_map);
1772 }
1773
1774 // Now that we have the environ map, we might need to mutate it to insert
1775 // .dll search paths because Windows doesn't have rpaths.
1776 const arg0 = conf_run.args.slice[0].get(conf);
1777 if (arg0.producer.value) |producer_index| {
1778 const producer_step = producer_index.ptr(conf);
1779 const producer = producer_step.extended.get(conf.extra).compile;
1780 const root_module = producer.root_module.get(conf);
1781 const root_module_target = root_module.resolved_target.get(conf).?.result.get(conf);
1782 if (root_module_target.flags.os_tag == .windows) {
1783 try addPathForDynLibs(maker, arena, producer_index, &environ_map, argv[0]);
1784 }
1785 }
1786
1787 const cwd_string = switch (cwd) {
1788 .path => |p| p,
1789 .dir => unreachable,
1790 .inherit => null,
1791 };
1792 try graph.handleVerbose(cwd_string, &environ_map, argv);
1793
1794 const opt_generic_result = spawnChildAndCollect(
1795 arena,
1796 run_index,
1797 run,
1798 maker,
1799 progress_node,
1800 argv,
1801 &environ_map,
1802 has_side_effects,
1803 fuzz_context,
1804 ) catch |err| term: {
1805 switch (err) {
1806 error.InvalidExe, // cpu arch mismatch
1807 error.FileNotFound, // can happen with a wrong dynamic linker path
1808 => interpret: {
1809 const producer_index = arg0.producer.value orelse break :interpret;
1810 const producer_step = producer_index.ptr(conf);
1811 const producer = producer_step.extended.get(conf.extra).compile;
1812 switch (producer.flags3.kind) {
1813 .exe, .@"test" => {},
1814 else => break :interpret,
1815 }
1816 const root_module = producer.root_module.get(conf);
1817 const root_module_target = root_module.resolved_target.get(conf).?.result.get(conf);
1818 const other_target_query = root_module_target.unwrap(conf);
1819 const root_target = std.zig.system.resolveTargetQuery(io, other_target_query) catch unreachable;
1820 const link_libc = maker.stepByIndex(producer_index).extended.compile.is_linking_libc;
1821
1822 const host: std.Target = std.zig.system.resolveTargetQuery(io, .{}) catch |he| switch (he) {
1823 error.Canceled => |e| return e,
1824 else => builtin.target,
1825 };
1826
1827 const need_cross_libc = link_libc and root_target.os.tag == .linux and
1828 switch (producer.flags2.linkage) {
1829 .static => false,
1830 .dynamic => true,
1831 .default => root_target.isGnuLibC(),
1832 };
1833 switch (std.zig.system.getExternalExecutor(io, &root_target, .{
1834 .host_cpu_arch = host.cpu.arch,
1835 .host_os_tag = host.os.tag,
1836 .qemu_fixes_dl = need_cross_libc and graph.libc_runtimes_dir != null,
1837 .link_libc = link_libc,
1838 })) {
1839 .native, .rosetta => {
1840 if (allow_skip) return error.MakeSkipped;
1841 break :interpret;
1842 },
1843 .wine => |bin_name| {
1844 if (graph.enable_wine) {
1845 try interp_argv.ensureUnusedCapacity(arena, 1 + argv.len);
1846 interp_argv.appendAssumeCapacity(bin_name);
1847 interp_argv.appendSliceAssumeCapacity(argv);
1848
1849 // Wine's excessive stderr logging is only situationally helpful. Disable it by default, but
1850 // allow the user to override it (e.g. with `WINEDEBUG=err+all`) if desired.
1851 if (environ_map.get("WINEDEBUG") == null) {
1852 try environ_map.put("WINEDEBUG", "-all");
1853 }
1854 } else {
1855 return failForeign(arena, &conf_run, maker, run_index, "-fwine", argv[0], &root_target, &host);
1856 }
1857 },
1858 .qemu => |bin_name| {
1859 if (graph.enable_qemu) {
1860 try interp_argv.ensureUnusedCapacity(arena, 3 + argv.len);
1861 interp_argv.appendAssumeCapacity(bin_name);
1862
1863 if (need_cross_libc) {
1864 if (graph.libc_runtimes_dir) |dir| {
1865 interp_argv.appendAssumeCapacity("-L");
1866 interp_argv.appendAssumeCapacity(try Dir.path.join(arena, &.{
1867 dir,
1868 try if (root_target.isGnuLibC()) std.zig.target.glibcRuntimeTriple(
1869 arena,
1870 root_target.cpu.arch,
1871 root_target.os.tag,
1872 root_target.abi,
1873 ) else if (root_target.isMuslLibC()) std.zig.target.muslRuntimeTriple(
1874 arena,
1875 root_target.cpu.arch,
1876 root_target.abi,
1877 ) else unreachable,
1878 }));
1879 } else return failForeign(arena, &conf_run, maker, run_index, "--libc-runtimes", argv[0], &root_target, &host);
1880 }
1881
1882 interp_argv.appendSliceAssumeCapacity(argv);
1883 } else return failForeign(arena, &conf_run, maker, run_index, "-fqemu", argv[0], &root_target, &host);
1884 },
1885 .darling => |bin_name| {
1886 if (graph.enable_darling) {
1887 try interp_argv.ensureUnusedCapacity(arena, 1 + argv.len);
1888 interp_argv.appendAssumeCapacity(bin_name);
1889 interp_argv.appendSliceAssumeCapacity(argv);
1890 } else {
1891 return failForeign(arena, &conf_run, maker, run_index, "-fdarling", argv[0], &root_target, &host);
1892 }
1893 },
1894 .wasmtime => |bin_name| {
1895 if (graph.enable_wasmtime) {
1896 try interp_argv.ensureUnusedCapacity(arena, 3 + argv.len);
1897 interp_argv.appendAssumeCapacity(bin_name);
1898 interp_argv.appendAssumeCapacity("--dir=.");
1899 // Wasmtime doeesn't inherit environment variables from the parent process
1900 // by default. '-S inherit-env' was added in Wasmtime version 20.
1901 interp_argv.appendAssumeCapacity("-Sinherit-env");
1902 interp_argv.appendSliceAssumeCapacity(argv);
1903 } else {
1904 return failForeign(arena, &conf_run, maker, run_index, "-fwasmtime", argv[0], &root_target, &host);
1905 }
1906 },
1907 .bad_dl => |foreign_dl| {
1908 if (allow_skip) return error.MakeSkipped;
1909
1910 const host_dl = host.dynamic_linker.get() orelse "(none)";
1911
1912 return step.fail(maker,
1913 \\the host system is unable to execute binaries from the target
1914 \\ because the host dynamic linker is '{s}',
1915 \\ while the target dynamic linker is '{s}'.
1916 \\ consider setting the dynamic linker or enabling skip_foreign_checks in the Run step
1917 , .{ host_dl, foreign_dl });
1918 },
1919 .bad_os_or_cpu => {
1920 if (allow_skip) return error.MakeSkipped;
1921
1922 const host_name = try host.zigTriple(arena);
1923 const foreign_name = try root_target.zigTriple(arena);
1924
1925 return step.fail(maker, "the host system ({s}) is unable to execute binaries from the target ({s})", .{
1926 host_name, foreign_name,
1927 });
1928 },
1929 }
1930
1931 step.clearFailedCommand(gpa);
1932 try graph.handleVerbose(cwd_string, &environ_map, interp_argv.items);
1933
1934 break :term spawnChildAndCollect(
1935 arena,
1936 run_index,
1937 run,
1938 maker,
1939 progress_node,
1940 interp_argv.items,
1941 &environ_map,
1942 has_side_effects,
1943 fuzz_context,
1944 ) catch |e| {
1945 if (!conf_run.flags.failing_to_execute_foreign_is_an_error) return error.MakeSkipped;
1946 if (e == error.MakeFailed) return error.MakeFailed; // error already reported
1947 return step.fail(maker, "unable to spawn interpreter {s}: {t}", .{ interp_argv.items[0], e });
1948 };
1949 },
1950 error.MakeFailed, error.OutOfMemory, error.Canceled => |e| return e,
1951 else => {},
1952 }
1953 return step.fail(maker, "failed to spawn and capture stdio from {s}: {t}", .{ argv[0], err });
1954 };
1955
1956 const generic_result = opt_generic_result orelse {
1957 assert(conf_run.flags.stdio == .zig_test);
1958 // Specific errors have already been reported, and test results are populated. All we need
1959 // to do is report step failure if any test failed.
1960 if (!step.test_results.isSuccess()) return error.MakeFailed;
1961 return;
1962 };
1963
1964 assert(fuzz_context == null);
1965 assert(conf_run.flags.stdio != .zig_test);
1966
1967 // Capture stdout and stderr to GeneratedFile objects.
1968 const Stream = struct {
1969 captured: ?Configuration.Step.Run.CapturedStream,
1970 bytes: ?[]const u8,
1971 trim_whitespace: Configuration.Step.Run.TrimWhitespace,
1972 };
1973 for (&[_]Stream{
1974 .{
1975 .captured = conf_run.captured_stdout.value,
1976 .bytes = generic_result.stdout,
1977 .trim_whitespace = conf_run.flags.stdout_trim_whitespace,
1978 },
1979 .{
1980 .captured = conf_run.captured_stderr.value,
1981 .bytes = generic_result.stderr,
1982 .trim_whitespace = conf_run.flags.stderr_trim_whitespace,
1983 },
1984 }) |*stream| {
1985 if (stream.captured) |captured| {
1986 const output_path: Path = .{
1987 .root_dir = cache_root,
1988 .sub_path = try Dir.path.join(graph.arena, &.{
1989 output_dir_path, captured.basename.slice(conf),
1990 }),
1991 };
1992 maker.generatedPath(captured.generated_file).* = output_path;
1993
1994 const sub_path_parent = output_path.dirname().?;
1995 sub_path_parent.root_dir.handle.createDirPath(io, sub_path_parent.sub_path) catch |err|
1996 return step.fail(maker, "unable to make path {f}: {t}", .{ sub_path_parent, err });
1997
1998 const data = switch (stream.trim_whitespace) {
1999 .none => stream.bytes.?,
2000 .all => mem.trim(u8, stream.bytes.?, &std.ascii.whitespace),
2001 .leading => mem.trimStart(u8, stream.bytes.?, &std.ascii.whitespace),
2002 .trailing => mem.trimEnd(u8, stream.bytes.?, &std.ascii.whitespace),
2003 };
2004 output_path.root_dir.handle.writeFile(io, .{
2005 .sub_path = output_path.sub_path,
2006 .data = data,
2007 }) catch |err| return step.fail(maker, "unable to write file {f}: {t}", .{ output_path, err });
2008 }
2009 }
2010
2011 switch (conf_run.flags.stdio) {
2012 .zig_test => unreachable,
2013 .check => {
2014 if (conf_run.expect_stderr_exact.value) |bytes| {
2015 const expected_bytes = bytes.slice(conf);
2016 if (!mem.eql(u8, expected_bytes, generic_result.stderr.?)) {
2017 return step.fail(maker,
2018 \\========= expected this stderr: =========
2019 \\{s}
2020 \\========= but found: ====================
2021 \\{s}
2022 , .{
2023 expected_bytes,
2024 generic_result.stderr.?,
2025 });
2026 }
2027 }
2028 if (conf_run.expect_stdout_exact.value) |bytes| {
2029 const expected_bytes = bytes.slice(conf);
2030 if (!mem.eql(u8, expected_bytes, generic_result.stdout.?)) {
2031 return step.fail(maker,
2032 \\========= expected this stdout: =========
2033 \\{s}
2034 \\========= but found: ====================
2035 \\{s}
2036 , .{
2037 expected_bytes,
2038 generic_result.stdout.?,
2039 });
2040 }
2041 }
2042 for (conf_run.expect_stderr_match.slice) |bytes| {
2043 const match = bytes.slice(conf);
2044 if (mem.find(u8, generic_result.stderr.?, match) == null) {
2045 return step.fail(maker,
2046 \\========= expected to find in stderr: =========
2047 \\{s}
2048 \\========= but stderr does not contain it: =====
2049 \\{s}
2050 , .{
2051 match,
2052 generic_result.stderr.?,
2053 });
2054 }
2055 }
2056 for (conf_run.expect_stdout_match.slice) |bytes| {
2057 const match = bytes.slice(conf);
2058 if (mem.find(u8, generic_result.stdout.?, match) == null) {
2059 return step.fail(maker,
2060 \\========= expected to find in stdout: =========
2061 \\{s}
2062 \\========= but stdout does not contain it: =====
2063 \\{s}
2064 , .{
2065 match,
2066 generic_result.stdout.?,
2067 });
2068 }
2069 }
2070 if (conf_run.expect_term_value.value) |expected_term_value| {
2071 const expected_term: process.Child.Term = switch (conf_run.flags2.expect_term_status) {
2072 .exited => .{ .exited = @intCast(expected_term_value) },
2073 .signal => .{ .signal = @enumFromInt(expected_term_value) },
2074 .stopped => .{ .stopped = @enumFromInt(expected_term_value) },
2075 .unknown => .{ .unknown = expected_term_value },
2076 };
2077 if (!termMatches(expected_term, generic_result.term)) {
2078 return step.fail(maker, "process {f} (expected {f})", .{
2079 fmtTerm(generic_result.term),
2080 fmtTerm(expected_term),
2081 });
2082 }
2083 }
2084 },
2085 else => {
2086 // On failure, report captured stderr like normal standard error output.
2087 const bad_exit = switch (generic_result.term) {
2088 .exited => |code| code != 0,
2089 .signal, .stopped, .unknown => true,
2090 };
2091 if (bad_exit) {
2092 if (generic_result.stderr) |bytes| {
2093 step.result_stderr = bytes;
2094 }
2095 }
2096
2097 try step.handleChildProcessTerm(maker, generic_result.term);
2098 },
2099 }
2100}
2101
2102const EvalGenericResult = struct {
2103 term: process.Child.Term,
2104 stdout: ?[]const u8,
2105 stderr: ?[]const u8,
2106};
2107
2108fn spawnChildAndCollect(
2109 arena: Allocator,
2110 run_index: Configuration.Step.Index,
2111 run: *Run,
2112 maker: *Maker,
2113 progress_node: std.Progress.Node,
2114 argv: []const []const u8,
2115 environ_map: *EnvMap,
2116 has_side_effects: bool,
2117 fuzz_context: ?FuzzContext,
2118) !?EvalGenericResult {
2119 const step = maker.stepByIndex(run_index);
2120 const graph = maker.graph;
2121 const io = graph.io;
2122 const gpa = maker.gpa;
2123 const conf = &maker.scanned_config.configuration;
2124 const conf_step = run_index.ptr(conf);
2125 const conf_run = conf_step.extended.get(conf.extra).run;
2126
2127 if (fuzz_context != null) {
2128 assert(!has_side_effects);
2129 assert(conf_run.flags.stdio == .zig_test);
2130 }
2131
2132 const child_cwd: process.Child.Cwd = if (conf_run.cwd.value) |lazy_cwd|
2133 .{ .path = try maker.resolveLazyPathIndexAbs(arena, lazy_cwd, run_index) }
2134 else
2135 .inherit;
2136
2137 // If an error occurs, it's caused by this command:
2138 step.clearFailedCommand(gpa);
2139 step.result_failed_command = try std.zig.allocPrintCmd(gpa, argv, .{
2140 .cwd = switch (child_cwd) {
2141 .path => |p| p,
2142 .dir => unreachable,
2143 .inherit => null,
2144 },
2145 .child_env = environ_map,
2146 .parent_env = &graph.environ_map,
2147 });
2148
2149 try step.handleChildProcUnsupported(maker);
2150
2151 var spawn_options: process.SpawnOptions = .{
2152 .argv = argv,
2153 .cwd = child_cwd,
2154 .environ_map = environ_map,
2155 .request_resource_usage_statistics = true,
2156 .stdin = if (conf_run.stdin.u != .none) s: {
2157 assert(conf_run.flags.stdio != .inherit);
2158 break :s .pipe;
2159 } else switch (conf_run.flags.stdio) {
2160 .infer_from_args => if (has_side_effects) .inherit else .ignore,
2161 .inherit => .inherit,
2162 .check => .ignore,
2163 .zig_test => .pipe,
2164 },
2165 .stdout = if (conf_run.captured_stdout.value != null) .pipe else switch (conf_run.flags.stdio) {
2166 .infer_from_args => if (has_side_effects) .inherit else .ignore,
2167 .inherit => .inherit,
2168 .check => if (checksContainStdout(&conf_run)) .pipe else .ignore,
2169 .zig_test => .pipe,
2170 },
2171 .stderr = if (conf_run.captured_stderr.value != null) .pipe else switch (conf_run.flags.stdio) {
2172 .infer_from_args => if (has_side_effects) .inherit else .pipe,
2173 .inherit => .inherit,
2174 .check => .pipe,
2175 .zig_test => .pipe,
2176 },
2177 };
2178
2179 if (conf_run.flags.stdio == .zig_test) {
2180 const started: Io.Clock.Timestamp = .now(io, .awake);
2181 const result = evalZigTest(graph.arena, run, run_index, maker, progress_node, spawn_options, fuzz_context) catch |err| switch (err) {
2182 error.Canceled => |e| return e,
2183 else => |e| e,
2184 };
2185 step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds);
2186 try result;
2187 return null;
2188 } else {
2189 const inherit = spawn_options.stdout == .inherit or spawn_options.stderr == .inherit;
2190 if (!conf_run.flags.disable_zig_progress and !inherit) {
2191 spawn_options.progress_node = progress_node;
2192 }
2193 const terminal_mode: Io.Terminal.Mode = if (inherit) m: {
2194 const stderr = try io.lockStderr(&.{}, graph.stderr_mode);
2195 break :m stderr.terminal_mode;
2196 } else .no_color;
2197 defer if (inherit) io.unlockStderr();
2198 try setColorEnvironmentVariables(&conf_run, environ_map, terminal_mode);
2199
2200 const started: Io.Clock.Timestamp = .now(io, .awake);
2201 const result = evalGeneric(graph.arena, run_index, maker, spawn_options) catch |err| switch (err) {
2202 error.Canceled => |e| return e,
2203 else => |e| e,
2204 };
2205 step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds);
2206 return try result;
2207 }
2208}
2209
2210fn termMatches(expected: ?process.Child.Term, actual: process.Child.Term) bool {
2211 return if (expected) |e| switch (e) {
2212 .exited => |expected_code| switch (actual) {
2213 .exited => |actual_code| expected_code == actual_code,
2214 else => false,
2215 },
2216 .signal => |expected_sig| switch (actual) {
2217 .signal => |actual_sig| expected_sig == actual_sig,
2218 else => false,
2219 },
2220 .stopped => |expected_sig| switch (actual) {
2221 .stopped => |actual_sig| expected_sig == actual_sig,
2222 else => false,
2223 },
2224 .unknown => |expected_code| switch (actual) {
2225 .unknown => |actual_code| expected_code == actual_code,
2226 else => false,
2227 },
2228 } else switch (actual) {
2229 .exited => true,
2230 else => false,
2231 };
2232}
2233
2234fn setColorEnvironmentVariables(
2235 conf_run: *const Configuration.Step.Run,
2236 environ_map: *EnvMap,
2237 terminal_mode: Io.Terminal.Mode,
2238) !void {
2239 color: switch (conf_run.flags.color) {
2240 .manual => {},
2241 .enable => {
2242 try environ_map.put("CLICOLOR_FORCE", "1");
2243 _ = environ_map.swapRemove("NO_COLOR");
2244 },
2245 .disable => {
2246 try environ_map.put("NO_COLOR", "1");
2247 _ = environ_map.swapRemove("CLICOLOR_FORCE");
2248 },
2249 .inherit => switch (terminal_mode) {
2250 .no_color, .windows_api => continue :color .disable,
2251 .escape_codes => continue :color .enable,
2252 },
2253 .auto => {
2254 const capture_stderr = conf_run.captured_stderr.value != null or switch (conf_run.flags.stdio) {
2255 .check => checksContainStderr(conf_run),
2256 .infer_from_args, .inherit, .zig_test => false,
2257 };
2258 if (capture_stderr) {
2259 continue :color .disable;
2260 } else {
2261 continue :color .inherit;
2262 }
2263 },
2264 }
2265}
2266
2267fn checksContainStdout(conf_run: *const Configuration.Step.Run) bool {
2268 return conf_run.expect_stdout_exact.value != null or conf_run.expect_stdout_match.slice.len != 0;
2269}
2270
2271fn checksContainStderr(conf_run: *const Configuration.Step.Run) bool {
2272 return conf_run.expect_stderr_exact.value != null or conf_run.expect_stderr_match.slice.len != 0;
2273}
2274
2275/// If `path` is cwd-relative, make it relative to the cwd of the child instead.
2276///
2277/// Whenever a path is included in the argv of a child, it should be put through this function first
2278/// to make sure the child doesn't see paths relative to a cwd other than its own.
2279fn convertPathArg(arena: Allocator, run_index: Configuration.Step.Index, maker: *Maker, path: Path) ![]const u8 {
2280 const conf = &maker.scanned_config.configuration;
2281 const conf_step = run_index.ptr(conf);
2282 const conf_run = conf_step.extended.get(conf.extra).run;
2283 const graph = maker.graph;
2284
2285 const path_str = try path.toString(arena);
2286 if (Dir.path.isAbsolute(path_str)) {
2287 // Absolute paths don't need changing.
2288 return path_str;
2289 }
2290 const child_cwd_rel: []const u8 = rel: {
2291 const child_lazy_cwd = conf_run.cwd.value orelse break :rel path_str;
2292 const child_cwd = try maker.resolveLazyPathIndexAbs(arena, child_lazy_cwd, run_index);
2293 // Convert it from relative to *our* cwd, to relative to the *child's* cwd.
2294 break :rel try Dir.path.relative(arena, graph.cache.cwd, &graph.environ_map, child_cwd, path_str);
2295 };
2296 // Not every path can be made relative, e.g. if the path and the child cwd are on different
2297 // disk designators on Windows. In that case, `relative` will return an absolute path which we can
2298 // just return.
2299 if (Dir.path.isAbsolute(child_cwd_rel)) return child_cwd_rel;
2300
2301 // We're not done yet. In some cases this path must be prefixed with './':
2302 // * On POSIX, the executable name cannot be a single component like 'foo'
2303 // * Some executables might treat a leading '-' like a flag, which we must avoid
2304 // There's no harm in it, so just *always* apply this prefix.
2305 return Dir.path.join(arena, &.{ ".", child_cwd_rel });
2306}
2307
2308fn addPathForDynLibs(
2309 maker: *Maker,
2310 arena: Allocator,
2311 artifact: Configuration.Step.Index,
2312 environ_map: *process.Environ.Map,
2313 argv0: []const u8,
2314) !void {
2315 const conf = &maker.scanned_config.configuration;
2316 const graph = maker.graph;
2317 const use_wine = graph.enable_wine and builtin.os.tag != .windows and std.ascii.endsWithIgnoreCase(argv0, ".exe");
2318 const path_key = if (use_wine) "WINEPATH" else "PATH";
2319 const path_delimiter: u8 = if (builtin.os.tag == .windows or use_wine)
2320 Dir.path.delimiter_windows
2321 else
2322 Dir.path.delimiter;
2323
2324 var module_graph: Step.Compile.ModuleGraph = .empty;
2325 const compile_deps = try Step.Compile.getCompileDependencies(arena, &module_graph, conf, artifact, true);
2326
2327 for (compile_deps) |dep_index| {
2328 const conf_comp_step = dep_index.ptr(conf);
2329 const conf_comp = conf_comp_step.extended.get(conf.extra).compile;
2330 const root_module = conf_comp.root_module.get(conf);
2331 const target = root_module.resolved_target.get(conf).?.result.get(conf);
2332 if (target.flags.os_tag == .windows and conf_comp.isDynamicLibrary()) {
2333 const dll_path = try maker.generatedPath(conf_comp.generated_bin.value.?).toString(arena);
2334 const search_path = Dir.path.dirname(dll_path).?;
2335 if (environ_map.get(path_key)) |prev_path| {
2336 const new_path = try allocPrint(arena, "{s}{c}{s}", .{ prev_path, path_delimiter, search_path });
2337 try environ_map.put(path_key, new_path);
2338 } else {
2339 try environ_map.put(path_key, search_path);
2340 }
2341 }
2342 }
2343}
2344
2345fn failForeign(
2346 arena: Allocator,
2347 conf_run: *const Configuration.Step.Run,
2348 maker: *Maker,
2349 step_index: Configuration.Step.Index,
2350 suggested_flag: []const u8,
2351 argv0: []const u8,
2352 artifact_target: *const std.Target,
2353 host_target: *const std.Target,
2354) Step.ExtendedMakeError {
2355 const step = maker.stepByIndex(step_index);
2356 switch (conf_run.flags.stdio) {
2357 .check, .zig_test => {
2358 if (conf_run.flags.skip_foreign_checks) return error.MakeSkipped;
2359
2360 const host_name = try host_target.zigTriple(arena);
2361 const foreign_name = try artifact_target.zigTriple(arena);
2362
2363 return step.fail(maker,
2364 \\unable to spawn foreign binary '{s}' ({s}) on host system ({s})
2365 \\ consider using {s} or enabling skip_foreign_checks in the Run step
2366 , .{ argv0, foreign_name, host_name, suggested_flag });
2367 },
2368 else => {
2369 return step.fail(maker, "unable to spawn foreign binary '{s}'", .{argv0});
2370 },
2371 }
2372}
lib/compiler/Maker/Step/TranslateC.zig created+152
......@@ -0,0 +1,152 @@
1const TranslateC = @This();
2
3const std = @import("std");
4const Io = std.Io;
5const Configuration = std.Build.Configuration;
6const allocPrint = std.fmt.allocPrint;
7const assert = std.debug.assert;
8
9const Step = @import("../Step.zig");
10const Maker = @import("../../Maker.zig");
11const PkgConfig = @import("../PkgConfig.zig");
12
13pub fn make(
14 translate_c: *TranslateC,
15 step_index: Configuration.Step.Index,
16 maker: *Maker,
17 progress_node: std.Progress.Node,
18) Step.ExtendedMakeError!void {
19 _ = translate_c;
20 const graph = maker.graph;
21 const arena = graph.arena; // TODO don't leak into the process arena
22 const step = maker.stepByIndex(step_index);
23 const conf = &maker.scanned_config.configuration;
24 const conf_step = step_index.ptr(conf);
25 const conf_tc = conf_step.extended.get(conf.extra).translate_c;
26 const cache_root = graph.local_cache_root;
27
28 var argv: std.ArrayList([]const u8) = .empty;
29
30 try argv.ensureUnusedCapacity(arena, 10);
31 argv.appendAssumeCapacity(graph.zig_exe);
32 argv.appendAssumeCapacity("translate-c");
33 if (conf_tc.flags.link_libc) {
34 argv.appendAssumeCapacity("-lc");
35 }
36
37 argv.appendAssumeCapacity("--cache-dir");
38 argv.appendAssumeCapacity(cache_root.path orelse ".");
39
40 argv.appendAssumeCapacity("--global-cache-dir");
41 argv.appendAssumeCapacity(graph.global_cache_root.path orelse ".");
42
43 if (conf_tc.target.get(conf).?.query.unwrap()) |compact_query| {
44 const query = compact_query.get(conf).unwrap(conf);
45 argv.appendAssumeCapacity("-target");
46 argv.appendAssumeCapacity(try query.zigTriple(arena));
47 }
48
49 switch (conf_tc.flags.optimize) {
50 .debug, .default => {}, // Skip since it's the default.
51 else => argv.appendAssumeCapacity(try allocPrint(arena, "-O{t}", .{conf_tc.flags.optimize})),
52 }
53
54 try argv.ensureUnusedCapacity(arena, conf_tc.include_dirs.len * 2);
55 for (0..conf_tc.include_dirs.len) |i|
56 try Step.Compile.appendIncludeDirFlags(arena, conf_tc.include_dirs.get(conf.extra, i), &argv, step_index, maker);
57
58 for (conf_tc.c_macros.slice) |c_macro| {
59 (try argv.addManyAsArray(arena, 2)).* = .{ "-D", c_macro.slice(conf) };
60 }
61
62 var prev_search_strategy: std.Build.Module.SystemLib.SearchStrategy = .paths_first;
63 var prev_preferred_link_mode: std.builtin.LinkMode = .dynamic;
64 var seen_system_libs: std.AutoArrayHashMapUnmanaged(Configuration.String, []const []const u8) = .empty;
65
66 for (conf_tc.system_libs.slice) |system_lib_index| {
67 const system_lib = system_lib_index.get(conf);
68 const system_lib_name = system_lib.name.slice(conf);
69 const system_lib_gop = try seen_system_libs.getOrPut(arena, system_lib.name);
70 if (system_lib_gop.found_existing) {
71 try argv.appendSlice(arena, system_lib_gop.value_ptr.*);
72 continue;
73 } else {
74 system_lib_gop.value_ptr.* = &.{};
75 }
76
77 if ((system_lib.flags.search_strategy != prev_search_strategy or
78 system_lib.flags.preferred_link_mode != prev_preferred_link_mode))
79 {
80 try argv.ensureUnusedCapacity(arena, 1);
81 switch (system_lib.flags.search_strategy) {
82 .no_fallback => switch (system_lib.flags.preferred_link_mode) {
83 .dynamic => argv.appendAssumeCapacity("-search_dylibs_only"),
84 .static => argv.appendAssumeCapacity("-search_static_only"),
85 },
86 .paths_first => switch (system_lib.flags.preferred_link_mode) {
87 .dynamic => argv.appendAssumeCapacity("-search_paths_first"),
88 .static => argv.appendAssumeCapacity("-search_paths_first_static"),
89 },
90 .mode_first => switch (system_lib.flags.preferred_link_mode) {
91 .dynamic => argv.appendAssumeCapacity("-search_dylibs_first"),
92 .static => argv.appendAssumeCapacity("-search_static_first"),
93 },
94 }
95 prev_search_strategy = system_lib.flags.search_strategy;
96 prev_preferred_link_mode = system_lib.flags.preferred_link_mode;
97 }
98
99 const prefix: []const u8 = prefix: {
100 if (system_lib.flags.needed) break :prefix "-needed-l";
101 if (system_lib.flags.weak) break :prefix "-weak-l";
102 break :prefix "-l";
103 };
104 l: {
105 pc: {
106 const force = switch (system_lib.flags.use_pkg_config) {
107 .no => break :pc,
108 .yes => false,
109 .force => true,
110 };
111
112 const pkg_conf_node = progress_node.start("pkg-config", 0);
113 defer pkg_conf_node.end();
114
115 if (PkgConfig.run(maker, step, pkg_conf_node, system_lib_name, force)) |result| {
116 try argv.appendSlice(arena, result.cflags);
117 try argv.appendSlice(arena, result.libs);
118 try seen_system_libs.put(arena, system_lib.name, result.cflags);
119 break :l;
120 } else |err| switch (err) {
121 error.PkgConfigUnavailable,
122 error.PackageNotFound,
123 => {
124 // pkg-config failed, so fall back to linking the library by name directly.
125 assert(!force);
126 break :pc;
127 },
128 else => |e| return e,
129 }
130 }
131 try argv.append(arena, try allocPrint(arena, "{s}{s}", .{
132 prefix, system_lib_name,
133 }));
134 }
135 }
136
137 try argv.ensureUnusedCapacity(arena, 2);
138
139 const c_source_path = try maker.resolveLazyPathIndexAbs(arena, conf_tc.src_path, step_index);
140 argv.appendAssumeCapacity(c_source_path);
141
142 argv.appendAssumeCapacity("--listen=-");
143 const output_dir_path = (Step.evalZigProcess(step_index, maker, argv.items, progress_node, false) catch |err| switch (err) {
144 error.NeedCompileErrorCheck => unreachable,
145 else => |e| return e,
146 }).?;
147
148 const stem = Io.Dir.path.stem(Io.Dir.path.basename(c_source_path));
149 const out_basename = try allocPrint(arena, "{s}.zig", .{stem});
150
151 maker.generatedPath(conf_tc.output_file).* = try output_dir_path.join(arena, out_basename);
152}
lib/compiler/Maker/Step/UpdateSourceFiles.zig created+89
......@@ -0,0 +1,89 @@
1const UpdateSourceFiles = @This();
2
3const std = @import("std");
4const Io = std.Io;
5const Path = std.Build.Cache.Path;
6const allocPrint = std.fmt.allocPrint;
7const Configuration = std.Build.Configuration;
8
9const Step = @import("../Step.zig");
10const Maker = @import("../../Maker.zig");
11
12pub fn make(
13 usf: *UpdateSourceFiles,
14 step_index: Configuration.Step.Index,
15 maker: *Maker,
16 progress_node: std.Progress.Node,
17) Step.ExtendedMakeError!void {
18 _ = usf;
19 const graph = maker.graph;
20 const arena = maker.graph.arena; // TODO don't leak into process arena
21 const io = graph.io;
22 const step = maker.stepByIndex(step_index);
23 const conf = &maker.scanned_config.configuration;
24 const conf_step = step_index.ptr(conf);
25 const conf_usf = conf_step.extended.get(conf.extra).update_source_files;
26 const build_root = graph.build_root_directory;
27
28 if (conf_step.owner != .root)
29 return step.fail(maker, "non-root package attempted to update its source files", .{});
30
31 var any_miss = false;
32
33 progress_node.setEstimatedTotalItems(conf_usf.embeds.slice.len + conf_usf.copies.slice.len);
34
35 step.clearWatchInputs(maker);
36
37 for (conf_usf.embeds.slice) |*embed| {
38 const dest_path: Path = .{
39 .root_dir = build_root,
40 .sub_path = embed.sub_path.slice(conf),
41 };
42 if (Io.Dir.path.dirname(dest_path.sub_path)) |dirname| {
43 const dirname_path: Path = .{
44 .root_dir = build_root,
45 .sub_path = dirname,
46 };
47 dirname_path.root_dir.handle.createDirPath(io, dirname_path.sub_path) catch |err|
48 return step.fail(maker, "failed creating path {f}: {t}", .{ dirname_path, err });
49 }
50 dest_path.root_dir.handle.writeFile(io, .{
51 .sub_path = dest_path.sub_path,
52 .data = embed.contents.slice(conf),
53 }) catch |err| return step.fail(maker, "failed writing file {f}: {t}", .{ dest_path, err });
54 any_miss = true;
55 progress_node.completeOne();
56 }
57
58 for (conf_usf.copies.slice) |*copy| {
59 const dest_path: Path = .{
60 .root_dir = build_root,
61 .sub_path = copy.sub_path.slice(conf),
62 };
63 if (Io.Dir.path.dirname(dest_path.sub_path)) |dirname| {
64 const dirname_path: Path = .{
65 .root_dir = build_root,
66 .sub_path = dirname,
67 };
68 dirname_path.root_dir.handle.createDirPath(io, dirname_path.sub_path) catch |err|
69 return step.fail(maker, "failed creating path {f}: {t}", .{ dirname_path, err });
70 }
71 const src_lazy_path = copy.src_file.get(conf);
72 const source_path = try maker.resolveLazyPath(arena, src_lazy_path, step_index);
73 try step.addWatchInput(maker, arena, src_lazy_path);
74
75 const prev_status = source_path.root_dir.handle.updateFile(
76 io,
77 source_path.sub_path,
78 dest_path.root_dir.handle,
79 dest_path.sub_path,
80 .{},
81 ) catch |err| return step.fail(maker, "failed updating file from {f} to {f}: {t}", .{
82 source_path, dest_path, err,
83 });
84 any_miss = any_miss or prev_status == .stale;
85 progress_node.completeOne();
86 }
87
88 step.result_cached = !any_miss;
89}
lib/compiler/Maker/Step/WriteFile.zig created+294
......@@ -0,0 +1,294 @@
1const WriteFile = @This();
2
3const std = @import("std");
4const Io = std.Io;
5const assert = std.debug.assert;
6const Path = std.Build.Cache.Path;
7const allocPrint = std.fmt.allocPrint;
8const Configuration = std.Build.Configuration;
9
10const Step = @import("../Step.zig");
11const Maker = @import("../../Maker.zig");
12
13pub fn make(
14 wf: *WriteFile,
15 step_index: Configuration.Step.Index,
16 maker: *Maker,
17 progress_node: std.Progress.Node,
18) Step.ExtendedMakeError!void {
19 _ = wf;
20 const graph = maker.graph;
21 const gpa = maker.gpa;
22 const arena = maker.graph.arena; // TODO don't leak into process arena
23 const io = graph.io;
24 const step = maker.stepByIndex(step_index);
25 const conf = &maker.scanned_config.configuration;
26 const conf_step = step_index.ptr(conf);
27 const conf_wf = conf_step.extended.get(conf.extra).write_file;
28 const cache_root = graph.local_cache_root;
29 const directories = conf_wf.directories.slice;
30
31 const open_dir_cache = try arena.alloc(Io.Dir, directories.len);
32 var open_dirs_count: u32 = 0;
33 defer Io.Dir.closeMany(io, open_dir_cache[0..open_dirs_count]);
34
35 // Doesn't yet include contents of directories.
36 var total_items: usize = conf_wf.embeds.slice.len + conf_wf.copies.slice.len + conf_wf.directories.slice.len;
37 progress_node.setEstimatedTotalItems(total_items);
38
39 switch (conf_wf.flags.mode) {
40 .whole_cached => {
41 step.clearWatchInputs(maker);
42
43 // The cache is used here primarily as a way to find a canonical
44 // location to put build artifacts without parallel step execution
45 // clobbering each other.
46
47 var man = graph.cache.obtain();
48 defer man.deinit();
49
50 for (conf_wf.embeds.slice) |*embed| {
51 man.hash.addBytes(embed.sub_path.slice(conf));
52 man.hash.addBytes(embed.contents.slice(conf));
53 }
54
55 for (conf_wf.copies.slice) |*copy| {
56 man.hash.addBytes(copy.sub_path.slice(conf));
57 const src_lazy_path = copy.src_file.get(conf);
58 const source_path = try maker.resolveLazyPath(arena, src_lazy_path, step_index);
59 _ = try man.addFilePath(source_path, null);
60 try step.addWatchInput(maker, arena, src_lazy_path);
61 }
62
63 for (directories, open_dir_cache) |conf_dir, *opened_dir| {
64 const exclude_extensions = conf_dir.exclude_extensions.slice(conf) orelse &.{};
65 const include_extensions = conf_dir.include_extensions.slice(conf);
66
67 man.hash.addBytes(conf_dir.sub_path.slice(conf));
68 for (exclude_extensions) |ext| man.hash.addBytes(ext.slice(conf));
69 if (include_extensions) |includes| for (includes) |inc| {
70 man.hash.addBytes(inc.slice(conf));
71 };
72
73 const src_lazy_path = conf_dir.src_path.get(conf);
74 const need_derived_inputs = try step.addDirectoryWatchInput(maker, src_lazy_path);
75 const src_dir_path = try maker.resolveLazyPath(arena, src_lazy_path, step_index);
76
77 var src_dir = src_dir_path.root_dir.handle.openDir(io, src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| {
78 return step.fail(maker, "failed opening source directory {f}: {t}", .{ src_dir_path, err });
79 };
80 opened_dir.* = src_dir;
81 open_dirs_count += 1;
82
83 var it = try src_dir.walk(gpa);
84 defer it.deinit();
85 while (it.next(io) catch |err| switch (err) {
86 error.Canceled, error.OutOfMemory => |e| return e,
87 else => |e| return step.fail(maker, "failed iterating dir {f}: {t}", .{ src_dir_path, e }),
88 }) |entry| {
89 if (!pathIncluded(conf, exclude_extensions, include_extensions, entry.path)) continue;
90
91 switch (entry.kind) {
92 .directory => {
93 if (need_derived_inputs) {
94 const entry_path = try src_dir_path.join(arena, entry.path);
95 try step.addDirectoryWatchInputFromPath(maker, entry_path);
96 }
97 },
98 .file => {
99 const entry_path = try src_dir_path.join(arena, entry.path);
100 _ = try man.addFilePath(entry_path, null);
101 total_items += 1;
102 },
103 else => continue,
104 }
105 }
106 }
107
108 if (try step.cacheHit(maker, &man)) {
109 const digest = man.final();
110 maker.generatedPath(conf_wf.generated_directory).* = .{
111 .root_dir = cache_root,
112 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }),
113 };
114 assert(step.result_cached);
115 return;
116 }
117
118 const digest = man.final();
119 const out_path: Path = .{
120 .root_dir = cache_root,
121 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }),
122 };
123
124 progress_node.setEstimatedTotalItems(total_items);
125 try operate(maker, step_index, open_dir_cache, out_path, progress_node);
126 try step.writeManifest(maker, &man);
127
128 maker.generatedPath(conf_wf.generated_directory).* = out_path;
129 },
130 .tmp => {
131 step.result_cached = false;
132
133 var rand_int: u64 = undefined;
134 io.random(@ptrCast(&rand_int));
135 const hex_digest = std.fmt.hex(rand_int);
136
137 const out_path: Path = .{
138 .root_dir = cache_root,
139 .sub_path = try Io.Dir.path.join(arena, &.{ "tmp", &hex_digest }),
140 };
141
142 try operate(maker, step_index, open_dir_cache, out_path, progress_node);
143
144 maker.generatedPath(conf_wf.generated_directory).* = out_path;
145 },
146 .mutate => {
147 step.result_cached = false;
148 const root_path = try maker.resolveLazyPathIndex(arena, conf_wf.mutate_path.value.?, step_index);
149 try operate(maker, step_index, open_dir_cache, root_path, progress_node);
150 maker.generatedPath(conf_wf.generated_directory).* = root_path;
151 },
152 }
153}
154
155fn operate(
156 maker: *Maker,
157 step_index: Configuration.Step.Index,
158 open_dir_cache: []const Io.Dir,
159 root_path: std.Build.Cache.Path,
160 progress_node: std.Progress.Node,
161) !void {
162 const graph = maker.graph;
163 const gpa = maker.gpa;
164 const arena = maker.graph.arena; // TODO don't leak into process arena
165 const io = graph.io;
166 const step = maker.stepByIndex(step_index);
167 const conf = &maker.scanned_config.configuration;
168 const conf_step = step_index.ptr(conf);
169 const conf_wf = conf_step.extended.get(conf.extra).write_file;
170
171 const root_directory: std.Build.Cache.Directory = .{
172 .handle = root_path.root_dir.handle.createDirPathOpen(io, root_path.sub_path, .{}) catch |err|
173 return step.fail(maker, "failed creating path {f}: {t}", .{ root_path, err }),
174 .path = try root_path.toString(arena),
175 };
176 defer root_directory.handle.close(io);
177
178 for (conf_wf.embeds.slice) |*embed| {
179 const dest_path: Path = .{
180 .root_dir = root_directory,
181 .sub_path = embed.sub_path.slice(conf),
182 };
183 if (Io.Dir.path.dirname(dest_path.sub_path)) |dirname| {
184 const dirname_path: Path = .{
185 .root_dir = root_directory,
186 .sub_path = dirname,
187 };
188 dirname_path.root_dir.handle.createDirPath(io, dirname_path.sub_path) catch |err|
189 return step.fail(maker, "failed creating path {f}: {t}", .{ dirname_path, err });
190 }
191 dest_path.root_dir.handle.writeFile(io, .{
192 .sub_path = dest_path.sub_path,
193 .data = embed.contents.slice(conf),
194 }) catch |err| return step.fail(maker, "failed writing contents to file {f}: {t}", .{ dest_path, err });
195 progress_node.completeOne();
196 }
197
198 for (conf_wf.copies.slice) |*copy| {
199 const dest_path: Path = .{
200 .root_dir = root_directory,
201 .sub_path = copy.sub_path.slice(conf),
202 };
203 // Rather than passing make_path = true below, this optimizes for the
204 // more common case where the directory does not exist.
205 if (Io.Dir.path.dirname(dest_path.sub_path)) |dirname| {
206 const dirname_path: Path = .{
207 .root_dir = root_directory,
208 .sub_path = dirname,
209 };
210 dirname_path.root_dir.handle.createDirPath(io, dirname_path.sub_path) catch |err|
211 return step.fail(maker, "failed creating path {f}: {t}", .{ dirname_path, err });
212 }
213 const source_path = try maker.resolveLazyPathIndex(arena, copy.src_file, step_index);
214 Io.Dir.copyFile(
215 source_path.root_dir.handle,
216 source_path.sub_path,
217 dest_path.root_dir.handle,
218 dest_path.sub_path,
219 io,
220 .{},
221 ) catch |err| return step.fail(maker, "failed copying file from {f} to {f}: {t}", .{
222 source_path, dest_path, err,
223 });
224 progress_node.completeOne();
225 }
226
227 for (conf_wf.directories.slice, open_dir_cache) |conf_dir, already_open_dir| {
228 const exclude_extensions = conf_dir.exclude_extensions.slice(conf) orelse &.{};
229 const include_extensions = conf_dir.include_extensions.slice(conf);
230
231 const src_dir_path = try maker.resolveLazyPathIndex(arena, conf_dir.src_path, step_index);
232 const dest_dir_path: Path = .{
233 .root_dir = root_directory,
234 .sub_path = conf_dir.sub_path.slice(conf),
235 };
236
237 if (dest_dir_path.sub_path.len != 0) {
238 dest_dir_path.root_dir.handle.createDirPath(io, dest_dir_path.sub_path) catch |err|
239 return step.fail(maker, "failed creating path {f}: {t}", .{ dest_dir_path, err });
240 }
241
242 var it = try already_open_dir.walk(gpa);
243 defer it.deinit();
244 while (it.next(io) catch |err| switch (err) {
245 error.Canceled, error.OutOfMemory => |e| return e,
246 else => |e| return step.fail(maker, "failed iterating dir {f}: {t}", .{ src_dir_path, e }),
247 }) |entry| {
248 if (!pathIncluded(conf, exclude_extensions, include_extensions, entry.path)) continue;
249
250 const src_entry_path = try src_dir_path.join(arena, entry.path);
251 const dest_path = try dest_dir_path.join(arena, entry.path);
252 switch (entry.kind) {
253 .directory => dest_path.root_dir.handle.createDirPath(io, dest_path.sub_path) catch |err| {
254 return step.fail(maker, "failed creating path {f}: {t}", .{ dest_path, err });
255 },
256 .file => {
257 Io.Dir.copyFile(
258 src_entry_path.root_dir.handle,
259 src_entry_path.sub_path,
260 dest_path.root_dir.handle,
261 dest_path.sub_path,
262 io,
263 .{ .make_path = true }, // Directory entry may be filtered out above.
264 ) catch |err| return step.fail(maker, "failed copying file from {f} to {f}: {t}", .{
265 src_entry_path, dest_path, err,
266 });
267 progress_node.completeOne();
268 },
269 else => continue,
270 }
271 }
272 }
273}
274
275fn pathIncluded(
276 conf: *const Configuration,
277 exclude_extensions: []const Configuration.String,
278 include_extensions: ?[]const Configuration.String,
279 path: []const u8,
280) bool {
281 for (exclude_extensions) |ext| {
282 if (std.mem.endsWith(u8, path, ext.slice(conf)))
283 return false;
284 }
285 if (include_extensions) |incs| {
286 for (incs) |inc| {
287 if (std.mem.endsWith(u8, path, inc.slice(conf)))
288 return true;
289 } else {
290 return false;
291 }
292 }
293 return true;
294}
lib/compiler/Maker/Watch.zig created+989
......@@ -0,0 +1,989 @@
1const Watch = @This();
2const builtin = @import("builtin");
3
4const std = @import("std");
5const Io = std.Io;
6const Allocator = std.mem.Allocator;
7const assert = std.debug.assert;
8const fatal = std.process.fatal;
9const Configuration = std.Build.Configuration;
10
11const FsEvents = @import("Watch/FsEvents.zig");
12const Step = @import("Step.zig");
13const Maker = @import("../Maker.zig");
14
15os: Os,
16/// The number to show as the number of directories being watched.
17dir_count: usize,
18// These fields are common to most implementations so are kept here for simplicity.
19// They are `undefined` on implementations which do not utilize then.
20dir_table: DirTable,
21generation: Generation,
22maker: *Maker,
23
24pub const have_impl = Os != void;
25
26/// Key is the directory to watch which contains one or more files we are
27/// interested in noticing changes to.
28///
29/// Value is generation.
30const DirTable = std.ArrayHashMapUnmanaged(Cache.Path, void, Cache.Path.TableAdapter, false);
31
32/// Special key of "." means any changes in this directory trigger the steps.
33const ReactionSet = std.StringArrayHashMapUnmanaged(StepSet);
34const StepSet = std.AutoArrayHashMapUnmanaged(Configuration.Step.Index, Generation);
35
36const Generation = u8;
37
38const Hash = std.hash.Wyhash;
39const Cache = std.Build.Cache;
40
41const Os = switch (builtin.os.tag) {
42 .linux => struct {
43 const posix = std.posix;
44
45 /// Keyed differently but indexes correspond 1:1 with `dir_table`.
46 handle_table: HandleTable,
47 /// fanotify file descriptors are keyed by mount id since marks
48 /// are limited to a single filesystem.
49 poll_fds: std.AutoArrayHashMapUnmanaged(MountId, posix.pollfd),
50
51 const MountId = i32;
52 const HandleTable = std.ArrayHashMapUnmanaged(FileHandle, struct { mount_id: MountId, reaction_set: ReactionSet }, FileHandle.Adapter, false);
53
54 const fan_mask: std.os.linux.fanotify.MarkMask = .{
55 .CLOSE_WRITE = true,
56 .CREATE = true,
57 .DELETE = true,
58 .DELETE_SELF = true,
59 .EVENT_ON_CHILD = true,
60 .MOVED_FROM = true,
61 .MOVED_TO = true,
62 .MOVE_SELF = true,
63 .ONDIR = true,
64 };
65
66 const FileHandle = struct {
67 handle: *align(1) std.os.linux.file_handle,
68
69 fn clone(lfh: FileHandle, gpa: Allocator) Allocator.Error!FileHandle {
70 const bytes = lfh.slice();
71 const new_ptr = try gpa.alignedAlloc(
72 u8,
73 .of(std.os.linux.file_handle),
74 @sizeOf(std.os.linux.file_handle) + bytes.len,
75 );
76 const new_header: *std.os.linux.file_handle = @ptrCast(new_ptr);
77 new_header.* = lfh.handle.*;
78 const new: FileHandle = .{ .handle = new_header };
79 @memcpy(new.slice(), lfh.slice());
80 return new;
81 }
82
83 fn destroy(lfh: FileHandle, gpa: Allocator) void {
84 const ptr: [*]u8 = @ptrCast(lfh.handle);
85 const allocated_slice = ptr[0 .. @sizeOf(std.os.linux.file_handle) + lfh.handle.handle_bytes];
86 return gpa.free(allocated_slice);
87 }
88
89 fn slice(lfh: FileHandle) []u8 {
90 const ptr: [*]u8 = &lfh.handle.f_handle;
91 return ptr[0..lfh.handle.handle_bytes];
92 }
93
94 const Adapter = struct {
95 pub fn hash(self: Adapter, a: FileHandle) u32 {
96 _ = self;
97 const unsigned_type: u32 = @bitCast(a.handle.handle_type);
98 return @truncate(Hash.hash(unsigned_type, a.slice()));
99 }
100 pub fn eql(self: Adapter, a: FileHandle, b: FileHandle, b_index: usize) bool {
101 _ = self;
102 _ = b_index;
103 return a.handle.handle_type == b.handle.handle_type and std.mem.eql(u8, a.slice(), b.slice());
104 }
105 };
106 };
107
108 fn init(maker: *Maker) !Watch {
109 return .{
110 .dir_table = .{},
111 .dir_count = 0,
112 .os = switch (builtin.os.tag) {
113 .linux => .{
114 .handle_table = .{},
115 .poll_fds = .{},
116 },
117 else => {},
118 },
119 .generation = 0,
120 .maker = maker,
121 };
122 }
123
124 fn getDirHandle(gpa: Allocator, path: std.Build.Cache.Path, mount_id: *MountId) !FileHandle {
125 var file_handle_buffer: [@sizeOf(std.os.linux.file_handle) + 128]u8 align(@alignOf(std.os.linux.file_handle)) = undefined;
126 var buf: [std.fs.max_path_bytes]u8 = undefined;
127 const adjusted_path = if (path.sub_path.len == 0) "./" else std.fmt.bufPrint(&buf, "{s}/", .{
128 path.sub_path,
129 }) catch return error.NameTooLong;
130 const stack_ptr: *std.os.linux.file_handle = @ptrCast(&file_handle_buffer);
131 stack_ptr.handle_bytes = file_handle_buffer.len - @sizeOf(std.os.linux.file_handle);
132 try posix.name_to_handle_at(path.root_dir.handle.handle, adjusted_path, stack_ptr, mount_id, std.os.linux.AT.HANDLE_FID);
133 const stack_lfh: FileHandle = .{ .handle = stack_ptr };
134 return stack_lfh.clone(gpa);
135 }
136
137 fn markDirtySteps(w: *Watch, fan_fd: posix.fd_t) !bool {
138 const maker = w.maker;
139 const fanotify = std.os.linux.fanotify;
140 const M = fanotify.event_metadata;
141 var events_buf: [256 + 4096]u8 = undefined;
142 var any_dirty = false;
143 while (true) {
144 var len = posix.read(fan_fd, &events_buf) catch |err| switch (err) {
145 error.WouldBlock => return any_dirty,
146 else => |e| return e,
147 };
148 var meta: [*]align(1) M = @ptrCast(&events_buf);
149 while (len >= @sizeOf(M) and meta[0].event_len >= @sizeOf(M) and meta[0].event_len <= len) : ({
150 len -= meta[0].event_len;
151 meta = @ptrCast(@as([*]u8, @ptrCast(meta)) + meta[0].event_len);
152 }) {
153 assert(meta[0].vers == M.VERSION);
154 if (meta[0].mask.Q_OVERFLOW) {
155 any_dirty = true;
156 std.log.warn("file system watch queue overflowed; falling back to fstat", .{});
157 markAllFilesDirty(w);
158 return true;
159 }
160 const fid: *align(1) fanotify.event_info_fid = @ptrCast(meta + 1);
161 switch (fid.hdr.info_type) {
162 .DFID_NAME => {
163 const file_handle: *align(1) std.os.linux.file_handle = @ptrCast(&fid.handle);
164 const file_name_z: [*:0]u8 = @ptrCast((&file_handle.f_handle).ptr + file_handle.handle_bytes);
165 const file_name = std.mem.span(file_name_z);
166 const lfh: FileHandle = .{ .handle = file_handle };
167 if (w.os.handle_table.getPtr(lfh)) |value| {
168 if (value.reaction_set.getPtr(".")) |glob_set|
169 any_dirty = markStepSetDirty(maker, glob_set, any_dirty);
170 if (value.reaction_set.getPtr(file_name)) |step_set|
171 any_dirty = markStepSetDirty(maker, step_set, any_dirty);
172 }
173 },
174 else => |t| std.log.warn("unexpected fanotify event '{t}'", .{t}),
175 }
176 }
177 }
178 }
179
180 fn update(w: *Watch, steps: []const Configuration.Step.Index) !void {
181 const maker = w.maker;
182 const gpa = maker.gpa;
183
184 // Add missing marks and note persisted ones.
185 for (steps) |step_index| {
186 const step = maker.stepByIndex(step_index);
187 for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| {
188 const reaction_set = rs: {
189 const gop = try w.dir_table.getOrPut(gpa, path);
190 if (!gop.found_existing) {
191 var mount_id: MountId = undefined;
192 const dir_handle = getDirHandle(gpa, path, &mount_id) catch |err| switch (err) {
193 error.FileNotFound => {
194 std.debug.assert(w.dir_table.swapRemove(path));
195 continue;
196 },
197 else => return err,
198 };
199 const fan_fd = blk: {
200 const fd_gop = try w.os.poll_fds.getOrPut(gpa, mount_id);
201 if (!fd_gop.found_existing) {
202 const fan_fd = std.posix.fanotify_init(.{
203 .CLASS = .NOTIF,
204 .CLOEXEC = true,
205 .NONBLOCK = true,
206 .REPORT_NAME = true,
207 .REPORT_DIR_FID = true,
208 .REPORT_FID = true,
209 .REPORT_TARGET_FID = true,
210 }, 0) catch |err| switch (err) {
211 error.UnsupportedFlags => fatal("fanotify_init failed due to old kernel; requires 5.17+", .{}),
212 else => |e| return e,
213 };
214 fd_gop.value_ptr.* = .{
215 .fd = fan_fd,
216 .events = std.posix.POLL.IN,
217 .revents = undefined,
218 };
219 }
220 break :blk fd_gop.value_ptr.*.fd;
221 };
222 // `dir_handle` may already be present in the table in
223 // the case that we have multiple Cache.Path instances
224 // that compare inequal but ultimately point to the same
225 // directory on the file system.
226 // In such case, we must revert adding this directory, but keep
227 // the additions to the step set.
228 const dh_gop = try w.os.handle_table.getOrPut(gpa, dir_handle);
229 if (dh_gop.found_existing) {
230 _ = w.dir_table.pop();
231 } else {
232 assert(dh_gop.index == gop.index);
233 dh_gop.value_ptr.* = .{ .mount_id = mount_id, .reaction_set = .{} };
234 posix.fanotify_mark(fan_fd, .{
235 .ADD = true,
236 .ONLYDIR = true,
237 }, fan_mask, path.root_dir.handle.handle, path.subPathOrDot()) catch |err|
238 fatal("unable to watch {f}: {t}", .{ path, err });
239 }
240 break :rs &dh_gop.value_ptr.reaction_set;
241 }
242 break :rs &w.os.handle_table.values()[gop.index].reaction_set;
243 };
244 for (files.items) |basename| {
245 const gop = try reaction_set.getOrPut(gpa, basename);
246 if (!gop.found_existing) gop.value_ptr.* = .{};
247 try gop.value_ptr.put(gpa, step_index, w.generation);
248 }
249 }
250 }
251
252 {
253 // Remove marks for files that are no longer inputs.
254 var i: usize = 0;
255 while (i < w.os.handle_table.entries.len) {
256 {
257 const reaction_set = &w.os.handle_table.values()[i].reaction_set;
258 var step_set_i: usize = 0;
259 while (step_set_i < reaction_set.entries.len) {
260 const step_set = &reaction_set.values()[step_set_i];
261 var dirent_i: usize = 0;
262 while (dirent_i < step_set.entries.len) {
263 const generations = step_set.values();
264 if (generations[dirent_i] == w.generation) {
265 dirent_i += 1;
266 continue;
267 }
268 step_set.swapRemoveAt(dirent_i);
269 }
270 if (step_set.entries.len > 0) {
271 step_set_i += 1;
272 continue;
273 }
274 reaction_set.swapRemoveAt(step_set_i);
275 }
276 if (reaction_set.entries.len > 0) {
277 i += 1;
278 continue;
279 }
280 }
281
282 const path = w.dir_table.keys()[i];
283
284 const mount_id = w.os.handle_table.values()[i].mount_id;
285 const fan_fd = w.os.poll_fds.getEntry(mount_id).?.value_ptr.fd;
286 posix.fanotify_mark(fan_fd, .{
287 .REMOVE = true,
288 .ONLYDIR = true,
289 }, fan_mask, path.root_dir.handle.handle, path.subPathOrDot()) catch |err| switch (err) {
290 error.FileNotFound => {}, // Expected, harmless.
291 else => |e| std.log.warn("unable to unwatch {f}: {t}", .{ path, e }),
292 };
293
294 w.dir_table.swapRemoveAt(i);
295 w.os.handle_table.swapRemoveAt(i);
296 }
297 w.generation +%= 1;
298 }
299 w.dir_count = w.dir_table.count();
300 }
301
302 fn wait(w: *Watch, timeout: Timeout) !WaitResult {
303 const events_len = try std.posix.poll(w.os.poll_fds.values(), timeout.to_i32_ms());
304 if (events_len == 0)
305 return .timeout;
306 for (w.os.poll_fds.values()) |poll_fd| {
307 if (poll_fd.revents & std.posix.POLL.IN == std.posix.POLL.IN and try markDirtySteps(w, poll_fd.fd))
308 return .dirty;
309 }
310 return .clean;
311 }
312 },
313 .windows => struct {
314 const windows = std.os.windows;
315
316 /// Keyed differently but indexes correspond 1:1 with `dir_table`.
317 handle_table: std.ArrayHashMapUnmanaged(*Directory, void, Directory.TableAdapter, false),
318 ready_dirs: std.DoublyLinkedList,
319
320 const FileId = struct {
321 volumeSerialNumber: windows.ULONG,
322 indexNumber: windows.LARGE_INTEGER,
323 };
324
325 const Directory = struct {
326 reaction_set: ReactionSet,
327 id: FileId,
328 file: Io.File,
329 state: enum { idle, listening, ready },
330 iosb: windows.IO_STATUS_BLOCK,
331 // 64 KB is the packet size limit when monitoring over a network.
332 // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-readdirectorychangesw#remarks
333 buffer: [64 * 1024]u8 align(@alignOf(windows.FILE.NOTIFY.INFORMATION)),
334 ready_node: std.DoublyLinkedList.Node,
335
336 /// Start listening for events, buffer field will be overwritten eventually.
337 fn startListening(dir: *Directory, w: *Watch) !void {
338 assert(dir.file.flags.nonblocking);
339 assert(dir.state == .idle);
340 switch (windows.ntdll.NtNotifyChangeDirectoryFileEx(
341 dir.file.handle,
342 null,
343 &notifyApc,
344 w,
345 &dir.iosb,
346 &dir.buffer,
347 dir.buffer.len,
348 .{
349 .FILE_NAME = true,
350 .DIR_NAME = true,
351 .SIZE = true,
352 .LAST_WRITE = true,
353 .CREATION = true,
354 },
355 .FALSE,
356 .Notify,
357 )) {
358 .SUCCESS, .PENDING => dir.state = .listening,
359 .ILLEGAL_FUNCTION => return error.ReadDirectoryChangesUnsupported,
360 else => |status| return windows.unexpectedStatus(status),
361 }
362 }
363
364 fn notifyApc(apc_context: ?*anyopaque, iosb: *windows.IO_STATUS_BLOCK, _: windows.ULONG) align(std.Io.Threaded.apc_align) callconv(.winapi) void {
365 const w: *Watch = @ptrCast(@alignCast(apc_context));
366 const dir: *Directory = @fieldParentPtr("iosb", iosb);
367 assert(iosb.u.Status != .PENDING);
368 assert(dir.state == .listening);
369 w.os.ready_dirs.append(&dir.ready_node);
370 dir.state = .ready;
371 }
372
373 fn init(gpa: Allocator, path: Cache.Path) !*Directory {
374 // The following code is a drawn out NtCreateFile call. (mostly adapted from Io.Dir.makeOpenDirAccessMaskW)
375 // It's necessary in order to get the specific flags that are required when calling ReadDirectoryChangesW.
376 var dir_handle: windows.HANDLE = undefined;
377 const root_fd = path.root_dir.handle.handle;
378 const sub_path = path.subPathOrDot();
379 const sub_path_w = try Io.Threaded.sliceToPrefixedFileW(root_fd, sub_path, .{}); // TODO eliminate this call
380 var iosb: windows.IO_STATUS_BLOCK = undefined;
381 switch (windows.ntdll.NtCreateFile(
382 &dir_handle,
383 .{
384 .SPECIFIC = .{ .FILE_DIRECTORY = .{
385 .LIST = true,
386 } },
387 .STANDARD = .{ .SYNCHRONIZE = true },
388 .GENERIC = .{ .READ = true },
389 },
390 &.{
391 .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w.span())) null else root_fd,
392 .ObjectName = @constCast(&sub_path_w.string()),
393 },
394 &iosb,
395 null,
396 .{},
397 .VALID_FLAGS,
398 .OPEN,
399 .{
400 .DIRECTORY_FILE = true,
401 .IO = .ASYNCHRONOUS,
402 .OPEN_FOR_BACKUP_INTENT = true,
403 },
404 null,
405 0,
406 )) {
407 .SUCCESS => {},
408 .OBJECT_NAME_INVALID => return error.BadPathName,
409 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
410 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
411 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
412 .NOT_A_DIRECTORY => return error.NotDir,
413 // This can happen if the directory has 'List folder contents' permission set to 'Deny'
414 .ACCESS_DENIED => return error.AccessDenied,
415 .INVALID_PARAMETER => unreachable,
416 else => |rc| return windows.unexpectedStatus(rc),
417 }
418 assert(dir_handle != windows.INVALID_HANDLE_VALUE);
419 errdefer windows.CloseHandle(dir_handle);
420
421 const dir_id = try getFileId(dir_handle);
422
423 const dir = try gpa.create(Directory);
424 dir.* = .{
425 .reaction_set = .empty,
426 .id = dir_id,
427 .file = .{ .handle = dir_handle, .flags = .{ .nonblocking = true } },
428 .state = .idle,
429 .iosb = undefined,
430 .buffer = undefined,
431 .ready_node = undefined,
432 };
433 return dir;
434 }
435
436 fn deinit(dir: *Directory, gpa: Allocator, w: *Watch) void {
437 state: switch (dir.state) {
438 .idle => {},
439 .listening => {
440 var cancel_iosb: windows.IO_STATUS_BLOCK = undefined;
441 _ = windows.ntdll.NtCancelIoFileEx(dir.file.handle, &dir.iosb, &cancel_iosb);
442 while (switch (dir.state) {
443 .idle => unreachable,
444 .listening => true,
445 .ready => false,
446 }) Io.Threaded.waitForApcOrAlert();
447 continue :state .ready;
448 },
449 .ready => w.os.ready_dirs.remove(&dir.ready_node),
450 }
451 windows.CloseHandle(dir.file.handle);
452 gpa.destroy(dir);
453 }
454
455 /// Useful to make `*Directory` a key in `std.ArrayHashMap`.
456 const TableAdapter = struct {
457 pub fn hash(_: TableAdapter, lhs_dir: *Directory) u32 {
458 return @truncate(Hash.hash(lhs_dir.id.volumeSerialNumber, @ptrCast(&lhs_dir.id.indexNumber)));
459 }
460 pub fn eql(_: TableAdapter, lhs_dir: *Directory, rhs_dir: *Directory, rhs_index: usize) bool {
461 _ = rhs_index;
462 return lhs_dir.id.volumeSerialNumber == rhs_dir.id.volumeSerialNumber and
463 lhs_dir.id.indexNumber == rhs_dir.id.indexNumber;
464 }
465 };
466 };
467
468 fn init(maker: *Maker) !Watch {
469 return .{
470 .dir_table = .{},
471 .dir_count = 0,
472 .os = switch (builtin.os.tag) {
473 .windows => .{
474 .handle_table = .empty,
475 .ready_dirs = .{},
476 },
477 else => {},
478 },
479 .generation = 0,
480 .maker = maker,
481 };
482 }
483
484 fn getFileId(handle: windows.HANDLE) !FileId {
485 var file_id: FileId = undefined;
486 var io_status: windows.IO_STATUS_BLOCK = undefined;
487 var volume_info: windows.FILE.FS_VOLUME_INFORMATION = undefined;
488 switch (windows.ntdll.NtQueryVolumeInformationFile(
489 handle,
490 &io_status,
491 &volume_info,
492 @sizeOf(windows.FILE.FS_VOLUME_INFORMATION),
493 .Volume,
494 )) {
495 .SUCCESS => {},
496 // Buffer overflow here indicates that there is more information available than was able to be stored in the buffer
497 // size provided. This is treated as success because the type of variable-length information that this would be relevant for
498 // (name, volume name, etc) we don't care about.
499 .BUFFER_OVERFLOW => {},
500 else => |rc| return windows.unexpectedStatus(rc),
501 }
502 file_id.volumeSerialNumber = volume_info.VolumeSerialNumber;
503 var internal_info: windows.FILE.INTERNAL_INFORMATION = undefined;
504 switch (windows.ntdll.NtQueryInformationFile(
505 handle,
506 &io_status,
507 &internal_info,
508 @sizeOf(windows.FILE.INTERNAL_INFORMATION),
509 .Internal,
510 )) {
511 .SUCCESS => {},
512 else => |rc| return windows.unexpectedStatus(rc),
513 }
514 file_id.indexNumber = internal_info.IndexNumber;
515 return file_id;
516 }
517
518 fn markDirtySteps(w: *Watch, dir: *Directory) !bool {
519 const maker = w.maker;
520
521 var any_dirty = false;
522 const bytes_returned = dir.iosb.Information;
523 if (bytes_returned == 0) {
524 std.log.warn("file system watch queue overflowed; falling back to fstat", .{});
525 markAllFilesDirty(w);
526 try dir.startListening(w);
527 return true;
528 }
529 var file_name_buf: [std.fs.max_path_bytes]u8 = undefined;
530 var offset: usize = 0;
531 while (true) {
532 const notify: *windows.FILE.NOTIFY.INFORMATION = @ptrCast(@alignCast(&dir.buffer[offset]));
533 const file_name = file_name_buf[0..std.unicode.wtf16LeToWtf8(&file_name_buf, notify.fileName())];
534 if (dir.reaction_set.getPtr(".")) |glob_set|
535 any_dirty = markStepSetDirty(maker, glob_set, any_dirty);
536 if (dir.reaction_set.getPtr(file_name)) |step_set|
537 any_dirty = markStepSetDirty(maker, step_set, any_dirty);
538 if (notify.NextEntryOffset == 0)
539 break;
540
541 offset += notify.NextEntryOffset;
542 }
543
544 // We call this now since at this point we have finished reading dir.buffer.
545 try dir.startListening(w);
546 return any_dirty;
547 }
548
549 fn update(w: *Watch, steps: []const Configuration.Step.Index) !void {
550 const maker = w.maker;
551 const gpa = maker.gpa;
552 // Add missing marks and note persisted ones.
553 for (steps) |step_index| {
554 const step = maker.stepByIndex(step_index);
555 for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| {
556 const dir = dir: {
557 const gop = try w.dir_table.getOrPut(gpa, path);
558 if (!gop.found_existing) {
559 const dir: *Directory = try .init(gpa, path);
560 errdefer dir.deinit(gpa, w);
561 // `dir.id` may already be present in the table in
562 // the case that we have multiple Cache.Path instances
563 // that compare inequal but ultimately point to the same
564 // directory on the file system.
565 // In such case, we must revert adding this directory, but keep
566 // the additions to the step set.
567 const dh_gop = try w.os.handle_table.getOrPut(gpa, dir);
568 if (dh_gop.found_existing) {
569 dir.deinit(gpa, w);
570 _ = w.dir_table.pop();
571 break :dir w.os.handle_table.keys()[dh_gop.index];
572 } else {
573 assert(dh_gop.index == gop.index);
574 try dir.startListening(w);
575 break :dir dir;
576 }
577 }
578 break :dir w.os.handle_table.keys()[gop.index];
579 };
580 for (files.items) |basename| {
581 const gop = try dir.reaction_set.getOrPut(gpa, basename);
582 if (!gop.found_existing) gop.value_ptr.* = .{};
583 try gop.value_ptr.put(gpa, step_index, w.generation);
584 }
585 }
586 }
587
588 {
589 // Remove marks for files that are no longer inputs.
590 var i: usize = 0;
591 while (i < w.os.handle_table.entries.len) {
592 const dir = w.os.handle_table.keys()[i];
593 {
594 var step_set_i: usize = 0;
595 while (step_set_i < dir.reaction_set.entries.len) {
596 const step_set = &dir.reaction_set.values()[step_set_i];
597 var dirent_i: usize = 0;
598 while (dirent_i < step_set.entries.len) {
599 const generations = step_set.values();
600 if (generations[dirent_i] == w.generation) {
601 dirent_i += 1;
602 continue;
603 }
604 step_set.swapRemoveAt(dirent_i);
605 }
606 if (step_set.entries.len > 0) {
607 step_set_i += 1;
608 continue;
609 }
610 dir.reaction_set.swapRemoveAt(step_set_i);
611 }
612 if (dir.reaction_set.entries.len > 0) {
613 i += 1;
614 continue;
615 }
616 }
617
618 w.dir_table.swapRemoveAt(i);
619 w.os.handle_table.swapRemoveAt(i);
620 dir.deinit(gpa, w);
621 }
622 w.generation +%= 1;
623 }
624 w.dir_count = w.dir_table.count();
625 }
626
627 fn wait(w: *Watch, timeout: Timeout) !WaitResult {
628 const maker = w.maker;
629 const io = maker.graph.io;
630
631 for (0..2) |attempt| {
632 while (w.os.ready_dirs.popFirst()) |ready_node| {
633 const dir: *Directory = @fieldParentPtr("ready_node", ready_node);
634 assert(dir.state == .ready);
635 dir.state = .idle;
636 switch (dir.iosb.u.Status) {
637 .SUCCESS => return if (try markDirtySteps(w, dir)) .dirty else .clean,
638 .PENDING => unreachable,
639 .CANCELLED => {},
640 else => |status| return windows.unexpectedStatus(status),
641 }
642 try dir.startListening(w);
643 }
644 try io.checkCancel();
645 if (attempt == 1) return .timeout;
646 const delay_interval: windows.LARGE_INTEGER = switch (timeout) {
647 .none => std.math.minInt(windows.LARGE_INTEGER),
648 .ms => |ms| -@as(windows.LARGE_INTEGER, ms) * (std.time.ns_per_ms / 100),
649 };
650 _ = windows.ntdll.NtDelayExecution(.TRUE, &delay_interval);
651 } else unreachable;
652 }
653 },
654 .dragonfly, .freebsd, .netbsd, .openbsd, .ios, .tvos, .visionos, .watchos => struct {
655 const posix = std.posix;
656
657 kq_fd: i32,
658 /// Indexes correspond 1:1 with `dir_table`.
659 handles: std.MultiArrayList(struct {
660 rs: ReactionSet,
661 /// If the corresponding dir_table Path has sub_path == "", then it
662 /// suffices as the open directory handle, and this value will be
663 /// -1. Otherwise, it needs to be opened in update(), and will be
664 /// stored here.
665 dir_fd: i32,
666 }),
667
668 const dir_open_flags: posix.O = f: {
669 var f: posix.O = .{
670 .ACCMODE = .RDONLY,
671 .NOFOLLOW = false,
672 .DIRECTORY = true,
673 .CLOEXEC = true,
674 };
675 if (@hasField(posix.O, "EVTONLY")) f.EVTONLY = true;
676 if (@hasField(posix.O, "PATH")) f.PATH = true;
677 break :f f;
678 };
679
680 const EV = std.c.EV;
681 const NOTE = std.c.NOTE;
682
683 fn init(maker: *Maker) !Watch {
684 return .{
685 .dir_table = .{},
686 .dir_count = 0,
687 .os = .{
688 .kq_fd = try Io.Kqueue.createFileDescriptor(),
689 .handles = .empty,
690 },
691 .generation = 0,
692 .maker = maker,
693 };
694 }
695
696 fn update(w: *Watch, steps: []const Configuration.Step.Index) !void {
697 const maker = w.maker;
698 const gpa = maker.gpa;
699 const handles = &w.os.handles;
700 for (steps) |step_index| {
701 const step = maker.stepByIndex(step_index);
702 for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| {
703 const reaction_set = rs: {
704 const gop = try w.dir_table.getOrPut(gpa, path);
705 if (!gop.found_existing) {
706 const skip_open_dir = path.sub_path.len == 0;
707 const dir_fd = if (skip_open_dir)
708 path.root_dir.handle.handle
709 else
710 posix.openat(path.root_dir.handle.handle, path.sub_path, dir_open_flags, 0) catch |err| {
711 fatal("failed to open directory {f}: {t}", .{ path, err });
712 };
713 // Empirically the dir has to stay open or else no events are triggered.
714 errdefer if (!skip_open_dir) std.Io.Threaded.closeFd(dir_fd);
715 const changes = [1]posix.Kevent{.{
716 .ident = @bitCast(@as(isize, dir_fd)),
717 .filter = std.c.EVFILT.VNODE,
718 .flags = EV.ADD | EV.ENABLE | EV.CLEAR,
719 .fflags = NOTE.DELETE | NOTE.WRITE | NOTE.RENAME | NOTE.REVOKE,
720 .data = 0,
721 .udata = gop.index,
722 }};
723 _ = try Io.Kqueue.kevent(w.os.kq_fd, &changes, &.{}, null);
724 assert(handles.len == gop.index);
725 try handles.append(gpa, .{
726 .rs = .{},
727 .dir_fd = if (skip_open_dir) -1 else dir_fd,
728 });
729 }
730
731 break :rs &handles.items(.rs)[gop.index];
732 };
733 for (files.items) |basename| {
734 const gop = try reaction_set.getOrPut(gpa, basename);
735 if (!gop.found_existing) gop.value_ptr.* = .{};
736 try gop.value_ptr.put(gpa, step_index, w.generation);
737 }
738 }
739 }
740
741 {
742 // Remove marks for files that are no longer inputs.
743 var i: usize = 0;
744 while (i < handles.len) {
745 {
746 const reaction_set = &handles.items(.rs)[i];
747 var step_set_i: usize = 0;
748 while (step_set_i < reaction_set.entries.len) {
749 const step_set = &reaction_set.values()[step_set_i];
750 var dirent_i: usize = 0;
751 while (dirent_i < step_set.entries.len) {
752 const generations = step_set.values();
753 if (generations[dirent_i] == w.generation) {
754 dirent_i += 1;
755 continue;
756 }
757 step_set.swapRemoveAt(dirent_i);
758 }
759 if (step_set.entries.len > 0) {
760 step_set_i += 1;
761 continue;
762 }
763 reaction_set.swapRemoveAt(step_set_i);
764 }
765 if (reaction_set.entries.len > 0) {
766 i += 1;
767 continue;
768 }
769 }
770
771 // If the sub_path == "" then this patch has already the
772 // dir fd that we need to use as the ident to remove the
773 // event. If it was opened above with openat() then we need
774 // to access that data via the dir_fd field.
775 const path = w.dir_table.keys()[i];
776 const dir_fd = if (path.sub_path.len == 0)
777 path.root_dir.handle.handle
778 else
779 handles.items(.dir_fd)[i];
780 assert(dir_fd != -1);
781
782 // The changelist also needs to update the udata field of the last
783 // event, since we are doing a swap remove, and we store the dir_table
784 // index in the udata field.
785 const last_dir_fd = fd: {
786 const last_path = w.dir_table.keys()[handles.len - 1];
787 const last_dir_fd = if (last_path.sub_path.len == 0)
788 last_path.root_dir.handle.handle
789 else
790 handles.items(.dir_fd)[handles.len - 1];
791 assert(last_dir_fd != -1);
792 break :fd last_dir_fd;
793 };
794 const changes = [_]posix.Kevent{
795 .{
796 .ident = @bitCast(@as(isize, dir_fd)),
797 .filter = std.c.EVFILT.VNODE,
798 .flags = EV.DELETE,
799 .fflags = 0,
800 .data = 0,
801 .udata = i,
802 },
803 .{
804 .ident = @bitCast(@as(isize, last_dir_fd)),
805 .filter = std.c.EVFILT.VNODE,
806 .flags = EV.ADD,
807 .fflags = NOTE.DELETE | NOTE.WRITE | NOTE.RENAME | NOTE.REVOKE,
808 .data = 0,
809 .udata = i,
810 },
811 };
812 const filtered_changes = if (i == handles.len - 1) changes[0..1] else &changes;
813 _ = try Io.Kqueue.kevent(w.os.kq_fd, filtered_changes, &.{}, null);
814 if (path.sub_path.len != 0) std.Io.Threaded.closeFd(dir_fd);
815
816 w.dir_table.swapRemoveAt(i);
817 handles.swapRemove(i);
818 }
819 w.generation +%= 1;
820 }
821 w.dir_count = w.dir_table.count();
822 }
823
824 fn wait(w: *Watch, timeout: Timeout) !WaitResult {
825 const maker = w.maker;
826 var timespec_buffer: posix.timespec = undefined;
827 var event_buffer: [100]posix.Kevent = undefined;
828 var n = try Io.Kqueue.kevent(w.os.kq_fd, &.{}, &event_buffer, timeout.toTimespec(&timespec_buffer));
829 if (n == 0) return .timeout;
830 const reaction_sets = w.os.handles.items(.rs);
831 var any_dirty = markDirtySteps(maker, reaction_sets, event_buffer[0..n], false);
832 timespec_buffer = .{ .sec = 0, .nsec = 0 };
833 while (n == event_buffer.len) {
834 n = try Io.Kqueue.kevent(w.os.kq_fd, &.{}, &event_buffer, &timespec_buffer);
835 if (n == 0) break;
836 any_dirty = markDirtySteps(maker, reaction_sets, event_buffer[0..n], any_dirty);
837 }
838 return if (any_dirty) .dirty else .clean;
839 }
840
841 fn markDirtySteps(
842 maker: *Maker,
843 reaction_sets: []ReactionSet,
844 events: []const std.c.Kevent,
845 start_any_dirty: bool,
846 ) bool {
847 var any_dirty = start_any_dirty;
848 for (events) |event| {
849 const index: usize = @intCast(event.udata);
850 const reaction_set = &reaction_sets[index];
851 // If we knew the basename of the changed file, here we would
852 // mark only the step set dirty, and possibly the glob set:
853 //if (reaction_set.getPtr(".")) |glob_set|
854 // any_dirty = markStepSetDirty(maker, glob_set, any_dirty);
855 //if (reaction_set.getPtr(file_name)) |step_set|
856 // any_dirty = markStepSetDirty(maker, step_set, any_dirty);
857 // However we don't know the file name so just mark all the
858 // sets dirty for this directory.
859 for (reaction_set.values()) |*step_set| {
860 any_dirty = markStepSetDirty(maker, step_set, any_dirty);
861 }
862 }
863 return any_dirty;
864 }
865 },
866 .macos => struct {
867 fse: FsEvents,
868
869 fn init(maker: *Maker) !Watch {
870 return .{
871 .os = .{ .fse = try .init(maker.graph.cache.cwd) },
872 .dir_count = 0,
873 .dir_table = undefined,
874 .generation = undefined,
875 .maker = maker,
876 };
877 }
878 fn update(w: *Watch, steps: []const Configuration.Step.Index) !void {
879 try w.os.fse.setPaths(w.maker, steps);
880 w.dir_count = w.os.fse.watch_roots.len;
881 }
882 fn wait(w: *Watch, timeout: Timeout) !WaitResult {
883 return w.os.fse.wait(w.maker, switch (timeout) {
884 .none => null,
885 .ms => |ms| @as(u64, ms) * std.time.ns_per_ms,
886 });
887 }
888 },
889 else => void,
890};
891
892pub fn init(maker: *Maker) !Watch {
893 return Os.init(maker);
894}
895
896pub const Match = struct {
897 /// Relative to the watched directory, the file path that triggers this
898 /// match.
899 basename: []const u8,
900 /// The step to re-run when file corresponding to `basename` is changed.
901 step_index: Configuration.Step.Index,
902
903 pub const Context = struct {
904 pub fn hash(self: Context, a: Match) u32 {
905 _ = self;
906 var hasher = Hash.init(@intFromEnum(a.step_index));
907 hasher.update(a.basename);
908 return @truncate(hasher.final());
909 }
910 pub fn eql(self: Context, a: Match, b: Match, b_index: usize) bool {
911 _ = self;
912 _ = b_index;
913 return a.step_index == b.step_index and std.mem.eql(u8, a.basename, b.basename);
914 }
915 };
916};
917
918fn markAllFilesDirty(w: *Watch) void {
919 const maker = w.maker;
920
921 for (switch (builtin.os.tag) {
922 .windows => w.os.handle_table.keys(),
923 else => w.os.handle_table.values(),
924 }) |item| {
925 const reaction_set = switch (builtin.os.tag) {
926 .linux, .windows => item.reaction_set,
927 else => item,
928 };
929 for (reaction_set.values()) |step_set| {
930 for (step_set.keys()) |step_index| {
931 const step = maker.stepByIndex(step_index);
932 _ = maker.invalidateResult(step);
933 }
934 }
935 }
936}
937
938fn markStepSetDirty(maker: *Maker, step_set: *StepSet, any_dirty: bool) bool {
939 var this_any_dirty = false;
940 for (step_set.keys()) |step_index| {
941 const step = maker.stepByIndex(step_index);
942 if (maker.invalidateResult(step)) this_any_dirty = true;
943 }
944 return any_dirty or this_any_dirty;
945}
946
947pub fn update(w: *Watch, steps: []const Configuration.Step.Index) !void {
948 return Os.update(w, steps);
949}
950
951pub const Timeout = union(enum) {
952 none,
953 ms: u16,
954
955 pub fn to_i32_ms(t: Timeout) i32 {
956 return switch (t) {
957 .none => -1,
958 .ms => |ms| ms,
959 };
960 }
961
962 pub fn toTimespec(t: Timeout, buf: *std.posix.timespec) ?*std.posix.timespec {
963 return switch (t) {
964 .none => null,
965 .ms => |ms_u16| {
966 const ms: isize = ms_u16;
967 buf.* = .{
968 .sec = @divTrunc(ms, std.time.ms_per_s),
969 .nsec = @rem(ms, std.time.ms_per_s) * std.time.ns_per_ms,
970 };
971 return buf;
972 },
973 };
974 }
975};
976
977pub const WaitResult = enum {
978 timeout,
979 /// File system watching triggered on files that were marked as inputs to at least one Step.
980 /// Relevant steps have been marked dirty.
981 dirty,
982 /// File system watching triggered but none of the events were relevant to
983 /// what we are listening to. There is nothing to do.
984 clean,
985};
986
987pub fn wait(w: *Watch, timeout: Timeout) !WaitResult {
988 return Os.wait(w, timeout);
989}
lib/compiler/Maker/Watch/FsEvents.zig created+488
......@@ -0,0 +1,488 @@
1//! An implementation of file-system watching based on the `FSEventStream` API in macOS.
2//! While macOS supports kqueue, it does not allow detecting changes to files without
3//! placing watches on each individual file, meaning FD limits are reached incredibly
4//! quickly. The File System Events API works differently: it implements *recursive*
5//! directory watches, managed by a system service. Rather than being in libc, the API is
6//! exposed by the CoreServices framework. To avoid a compile dependency on the framework
7//! bundle, we dynamically load CoreServices with `std.DynLib`.
8//!
9//! While the logic in this file *is* specialized to `std.Build.Watch`, efforts have been
10//! made to keep that specialization to a minimum. Other use cases could be served with
11//! relatively minimal modifications to the `watch_paths` field and its usages (in
12//! particular the `setPaths` function). We avoid using the global GCD dispatch queue in
13//! favour of creating our own and synchronizing with an explicit semaphore, meaning this
14//! logic is thread-safe and does not affect process-global state.
15//!
16//! In theory, this API is quite good at avoiding filesystem race conditions. In practice,
17//! the logic that would avoid them is currently disabled, because the build system kind
18//! of relies on them at the time of writing to avoid redundant work -- see the comment at
19//! the top of `wait` for details.
20const FsEvents = @This();
21
22const enable_debug_logs = false;
23
24core_services: std.DynLib,
25resolved_symbols: ResolvedSymbols,
26
27paths_arena: std.heap.ArenaAllocator.State,
28/// The roots of the recursive watches. FSEvents has relatively small limits on the number
29/// of watched paths, so this slice must not be too long. The paths themselves are allocated
30/// into `paths_arena`, but this slice is allocated into the GPA.
31watch_roots: [][:0]const u8,
32/// All of the paths being watched. Value is the set of steps which depend on the file/directory.
33/// Keys and values are in `paths_arena`, but this map is allocated into the GPA.
34watch_paths: std.array_hash_map.String([]const std.Build.Configuration.Step.Index),
35
36/// The semaphore we use to block the thread calling `wait` until the callback determines a relevant
37/// event has occurred. This is retained across `wait` calls for simplicity and efficiency.
38waiting_semaphore: dispatch.semaphore_t,
39/// This dispatch queue is created by us and executes serially. It exists exclusively to trigger the
40/// callbacks of the FSEventStream we create. This is not in use outside of `wait`, but is retained
41/// across `wait` calls for simplicity and efficiency.
42dispatch_queue: dispatch.queue_t,
43/// In theory, this field avoids race conditions. In practice, it is essentially unused at the time
44/// of writing. See the comment at the start of `wait` for details.
45since_event: FSEventStreamEventId,
46
47cwd_path: []const u8,
48
49/// All of the symbols we pull from the `dlopen`ed CoreServices framework. If any of these symbols
50/// is not present, `init` will close the framework and return an error.
51const ResolvedSymbols = struct {
52 FSEventStreamCreate: *const fn (
53 allocator: CFAllocatorRef,
54 callback: FSEventStreamCallback,
55 ctx: ?*const FSEventStreamContext,
56 paths_to_watch: CFArrayRef,
57 since_when: FSEventStreamEventId,
58 latency: CFTimeInterval,
59 flags: FSEventStreamCreateFlags,
60 ) callconv(.c) FSEventStreamRef,
61 FSEventStreamSetDispatchQueue: *const fn (stream: FSEventStreamRef, queue: dispatch.queue_t) callconv(.c) void,
62 FSEventStreamStart: *const fn (stream: FSEventStreamRef) callconv(.c) bool,
63 FSEventStreamStop: *const fn (stream: FSEventStreamRef) callconv(.c) void,
64 FSEventStreamInvalidate: *const fn (stream: FSEventStreamRef) callconv(.c) void,
65 FSEventStreamRelease: *const fn (stream: FSEventStreamRef) callconv(.c) void,
66 FSEventStreamGetLatestEventId: *const fn (stream: ConstFSEventStreamRef) callconv(.c) FSEventStreamEventId,
67 FSEventsGetCurrentEventId: *const fn () callconv(.c) FSEventStreamEventId,
68 CFRelease: *const fn (cf: *const anyopaque) callconv(.c) void,
69 CFArrayCreate: *const fn (
70 allocator: CFAllocatorRef,
71 values: [*]const usize,
72 num_values: CFIndex,
73 call_backs: ?*const CFArrayCallBacks,
74 ) callconv(.c) CFArrayRef,
75 CFStringCreateWithCString: *const fn (
76 alloc: CFAllocatorRef,
77 c_str: [*:0]const u8,
78 encoding: CFStringEncoding,
79 ) callconv(.c) CFStringRef,
80 CFAllocatorCreate: *const fn (allocator: CFAllocatorRef, context: *const CFAllocatorContext) callconv(.c) CFAllocatorRef,
81 kCFAllocatorUseContext: *const CFAllocatorRef,
82};
83
84pub fn init(cwd_path: []const u8) error{ OpenFrameworkFailed, MissingCoreServicesSymbol, SystemResources }!FsEvents {
85 var core_services = std.DynLib.open("/System/Library/Frameworks/CoreServices.framework/CoreServices") catch
86 return error.OpenFrameworkFailed;
87 errdefer core_services.close();
88
89 var resolved_symbols: ResolvedSymbols = undefined;
90 inline for (@typeInfo(ResolvedSymbols).@"struct".fields) |f| {
91 @field(resolved_symbols, f.name) = core_services.lookup(f.type, f.name) orelse return error.MissingCoreServicesSymbol;
92 }
93
94 return .{
95 .core_services = core_services,
96 .resolved_symbols = resolved_symbols,
97 .paths_arena = .{},
98 .watch_roots = &.{},
99 .watch_paths = .empty,
100 .waiting_semaphore = dispatch.semaphore_create(0) orelse return error.SystemResources,
101 .dispatch_queue = dispatch.queue_create("zig-watch", .SERIAL()) orelse return error.SystemResources,
102 // Not `.since_now`, because this means we can init `FsEvents` *before* we do work in order
103 // to notice any changes which happened during said work.
104 .since_event = resolved_symbols.FSEventsGetCurrentEventId(),
105 .cwd_path = cwd_path,
106 };
107}
108
109pub fn deinit(fse: *FsEvents, gpa: Allocator, io: Io) void {
110 fse.waiting_semaphore.as_object().release();
111 fse.dispatch_queue.as_object().release();
112 fse.core_services.close(io);
113
114 gpa.free(fse.watch_roots);
115 fse.watch_paths.deinit(gpa);
116 {
117 var paths_arena = fse.paths_arena.promote(gpa);
118 paths_arena.deinit();
119 }
120}
121
122pub fn setPaths(fse: *FsEvents, maker: *Maker, steps: []const std.Build.Configuration.Step.Index) !void {
123 const gpa = maker.gpa;
124
125 var paths_arena_instance = fse.paths_arena.promote(gpa);
126 defer fse.paths_arena = paths_arena_instance.state;
127 const paths_arena = paths_arena_instance.allocator();
128
129 var need_dirs: std.array_hash_map.String(void) = .empty;
130 defer need_dirs.deinit(gpa);
131
132 fse.watch_paths.clearRetainingCapacity();
133
134 // We take `step_index` by pointer for a slight memory optimization in a moment.
135 for (steps) |*step_index| {
136 const step = maker.stepByIndex(step_index.*);
137 for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| {
138 const resolved_dir = try std.fs.path.resolvePosix(paths_arena, &.{
139 fse.cwd_path, path.root_dir.path orelse ".", path.sub_path,
140 });
141 try need_dirs.put(gpa, resolved_dir, {});
142 for (files.items) |file_name| {
143 const watch_path = if (std.mem.eql(u8, file_name, "."))
144 resolved_dir
145 else
146 try std.fs.path.join(paths_arena, &.{ resolved_dir, file_name });
147 const gop = try fse.watch_paths.getOrPut(gpa, watch_path);
148 if (gop.found_existing) {
149 const old_steps = gop.value_ptr.*;
150 const new_steps = try paths_arena.alloc(std.Build.Configuration.Step.Index, old_steps.len + 1);
151 @memcpy(new_steps[0..old_steps.len], old_steps);
152 new_steps[old_steps.len] = step_index.*;
153 gop.value_ptr.* = new_steps;
154 } else {
155 // This is why we captured `step` by pointer! We can avoid allocating a slice of one
156 // step in the arena in the common case where a file is referenced by only one step.
157 gop.value_ptr.* = step_index[0..1];
158 }
159 }
160 }
161 }
162
163 {
164 // There's no point looking at directories inside other ones (e.g. "/foo" and "/foo/bar").
165 // To eliminate these, we'll re-add directories in order of path length with a redundancy check.
166 const old_dirs = try gpa.dupe([]const u8, need_dirs.keys());
167 defer gpa.free(old_dirs);
168 std.mem.sort([]const u8, old_dirs, {}, struct {
169 fn lessThan(ctx: void, a: []const u8, b: []const u8) bool {
170 ctx;
171 return std.mem.lessThan(u8, a, b);
172 }
173 }.lessThan);
174 need_dirs.clearRetainingCapacity();
175 for (old_dirs) |dir_path| {
176 var it: std.fs.path.ComponentIterator(.posix, u8) = .init(dir_path);
177 while (it.next()) |component| {
178 if (need_dirs.contains(component.path)) {
179 // this path is '/foo/bar/qux', but '/foo' or '/foo/bar' was already added
180 break;
181 }
182 } else {
183 need_dirs.putAssumeCapacityNoClobber(dir_path, {});
184 }
185 }
186 }
187
188 // `need_dirs` is now a set of directories to watch with no redundancy. In practice, this is very
189 // likely to have reduced it to a quite small set (e.g. it'll typically coalesce a full `src/`
190 // directory into one entry). However, the FSEventStream API has a fairly low undocumented limit
191 // on total watches (supposedly 4096), so we should handle the case where we exceed it. To be
192 // safe, because this API can be a little unpredictable, we'll cap ourselves a little *below*
193 // that known limit.
194 if (need_dirs.count() > 2048) {
195 // Fallback: watch the whole filesystem. This is excessive, but... it *works* :P
196 if (enable_debug_logs) watch_log.debug("too many dirs; recursively watching root", .{});
197 fse.watch_roots = try gpa.realloc(fse.watch_roots, 1);
198 fse.watch_roots[0] = "/";
199 } else {
200 fse.watch_roots = try gpa.realloc(fse.watch_roots, need_dirs.count());
201 for (fse.watch_roots, need_dirs.keys()) |*out, in| {
202 out.* = try paths_arena.dupeSentinel(u8, in, 0);
203 }
204 }
205 if (enable_debug_logs) {
206 watch_log.debug("watching {d} paths using {d} recursive watches:", .{ fse.watch_paths.count(), fse.watch_roots.len });
207 for (fse.watch_roots) |dir_path| {
208 watch_log.debug("- '{s}'", .{dir_path});
209 }
210 }
211}
212
213pub fn wait(fse: *FsEvents, maker: *Maker, timeout_ns: ?u64) error{ OutOfMemory, StartFailed }!Watch.WaitResult {
214 if (fse.watch_roots.len == 0) @panic("nothing to watch");
215 const gpa = maker.gpa;
216
217 const rs = fse.resolved_symbols;
218
219 // At the time of writing, using `since_event` in the obvious way causes redundant rebuilds
220 // to occur, because one step modifies a file which is an input to another step. The solution
221 // to this problem will probably be either:
222 //
223 // a) Don't include the output of one step as a watch input of another; only mark external
224 // files as watch inputs. Or...
225 //
226 // b) Note the current event ID when a step begins, and disregard events preceding that ID
227 // when considering whether to dirty that step in `eventCallback`.
228 //
229 // For now, to avoid the redundant rebuilds, we bypass this `since_event` mechanism. This does
230 // introduce race conditions, but the other `std.Build.Watch` implementations suffer from those
231 // too at the time of writing, so this is kind of expected.
232 fse.since_event = .since_now;
233
234 const cf_allocator = rs.CFAllocatorCreate(rs.kCFAllocatorUseContext.*, &.{
235 .version = 0,
236 .info = @constCast(&gpa),
237 .retain = null,
238 .release = null,
239 .copy_description = null,
240 .allocate = &cf_alloc_callbacks.allocate,
241 .reallocate = &cf_alloc_callbacks.reallocate,
242 .deallocate = &cf_alloc_callbacks.deallocate,
243 .preferred_size = null,
244 }) orelse return error.OutOfMemory;
245 defer rs.CFRelease(cf_allocator);
246
247 const cf_paths = try gpa.alloc(?CFStringRef, fse.watch_roots.len);
248 @memset(cf_paths, null);
249 defer {
250 for (cf_paths) |o| if (o) |p| rs.CFRelease(p);
251 gpa.free(cf_paths);
252 }
253 for (fse.watch_roots, cf_paths) |raw_path, *cf_path| {
254 cf_path.* = rs.CFStringCreateWithCString(cf_allocator, raw_path, .utf8);
255 }
256 const cf_paths_array = rs.CFArrayCreate(cf_allocator, @ptrCast(cf_paths), @intCast(cf_paths.len), null);
257 defer rs.CFRelease(cf_paths_array);
258
259 const callback_ctx: EventCallbackCtx = .{
260 .fse = fse,
261 .maker = maker,
262 };
263 const event_stream = rs.FSEventStreamCreate(
264 null,
265 &eventCallback,
266 &.{
267 .version = 0,
268 .info = @constCast(&callback_ctx),
269 .retain = null,
270 .release = null,
271 .copy_description = null,
272 },
273 cf_paths_array,
274 fse.since_event,
275 0.05, // 0.05s latency; higher values increase efficiency by coalescing more events
276 .{ .watch_root = true, .file_events = true },
277 );
278 defer rs.FSEventStreamRelease(event_stream);
279 rs.FSEventStreamSetDispatchQueue(event_stream, fse.dispatch_queue);
280 defer rs.FSEventStreamInvalidate(event_stream);
281 if (!rs.FSEventStreamStart(event_stream)) return error.StartFailed;
282 defer rs.FSEventStreamStop(event_stream);
283 const result = fse.waiting_semaphore.wait(timeout: {
284 const ns = timeout_ns orelse break :timeout .FOREVER;
285 break :timeout .time(.NOW, @intCast(ns));
286 });
287 return switch (result) {
288 0 => .dirty,
289 else => .timeout,
290 };
291}
292
293const cf_alloc_callbacks = struct {
294 const log = std.log.scoped(.cf_alloc);
295 fn allocate(size: CFIndex, hint: CFOptionFlags, info: ?*const anyopaque) callconv(.c) ?*const anyopaque {
296 if (enable_debug_logs) log.debug("allocate {d}", .{size});
297 _ = hint;
298 const gpa: *const Allocator = @ptrCast(@alignCast(info));
299 const mem = gpa.alignedAlloc(u8, .of(usize), @intCast(size + @sizeOf(usize))) catch return null;
300 const metadata: *usize = @ptrCast(mem);
301 metadata.* = @intCast(size);
302 return mem[@sizeOf(usize)..].ptr;
303 }
304 fn reallocate(ptr: ?*anyopaque, new_size: CFIndex, hint: CFOptionFlags, info: ?*const anyopaque) callconv(.c) ?*const anyopaque {
305 if (enable_debug_logs) log.debug("reallocate @{*} {d}", .{ ptr, new_size });
306 _ = hint;
307 if (ptr == null or new_size == 0) return null; // not a bug: documentation explicitly states that realloc on NULL should return NULL
308 const gpa: *const Allocator = @ptrCast(@alignCast(info));
309 const old_base: [*]align(@alignOf(usize)) u8 = @alignCast(@as([*]u8, @ptrCast(ptr)) - @sizeOf(usize));
310 const old_size = @as(*const usize, @ptrCast(old_base)).*;
311 const old_mem = old_base[0 .. old_size + @sizeOf(usize)];
312 const new_mem = gpa.realloc(old_mem, @intCast(new_size + @sizeOf(usize))) catch return null;
313 const metadata: *usize = @ptrCast(new_mem);
314 metadata.* = @intCast(new_size);
315 return new_mem[@sizeOf(usize)..].ptr;
316 }
317 fn deallocate(ptr: *anyopaque, info: ?*const anyopaque) callconv(.c) void {
318 if (enable_debug_logs) log.debug("deallocate @{*}", .{ptr});
319 const gpa: *const Allocator = @ptrCast(@alignCast(info));
320 const old_base: [*]align(@alignOf(usize)) u8 = @alignCast(@as([*]u8, @ptrCast(ptr)) - @sizeOf(usize));
321 const old_size = @as(*const usize, @ptrCast(old_base)).*;
322 const old_mem = old_base[0 .. old_size + @sizeOf(usize)];
323 gpa.free(old_mem);
324 }
325};
326
327const EventCallbackCtx = struct {
328 fse: *FsEvents,
329 maker: *Maker,
330};
331
332fn eventCallback(
333 stream: ConstFSEventStreamRef,
334 client_callback_info: ?*anyopaque,
335 num_events: usize,
336 events_paths_ptr: *anyopaque,
337 events_flags_ptr: [*]const FSEventStreamEventFlags,
338 events_ids_ptr: [*]const FSEventStreamEventId,
339) callconv(.c) void {
340 const ctx: *const EventCallbackCtx = @ptrCast(@alignCast(client_callback_info));
341 const maker = ctx.maker;
342 const fse = ctx.fse;
343 const rs = fse.resolved_symbols;
344 const events_paths_ptr_casted: [*]const [*:0]const u8 = @ptrCast(@alignCast(events_paths_ptr));
345 const events_paths = events_paths_ptr_casted[0..num_events];
346 const events_ids = events_ids_ptr[0..num_events];
347 const events_flags = events_flags_ptr[0..num_events];
348 var any_dirty = false;
349 for (events_paths, events_ids, events_flags) |event_path_nts, event_id, event_flags| {
350 _ = event_id;
351 if (event_flags.history_done) continue; // sentinel
352 const event_path = std.mem.span(event_path_nts);
353 switch (event_flags.must_scan_sub_dirs) {
354 false => {
355 if (fse.watch_paths.get(event_path)) |steps| {
356 assert(steps.len > 0);
357 if (invalidateSteps(maker, steps)) any_dirty = true;
358 }
359 if (std.fs.path.dirname(event_path)) |event_dirname| {
360 // Modifying '/foo/bar' triggers the watch on '/foo'.
361 if (fse.watch_paths.get(event_dirname)) |steps| {
362 assert(steps.len > 0);
363 if (invalidateSteps(maker, steps)) any_dirty = true;
364 }
365 }
366 },
367 true => {
368 // This is unlikely, but can occasionally happen when bottlenecked: events have been
369 // coalesced into one. We want to see if any of these events are actually relevant
370 // to us. The only way we can reasonably do that in this rare edge case is iterate
371 // the watch paths and see if any is under this directory. That's acceptable because
372 // we would otherwise kick off a rebuild which would be clearing those paths anyway.
373 const changed_path = std.fs.path.dirname(event_path) orelse event_path;
374 for (fse.watch_paths.keys(), fse.watch_paths.values()) |watching_path, steps| {
375 if (dirStartsWith(watching_path, changed_path)) {
376 if (invalidateSteps(maker, steps)) any_dirty = true;
377 }
378 }
379 },
380 }
381 }
382 if (any_dirty) {
383 fse.since_event = rs.FSEventStreamGetLatestEventId(stream);
384 _ = fse.waiting_semaphore.signal();
385 }
386}
387fn dirStartsWith(path: []const u8, prefix: []const u8) bool {
388 if (std.mem.eql(u8, path, prefix)) return true;
389 if (!std.mem.startsWith(u8, path, prefix)) return false;
390 if (path[prefix.len] != '/') return false; // `path` is `/foo/barx`, `prefix` is `/foo/bar`
391 return true; // `path` is `/foo/bar/...`, `prefix` is `/foo/bar`
392}
393
394fn invalidateSteps(maker: *Maker, steps: []const std.Build.Configuration.Step.Index) bool {
395 var any_dirty = false;
396 for (steps) |step_index| {
397 const step = maker.stepByIndex(step_index);
398 if (maker.invalidateResult(step)) any_dirty = true;
399 }
400 return any_dirty;
401}
402
403const CFAllocatorRef = ?*const opaque {};
404const CFArrayRef = *const opaque {};
405const CFStringRef = *const opaque {};
406const CFTimeInterval = f64;
407const CFIndex = i32;
408const CFOptionFlags = enum(u32) { _ };
409const CFAllocatorRetainCallBack = *const fn (info: ?*const anyopaque) callconv(.c) *const anyopaque;
410const CFAllocatorReleaseCallBack = *const fn (info: ?*const anyopaque) callconv(.c) void;
411const CFAllocatorCopyDescriptionCallBack = *const fn (info: ?*const anyopaque) callconv(.c) CFStringRef;
412const CFAllocatorAllocateCallBack = *const fn (alloc_size: CFIndex, hint: CFOptionFlags, info: ?*const anyopaque) callconv(.c) ?*const anyopaque;
413const CFAllocatorReallocateCallBack = *const fn (ptr: ?*anyopaque, new_size: CFIndex, hint: CFOptionFlags, info: ?*const anyopaque) callconv(.c) ?*const anyopaque;
414const CFAllocatorDeallocateCallBack = *const fn (ptr: *anyopaque, info: ?*const anyopaque) callconv(.c) void;
415const CFAllocatorPreferredSizeCallBack = *const fn (size: CFIndex, hint: CFOptionFlags, info: ?*const anyopaque) callconv(.c) CFIndex;
416const CFAllocatorContext = extern struct {
417 version: CFIndex,
418 info: ?*anyopaque,
419 retain: ?CFAllocatorRetainCallBack,
420 release: ?CFAllocatorReleaseCallBack,
421 copy_description: ?CFAllocatorCopyDescriptionCallBack,
422 allocate: CFAllocatorAllocateCallBack,
423 reallocate: ?CFAllocatorReallocateCallBack,
424 deallocate: ?CFAllocatorDeallocateCallBack,
425 preferred_size: ?CFAllocatorPreferredSizeCallBack,
426};
427const CFArrayCallBacks = opaque {};
428const CFStringEncoding = enum(u32) {
429 invalid_id = std.math.maxInt(u32),
430 mac_roman = 0,
431 windows_latin_1 = 0x500,
432 iso_latin_1 = 0x201,
433 next_step_latin = 0xB01,
434 ascii = 0x600,
435 unicode = 0x100,
436 utf8 = 0x8000100,
437 non_lossy_ascii = 0xBFF,
438};
439
440const FSEventStreamRef = *opaque {};
441const ConstFSEventStreamRef = *const @typeInfo(FSEventStreamRef).pointer.child;
442const FSEventStreamCallback = *const fn (
443 stream: ConstFSEventStreamRef,
444 client_callback_info: ?*anyopaque,
445 num_events: usize,
446 event_paths: *anyopaque,
447 event_flags: [*]const FSEventStreamEventFlags,
448 event_ids: [*]const FSEventStreamEventId,
449) callconv(.c) void;
450const FSEventStreamContext = extern struct {
451 version: CFIndex,
452 info: ?*anyopaque,
453 retain: ?CFAllocatorRetainCallBack,
454 release: ?CFAllocatorReleaseCallBack,
455 copy_description: ?CFAllocatorCopyDescriptionCallBack,
456};
457const FSEventStreamEventId = enum(u64) {
458 since_now = std.math.maxInt(u64),
459 _,
460};
461const FSEventStreamCreateFlags = packed struct(u32) {
462 use_cf_types: bool = false,
463 no_defer: bool = false,
464 watch_root: bool = false,
465 ignore_self: bool = false,
466 file_events: bool = false,
467 _: u27 = 0,
468};
469const FSEventStreamEventFlags = packed struct(u32) {
470 must_scan_sub_dirs: bool,
471 user_dropped: bool,
472 kernel_dropped: bool,
473 event_ids_wrapped: bool,
474 history_done: bool,
475 root_changed: bool,
476 mount: bool,
477 unmount: bool,
478 _: u24 = 0,
479};
480
481const dispatch = std.c.dispatch;
482const std = @import("std");
483const Io = std.Io;
484const assert = std.debug.assert;
485const Allocator = std.mem.Allocator;
486const watch_log = std.log.scoped(.watch);
487const Maker = @import("../../Maker.zig");
488const Watch = @import("../Watch.zig");
lib/compiler/Maker/WebServer.zig created+953
......@@ -0,0 +1,953 @@
1const WebServer = @This();
2
3const builtin = @import("builtin");
4
5const std = @import("std");
6const Allocator = std.mem.Allocator;
7const Cache = std.Build.Cache;
8const Configuration = std.Build.Configuration;
9const Io = std.Io;
10const abi = std.Build.abi;
11const assert = std.debug.assert;
12const http = std.http;
13const log = std.log.scoped(.web_server);
14const mem = std.mem;
15const net = std.Io.net;
16
17const Maker = @import("../Maker.zig");
18const Fuzz = @import("Fuzz.zig");
19const Graph = @import("Graph.zig");
20const Step = @import("Step.zig");
21
22maker: *Maker,
23listen_address: net.IpAddress,
24root_prog_node: std.Progress.Node,
25
26tcp_server: ?net.Server,
27serve_task: ?Io.Future(Io.Cancelable!void),
28
29/// Uses `Io.Clock.awake`.
30base_timestamp: Io.Timestamp,
31/// The "step name" data which trails `abi.Hello`, for the steps in `all_steps`.
32step_names_trailing: []u8,
33
34/// The bit-packed "step status" data. Values are `abi.StepUpdate.Status`. LSBs are earlier steps.
35/// Accessed atomically.
36step_status_bits: []u8,
37
38fuzz: ?Fuzz,
39time_report_mutex: Io.Mutex,
40time_report_msgs: [][]u8,
41time_report_update_times: []i64,
42
43build_status: std.atomic.Value(abi.BuildStatus),
44/// When an event occurs which means WebSocket clients should be sent updates, call `notifyUpdate`
45/// to increment this value. Each client thread waits for this increment with `Io.futexWaitTimeout`, so
46/// `notifyUpdate` will wake those threads. Updates are sent on a short interval regardless, so it
47/// is recommended to only use `notifyUpdate` for changes which the user should see immediately. For
48/// instance, we do not call `notifyUpdate` when the number of "unique runs" in the fuzzer changes,
49/// because this value changes quickly so this would result in constantly spamming all clients with
50/// an unreasonable number of packets.
51update_id: std.atomic.Value(u32),
52
53runner_request_mutex: Io.Mutex,
54runner_request_ready_cond: Io.Condition,
55runner_request_empty_cond: Io.Condition,
56runner_request: ?RunnerRequest,
57
58/// If a client is not explicitly notified of changes with `notifyUpdate`, it will be sent updates
59/// on a fixed interval of this many milliseconds.
60const default_update_interval_ms = 500;
61
62pub const base_clock: Io.Clock = .awake;
63
64/// Thread-safe. Triggers updates to be sent to connected WebSocket clients; see `update_id`.
65pub fn notifyUpdate(ws: *WebServer) void {
66 const io = ws.maker.graph.io;
67 _ = ws.update_id.rmw(.Add, 1, .release);
68 io.futexWake(u32, &ws.update_id.raw, 16);
69}
70
71pub const Options = struct {
72 maker: *Maker,
73 root_prog_node: std.Progress.Node,
74 listen_address: net.IpAddress,
75 base_timestamp: Io.Clock.Timestamp,
76};
77pub fn init(opts: Options) WebServer {
78 // The upcoming `Io` interface should allow us to use `Io.async` and `Io.concurrent`
79 // instead of threads, so that the web server can function in single-threaded builds.
80 comptime assert(!builtin.single_threaded);
81 assert(opts.base_timestamp.clock == base_clock);
82
83 const maker = opts.maker;
84 const all_steps = maker.step_stack.keys();
85 const c = &maker.scanned_config.configuration;
86 const gpa = maker.gpa;
87 const graph = maker.graph;
88
89 const step_names_trailing = gpa.alloc(u8, len: {
90 var name_bytes: usize = 0;
91 for (all_steps) |step_index| name_bytes += step_index.ptr(c).name.slice(c).len;
92 break :len name_bytes + all_steps.len * 4;
93 }) catch @panic("out of memory");
94 {
95 const step_name_lens: []align(1) u32 = @ptrCast(step_names_trailing[0 .. all_steps.len * 4]);
96 var idx: usize = all_steps.len * 4;
97 for (all_steps, step_name_lens) |step_index, *name_len| {
98 const step_name = step_index.ptr(c).name.slice(c);
99 name_len.* = @intCast(step_name.len);
100 @memcpy(step_names_trailing[idx..][0..step_name.len], step_name);
101 idx += step_name.len;
102 }
103 assert(idx == step_names_trailing.len);
104 }
105
106 const step_status_bits = gpa.alloc(
107 u8,
108 std.math.divCeil(usize, all_steps.len, 4) catch unreachable,
109 ) catch @panic("out of memory");
110 @memset(step_status_bits, 0);
111
112 const time_reports_len: usize = if (graph.time_report) all_steps.len else 0;
113 const time_report_msgs = gpa.alloc([]u8, time_reports_len) catch @panic("out of memory");
114 const time_report_update_times = gpa.alloc(i64, time_reports_len) catch @panic("out of memory");
115 @memset(time_report_msgs, &.{});
116 @memset(time_report_update_times, std.math.minInt(i64));
117
118 return .{
119 .maker = maker,
120 .listen_address = opts.listen_address,
121 .root_prog_node = opts.root_prog_node,
122
123 .tcp_server = null,
124 .serve_task = null,
125
126 .base_timestamp = opts.base_timestamp.raw,
127 .step_names_trailing = step_names_trailing,
128
129 .step_status_bits = step_status_bits,
130
131 .fuzz = null,
132 .time_report_mutex = .init,
133 .time_report_msgs = time_report_msgs,
134 .time_report_update_times = time_report_update_times,
135
136 .build_status = .init(.idle),
137 .update_id = .init(0),
138
139 .runner_request_mutex = .init,
140 .runner_request_ready_cond = .init,
141 .runner_request_empty_cond = .init,
142 .runner_request = null,
143 };
144}
145pub fn deinit(ws: *WebServer) void {
146 const maker = ws.maker;
147 const gpa = maker.gpa;
148 const io = maker.graph.io;
149
150 gpa.free(ws.step_names_trailing);
151 gpa.free(ws.step_status_bits);
152
153 if (ws.fuzz) |*f| f.deinit();
154 for (ws.time_report_msgs) |msg| gpa.free(msg);
155 gpa.free(ws.time_report_msgs);
156 gpa.free(ws.time_report_update_times);
157
158 if (ws.serve_task) |t| {
159 if (ws.tcp_server) |*s| s.stream.close(io);
160 t.await();
161 }
162 if (ws.tcp_server) |*s| s.deinit();
163
164 gpa.free(ws.step_names_trailing);
165}
166pub fn start(ws: *WebServer) error{AlreadyReported}!void {
167 assert(ws.tcp_server == null);
168 assert(ws.serve_task == null);
169 const maker = ws.maker;
170 const io = maker.graph.io;
171
172 ws.tcp_server = ws.listen_address.listen(io, .{ .reuse_address = true }) catch |err| {
173 log.err("failed to listen to port {d}: {t}", .{ ws.listen_address.getPort(), err });
174 return error.AlreadyReported;
175 };
176 ws.serve_task = io.concurrent(serve, .{ws}) catch |err| {
177 log.err("unable to spawn web server thread: {t}", .{err});
178 ws.tcp_server.?.deinit(io);
179 ws.tcp_server = null;
180 return error.AlreadyReported;
181 };
182
183 log.info("web interface listening at http://{f}/", .{ws.tcp_server.?.socket.address});
184 if (ws.listen_address.getPort() == 0) {
185 log.info("hint: pass '--webui={f}' to use the same port next time", .{ws.tcp_server.?.socket.address});
186 }
187}
188fn serve(ws: *WebServer) Io.Cancelable!void {
189 const maker = ws.maker;
190 const io = maker.graph.io;
191
192 var group: Io.Group = .init;
193 defer group.cancel(io);
194
195 while (true) {
196 var stream = ws.tcp_server.?.accept(io) catch |err| switch (err) {
197 error.Canceled => |e| return e,
198 else => |e| {
199 log.err("failed to accept connection: {t}", .{e});
200 return;
201 },
202 };
203 group.concurrent(io, accept, .{ ws, stream }) catch |err| {
204 log.err("unable to spawn connection thread: {t}", .{err});
205 stream.close(io);
206 continue;
207 };
208 }
209}
210
211pub fn startBuild(ws: *WebServer) void {
212 if (ws.fuzz) |*fuzz| {
213 fuzz.deinit();
214 ws.fuzz = null;
215 }
216 for (ws.step_status_bits) |*bits| @atomicStore(u8, bits, 0, .monotonic);
217 ws.build_status.store(.running, .monotonic);
218 ws.notifyUpdate();
219}
220
221pub fn updateStepStatus(
222 ws: *WebServer,
223 step_index: Configuration.Step.Index,
224 new_status: abi.StepUpdate.Status,
225) void {
226 const maker = ws.maker;
227 const all_steps = maker.step_stack.keys();
228 const step_idx: u32 = for (all_steps, 0..) |s, i| {
229 if (s == step_index) break @intCast(i);
230 } else unreachable;
231 const ptr = &ws.step_status_bits[step_idx / 4];
232 const bit_offset: u3 = @intCast((step_idx % 4) * 2);
233 const old_bits: u2 = @truncate(@atomicLoad(u8, ptr, .monotonic) >> bit_offset);
234 const mask = @as(u8, @intFromEnum(new_status) ^ old_bits) << bit_offset;
235 _ = @atomicRmw(u8, ptr, .Xor, mask, .monotonic);
236 ws.notifyUpdate();
237}
238
239pub fn finishBuild(ws: *WebServer, opts: struct {
240 fuzz: bool,
241}) void {
242 const maker = ws.maker;
243 const all_steps = maker.step_stack.keys();
244
245 if (opts.fuzz) {
246 switch (builtin.os.tag) {
247 // Current implementation depends on two things that need to be ported to Windows:
248 // * Memory-mapping to share data between the fuzzer and build runner.
249 // * COFF/PE support added to `std.debug.Info` (it needs a batching API for resolving
250 // many addresses to source locations).
251 .windows => std.process.fatal("--fuzz not yet implemented for {t}", .{builtin.os.tag}),
252 else => {},
253 }
254 if (@bitSizeOf(usize) != 64) {
255 // Current implementation depends on posix.mmap()'s second
256 // parameter, `length: usize`, being compatible with file system's
257 // u64 return value. This is not the case on 32-bit platforms.
258 // Affects or affected by issues #5185, #22523, and #22464.
259 std.process.fatal("--fuzz not yet implemented on {d}-bit platforms", .{@bitSizeOf(usize)});
260 }
261
262 assert(ws.fuzz == null);
263
264 ws.build_status.store(.fuzz_init, .monotonic);
265 ws.notifyUpdate();
266
267 ws.fuzz = Fuzz.init(maker, all_steps, ws.root_prog_node, .{ .forever = .{ .ws = ws } }) catch |err|
268 std.process.fatal("failed to start fuzzer: {t}", .{err});
269 ws.fuzz.?.start();
270 }
271
272 ws.build_status.store(if (maker.watch) .watching else .idle, .monotonic);
273 ws.notifyUpdate();
274}
275
276pub fn now(ws: *const WebServer) i64 {
277 const maker = ws.maker;
278 const io = maker.graph.io;
279 const ts = base_clock.now(io);
280 return @intCast(ws.base_timestamp.durationTo(ts).toNanoseconds());
281}
282
283fn accept(ws: *WebServer, stream: net.Stream) void {
284 const maker = ws.maker;
285 const io = maker.graph.io;
286
287 defer {
288 // `net.Stream.close` wants to helpfully overwrite `stream` with
289 // `undefined`, but it cannot do so since it is an immutable parameter.
290 var copy = stream;
291 copy.close(io);
292 }
293 var send_buffer: [4096]u8 = undefined;
294 var recv_buffer: [4096]u8 = undefined;
295 var connection_reader = stream.reader(io, &recv_buffer);
296 var connection_writer = stream.writer(io, &send_buffer);
297 var server: http.Server = .init(&connection_reader.interface, &connection_writer.interface);
298
299 while (true) {
300 var request = server.receiveHead() catch |err| switch (err) {
301 error.HttpConnectionClosing => return,
302 else => return log.err("failed to receive http request: {t}", .{err}),
303 };
304 switch (request.upgradeRequested()) {
305 .websocket => |opt_key| {
306 const key = opt_key orelse return log.err("missing websocket key", .{});
307 var web_socket = request.respondWebSocket(.{ .key = key }) catch {
308 return log.err("failed to respond web socket: {t}", .{connection_writer.err.?});
309 };
310 ws.serveWebSocket(&web_socket) catch |err| {
311 log.err("failed to serve websocket: {t}", .{err});
312 return;
313 };
314 comptime unreachable;
315 },
316 .other => |name| return log.err("unknown upgrade request: {s}", .{name}),
317 .none => {
318 ws.serveRequest(&request) catch |err| switch (err) {
319 error.AlreadyReported => return,
320 else => {
321 log.err("failed to serve '{s}': {t}", .{ request.head.target, err });
322 return;
323 },
324 };
325 },
326 }
327 }
328}
329
330fn serveWebSocket(ws: *WebServer, sock: *http.Server.WebSocket) !noreturn {
331 const maker = ws.maker;
332 const gpa = maker.gpa;
333 const graph = maker.graph;
334 const io = graph.io;
335 const all_steps = maker.step_stack.keys();
336
337 var prev_build_status = ws.build_status.load(.monotonic);
338
339 const prev_step_status_bits = try gpa.alloc(u8, ws.step_status_bits.len);
340 defer gpa.free(prev_step_status_bits);
341 for (prev_step_status_bits, ws.step_status_bits) |*copy, *shared| {
342 copy.* = @atomicLoad(u8, shared, .monotonic);
343 }
344
345 var recv_thread = try io.concurrent(recvWebSocketMessages, .{ ws, sock });
346 defer recv_thread.cancel(io);
347
348 {
349 const hello_header: abi.Hello = .{
350 .status = prev_build_status,
351 .flags = .{
352 .time_report = graph.time_report,
353 },
354 .timestamp = ws.now(),
355 .steps_len = @intCast(all_steps.len),
356 };
357 var bufs: [3][]const u8 = .{ @ptrCast(&hello_header), ws.step_names_trailing, prev_step_status_bits };
358 try sock.writeMessageVec(&bufs, .binary);
359 }
360
361 var prev_fuzz: Fuzz.Previous = .init;
362 var prev_time: i64 = std.math.minInt(i64);
363 while (true) {
364 const start_time = ws.now();
365 const start_update_id = ws.update_id.load(.acquire);
366
367 if (ws.fuzz) |*fuzz| {
368 try fuzz.sendUpdate(sock, &prev_fuzz);
369 }
370
371 {
372 try ws.time_report_mutex.lock(io);
373 defer ws.time_report_mutex.unlock(io);
374 for (ws.time_report_msgs, ws.time_report_update_times) |msg, update_time| {
375 if (update_time <= prev_time) continue;
376 // We want to send `msg`, but shouldn't block `ws.time_report_mutex` while we do, so
377 // that we don't hold up the build system on the client accepting this packet.
378 const owned_msg = try gpa.dupe(u8, msg);
379 defer gpa.free(owned_msg);
380 // Temporarily unlock, then re-lock after the message is sent.
381 ws.time_report_mutex.unlock(io);
382 defer ws.time_report_mutex.lockUncancelable(io);
383 try sock.writeMessage(owned_msg, .binary);
384 }
385 }
386
387 {
388 const build_status = ws.build_status.load(.monotonic);
389 if (build_status != prev_build_status) {
390 prev_build_status = build_status;
391 const msg: abi.StatusUpdate = .{ .new = build_status };
392 try sock.writeMessage(@ptrCast(&msg), .binary);
393 }
394 }
395
396 for (prev_step_status_bits, ws.step_status_bits, 0..) |*prev_byte, *shared, byte_idx| {
397 const cur_byte = @atomicLoad(u8, shared, .monotonic);
398 if (prev_byte.* == cur_byte) continue;
399 const cur: [4]abi.StepUpdate.Status = .{
400 @enumFromInt(@as(u2, @truncate(cur_byte >> 0))),
401 @enumFromInt(@as(u2, @truncate(cur_byte >> 2))),
402 @enumFromInt(@as(u2, @truncate(cur_byte >> 4))),
403 @enumFromInt(@as(u2, @truncate(cur_byte >> 6))),
404 };
405 const prev: [4]abi.StepUpdate.Status = .{
406 @enumFromInt(@as(u2, @truncate(prev_byte.* >> 0))),
407 @enumFromInt(@as(u2, @truncate(prev_byte.* >> 2))),
408 @enumFromInt(@as(u2, @truncate(prev_byte.* >> 4))),
409 @enumFromInt(@as(u2, @truncate(prev_byte.* >> 6))),
410 };
411 for (cur, prev, byte_idx * 4..) |cur_status, prev_status, step_idx| {
412 const msg: abi.StepUpdate = .{ .step_idx = @intCast(step_idx), .bits = .{ .status = cur_status } };
413 if (cur_status != prev_status) try sock.writeMessage(@ptrCast(&msg), .binary);
414 }
415 prev_byte.* = cur_byte;
416 }
417
418 prev_time = start_time;
419
420 const old_cp = io.swapCancelProtection(.blocked);
421 defer _ = io.swapCancelProtection(old_cp);
422 io.futexWaitTimeout(
423 u32,
424 &ws.update_id.raw,
425 start_update_id,
426 .{ .duration = .{
427 .clock = .awake,
428 .raw = .fromMilliseconds(default_update_interval_ms),
429 } },
430 ) catch |err| switch (err) {
431 error.Canceled => unreachable,
432 };
433 }
434}
435fn recvWebSocketMessages(ws: *WebServer, sock: *http.Server.WebSocket) void {
436 const maker = ws.maker;
437 const io = maker.graph.io;
438
439 while (true) {
440 const msg = sock.readSmallMessage() catch return;
441 if (msg.opcode != .binary) continue;
442 if (msg.data.len == 0) continue;
443 const tag: abi.ToServerTag = @enumFromInt(msg.data[0]);
444 switch (tag) {
445 _ => continue,
446 .rebuild => while (true) {
447 ws.runner_request_mutex.lock(io) catch |err| switch (err) {
448 error.Canceled => return,
449 };
450 defer ws.runner_request_mutex.unlock(io);
451 if (ws.runner_request == null) {
452 ws.runner_request = .rebuild;
453 ws.runner_request_ready_cond.signal(io);
454 break;
455 }
456 ws.runner_request_empty_cond.wait(io, &ws.runner_request_mutex) catch return;
457 },
458 }
459 }
460}
461
462fn serveRequest(ws: *WebServer, req: *http.Server.Request) !void {
463 // Strip an optional leading '/debug' component from the request.
464 const target: []const u8, const debug: bool = target: {
465 if (mem.eql(u8, req.head.target, "/debug")) break :target .{ "/", true };
466 if (mem.eql(u8, req.head.target, "/debug/")) break :target .{ "/", true };
467 if (mem.startsWith(u8, req.head.target, "/debug/")) break :target .{ req.head.target["/debug".len..], true };
468 break :target .{ req.head.target, false };
469 };
470
471 if (mem.eql(u8, target, "/")) return serveLibFile(ws, req, "build-web/index.html", "text/html");
472 if (mem.eql(u8, target, "/main.js")) return serveLibFile(ws, req, "build-web/main.js", "application/javascript");
473 if (mem.eql(u8, target, "/style.css")) return serveLibFile(ws, req, "build-web/style.css", "text/css");
474 if (mem.eql(u8, target, "/time_report.css")) return serveLibFile(ws, req, "build-web/time_report.css", "text/css");
475 if (mem.eql(u8, target, "/main.wasm")) return serveClientWasm(ws, req, if (debug) .Debug else .ReleaseFast);
476
477 if (ws.fuzz) |*fuzz| {
478 if (mem.eql(u8, target, "/sources.tar")) return fuzz.serveSourcesTar(req);
479 }
480
481 try req.respond("not found", .{
482 .status = .not_found,
483 .extra_headers = &.{
484 .{ .name = "Content-Type", .value = "text/plain" },
485 },
486 });
487}
488
489fn serveLibFile(
490 ws: *WebServer,
491 request: *http.Server.Request,
492 sub_path: []const u8,
493 content_type: []const u8,
494) !void {
495 const maker = ws.maker;
496 const graph = maker.graph;
497
498 return serveFile(ws, request, .{
499 .root_dir = graph.zig_lib_directory,
500 .sub_path = sub_path,
501 }, content_type);
502}
503fn serveClientWasm(
504 ws: *WebServer,
505 req: *http.Server.Request,
506 optimize_mode: std.builtin.OptimizeMode,
507) !void {
508 const gpa = ws.maker.gpa;
509
510 var arena_state: std.heap.ArenaAllocator = .init(gpa);
511 defer arena_state.deinit();
512 const arena = arena_state.allocator();
513
514 // We always rebuild the wasm on-the-fly, so that if it is edited the user can just refresh the page.
515 const bin_path = try buildClientWasm(ws, arena, optimize_mode);
516 return serveFile(ws, req, bin_path, "application/wasm");
517}
518
519pub fn serveFile(
520 ws: *WebServer,
521 request: *http.Server.Request,
522 path: Cache.Path,
523 content_type: []const u8,
524) !void {
525 const maker = ws.maker;
526 const gpa = ws.maker.gpa;
527 const io = maker.graph.io;
528
529 // The desired API is actually sendfile, which will require enhancing http.Server.
530 // We load the file with every request so that the user can make changes to the file
531 // and refresh the HTML page without restarting this server.
532 const file_contents = path.root_dir.handle.readFileAlloc(io, path.sub_path, gpa, .limited(10 * 1024 * 1024)) catch |err| {
533 log.err("failed to read '{f}': {t}", .{ path, err });
534 return error.AlreadyReported;
535 };
536 defer gpa.free(file_contents);
537 try request.respond(file_contents, .{
538 .extra_headers = &.{
539 .{ .name = "Content-Type", .value = content_type },
540 cache_control_header,
541 },
542 });
543}
544pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []const Cache.Path) !void {
545 const maker = ws.maker;
546 const graph = maker.graph;
547 const io = graph.io;
548
549 var send_buffer: [0x4000]u8 = undefined;
550 var response = try request.respondStreaming(&send_buffer, .{
551 .respond_options = .{
552 .extra_headers = &.{
553 .{ .name = "Content-Type", .value = "application/x-tar" },
554 cache_control_header,
555 },
556 },
557 });
558
559 var archiver: std.tar.Writer = .{ .underlying_writer = &response.writer };
560
561 for (paths) |path| {
562 var file = path.root_dir.handle.openFile(io, path.sub_path, .{}) catch |err| {
563 log.err("failed to open '{f}': {s}", .{ path, @errorName(err) });
564 continue;
565 };
566 defer file.close(io);
567 const stat = try file.stat(io);
568 var read_buffer: [1024]u8 = undefined;
569 var file_reader: Io.File.Reader = .initSize(file, io, &read_buffer, stat.size);
570
571 archiver.prefix = path.root_dir.path orelse graph.cache.cwd;
572 try archiver.writeFile(path.sub_path, &file_reader, @intCast(stat.mtime.toSeconds()));
573 }
574
575 // intentionally not calling `archiver.finishPedantically`
576 try response.end();
577}
578
579fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.OptimizeMode) !Cache.Path {
580 const root_name = "build-web";
581 const arch_os_abi = "wasm32-freestanding";
582 const cpu_features = "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext";
583
584 const maker = ws.maker;
585 const gpa = maker.gpa;
586 const graph = maker.graph;
587 const io = graph.io;
588
589 const main_src_path: Cache.Path = .{
590 .root_dir = graph.zig_lib_directory,
591 .sub_path = "build-web/main.zig",
592 };
593 const walk_src_path: Cache.Path = .{
594 .root_dir = graph.zig_lib_directory,
595 .sub_path = "docs/wasm/Walk.zig",
596 };
597 const html_render_src_path: Cache.Path = .{
598 .root_dir = graph.zig_lib_directory,
599 .sub_path = "docs/wasm/html_render.zig",
600 };
601
602 var argv: std.ArrayList([]const u8) = .empty;
603
604 try argv.appendSlice(arena, &.{
605 graph.zig_exe, "build-exe", //
606 "-fno-entry", //
607 "-O", @tagName(optimize), //
608 "-target", arch_os_abi, //
609 "-mcpu", cpu_features, //
610 "--cache-dir", graph.global_cache_root.path orelse ".", //
611 "--global-cache-dir", graph.global_cache_root.path orelse ".", //
612 "--zig-lib-dir", graph.zig_lib_directory.path orelse ".", //
613 "--name", root_name, //
614 "-rdynamic", //
615 "-fsingle-threaded", //
616 "--dep", "Walk", //
617 "--dep", "html_render", //
618 try std.fmt.allocPrint(arena, "-Mroot={f}", .{main_src_path}), //
619 try std.fmt.allocPrint(arena, "-MWalk={f}", .{walk_src_path}), //
620 "--dep", "Walk", //
621 try std.fmt.allocPrint(arena, "-Mhtml_render={f}", .{html_render_src_path}), //
622 "--listen=-",
623 });
624
625 var child = try std.process.spawn(io, .{
626 .argv = argv.items,
627 .environ_map = &graph.environ_map,
628 .stdin = .pipe,
629 .stdout = .pipe,
630 .stderr = .pipe,
631 });
632 defer child.kill(io);
633
634 var stderr_task = try io.concurrent(readStreamAlloc, .{ gpa, io, child.stderr.?, .unlimited });
635 defer if (stderr_task.cancel(io)) |slice| gpa.free(slice) else |_| {};
636
637 var stdout_buffer: [512]u8 = undefined;
638 var stdout_reader: Io.File.Reader = .initStreaming(child.stdout.?, io, &stdout_buffer);
639 const stdout = &stdout_reader.interface;
640
641 {
642 var w = child.stdin.?.writer(io, &.{});
643 w.interface.writeStruct(std.zig.Client.Message.Header{ .tag = .update, .bytes_len = 0 }, .little) catch |err| switch (err) {
644 error.WriteFailed => return w.err.?,
645 };
646 w.interface.writeStruct(std.zig.Client.Message.Header{ .tag = .exit, .bytes_len = 0 }, .little) catch |err| switch (err) {
647 error.WriteFailed => return w.err.?,
648 };
649 }
650
651 const Header = std.zig.Server.Message.Header;
652
653 var result: ?Cache.Path = null;
654 var result_error_bundle = std.zig.ErrorBundle.empty;
655 var body_buffer: std.ArrayList(u8) = .empty;
656 defer body_buffer.deinit(gpa);
657
658 while (true) {
659 const header = stdout.takeStruct(Header, .little) catch |err| switch (err) {
660 error.ReadFailed => |e| return e,
661 error.EndOfStream => break,
662 };
663 body_buffer.clearRetainingCapacity();
664 try stdout.appendExact(gpa, &body_buffer, header.bytes_len);
665 const body = body_buffer.items;
666
667 switch (header.tag) {
668 .zig_version => {
669 if (!std.mem.eql(u8, builtin.zig_version_string, body)) {
670 return error.ZigProtocolVersionMismatch;
671 }
672 },
673 .error_bundle => {
674 result_error_bundle = try std.zig.Server.allocErrorBundle(arena, body);
675 },
676 .emit_digest => {
677 const EmitDigest = std.zig.Server.Message.EmitDigest;
678 const ebp_hdr: *align(1) const EmitDigest = @ptrCast(body);
679 if (!ebp_hdr.flags.cache_hit) {
680 log.info("source changes detected; rebuilt wasm component", .{});
681 }
682 const digest = body[@sizeOf(EmitDigest)..][0..Cache.bin_digest_len];
683 result = .{
684 .root_dir = graph.global_cache_root,
685 .sub_path = try arena.dupe(u8, "o" ++ std.fs.path.sep_str ++ Cache.binToHex(digest.*)),
686 };
687 },
688 else => {}, // ignore other messages
689 }
690 }
691
692 const stderr_contents = try stderr_task.await(io);
693 if (stderr_contents.len > 0) {
694 std.debug.print("{s}", .{stderr_contents});
695 }
696
697 // Send EOF to stdin.
698 child.stdin.?.close(io);
699 child.stdin = null;
700
701 switch (try child.wait(io)) {
702 .exited => |code| {
703 if (code != 0) {
704 log.err(
705 "the following command exited with error code {d}:\n{s}",
706 .{ code, try std.zig.allocPrintCmd(arena, argv.items, .{}) },
707 );
708 return error.WasmCompilationFailed;
709 }
710 },
711 .signal => |sig| {
712 log.err(
713 "the following command terminated with signal {t}:\n{s}",
714 .{ sig, try std.zig.allocPrintCmd(arena, argv.items, .{}) },
715 );
716 return error.WasmCompilationFailed;
717 },
718 .stopped => |sig| {
719 log.err(
720 "the following command stopped unexpectedly with signal {t}:\n{s}",
721 .{ sig, try std.zig.allocPrintCmd(arena, argv.items, .{}) },
722 );
723 return error.WasmCompilationFailed;
724 },
725 .unknown => {
726 log.err(
727 "the following command terminated unexpectedly:\n{s}",
728 .{try std.zig.allocPrintCmd(arena, argv.items, .{})},
729 );
730 return error.WasmCompilationFailed;
731 },
732 }
733
734 if (result_error_bundle.errorMessageCount() > 0) {
735 try result_error_bundle.renderToStderr(io, .{}, .auto);
736 log.err("the following command failed with {d} compilation errors:\n{s}", .{
737 result_error_bundle.errorMessageCount(),
738 try std.zig.allocPrintCmd(arena, argv.items, .{}),
739 });
740 return error.WasmCompilationFailed;
741 }
742
743 const base_path = result orelse {
744 log.err("child process failed to report result\n{s}", .{
745 try std.zig.allocPrintCmd(arena, argv.items, .{}),
746 });
747 return error.WasmCompilationFailed;
748 };
749 const target = std.zig.system.resolveTargetQuery(io, std.Build.parseTargetQuery(.{
750 .arch_os_abi = arch_os_abi,
751 .cpu_features = cpu_features,
752 }) catch unreachable) catch unreachable;
753 const bin_name = try std.zig.binNameAlloc(arena, .{
754 .root_name = root_name,
755 .cpu_arch = target.cpu.arch,
756 .os_tag = target.os.tag,
757 .ofmt = target.ofmt,
758 .abi = target.abi,
759 .output_mode = .Exe,
760 });
761 return base_path.join(arena, bin_name);
762}
763
764fn readStreamAlloc(gpa: Allocator, io: Io, file: Io.File, limit: Io.Limit) ![]u8 {
765 var file_reader: Io.File.Reader = .initStreaming(file, io, &.{});
766 return file_reader.interface.allocRemaining(gpa, limit) catch |err| switch (err) {
767 error.ReadFailed => return file_reader.err.?,
768 else => |e| return e,
769 };
770}
771
772pub fn updateTimeReportCompile(ws: *WebServer, opts: struct {
773 compile_step: Configuration.Step.Index,
774
775 use_llvm: bool,
776 stats: abi.time_report.CompileResult.Stats,
777 ns_total: u64,
778
779 llvm_pass_timings_len: u32,
780 files_len: u32,
781 decls_len: u32,
782
783 /// The trailing data of `abi.time_report.CompileResult`, except the step name.
784 trailing: []const u8,
785}) void {
786 const maker = ws.maker;
787 const gpa = maker.gpa;
788 const io = maker.graph.io;
789 const all_steps = maker.step_stack.keys();
790
791 const step_idx: u32 = for (all_steps, 0..) |s, i| {
792 if (s == opts.compile_step) break @intCast(i);
793 } else unreachable;
794
795 const old_buf = old: {
796 ws.time_report_mutex.lock(io) catch return;
797 defer ws.time_report_mutex.unlock(io);
798 const old = ws.time_report_msgs[step_idx];
799 ws.time_report_msgs[step_idx] = &.{};
800 break :old old;
801 };
802 const buf = gpa.realloc(old_buf, @sizeOf(abi.time_report.CompileResult) + opts.trailing.len) catch @panic("out of memory");
803
804 const out_header: *align(1) abi.time_report.CompileResult = @ptrCast(buf[0..@sizeOf(abi.time_report.CompileResult)]);
805 out_header.* = .{
806 .step_idx = step_idx,
807 .flags = .{
808 .use_llvm = opts.use_llvm,
809 },
810 .stats = opts.stats,
811 .ns_total = opts.ns_total,
812 .llvm_pass_timings_len = opts.llvm_pass_timings_len,
813 .files_len = opts.files_len,
814 .decls_len = opts.decls_len,
815 };
816 @memcpy(buf[@sizeOf(abi.time_report.CompileResult)..], opts.trailing);
817
818 {
819 ws.time_report_mutex.lock(io) catch return;
820 defer ws.time_report_mutex.unlock(io);
821 assert(ws.time_report_msgs[step_idx].len == 0);
822 ws.time_report_msgs[step_idx] = buf;
823 ws.time_report_update_times[step_idx] = ws.now();
824 }
825 ws.notifyUpdate();
826}
827
828pub fn updateTimeReportGeneric(ws: *WebServer, step_index: Configuration.Step.Index, duration: Io.Duration) void {
829 const maker = ws.maker;
830 const gpa = maker.gpa;
831 const io = maker.graph.io;
832 const all_steps = maker.step_stack.keys();
833
834 const step_idx: u32 = for (all_steps, 0..) |s, i| {
835 if (s == step_index) break @intCast(i);
836 } else unreachable;
837
838 const old_buf = old: {
839 ws.time_report_mutex.lock(io) catch return;
840 defer ws.time_report_mutex.unlock(io);
841 const old = ws.time_report_msgs[step_idx];
842 ws.time_report_msgs[step_idx] = &.{};
843 break :old old;
844 };
845 const buf = gpa.realloc(old_buf, @sizeOf(abi.time_report.GenericResult)) catch @panic("out of memory");
846 const out: *align(1) abi.time_report.GenericResult = @ptrCast(buf);
847 out.* = .{
848 .step_idx = step_idx,
849 .ns_total = @intCast(duration.toNanoseconds()),
850 };
851 {
852 ws.time_report_mutex.lock(io) catch return;
853 defer ws.time_report_mutex.unlock(io);
854 assert(ws.time_report_msgs[step_idx].len == 0);
855 ws.time_report_msgs[step_idx] = buf;
856 ws.time_report_update_times[step_idx] = ws.now();
857 }
858 ws.notifyUpdate();
859}
860
861pub fn updateTimeReportRunTest(
862 ws: *WebServer,
863 run_step_index: Configuration.Step.Index,
864 tests: *const Step.Run.CachedTestMetadata,
865 ns_per_test: []const u64,
866) void {
867 const maker = ws.maker;
868 const gpa = maker.gpa;
869 const io = maker.graph.io;
870 const all_steps = maker.step_stack.keys();
871
872 const step_idx: u32 = for (all_steps, 0..) |s, i| {
873 if (s == run_step_index) break @intCast(i);
874 } else unreachable;
875
876 assert(tests.names.len == ns_per_test.len);
877 const tests_len: u32 = @intCast(tests.names.len);
878
879 const new_len: u64 = len: {
880 var names_len: u64 = 0;
881 for (0..tests_len) |i| {
882 names_len += tests.testName(@intCast(i)).len + 1;
883 }
884 break :len @sizeOf(abi.time_report.RunTestResult) + names_len + 8 * tests_len;
885 };
886 const old_buf = old: {
887 ws.time_report_mutex.lock(io) catch return;
888 defer ws.time_report_mutex.unlock(io);
889 const old = ws.time_report_msgs[step_idx];
890 ws.time_report_msgs[step_idx] = &.{};
891 break :old old;
892 };
893 const buf = gpa.realloc(old_buf, new_len) catch @panic("out of memory");
894
895 const out_header: *align(1) abi.time_report.RunTestResult = @ptrCast(buf[0..@sizeOf(abi.time_report.RunTestResult)]);
896 out_header.* = .{
897 .step_idx = step_idx,
898 .tests_len = tests_len,
899 };
900 var offset: usize = @sizeOf(abi.time_report.RunTestResult);
901 const ns_per_test_out: []align(1) u64 = @ptrCast(buf[offset..][0 .. tests_len * 8]);
902 @memcpy(ns_per_test_out, ns_per_test);
903 offset += tests_len * 8;
904 for (0..tests_len) |i| {
905 const name = tests.testName(@intCast(i));
906 @memcpy(buf[offset..][0..name.len], name);
907 buf[offset..][name.len] = 0;
908 offset += name.len + 1;
909 }
910 assert(offset == buf.len);
911
912 {
913 ws.time_report_mutex.lock(io) catch return;
914 defer ws.time_report_mutex.unlock(io);
915 assert(ws.time_report_msgs[step_idx].len == 0);
916 ws.time_report_msgs[step_idx] = buf;
917 ws.time_report_update_times[step_idx] = ws.now();
918 }
919 ws.notifyUpdate();
920}
921
922const RunnerRequest = union(enum) {
923 rebuild,
924};
925pub fn getRunnerRequest(ws: *WebServer) ?RunnerRequest {
926 const io = ws.maker.graph.io;
927 ws.runner_request_mutex.lock(io) catch return;
928 defer ws.runner_request_mutex.unlock(io);
929 if (ws.runner_request) |req| {
930 ws.runner_request = null;
931 ws.runner_request_empty_cond.signal();
932 return req;
933 }
934 return null;
935}
936pub fn wait(ws: *WebServer) Io.Cancelable!RunnerRequest {
937 const io = ws.maker.graph.io;
938 try ws.runner_request_mutex.lock(io);
939 defer ws.runner_request_mutex.unlock(io);
940 while (true) {
941 if (ws.runner_request) |req| {
942 ws.runner_request = null;
943 ws.runner_request_empty_cond.signal(io);
944 return req;
945 }
946 try ws.runner_request_ready_cond.wait(io, &ws.runner_request_mutex);
947 }
948}
949
950const cache_control_header: http.Header = .{
951 .name = "Cache-Control",
952 .value = "max-age=0, must-revalidate",
953};
lib/compiler/aro/aro/Driver.zig+4-3
......@@ -1041,9 +1041,10 @@ fn parseTarget(d: *Driver, arch_os_abi: []const u8, opt_cpu_features: ?[]const u
10411041 } else if (mem.eql(u8, cpu_name, "baseline")) {
10421042 query.cpu_model = .baseline;
10431043 } else {
1044 query.cpu_model = .{ .explicit = arch.parseCpuModel(cpu_name) catch |er| switch (er) {
1045 error.UnknownCpuModel => return d.fatal("unknown CPU model: '{s}'", .{cpu_name}),
1046 } };
1044 query.cpu_model = .{
1045 .explicit = arch.parseCpuModel(cpu_name) orelse
1046 return d.fatal("unknown CPU model: '{s}'", .{cpu_name}),
1047 };
10471048 }
10481049
10491050 if (opt_sub_arch) |sub_arch| {
lib/compiler/build_runner.zig deleted-1857
......@@ -1,1857 +0,0 @@
1const runner = @This();
2const builtin = @import("builtin");
3
4const std = @import("std");
5const Io = std.Io;
6const assert = std.debug.assert;
7const fmt = std.fmt;
8const mem = std.mem;
9const process = std.process;
10const File = std.Io.File;
11const Step = std.Build.Step;
12const Watch = std.Build.Watch;
13const WebServer = std.Build.WebServer;
14const Allocator = std.mem.Allocator;
15const fatal = std.process.fatal;
16const Writer = std.Io.Writer;
17
18pub const root = @import("@build");
19pub const dependencies = @import("@dependencies");
20
21pub const std_options: std.Options = .{
22 .side_channels_mitigations = .none,
23 .http_disable_tls = true,
24};
25
26pub fn main(init: process.Init.Minimal) !void {
27 // The build runner is often short-lived, but thanks to `--watch` and `--webui`, that's not
28 // always the case. So, we do need a true gpa for some things.
29 var safe_gpa_state: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{});
30 defer _ = safe_gpa_state.deinit();
31 const gpa = safe_gpa_state.allocator();
32
33 var threaded: std.Io.Threaded = .init(gpa, .{
34 .environ = init.environ,
35 .argv0 = .init(init.args),
36 });
37 defer threaded.deinit();
38 const io = threaded.io();
39
40 // ...but we'll back our arena by `std.heap.page_allocator` for efficiency.
41 var arena_instance: std.heap.ArenaAllocator = .init(std.heap.page_allocator);
42 defer arena_instance.deinit();
43 const arena = arena_instance.allocator();
44
45 const args = try init.args.toSlice(arena);
46
47 // skip my own exe name
48 var arg_idx: usize = 1;
49
50 const zig_exe = nextArg(args, &arg_idx) orelse fatal("missing zig compiler path", .{});
51 const zig_lib_dir = nextArg(args, &arg_idx) orelse fatal("missing zig lib directory path", .{});
52 const build_root = nextArg(args, &arg_idx) orelse fatal("missing build root directory path", .{});
53 const cache_root = nextArg(args, &arg_idx) orelse fatal("missing cache root directory path", .{});
54 const global_cache_root = nextArg(args, &arg_idx) orelse fatal("missing global cache root directory path", .{});
55
56 const cwd: Io.Dir = .cwd();
57
58 const zig_lib_directory: std.Build.Cache.Directory = .{
59 .path = zig_lib_dir,
60 .handle = try cwd.openDir(io, zig_lib_dir, .{}),
61 };
62
63 const build_root_directory: std.Build.Cache.Directory = .{
64 .path = build_root,
65 .handle = try cwd.openDir(io, build_root, .{}),
66 };
67
68 const local_cache_directory: std.Build.Cache.Directory = .{
69 .path = cache_root,
70 .handle = try cwd.createDirPathOpen(io, cache_root, .{}),
71 };
72
73 const global_cache_directory: std.Build.Cache.Directory = .{
74 .path = global_cache_root,
75 .handle = try cwd.createDirPathOpen(io, global_cache_root, .{}),
76 };
77
78 var graph: std.Build.Graph = .{
79 .io = io,
80 .arena = arena,
81 .cache = .{
82 .io = io,
83 .gpa = gpa,
84 .manifest_dir = try local_cache_directory.handle.createDirPathOpen(io, "h", .{}),
85 .cwd = try process.currentPathAlloc(io, arena),
86 },
87 .zig_exe = zig_exe,
88 .environ_map = try init.environ.createMap(arena),
89 .global_cache_root = global_cache_directory,
90 .zig_lib_directory = zig_lib_directory,
91 .host = .{
92 .query = .{},
93 .result = try std.zig.system.resolveTargetQuery(io, .{}),
94 },
95 .time_report = false,
96 };
97
98 graph.cache.addPrefix(.{ .path = null, .handle = cwd });
99 graph.cache.addPrefix(build_root_directory);
100 graph.cache.addPrefix(local_cache_directory);
101 graph.cache.addPrefix(global_cache_directory);
102 graph.cache.hash.addBytes(builtin.zig_version_string);
103
104 const builder = try std.Build.create(
105 &graph,
106 build_root_directory,
107 local_cache_directory,
108 dependencies.root_deps,
109 );
110
111 var targets = std.array_list.Managed([]const u8).init(arena);
112 var debug_log_scopes = std.array_list.Managed([]const u8).init(arena);
113
114 var install_prefix: ?[]const u8 = null;
115 var dir_list = std.Build.DirList{};
116 var error_style: ErrorStyle = .verbose;
117 var multiline_errors: MultilineErrors = .indent;
118 var summary: ?Summary = null;
119 var max_rss: u64 = 0;
120 var skip_oom_steps = false;
121 var test_timeout_ns: ?u64 = null;
122 var color: Color = .auto;
123 var help_menu = false;
124 var steps_menu = false;
125 var output_tmp_nonce: ?[16]u8 = null;
126 var watch = false;
127 var fuzz: ?std.Build.Fuzz.Mode = null;
128 var debounce_interval_ms: u16 = 50;
129 var webui_listen: ?Io.net.IpAddress = null;
130
131 if (std.zig.EnvVar.ZIG_BUILD_ERROR_STYLE.get(&graph.environ_map)) |str| {
132 if (std.meta.stringToEnum(ErrorStyle, str)) |style| {
133 error_style = style;
134 }
135 }
136
137 if (std.zig.EnvVar.ZIG_BUILD_MULTILINE_ERRORS.get(&graph.environ_map)) |str| {
138 if (std.meta.stringToEnum(MultilineErrors, str)) |style| {
139 multiline_errors = style;
140 }
141 }
142
143 while (nextArg(args, &arg_idx)) |arg| {
144 if (mem.startsWith(u8, arg, "-Z")) {
145 if (arg.len != 18) fatalWithHint("bad argument: '{s}'", .{arg});
146 output_tmp_nonce = arg[2..18].*;
147 } else if (mem.startsWith(u8, arg, "-D")) {
148 const option_contents = arg[2..];
149 if (option_contents.len == 0)
150 fatalWithHint("expected option name after '-D'", .{});
151 if (mem.indexOfScalar(u8, option_contents, '=')) |name_end| {
152 const option_name = option_contents[0..name_end];
153 const option_value = option_contents[name_end + 1 ..];
154 if (try builder.addUserInputOption(option_name, option_value))
155 fatal(" access the help menu with 'zig build -h'", .{});
156 } else {
157 if (try builder.addUserInputFlag(option_contents))
158 fatal(" access the help menu with 'zig build -h'", .{});
159 }
160 } else if (mem.startsWith(u8, arg, "-")) {
161 if (mem.eql(u8, arg, "--verbose")) {
162 builder.verbose = true;
163 } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
164 help_menu = true;
165 } else if (mem.eql(u8, arg, "-p") or mem.eql(u8, arg, "--prefix")) {
166 install_prefix = nextArgOrFatal(args, &arg_idx);
167 } else if (mem.eql(u8, arg, "-l") or mem.eql(u8, arg, "--list-steps")) {
168 steps_menu = true;
169 } else if (mem.startsWith(u8, arg, "-fsys=")) {
170 const name = arg["-fsys=".len..];
171 graph.system_library_options.put(arena, name, .user_enabled) catch @panic("OOM");
172 } else if (mem.startsWith(u8, arg, "-fno-sys=")) {
173 const name = arg["-fno-sys=".len..];
174 graph.system_library_options.put(arena, name, .user_disabled) catch @panic("OOM");
175 } else if (mem.eql(u8, arg, "--release")) {
176 builder.release_mode = .any;
177 } else if (mem.startsWith(u8, arg, "--release=")) {
178 const text = arg["--release=".len..];
179 builder.release_mode = std.meta.stringToEnum(std.Build.ReleaseMode, text) orelse {
180 fatalWithHint("expected [off|any|fast|safe|small] in '{s}', found '{s}'", .{
181 arg, text,
182 });
183 };
184 } else if (mem.eql(u8, arg, "--prefix-lib-dir")) {
185 dir_list.lib_dir = nextArgOrFatal(args, &arg_idx);
186 } else if (mem.eql(u8, arg, "--prefix-exe-dir")) {
187 dir_list.exe_dir = nextArgOrFatal(args, &arg_idx);
188 } else if (mem.eql(u8, arg, "--prefix-include-dir")) {
189 dir_list.include_dir = nextArgOrFatal(args, &arg_idx);
190 } else if (mem.eql(u8, arg, "--sysroot")) {
191 builder.sysroot = nextArgOrFatal(args, &arg_idx);
192 } else if (mem.eql(u8, arg, "--maxrss")) {
193 const max_rss_text = nextArgOrFatal(args, &arg_idx);
194 max_rss = std.fmt.parseIntSizeSuffix(max_rss_text, 10) catch |err| {
195 std.debug.print("invalid byte size: '{s}': {s}\n", .{
196 max_rss_text, @errorName(err),
197 });
198 process.exit(1);
199 };
200 } else if (mem.eql(u8, arg, "--skip-oom-steps")) {
201 skip_oom_steps = true;
202 } else if (mem.eql(u8, arg, "--test-timeout")) {
203 const units: []const struct { []const u8, u64 } = &.{
204 .{ "ns", 1 },
205 .{ "nanosecond", 1 },
206 .{ "us", std.time.ns_per_us },
207 .{ "microsecond", std.time.ns_per_us },
208 .{ "ms", std.time.ns_per_ms },
209 .{ "millisecond", std.time.ns_per_ms },
210 .{ "s", std.time.ns_per_s },
211 .{ "second", std.time.ns_per_s },
212 .{ "m", std.time.ns_per_min },
213 .{ "minute", std.time.ns_per_min },
214 .{ "h", std.time.ns_per_hour },
215 .{ "hour", std.time.ns_per_hour },
216 };
217 const timeout_str = nextArgOrFatal(args, &arg_idx);
218 const num_end_idx = std.mem.findLastNone(u8, timeout_str, "abcdefghijklmnopqrstuvwxyz") orelse fatal(
219 "invalid timeout '{s}': expected unit (ns, us, ms, s, m, h)",
220 .{timeout_str},
221 );
222 const num_str = timeout_str[0 .. num_end_idx + 1];
223 const unit_str = timeout_str[num_end_idx + 1 ..];
224 const unit_factor: f64 = for (units) |unit_and_factor| {
225 if (std.mem.eql(u8, unit_str, unit_and_factor[0])) {
226 break @floatFromInt(unit_and_factor[1]);
227 }
228 } else fatal(
229 "invalid timeout '{s}': invalid unit '{s}' (expected ns, us, ms, s, m, h)",
230 .{ timeout_str, unit_str },
231 );
232 const num_parsed = std.fmt.parseFloat(f64, num_str) catch |err| fatal(
233 "invalid timeout '{s}': invalid number '{s}' ({t})",
234 .{ timeout_str, num_str, err },
235 );
236 test_timeout_ns = std.math.lossyCast(u64, unit_factor * num_parsed);
237 } else if (mem.eql(u8, arg, "--search-prefix")) {
238 const search_prefix = nextArgOrFatal(args, &arg_idx);
239 builder.addSearchPrefix(search_prefix);
240 } else if (mem.eql(u8, arg, "--libc")) {
241 builder.libc_file = nextArgOrFatal(args, &arg_idx);
242 } else if (mem.eql(u8, arg, "--color")) {
243 const next_arg = nextArg(args, &arg_idx) orelse
244 fatalWithHint("expected [auto|on|off] after '{s}'", .{arg});
245 color = std.meta.stringToEnum(Color, next_arg) orelse {
246 fatalWithHint("expected [auto|on|off] after '{s}', found '{s}'", .{
247 arg, next_arg,
248 });
249 };
250 } else if (mem.eql(u8, arg, "--error-style")) {
251 const next_arg = nextArg(args, &arg_idx) orelse
252 fatalWithHint("expected style after '{s}'", .{arg});
253 error_style = std.meta.stringToEnum(ErrorStyle, next_arg) orelse {
254 fatalWithHint("expected style after '{s}', found '{s}'", .{ arg, next_arg });
255 };
256 } else if (mem.eql(u8, arg, "--multiline-errors")) {
257 const next_arg = nextArg(args, &arg_idx) orelse
258 fatalWithHint("expected style after '{s}'", .{arg});
259 multiline_errors = std.meta.stringToEnum(MultilineErrors, next_arg) orelse {
260 fatalWithHint("expected style after '{s}', found '{s}'", .{ arg, next_arg });
261 };
262 } else if (mem.eql(u8, arg, "--summary")) {
263 const next_arg = nextArg(args, &arg_idx) orelse
264 fatalWithHint("expected [all|new|failures|line|none] after '{s}'", .{arg});
265 summary = std.meta.stringToEnum(Summary, next_arg) orelse {
266 fatalWithHint("expected [all|new|failures|line|none] after '{s}', found '{s}'", .{
267 arg, next_arg,
268 });
269 };
270 } else if (mem.eql(u8, arg, "--seed")) {
271 const next_arg = nextArg(args, &arg_idx) orelse
272 fatalWithHint("expected u32 after '{s}'", .{arg});
273 graph.random_seed = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err| {
274 fatal("unable to parse seed '{s}' as unsigned 32-bit integer: {s}\n", .{
275 next_arg, @errorName(err),
276 });
277 };
278 } else if (mem.eql(u8, arg, "--build-id")) {
279 builder.build_id = .fast;
280 } else if (mem.startsWith(u8, arg, "--build-id=")) {
281 const style = arg["--build-id=".len..];
282 builder.build_id = std.zig.BuildId.parse(style) catch |err| {
283 fatal("unable to parse --build-id style '{s}': {s}", .{
284 style, @errorName(err),
285 });
286 };
287 } else if (mem.eql(u8, arg, "--debounce")) {
288 const next_arg = nextArg(args, &arg_idx) orelse
289 fatalWithHint("expected u16 after '{s}'", .{arg});
290 debounce_interval_ms = std.fmt.parseUnsigned(u16, next_arg, 0) catch |err| {
291 fatal("unable to parse debounce interval '{s}' as unsigned 16-bit integer: {t}\n", .{
292 next_arg, err,
293 });
294 };
295 } else if (mem.eql(u8, arg, "--webui")) {
296 if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) };
297 } else if (mem.startsWith(u8, arg, "--webui=")) {
298 const addr_str = arg["--webui=".len..];
299 if (std.mem.eql(u8, addr_str, "-")) fatal("web interface cannot listen on stdio", .{});
300 webui_listen = Io.net.IpAddress.parseLiteral(addr_str) catch |err| {
301 fatal("invalid web UI address '{s}': {s}", .{ addr_str, @errorName(err) });
302 };
303 } else if (mem.eql(u8, arg, "--debug-log")) {
304 const next_arg = nextArgOrFatal(args, &arg_idx);
305 try debug_log_scopes.append(next_arg);
306 } else if (mem.eql(u8, arg, "--debug-pkg-config")) {
307 builder.debug_pkg_config = true;
308 } else if (mem.eql(u8, arg, "--debug-rt")) {
309 graph.debug_compiler_runtime_libs = .Debug;
310 } else if (mem.cutPrefix(u8, arg, "--debug-rt=")) |rest| {
311 graph.debug_compiler_runtime_libs =
312 std.meta.stringToEnum(std.builtin.OptimizeMode, rest) orelse
313 fatal("unrecognized optimization mode: '{s}'", .{rest});
314 } else if (mem.eql(u8, arg, "--debug-compile-errors")) {
315 builder.debug_compile_errors = true;
316 } else if (mem.eql(u8, arg, "--debug-incremental")) {
317 builder.debug_incremental = true;
318 } else if (mem.eql(u8, arg, "--system")) {
319 // The usage text shows another argument after this parameter
320 // but it is handled by the parent process. The build runner
321 // only sees this flag.
322 graph.system_package_mode = true;
323 } else if (mem.eql(u8, arg, "--libc-runtimes") or mem.eql(u8, arg, "--glibc-runtimes")) {
324 // --glibc-runtimes was the old name of the flag; kept for compatibility for now.
325 builder.libc_runtimes_dir = nextArgOrFatal(args, &arg_idx);
326 } else if (mem.eql(u8, arg, "--verbose-link")) {
327 builder.verbose_link = true;
328 } else if (mem.eql(u8, arg, "--verbose-air")) {
329 builder.verbose_air = true;
330 } else if (mem.eql(u8, arg, "--verbose-llvm-ir")) {
331 builder.verbose_llvm_ir = "-";
332 } else if (mem.startsWith(u8, arg, "--verbose-llvm-ir=")) {
333 builder.verbose_llvm_ir = arg["--verbose-llvm-ir=".len..];
334 } else if (mem.startsWith(u8, arg, "--verbose-llvm-bc=")) {
335 builder.verbose_llvm_bc = arg["--verbose-llvm-bc=".len..];
336 } else if (mem.eql(u8, arg, "--verbose-cc")) {
337 builder.verbose_cc = true;
338 } else if (mem.eql(u8, arg, "--verbose-llvm-cpu-features")) {
339 builder.verbose_llvm_cpu_features = true;
340 } else if (mem.eql(u8, arg, "--watch")) {
341 watch = true;
342 } else if (mem.eql(u8, arg, "--time-report")) {
343 graph.time_report = true;
344 if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) };
345 } else if (mem.eql(u8, arg, "--fuzz")) {
346 fuzz = .{ .forever = undefined };
347 if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) };
348 } else if (mem.startsWith(u8, arg, "--fuzz=")) {
349 const value = arg["--fuzz=".len..];
350 if (value.len == 0) fatal("missing argument to --fuzz", .{});
351
352 const unit: u8 = value[value.len - 1];
353 const digits = switch (unit) {
354 '0'...'9' => value,
355 'K', 'M', 'G' => value[0 .. value.len - 1],
356 else => fatal(
357 "invalid argument to --fuzz, expected a positive number optionally suffixed by one of: [KMG]",
358 .{},
359 ),
360 };
361
362 const amount = std.fmt.parseInt(u64, digits, 10) catch {
363 fatal(
364 "invalid argument to --fuzz, expected a positive number optionally suffixed by one of: [KMG]",
365 .{},
366 );
367 };
368
369 const normalized_amount = std.math.mul(u64, amount, switch (unit) {
370 else => unreachable,
371 '0'...'9' => 1,
372 'K' => 1000,
373 'M' => 1_000_000,
374 'G' => 1_000_000_000,
375 }) catch fatal("fuzzing limit amount overflows u64", .{});
376
377 fuzz = .{
378 .limit = .{
379 .amount = normalized_amount,
380 },
381 };
382 } else if (mem.eql(u8, arg, "-fincremental")) {
383 graph.incremental = true;
384 } else if (mem.eql(u8, arg, "-fno-incremental")) {
385 graph.incremental = false;
386 } else if (mem.eql(u8, arg, "-fwine")) {
387 builder.enable_wine = true;
388 } else if (mem.eql(u8, arg, "-fno-wine")) {
389 builder.enable_wine = false;
390 } else if (mem.eql(u8, arg, "-fqemu")) {
391 builder.enable_qemu = true;
392 } else if (mem.eql(u8, arg, "-fno-qemu")) {
393 builder.enable_qemu = false;
394 } else if (mem.eql(u8, arg, "-fwasmtime")) {
395 builder.enable_wasmtime = true;
396 } else if (mem.eql(u8, arg, "-fno-wasmtime")) {
397 builder.enable_wasmtime = false;
398 } else if (mem.eql(u8, arg, "-frosetta")) {
399 builder.enable_rosetta = true;
400 } else if (mem.eql(u8, arg, "-fno-rosetta")) {
401 builder.enable_rosetta = false;
402 } else if (mem.eql(u8, arg, "-fdarling")) {
403 builder.enable_darling = true;
404 } else if (mem.eql(u8, arg, "-fno-darling")) {
405 builder.enable_darling = false;
406 } else if (mem.eql(u8, arg, "-fallow-so-scripts")) {
407 graph.allow_so_scripts = true;
408 } else if (mem.eql(u8, arg, "-fno-allow-so-scripts")) {
409 graph.allow_so_scripts = false;
410 } else if (mem.eql(u8, arg, "-freference-trace")) {
411 builder.reference_trace = 256;
412 } else if (mem.startsWith(u8, arg, "-freference-trace=")) {
413 const num = arg["-freference-trace=".len..];
414 builder.reference_trace = std.fmt.parseUnsigned(u32, num, 10) catch |err| {
415 std.debug.print("unable to parse reference_trace count '{s}': {s}", .{ num, @errorName(err) });
416 process.exit(1);
417 };
418 } else if (mem.eql(u8, arg, "-fno-reference-trace")) {
419 builder.reference_trace = null;
420 } else if (mem.cutPrefix(u8, arg, "-j")) |text| {
421 const n = std.fmt.parseUnsigned(u32, text, 10) catch |err|
422 fatal("unable to parse jobs count '{s}': {t}", .{ text, err });
423 if (n < 1) fatal("number of jobs must be at least 1", .{});
424 threaded.setAsyncLimit(.limited(n));
425 graph.max_jobs = n;
426 } else if (mem.eql(u8, arg, "--")) {
427 builder.args = argsRest(args, arg_idx);
428 break;
429 } else {
430 fatalWithHint("unrecognized argument: '{s}'", .{arg});
431 }
432 } else {
433 try targets.append(arg);
434 }
435 }
436
437 const NO_COLOR = std.zig.EnvVar.NO_COLOR.isSet(&graph.environ_map);
438 const CLICOLOR_FORCE = std.zig.EnvVar.CLICOLOR_FORCE.isSet(&graph.environ_map);
439
440 graph.stderr_mode = switch (color) {
441 .auto => try .detect(io, .stderr(), NO_COLOR, CLICOLOR_FORCE),
442 .on => .escape_codes,
443 .off => .no_color,
444 };
445
446 if (webui_listen != null) {
447 if (watch) fatal("using '--webui' and '--watch' together is not yet supported; consider omitting '--watch' in favour of the web UI \"Rebuild\" button", .{});
448 if (builtin.single_threaded) fatal("'--webui' is not yet supported on single-threaded hosts", .{});
449 }
450
451 const main_progress_node = std.Progress.start(io, .{
452 .disable_printing = (color == .off),
453 });
454 defer main_progress_node.end();
455
456 builder.debug_log_scopes = debug_log_scopes.items;
457 builder.resolveInstallPrefix(install_prefix, dir_list);
458 {
459 var prog_node = main_progress_node.start("Configure", 0);
460 defer prog_node.end();
461 try builder.runBuild(root);
462 createModuleDependencies(builder) catch @panic("OOM");
463 }
464
465 if (graph.needed_lazy_dependencies.entries.len != 0) {
466 var buffer: std.ArrayList(u8) = .empty;
467 for (graph.needed_lazy_dependencies.keys()) |k| {
468 try buffer.appendSlice(arena, k);
469 try buffer.append(arena, '\n');
470 }
471 const s = std.fs.path.sep_str;
472 const tmp_sub_path = "tmp" ++ s ++ (output_tmp_nonce orelse fatal("missing -Z arg", .{}));
473 local_cache_directory.handle.writeFile(io, .{
474 .sub_path = tmp_sub_path,
475 .data = buffer.items,
476 .flags = .{ .exclusive = true },
477 }) catch |err| {
478 fatal("unable to write configuration results to '{f}{s}': {s}", .{
479 local_cache_directory, tmp_sub_path, @errorName(err),
480 });
481 };
482 process.exit(3); // Indicate configure phase failed with meaningful stdout.
483 }
484
485 if (builder.validateUserInputDidItFail()) {
486 fatal(" access the help menu with 'zig build -h'", .{});
487 }
488
489 validateSystemLibraryOptions(builder);
490
491 if (help_menu) {
492 var w = initStdoutWriter(io);
493 printUsage(builder, w) catch return stdout_writer_allocation.err.?;
494 w.flush() catch return stdout_writer_allocation.err.?;
495 return;
496 }
497
498 if (steps_menu) {
499 var w = initStdoutWriter(io);
500 printSteps(builder, w) catch return stdout_writer_allocation.err.?;
501 w.flush() catch return stdout_writer_allocation.err.?;
502 return;
503 }
504
505 var run: Run = .{
506 .gpa = gpa,
507
508 .available_rss = max_rss,
509 .max_rss_is_default = false,
510 .max_rss_mutex = .init,
511 .skip_oom_steps = skip_oom_steps,
512 .unit_test_timeout_ns = test_timeout_ns,
513
514 .watch = watch,
515 .web_server = undefined, // set after `prepare`
516 .memory_blocked_steps = .empty,
517 .step_stack = .empty,
518
519 .error_style = error_style,
520 .multiline_errors = multiline_errors,
521 .summary = summary orelse if (watch or webui_listen != null) .line else .failures,
522 };
523 defer {
524 run.memory_blocked_steps.deinit(gpa);
525 run.step_stack.deinit(gpa);
526 }
527
528 if (run.available_rss == 0) {
529 run.available_rss = process.totalSystemMemory() catch std.math.maxInt(u64);
530 run.max_rss_is_default = true;
531 }
532
533 prepare(arena, builder, targets.items, &run, graph.random_seed) catch |err| switch (err) {
534 error.DependencyLoopDetected, error.InsufficientMemory => {
535 // Perhaps in the future there could be an Advanced Options flag
536 // such as --debug-build-runner-leaks which would make this code
537 // return instead of calling exit.
538 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};
539 process.exit(1);
540 },
541 else => |e| return e,
542 };
543
544 var w: Watch = w: {
545 if (!watch) break :w undefined;
546 if (!Watch.have_impl) fatal("--watch not yet implemented for {t}", .{builtin.os.tag});
547 break :w try .init(graph.cache.cwd);
548 };
549
550 const now = Io.Clock.Timestamp.now(io, .awake);
551
552 run.web_server = if (webui_listen) |listen_address| ws: {
553 if (builtin.single_threaded) unreachable; // `fatal` above
554 break :ws .init(.{
555 .gpa = gpa,
556 .graph = &graph,
557 .all_steps = run.step_stack.keys(),
558 .root_prog_node = main_progress_node,
559 .watch = watch,
560 .listen_address = listen_address,
561 .base_timestamp = now,
562 });
563 } else null;
564
565 if (run.web_server) |*ws| {
566 ws.start() catch |err| fatal("failed to start web server: {t}", .{err});
567 }
568
569 rebuild: while (true) : (if (run.error_style.clearOnUpdate()) {
570 const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode);
571 defer io.unlockStderr();
572 try stderr.file_writer.interface.writeAll("\x1B[2J\x1B[3J\x1B[H");
573 }) {
574 if (run.web_server) |*ws| ws.startBuild();
575
576 try runStepNames(
577 builder,
578 targets.items,
579 main_progress_node,
580 &run,
581 fuzz,
582 );
583
584 if (run.web_server) |*web_server| {
585 if (fuzz) |mode| if (mode != .forever) fatal(
586 "error: limited fuzzing is not implemented yet for --webui",
587 .{},
588 );
589
590 web_server.finishBuild(.{ .fuzz = fuzz != null });
591 }
592
593 if (run.web_server) |*ws| {
594 assert(!watch); // fatal error after CLI parsing
595 while (true) switch (try ws.wait()) {
596 .rebuild => {
597 for (run.step_stack.keys()) |step| {
598 step.state = .precheck_done;
599 step.pending_deps = @intCast(step.dependencies.items.len);
600 step.reset(gpa);
601 }
602 continue :rebuild;
603 },
604 };
605 }
606
607 // Comptime-known guard to prevent including the logic below when `!Watch.have_impl`.
608 if (!Watch.have_impl) unreachable;
609
610 try w.update(gpa, run.step_stack.keys());
611
612 // Wait until a file system notification arrives. Read all such events
613 // until the buffer is empty. Then wait for a debounce interval, resetting
614 // if any more events come in. After the debounce interval has passed,
615 // trigger a rebuild on all steps with modified inputs, as well as their
616 // recursive dependants.
617 var caption_buf: [std.Progress.Node.max_name_len]u8 = undefined;
618 const caption = std.fmt.bufPrint(&caption_buf, "watching {d} directories, {d} processes", .{
619 w.dir_count, countSubProcesses(run.step_stack.keys()),
620 }) catch &caption_buf;
621 var debouncing_node = main_progress_node.start(caption, 0);
622 var in_debounce = false;
623 while (true) switch (try w.wait(gpa, io, if (in_debounce) .{ .ms = debounce_interval_ms } else .none)) {
624 .timeout => {
625 assert(in_debounce);
626 debouncing_node.end();
627 markFailedStepsDirty(gpa, run.step_stack.keys());
628 continue :rebuild;
629 },
630 .dirty => if (!in_debounce) {
631 in_debounce = true;
632 debouncing_node.end();
633 debouncing_node = main_progress_node.start("Debouncing (Change Detected)", 0);
634 },
635 .clean => {},
636 };
637 }
638}
639
640fn markFailedStepsDirty(gpa: Allocator, all_steps: []const *Step) void {
641 for (all_steps) |step| switch (step.state) {
642 .dependency_failure, .failure, .skipped => _ = step.invalidateResult(gpa),
643 else => continue,
644 };
645 // Now that all dirty steps have been found, the remaining steps that
646 // succeeded from last run shall be marked "cached".
647 for (all_steps) |step| switch (step.state) {
648 .success => step.result_cached = true,
649 else => continue,
650 };
651}
652
653fn countSubProcesses(all_steps: []const *Step) usize {
654 var count: usize = 0;
655 for (all_steps) |s| {
656 count += @intFromBool(s.getZigProcess() != null);
657 }
658 return count;
659}
660
661const Run = struct {
662 gpa: Allocator,
663
664 available_rss: usize,
665 max_rss_is_default: bool,
666 max_rss_mutex: Io.Mutex,
667 skip_oom_steps: bool,
668 unit_test_timeout_ns: ?u64,
669 watch: bool,
670 web_server: if (!builtin.single_threaded) ?WebServer else ?noreturn,
671 /// Allocated into `gpa`.
672 memory_blocked_steps: std.ArrayList(*Step),
673 /// Allocated into `gpa`.
674 step_stack: std.AutoArrayHashMapUnmanaged(*Step, void),
675
676 error_style: ErrorStyle,
677 multiline_errors: MultilineErrors,
678 summary: Summary,
679};
680
681fn prepare(
682 arena: Allocator,
683 b: *std.Build,
684 step_names: []const []const u8,
685 run: *Run,
686 seed: u32,
687) !void {
688 const gpa = run.gpa;
689 const step_stack = &run.step_stack;
690
691 if (step_names.len == 0) {
692 try step_stack.put(gpa, b.default_step, {});
693 } else {
694 try step_stack.ensureUnusedCapacity(gpa, step_names.len);
695 for (0..step_names.len) |i| {
696 const step_name = step_names[step_names.len - i - 1];
697 const s = b.top_level_steps.get(step_name) orelse {
698 std.log.info("access the help menu with \"zig build -h\"", .{});
699 fatal("no step named '{s}'", .{step_name});
700 };
701 step_stack.putAssumeCapacity(&s.step, {});
702 }
703 }
704
705 const starting_steps = try arena.dupe(*Step, step_stack.keys());
706
707 var rng = std.Random.DefaultPrng.init(seed);
708 const rand = rng.random();
709 rand.shuffle(*Step, starting_steps);
710
711 for (starting_steps) |s| {
712 try constructGraphAndCheckForDependencyLoop(gpa, b, s, &run.step_stack, rand);
713 }
714
715 {
716 // Check that we have enough memory to complete the build.
717 var any_problems = false;
718 var max_needed: usize = 0;
719 for (step_stack.keys()) |s| {
720 if (s.max_rss == 0) continue;
721 max_needed = @max(max_needed, s.max_rss);
722 if (s.max_rss > run.available_rss) {
723 if (run.skip_oom_steps) {
724 s.state = .skipped_oom;
725 for (s.dependants.items) |dependant| {
726 dependant.pending_deps -= 1;
727 }
728 } else {
729 std.log.err("{s}{s}: this step declares an upper bound of {d} bytes of memory, exceeding the available {d} bytes of memory", .{
730 s.owner.dep_prefix, s.name, s.max_rss, run.available_rss,
731 });
732 any_problems = true;
733 }
734 }
735 }
736 if (any_problems) {
737 if (run.max_rss_is_default) {
738 std.log.info("use --maxrss {d} to proceed, risking system memory exhaustion", .{
739 max_needed,
740 });
741 }
742 return error.InsufficientMemory;
743 }
744 }
745}
746
747fn runStepNames(
748 b: *std.Build,
749 step_names: []const []const u8,
750 parent_prog_node: std.Progress.Node,
751 run: *Run,
752 fuzz: ?std.Build.Fuzz.Mode,
753) !void {
754 const gpa = run.gpa;
755 const graph = b.graph;
756 const io = graph.io;
757 const step_stack = &run.step_stack;
758
759 {
760 // Collect the initial set of tasks (those with no outstanding dependencies) into a buffer,
761 // then spawn them. The buffer is so that we don't race with `makeStep` and end up thinking
762 // a step is initial when it actually became ready due to an earlier initial step.
763 var initial_set: std.ArrayList(*Step) = .empty;
764 defer initial_set.deinit(gpa);
765 try initial_set.ensureUnusedCapacity(gpa, step_stack.count());
766 for (step_stack.keys()) |s| {
767 if (s.state == .precheck_done and s.pending_deps == 0) {
768 initial_set.appendAssumeCapacity(s);
769 }
770 }
771
772 const step_prog = parent_prog_node.start("steps", step_stack.count());
773 defer step_prog.end();
774
775 var group: Io.Group = .init;
776 defer group.cancel(io);
777 // Start working on all of the initial steps...
778 for (initial_set.items) |s| try stepReady(&group, b, s, step_prog, run);
779 // ...and `makeStep` will trigger every other step when their last dependency finishes.
780 try group.await(io);
781 }
782
783 assert(run.memory_blocked_steps.items.len == 0);
784
785 var test_pass_count: usize = 0;
786 var test_skip_count: usize = 0;
787 var test_fail_count: usize = 0;
788 var test_crash_count: usize = 0;
789 var test_timeout_count: usize = 0;
790
791 var test_count: usize = 0;
792
793 var success_count: usize = 0;
794 var skipped_count: usize = 0;
795 var failure_count: usize = 0;
796 var pending_count: usize = 0;
797 var total_compile_errors: usize = 0;
798
799 var cleanup_task = io.async(cleanTmpFiles, .{ io, step_stack.keys() });
800 defer cleanup_task.await(io);
801
802 for (step_stack.keys()) |s| {
803 test_pass_count += s.test_results.passCount();
804 test_skip_count += s.test_results.skip_count;
805 test_fail_count += s.test_results.fail_count;
806 test_crash_count += s.test_results.crash_count;
807 test_timeout_count += s.test_results.timeout_count;
808
809 test_count += s.test_results.test_count;
810
811 switch (s.state) {
812 .precheck_unstarted => unreachable,
813 .precheck_started => unreachable,
814 .precheck_done => unreachable,
815 .dependency_failure => pending_count += 1,
816 .success => success_count += 1,
817 .skipped, .skipped_oom => skipped_count += 1,
818 .failure => {
819 failure_count += 1;
820 const compile_errors_len = s.result_error_bundle.errorMessageCount();
821 if (compile_errors_len > 0) {
822 total_compile_errors += compile_errors_len;
823 }
824 },
825 }
826 }
827
828 if (fuzz) |mode| blk: {
829 switch (builtin.os.tag) {
830 // Current implementation depends on two things that need to be ported to Windows:
831 // * Memory-mapping to share data between the fuzzer and build runner.
832 // * COFF/PE support added to `std.debug.Info` (it needs a batching API for resolving
833 // many addresses to source locations).
834 .windows => fatal("--fuzz not yet implemented for {t}", .{builtin.os.tag}),
835 else => {},
836 }
837 if (@bitSizeOf(usize) != 64) {
838 // Current implementation depends on posix.mmap()'s second parameter, `length: usize`,
839 // being compatible with file system's u64 return value. This is not the case
840 // on 32-bit platforms.
841 // Affects or affected by issues #5185, #22523, and #22464.
842 fatal("--fuzz not yet implemented on {d}-bit platforms", .{@bitSizeOf(usize)});
843 }
844
845 switch (mode) {
846 .forever => break :blk,
847 .limit => {},
848 }
849
850 assert(mode == .limit);
851 var f = std.Build.Fuzz.init(
852 gpa,
853 io,
854 step_stack.keys(),
855 parent_prog_node,
856 mode,
857 ) catch |err| fatal("failed to start fuzzer: {t}", .{err});
858 defer f.deinit();
859
860 f.start();
861 try f.waitAndPrintReport();
862 }
863
864 // Every test has a state
865 assert(test_pass_count + test_skip_count + test_fail_count + test_crash_count + test_timeout_count == test_count);
866
867 if (failure_count == 0) {
868 std.Progress.setStatus(.success);
869 } else {
870 std.Progress.setStatus(.failure);
871 }
872
873 summary: {
874 switch (run.summary) {
875 .all, .new, .line => {},
876 .failures => if (failure_count == 0) break :summary,
877 .none => break :summary,
878 }
879
880 const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode);
881 defer io.unlockStderr();
882 const t = stderr.terminal();
883 const w = &stderr.file_writer.interface;
884
885 const total_count = success_count + failure_count + pending_count + skipped_count;
886 t.setColor(.cyan) catch {};
887 t.setColor(.bold) catch {};
888 w.writeAll("Build Summary: ") catch {};
889 t.setColor(.reset) catch {};
890 w.print("{d}/{d} steps succeeded", .{ success_count, total_count }) catch {};
891 {
892 t.setColor(.dim) catch {};
893 var first = true;
894 if (skipped_count > 0) {
895 w.print("{s}{d} skipped", .{ if (first) " (" else ", ", skipped_count }) catch {};
896 first = false;
897 }
898 if (failure_count > 0) {
899 w.print("{s}{d} failed", .{ if (first) " (" else ", ", failure_count }) catch {};
900 first = false;
901 }
902 if (!first) w.writeByte(')') catch {};
903 t.setColor(.reset) catch {};
904 }
905
906 if (test_count > 0) {
907 w.print("; {d}/{d} tests passed", .{ test_pass_count, test_count }) catch {};
908 t.setColor(.dim) catch {};
909 var first = true;
910 if (test_skip_count > 0) {
911 w.print("{s}{d} skipped", .{ if (first) " (" else ", ", test_skip_count }) catch {};
912 first = false;
913 }
914 if (test_fail_count > 0) {
915 w.print("{s}{d} failed", .{ if (first) " (" else ", ", test_fail_count }) catch {};
916 first = false;
917 }
918 if (test_crash_count > 0) {
919 w.print("{s}{d} crashed", .{ if (first) " (" else ", ", test_crash_count }) catch {};
920 first = false;
921 }
922 if (test_timeout_count > 0) {
923 w.print("{s}{d} timed out", .{ if (first) " (" else ", ", test_timeout_count }) catch {};
924 first = false;
925 }
926 if (!first) w.writeByte(')') catch {};
927 t.setColor(.reset) catch {};
928 }
929
930 w.writeAll("\n") catch {};
931
932 if (run.summary == .line) break :summary;
933
934 // Print a fancy tree with build results.
935 var step_stack_copy = try step_stack.clone(gpa);
936 defer step_stack_copy.deinit(gpa);
937
938 var print_node: PrintNode = .{ .parent = null };
939 if (step_names.len == 0) {
940 print_node.last = true;
941 printTreeStep(b, b.default_step, run, t, &print_node, &step_stack_copy) catch {};
942 } else {
943 const last_index = if (run.summary == .all) b.top_level_steps.count() else blk: {
944 var i: usize = step_names.len;
945 while (i > 0) {
946 i -= 1;
947 const step = b.top_level_steps.get(step_names[i]).?.step;
948 const found = switch (run.summary) {
949 .all, .line, .none => unreachable,
950 .failures => step.state != .success,
951 .new => !step.result_cached,
952 };
953 if (found) break :blk i;
954 }
955 break :blk b.top_level_steps.count();
956 };
957 for (step_names, 0..) |step_name, i| {
958 const tls = b.top_level_steps.get(step_name).?;
959 print_node.last = i + 1 == last_index;
960 printTreeStep(b, &tls.step, run, t, &print_node, &step_stack_copy) catch {};
961 }
962 }
963 w.writeByte('\n') catch {};
964 }
965
966 if (run.watch or run.web_server != null) return;
967
968 // Perhaps in the future there could be an Advanced Options flag such as
969 // --debug-build-runner-leaks which would make this code return instead of
970 // calling exit.
971
972 const code: u8 = code: {
973 if (failure_count == 0) break :code 0; // success
974 if (run.error_style.verboseContext()) break :code 1; // failure; print build command
975 break :code 2; // failure; do not print build command
976 };
977 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};
978 process.exit(code);
979}
980
981const PrintNode = struct {
982 parent: ?*PrintNode,
983 last: bool = false,
984};
985
986fn printPrefix(node: *PrintNode, stderr: Io.Terminal) !void {
987 const parent = node.parent orelse return;
988 const writer = stderr.writer;
989 if (parent.parent == null) return;
990 try printPrefix(parent, stderr);
991 if (parent.last) {
992 try writer.writeAll(" ");
993 } else {
994 try writer.writeAll(switch (stderr.mode) {
995 .escape_codes => "\x1B\x28\x30\x78\x1B\x28\x42 ", // │
996 else => "| ",
997 });
998 }
999}
1000
1001fn printChildNodePrefix(stderr: Io.Terminal) !void {
1002 try stderr.writer.writeAll(switch (stderr.mode) {
1003 .escape_codes => "\x1B\x28\x30\x6d\x71\x1B\x28\x42 ", // └─
1004 else => "+- ",
1005 });
1006}
1007
1008fn printStepStatus(s: *Step, stderr: Io.Terminal, run: *const Run) !void {
1009 const writer = stderr.writer;
1010 switch (s.state) {
1011 .precheck_unstarted => unreachable,
1012 .precheck_started => unreachable,
1013 .precheck_done => unreachable,
1014
1015 .dependency_failure => {
1016 try stderr.setColor(.dim);
1017 try writer.writeAll(" transitive failure\n");
1018 try stderr.setColor(.reset);
1019 },
1020
1021 .success => {
1022 try stderr.setColor(.green);
1023 if (s.result_cached) {
1024 try writer.writeAll(" cached");
1025 } else if (s.test_results.test_count > 0) {
1026 const pass_count = s.test_results.passCount();
1027 assert(s.test_results.test_count == pass_count + s.test_results.skip_count);
1028 try writer.print(" {d} pass", .{pass_count});
1029 if (s.test_results.skip_count > 0) {
1030 try stderr.setColor(.reset);
1031 try writer.writeAll(", ");
1032 try stderr.setColor(.yellow);
1033 try writer.print("{d} skip", .{s.test_results.skip_count});
1034 }
1035 try stderr.setColor(.reset);
1036 try writer.print(" ({d} total)", .{s.test_results.test_count});
1037 } else {
1038 try writer.writeAll(" success");
1039 }
1040 try stderr.setColor(.reset);
1041 if (s.result_duration_ns) |ns| {
1042 try stderr.setColor(.dim);
1043 if (ns >= std.time.ns_per_min) {
1044 try writer.print(" {d}m", .{ns / std.time.ns_per_min});
1045 } else if (ns >= std.time.ns_per_s) {
1046 try writer.print(" {d}s", .{ns / std.time.ns_per_s});
1047 } else if (ns >= std.time.ns_per_ms) {
1048 try writer.print(" {d}ms", .{ns / std.time.ns_per_ms});
1049 } else if (ns >= std.time.ns_per_us) {
1050 try writer.print(" {d}us", .{ns / std.time.ns_per_us});
1051 } else {
1052 try writer.print(" {d}ns", .{ns});
1053 }
1054 try stderr.setColor(.reset);
1055 }
1056 if (s.result_peak_rss != 0) {
1057 const rss = s.result_peak_rss;
1058 try stderr.setColor(.dim);
1059 if (rss >= 1000_000_000) {
1060 try writer.print(" MaxRSS:{d}G", .{rss / 1000_000_000});
1061 } else if (rss >= 1000_000) {
1062 try writer.print(" MaxRSS:{d}M", .{rss / 1000_000});
1063 } else if (rss >= 1000) {
1064 try writer.print(" MaxRSS:{d}K", .{rss / 1000});
1065 } else {
1066 try writer.print(" MaxRSS:{d}B", .{rss});
1067 }
1068 try stderr.setColor(.reset);
1069 }
1070 try writer.writeAll("\n");
1071 },
1072 .skipped => {
1073 try stderr.setColor(.yellow);
1074 try writer.writeAll(" skipped\n");
1075 try stderr.setColor(.reset);
1076 },
1077 .skipped_oom => {
1078 try stderr.setColor(.yellow);
1079 try writer.writeAll(" skipped (not enough memory)");
1080 try stderr.setColor(.dim);
1081 try writer.print(" upper bound of {d} exceeded runner limit ({d})\n", .{ s.max_rss, run.available_rss });
1082 try stderr.setColor(.reset);
1083 },
1084 .failure => {
1085 try printStepFailure(s, stderr, false);
1086 try stderr.setColor(.reset);
1087 },
1088 }
1089}
1090
1091fn printStepFailure(s: *Step, stderr: Io.Terminal, dim: bool) !void {
1092 const w = stderr.writer;
1093 if (s.result_error_bundle.errorMessageCount() > 0) {
1094 try stderr.setColor(.red);
1095 try w.print(" {d} errors\n", .{
1096 s.result_error_bundle.errorMessageCount(),
1097 });
1098 } else if (!s.test_results.isSuccess()) {
1099 // These first values include all of the test "statuses". Every test is either passsed,
1100 // skipped, failed, crashed, or timed out.
1101 try stderr.setColor(.green);
1102 try w.print(" {d} pass", .{s.test_results.passCount()});
1103 try stderr.setColor(.reset);
1104 if (dim) try stderr.setColor(.dim);
1105 if (s.test_results.skip_count > 0) {
1106 try w.writeAll(", ");
1107 try stderr.setColor(.yellow);
1108 try w.print("{d} skip", .{s.test_results.skip_count});
1109 try stderr.setColor(.reset);
1110 if (dim) try stderr.setColor(.dim);
1111 }
1112 if (s.test_results.fail_count > 0) {
1113 try w.writeAll(", ");
1114 try stderr.setColor(.red);
1115 try w.print("{d} fail", .{s.test_results.fail_count});
1116 try stderr.setColor(.reset);
1117 if (dim) try stderr.setColor(.dim);
1118 }
1119 if (s.test_results.crash_count > 0) {
1120 try w.writeAll(", ");
1121 try stderr.setColor(.red);
1122 try w.print("{d} crash", .{s.test_results.crash_count});
1123 try stderr.setColor(.reset);
1124 if (dim) try stderr.setColor(.dim);
1125 }
1126 if (s.test_results.timeout_count > 0) {
1127 try w.writeAll(", ");
1128 try stderr.setColor(.red);
1129 try w.print("{d} timeout", .{s.test_results.timeout_count});
1130 try stderr.setColor(.reset);
1131 if (dim) try stderr.setColor(.dim);
1132 }
1133 try w.print(" ({d} total)", .{s.test_results.test_count});
1134
1135 // Memory leaks are intentionally written after the total, because is isn't a test *status*,
1136 // but just a flag that any tests -- even passed ones -- can have. We also use a different
1137 // separator, so it looks like:
1138 // 2 pass, 1 skip, 2 fail (5 total); 2 leaks
1139 if (s.test_results.leak_count > 0) {
1140 try w.writeAll("; ");
1141 try stderr.setColor(.red);
1142 try w.print("{d} leaks", .{s.test_results.leak_count});
1143 try stderr.setColor(.reset);
1144 if (dim) try stderr.setColor(.dim);
1145 }
1146
1147 // It's usually not helpful to know how many error logs there were because they tend to
1148 // just come with other errors (e.g. crashes and leaks print stack traces, and clean
1149 // failures print error traces). So only mention them if they're the only thing causing
1150 // the failure.
1151 const show_err_logs: bool = show: {
1152 var alt_results = s.test_results;
1153 alt_results.log_err_count = 0;
1154 break :show alt_results.isSuccess();
1155 };
1156 if (show_err_logs) {
1157 try w.writeAll("; ");
1158 try stderr.setColor(.red);
1159 try w.print("{d} error logs", .{s.test_results.log_err_count});
1160 try stderr.setColor(.reset);
1161 if (dim) try stderr.setColor(.dim);
1162 }
1163
1164 try w.writeAll("\n");
1165 } else if (s.result_error_msgs.items.len > 0) {
1166 try stderr.setColor(.red);
1167 try w.writeAll(" failure\n");
1168 } else {
1169 assert(s.result_stderr.len > 0);
1170 try stderr.setColor(.red);
1171 try w.writeAll(" w\n");
1172 }
1173}
1174
1175fn printTreeStep(
1176 b: *std.Build,
1177 s: *Step,
1178 run: *const Run,
1179 stderr: Io.Terminal,
1180 parent_node: *PrintNode,
1181 step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),
1182) !void {
1183 const writer = stderr.writer;
1184 const first = step_stack.swapRemove(s);
1185 const summary = run.summary;
1186 const skip = switch (summary) {
1187 .none, .line => unreachable,
1188 .all => false,
1189 .new => s.result_cached,
1190 .failures => s.state == .success,
1191 };
1192 if (skip) return;
1193 try printPrefix(parent_node, stderr);
1194
1195 if (parent_node.parent != null) {
1196 if (parent_node.last) {
1197 try printChildNodePrefix(stderr);
1198 } else {
1199 try writer.writeAll(switch (stderr.mode) {
1200 .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─
1201 else => "+- ",
1202 });
1203 }
1204 }
1205
1206 if (!first) try stderr.setColor(.dim);
1207
1208 // dep_prefix omitted here because it is redundant with the tree.
1209 try writer.writeAll(s.name);
1210
1211 if (first) {
1212 try printStepStatus(s, stderr, run);
1213
1214 const last_index = if (summary == .all) s.dependencies.items.len -| 1 else blk: {
1215 var i: usize = s.dependencies.items.len;
1216 while (i > 0) {
1217 i -= 1;
1218
1219 const step = s.dependencies.items[i];
1220 const found = switch (summary) {
1221 .all, .line, .none => unreachable,
1222 .failures => step.state != .success,
1223 .new => !step.result_cached,
1224 };
1225 if (found) break :blk i;
1226 }
1227 break :blk s.dependencies.items.len -| 1;
1228 };
1229 for (s.dependencies.items, 0..) |dep, i| {
1230 var print_node: PrintNode = .{
1231 .parent = parent_node,
1232 .last = i == last_index,
1233 };
1234 try printTreeStep(b, dep, run, stderr, &print_node, step_stack);
1235 }
1236 } else {
1237 if (s.dependencies.items.len == 0) {
1238 try writer.writeAll(" (reused)\n");
1239 } else {
1240 try writer.print(" (+{d} more reused dependencies)\n", .{
1241 s.dependencies.items.len,
1242 });
1243 }
1244 try stderr.setColor(.reset);
1245 }
1246}
1247
1248/// Traverse the dependency graph depth-first and make it undirected by having
1249/// steps know their dependants (they only know dependencies at start).
1250/// Along the way, check that there is no dependency loop, and record the steps
1251/// in traversal order in `step_stack`.
1252/// Each step has its dependencies traversed in random order, this accomplishes
1253/// two things:
1254/// - `step_stack` will be in randomized-depth-first order, so the build runner
1255/// spawns initial steps in a random order
1256/// - each step's `dependants` list is also filled in a random order, so that
1257/// when it finishes executing in `makeStep`, it spawns next steps to run in
1258/// random order
1259fn constructGraphAndCheckForDependencyLoop(
1260 gpa: Allocator,
1261 b: *std.Build,
1262 s: *Step,
1263 step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),
1264 rand: std.Random,
1265) !void {
1266 switch (s.state) {
1267 .precheck_started => {
1268 std.debug.print("dependency loop detected:\n {s}\n", .{s.name});
1269 return error.DependencyLoopDetected;
1270 },
1271 .precheck_unstarted => {
1272 s.state = .precheck_started;
1273
1274 try step_stack.ensureUnusedCapacity(gpa, s.dependencies.items.len);
1275
1276 // We dupe to avoid shuffling the steps in the summary, it depends
1277 // on s.dependencies' order.
1278 const deps = gpa.dupe(*Step, s.dependencies.items) catch @panic("OOM");
1279 defer gpa.free(deps);
1280
1281 rand.shuffle(*Step, deps);
1282
1283 for (deps) |dep| {
1284 try step_stack.put(gpa, dep, {});
1285 try dep.dependants.append(b.allocator, s);
1286 constructGraphAndCheckForDependencyLoop(gpa, b, dep, step_stack, rand) catch |err| {
1287 if (err == error.DependencyLoopDetected) {
1288 std.debug.print(" {s}\n", .{s.name});
1289 }
1290 return err;
1291 };
1292 }
1293
1294 s.state = .precheck_done;
1295 s.pending_deps = @intCast(s.dependencies.items.len);
1296 },
1297 .precheck_done => {},
1298
1299 // These don't happen until we actually run the step graph.
1300 .dependency_failure => unreachable,
1301 .success => unreachable,
1302 .failure => unreachable,
1303 .skipped => unreachable,
1304 .skipped_oom => unreachable,
1305 }
1306}
1307
1308/// Runs the "make" function of the single step `s`, updates its state, and then spawns newly-ready
1309/// dependant steps in `group`. If `s` makes an RSS claim (i.e. `s.max_rss != 0`), the caller must
1310/// have already subtracted this value from `run.available_rss`. This function will release the RSS
1311/// claim (i.e. add `s.max_rss` back into `run.available_rss`) and queue any viable memory-blocked
1312/// steps after "make" completes for `s`.
1313fn makeStep(
1314 group: *Io.Group,
1315 b: *std.Build,
1316 s: *Step,
1317 root_prog_node: std.Progress.Node,
1318 run: *Run,
1319) Io.Cancelable!void {
1320 const graph = b.graph;
1321 const io = graph.io;
1322 const gpa = run.gpa;
1323
1324 {
1325 const step_prog_node = root_prog_node.start(s.name, 0);
1326 defer step_prog_node.end();
1327
1328 if (run.web_server) |*ws| ws.updateStepStatus(s, .wip);
1329
1330 const new_state: Step.State = for (s.dependencies.items) |dep| {
1331 switch (@atomicLoad(Step.State, &dep.state, .monotonic)) {
1332 .precheck_unstarted => unreachable,
1333 .precheck_started => unreachable,
1334 .precheck_done => unreachable,
1335
1336 .failure,
1337 .dependency_failure,
1338 .skipped_oom,
1339 => break .dependency_failure,
1340
1341 .success, .skipped => {},
1342 }
1343 } else if (s.make(.{
1344 .progress_node = step_prog_node,
1345 .watch = run.watch,
1346 .web_server = if (run.web_server) |*ws| ws else null,
1347 .unit_test_timeout_ns = run.unit_test_timeout_ns,
1348 .gpa = gpa,
1349 })) state: {
1350 break :state .success;
1351 } else |err| switch (err) {
1352 error.MakeFailed => .failure,
1353 error.MakeSkipped => .skipped,
1354 };
1355
1356 @atomicStore(Step.State, &s.state, new_state, .monotonic);
1357
1358 switch (new_state) {
1359 .precheck_unstarted => unreachable,
1360 .precheck_started => unreachable,
1361 .precheck_done => unreachable,
1362
1363 .failure,
1364 .dependency_failure,
1365 .skipped_oom,
1366 => {
1367 if (run.web_server) |*ws| ws.updateStepStatus(s, .failure);
1368 std.Progress.setStatus(.failure_working);
1369 },
1370
1371 .success,
1372 .skipped,
1373 => {
1374 if (run.web_server) |*ws| ws.updateStepStatus(s, .success);
1375 },
1376 }
1377 }
1378
1379 // No matter the result, we want to display error/warning messages.
1380 if (s.result_error_bundle.errorMessageCount() > 0 or
1381 s.result_error_msgs.items.len > 0 or
1382 s.result_stderr.len > 0)
1383 {
1384 const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode);
1385 defer io.unlockStderr();
1386 printErrorMessages(gpa, s, .{}, stderr.terminal(), run.error_style, run.multiline_errors) catch {};
1387 }
1388
1389 if (s.max_rss != 0) {
1390 var dispatch_set: std.ArrayList(*Step) = .empty;
1391 defer dispatch_set.deinit(gpa);
1392
1393 // Release our RSS claim and kick off some blocked steps if possible. We use `dispatch_set`
1394 // as a staging buffer to avoid recursing into `makeStep` while `run.max_rss_mutex` is held.
1395 {
1396 try run.max_rss_mutex.lock(io);
1397 defer run.max_rss_mutex.unlock(io);
1398 run.available_rss += s.max_rss;
1399 dispatch_set.ensureUnusedCapacity(gpa, run.memory_blocked_steps.items.len) catch @panic("OOM");
1400 while (run.memory_blocked_steps.getLast()) |candidate| {
1401 if (run.available_rss < candidate.max_rss) break;
1402 assert(run.memory_blocked_steps.pop() == candidate);
1403 dispatch_set.appendAssumeCapacity(candidate);
1404 }
1405 }
1406 for (dispatch_set.items) |candidate| {
1407 group.async(io, makeStep, .{ group, b, candidate, root_prog_node, run });
1408 }
1409 }
1410
1411 for (s.dependants.items) |dependant| {
1412 // `.acq_rel` synchronizes with itself to ensure all dependencies' final states are visible when this hits 0.
1413 if (@atomicRmw(u32, &dependant.pending_deps, .Sub, 1, .acq_rel) == 1) {
1414 try stepReady(group, b, dependant, root_prog_node, run);
1415 }
1416 }
1417}
1418
1419fn stepReady(
1420 group: *Io.Group,
1421 b: *std.Build,
1422 s: *Step,
1423 root_prog_node: std.Progress.Node,
1424 run: *Run,
1425) !void {
1426 const io = b.graph.io;
1427 if (s.max_rss != 0) {
1428 try run.max_rss_mutex.lock(io);
1429 defer run.max_rss_mutex.unlock(io);
1430 if (run.available_rss < s.max_rss) {
1431 // Running this step right now could possibly exceed the allotted RSS.
1432 run.memory_blocked_steps.append(run.gpa, s) catch @panic("OOM");
1433 return;
1434 }
1435 run.available_rss -= s.max_rss;
1436 }
1437 group.async(io, makeStep, .{ group, b, s, root_prog_node, run });
1438}
1439
1440pub fn printErrorMessages(
1441 gpa: Allocator,
1442 failing_step: *Step,
1443 options: std.zig.ErrorBundle.RenderOptions,
1444 stderr: Io.Terminal,
1445 error_style: ErrorStyle,
1446 multiline_errors: MultilineErrors,
1447) !void {
1448 const writer = stderr.writer;
1449 if (error_style.verboseContext()) {
1450 // Provide context for where these error messages are coming from by
1451 // printing the corresponding Step subtree.
1452 var step_stack: std.ArrayList(*Step) = .empty;
1453 defer step_stack.deinit(gpa);
1454 try step_stack.append(gpa, failing_step);
1455 while (step_stack.items[step_stack.items.len - 1].dependants.items.len != 0) {
1456 try step_stack.append(gpa, step_stack.items[step_stack.items.len - 1].dependants.items[0]);
1457 }
1458
1459 // Now, `step_stack` has the subtree that we want to print, in reverse order.
1460 try stderr.setColor(.dim);
1461 var indent: usize = 0;
1462 while (step_stack.pop()) |s| : (indent += 1) {
1463 if (indent > 0) {
1464 try writer.splatByteAll(' ', (indent - 1) * 3);
1465 try printChildNodePrefix(stderr);
1466 }
1467
1468 try writer.writeAll(s.name);
1469
1470 if (s == failing_step) {
1471 try printStepFailure(s, stderr, true);
1472 } else {
1473 try writer.writeAll("\n");
1474 }
1475 }
1476 try stderr.setColor(.reset);
1477 } else {
1478 // Just print the failing step itself.
1479 try stderr.setColor(.dim);
1480 try writer.writeAll(failing_step.name);
1481 try printStepFailure(failing_step, stderr, true);
1482 try stderr.setColor(.reset);
1483 }
1484
1485 if (failing_step.result_stderr.len > 0) {
1486 try writer.writeAll(failing_step.result_stderr);
1487 if (!mem.endsWith(u8, failing_step.result_stderr, "\n")) {
1488 try writer.writeAll("\n");
1489 }
1490 }
1491
1492 try failing_step.result_error_bundle.renderToTerminal(options, stderr);
1493
1494 for (failing_step.result_error_msgs.items) |msg| {
1495 try stderr.setColor(.red);
1496 try writer.writeAll("error:");
1497 try stderr.setColor(.reset);
1498 if (std.mem.indexOfScalar(u8, msg, '\n') == null) {
1499 try writer.print(" {s}\n", .{msg});
1500 } else switch (multiline_errors) {
1501 .indent => {
1502 var it = std.mem.splitScalar(u8, msg, '\n');
1503 try writer.print(" {s}\n", .{it.first()});
1504 while (it.next()) |line| {
1505 try writer.print(" {s}\n", .{line});
1506 }
1507 },
1508 .newline => try writer.print("\n{s}\n", .{msg}),
1509 .none => try writer.print(" {s}\n", .{msg}),
1510 }
1511 }
1512
1513 if (error_style.verboseContext()) {
1514 if (failing_step.result_failed_command) |cmd_str| {
1515 try stderr.setColor(.red);
1516 try writer.writeAll("failed command: ");
1517 try stderr.setColor(.reset);
1518 try writer.writeAll(cmd_str);
1519 try writer.writeByte('\n');
1520 }
1521 }
1522
1523 try writer.writeByte('\n');
1524}
1525
1526fn printSteps(builder: *std.Build, w: *Writer) !void {
1527 const arena = builder.graph.arena;
1528 for (builder.top_level_steps.values()) |top_level_step| {
1529 const name = if (&top_level_step.step == builder.default_step)
1530 try fmt.allocPrint(arena, "{s} (default)", .{top_level_step.step.name})
1531 else
1532 top_level_step.step.name;
1533 try w.print(" {s:<28} {s}\n", .{ name, top_level_step.description });
1534 }
1535}
1536
1537fn printUsage(b: *std.Build, w: *Writer) !void {
1538 const arena = b.graph.arena;
1539
1540 try w.print(
1541 \\Usage: {s} build [steps] [options]
1542 \\
1543 \\Steps:
1544 \\
1545 , .{b.graph.zig_exe});
1546 try printSteps(b, w);
1547 try w.writeAll(
1548 \\
1549 \\Project-Specific Options:
1550 \\
1551 );
1552
1553 if (b.available_options_list.items.len == 0) {
1554 try w.print(" (none)\n", .{});
1555 } else {
1556 for (b.available_options_list.items) |option| {
1557 const name = try fmt.allocPrint(arena, " -D{s}=[{t}]", .{ option.name, option.type_id });
1558 try w.print("{s:<30} {s}\n", .{ name, option.description });
1559 if (option.enum_options) |enum_options| {
1560 const padding: [33]u8 = @splat(' ');
1561 try w.writeAll(padding ++ "Supported Values:\n");
1562 for (enum_options) |enum_option| {
1563 try w.print(padding ++ " {s}\n", .{enum_option});
1564 }
1565 }
1566 }
1567 }
1568
1569 try w.writeAll(
1570 \\
1571 \\System Integration Options:
1572 \\ --search-prefix [path] Add a path to look for binaries, libraries, headers
1573 \\ --sysroot [path] Set the system root directory (usually /)
1574 \\ --libc [file] Provide a file which specifies libc paths
1575 \\
1576 \\ --system [pkgdir] Disable package fetching; enable all integrations
1577 \\ -fsys=[name] Enable a system integration
1578 \\ -fno-sys=[name] Disable a system integration
1579 \\
1580 \\ -fdarling, -fno-darling Integration with system-installed Darling to
1581 \\ execute macOS programs on Linux hosts
1582 \\ (default: no)
1583 \\ -fqemu, -fno-qemu Integration with system-installed QEMU to execute
1584 \\ foreign-architecture programs on Linux hosts
1585 \\ (default: no)
1586 \\ --libc-runtimes [path] Enhances QEMU integration by providing dynamic libc
1587 \\ (e.g. glibc or musl) built for multiple foreign
1588 \\ architectures, allowing execution of non-native
1589 \\ programs that link with libc.
1590 \\ -frosetta, -fno-rosetta Rely on Rosetta to execute x86_64 programs on
1591 \\ ARM64 macOS hosts. (default: no)
1592 \\ -fwasmtime, -fno-wasmtime Integration with system-installed wasmtime to
1593 \\ execute WASI binaries. (default: no)
1594 \\ -fwine, -fno-wine Integration with system-installed Wine to execute
1595 \\ Windows programs on Linux hosts. (default: no)
1596 \\
1597 \\ Available System Integrations: Enabled:
1598 \\
1599 );
1600 if (b.graph.system_library_options.entries.len == 0) {
1601 try w.writeAll(" (none) -\n");
1602 } else {
1603 for (b.graph.system_library_options.keys(), b.graph.system_library_options.values()) |k, v| {
1604 const status = switch (v) {
1605 .declared_enabled => "yes",
1606 .declared_disabled => "no",
1607 .user_enabled, .user_disabled => unreachable, // already emitted error
1608 };
1609 try w.print(" {s:<43} {s}\n", .{ k, status });
1610 }
1611 }
1612
1613 try w.writeAll(
1614 \\
1615 \\General Options:
1616 \\ -h, --help Print this help and exit
1617 \\ -l, --list-steps Print available steps
1618 \\
1619 \\ -p, --prefix [path] Where to install files (default: zig-out)
1620 \\ --prefix-lib-dir [path] Where to install libraries
1621 \\ --prefix-exe-dir [path] Where to install executables
1622 \\ --prefix-include-dir [path] Where to install C header files
1623 \\ --release[=mode] Request release mode, optionally specifying a
1624 \\ preferred optimization mode: fast, safe, small
1625 \\
1626 \\ --verbose Print commands before executing them
1627 \\ --color [auto|off|on] Enable or disable colored error messages
1628 \\ --error-style [style] Control how build errors are printed
1629 \\ verbose (Default) Report errors with full context
1630 \\ minimal Report errors after summary, excluding context like command lines
1631 \\ verbose_clear Like 'verbose', but clear the terminal at the start of each update
1632 \\ minimal_clear Like 'minimal', but clear the terminal at the start of each update
1633 \\ --multiline-errors [style] Control how multi-line error messages are printed
1634 \\ indent (Default) Indent non-initial lines to align with initial line
1635 \\ newline Include a leading newline so that the error message is on its own lines
1636 \\ none Print as usual so the first line is misaligned
1637 \\ --summary [mode] Control the printing of the build summary
1638 \\ all Print the build summary in its entirety
1639 \\ new Omit cached steps
1640 \\ failures (Default if short-lived) Only print failed steps
1641 \\ line (Default if long-lived) Only print the single-line summary
1642 \\ none Do not print the build summary
1643 \\ -j<N> Limit concurrent jobs (default is to use all CPU cores)
1644 \\ --maxrss <bytes> Limit memory usage (default is to use available memory)
1645 \\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss
1646 \\ --test-timeout <timeout> Limit execution time of unit tests, terminating if exceeded.
1647 \\ The timeout must include a unit: ns, us, ms, s, m, h
1648 \\ --watch Continuously rebuild when source files are modified
1649 \\ --debounce <ms> Delay before rebuilding after changed file detected
1650 \\ --webui[=ip] Enable the web interface on the given IP address
1651 \\ --fuzz[=limit] Continuously search for unit test failures with an optional
1652 \\ limit to the max number of iterations. The argument supports
1653 \\ an optional 'K', 'M', or 'G' suffix (e.g. '10K'). Implies
1654 \\ '--webui' when no limit is specified.
1655 \\ --time-report Force full rebuild and provide detailed information on
1656 \\ compilation time of Zig source code (implies '--webui')
1657 \\ -fincremental Enable incremental compilation
1658 \\ -fno-incremental Disable incremental compilation
1659 \\
1660 \\Package Management Options:
1661 \\ --fetch[=mode] Fetch dependency tree (optionally choose laziness) and exit
1662 \\ needed (Default) Lazy dependencies are fetched as needed
1663 \\ all Lazy dependencies are always fetched
1664 \\ --fork=[path] Override one or more projects from dependency tree
1665 \\
1666 \\Advanced Options:
1667 \\ -freference-trace[=num] How many lines of reference trace should be shown per compile error
1668 \\ -fno-reference-trace Disable reference trace
1669 \\ -fallow-so-scripts Allows .so files to be GNU ld scripts
1670 \\ -fno-allow-so-scripts (default) .so files must be ELF files
1671 \\ --build-file [file] Override path to build.zig
1672 \\ --cache-dir [path] Override path to local Zig cache directory
1673 \\ --global-cache-dir [path] Override path to global Zig cache directory
1674 \\ --zig-lib-dir [arg] Override path to Zig lib directory
1675 \\ --build-runner [file] Override path to build runner
1676 \\ --seed [integer] For shuffling dependency traversal order (default: random)
1677 \\ --build-id[=style] At a minor link-time expense, embeds a build ID in binaries
1678 \\ fast 8-byte non-cryptographic hash (COFF, ELF, WASM)
1679 \\ sha1, tree 20-byte cryptographic hash (ELF, WASM)
1680 \\ md5 16-byte cryptographic hash (ELF)
1681 \\ uuid 16-byte random UUID (ELF, WASM)
1682 \\ 0x[hexstring] Constant ID, maximum 32 bytes (ELF, WASM)
1683 \\ none (default) No build ID
1684 \\ --debug-log [scope] Enable debugging the compiler
1685 \\ --debug-pkg-config Fail if unknown pkg-config flags encountered
1686 \\ --debug-rt Debug compiler runtime libraries
1687 \\ --verbose-link Enable compiler debug output for linking
1688 \\ --verbose-air Enable compiler debug output for Zig AIR
1689 \\ --verbose-llvm-ir[=file] Enable compiler debug output for LLVM IR
1690 \\ --verbose-llvm-bc=[file] Enable compiler debug output for LLVM BC
1691 \\ --verbose-cimport Enable compiler debug output for C imports
1692 \\ --verbose-cc Enable compiler debug output for C compilation
1693 \\ --verbose-llvm-cpu-features Enable compiler debug output for LLVM CPU features
1694 \\
1695 );
1696}
1697
1698fn nextArg(args: []const [:0]const u8, idx: *usize) ?[:0]const u8 {
1699 if (idx.* >= args.len) return null;
1700 defer idx.* += 1;
1701 return args[idx.*];
1702}
1703
1704fn nextArgOrFatal(args: []const [:0]const u8, idx: *usize) [:0]const u8 {
1705 return nextArg(args, idx) orelse {
1706 std.debug.print("expected argument after '{s}'\n access the help menu with 'zig build -h'\n", .{args[idx.* - 1]});
1707 process.exit(1);
1708 };
1709}
1710
1711fn argsRest(args: []const [:0]const u8, idx: usize) ?[]const [:0]const u8 {
1712 if (idx >= args.len) return null;
1713 return args[idx..];
1714}
1715
1716const Color = std.zig.Color;
1717const ErrorStyle = enum {
1718 verbose,
1719 minimal,
1720 verbose_clear,
1721 minimal_clear,
1722 fn verboseContext(s: ErrorStyle) bool {
1723 return switch (s) {
1724 .verbose, .verbose_clear => true,
1725 .minimal, .minimal_clear => false,
1726 };
1727 }
1728 fn clearOnUpdate(s: ErrorStyle) bool {
1729 return switch (s) {
1730 .verbose, .minimal => false,
1731 .verbose_clear, .minimal_clear => true,
1732 };
1733 }
1734};
1735const MultilineErrors = enum { indent, newline, none };
1736const Summary = enum { all, new, failures, line, none };
1737
1738fn fatalWithHint(comptime f: []const u8, args: anytype) noreturn {
1739 std.debug.print(f ++ "\n access the help menu with 'zig build -h'\n", args);
1740 process.exit(1);
1741}
1742
1743fn validateSystemLibraryOptions(b: *std.Build) void {
1744 var bad = false;
1745 for (b.graph.system_library_options.keys(), b.graph.system_library_options.values()) |k, v| {
1746 switch (v) {
1747 .user_disabled, .user_enabled => {
1748 // The user tried to enable or disable a system library integration, but
1749 // the build script did not recognize that option.
1750 std.debug.print("system library name not recognized by build script: '{s}'\n", .{k});
1751 bad = true;
1752 },
1753 .declared_disabled, .declared_enabled => {},
1754 }
1755 }
1756 if (bad) {
1757 std.debug.print(" access the help menu with 'zig build -h'\n", .{});
1758 process.exit(1);
1759 }
1760}
1761
1762/// Starting from all top-level steps in `b`, traverses the entire step graph
1763/// and adds all step dependencies implied by module graphs.
1764fn createModuleDependencies(b: *std.Build) Allocator.Error!void {
1765 const arena = b.graph.arena;
1766
1767 var all_steps: std.AutoArrayHashMapUnmanaged(*Step, void) = .empty;
1768 var next_step_idx: usize = 0;
1769
1770 try all_steps.ensureUnusedCapacity(arena, b.top_level_steps.count());
1771 for (b.top_level_steps.values()) |tls| {
1772 all_steps.putAssumeCapacityNoClobber(&tls.step, {});
1773 }
1774
1775 while (next_step_idx < all_steps.count()) {
1776 const step = all_steps.keys()[next_step_idx];
1777 next_step_idx += 1;
1778
1779 // Set up any implied dependencies for this step. It's important that we do this first, so
1780 // that the loop below discovers steps implied by the module graph.
1781 try createModuleDependenciesForStep(step);
1782
1783 try all_steps.ensureUnusedCapacity(arena, step.dependencies.items.len);
1784 for (step.dependencies.items) |other_step| {
1785 all_steps.putAssumeCapacity(other_step, {});
1786 }
1787 }
1788}
1789
1790/// If the given `Step` is a `Step.Compile`, adds any dependencies for that step which
1791/// are implied by the module graph rooted at `step.cast(Step.Compile).?.root_module`.
1792fn createModuleDependenciesForStep(step: *Step) Allocator.Error!void {
1793 const root_module = if (step.cast(Step.Compile)) |cs| root: {
1794 break :root cs.root_module;
1795 } else return; // not a compile step so no module dependencies
1796
1797 // Starting from `root_module`, discover all modules in this graph.
1798 const modules = root_module.getGraph().modules;
1799
1800 // For each of those modules, set up the implied step dependencies.
1801 for (modules) |mod| {
1802 if (mod.root_source_file) |lp| lp.addStepDependencies(step);
1803 for (mod.include_dirs.items) |include_dir| switch (include_dir) {
1804 .path,
1805 .path_system,
1806 .path_after,
1807 .framework_path,
1808 .framework_path_system,
1809 .embed_path,
1810 => |lp| lp.addStepDependencies(step),
1811
1812 .other_step => |other| {
1813 other.getEmittedIncludeTree().addStepDependencies(step);
1814 step.dependOn(&other.step);
1815 },
1816
1817 .config_header_step => |other| step.dependOn(&other.step),
1818 };
1819 for (mod.lib_paths.items) |lp| lp.addStepDependencies(step);
1820 for (mod.rpaths.items) |rpath| switch (rpath) {
1821 .lazy_path => |lp| lp.addStepDependencies(step),
1822 .special => {},
1823 };
1824 for (mod.link_objects.items) |link_object| switch (link_object) {
1825 .static_path,
1826 .assembly_file,
1827 => |lp| lp.addStepDependencies(step),
1828 .other_step => |other| step.dependOn(&other.step),
1829 .system_lib => {},
1830 .c_source_file => |source| source.file.addStepDependencies(step),
1831 .c_source_files => |source_files| source_files.root.addStepDependencies(step),
1832 .win32_resource_file => |rc_source| {
1833 rc_source.file.addStepDependencies(step);
1834 for (rc_source.include_paths) |lp| lp.addStepDependencies(step);
1835 },
1836 };
1837 }
1838}
1839
1840var stdio_buffer_allocation: [256]u8 = undefined;
1841var stdout_writer_allocation: Io.File.Writer = undefined;
1842
1843fn initStdoutWriter(io: Io) *Writer {
1844 stdout_writer_allocation = Io.File.stdout().writerStreaming(io, &stdio_buffer_allocation);
1845 return &stdout_writer_allocation.interface;
1846}
1847
1848fn cleanTmpFiles(io: Io, steps: []const *Step) void {
1849 for (steps) |step| {
1850 const wf = step.cast(std.Build.Step.WriteFile) orelse continue;
1851 if (wf.mode != .tmp) continue;
1852 const path = wf.generated_directory.path orelse continue;
1853 Io.Dir.cwd().deleteTree(io, path) catch |err| {
1854 std.log.warn("failed to delete {s}: {t}", .{ path, err });
1855 };
1856 }
1857}
lib/compiler/configurer.zig created+1402
......@@ -0,0 +1,1402 @@
1const builtin = @import("builtin");
2
3const std = @import("std");
4const Allocator = std.mem.Allocator;
5const Color = std.zig.Color;
6const Configuration = std.Build.Configuration;
7const File = std.Io.File;
8const Io = std.Io;
9const Step = std.Build.Step;
10const Writer = std.Io.Writer;
11const assert = std.debug.assert;
12const fatal = std.process.fatal;
13const fmt = std.fmt;
14const log = std.log;
15const mem = std.mem;
16const process = std.process;
17
18pub const root = @import("@build");
19pub const dependencies = @import("@dependencies");
20
21pub const std_options: std.Options = .{
22 .side_channels_mitigations = .none,
23 .http_disable_tls = true,
24};
25
26pub fn main(init: process.Init.Minimal) !void {
27 var arena_allocator: std.heap.ArenaAllocator = .init(std.heap.page_allocator);
28 defer arena_allocator.deinit();
29 const arena = arena_allocator.allocator();
30
31 // The configurer is always short-lived because all it does is serialize
32 // the configuration, which is picked up by a separate maker process.
33 var threaded: std.Io.Threaded = .init(arena, .{
34 .environ = init.environ,
35 .argv0 = .init(init.args),
36 });
37 defer threaded.deinit();
38 const io = threaded.io();
39
40 const args = try init.args.toSlice(arena);
41
42 var arg_i: usize = 1; // Skip own executable name.
43
44 const zig_exe = expectArgOrFatal(args, &arg_i, "--zig");
45 const build_root_sub_path = expectArgOrFatal(args, &arg_i, "--build-root");
46
47 var graph: std.Build.Graph = .{
48 .io = io,
49 .arena = arena,
50 .environ_map = try init.environ.createMap(arena),
51 // TODO get this from parent process instead
52 .host = .{
53 .query = .{},
54 .result = try std.zig.system.resolveTargetQuery(io, .{}),
55 },
56 .generated_files = .empty,
57 .zig_exe = zig_exe,
58
59 // Created before running the user's configure script so that some things
60 // can be added during script execution such as strings.
61 //
62 // Use of arena here is load-bearing because `std.Build.dupe` is
63 // implemented by string internment, and then returning the interned
64 // slice. When the string bytes array is reallocated, that reference
65 // must stay alive.
66 .wip_configuration = .init(arena),
67 };
68 assert(try graph.wip_configuration.addString("") == .empty);
69 assert(try graph.wip_configuration.addString("root") == .root);
70
71 const cwd: Io.Dir = .cwd();
72
73 const build_root: std.Build.Cache.Path = .{
74 .root_dir = .{
75 .handle = try cwd.openDir(io, build_root_sub_path, .{}),
76 .path = build_root_sub_path,
77 },
78 };
79
80 const builder = try std.Build.create(&graph, build_root, dependencies.root_deps);
81
82 var color: Color = .auto;
83
84 while (nextArg(args, &arg_i)) |arg| {
85 if (mem.cutPrefix(u8, arg, "-D")) |option_contents| {
86 if (option_contents.len == 0)
87 fatalWithHint("expected option name after '-D'", .{});
88 if (mem.indexOfScalar(u8, option_contents, '=')) |name_end| {
89 const option_name = option_contents[0..name_end];
90 const option_value = option_contents[name_end + 1 ..];
91 if (try builder.addUserInputOption(option_name, option_value))
92 fatal(" access the help menu with 'zig build -h'", .{});
93 } else {
94 if (try builder.addUserInputFlag(option_contents))
95 fatal(" access the help menu with 'zig build -h'", .{});
96 }
97 } else if (mem.cutPrefix(u8, arg, "-fsys=")) |name| {
98 try graph.system_integration_options.put(arena, name, .user_enabled);
99 } else if (mem.cutPrefix(u8, arg, "-fno-sys=")) |name| {
100 try graph.system_integration_options.put(arena, name, .user_disabled);
101 } else if (mem.eql(u8, arg, "--release")) {
102 graph.release_mode = .any;
103 } else if (mem.cutPrefix(u8, arg, "--release=")) |rest| {
104 graph.release_mode = std.meta.stringToEnum(std.Build.ReleaseMode, rest) orelse {
105 fatalWithHint("expected --release=[off|any|fast|safe|small]; found: {s}", .{arg});
106 };
107 } else if (mem.cutPrefix(u8, arg, "--color=")) |rest| {
108 color = std.meta.stringToEnum(Color, rest) orelse
109 fatalWithHint("expected --color=[auto|on|off]; found: {s}", .{arg});
110 } else if (mem.eql(u8, arg, "--system")) {
111 // The usage text shows another argument after this parameter
112 // but it is handled by the parent process. The build runner
113 // only sees this flag.
114 graph.system_package_mode = true;
115 } else if (mem.eql(u8, arg, "--verbose")) {
116 graph.verbose = true;
117 } else if (mem.cutPrefix(u8, arg, "--cache-poison=")) |rest| {
118 graph.cache_poison = std.meta.stringToEnum(std.Build.Graph.CachePoison, rest) orelse
119 fatalWithHint("expected --cache-poison=[pure|poisoned|disallowed|ignored]; found: {s}", .{arg});
120 } else if (mem.eql(u8, arg, "--search-prefix")) {
121 try graph.search_prefixes.append(arena, nextArgOrFatal(args, &arg_i));
122 } else {
123 fatalWithHint("unrecognized argument: {s}", .{arg});
124 }
125 }
126
127 const NO_COLOR = std.zig.EnvVar.NO_COLOR.isSet(&graph.environ_map);
128 const CLICOLOR_FORCE = std.zig.EnvVar.CLICOLOR_FORCE.isSet(&graph.environ_map);
129
130 graph.stderr_mode = switch (color) {
131 .auto => try .detect(io, .stderr(), NO_COLOR, CLICOLOR_FORCE),
132 .on => .escape_codes,
133 .off => .no_color,
134 };
135
136 try builder.runBuild(root);
137
138 if (builder.validateUserInputDidItFail()) {
139 fatal(" access the help menu with 'zig build -h'", .{});
140 }
141
142 try serializePackageOptions(builder, &graph.wip_configuration);
143 try serializeSystemIntegrationOptions(&graph, &graph.wip_configuration);
144
145 var stdout_buffer: [1024]u8 = undefined;
146 var file_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer);
147 serialize(builder, &graph.wip_configuration, &file_writer.interface) catch |err| switch (err) {
148 error.WriteFailed => fatal("failed to write configuration output: {t}", .{file_writer.err.?}),
149 error.OutOfMemory => |e| return e,
150 };
151 file_writer.flush() catch |err| fatal("failed to write configuration output: {t}", .{err});
152
153 // This executable is short-lived and run in Debug mode, so we'd rather
154 // have `zig build` run faster than catch resource leaks in the user's
155 // build.zig script (or, frankly, this configure runner), therefore we call
156 // exit directly here rather than cleanExit.
157 process.exit(0);
158}
159
160const Serialize = struct {
161 arena: Allocator,
162 wc: *Configuration.Wip,
163 module_map: std.AutoArrayHashMapUnmanaged(*std.Build.Module, Configuration.Module.Index) = .empty,
164 package_map: std.AutoArrayHashMapUnmanaged(*std.Build, Configuration.Package.Index) = .empty,
165 /// Index corresponds to `Configuration.steps` index.
166 step_map: std.AutoArrayHashMapUnmanaged(*Step, void) = .empty,
167
168 fn builderToPackage(s: *Serialize, b: *std.Build) !Configuration.Package.Index {
169 if (b.pkg_hash.len == 0) return .root;
170 const arena = s.arena;
171 const wc = s.wc;
172 const gop = try s.package_map.getOrPut(arena, b);
173 if (!gop.found_existing) {
174 gop.value_ptr.* = try wc.addExtra(Configuration.Package, .{
175 .hash = try wc.addString(b.pkg_hash),
176 .dep_prefix = try wc.addString(b.dep_prefix),
177 .root_path = try wc.addString(try b.root.toString(arena)),
178 });
179 }
180 return gop.value_ptr.*;
181 }
182
183 fn addOptionalLazyPathEnum(s: *Serialize, lp: ?std.Build.LazyPath) !Configuration.LazyPath.OptionalIndex {
184 const wc = s.wc;
185 return @enumFromInt(switch (lp orelse return .none) {
186 .src_path => |src_path| i: {
187 const sub_path = try wc.addString(src_path.sub_path);
188 break :i try wc.addExtraErased(Configuration.LazyPath.SourcePath, .{
189 .owner = try s.builderToPackage(src_path.owner),
190 .sub_path = sub_path,
191 });
192 },
193 .generated => |generated| i: {
194 const sub_path = try wc.addString(generated.sub_path);
195 break :i try wc.addExtraErased(Configuration.LazyPath.Generated, .{
196 .flags = .{ .up = @intCast(generated.up) },
197 .index = generated.index,
198 .sub_path = sub_path,
199 });
200 },
201 .cwd_relative => |cwd_relative_sub_path| i: {
202 const sub_path = try wc.addString(cwd_relative_sub_path);
203 break :i try wc.addExtraErased(Configuration.LazyPath.Relative, .{
204 .flags = .{ .base = .cwd },
205 .sub_path = sub_path,
206 });
207 },
208 .relative => |relative| i: {
209 break :i try wc.addExtraErased(Configuration.LazyPath.Relative, .{
210 .flags = .{ .base = relative.base },
211 .sub_path = try wc.addString(relative.sub_path),
212 });
213 },
214 .dependency => |dependency| i: {
215 const sub_path = try wc.addString(dependency.sub_path);
216 break :i try wc.addExtraErased(Configuration.LazyPath.SourcePath, .{
217 .owner = try s.builderToPackage(dependency.dependency.builder),
218 .sub_path = sub_path,
219 });
220 },
221 });
222 }
223
224 fn addOptionalLazyPath(s: *Serialize, lp: ?std.Build.LazyPath) !?Configuration.LazyPath.Index {
225 return (try addOptionalLazyPathEnum(s, lp)).unwrap();
226 }
227
228 fn addLazyPath(s: *Serialize, lp: std.Build.LazyPath) !Configuration.LazyPath.Index {
229 return @enumFromInt(@intFromEnum(try addOptionalLazyPathEnum(s, lp)));
230 }
231
232 fn addOptionalSemVer(s: *Serialize, sem_ver: ?std.SemanticVersion) !?Configuration.String {
233 return if (sem_ver) |sv| try s.wc.addSemVer(sv) else null;
234 }
235
236 fn addOptionalString(s: *Serialize, opt_slice: ?[]const u8) !?Configuration.String {
237 return if (opt_slice) |slice| try s.wc.addString(slice) else null;
238 }
239
240 fn addSystemLib(s: *Serialize, sl: *const std.Build.Module.SystemLib) !Configuration.SystemLib.Index {
241 const wc = s.wc;
242 return try wc.addDeduped(Configuration.SystemLib, .{
243 .flags = .{
244 .needed = sl.needed,
245 .weak = sl.weak,
246 .use_pkg_config = sl.use_pkg_config,
247 .preferred_link_mode = sl.preferred_link_mode,
248 .search_strategy = sl.search_strategy,
249 },
250 .name = try wc.addString(sl.name),
251 });
252 }
253
254 fn addCSourceFile(s: *Serialize, csf: *const std.Build.Module.CSourceFile) !Configuration.CSourceFile.Index {
255 const wc = s.wc;
256 const args = try initStringList(s, csf.flags);
257 return try wc.addExtra(Configuration.CSourceFile, .{
258 .flags = .{
259 .args_len = @intCast(args.len),
260 .lang = .init(csf.language),
261 },
262 .file = try addLazyPath(s, csf.file),
263 .args = .{ .slice = args },
264 });
265 }
266
267 fn addCSourceFiles(s: *Serialize, csf: *const std.Build.Module.CSourceFiles) !Configuration.CSourceFiles.Index {
268 const wc = s.wc;
269 const sub_paths = try initStringList(s, csf.files);
270 const args = try initStringList(s, csf.flags);
271 return try wc.addExtra(Configuration.CSourceFiles, .{
272 .flags = .{
273 .args_len = @intCast(args.len),
274 .lang = .init(csf.language),
275 },
276 .root = try addLazyPath(s, csf.root),
277 .sub_paths = .{ .slice = sub_paths },
278 .args = .{ .slice = args },
279 });
280 }
281
282 fn addRcSourceFile(s: *Serialize, rsf: *const std.Build.Module.RcSourceFile) !Configuration.RcSourceFile.Index {
283 const wc = s.wc;
284 const include_paths = try initLazyPathList(s, rsf.include_paths);
285 const args = try initStringList(s, rsf.flags);
286 return try wc.addExtra(Configuration.RcSourceFile, .{
287 .flags = .{
288 .args_len = @intCast(args.len),
289 .include_paths = include_paths.len != 0,
290 },
291 .file = try addLazyPath(s, rsf.file),
292 .include_paths = .{ .slice = include_paths },
293 .args = .{ .slice = args },
294 });
295 }
296
297 fn addEnvironMap(s: *Serialize, opt_map: ?*std.process.Environ.Map) !?Configuration.EnvironMap.Index {
298 const wc = s.wc;
299 const map = opt_map orelse return null;
300 return try wc.addDeduped(Configuration.EnvironMap, .{
301 .keys = try wc.addStringList(map.array_hash_map.keys()),
302 .values = try wc.addStringList(map.array_hash_map.values()),
303 });
304 }
305
306 fn initArgsList(s: *Serialize, args: []const Step.Run.Arg) ![]const Configuration.Step.Run.Arg.Index {
307 const wc = s.wc;
308 const result = try s.arena.alloc(Configuration.Step.Run.Arg.Index, args.len);
309 for (result, args) |*dest, src| {
310 dest.* = try wc.addExtra(Configuration.Step.Run.Arg, switch (src) {
311 .artifact => |a| .{
312 .flags = .{
313 .tag = .artifact,
314 .prefix = a.prefix.len != 0,
315 .suffix = false,
316 .basename = false,
317 .path = false,
318 .producer = true,
319 .generated = false,
320 .dep_file = false,
321 },
322 .prefix = .{ .value = if (a.prefix.len != 0) try wc.addString(a.prefix) else null },
323 .suffix = .{ .value = null },
324 .basename = .{ .value = null },
325 .path = .{ .value = null },
326 .producer = .{ .value = stepIndex(s, &a.artifact.step) },
327 .generated = .{ .value = null },
328 },
329 .lazy_path => |a| .{
330 .flags = .{
331 .tag = .path_file,
332 .prefix = a.prefix.len != 0,
333 .suffix = false,
334 .basename = false,
335 .path = true,
336 .producer = false,
337 .generated = false,
338 .dep_file = false,
339 },
340 .prefix = .{ .value = if (a.prefix.len != 0) try wc.addString(a.prefix) else null },
341 .suffix = .{ .value = null },
342 .basename = .{ .value = null },
343 .path = .{ .value = try addLazyPath(s, a.lazy_path) },
344 .producer = .{ .value = null },
345 .generated = .{ .value = null },
346 },
347 .decorated_directory => |a| .{
348 .flags = .{
349 .tag = .path_directory,
350 .prefix = a.prefix.len != 0,
351 .suffix = a.suffix.len != 0,
352 .basename = false,
353 .path = true,
354 .producer = false,
355 .generated = false,
356 .dep_file = false,
357 },
358 .prefix = .{ .value = if (a.prefix.len != 0) try wc.addString(a.prefix) else null },
359 .suffix = .{ .value = if (a.suffix.len != 0) try wc.addString(a.suffix) else null },
360 .basename = .{ .value = null },
361 .path = .{ .value = try addLazyPath(s, a.lazy_path) },
362 .producer = .{ .value = null },
363 .generated = .{ .value = null },
364 },
365 .file_content => |a| .{
366 .flags = .{
367 .tag = .file_content,
368 .prefix = a.prefix.len != 0,
369 .suffix = false,
370 .basename = false,
371 .path = true,
372 .producer = false,
373 .generated = false,
374 .dep_file = false,
375 },
376 .prefix = .{ .value = if (a.prefix.len != 0) try wc.addString(a.prefix) else null },
377 .suffix = .{ .value = null },
378 .basename = .{ .value = null },
379 .path = .{ .value = try addLazyPath(s, a.lazy_path) },
380 .producer = .{ .value = null },
381 .generated = .{ .value = null },
382 },
383 .bytes => |a| .{
384 .flags = .{
385 .tag = .string,
386 .prefix = true,
387 .suffix = false,
388 .basename = false,
389 .path = false,
390 .producer = false,
391 .generated = false,
392 .dep_file = false,
393 },
394 .prefix = .{ .value = try wc.addString(a) },
395 .suffix = .{ .value = null },
396 .basename = .{ .value = null },
397 .path = .{ .value = null },
398 .producer = .{ .value = null },
399 .generated = .{ .value = null },
400 },
401 .output_file, .output_file_dep => |a, tag| .{
402 .flags = .{
403 .tag = .output_file,
404 .prefix = a.prefix.len != 0,
405 .suffix = false,
406 .basename = a.basename.len != 0,
407 .path = false,
408 .producer = false,
409 .generated = true,
410 .dep_file = tag == .output_file_dep,
411 },
412 .prefix = .{ .value = if (a.prefix.len != 0) try wc.addString(a.prefix) else null },
413 .suffix = .{ .value = null },
414 .basename = .{ .value = if (a.basename.len != 0) try wc.addString(a.basename) else null },
415 .path = .{ .value = null },
416 .producer = .{ .value = null },
417 .generated = .{ .value = a.generated_file },
418 },
419 .output_directory => |a| .{
420 .flags = .{
421 .tag = .output_directory,
422 .prefix = a.prefix.len != 0,
423 .suffix = false,
424 .basename = a.basename.len != 0,
425 .path = false,
426 .producer = false,
427 .generated = true,
428 .dep_file = false,
429 },
430 .prefix = .{ .value = if (a.prefix.len != 0) try wc.addString(a.prefix) else null },
431 .suffix = .{ .value = null },
432 .basename = .{ .value = if (a.basename.len != 0) try wc.addString(a.basename) else null },
433 .path = .{ .value = null },
434 .producer = .{ .value = null },
435 .generated = .{ .value = a.generated_file },
436 },
437 .passthru => .{
438 .flags = .{
439 .tag = .passthru,
440 .prefix = false,
441 .suffix = false,
442 .basename = false,
443 .path = false,
444 .producer = false,
445 .generated = false,
446 .dep_file = false,
447 },
448 .prefix = .{ .value = null },
449 .suffix = .{ .value = null },
450 .basename = .{ .value = null },
451 .path = .{ .value = null },
452 .producer = .{ .value = null },
453 .generated = .{ .value = null },
454 },
455 });
456 }
457 return result;
458 }
459
460 fn initIncludeDirList(
461 s: *Serialize,
462 list: []const std.Build.Module.IncludeDir,
463 ) ![]const Configuration.Module.IncludeDir {
464 const result = try s.arena.alloc(Configuration.Module.IncludeDir, list.len);
465 for (result, list) |*dest, src| dest.* = switch (src) {
466 .path => |lp| .{ .path = try addLazyPath(s, lp) },
467 .path_system => |lp| .{ .path_system = try addLazyPath(s, lp) },
468 .path_after => |lp| .{ .path_after = try addLazyPath(s, lp) },
469 .framework_path => |lp| .{ .framework_path = try addLazyPath(s, lp) },
470 .framework_path_system => |lp| .{ .framework_path_system = try addLazyPath(s, lp) },
471 .embed_path => |lp| .{ .embed_path = try addLazyPath(s, lp) },
472 .other_step => |cs| .{ .path = try addLazyPath(s, cs.installed_headers_include_tree.?.getDirectory()) },
473 .config_header_step => |chs| .{ .config_header_step = stepIndex(s, &chs.step) },
474 };
475 return result;
476 }
477
478 fn initLazyPathList(s: *Serialize, list: []const std.Build.LazyPath) ![]const Configuration.LazyPath.Index {
479 const result = try s.arena.alloc(Configuration.LazyPath.Index, list.len);
480 for (result, list) |*dest, src| dest.* = try addLazyPath(s, src);
481 return result;
482 }
483
484 fn initStringList(s: *Serialize, list: []const []const u8) ![]const Configuration.String {
485 const wc = s.wc;
486 const result = try s.arena.alloc(Configuration.String, list.len);
487 for (result, list) |*dest, src| dest.* = try wc.addString(src);
488 return result;
489 }
490
491 fn initCopyList(s: *Serialize, list: []const Step.WriteFile.Copy) ![]const Configuration.Step.WriteFile.Copy {
492 const result = try s.arena.alloc(Configuration.Step.WriteFile.Copy, list.len);
493 for (result, list) |*dest, src| dest.* = .{
494 .sub_path = src.sub_path,
495 .src_file = try s.addLazyPath(src.src_file),
496 };
497 return result;
498 }
499
500 fn initOptionalStringList(s: *Serialize, list: []const ?[]const u8) ![]const Configuration.OptionalString {
501 const wc = s.wc;
502 const result = try s.arena.alloc(Configuration.OptionalString, list.len);
503 for (result, list) |*dest, src| dest.* = try wc.addOptionalString(src);
504 return result;
505 }
506
507 fn addModule(s: *Serialize, m: *std.Build.Module) !Configuration.Module.Index {
508 if (s.module_map.get(m)) |index| return index;
509
510 const wc = s.wc;
511 const arena = s.arena;
512
513 const rpaths = try arena.alloc(Configuration.Module.RPath, m.rpaths.items.len);
514 for (rpaths, m.rpaths.items) |*dest, src| dest.* = switch (src) {
515 .lazy_path => |lp| .{ .lazy_path = try addLazyPath(s, lp) },
516 .special => |slice| .{ .special = try wc.addString(slice) },
517 };
518
519 const link_objects = try arena.alloc(Configuration.Module.LinkObject, m.link_objects.items.len);
520 for (link_objects, m.link_objects.items) |*dest, *src| dest.* = switch (src.*) {
521 .static_path => |lp| .{ .static_path = try addLazyPath(s, lp) },
522 .other_step => |cs| .{ .other_step = stepIndex(s, &cs.step) },
523 .system_lib => |*sl| .{ .system_lib = try addSystemLib(s, sl) },
524 .assembly_file => |lp| .{ .assembly_file = try addLazyPath(s, lp) },
525 .c_source_file => |csf| .{ .c_source_file = try addCSourceFile(s, csf) },
526 .c_source_files => |csf| .{ .c_source_files = try addCSourceFiles(s, csf) },
527 .win32_resource_file => |wrf| .{ .win32_resource_file = try addRcSourceFile(s, wrf) },
528 };
529
530 const frameworks = try arena.alloc(Configuration.Module.Framework, m.frameworks.entries.len);
531 for (frameworks, m.frameworks.keys(), m.frameworks.values()) |*dest, name, options| dest.* = .{
532 .flags = .{
533 .needed = options.needed,
534 .weak = options.weak,
535 },
536 .name = try wc.addString(name),
537 };
538
539 const lib_paths = try initLazyPathList(s, m.lib_paths.items);
540 const c_macros = try initStringList(s, m.c_macros.items);
541 const export_symbol_names = try initStringList(s, m.export_symbol_names);
542
543 const module_index: Configuration.Module.Index = try wc.addExtra(Configuration.Module, .{
544 .flags = .{
545 .optimize = .init(m.optimize),
546 .strip = .init(m.strip),
547 .unwind_tables = .init(m.unwind_tables),
548 .dwarf_format = .init(m.dwarf_format),
549 .single_threaded = .init(m.single_threaded),
550 .stack_protector = .init(m.stack_protector),
551 .stack_check = .init(m.stack_check),
552 .sanitize_c = .init(m.sanitize_c),
553 .sanitize_thread = .init(m.sanitize_thread),
554 .fuzz = .init(m.fuzz),
555 .code_model = m.code_model,
556 .c_macros = c_macros.len != 0,
557 .include_dirs = m.include_dirs.items.len != 0,
558 .lib_paths = lib_paths.len != 0,
559 .rpaths = rpaths.len != 0,
560 .frameworks = frameworks.len != 0,
561 .link_objects = link_objects.len != 0,
562 .export_symbol_names = export_symbol_names.len != 0,
563 },
564 .flags2 = .{
565 .valgrind = .init(m.valgrind),
566 .pic = .init(m.pic),
567 .red_zone = .init(m.red_zone),
568 .omit_frame_pointer = .init(m.omit_frame_pointer),
569 .error_tracing = .init(m.error_tracing),
570 .link_libc = .init(m.link_libc),
571 .link_libcpp = .init(m.link_libcpp),
572 .no_builtin = .init(m.no_builtin),
573 },
574 .owner = try s.builderToPackage(m.owner),
575 .root_source_file = try s.addOptionalLazyPathEnum(m.root_source_file),
576 .import_table = .invalid,
577 .resolved_target = try addOptionalResolvedTarget(wc, m.resolved_target),
578 .c_macros = .{ .slice = c_macros },
579 .lib_paths = .{ .slice = lib_paths },
580 .export_symbol_names = .{ .slice = export_symbol_names },
581 .include_dirs = .init(try s.initIncludeDirList(m.include_dirs.items)),
582 .rpaths = .init(rpaths),
583 .link_objects = .init(link_objects),
584 .frameworks = .{ .slice = frameworks },
585 });
586
587 // The import table is the only place that modules can form dependency
588 // loops. Therefore, we populate the module indexes only after adding
589 // the module to module_map.
590 try s.module_map.putNoClobber(arena, m, module_index);
591
592 var imports = try std.MultiArrayList(Configuration.ImportTable.Import).initCapacity(arena, m.import_table.entries.len);
593 imports.len = m.import_table.entries.len;
594 for (
595 imports.items(.name),
596 imports.items(.module),
597 m.import_table.keys(),
598 m.import_table.values(),
599 ) |*dest_name, *dest_module, src_name, src_module| {
600 dest_name.* = try wc.addString(src_name);
601 dest_module.* = try addModule(s, src_module);
602 }
603
604 comptime assert(std.mem.eql(u8, @typeInfo(Configuration.Module).@"struct".fields[2].name, "import_table"));
605 comptime assert(@typeInfo(Configuration.Module).@"struct".fields[2].type == Configuration.ImportTable.Index);
606 assert(wc.extra.items[@intFromEnum(module_index) + 2] == @intFromEnum(Configuration.ImportTable.Index.invalid));
607 const import_table_index = try wc.addDeduped(Configuration.ImportTable, .{
608 .imports = .{ .mal = imports },
609 });
610 wc.extra.items[@intFromEnum(module_index) + 2] = @intFromEnum(import_table_index);
611
612 return module_index;
613 }
614
615 fn stepIndex(s: *const Serialize, step: *Step) Configuration.Step.Index {
616 return @enumFromInt(s.step_map.getIndex(step).?);
617 }
618};
619
620fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void {
621 const graph = b.graph;
622 const arena = graph.arena;
623 const gpa = wc.gpa;
624
625 var s: Serialize = .{ .wc = wc, .arena = arena };
626
627 // Starting from all top-level steps in `b`, traverse the entire step graph
628 // and add all step dependencies implied by module graphs.
629 const top_level_steps = b.top_level_steps.values();
630 try s.step_map.ensureUnusedCapacity(arena, top_level_steps.len);
631 for (top_level_steps) |tls| {
632 s.step_map.putAssumeCapacityNoClobber(&tls.step, {});
633 }
634 {
635 while (wc.steps.items.len < s.step_map.count()) {
636 const step = s.step_map.keys()[wc.steps.items.len];
637
638 // Set up any implied dependencies for this step. It's important that we do this first, so
639 // that the loop below discovers steps implied by the module graph.
640 try createModuleDependenciesForStep(step);
641
642 try s.step_map.ensureUnusedCapacity(arena, step.dependencies.items.len);
643 for (step.dependencies.items) |other_step| {
644 s.step_map.putAssumeCapacity(other_step, {});
645 }
646
647 // Add and then de-duplicate dependencies.
648 const dep_steps = try arena.alloc(Configuration.Step.Index, step.dependencies.items.len);
649 for (dep_steps, step.dependencies.items) |*dest, src|
650 dest.* = @enumFromInt(s.step_map.getIndex(src).?);
651
652 const deps: Configuration.Deps.Index = try wc.addDeduped(Configuration.Deps, .{
653 .steps = .{ .slice = dep_steps },
654 });
655
656 try wc.steps.ensureTotalCapacity(gpa, s.step_map.entries.capacity);
657 wc.steps.appendAssumeCapacity(.{
658 .name = try wc.addString(step.name),
659 .owner = try s.builderToPackage(step.owner),
660 .deps = deps,
661 .max_rss = .fromBytes(step.max_rss),
662 .extended = @enumFromInt(switch (step.tag) {
663 .top_level => e: {
664 const top_level: *Step.TopLevel = @fieldParentPtr("step", step);
665 break :e try wc.addExtraErased(Configuration.Step.TopLevel, .{
666 .description = try wc.addString(top_level.description),
667 });
668 },
669 .compile => e: {
670 const c: *Step.Compile = @fieldParentPtr("step", step);
671 const exec_cmd_args: []const ?[]const u8 = c.exec_cmd_args orelse &.{};
672 const installed_headers: []u32 = try arena.alloc(u32, c.installed_headers.items.len);
673 for (installed_headers, c.installed_headers.items) |*dst, src| switch (src) {
674 .file => |file| {
675 dst.* = try wc.addExtraErased(Configuration.Step.Compile.InstalledHeader.File, .{
676 .source = try s.addLazyPath(file.source),
677 .dest_sub_path = try wc.addString(file.dest_rel_path),
678 });
679 },
680 .directory => |directory| {
681 const include_extensions = directory.options.include_extensions orelse &.{};
682 dst.* = try wc.addExtraErased(Configuration.Step.Compile.InstalledHeader.Directory, .{
683 .flags = .{
684 .include_extensions = include_extensions.len != 0,
685 .exclude_extensions = directory.options.exclude_extensions.len != 0,
686 },
687 .source = try s.addLazyPath(directory.source),
688 .dest_sub_path = try wc.addString(directory.dest_rel_path),
689 .exclude_extensions = .{ .slice = try s.initStringList(directory.options.exclude_extensions) },
690 .include_extensions = .{ .slice = try s.initStringList(include_extensions) },
691 });
692 },
693 };
694
695 break :e try wc.addExtraErased(Configuration.Step.Compile, .{
696 .flags = .{
697 .filters_len = c.filters.len != 0,
698 .exec_cmd_args_len = exec_cmd_args.len != 0,
699 .installed_headers_len = installed_headers.len != 0,
700 .force_undefined_symbols_len = c.force_undefined_symbols.entries.len != 0,
701
702 .verbose_link = c.verbose_link,
703 .verbose_cc = c.verbose_cc,
704 .rdynamic = c.rdynamic,
705 .import_memory = c.import_memory,
706 .export_memory = c.export_memory,
707 .import_symbols = c.import_symbols,
708 .import_table = c.import_table,
709 .export_table = c.export_table,
710 .shared_memory = c.shared_memory,
711 .link_eh_frame_hdr = c.link_eh_frame_hdr,
712 .link_emit_relocs = c.link_emit_relocs,
713 .link_function_sections = c.link_function_sections,
714 .link_data_sections = c.link_data_sections,
715 .linker_dynamicbase = c.linker_dynamicbase,
716 .link_z_notext = c.link_z_notext,
717 .link_z_relro = c.link_z_relro,
718 .link_z_lazy = c.link_z_lazy,
719 .link_z_defs = c.link_z_defs,
720 .headerpad_max_install_names = c.headerpad_max_install_names,
721 .dead_strip_dylibs = c.dead_strip_dylibs,
722 .force_load_objc = c.force_load_objc,
723 .discard_local_symbols = c.discard_local_symbols,
724 .mingw_unicode_entry_point = c.mingw_unicode_entry_point,
725 },
726 .flags2 = .{
727 .pie = .init(c.pie),
728 .formatted_panics = .init(c.formatted_panics),
729 .bundle_compiler_rt = .init(c.bundle_compiler_rt),
730 .bundle_ubsan_rt = .init(c.bundle_ubsan_rt),
731 .each_lib_rpath = .init(c.each_lib_rpath),
732 .link_gc_sections = .init(c.link_gc_sections),
733 .linker_allow_shlib_undefined = .init(c.linker_allow_shlib_undefined),
734 .linker_allow_undefined_version = .init(c.linker_allow_undefined_version),
735 .linker_enable_new_dtags = .init(c.linker_enable_new_dtags),
736 .dll_export_fns = .init(c.dll_export_fns),
737 .use_llvm = .init(c.use_llvm),
738 .use_lld = .init(c.use_lld),
739 .use_new_linker = .init(c.use_new_linker),
740 .allow_so_scripts = .init(c.allow_so_scripts),
741 .sanitize_coverage_trace_pc_guard = .init(c.sanitize_coverage_trace_pc_guard),
742 .linkage = .init(c.linkage),
743 },
744 .flags3 = .{
745 .is_linking_libc = c.is_linking_libc,
746 .is_linking_libcpp = c.is_linking_libcpp,
747 .version = c.version != null,
748 .compress_debug_sections = c.compress_debug_sections,
749 .initial_memory = c.initial_memory != null,
750 .max_memory = c.max_memory != null,
751 .kind = c.kind,
752 .global_base = c.global_base != null,
753 .test_runner = if (c.test_runner) |tr| switch (tr.mode) {
754 .simple => .simple,
755 .server => .server,
756 } else .default,
757 .wasi_exec_model = .init(c.wasi_exec_model),
758 .win32_manifest = c.win32_manifest != null,
759 .win32_module_definition = c.win32_module_definition != null,
760 .zig_lib_dir = c.zig_lib_dir != null,
761 .rc_includes = c.rc_includes,
762 .image_base = c.image_base != null,
763 .build_id = .init(c.build_id),
764 .entry = switch (c.entry) {
765 .default => .default,
766 .disabled => .disabled,
767 .enabled => .enabled,
768 .symbol_name => .symbol_name,
769 },
770 .lto = .init(c.lto),
771 .subsystem = .init(c.subsystem),
772 },
773 .flags4 = .{
774 .libc_file = c.libc_file != null,
775 .link_z_common_page_size = c.link_z_common_page_size != null,
776 .link_z_max_page_size = c.link_z_max_page_size != null,
777 .pagezero_size = c.pagezero_size != null,
778 .stack_size = c.stack_size != null,
779 .headerpad_size = c.headerpad_size != null,
780 .error_limit = c.error_limit != null,
781 .install_name = c.install_name != null,
782 .entitlements = c.entitlements != null,
783 .expect_errors = if (c.expect_errors) |x| switch (x) {
784 .contains => .contains,
785 .exact => .exact,
786 .starts_with => .starts_with,
787 .stderr_contains => .stderr_contains,
788 } else .none,
789 .linker_script = c.linker_script != null,
790 .version_script = c.version_script != null,
791 .emit_directory = c.emit_directory != .none,
792 .generated_docs = c.generated_docs != .none,
793 .generated_asm = c.generated_asm != .none,
794 .generated_bin = c.generated_bin != .none,
795 .generated_pdb = c.generated_pdb != .none,
796 .generated_implib = c.generated_implib != .none,
797 .generated_llvm_bc = c.generated_llvm_bc != .none,
798 .generated_llvm_ir = c.generated_llvm_ir != .none,
799 .generated_h = c.generated_h != .none,
800 },
801 .root_module = try s.addModule(c.root_module),
802 .root_name = try wc.addString(c.name),
803 .linker_script = .{ .value = try s.addOptionalLazyPath(c.linker_script) },
804 .version_script = .{ .value = try s.addOptionalLazyPath(c.version_script) },
805 .zig_lib_dir = .{ .value = try s.addOptionalLazyPath(c.zig_lib_dir) },
806 .libc_file = .{ .value = try s.addOptionalLazyPath(c.libc_file) },
807 .win32_manifest = .{ .value = try s.addOptionalLazyPath(c.win32_manifest) },
808 .win32_module_definition = .{ .value = try s.addOptionalLazyPath(c.win32_module_definition) },
809 .entitlements = .{ .value = try s.addOptionalLazyPath(c.entitlements) },
810 .version = .{ .value = try s.addOptionalSemVer(c.version) },
811 .install_name = .{ .value = try s.addOptionalString(c.install_name) },
812 .initial_memory = .{ .value = c.initial_memory },
813 .max_memory = .{ .value = c.max_memory },
814 .global_base = .{ .value = c.global_base },
815 .image_base = .{ .value = c.image_base },
816 .link_z_common_page_size = .{ .value = c.link_z_common_page_size },
817 .link_z_max_page_size = .{ .value = c.link_z_max_page_size },
818 .pagezero_size = .{ .value = c.pagezero_size },
819 .stack_size = .{ .value = c.stack_size },
820 .headerpad_size = .{ .value = c.headerpad_size },
821 .error_limit = .{ .value = c.error_limit },
822 .entry = .{ .value = switch (c.entry) {
823 .symbol_name => |name| try wc.addString(name),
824 .default, .disabled, .enabled => null,
825 } },
826 .build_id = .{ .value = if (c.build_id) |id| switch (id) {
827 .hexstring => |*hexstring| try wc.addString(hexstring.toSlice()),
828 .none, .fast, .uuid, .sha1, .md5 => null,
829 } else null },
830 .filters = .{ .slice = try s.initStringList(c.filters) },
831 .exec_cmd_args = .{ .slice = try s.initOptionalStringList(exec_cmd_args) },
832 .installed_headers = .initErased(installed_headers),
833 .force_undefined_symbols = .{ .slice = try s.initStringList(c.force_undefined_symbols.keys()) },
834 .expect_errors = .{ .u = if (c.expect_errors) |x| switch (x) {
835 .contains => |slice| .{ .contains = try wc.addString(slice) },
836 .exact => |exact| .{ .exact = .{ .slice = try s.initStringList(exact) } },
837 .starts_with => |slice| .{ .starts_with = try wc.addString(slice) },
838 .stderr_contains => |slice| .{ .stderr_contains = try wc.addString(slice) },
839 } else .none },
840 .test_runner = .{ .u = if (c.test_runner) |tr| switch (tr.mode) {
841 .simple => .{ .simple = try s.addLazyPath(tr.path) },
842 .server => .{ .server = try s.addLazyPath(tr.path) },
843 } else .default },
844
845 .emit_directory = .{ .value = c.emit_directory.unwrap() },
846 .generated_docs = .{ .value = c.generated_docs.unwrap() },
847 .generated_asm = .{ .value = c.generated_asm.unwrap() },
848 .generated_bin = .{ .value = c.generated_bin.unwrap() },
849 .generated_pdb = .{ .value = c.generated_pdb.unwrap() },
850 .generated_implib = .{ .value = c.generated_implib.unwrap() },
851 .generated_llvm_bc = .{ .value = c.generated_llvm_bc.unwrap() },
852 .generated_llvm_ir = .{ .value = c.generated_llvm_ir.unwrap() },
853 .generated_h = .{ .value = c.generated_h.unwrap() },
854 });
855 },
856 .install_artifact => e: {
857 const ia: *Step.InstallArtifact = @fieldParentPtr("step", step);
858 break :e try wc.addExtraErased(Configuration.Step.InstallArtifact, .{
859 .flags = .{
860 .dylib_symlinks = ia.dylib_symlinks,
861 .bin_dir = ia.dest_dir != null,
862 .implib_dir = ia.implib_dir != null,
863 .pdb_dir = ia.pdb_dir != null,
864 .h_dir = ia.h_dir != null,
865 .bin_sub_path = ia.dest_sub_path != null,
866 },
867 .bin_dir = .{ .value = try addInstallDirDefaultNull(wc, ia.dest_dir) },
868 .implib_dir = .{ .value = try addInstallDirDefaultNull(wc, ia.implib_dir) },
869 .pdb_dir = .{ .value = try addInstallDirDefaultNull(wc, ia.pdb_dir) },
870 .h_dir = .{ .value = try addInstallDirDefaultNull(wc, ia.h_dir) },
871 .bin_sub_path = .{ .value = try s.addOptionalString(ia.dest_sub_path) },
872 });
873 },
874 .install_file => e: {
875 const sif: *Step.InstallFile = @fieldParentPtr("step", step);
876 break :e try wc.addExtraErased(Configuration.Step.InstallFile, .{
877 .source = try s.addLazyPath(sif.source),
878 .dest_dir = try addInstallDir(wc, sif.dir),
879 .dest_sub_path = try wc.addString(sif.dest_rel_path),
880 });
881 },
882 .install_dir => e: {
883 const sid: *Step.InstallDir = @fieldParentPtr("step", step);
884 const dest_sub_path: ?[]const u8 = if (sid.options.install_subdir.len != 0)
885 sid.options.install_subdir
886 else
887 null;
888 const include_extensions = sid.options.include_extensions orelse &.{};
889 break :e try wc.addExtraErased(Configuration.Step.InstallDir, .{
890 .flags = .{
891 .dest_sub_path = dest_sub_path != null,
892 .exclude_extensions = sid.options.exclude_extensions.len != 0,
893 .include_extensions = include_extensions.len != 0,
894 .include_extensions_active = sid.options.include_extensions != null,
895 .blank_extensions = sid.options.blank_extensions.len != 0,
896 },
897 .source_dir = try s.addLazyPath(sid.options.source_dir),
898 .dest_dir = try addInstallDir(wc, sid.options.install_dir),
899 .dest_sub_path = .{ .value = try s.addOptionalString(dest_sub_path) },
900 .exclude_extensions = .{ .slice = try s.initStringList(sid.options.exclude_extensions) },
901 .include_extensions = .{ .slice = try s.initStringList(include_extensions) },
902 .blank_extensions = .{ .slice = try s.initStringList(sid.options.blank_extensions) },
903 });
904 },
905 .fail => e: {
906 const sf: *Step.Fail = @fieldParentPtr("step", step);
907 break :e try wc.addExtraErased(Configuration.Step.Fail, .{
908 .msg = sf.error_msg,
909 });
910 },
911 .find_program => e: {
912 const fp: *Step.FindProgram = @fieldParentPtr("step", step);
913 break :e try wc.addExtraErased(Configuration.Step.FindProgram, .{
914 .names = fp.names,
915 .found_path = fp.found_path,
916 });
917 },
918 .fmt => e: {
919 const sf: *Step.Fmt = @fieldParentPtr("step", step);
920 break :e try wc.addExtraErased(Configuration.Step.Fmt, .{
921 .flags = .{
922 .paths = sf.paths.len != 0,
923 .exclude_paths = sf.exclude_paths.len != 0,
924 .check = sf.check,
925 },
926 .paths = .{ .slice = try s.initLazyPathList(sf.paths) },
927 .exclude_paths = .{ .slice = try s.initLazyPathList(sf.exclude_paths) },
928 });
929 },
930 .translate_c => e: {
931 const tc: *Step.TranslateC = @fieldParentPtr("step", step);
932
933 const system_libs = try arena.alloc(Configuration.SystemLib.Index, tc.system_libs.items.len);
934 for (system_libs, tc.system_libs.items) |*dest, *src| dest.* = try s.addSystemLib(src);
935
936 break :e try wc.addExtraErased(Configuration.Step.TranslateC, .{
937 .flags = .{
938 .include_dirs = tc.include_dirs.items.len != 0,
939 .system_libs = system_libs.len != 0,
940 .c_macros = tc.c_macros.items.len != 0,
941 .link_libc = tc.link_libc,
942 .optimize = .init(tc.optimize),
943 },
944 .src_path = try s.addLazyPath(tc.source),
945 .output_file = tc.output_file,
946 .include_dirs = .init(try s.initIncludeDirList(tc.include_dirs.items)),
947 .system_libs = .{ .slice = system_libs },
948 .c_macros = .{ .slice = tc.c_macros.items },
949 .target = try addOptionalResolvedTarget(wc, tc.target),
950 });
951 },
952 .write_file => e: {
953 const wf: *Step.WriteFile = @fieldParentPtr("step", step);
954
955 const directories = try arena.alloc(
956 Configuration.Step.WriteFile.Directory,
957 wf.directories.items.len,
958 );
959 for (directories, wf.directories.items) |*dest, src| dest.* = .{
960 .sub_path = src.sub_path,
961 .src_path = try s.addLazyPath(src.src_path),
962 .exclude_extensions = src.exclude_extensions,
963 .include_extensions = src.include_extensions,
964 };
965
966 break :e try wc.addExtraErased(Configuration.Step.WriteFile, .{
967 .flags = .{
968 .embeds = wf.embeds.items.len != 0,
969 .copies = wf.copies.items.len != 0,
970 .directories = directories.len != 0,
971 .mode = switch (wf.mode) {
972 .whole_cached => .whole_cached,
973 .tmp => .tmp,
974 .mutate => .mutate,
975 },
976 },
977 .generated_directory = wf.generated_directory,
978 .embeds = .{ .slice = wf.embeds.items },
979 .copies = .{ .slice = try s.initCopyList(wf.copies.items) },
980 .directories = .{ .slice = directories },
981 .mutate_path = .{ .value = switch (wf.mode) {
982 .mutate => |lp| try s.addLazyPath(lp),
983 .whole_cached, .tmp => null,
984 } },
985 });
986 },
987 .update_source_files => e: {
988 const usf: *Step.UpdateSourceFiles = @fieldParentPtr("step", step);
989 break :e try wc.addExtraErased(Configuration.Step.UpdateSourceFiles, .{
990 .flags = .{
991 .embeds = usf.embeds.items.len != 0,
992 .copies = usf.copies.items.len != 0,
993 },
994 .embeds = .{ .slice = usf.embeds.items },
995 .copies = .{ .slice = try s.initCopyList(usf.copies.items) },
996 });
997 },
998 .run => e: {
999 const run: *Step.Run = @fieldParentPtr("step", step);
1000 var expect_stderr_exact: ?Configuration.Bytes = null;
1001 var expect_stdout_exact: ?Configuration.Bytes = null;
1002 var expect_stderr_match: std.ArrayList(Configuration.Bytes) = .empty;
1003 var expect_stdout_match: std.ArrayList(Configuration.Bytes) = .empty;
1004 var expect_term: ?struct {
1005 status: Configuration.Step.Run.ExpectTermStatus,
1006 value: u32,
1007 } = null;
1008 switch (run.stdio) {
1009 .check => |checks| for (checks.items) |check| switch (check) {
1010 .expect_stderr_exact => |bytes| expect_stderr_exact = try wc.addBytes(bytes),
1011 .expect_stdout_exact => |bytes| expect_stdout_exact = try wc.addBytes(bytes),
1012 .expect_stderr_match => |bytes| {
1013 try expect_stderr_match.append(arena, try wc.addBytes(bytes));
1014 },
1015 .expect_stdout_match => |bytes| {
1016 try expect_stdout_match.append(arena, try wc.addBytes(bytes));
1017 },
1018 .expect_term => |t| expect_term = switch (t) {
1019 .exited => |x| .{ .status = .exited, .value = x },
1020 .signal => |x| .{ .status = .signal, .value = @intFromEnum(x) },
1021 .stopped => |x| .{ .status = .stopped, .value = @intFromEnum(x) },
1022 .unknown => |x| .{ .status = .unknown, .value = x },
1023 },
1024 },
1025 else => {},
1026 }
1027
1028 break :e try wc.addExtraErased(Configuration.Step.Run, .{
1029 .flags = .{
1030 .disable_zig_progress = run.disable_zig_progress,
1031 .skip_foreign_checks = run.skip_foreign_checks,
1032 .failing_to_execute_foreign_is_an_error = run.failing_to_execute_foreign_is_an_error,
1033 .has_side_effects = run.has_side_effects,
1034 .test_runner_mode = run.test_runner_mode,
1035 .color = run.color,
1036 .stdio = switch (run.stdio) {
1037 .infer_from_args => .infer_from_args,
1038 .inherit => .inherit,
1039 .check => .check,
1040 .zig_test => .zig_test,
1041 },
1042 .stdin = switch (run.stdin) {
1043 .none => .none,
1044 .bytes => .bytes,
1045 .lazy_path => .lazy_path,
1046 },
1047 .stdout_trim_whitespace = if (run.captured_stdout) |cs| cs.trim_whitespace else .none,
1048 .stderr_trim_whitespace = if (run.captured_stderr) |cs| cs.trim_whitespace else .none,
1049 .stdio_limit = run.stdio_limit != .unlimited,
1050 .producer = run.producer != null,
1051 .cwd = run.cwd != null,
1052 .captured_stdout = run.captured_stdout != null,
1053 .captured_stderr = run.captured_stderr != null,
1054 .environ_map = run.environ_map != null,
1055 },
1056 .flags2 = .{
1057 .expect_stderr_exact = expect_stderr_exact != null,
1058 .expect_stdout_exact = expect_stdout_exact != null,
1059 .expect_stderr_match = expect_stderr_match.items.len != 0,
1060 .expect_stdout_match = expect_stdout_match.items.len != 0,
1061 .expect_term = expect_term != null,
1062 .expect_term_status = if (expect_term) |t| t.status else .exited,
1063 },
1064 .file_inputs = .{ .slice = try s.initLazyPathList(run.file_inputs.items) },
1065 .args = .{ .slice = try s.initArgsList(run.argv.items) },
1066 .cwd = .{ .value = try s.addOptionalLazyPath(run.cwd) },
1067 .captured_stdout = .{ .value = if (run.captured_stdout) |cs| .{
1068 .basename = try wc.addString(cs.output.basename),
1069 .generated_file = cs.output.generated_file,
1070 } else null },
1071 .captured_stderr = .{ .value = if (run.captured_stderr) |cs| .{
1072 .basename = try wc.addString(cs.output.basename),
1073 .generated_file = cs.output.generated_file,
1074 } else null },
1075 .environ_map = .{ .value = try s.addEnvironMap(run.environ_map) },
1076 .expect_term_value = .{ .value = if (expect_term) |t| t.value else null },
1077 .stdio_limit = .{ .value = run.stdio_limit.toInt() },
1078 .producer = .{ .value = if (run.producer) |cs| s.stepIndex(&cs.step) else null },
1079 .expect_stderr_exact = .{ .value = if (expect_stderr_exact) |bytes| bytes else null },
1080 .expect_stdout_exact = .{ .value = if (expect_stdout_exact) |bytes| bytes else null },
1081 .expect_stderr_match = .{ .slice = expect_stderr_match.items },
1082 .expect_stdout_match = .{ .slice = expect_stdout_match.items },
1083 .stdin = .{ .u = switch (run.stdin) {
1084 .none => .none,
1085 .bytes => |bytes| .{ .bytes = try wc.addBytes(bytes) },
1086 .lazy_path => |lp| .{ .lazy_path = try s.addLazyPath(lp) },
1087 } },
1088 });
1089 },
1090 .check_file => e: {
1091 const cf: *Step.CheckFile = @fieldParentPtr("step", step);
1092 break :e try wc.addExtraErased(Configuration.Step.CheckFile, .{
1093 .flags = .{
1094 .expected_exact = cf.expected_exact != null,
1095 .expected_matches = cf.expected_matches.len != 0,
1096 .max_bytes = cf.max_bytes != null,
1097 },
1098 .file = try s.addLazyPath(cf.file),
1099 .expected_exact = .{ .value = cf.expected_exact },
1100 .expected_matches = .{ .slice = cf.expected_matches },
1101 .max_bytes = .{ .value = cf.max_bytes },
1102 });
1103 },
1104 .config_header => e: {
1105 const ch: *Step.ConfigHeader = @fieldParentPtr("step", step);
1106 const lazy_path: ?std.Build.LazyPath = ch.style.getPath();
1107 const pairs = try arena.alloc(Configuration.Step.ConfigHeader.Value.Pair, ch.values.count());
1108 for (pairs, ch.values.keys(), ch.values.values()) |*pair, key, value| pair.* = .{
1109 .key = try wc.addString(key),
1110 .index = switch (value) {
1111 .undef => .undef,
1112 .defined => .defined,
1113 .boolean => |x| switch (x) {
1114 false => .bool_false,
1115 true => .bool_true,
1116 },
1117 .int => |x| switch (x) {
1118 0 => .int_0,
1119 1 => .int_1,
1120 else => try wc.addExtra(Configuration.Step.ConfigHeader.Value, .initSigned(x)),
1121 },
1122 .ident => |x| try wc.addExtra(Configuration.Step.ConfigHeader.Value, .{
1123 .flags = .{
1124 .tag = .ident,
1125 .small = 0,
1126 },
1127 .i64 = .{ .value = null },
1128 .u64 = .{ .value = null },
1129 .ident = .{ .value = try wc.addString(x) },
1130 .string = .{ .value = null },
1131 }),
1132 .string => |x| try wc.addExtra(Configuration.Step.ConfigHeader.Value, .{
1133 .flags = .{
1134 .tag = .string,
1135 .small = 0,
1136 },
1137 .i64 = .{ .value = null },
1138 .u64 = .{ .value = null },
1139 .ident = .{ .value = null },
1140 .string = .{ .value = try wc.addString(x) },
1141 }),
1142 },
1143 };
1144 break :e try wc.addExtraErased(Configuration.Step.ConfigHeader, .{
1145 .flags = .{
1146 .template_file = lazy_path != null,
1147 .style = .init(ch.style),
1148 .input_size_limit = ch.input_size_limit != null,
1149 .include_guard = ch.include_guard != .none,
1150 },
1151 .template_file = .{ .value = try s.addOptionalLazyPath(lazy_path) },
1152 .generated_dir = ch.generated_dir,
1153 .input_size_limit = .{ .value = ch.input_size_limit },
1154 .include_path = try wc.addString(ch.include_path),
1155 .include_guard = .{ .value = ch.include_guard.unwrap() },
1156 .values = .{ .slice = pairs },
1157 });
1158 },
1159 .obj_copy => e: {
1160 const oc: *Step.ObjCopy = @fieldParentPtr("step", step);
1161
1162 const debug_basename: ?Configuration.String = if (oc.debug_file) |df|
1163 df.basename.unwrap()
1164 else
1165 null;
1166
1167 const debug_file: ?Configuration.GeneratedFileIndex = if (oc.debug_file) |df|
1168 df.output_file
1169 else
1170 null;
1171
1172 const add_sections = try arena.alloc(
1173 Configuration.Step.ObjCopy.AddSection,
1174 oc.add_sections.items.len,
1175 );
1176 for (add_sections, oc.add_sections.items) |*dest, src| dest.* = .{
1177 .section_name = src.section_name,
1178 .file_path = try s.addLazyPath(src.file_path),
1179 };
1180
1181 break :e try wc.addExtraErased(Configuration.Step.ObjCopy, .{
1182 .flags = .{
1183 .basename = oc.basename != .none,
1184 .debug_file = debug_file != null,
1185 .debug_basename = debug_basename != null,
1186 .format = .init(oc.format),
1187 .strip = oc.strip,
1188 .compress_debug = oc.compress_debug,
1189 .only_section = oc.only_section != .none,
1190 .pad_to = oc.pad_to != null,
1191 .add_section = add_sections.len != 0,
1192 .update_section = oc.update_sections.items.len != 0,
1193 },
1194 .input_file = try s.addLazyPath(oc.input_file),
1195 .output_file = oc.output_file,
1196 .basename = .{ .value = oc.basename.unwrap() },
1197 .debug_file = .{ .value = debug_file },
1198 .debug_basename = .{ .value = debug_basename },
1199 .only_section = .{ .value = oc.only_section.unwrap() },
1200 .pad_to = .{ .value = oc.pad_to },
1201 .add_section = .{ .slice = add_sections },
1202 .update_section = .{ .slice = oc.update_sections.items },
1203 });
1204 },
1205 .options => e: {
1206 const so: *Step.Options = @fieldParentPtr("step", step);
1207
1208 const args = try arena.alloc(Configuration.Step.Options.Arg, so.args.items.len);
1209 for (args, so.args.items) |*dest, src| dest.* = .{
1210 .name = src.name,
1211 .path = try s.addLazyPath(src.path),
1212 };
1213
1214 break :e try wc.addExtraErased(Configuration.Step.Options, .{
1215 .flags = .{
1216 .args = so.args.items.len != 0,
1217 },
1218 .generated_file = so.generated_file,
1219 .contents = try wc.addBytes(so.contents.items),
1220 .args = .{ .slice = args },
1221 });
1222 },
1223 }),
1224 });
1225 }
1226 }
1227
1228 try wc.unlazy_deps.ensureUnusedCapacity(gpa, graph.needed_lazy_dependencies.keys().len);
1229 for (graph.needed_lazy_dependencies.keys()) |k| {
1230 wc.unlazy_deps.appendAssumeCapacity(try wc.addString(k));
1231 }
1232
1233 try wc.write(writer, .{
1234 .default_step = s.stepIndex(b.default_step),
1235 .generated_files_len = @intCast(graph.generated_files.items.len),
1236 .poisoned = switch (graph.cache_poison) {
1237 .pure, .disallowed, .ignored => false,
1238 .poisoned => true,
1239 },
1240 });
1241}
1242
1243fn addOptionalResolvedTarget(
1244 wc: *Configuration.Wip,
1245 optional_resolved_target: ?std.Build.ResolvedTarget,
1246) !Configuration.ResolvedTarget.OptionalIndex {
1247 const resolved_target = optional_resolved_target orelse return .none;
1248 return .init(try wc.addDeduped(Configuration.ResolvedTarget, .{
1249 .query = try wc.addTargetQuery(&resolved_target.query),
1250 .result = try wc.addTarget(resolved_target.result),
1251 }));
1252}
1253
1254fn addInstallDir(wc: *Configuration.Wip, install_dir: ?std.Build.InstallDir) !Configuration.InstallDestDir {
1255 switch (install_dir orelse return .none) {
1256 .prefix => return .prefix,
1257 .lib => return .lib,
1258 .bin => return .bin,
1259 .header => return .header,
1260 .custom => |sub_path| return .initCustom(try wc.addString(sub_path)),
1261 }
1262}
1263
1264fn addInstallDirDefaultNull(wc: *Configuration.Wip, install_dir: ?std.Build.InstallDir) !?Configuration.InstallDestDir {
1265 return try addInstallDir(wc, install_dir orelse return null);
1266}
1267
1268/// If the given `Step` is a `Step.Compile`, adds any dependencies for that step which
1269/// are implied by the module graph rooted at `step.cast(Step.Compile).?.root_module`.
1270fn createModuleDependenciesForStep(step: *Step) Allocator.Error!void {
1271 const root_module = if (step.cast(Step.Compile)) |cs| root: {
1272 break :root cs.root_module;
1273 } else return; // not a compile step so no module dependencies
1274
1275 // Starting from `root_module`, discover all modules in this graph.
1276 const modules = root_module.getGraph().modules;
1277
1278 // For each of those modules, set up the implied step dependencies.
1279 for (modules) |mod| {
1280 if (mod.root_source_file) |lp| lp.addStepDependencies(step);
1281 for (mod.include_dirs.items) |include_dir| switch (include_dir) {
1282 .path,
1283 .path_system,
1284 .path_after,
1285 .framework_path,
1286 .framework_path_system,
1287 .embed_path,
1288 => |lp| lp.addStepDependencies(step),
1289
1290 .other_step => |other| {
1291 other.getEmittedIncludeTree().addStepDependencies(step);
1292 step.dependOn(&other.step);
1293 },
1294
1295 .config_header_step => |other| step.dependOn(&other.step),
1296 };
1297 for (mod.lib_paths.items) |lp| lp.addStepDependencies(step);
1298 for (mod.rpaths.items) |rpath| switch (rpath) {
1299 .lazy_path => |lp| lp.addStepDependencies(step),
1300 .special => {},
1301 };
1302 for (mod.link_objects.items) |link_object| switch (link_object) {
1303 .static_path,
1304 .assembly_file,
1305 => |lp| lp.addStepDependencies(step),
1306 .other_step => |other| step.dependOn(&other.step),
1307 .system_lib => {},
1308 .c_source_file => |source| source.file.addStepDependencies(step),
1309 .c_source_files => |source_files| source_files.root.addStepDependencies(step),
1310 .win32_resource_file => |rc_source| {
1311 rc_source.file.addStepDependencies(step);
1312 for (rc_source.include_paths) |lp| lp.addStepDependencies(step);
1313 },
1314 };
1315 }
1316}
1317
1318fn nextArg(args: []const [:0]const u8, idx: *usize) ?[:0]const u8 {
1319 if (idx.* >= args.len) return null;
1320 defer idx.* += 1;
1321 return args[idx.*];
1322}
1323
1324fn nextArgOrFatal(args: []const [:0]const u8, idx: *usize) [:0]const u8 {
1325 return nextArg(args, idx) orelse {
1326 fatalWithHint("expected argument after {q}", .{args[idx.* - 1]});
1327 };
1328}
1329
1330fn expectArgOrFatal(args: []const [:0]const u8, index_ptr: *usize, first: []const u8) []const u8 {
1331 const next_arg = nextArg(args, index_ptr) orelse fatal("missing {q} argument", .{first});
1332 if (!mem.eql(u8, first, next_arg)) fatal("expected {q} instead of {q}", .{ first, next_arg });
1333 const arg = nextArg(args, index_ptr) orelse fatal("expected argument after {q}", .{first});
1334 return arg;
1335}
1336
1337const ErrorStyle = enum {
1338 verbose,
1339 minimal,
1340 verbose_clear,
1341 minimal_clear,
1342 fn verboseContext(s: ErrorStyle) bool {
1343 return switch (s) {
1344 .verbose, .verbose_clear => true,
1345 .minimal, .minimal_clear => false,
1346 };
1347 }
1348 fn clearOnUpdate(s: ErrorStyle) bool {
1349 return switch (s) {
1350 .verbose, .minimal => false,
1351 .verbose_clear, .minimal_clear => true,
1352 };
1353 }
1354};
1355const MultilineErrors = enum { indent, newline, none };
1356const Summary = enum { all, new, failures, line, none };
1357
1358fn fatalWithHint(comptime f: []const u8, args: anytype) noreturn {
1359 log.info("to access the help menu: zig build -h", .{});
1360 fatal(f, args);
1361}
1362
1363fn serializeSystemIntegrationOptions(graph: *std.Build.Graph, wc: *Configuration.Wip) Allocator.Error!void {
1364 const gpa = wc.gpa;
1365
1366 var bad = false;
1367 try wc.system_integrations.ensureTotalCapacityPrecise(gpa, graph.system_integration_options.entries.len);
1368 for (graph.system_integration_options.keys(), graph.system_integration_options.values()) |k, v| {
1369 wc.system_integrations.appendAssumeCapacity(.{
1370 .name = try wc.addString(k),
1371 .status = switch (v) {
1372 .user_disabled, .user_enabled => x: {
1373 // The user tried to enable or disable a system library integration, but
1374 // the configure script did not recognize that option.
1375 log.err("system integration name not recognized by configure script: {s}", .{k});
1376 bad = true;
1377 break :x .disabled;
1378 },
1379 .declared_disabled => .disabled,
1380 .declared_enabled => .enabled,
1381 },
1382 });
1383 }
1384 if (bad) {
1385 log.info("help menu contains available options: zig build -h", .{});
1386 process.exit(1);
1387 }
1388}
1389
1390fn serializePackageOptions(b: *std.Build, wc: *Configuration.Wip) Allocator.Error!void {
1391 const gpa = wc.gpa;
1392
1393 try wc.available_options.ensureTotalCapacityPrecise(gpa, b.available_options_map.count());
1394 for (b.available_options_map.keys(), b.available_options_map.values()) |name, *opt| {
1395 wc.available_options.appendAssumeCapacity(.{
1396 .name = try wc.addString(name),
1397 .description = try wc.addString(opt.description),
1398 .type = opt.type_id,
1399 .enum_options = if (opt.enum_options) |enum_vals| .init(try wc.addStringList(enum_vals)) else .none,
1400 });
1401 }
1402}
lib/compiler/std-docs.zig+16-37
......@@ -271,12 +271,16 @@ fn serveWasm(
271271 // Do the compilation every request, so that the user can edit the files
272272 // and see the changes without restarting the server.
273273 const wasm_base_path = try buildWasmBinary(arena, context, optimize_mode);
274 const target = std.zig.system.resolveTargetQuery(io, std.Build.parseTargetQuery(.{
275 .arch_os_abi = autodoc_arch_os_abi,
276 .cpu_features = autodoc_cpu_features,
277 }) catch unreachable) catch unreachable;
274278 const bin_name = try std.zig.binNameAlloc(arena, .{
275279 .root_name = autodoc_root_name,
276 .target = &(std.zig.system.resolveTargetQuery(io, std.Build.parseTargetQuery(.{
277 .arch_os_abi = autodoc_arch_os_abi,
278 .cpu_features = autodoc_cpu_features,
279 }) catch unreachable) catch unreachable),
280 .cpu_arch = target.cpu.arch,
281 .os_tag = target.os.tag,
282 .ofmt = target.ofmt,
283 .abi = target.abi,
280284 .output_mode = .Exe,
281285 });
282286 // std.http.Server does not have a sendfile API yet.
......@@ -406,51 +410,26 @@ fn buildWasmBinary(
406410 child.stdin.?.close(io);
407411 child.stdin = null;
408412
409 switch (try child.wait(io)) {
410 .exited => |code| {
411 if (code != 0) {
412 std.log.err(
413 "the following command exited with error code {d}:\n{s}",
414 .{ code, try std.Build.Step.allocPrintCmd(arena, .inherit, null, argv.items) },
415 );
416 return error.WasmCompilationFailed;
417 }
418 },
419 .signal => |sig| {
420 std.log.err(
421 "the following command terminated with signal {t}:\n{s}",
422 .{ sig, try std.Build.Step.allocPrintCmd(arena, .inherit, null, argv.items) },
423 );
424 return error.WasmCompilationFailed;
425 },
426 .stopped => |sig| {
427 std.log.err(
428 "the following command stopped unexpectedly with signal {t}:\n{s}",
429 .{ sig, try std.Build.Step.allocPrintCmd(arena, .inherit, null, argv.items) },
430 );
431 return error.WasmCompilationFailed;
432 },
433 .unknown => {
434 std.log.err(
435 "the following command terminated unexpectedly:\n{s}",
436 .{try std.Build.Step.allocPrintCmd(arena, .inherit, null, argv.items)},
437 );
438 return error.WasmCompilationFailed;
439 },
413 const term = try child.wait(io);
414 if (!term.success()) {
415 std.log.err("the following command {f}:\n{s}", .{
416 term, try std.zig.allocPrintCmd(arena, argv.items, .{}),
417 });
418 return error.WasmCompilationFailed;
440419 }
441420
442421 if (result_error_bundle.errorMessageCount() > 0) {
443422 try result_error_bundle.renderToStderr(io, .{}, .auto);
444423 std.log.err("the following command failed with {d} compilation errors:\n{s}", .{
445424 result_error_bundle.errorMessageCount(),
446 try std.Build.Step.allocPrintCmd(arena, .inherit, null, argv.items),
425 try std.zig.allocPrintCmd(arena, argv.items, .{}),
447426 });
448427 return error.WasmCompilationFailed;
449428 }
450429
451430 return result orelse {
452431 std.log.err("child process failed to report result\n{s}", .{
453 try std.Build.Step.allocPrintCmd(arena, .inherit, null, argv.items),
432 try std.zig.allocPrintCmd(arena, argv.items, .{}),
454433 });
455434 return error.WasmCompilationFailed;
456435 };
lib/init/build.zig+1-3
......@@ -111,9 +111,7 @@ pub fn build(b: *std.Build) void {
111111
112112 // This allows the user to pass arguments to the application in the build
113113 // command itself, like this: `zig build run -- arg1 arg2 etc`
114 if (b.args) |args| {
115 run_cmd.addArgs(args);
116 }
114 run_cmd.addPassthruArgs();
117115
118116 // Creates an executable that will run `test` blocks from the provided module.
119117 // Here `mod` needs to define a target, which is why earlier we made sure to
lib/std/Build.zig+639-713
......@@ -1,4 +1,5 @@
11const Build = @This();
2
23const builtin = @import("builtin");
34
45const std = @import("std.zig");
......@@ -19,48 +20,23 @@ const ArrayList = std.ArrayList;
1920pub const Cache = @import("Build/Cache.zig");
2021pub const Step = @import("Build/Step.zig");
2122pub const Module = @import("Build/Module.zig");
22pub const Watch = @import("Build/Watch.zig");
23pub const Fuzz = @import("Build/Fuzz.zig");
24pub const WebServer = @import("Build/WebServer.zig");
2523pub const abi = @import("Build/abi.zig");
24/// The serialized output of configure phase ingested by make phase.
25pub const Configuration = @import("Build/Configuration.zig");
2626
2727/// Shared state among all Build instances.
2828graph: *Graph,
29install_tls: TopLevelStep,
30uninstall_tls: TopLevelStep,
29install_tls: Step.TopLevel,
30uninstall_tls: Step.TopLevel,
3131allocator: Allocator,
3232user_input_options: UserInputOptionsMap,
33available_options_map: AvailableOptionsMap,
34available_options_list: std.array_list.Managed(AvailableOption),
35verbose: bool,
36verbose_link: bool,
37verbose_cc: bool,
38verbose_air: bool,
39verbose_llvm_ir: ?[]const u8,
40verbose_llvm_bc: ?[]const u8,
41verbose_llvm_cpu_features: bool,
42reference_trace: ?u32 = null,
33available_options_map: std.array_hash_map.String(AvailableOption) = .empty,
4334invalid_user_input: bool,
4435default_step: *Step,
45top_level_steps: std.StringArrayHashMapUnmanaged(*TopLevelStep),
46install_prefix: []const u8,
47dest_dir: ?[]const u8,
48lib_dir: []const u8,
49exe_dir: []const u8,
50h_dir: []const u8,
51install_path: []const u8,
52sysroot: ?[]const u8 = null,
53search_prefixes: ArrayList([]const u8),
54libc_file: ?[]const u8 = null,
36top_level_steps: std.StringArrayHashMapUnmanaged(*Step.TopLevel),
5537/// Path to the directory containing build.zig.
56build_root: Cache.Directory,
57cache_root: Cache.Directory,
58pkg_config_pkg_list: ?(PkgConfigError![]const PkgConfigPkg) = null,
59args: ?[]const []const u8 = null,
38root: Cache.Path,
6039debug_log_scopes: []const []const u8 = &.{},
61debug_compile_errors: bool = false,
62debug_incremental: bool = false,
63debug_pkg_config: bool = false,
6440/// Number of stack frames captured when a `StackTrace` is recorded for debug purposes,
6541/// in particular at `Step` creation.
6642/// Set to 0 to disable stack collection.
......@@ -76,12 +52,6 @@ enable_rosetta: bool = false,
7652enable_wasmtime: bool = false,
7753/// Use system Wine installation to run cross compiled Windows build artifacts.
7854enable_wine: bool = false,
79/// After following the steps in https://codeberg.org/ziglang/infra/src/branch/master/libc-update/glibc.md,
80/// this will be the directory $glibc-build-dir/install/glibcs
81/// Given the example of the aarch64 target, this is the directory
82/// that contains the path `aarch64-linux-gnu/lib/ld-linux-aarch64.so.1`.
83/// Also works for dynamic musl.
84libc_runtimes_dir: ?[]const u8 = null,
8555
8656dep_prefix: []const u8 = "",
8757
......@@ -94,10 +64,6 @@ pkg_hash: []const u8,
9464/// A mapping from dependency names to package hashes.
9565available_deps: AvailableDeps,
9666
97release_mode: ReleaseMode,
98
99build_id: ?std.zig.BuildId = null,
100
10167pub const ReleaseMode = enum {
10268 off,
10369 any,
......@@ -112,33 +78,145 @@ pub const Graph = struct {
11278 io: Io,
11379 /// Process lifetime.
11480 arena: Allocator,
115 system_library_options: std.StringArrayHashMapUnmanaged(SystemLibraryMode) = .empty,
81 system_integration_options: std.StringArrayHashMapUnmanaged(SystemLibraryMode) = .empty,
11682 system_package_mode: bool = false,
117 debug_compiler_runtime_libs: ?std.builtin.OptimizeMode = null,
118 cache: Cache,
119 zig_exe: [:0]const u8,
83 zig_exe: []const u8,
12084 environ_map: process.Environ.Map,
121 global_cache_root: Cache.Directory,
122 zig_lib_directory: Cache.Directory,
12385 needed_lazy_dependencies: std.StringArrayHashMapUnmanaged(void) = .empty,
12486 /// Information about the native target. Computed before build() is invoked.
12587 host: ResolvedTarget,
126 incremental: ?bool = null,
127 random_seed: u32 = 0,
12888 dependency_cache: InitializedDepMap = .empty,
12989 allow_so_scripts: ?bool = null,
130 /// Steps should use `io` to limit the number of jobs, however in the case of
131 /// a single step spawning a fixed number of processes this can be used.
132 max_jobs: ?u32 = null,
133 time_report: bool,
90 time_report: bool = false,
91 verbose: bool = false,
13492 /// Similar to the `Io.Terminal.Mode` returned by `Io.lockStderr`, but also
13593 /// respects the '--color' flag.
13694 stderr_mode: ?Io.Terminal.Mode = null,
95 release_mode: ReleaseMode = .off,
96
97 /// Indexes correspond to `Configuration.GeneratedFileIndex`.
98 generated_files: std.ArrayList(*Step),
99 wip_configuration: Configuration.Wip,
100
101 cache_poison: CachePoison = .pure,
102 /// Observing this data causes cache poisoning. See `CachePoison`.
103 search_prefixes: std.ArrayList([]const u8) = .empty,
104
105 /// If the cache is poisoned means that the **configure logic** had side
106 /// effects, or otherwise did something that could not be tracked by the
107 /// cache system.
108 ///
109 /// This is not to be confused with whether individual steps may have side
110 /// effects when being evaluated; it has to do with the logic inside build.zig
111 /// itself. For example, a `Run` step that prints "hello world" has side
112 /// effects *at make time* and therefore does not warrant setting this flag,
113 /// while checking for the existence of `scdoc` *at configure time* in order to
114 /// choose the default value for a configuration option does.
115 ///
116 /// Keeping the cache pure will make `zig build` faster, bypassing the
117 /// configurer process when identical configuration would be generated.
118 ///
119 /// When the cache is poisoned, the maker process will delete the build
120 /// configuration file upon ingesting it since it cannot be reused.
121 pub const CachePoison = enum {
122 pure,
123 poisoned,
124 /// Indicates the user would like to see a stack trace if the cache
125 /// would become poisoned.
126 disallowed,
127 /// Indicates the user would like to ignore the cache being poisoned
128 /// and cache anyway, opting into cache hits on stale configuration.
129 ignored,
130 };
131
132 pub fn addGeneratedFile(graph: *Graph, owner: *Step) Configuration.GeneratedFileIndex {
133 graph.generated_files.append(graph.arena, owner) catch @panic("OOM");
134 return @enumFromInt(graph.generated_files.items.len - 1);
135 }
136
137 pub fn dupeString(graph: *const Graph, bytes: []const u8) []const u8 {
138 return graph.arena.dupe(u8, bytes) catch @panic("OOM");
139 }
140
141 pub fn dupePath(graph: *const Graph, bytes: []const u8) []const u8 {
142 return dupePathInner(graph.arena, bytes);
143 }
144
145 fn dupePathInner(arena: Allocator, bytes: []const u8) []const u8 {
146 if (builtin.os.tag != .windows) return arena.dupe(u8, bytes) catch @panic("OOM");
147 const the_copy = arena.dupe(u8, bytes) catch @panic("OOM");
148 mem.replaceScalar(u8, the_copy, '/', '\\');
149 return the_copy;
150 }
151
152 pub fn dupeStrings(graph: *const Graph, strings: []const []const u8) []const []const u8 {
153 const array = graph.alloc([]const u8, strings.len);
154 for (array, strings) |*dest, source| dest.* = dupeString(graph, source);
155 return array;
156 }
157
158 /// An absolute path or a path relative to the current working directory of
159 /// the build runner process.
160 ///
161 /// Use of this function indicates a dependency on the host system.
162 pub fn cwdRelativePath(graph: *Graph, sub_path: []const u8) LazyPath {
163 return @This().path(graph, .cwd, sub_path);
164 }
165
166 /// A path whose components and contents are known at some point during
167 /// `Step` resolution, relative to the provided base directory.
168 pub fn path(graph: *Graph, base: Configuration.Path.Base, sub_path: []const u8) LazyPath {
169 return .{ .relative = .{
170 .base = base,
171 .sub_path = @This().dupePath(graph, sub_path),
172 } };
173 }
174
175 /// Allocates using the global process arena, failing the build on
176 /// allocation failure.
177 pub fn alloc(graph: *const Graph, comptime T: type, n: usize) []T {
178 return graph.arena.allocAdvancedWithRetAddr(T, null, n, @returnAddress()) catch @panic("OOM");
179 }
180
181 /// Allocates using the global process arena, failing the build on
182 /// allocation failure.
183 pub fn create(graph: *const Graph, comptime T: type) *T {
184 return @ptrCast(graph.arena.allocBytesAligned(.of(T), @sizeOf(T), @returnAddress()) catch @panic("OOM"));
185 }
186
187 pub fn addBytesList(graph: *Graph, bytes_list: []const []const u8) []const Configuration.Bytes {
188 const result = graph.alloc(Configuration.Bytes, bytes_list.len);
189 for (result, bytes_list) |*d, s| d.* = addBytes(graph, s);
190 return result;
191 }
192
193 pub fn addBytes(graph: *Graph, bytes: []const u8) Configuration.Bytes {
194 const wc = &graph.wip_configuration;
195 return wc.addBytes(bytes) catch @panic("OOM");
196 }
197
198 pub fn addString(graph: *Graph, bytes: []const u8) Configuration.String {
199 const wc = &graph.wip_configuration;
200 return wc.addString(bytes) catch @panic("OOM");
201 }
202
203 /// Indicates that the **configure logic** had side effects, or otherwise
204 /// did something that could not be tracked by the cache system.
205 ///
206 /// See `CachePoison` documentation for more details.
207 pub fn poisonCache(graph: *Graph) void {
208 switch (graph.cache_poison) {
209 .pure => graph.cache_poison = .poisoned,
210 .poisoned => return,
211 .disallowed => @panic("cache poisoned"),
212 .ignored => log.warn("ignoring cache poisoning", .{}),
213 }
214 }
137215};
138216
139217const AvailableDeps = []const struct { []const u8, []const u8 };
140218
141const SystemLibraryMode = enum {
219pub const SystemLibraryMode = enum {
142220 /// User asked for the library to be disabled.
143221 /// The build runner has not confirmed whether the setting is recognized yet.
144222 user_disabled,
......@@ -187,31 +265,11 @@ const InitializedDepContext = struct {
187265 }
188266};
189267
190pub const RunError = error{
191 ReadFailure,
192 ExitCodeFailure,
193 ProcessTerminated,
194 ExecNotSupported,
195} || std.process.SpawnError;
196
197pub const PkgConfigError = error{
198 PkgConfigCrashed,
199 PkgConfigFailed,
200 PkgConfigNotInstalled,
201 PkgConfigInvalidOutput,
202};
203
204pub const PkgConfigPkg = struct {
205 name: []const u8,
206 desc: []const u8,
207};
208
209268const UserInputOptionsMap = StringHashMap(UserInputOption);
210const AvailableOptionsMap = StringHashMap(AvailableOption);
211269
212270const AvailableOption = struct {
213271 name: []const u8,
214 type_id: TypeId,
272 type_id: Configuration.AvailableOption.Type,
215273 description: []const u8,
216274 /// If the `type_id` is `enum` or `enum_list` this provides the list of enum options
217275 enum_options: ?[]const []const u8,
......@@ -232,36 +290,9 @@ const UserValue = union(enum) {
232290 lazy_path_list: std.array_list.Managed(LazyPath),
233291};
234292
235const TypeId = enum {
236 bool,
237 int,
238 float,
239 @"enum",
240 enum_list,
241 string,
242 list,
243 build_id,
244 lazy_path,
245 lazy_path_list,
246};
247
248const TopLevelStep = struct {
249 pub const base_id: Step.Id = .top_level;
250
251 step: Step,
252 description: []const u8,
253};
254
255pub const DirList = struct {
256 lib_dir: ?[]const u8 = null,
257 exe_dir: ?[]const u8 = null,
258 include_dir: ?[]const u8 = null,
259};
260
261293pub fn create(
262294 graph: *Graph,
263 build_root: Cache.Directory,
264 cache_root: Cache.Directory,
295 root: Cache.Path,
265296 available_deps: AvailableDeps,
266297) error{OutOfMemory}!*Build {
267298 const arena = graph.arena;
......@@ -269,31 +300,15 @@ pub fn create(
269300 const b = try arena.create(Build);
270301 b.* = .{
271302 .graph = graph,
272 .build_root = build_root,
273 .cache_root = cache_root,
274 .verbose = false,
275 .verbose_link = false,
276 .verbose_cc = false,
277 .verbose_air = false,
278 .verbose_llvm_ir = null,
279 .verbose_llvm_bc = null,
280 .verbose_llvm_cpu_features = false,
303 .root = root,
281304 .invalid_user_input = false,
282305 .allocator = arena,
283306 .user_input_options = UserInputOptionsMap.init(arena),
284 .available_options_map = AvailableOptionsMap.init(arena),
285 .available_options_list = std.array_list.Managed(AvailableOption).init(arena),
286307 .top_level_steps = .{},
287308 .default_step = undefined,
288 .search_prefixes = .empty,
289 .install_prefix = undefined,
290 .lib_dir = undefined,
291 .exe_dir = undefined,
292 .h_dir = undefined,
293 .dest_dir = graph.environ_map.get("DESTDIR"),
294309 .install_tls = .{
295310 .step = .init(.{
296 .id = TopLevelStep.base_id,
311 .tag = .top_level,
297312 .name = "install",
298313 .owner = b,
299314 }),
......@@ -301,21 +316,17 @@ pub fn create(
301316 },
302317 .uninstall_tls = .{
303318 .step = .init(.{
304 .id = TopLevelStep.base_id,
319 .tag = .top_level,
305320 .name = "uninstall",
306321 .owner = b,
307 .makeFn = makeUninstall,
308322 }),
309323 .description = "Remove build artifacts from prefix path",
310324 },
311 .install_path = undefined,
312 .args = null,
313325 .modules = .empty,
314326 .named_writefiles = .empty,
315327 .named_lazy_paths = .empty,
316328 .pkg_hash = "",
317329 .available_deps = available_deps,
318 .release_mode = .off,
319330 };
320331 try b.top_level_steps.put(arena, b.install_tls.step.name, &b.install_tls);
321332 try b.top_level_steps.put(arena, b.uninstall_tls.step.name, &b.uninstall_tls);
......@@ -326,32 +337,20 @@ pub fn create(
326337fn createChild(
327338 parent: *Build,
328339 dep_name: []const u8,
329 build_root: Cache.Directory,
330 pkg_hash: []const u8,
331 pkg_deps: AvailableDeps,
332 user_input_options: UserInputOptionsMap,
333) error{OutOfMemory}!*Build {
334 const child = try createChildOnly(parent, dep_name, build_root, pkg_hash, pkg_deps, user_input_options);
335 try determineAndApplyInstallPrefix(child);
336 return child;
337}
338
339fn createChildOnly(
340 parent: *Build,
341 dep_name: []const u8,
342 build_root: Cache.Directory,
340 root: Cache.Path,
343341 pkg_hash: []const u8,
344342 pkg_deps: AvailableDeps,
345343 user_input_options: UserInputOptionsMap,
346344) error{OutOfMemory}!*Build {
347 const allocator = parent.allocator;
348 const child = try allocator.create(Build);
345 const arena = parent.graph.arena;
346 const child = try arena.create(Build);
349347 child.* = .{
350348 .graph = parent.graph,
351 .allocator = allocator,
349 .root = root,
350 .allocator = arena,
352351 .install_tls = .{
353352 .step = .init(.{
354 .id = TopLevelStep.base_id,
353 .tag = .top_level,
355354 .name = "install",
356355 .owner = child,
357356 }),
......@@ -359,58 +358,31 @@ fn createChildOnly(
359358 },
360359 .uninstall_tls = .{
361360 .step = .init(.{
362 .id = TopLevelStep.base_id,
361 .tag = .top_level,
363362 .name = "uninstall",
364363 .owner = child,
365 .makeFn = makeUninstall,
366364 }),
367365 .description = "Remove build artifacts from prefix path",
368366 },
369367 .user_input_options = user_input_options,
370 .available_options_map = AvailableOptionsMap.init(allocator),
371 .available_options_list = std.array_list.Managed(AvailableOption).init(allocator),
372 .verbose = parent.verbose,
373 .verbose_link = parent.verbose_link,
374 .verbose_cc = parent.verbose_cc,
375 .verbose_air = parent.verbose_air,
376 .verbose_llvm_ir = parent.verbose_llvm_ir,
377 .verbose_llvm_bc = parent.verbose_llvm_bc,
378 .verbose_llvm_cpu_features = parent.verbose_llvm_cpu_features,
379 .reference_trace = parent.reference_trace,
380368 .invalid_user_input = false,
381369 .default_step = undefined,
382370 .top_level_steps = .{},
383 .install_prefix = undefined,
384 .dest_dir = parent.dest_dir,
385 .lib_dir = parent.lib_dir,
386 .exe_dir = parent.exe_dir,
387 .h_dir = parent.h_dir,
388 .install_path = parent.install_path,
389 .sysroot = parent.sysroot,
390 .search_prefixes = parent.search_prefixes,
391 .libc_file = parent.libc_file,
392 .build_root = build_root,
393 .cache_root = parent.cache_root,
394371 .debug_log_scopes = parent.debug_log_scopes,
395 .debug_compile_errors = parent.debug_compile_errors,
396 .debug_incremental = parent.debug_incremental,
397 .debug_pkg_config = parent.debug_pkg_config,
398372 .enable_darling = parent.enable_darling,
399373 .enable_qemu = parent.enable_qemu,
400374 .enable_rosetta = parent.enable_rosetta,
401375 .enable_wasmtime = parent.enable_wasmtime,
402376 .enable_wine = parent.enable_wine,
403 .libc_runtimes_dir = parent.libc_runtimes_dir,
404377 .dep_prefix = parent.fmt("{s}{s}.", .{ parent.dep_prefix, dep_name }),
405378 .modules = .empty,
406379 .named_writefiles = .empty,
407380 .named_lazy_paths = .empty,
408381 .pkg_hash = pkg_hash,
409382 .available_deps = pkg_deps,
410 .release_mode = parent.release_mode,
411383 };
412 try child.top_level_steps.put(allocator, child.install_tls.step.name, &child.install_tls);
413 try child.top_level_steps.put(allocator, child.uninstall_tls.step.name, &child.uninstall_tls);
384 try child.top_level_steps.put(arena, child.install_tls.step.name, &child.install_tls);
385 try child.top_level_steps.put(arena, child.uninstall_tls.step.name, &child.uninstall_tls);
414386 child.default_step = &child.install_tls.step;
415387 return child;
416388}
......@@ -624,13 +596,17 @@ const OrderedUserValue = union(enum) {
624596 hasher.update(sp.sub_path);
625597 },
626598 .generated => |gen| {
627 hasher.update(gen.file.step.owner.pkg_hash);
628 hasher.update(std.mem.asBytes(&gen.up));
599 hasher.update(@ptrCast(&gen.index));
600 hasher.update(@ptrCast(&gen.up));
629601 hasher.update(gen.sub_path);
630602 },
631603 .cwd_relative => |rel_path| {
632604 hasher.update(rel_path);
633605 },
606 .relative => |r| {
607 hasher.update(@ptrCast(&r.base));
608 hasher.update(@ptrCast(&r.sub_path));
609 },
634610 .dependency => |dep| {
635611 hasher.update(dep.dependency.builder.pkg_hash);
636612 hasher.update(dep.sub_path);
......@@ -702,59 +678,6 @@ fn hashUserInputOptionsMap(allocator: Allocator, user_input_options: UserInputOp
702678 user_option.hash(hasher);
703679}
704680
705fn determineAndApplyInstallPrefix(b: *Build) error{OutOfMemory}!void {
706 // Create an installation directory local to this package. This will be used when
707 // dependant packages require a standard prefix, such as include directories for C headers.
708 var hash = b.graph.cache.hash;
709 // Random bytes to make unique. Refresh this with new random bytes when
710 // implementation is modified in a non-backwards-compatible way.
711 hash.add(@as(u32, 0xd8cb0055));
712 hash.addBytes(b.dep_prefix);
713
714 var wyhash = std.hash.Wyhash.init(0);
715 hashUserInputOptionsMap(b.allocator, b.user_input_options, &wyhash);
716 hash.add(wyhash.final());
717
718 const digest = hash.final();
719 const install_prefix = try b.cache_root.join(b.allocator, &.{ "i", &digest });
720 b.resolveInstallPrefix(install_prefix, .{});
721}
722
723/// This function is intended to be called by lib/build_runner.zig, not a build.zig file.
724pub fn resolveInstallPrefix(b: *Build, install_prefix: ?[]const u8, dir_list: DirList) void {
725 if (b.dest_dir) |dest_dir| {
726 b.install_prefix = install_prefix orelse "/usr";
727 b.install_path = b.pathJoin(&.{ dest_dir, b.install_prefix });
728 } else {
729 b.install_prefix = install_prefix orelse
730 (b.build_root.join(b.allocator, &.{"zig-out"}) catch @panic("unhandled error"));
731 b.install_path = b.install_prefix;
732 }
733
734 var lib_list = [_][]const u8{ b.install_path, "lib" };
735 var exe_list = [_][]const u8{ b.install_path, "bin" };
736 var h_list = [_][]const u8{ b.install_path, "include" };
737
738 if (dir_list.lib_dir) |dir| {
739 if (fs.path.isAbsolute(dir)) lib_list[0] = b.dest_dir orelse "";
740 lib_list[1] = dir;
741 }
742
743 if (dir_list.exe_dir) |dir| {
744 if (fs.path.isAbsolute(dir)) exe_list[0] = b.dest_dir orelse "";
745 exe_list[1] = dir;
746 }
747
748 if (dir_list.include_dir) |dir| {
749 if (fs.path.isAbsolute(dir)) h_list[0] = b.dest_dir orelse "";
750 h_list[1] = dir;
751 }
752
753 b.lib_dir = b.pathJoin(&lib_list);
754 b.exe_dir = b.pathJoin(&exe_list);
755 b.h_dir = b.pathJoin(&h_list);
756}
757
758681/// Create a set of key-value pairs that can be converted into a Zig source
759682/// file and then inserted into a Zig compilation's module table for importing.
760683/// In other words, this provides a way to expose build.zig values to Zig
......@@ -904,8 +827,10 @@ pub const AssemblyOptions = struct {
904827/// it available to other packages which depend on this one.
905828/// `createModule` can be used instead to create a private module.
906829pub fn addModule(b: *Build, name: []const u8, options: Module.CreateOptions) *Module {
830 const graph = b.graph;
831 const arena = graph.arena;
907832 const module = Module.create(b, options);
908 b.modules.put(b.graph.arena, b.dupe(name), module) catch @panic("OOM");
833 b.modules.put(arena, graph.dupeString(name), module) catch @panic("OOM");
909834 return module;
910835}
911836
......@@ -916,11 +841,23 @@ pub fn createModule(b: *Build, options: Module.CreateOptions) *Module {
916841 return Module.create(b, options);
917842}
918843
919/// Initializes a `Step.Run` with argv, which must at least have the path to the
920/// executable. More command line arguments can be added with `addArg`,
921/// `addArgs`, and `addArtifactArg`.
922/// Be careful using this function, as it introduces a system dependency.
923/// To run an executable built with zig build, see `Step.Compile.run`.
844/// Creates a step that executes a process on the host system.
845///
846/// `argv` is one or more command line arguments passed to the executed
847/// process. The first element is the name of the executable to run. More
848/// command line arguments can be added with methods of `Step.Run`, such as:
849/// * `Step.Run.addArgs`
850/// * `Step.Run.addArtifactArg`
851/// * `Step.Run.addFileArg`
852/// * `Step.Run.addOutputFileArg`
853///
854/// This function introduces a system dependency, compromising reproducibility
855/// and making it more difficult to set up one's computer in order to build the
856/// project from source.
857///
858/// See also:
859/// * `addRunArtifact`
860/// * `addRunFile`
924861pub fn addSystemCommand(b: *Build, argv: []const []const u8) *Step.Run {
925862 assert(argv.len >= 1);
926863 const run_step = Step.Run.create(b, b.fmt("run {s}", .{argv[0]}));
......@@ -930,16 +867,22 @@ pub fn addSystemCommand(b: *Build, argv: []const []const u8) *Step.Run {
930867
931868/// Creates a `Step.Run` with an executable built with `addExecutable`.
932869/// Add command line arguments with methods of `Step.Run`.
870///
871/// It doesn't have to target the host. In some cases cross-compiled binaries
872/// can even be executed.
873///
874/// This is declarative; it constructs a build step that may or may not be run
875/// depending on the options provided by the user to the build command.
876///
877/// See also:
878/// * `addSystemCommand`
879/// * `addRunFile`
933880pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run {
934 // It doesn't have to be native. We catch that if you actually try to run it.
935 // Consider that this is declarative; the run step may not be run unless a user
936 // option is supplied.
937
938881 // Avoid the common case of the step name looking like "run test test".
939882 const step_name = if (exe.kind.isTest() and mem.eql(u8, exe.name, "test"))
940 b.fmt("run {s}", .{@tagName(exe.kind)})
883 b.fmt("run {t}", .{exe.kind})
941884 else
942 b.fmt("run {s} {s}", .{ @tagName(exe.kind), exe.name });
885 b.fmt("run {t} {s}", .{ exe.kind, exe.name });
943886
944887 const run_step = Step.Run.create(b, step_name);
945888 run_step.producer = exe;
......@@ -994,6 +937,19 @@ pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run {
994937 return run_step;
995938}
996939
940/// Creates a step that executes the provided file.
941///
942/// Add more command line arguments via methods of `Step.Run`.
943///
944/// See also:
945/// * `addSystemCommand`
946/// * `addRunArtifact`
947pub fn addRunFile(b: *Build, executable: LazyPath) *Step.Run {
948 const run_step = Step.Run.create(b, b.fmt("run {f}", .{executable.fmt(b.graph)}));
949 run_step.addFileArg(executable);
950 return run_step;
951}
952
997953/// Using the `values` provided, produces a C header file, possibly based on a
998954/// template input file (e.g. config.h.in).
999955/// When an input template file is provided, this function will fail the build
......@@ -1013,36 +969,18 @@ pub fn addConfigHeader(
1013969 return config_header_step;
1014970}
1015971
1016/// Allocator.dupe without the need to handle out of memory.
1017pub fn dupe(b: *Build, bytes: []const u8) []u8 {
1018 return dupeInner(b.allocator, bytes);
1019}
1020
1021pub fn dupeInner(allocator: std.mem.Allocator, bytes: []const u8) []u8 {
1022 return allocator.dupe(u8, bytes) catch @panic("OOM");
972pub fn dupe(b: *Build, bytes: []const u8) []const u8 {
973 return b.graph.dupeString(bytes);
1023974}
1024975
1025/// Duplicates an array of strings without the need to handle out of memory.
1026pub fn dupeStrings(b: *Build, strings: []const []const u8) [][]u8 {
1027 const array = b.allocator.alloc([]u8, strings.len) catch @panic("OOM");
1028 for (array, strings) |*dest, source| dest.* = b.dupe(source);
1029 return array;
976/// Deprecated, call `Graph.dupeStrings` instead.
977pub fn dupeStrings(b: *Build, strings: []const []const u8) []const []const u8 {
978 return b.graph.dupeStrings(strings);
1030979}
1031980
1032/// Duplicates a path and converts all slashes to the OS's canonical path separator.
1033pub fn dupePath(b: *Build, bytes: []const u8) []u8 {
1034 return dupePathInner(b.allocator, bytes);
1035}
1036
1037fn dupePathInner(allocator: std.mem.Allocator, bytes: []const u8) []u8 {
1038 const the_copy = dupeInner(allocator, bytes);
1039 for (the_copy) |*byte| {
1040 switch (byte.*) {
1041 '/', '\\' => byte.* = fs.path.sep,
1042 else => {},
1043 }
1044 }
1045 return the_copy;
981/// Deprecated, call `Graph.dupePath` instead.
982pub fn dupePath(b: *Build, bytes: []const u8) []const u8 {
983 return b.graph.dupePath(bytes);
1046984}
1047985
1048986pub fn addWriteFile(b: *Build, file_path: []const u8, data: []const u8) *Step.WriteFile {
......@@ -1052,13 +990,15 @@ pub fn addWriteFile(b: *Build, file_path: []const u8, data: []const u8) *Step.Wr
1052990}
1053991
1054992pub fn addNamedWriteFiles(b: *Build, name: []const u8) *Step.WriteFile {
993 const graph = b.graph;
1055994 const wf = Step.WriteFile.create(b);
1056 b.named_writefiles.put(b.graph.arena, b.dupe(name), wf) catch @panic("OOM");
995 b.named_writefiles.put(graph.arena, graph.dupeString(name), wf) catch @panic("OOM");
1057996 return wf;
1058997}
1059998
1060999pub fn addNamedLazyPath(b: *Build, name: []const u8, lp: LazyPath) void {
1061 b.named_lazy_paths.put(b.graph.arena, b.dupe(name), lp.dupe(b)) catch @panic("OOM");
1000 const graph = b.graph;
1001 b.named_lazy_paths.put(graph.arena, graph.dupeString(name), lp.dupe(graph)) catch @panic("OOM");
10621002}
10631003
10641004/// Creates a step for mutating files inside a temporary directory created lazily
......@@ -1097,6 +1037,16 @@ pub fn addWriteFiles(b: *Build) *Step.WriteFile {
10971037 return Step.WriteFile.create(b);
10981038}
10991039
1040/// Creates a step for writing data to paths relative to the build root,
1041/// mutating the project's source files.
1042///
1043/// This build step was designed not to be used during the normal build
1044/// process, but rather as a utility run by a developer with intention to
1045/// update source files, which will then be committed to version control.
1046///
1047/// Example use cases:
1048/// * precompiling assets which are tracked by version control
1049/// * snapshot testing
11001050pub fn addUpdateSourceFiles(b: *Build) *Step.UpdateSourceFiles {
11011051 return Step.UpdateSourceFiles.create(b);
11021052}
......@@ -1121,28 +1071,21 @@ pub fn getUninstallStep(b: *Build) *Step {
11211071 return &b.uninstall_tls.step;
11221072}
11231073
1124fn makeUninstall(uninstall_step: *Step, options: Step.MakeOptions) anyerror!void {
1125 _ = options;
1126 const uninstall_tls: *TopLevelStep = @fieldParentPtr("step", uninstall_step);
1127 const b: *Build = @fieldParentPtr("uninstall_tls", uninstall_tls);
1128
1129 _ = b;
1130 @panic("TODO implement https://github.com/ziglang/zig/issues/14943");
1131}
1132
11331074/// Creates a configuration option to be passed to the build.zig script.
11341075/// When a user directly runs `zig build`, they can set these options with `-D` arguments.
11351076/// When a project depends on a Zig package as a dependency, it programmatically sets
11361077/// these options when calling the dependency's build.zig script as a function.
11371078/// `null` is returned when an option is left to default.
11381079pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw: []const u8) ?T {
1139 const name = b.dupe(name_raw);
1140 const description = b.dupe(description_raw);
1080 const graph = b.graph;
1081 const arena = graph.arena;
1082 const name = graph.dupeString(name_raw);
1083 const description = graph.dupeString(description_raw);
11411084 const type_id = comptime typeToEnum(T);
11421085 const enum_options = if (type_id == .@"enum" or type_id == .enum_list) blk: {
11431086 const EnumType = if (type_id == .enum_list) @typeInfo(T).pointer.child else T;
11441087 const fields = comptime std.meta.fields(EnumType);
1145 var options = std.array_list.Managed([]const u8).initCapacity(b.allocator, fields.len) catch @panic("OOM");
1088 var options = std.array_list.Managed([]const u8).initCapacity(arena, fields.len) catch @panic("OOM");
11461089
11471090 inline for (fields) |field| {
11481091 options.appendAssumeCapacity(field.name);
......@@ -1156,10 +1099,9 @@ pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw
11561099 .description = description,
11571100 .enum_options = enum_options,
11581101 };
1159 if ((b.available_options_map.fetchPut(name, available_option) catch @panic("OOM")) != null) {
1160 panic("Option '{s}' declared twice", .{name});
1102 if ((b.available_options_map.fetchPut(arena, name, available_option) catch @panic("OOM")) != null) {
1103 panic("option '{s}' declared twice", .{name});
11611104 }
1162 b.available_options_list.append(available_option) catch @panic("OOM");
11631105
11641106 const option_ptr = b.user_input_options.getPtr(name) orelse return null;
11651107 option_ptr.used = true;
......@@ -1172,36 +1114,32 @@ pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw
11721114 } else if (mem.eql(u8, s, "false")) {
11731115 return false;
11741116 } else {
1175 log.err("Expected -D{s} to be a boolean, but received '{s}'", .{ name, s });
1117 log.err("expected -D{s} to be a boolean; received: {s}", .{ name, s });
11761118 b.markInvalidUserInput();
11771119 return null;
11781120 }
11791121 },
11801122 .list, .map, .lazy_path, .lazy_path_list => {
1181 log.err("Expected -D{s} to be a boolean, but received a {s}.", .{
1182 name, @tagName(option_ptr.value),
1183 });
1123 log.err("expected -D{s} to be a boolean; received: {t}", .{ name, option_ptr.value });
11841124 b.markInvalidUserInput();
11851125 return null;
11861126 },
11871127 },
11881128 .int => switch (option_ptr.value) {
11891129 .flag, .list, .map, .lazy_path, .lazy_path_list => {
1190 log.err("Expected -D{s} to be an integer, but received a {s}.", .{
1191 name, @tagName(option_ptr.value),
1192 });
1130 log.err("expected -D{s} to be an integer; received: {t}", .{ name, option_ptr.value });
11931131 b.markInvalidUserInput();
11941132 return null;
11951133 },
11961134 .scalar => |s| {
11971135 const n = std.fmt.parseInt(T, s, 10) catch |err| switch (err) {
11981136 error.Overflow => {
1199 log.err("-D{s} value {s} cannot fit into type {s}.", .{ name, s, @typeName(T) });
1137 log.err("-D{s} value {s} cannot fit into type {s}", .{ name, s, @typeName(T) });
12001138 b.markInvalidUserInput();
12011139 return null;
12021140 },
12031141 else => {
1204 log.err("Expected -D{s} to be an integer of type {s}.", .{ name, @typeName(T) });
1142 log.err("expected -D{s} to be an integer of type {s}", .{ name, @typeName(T) });
12051143 b.markInvalidUserInput();
12061144 return null;
12071145 },
......@@ -1211,15 +1149,13 @@ pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw
12111149 },
12121150 .float => switch (option_ptr.value) {
12131151 .flag, .map, .list, .lazy_path, .lazy_path_list => {
1214 log.err("Expected -D{s} to be a float, but received a {s}.", .{
1215 name, @tagName(option_ptr.value),
1216 });
1152 log.err("expected -D{s} to be a float; received: {t}", .{ name, option_ptr.value });
12171153 b.markInvalidUserInput();
12181154 return null;
12191155 },
12201156 .scalar => |s| {
12211157 const n = std.fmt.parseFloat(T, s) catch {
1222 log.err("Expected -D{s} to be a float of type {s}.", .{ name, @typeName(T) });
1158 log.err("expected -D{s} to be a float of type {s}", .{ name, @typeName(T) });
12231159 b.markInvalidUserInput();
12241160 return null;
12251161 };
......@@ -1228,9 +1164,7 @@ pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw
12281164 },
12291165 .@"enum" => switch (option_ptr.value) {
12301166 .flag, .map, .list, .lazy_path, .lazy_path_list => {
1231 log.err("Expected -D{s} to be an enum, but received a {s}.", .{
1232 name, @tagName(option_ptr.value),
1233 });
1167 log.err("expected -D{s} to be an enum; received: {t}.", .{ name, option_ptr.value });
12341168 b.markInvalidUserInput();
12351169 return null;
12361170 },
......@@ -1238,7 +1172,7 @@ pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw
12381172 if (std.meta.stringToEnum(T, s)) |enum_lit| {
12391173 return enum_lit;
12401174 } else {
1241 log.err("Expected -D{s} to be of type {s}.", .{ name, @typeName(T) });
1175 log.err("expected -D{s} to be of type {s}", .{ name, @typeName(T) });
12421176 b.markInvalidUserInput();
12431177 return null;
12441178 }
......@@ -1246,9 +1180,7 @@ pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw
12461180 },
12471181 .string => switch (option_ptr.value) {
12481182 .flag, .list, .map, .lazy_path, .lazy_path_list => {
1249 log.err("Expected -D{s} to be a string, but received a {s}.", .{
1250 name, @tagName(option_ptr.value),
1251 });
1183 log.err("expected -D{s} to be a string; received: {t}", .{ name, option_ptr.value });
12521184 b.markInvalidUserInput();
12531185 return null;
12541186 },
......@@ -1256,9 +1188,7 @@ pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw
12561188 },
12571189 .build_id => switch (option_ptr.value) {
12581190 .flag, .map, .list, .lazy_path, .lazy_path_list => {
1259 log.err("Expected -D{s} to be an enum, but received a {s}.", .{
1260 name, @tagName(option_ptr.value),
1261 });
1191 log.err("expected -D{s} to be an enum; received: {t}.", .{ name, option_ptr.value });
12621192 b.markInvalidUserInput();
12631193 return null;
12641194 },
......@@ -1266,7 +1196,7 @@ pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw
12661196 if (std.zig.BuildId.parse(s)) |build_id| {
12671197 return build_id;
12681198 } else |err| {
1269 log.err("unable to parse option '-D{s}': {s}", .{ name, @errorName(err) });
1199 log.err("failed to parse option -D{s}: {t}", .{ name, err });
12701200 b.markInvalidUserInput();
12711201 return null;
12721202 }
......@@ -1274,42 +1204,38 @@ pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw
12741204 },
12751205 .list => switch (option_ptr.value) {
12761206 .flag, .map, .lazy_path, .lazy_path_list => {
1277 log.err("Expected -D{s} to be a list, but received a {s}.", .{
1278 name, @tagName(option_ptr.value),
1279 });
1207 log.err("expected -D{s} to be a list; received: {t}", .{ name, option_ptr.value });
12801208 b.markInvalidUserInput();
12811209 return null;
12821210 },
12831211 .scalar => |s| {
1284 return b.allocator.dupe([]const u8, &[_][]const u8{s}) catch @panic("OOM");
1212 return arena.dupe([]const u8, &[_][]const u8{s}) catch @panic("OOM");
12851213 },
12861214 .list => |lst| return lst.items,
12871215 },
12881216 .enum_list => switch (option_ptr.value) {
12891217 .flag, .map, .lazy_path, .lazy_path_list => {
1290 log.err("Expected -D{s} to be a list, but received a {s}.", .{
1291 name, @tagName(option_ptr.value),
1292 });
1218 log.err("expected -D{s} to be a list; received: {t}", .{ name, option_ptr.value });
12931219 b.markInvalidUserInput();
12941220 return null;
12951221 },
12961222 .scalar => |s| {
12971223 const Child = @typeInfo(T).pointer.child;
12981224 const value = std.meta.stringToEnum(Child, s) orelse {
1299 log.err("Expected -D{s} to be of type {s}.", .{ name, @typeName(Child) });
1225 log.err("expected -D{s} to be of type {s}", .{ name, @typeName(Child) });
13001226 b.markInvalidUserInput();
13011227 return null;
13021228 };
1303 return b.allocator.dupe(Child, &[_]Child{value}) catch @panic("OOM");
1229 return arena.dupe(Child, &[_]Child{value}) catch @panic("OOM");
13041230 },
13051231 .list => |lst| {
13061232 const Child = @typeInfo(T).pointer.child;
1307 const new_list = b.allocator.alloc(Child, lst.items.len) catch @panic("OOM");
1233 const new_list = graph.alloc(Child, lst.items.len);
13081234 for (new_list, lst.items) |*new_item, str| {
13091235 new_item.* = std.meta.stringToEnum(Child, str) orelse {
1310 log.err("Expected -D{s} to be of type {s}.", .{ name, @typeName(Child) });
1236 log.err("expected -D{s} to be of type {s}", .{ name, @typeName(Child) });
13111237 b.markInvalidUserInput();
1312 b.allocator.free(new_list);
1238 arena.free(new_list);
13131239 return null;
13141240 };
13151241 }
......@@ -1320,18 +1246,16 @@ pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw
13201246 .scalar => |s| return .{ .cwd_relative = s },
13211247 .lazy_path => |lp| return lp,
13221248 .flag, .map, .list, .lazy_path_list => {
1323 log.err("Expected -D{s} to be a path, but received a {s}.", .{
1324 name, @tagName(option_ptr.value),
1325 });
1249 log.err("expected -D{s} to be a path; received: {t}", .{ name, option_ptr.value });
13261250 b.markInvalidUserInput();
13271251 return null;
13281252 },
13291253 },
13301254 .lazy_path_list => switch (option_ptr.value) {
1331 .scalar => |s| return b.allocator.dupe(LazyPath, &[_]LazyPath{.{ .cwd_relative = s }}) catch @panic("OOM"),
1332 .lazy_path => |lp| return b.allocator.dupe(LazyPath, &[_]LazyPath{lp}) catch @panic("OOM"),
1255 .scalar => |s| return arena.dupe(LazyPath, &[_]LazyPath{.{ .cwd_relative = s }}) catch @panic("OOM"),
1256 .lazy_path => |lp| return arena.dupe(LazyPath, &[_]LazyPath{lp}) catch @panic("OOM"),
13331257 .list => |lst| {
1334 const new_list = b.allocator.alloc(LazyPath, lst.items.len) catch @panic("OOM");
1258 const new_list = graph.alloc(LazyPath, lst.items.len);
13351259 for (new_list, lst.items) |*new_item, str| {
13361260 new_item.* = .{ .cwd_relative = str };
13371261 }
......@@ -1339,9 +1263,7 @@ pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw
13391263 },
13401264 .lazy_path_list => |lp_list| return lp_list.items,
13411265 .flag, .map => {
1342 log.err("Expected -D{s} to be a path, but received a {s}.", .{
1343 name, @tagName(option_ptr.value),
1344 });
1266 log.err("expected -D{s} to be a path; received: {t}", .{ name, option_ptr.value });
13451267 b.markInvalidUserInput();
13461268 return null;
13471269 },
......@@ -1350,17 +1272,19 @@ pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw
13501272}
13511273
13521274pub fn step(b: *Build, name: []const u8, description: []const u8) *Step {
1353 const step_info = b.allocator.create(TopLevelStep) catch @panic("OOM");
1275 const graph = b.graph;
1276 const arena = graph.arena;
1277 const step_info = arena.create(Step.TopLevel) catch @panic("OOM");
13541278 step_info.* = .{
13551279 .step = .init(.{
1356 .id = TopLevelStep.base_id,
1280 .tag = .top_level,
13571281 .name = name,
13581282 .owner = b,
13591283 }),
1360 .description = b.dupe(description),
1284 .description = graph.dupeString(description),
13611285 };
1362 const gop = b.top_level_steps.getOrPut(b.allocator, name) catch @panic("OOM");
1363 if (gop.found_existing) std.debug.panic("A top-level step with name \"{s}\" already exists", .{name});
1286 const gop = b.top_level_steps.getOrPut(arena, name) catch @panic("OOM");
1287 if (gop.found_existing) panic("A top-level step with name \"{s}\" already exists", .{name});
13641288
13651289 gop.key_ptr.* = step_info.step.name;
13661290 gop.value_ptr.* = step_info;
......@@ -1373,8 +1297,10 @@ pub const StandardOptimizeOptionOptions = struct {
13731297};
13741298
13751299pub fn standardOptimizeOption(b: *Build, options: StandardOptimizeOptionOptions) std.builtin.OptimizeMode {
1300 const graph = b.graph;
1301
13761302 if (options.preferred_optimize_mode) |mode| {
1377 if (b.option(bool, "release", "optimize for end users") orelse (b.release_mode != .off)) {
1303 if (b.option(bool, "release", "optimize for end users") orelse (graph.release_mode != .off)) {
13781304 return mode;
13791305 } else {
13801306 return .Debug;
......@@ -1389,7 +1315,7 @@ pub fn standardOptimizeOption(b: *Build, options: StandardOptimizeOptionOptions)
13891315 return mode;
13901316 }
13911317
1392 return switch (b.release_mode) {
1318 return switch (graph.release_mode) {
13931319 .off => .Debug,
13941320 .any => {
13951321 std.debug.print("the project does not declare a preferred optimization mode. choose: --release=fast, --release=safe, or --release=small\n", .{});
......@@ -1424,8 +1350,8 @@ pub fn parseTargetQuery(options: std.Target.Query.ParseOptions) error{ParseFaile
14241350 opts_copy.diagnostics = &diags;
14251351 return std.Target.Query.parse(opts_copy) catch |err| switch (err) {
14261352 error.UnknownCpuModel => {
1427 std.debug.print("unknown CPU: '{s}'\navailable CPUs for architecture '{s}':\n", .{
1428 diags.cpu_name.?, @tagName(diags.arch.?),
1353 std.debug.print("unknown CPU: '{s}'\navailable CPUs for architecture '{t}':\n", .{
1354 diags.cpu_name.?, diags.arch.?,
14291355 });
14301356 for (diags.arch.?.allCpuModels()) |cpu| {
14311357 std.debug.print(" {s}\n", .{cpu.name});
......@@ -1435,11 +1361,10 @@ pub fn parseTargetQuery(options: std.Target.Query.ParseOptions) error{ParseFaile
14351361 error.UnknownCpuFeature => {
14361362 std.debug.print(
14371363 \\unknown CPU feature: '{s}'
1438 \\available CPU features for architecture '{s}':
1364 \\available CPU features for architecture '{t}':
14391365 \\
14401366 , .{
1441 diags.unknown_feature_name.?,
1442 @tagName(diags.arch.?),
1367 diags.unknown_feature_name.?, diags.arch.?,
14431368 });
14441369 for (diags.arch.?.allFeaturesList()) |feature| {
14451370 std.debug.print(" {s}: {s}\n", .{ feature.name, feature.description });
......@@ -1468,6 +1393,9 @@ pub fn parseTargetQuery(options: std.Target.Query.ParseOptions) error{ParseFaile
14681393
14691394/// Exposes standard `zig build` options for choosing a target.
14701395pub fn standardTargetOptionsQueryOnly(b: *Build, args: StandardTargetOptionsArgs) Target.Query {
1396 const graph = b.graph;
1397 const arena = graph.arena;
1398
14711399 const maybe_triple = b.option(
14721400 []const u8,
14731401 "target",
......@@ -1516,20 +1444,22 @@ pub fn standardTargetOptionsQueryOnly(b: *Build, args: StandardTargetOptionsArgs
15161444
15171445 for (whitelist) |q| {
15181446 log.info("allowed target: -Dtarget={s} -Dcpu={s}", .{
1519 q.zigTriple(b.allocator) catch @panic("OOM"),
1520 q.serializeCpuAlloc(b.allocator) catch @panic("OOM"),
1447 q.zigTriple(arena) catch @panic("OOM"),
1448 q.serializeCpuAlloc(arena) catch @panic("OOM"),
15211449 });
15221450 }
15231451 log.err("chosen target '{s}' does not match one of the allowed targets", .{
1524 selected_target.zigTriple(b.allocator) catch @panic("OOM"),
1452 selected_target.zigTriple(arena) catch @panic("OOM"),
15251453 });
15261454 b.markInvalidUserInput();
15271455 return args.default_target;
15281456}
15291457
15301458pub fn addUserInputOption(b: *Build, name_raw: []const u8, value_raw: []const u8) error{OutOfMemory}!bool {
1531 const name = b.dupe(name_raw);
1532 const value = b.dupe(value_raw);
1459 const graph = b.graph;
1460 const arena = graph.arena;
1461 const name = graph.dupeString(name_raw);
1462 const value = graph.dupeString(value_raw);
15331463 const gop = try b.user_input_options.getOrPut(name);
15341464 if (!gop.found_existing) {
15351465 gop.value_ptr.* = UserInputOption{
......@@ -1544,7 +1474,7 @@ pub fn addUserInputOption(b: *Build, name_raw: []const u8, value_raw: []const u8
15441474 switch (gop.value_ptr.value) {
15451475 .scalar => |s| {
15461476 // turn it into a list
1547 var list = std.array_list.Managed([]const u8).init(b.allocator);
1477 var list = std.array_list.Managed([]const u8).init(arena);
15481478 try list.append(s);
15491479 try list.append(value);
15501480 try b.user_input_options.put(name, .{
......@@ -1572,7 +1502,9 @@ pub fn addUserInputOption(b: *Build, name_raw: []const u8, value_raw: []const u8
15721502 return true;
15731503 },
15741504 .lazy_path, .lazy_path_list => {
1575 log.warn("the lazy path value type isn't added from the CLI, but somehow '{s}' is a .{f}", .{ name, std.zig.fmtId(@tagName(gop.value_ptr.value)) });
1505 log.warn("the lazy path value type isn't added from the CLI, but somehow '{s}' is a .{f}", .{
1506 name, std.zig.fmtId(@tagName(gop.value_ptr.value)),
1507 });
15761508 return true;
15771509 },
15781510 }
......@@ -1580,7 +1512,8 @@ pub fn addUserInputOption(b: *Build, name_raw: []const u8, value_raw: []const u8
15801512}
15811513
15821514pub fn addUserInputFlag(b: *Build, name_raw: []const u8) error{OutOfMemory}!bool {
1583 const name = b.dupe(name_raw);
1515 const graph = b.graph;
1516 const name = graph.dupeString(name_raw);
15841517 const gop = try b.user_input_options.getOrPut(name);
15851518 if (!gop.found_existing) {
15861519 gop.value_ptr.* = .{
......@@ -1602,7 +1535,7 @@ pub fn addUserInputFlag(b: *Build, name_raw: []const u8) error{OutOfMemory}!bool
16021535 return true;
16031536 },
16041537 .lazy_path => |lp| {
1605 log.err("Flag '-D{s}' conflicts with option '-D{s}={s}'.", .{ name, name, lp.getDisplayName() });
1538 log.err("Flag '-D{s}' conflicts with option '-D{s}={f}'.", .{ name, name, lp });
16061539 return true;
16071540 },
16081541
......@@ -1611,7 +1544,7 @@ pub fn addUserInputFlag(b: *Build, name_raw: []const u8) error{OutOfMemory}!bool
16111544 return false;
16121545}
16131546
1614fn typeToEnum(comptime T: type) TypeId {
1547fn typeToEnum(comptime T: type) Configuration.AvailableOption.Type {
16151548 return switch (T) {
16161549 std.zig.BuildId => .build_id,
16171550 LazyPath => .lazy_path,
......@@ -1732,26 +1665,10 @@ pub fn addCheckFile(
17321665 return Step.CheckFile.create(b, file_source, options);
17331666}
17341667
1735pub fn truncateFile(b: *Build, dest_path: []const u8) (Io.Dir.CreateDirError || Io.Dir.StatFileError)!void {
1736 const io = b.graph.io;
1737 if (b.verbose) log.info("truncate {s}", .{dest_path});
1738 const cwd = Io.Dir.cwd();
1739 var src_file = cwd.createFile(io, dest_path, .{}) catch |err| switch (err) {
1740 error.FileNotFound => blk: {
1741 if (fs.path.dirname(dest_path)) |dirname| {
1742 try cwd.createDirPath(io, dirname);
1743 }
1744 break :blk try cwd.createFile(io, dest_path, .{});
1745 },
1746 else => |e| return e,
1747 };
1748 src_file.close(io);
1749}
1750
17511668/// References a file or directory relative to the source root.
17521669pub fn path(b: *Build, sub_path: []const u8) LazyPath {
17531670 if (fs.path.isAbsolute(sub_path)) {
1754 std.debug.panic("sub_path is expected to be relative to the build root, but was this absolute path: '{s}'. It is best avoid absolute paths, but if you must, it is supported by LazyPath.cwd_relative", .{
1671 panic("sub_path is expected to be relative to the build root, but was this absolute path: '{s}'. Absolute paths can cause problems but can be created via Graph.cwdRelativePath", .{
17551672 sub_path,
17561673 });
17571674 }
......@@ -1761,27 +1678,106 @@ pub fn path(b: *Build, sub_path: []const u8) LazyPath {
17611678 } };
17621679}
17631680
1764/// This is low-level implementation details of the build system, not meant to
1765/// be called by users' build scripts. Even in the build system itself it is a
1766/// code smell to call this function.
1767pub fn pathFromRoot(b: *Build, sub_path: []const u8) []u8 {
1768 return b.pathResolve(&.{ b.build_root.path orelse ".", sub_path });
1769}
1770
1771fn pathFromCwd(b: *Build, sub_path: []const u8) []u8 {
1772 return b.pathResolve(&.{ b.graph.cache.cwd, sub_path });
1681/// Creates a list of files and/or directories relative to the source root.
1682pub fn pathList(b: *Build, sub_paths: []const []const u8) []const LazyPath {
1683 const graph = b.graph;
1684 const result = graph.alloc(LazyPath, sub_paths.len);
1685 for (result, sub_paths) |*d, s| d.* = path(b, s);
1686 return result;
17731687}
17741688
17751689pub fn pathJoin(b: *Build, paths: []const []const u8) []u8 {
1776 return fs.path.join(b.allocator, paths) catch @panic("OOM");
1690 const graph = b.graph;
1691 const arena = graph.arena;
1692 return fs.path.join(arena, paths) catch @panic("OOM");
17771693}
17781694
17791695pub fn pathResolve(b: *Build, paths: []const []const u8) []u8 {
1780 return fs.path.resolve(b.allocator, paths) catch @panic("OOM");
1696 const graph = b.graph;
1697 const arena = graph.arena;
1698 return fs.path.resolve(arena, paths) catch @panic("OOM");
17811699}
17821700
17831701pub fn fmt(b: *Build, comptime format: []const u8, args: anytype) []u8 {
1784 return std.fmt.allocPrint(b.allocator, format, args) catch @panic("OOM");
1702 const graph = b.graph;
1703 const arena = graph.arena;
1704 return std.fmt.allocPrint(arena, format, args) catch @panic("OOM");
1705}
1706
1707/// Creates an anonymous `Step` that searches for an executable on the host that
1708/// has more than one possible name.
1709///
1710/// Returns the `LazyPath` of the found executable. The search only takes place
1711/// if the `LazyPath` will be used by a depending `Step`.
1712///
1713/// This API is useful in the following cases:
1714/// * The binary is not named the same across all systems (for example "python"
1715/// vs "python3").
1716/// * The binary may be produced by building from source rather than being
1717/// globally installed and will therefore be possibly found in one of the
1718/// search prefix paths.
1719///
1720/// Names are searched in order, observing search prefixes first and then PATH
1721/// environment variable.
1722///
1723/// Windows file name extensions are searched automatically, respecting the
1724/// PATHEXT environment variable, so they need not be included in this list.
1725/// However, even on Windows, the names will be checked without appending
1726/// extensions first, so that can be used as a priority system.
1727///
1728/// See also:
1729/// * `findProgram`
1730pub fn findProgramLazy(b: *Build, options: Step.FindProgram.Options) LazyPath {
1731 return .{ .generated = .{ .index = Step.FindProgram.create(b, options).found_path } };
1732}
1733
1734pub const FindProgramOptions = Step.FindProgram.Options;
1735
1736/// Immediately (in the configure phase), searches for an executable on the host
1737/// that has more than one possible name.
1738///
1739/// Calling this function poisons the configuration cache, so it is only
1740/// appropriate when the existence of the program or its output needs to be
1741/// observed by configuration logic. For more information, see
1742/// `Graph.CachePoison` documentation.
1743///
1744/// Names are searched in order, observing search prefixes first and then PATH
1745/// environment variable.
1746///
1747/// Windows file name extensions are searched automatically, respecting the
1748/// PATHEXT environment variable, so they need not be included in this list.
1749/// However, even on Windows, the names will be checked without appending
1750/// extensions first, so that can be used as a priority system.
1751///
1752/// See also:
1753/// * `findProgramLazy`
1754pub fn findProgram(b: *Build, options: FindProgramOptions) ?[]const u8 {
1755 const graph = b.graph;
1756
1757 // Because it observes search prefixes and contents of directories in PATH.
1758 graph.poisonCache();
1759
1760 for (options.names) |name| {
1761 if (Io.Dir.path.isAbsolute(name)) {
1762 if (tryFindProgram(b, name)) |found| return found;
1763 }
1764 for (graph.search_prefixes.items) |search_prefix| {
1765 const full_path = b.pathJoin(&.{ search_prefix, "bin", name });
1766 if (tryFindProgram(b, full_path)) |found| return found;
1767 }
1768 }
1769
1770 if (b.graph.environ_map.get("PATH")) |PATH| {
1771 for (options.names) |name| {
1772 var it = mem.tokenizeScalar(u8, PATH, Io.Dir.path.delimiter);
1773 while (it.next()) |p| {
1774 const full_path = b.pathJoin(&.{ p, name });
1775 if (tryFindProgram(b, full_path)) |found| return found;
1776 }
1777 }
1778 }
1779
1780 return null;
17851781}
17861782
17871783fn supportedWindowsProgramExtension(ext: []const u8) bool {
......@@ -1792,14 +1788,17 @@ fn supportedWindowsProgramExtension(ext: []const u8) bool {
17921788}
17931789
17941790fn tryFindProgram(b: *Build, full_path: []const u8) ?[]const u8 {
1795 const io = b.graph.io;
1796 const arena = b.allocator;
1791 const graph = b.graph;
1792 const io = graph.io;
1793 const arena = graph.arena;
17971794
1798 if (b.build_root.handle.realPathFileAlloc(io, full_path, arena)) |p| {
1799 return p;
1795 if (Io.Dir.cwd().access(io, full_path, .{ .execute = true })) |_| {
1796 return full_path;
18001797 } else |err| switch (err) {
1801 error.OutOfMemory => @panic("OOM"),
1802 else => {},
1798 error.FileNotFound, error.AccessDenied, error.PermissionDenied => |e| {
1799 if (graph.verbose) log.info("searched: {t} {s}", .{ e, full_path });
1800 },
1801 else => |e| return panic("failed accessing {s}: {t}", .{ full_path, e }),
18031802 }
18041803
18051804 if (builtin.os.tag == .windows) {
......@@ -1809,14 +1808,16 @@ fn tryFindProgram(b: *Build, full_path: []const u8) ?[]const u8 {
18091808 while (it.next()) |ext| {
18101809 if (!supportedWindowsProgramExtension(ext)) continue;
18111810
1812 return b.build_root.handle.realPathFileAlloc(
1813 io,
1814 b.fmt("{s}{s}", .{ full_path, ext }),
1815 arena,
1816 ) catch |err| switch (err) {
1817 error.OutOfMemory => @panic("OOM"),
1818 else => continue,
1819 };
1811 const extended_path = try mem.concat(arena, &.{ full_path, ext });
1812
1813 if (Io.Dir.cwd().access(io, extended_path, .{ .execute = true })) |_| {
1814 return extended_path;
1815 } else |err| switch (err) {
1816 error.FileNotFound, error.AccessDenied, error.PermissionDenied => |e| {
1817 if (graph.verbose) log.info("searched: {t} {s}", .{ e, extended_path });
1818 },
1819 else => |e| return panic("failed accessing {s}: {t}", .{ extended_path, e }),
1820 }
18201821 }
18211822 }
18221823 }
......@@ -1824,114 +1825,158 @@ fn tryFindProgram(b: *Build, full_path: []const u8) ?[]const u8 {
18241825 return null;
18251826}
18261827
1827pub fn findProgram(b: *Build, names: []const []const u8, paths: []const []const u8) error{FileNotFound}![]const u8 {
1828 // TODO report error for ambiguous situations
1829 for (b.search_prefixes.items) |search_prefix| {
1830 for (names) |name| {
1831 if (fs.path.isAbsolute(name)) {
1832 return name;
1833 }
1834 return tryFindProgram(b, b.pathJoin(&.{ search_prefix, "bin", name })) orelse continue;
1835 }
1836 }
1837 if (b.graph.environ_map.get("PATH")) |PATH| {
1838 for (names) |name| {
1839 if (fs.path.isAbsolute(name)) {
1840 return name;
1841 }
1842 var it = mem.tokenizeScalar(u8, PATH, fs.path.delimiter);
1843 while (it.next()) |p| {
1844 return tryFindProgram(b, b.pathJoin(&.{ p, name })) orelse continue;
1845 }
1846 }
1847 }
1848 for (names) |name| {
1849 if (fs.path.isAbsolute(name)) {
1850 return name;
1851 }
1852 for (paths) |p| {
1853 return tryFindProgram(b, b.pathJoin(&.{ p, name })) orelse continue;
1854 }
1855 }
1856 return error.FileNotFound;
1857}
1858
1828/// Deprecated; use `runFallible`.
18591829pub fn runAllowFail(
18601830 b: *Build,
18611831 argv: []const []const u8,
1862 out_code: *u8,
1863 stderr_behavior: std.process.SpawnOptions.StdIo,
1864) RunError![]u8 {
1865 assert(argv.len != 0);
1832 exit_code: *u8,
1833 stderr_behavior: process.SpawnOptions.StdIo,
1834) anyerror![]u8 {
1835 if (!process.can_spawn) return error.ExecNotSupported;
1836 switch (runFallible(b, argv, .{
1837 .stderr_behavior = stderr_behavior,
1838 })) {
1839 .success => |stdout| return stdout,
1840 .spawn_failed => |err| return err,
1841 .bad_exit_code => |code| {
1842 exit_code.* = code;
1843 return error.ExitCodeFailure;
1844 },
1845 .crashed => {
1846 exit_code.* = 255;
1847 return error.ProcessTerminated;
1848 },
1849 }
1850}
1851
1852pub const RunOptions = struct {
1853 stderr_behavior: process.SpawnOptions.StdIo = .inherit,
1854 /// Fail the configuration if stdout is larger than this.
1855 stdout_limit: Io.Limit = .limited(1_000_000),
1856 /// Set to change the current working directory when spawning the child
1857 /// process.
1858 cwd: process.Child.Cwd = .inherit,
1859 /// Replaces the child environment when provided. The PATH value from here
1860 /// is not used to resolve `argv[0]`; that resolution always uses parent
1861 /// environment.
1862 environ_map: ?*const process.Environ.Map = null,
1863 expand_arg0: process.ArgExpansion = .no_expand,
1864};
1865
1866pub const RunResult = union(enum) {
1867 /// Thild process exited with code 0, writing this stdout.
1868 success: []u8,
1869 /// The child process could not be created.
1870 spawn_failed: process.SpawnError,
1871 /// The child process indicated failure.
1872 bad_exit_code: u8,
1873 /// The child process terminated abnormally.
1874 crashed,
1875};
18661876
1867 if (!process.can_spawn)
1868 return error.ExecNotSupported;
1877/// Executes the provided command immediately, allowing failure.
1878///
1879/// If the program exits successfully, stdout is returned. Otherwise, returns
1880/// an indication of failure.
1881///
1882/// See also:
1883/// * `run`.
1884pub fn runFallible(b: *Build, argv: []const []const u8, options: RunOptions) RunResult {
1885 assert(argv.len != 0);
18691886
18701887 const graph = b.graph;
18711888 const io = graph.io;
1889 const arena = graph.arena;
1890
1891 const print_opts: std.zig.AllocPrintCmdOptions = .{
1892 .cwd = switch (options.cwd) {
1893 .inherit => null,
1894 .path => |p| p,
1895 .dir => null, // Unknown without changing function signature of runFallible.
1896 },
1897 .child_env = options.environ_map,
1898 .parent_env = &graph.environ_map,
1899 };
18721900
1873 const max_output_size = 400 * 1024;
1874 try Step.handleVerbose2(b, .inherit, &graph.environ_map, argv);
1901 if (graph.verbose) {
1902 const text = std.zig.allocPrintCmd(arena, argv, print_opts) catch @panic("OOM");
1903 std.log.scoped(.verbose).info("{s}", .{text});
1904 }
18751905
1876 var child = try std.process.spawn(io, .{
1906 var child = process.spawn(io, .{
18771907 .argv = argv,
1878 .environ_map = &graph.environ_map,
18791908 .stdin = .ignore,
18801909 .stdout = .pipe,
1881 .stderr = stderr_behavior,
1882 });
1910 .stderr = options.stderr_behavior,
1911 .cwd = options.cwd,
1912 .environ_map = &graph.environ_map,
1913 .expand_arg0 = options.expand_arg0,
1914 }) catch |err| return .{ .spawn_failed = err };
18831915
18841916 var stdout_reader = child.stdout.?.readerStreaming(io, &.{});
1885 const stdout = stdout_reader.interface.allocRemaining(b.allocator, .limited(max_output_size)) catch {
1886 return error.ReadFailure;
1917 const stdout = stdout_reader.interface.allocRemaining(arena, options.stdout_limit) catch |err| switch (err) {
1918 error.ReadFailed => panic("failed to read from child: {t}", .{stdout_reader.err.?}),
1919 else => |e| panic("failed to read from child: {t}", .{e}),
18871920 };
1888 errdefer b.allocator.free(stdout);
1889
1890 const term = try child.wait(io);
1891 switch (term) {
1892 .exited => |code| {
1893 if (code != 0) {
1894 out_code.* = @as(u8, @truncate(code));
1895 return error.ExitCodeFailure;
1896 }
1897 return stdout;
1898 },
1899 .signal, .stopped => |sig| {
1900 out_code.* = @as(u8, @truncate(@intFromEnum(sig)));
1901 return error.ProcessTerminated;
1902 },
1903 .unknown => |code| {
1904 out_code.* = @as(u8, @truncate(code));
1905 return error.ProcessTerminated;
1921
1922 const term = child.wait(io) catch @panic("unexpected");
1923
1924 return switch (term) {
1925 .exited => |code| switch (code) {
1926 0 => .{ .success = stdout },
1927 else => .{ .bad_exit_code = code },
19061928 },
1907 }
1929 .signal, .stopped, .unknown => .crashed,
1930 };
19081931}
19091932
1910/// This is a helper function to be called from build.zig scripts, *not* from
1911/// inside step make() functions. If any errors occur, it fails the build with
1912/// a helpful message.
1933/// Executes the provided command immediately.
1934///
1935/// If the program exits successfully, stdout is returned. Otherwise, fails the
1936/// build with a helpful message.
1937///
1938/// See also:
1939/// * `runFallible`.
19131940pub fn run(b: *Build, argv: []const []const u8) []u8 {
1914 var code: u8 = undefined;
1915 return b.runAllowFail(argv, &code, .inherit) catch |err| process.fatal(
1916 "the following command failed with {t}:\n{s}",
1917 .{ err, Step.allocPrintCmd(b.allocator, .inherit, null, argv) catch @panic("OOM") },
1918 );
1941 const graph = b.graph;
1942 const arena = graph.arena;
1943 switch (b.runFallible(argv, .{
1944 .stderr_behavior = .inherit,
1945 })) {
1946 .success => |stdout| return stdout,
1947 .spawn_failed => |err| process.fatal("the following command failed with {t}:\n{s}", .{
1948 err, std.zig.allocPrintCmd(arena, argv, .{}) catch @panic("OOM"),
1949 }),
1950 .bad_exit_code => |code| process.fatal("the following command exited with code {d}:\n{s}", .{
1951 code, std.zig.allocPrintCmd(arena, argv, .{}) catch @panic("OOM"),
1952 }),
1953 .crashed => process.fatal("the following command crashed:\n{s}", .{
1954 std.zig.allocPrintCmd(arena, argv, .{}) catch @panic("OOM"),
1955 }),
1956 }
19191957}
19201958
1959/// Adds additional paths, equivalent to the `--search-prefix` arguments
1960/// provided by the user. Paths added with this function have lower precedence
1961/// than the ones specified by the user on the command line.
1962///
1963/// It is generally best practice to avoid calling this function, instead
1964/// relying on the user to provide these paths via the standard build system
1965/// interface. However, when integrating with other build systems, the user may
1966/// have already provided the information to the other build system, and thus
1967/// it is desirable to use that same information without requiring the user to
1968/// provide it again.
19211969pub fn addSearchPrefix(b: *Build, search_prefix: []const u8) void {
1922 b.search_prefixes.append(b.allocator, b.dupePath(search_prefix)) catch @panic("OOM");
1970 if (b.isRoot()) {
1971 const graph = b.graph;
1972 const wc = &graph.wip_configuration;
1973 const string = wc.addString(search_prefix) catch @panic("OOM");
1974 wc.search_prefixes.append(wc.gpa, string) catch @panic("OOM");
1975 }
19231976}
19241977
1925pub fn getInstallPath(b: *Build, dir: InstallDir, dest_rel_path: []const u8) []const u8 {
1926 assert(!fs.path.isAbsolute(dest_rel_path)); // Install paths must be relative to the prefix
1927 const base_dir = switch (dir) {
1928 .prefix => b.install_path,
1929 .bin => b.exe_dir,
1930 .lib => b.lib_dir,
1931 .header => b.h_dir,
1932 .custom => |p| b.pathJoin(&.{ b.install_path, p }),
1933 };
1934 return b.pathResolve(&.{ base_dir, dest_rel_path });
1978pub fn isRoot(b: *const Build) bool {
1979 return b.pkg_hash.len == 0;
19351980}
19361981
19371982pub const Dependency = struct {
......@@ -1987,14 +2032,15 @@ fn findPkgHashOrFatal(b: *Build, name: []const u8) []const u8 {
19872032 for (b.available_deps) |dep| {
19882033 if (mem.eql(u8, dep[0], name)) return dep[1];
19892034 }
1990
1991 const full_path = b.pathFromRoot("build.zig.zon");
1992 std.debug.panic("no dependency named '{s}' in '{s}'. All packages used in build.zig must be declared in this file", .{ name, full_path });
2035 std.log.info("all dependencies used by build.zig must be declared in corresponding build.zig.zon", .{});
2036 if (b.pkg_hash.len == 0) panic("no dependency named {s}", .{name});
2037 panic("no dependency named {s} in {s} ({s})", .{ name, b.dep_prefix, b.pkg_hash });
19932038}
19942039
19952040inline fn findImportPkgHashOrFatal(b: *Build, comptime asking_build_zig: type, comptime dep_name: []const u8) []const u8 {
19962041 const build_runner = @import("root");
19972042 const deps = build_runner.dependencies;
2043 const arena = b.graph.arena;
19982044
19992045 const b_pkg_hash, const b_pkg_deps = comptime for (@typeInfo(deps.packages).@"struct".decls) |decl| {
20002046 const pkg_hash = decl.name;
......@@ -2002,14 +2048,19 @@ inline fn findImportPkgHashOrFatal(b: *Build, comptime asking_build_zig: type, c
20022048 if (@hasDecl(pkg, "build_zig") and pkg.build_zig == asking_build_zig) break .{ pkg_hash, pkg.deps };
20032049 } else .{ "", deps.root_deps };
20042050 if (!std.mem.eql(u8, b_pkg_hash, b.pkg_hash)) {
2005 std.debug.panic("'{}' is not the struct that corresponds to '{s}'", .{ asking_build_zig, b.pathFromRoot("build.zig") });
2051 const build_zig_path = b.root.join(arena, "build.zig") catch @panic("OOM");
2052 panic("{} is not the struct that corresponds to {f}", .{
2053 asking_build_zig, build_zig_path,
2054 });
20062055 }
20072056 comptime for (b_pkg_deps) |dep| {
20082057 if (std.mem.eql(u8, dep[0], dep_name)) return dep[1];
20092058 };
20102059
2011 const full_path = b.pathFromRoot("build.zig.zon");
2012 std.debug.panic("no dependency named '{s}' in '{s}'. All packages used in build.zig must be declared in this file", .{ dep_name, full_path });
2060 const full_path = b.root.join(arena, "build.zig.zon") catch @panic("OOM");
2061 panic("no dependency named {s} in {f}. All packages used in build.zig must be declared in this file", .{
2062 dep_name, full_path,
2063 });
20132064}
20142065
20152066fn markNeededLazyDep(b: *Build, pkg_hash: []const u8) void {
......@@ -2026,6 +2077,7 @@ fn markNeededLazyDep(b: *Build, pkg_hash: []const u8) void {
20262077/// In other words, if this function returns `null` it means that the only
20272078/// purpose of completing the configure phase is to find out all the other lazy
20282079/// dependencies that are also required.
2080///
20292081/// It is allowed to use this function for non-lazy dependencies, in which case
20302082/// it will never return `null`. This allows toggling laziness via
20312083/// build.zig.zon without changing build.zig logic.
......@@ -2058,7 +2110,7 @@ pub fn dependency(b: *Build, name: []const u8, args: anytype) *Dependency {
20582110 if (mem.eql(u8, decl.name, pkg_hash)) {
20592111 const pkg = @field(deps.packages, decl.name);
20602112 if (@hasDecl(pkg, "available")) {
2061 std.debug.panic("dependency '{s}{s}' is marked as lazy in build.zig.zon which means it must use the lazyDependency function instead", .{ b.dep_prefix, name });
2113 panic("dependency '{s}{s}' is marked as lazy in build.zig.zon which means it must use the lazyDependency function instead", .{ b.dep_prefix, name });
20622114 }
20632115 return dependencyInner(b, name, pkg.build_root, if (@hasDecl(pkg, "build_zig")) pkg.build_zig else null, pkg_hash, pkg.deps, args);
20642116 }
......@@ -2111,6 +2163,8 @@ pub fn dependencyFromBuildZig(
21112163) *Dependency {
21122164 const build_runner = @import("root");
21132165 const deps = build_runner.dependencies;
2166 const graph = b.graph;
2167 const arena = graph.arena;
21142168
21152169 find_dep: {
21162170 const pkg, const pkg_hash = inline for (@typeInfo(deps.packages).@"struct".decls) |decl| {
......@@ -2124,8 +2178,8 @@ pub fn dependencyFromBuildZig(
21242178 return dependencyInner(b, dep_name, pkg.build_root, pkg.build_zig, pkg_hash, pkg.deps, args);
21252179 }
21262180
2127 const full_path = b.pathFromRoot("build.zig.zon");
2128 std.debug.panic("'{}' is not a build.zig struct of a dependency in '{s}'", .{ build_zig, full_path });
2181 const full_path = b.root.join(arena, "build.zig.zon") catch @panic("OOM");
2182 panic("{} is not a build.zig struct of a dependency in {f}", .{ build_zig, full_path });
21292183}
21302184
21312185fn userValuesAreSame(lhs: UserValue, rhs: UserValue) bool {
......@@ -2188,10 +2242,10 @@ fn userLazyPathsAreTheSame(lhs_lp: LazyPath, rhs_lp: LazyPath) bool {
21882242 if (lhs_sp.owner != rhs_sp.owner) return false;
21892243 if (std.mem.eql(u8, lhs_sp.sub_path, rhs_sp.sub_path)) return false;
21902244 },
2191 .generated => |lhs_gen| {
2192 const rhs_gen = rhs_lp.generated;
2245 .generated => |*lhs_gen| {
2246 const rhs_gen = &rhs_lp.generated;
21932247
2194 if (lhs_gen.file != rhs_gen.file) return false;
2248 if (lhs_gen.index != rhs_gen.index) return false;
21952249 if (lhs_gen.up != rhs_gen.up) return false;
21962250 if (std.mem.eql(u8, lhs_gen.sub_path, rhs_gen.sub_path)) return false;
21972251 },
......@@ -2200,6 +2254,7 @@ fn userLazyPathsAreTheSame(lhs_lp: LazyPath, rhs_lp: LazyPath) bool {
22002254
22012255 if (!std.mem.eql(u8, lhs_rel_path, rhs_rel_path)) return false;
22022256 },
2257 .relative => |lhs| return lhs.eql(rhs_lp.relative),
22032258 .dependency => |lhs_dep| {
22042259 const rhs_dep = rhs_lp.dependency;
22052260
......@@ -2219,25 +2274,25 @@ fn dependencyInner(
22192274 pkg_deps: AvailableDeps,
22202275 args: anytype,
22212276) *Dependency {
2222 const io = b.graph.io;
2223 const user_input_options = userInputOptionsFromArgs(b.allocator, args);
2224 if (b.graph.dependency_cache.getContext(.{
2277 const graph = b.graph;
2278 const io = graph.io;
2279 const arena = graph.arena;
2280 const user_input_options = userInputOptionsFromArgs(arena, args);
2281 if (graph.dependency_cache.getContext(.{
22252282 .build_root_string = build_root_string,
22262283 .user_input_options = user_input_options,
2227 }, .{ .allocator = b.graph.arena })) |dep|
2228 return dep;
2229
2230 const build_root: std.Build.Cache.Directory = .{
2231 .path = build_root_string,
2232 .handle = Io.Dir.cwd().openDir(io, build_root_string, .{}) catch |err| {
2233 std.debug.print("unable to open '{s}': {s}\n", .{
2234 build_root_string, @errorName(err),
2235 });
2236 process.exit(1);
2284 }, .{ .allocator = arena })) |dep| return dep;
2285
2286 const dep_root: Cache.Path = .{
2287 .root_dir = .{
2288 .path = build_root_string,
2289 .handle = Io.Dir.cwd().openDir(io, build_root_string, .{}) catch |err|
2290 process.fatal("unable to open {s}: {t}", .{ build_root_string, err }),
22372291 },
22382292 };
22392293
2240 const sub_builder = b.createChild(name, build_root, pkg_hash, pkg_deps, user_input_options) catch @panic("unhandled error");
2294 const sub_builder = b.createChild(name, dep_root, pkg_hash, pkg_deps, user_input_options) catch
2295 @panic("unhandled error");
22412296 if (build_zig) |bz| {
22422297 sub_builder.runBuild(bz) catch @panic("unhandled error");
22432298
......@@ -2246,13 +2301,13 @@ fn dependencyInner(
22462301 }
22472302 }
22482303
2249 const dep = b.allocator.create(Dependency) catch @panic("OOM");
2304 const dep = graph.create(Dependency);
22502305 dep.* = .{ .builder = sub_builder };
22512306
2252 b.graph.dependency_cache.putContext(b.graph.arena, .{
2307 graph.dependency_cache.putContext(arena, .{
22532308 .build_root_string = build_root_string,
22542309 .user_input_options = user_input_options,
2255 }, dep, .{ .allocator = b.graph.arena }) catch @panic("OOM");
2310 }, dep, .{ .allocator = arena }) catch @panic("OOM");
22562311 return dep;
22572312}
22582313
......@@ -2264,41 +2319,6 @@ pub fn runBuild(b: *Build, build_zig: anytype) anyerror!void {
22642319 }
22652320}
22662321
2267/// A file that is generated by a build step.
2268/// This struct is an interface that is meant to be used with `@fieldParentPtr` to implement the actual path logic.
2269pub const GeneratedFile = struct {
2270 /// The step that generates the file.
2271 step: *Step,
2272 /// The path to the generated file. Must be either absolute or relative to the build runner cwd.
2273 /// This value must be set in the `fn make()` of the `step` and must not be `null` afterwards.
2274 path: ?[]const u8 = null,
2275
2276 /// Deprecated, see `getPath3`.
2277 pub fn getPath(gen: GeneratedFile) []const u8 {
2278 return gen.step.owner.pathFromCwd(gen.path orelse std.debug.panic(
2279 "getPath() was called on a GeneratedFile that wasn't built yet. Is there a missing Step dependency on step '{s}'?",
2280 .{gen.step.name},
2281 ));
2282 }
2283
2284 /// Deprecated, see `getPath3`.
2285 pub fn getPath2(gen: GeneratedFile, src_builder: *Build, asking_step: ?*Step) []const u8 {
2286 return getPath3(gen, src_builder, asking_step) catch |err| switch (err) {
2287 error.Canceled => std.process.exit(1),
2288 };
2289 }
2290
2291 pub fn getPath3(gen: GeneratedFile, src_builder: *Build, asking_step: ?*Step) Io.Cancelable![]const u8 {
2292 return gen.path orelse {
2293 const graph = gen.step.owner.graph;
2294 const io = graph.io;
2295 const stderr = try io.lockStderr(&.{}, graph.stderr_mode);
2296 dumpBadGetPathHelp(gen.step, stderr.terminal(), src_builder, asking_step) catch {};
2297 @panic("misconfigured build script");
2298 };
2299 }
2300};
2301
23022322// dirnameAllowEmpty is a variant of fs.path.dirname
23032323// that allows "" to refer to the root for relative paths.
23042324//
......@@ -2338,7 +2358,7 @@ pub const LazyPath = union(enum) {
23382358 },
23392359
23402360 generated: struct {
2341 file: *const GeneratedFile,
2361 index: Configuration.GeneratedFileIndex,
23422362
23432363 /// The number of parent directories to go up.
23442364 /// 0 means the generated file itself.
......@@ -2350,14 +2370,7 @@ pub const LazyPath = union(enum) {
23502370 sub_path: []const u8 = "",
23512371 },
23522372
2353 /// An absolute path or a path relative to the current working directory of
2354 /// the build runner process.
2355 ///
2356 /// This is uncommon but used for system environment paths such as `--zig-lib-dir` which
2357 /// ignore the file system path of build.zig and instead are relative to the directory from
2358 /// which `zig build` was invoked.
2359 ///
2360 /// Use of this tag indicates a dependency on the host system.
2373 /// Deprecated; call `Graph.cwdRelativePath` instead.
23612374 cwd_relative: []const u8,
23622375
23632376 dependency: struct {
......@@ -2365,13 +2378,30 @@ pub const LazyPath = union(enum) {
23652378 sub_path: []const u8,
23662379 },
23672380
2381 relative: struct {
2382 base: Configuration.Path.Base,
2383 sub_path: []const u8 = "",
2384
2385 pub fn eql(a: @This(), b: @This()) bool {
2386 return a.base == b.base and mem.eql(u8, a.sub_path, b.sub_path);
2387 }
2388 },
2389
2390 /// Path to the Zig executable being used to execute "zig build".
2391 pub const zig_exe: LazyPath = .{ .relative = .{ .base = .zig_exe } };
2392 /// Path to the "lib/" directory from the Zig installation being used to
2393 /// execute "zig build".
2394 pub const zig_lib: LazyPath = .{ .relative = .{ .base = .zig_lib } };
2395 /// Path to the project's local cache directory (usually called ".zig-cache").
2396 pub const cache_root: LazyPath = .{ .relative = .{ .base = .local_cache } };
2397
23682398 /// Returns a lazy path referring to the directory containing this path.
23692399 ///
2370 /// The dirname is not allowed to escape the logical root for underlying path.
2371 /// For example, if the path is relative to the build root,
2372 /// the dirname is not allowed to traverse outside of the build root.
2373 /// Similarly, if the path is a generated file inside zig-cache,
2374 /// the dirname is not allowed to traverse outside of zig-cache.
2400 /// The dirname is not allowed to escape the logical root for underlying
2401 /// path. For example, if the path is relative to the build root, the
2402 /// dirname is not allowed to traverse outside of the build root.
2403 /// Similarly, if the path is a generated file inside zig-cache, the
2404 /// dirname is not allowed to traverse outside of zig-cache.
23752405 pub fn dirname(lazy_path: LazyPath) LazyPath {
23762406 return switch (lazy_path) {
23772407 .src_path => |sp| .{ .src_path = .{
......@@ -2382,11 +2412,11 @@ pub const LazyPath = union(enum) {
23822412 },
23832413 } },
23842414 .generated => |generated| .{ .generated = if (dirnameAllowEmpty(generated.sub_path)) |sub_dirname| .{
2385 .file = generated.file,
2415 .index = generated.index,
23862416 .up = generated.up,
23872417 .sub_path = sub_dirname,
23882418 } else .{
2389 .file = generated.file,
2419 .index = generated.index,
23902420 .up = generated.up + 1,
23912421 .sub_path = "",
23922422 } },
......@@ -2413,6 +2443,13 @@ pub const LazyPath = union(enum) {
24132443 }
24142444 },
24152445 },
2446 .relative => |r| .{ .relative = .{
2447 .base = r.base,
2448 .sub_path = dirnameAllowEmpty(r.sub_path) orelse {
2449 dumpBadDirnameHelp(null, null, "dirname() attempted to traverse outside the base path\n", .{}) catch {};
2450 @panic("misconfigured build script");
2451 },
2452 } },
24162453 .dependency => |dep| .{ .dependency = .{
24172454 .dependency = dep.dependency,
24182455 .sub_path = dirnameAllowEmpty(dep.sub_path) orelse {
......@@ -2427,7 +2464,9 @@ pub const LazyPath = union(enum) {
24272464 }
24282465
24292466 pub fn path(lazy_path: LazyPath, b: *Build, sub_path: []const u8) LazyPath {
2430 return lazy_path.join(b.allocator, sub_path) catch @panic("OOM");
2467 const graph = b.graph;
2468 const arena = graph.arena;
2469 return lazy_path.join(arena, sub_path) catch @panic("OOM");
24312470 }
24322471
24332472 pub fn join(lazy_path: LazyPath, arena: Allocator, sub_path: []const u8) Allocator.Error!LazyPath {
......@@ -2437,13 +2476,17 @@ pub const LazyPath = union(enum) {
24372476 .sub_path = try fs.path.resolve(arena, &.{ src.sub_path, sub_path }),
24382477 } },
24392478 .generated => |gen| .{ .generated = .{
2440 .file = gen.file,
2479 .index = gen.index,
24412480 .up = gen.up,
24422481 .sub_path = try fs.path.resolve(arena, &.{ gen.sub_path, sub_path }),
24432482 } },
24442483 .cwd_relative => |cwd_relative| .{
24452484 .cwd_relative = try fs.path.resolve(arena, &.{ cwd_relative, sub_path }),
24462485 },
2486 .relative => |r| .{ .relative = .{
2487 .base = r.base,
2488 .sub_path = try fs.path.resolve(arena, &.{ r.sub_path, sub_path }),
2489 } },
24472490 .dependency => |dep| .{ .dependency = .{
24482491 .dependency = dep.dependency,
24492492 .sub_path = try fs.path.resolve(arena, &.{ dep.sub_path, sub_path }),
......@@ -2451,148 +2494,59 @@ pub const LazyPath = union(enum) {
24512494 };
24522495 }
24532496
2454 /// Returns a string that can be shown to represent the file source.
2455 /// Either returns the path, `"generated"`, or `"dependency"`.
2497 /// Deprecated, use `format` instead.
24562498 pub fn getDisplayName(lazy_path: LazyPath) []const u8 {
24572499 return switch (lazy_path) {
24582500 .src_path => |sp| sp.sub_path,
24592501 .cwd_relative => |p| p,
24602502 .generated => "generated",
24612503 .dependency => "dependency",
2504 .relative => |r| @tagName(r.base),
24622505 };
24632506 }
24642507
2465 /// Adds dependencies this file source implies to the given step.
2466 pub fn addStepDependencies(lazy_path: LazyPath, other_step: *Step) void {
2467 switch (lazy_path) {
2468 .src_path, .cwd_relative, .dependency => {},
2469 .generated => |gen| other_step.dependOn(gen.file.step),
2508 pub fn format(lp: LazyPath, w: *Io.Writer) Io.Writer.Error!void {
2509 switch (lp) {
2510 .src_path => |sp| try w.writeAll(sp.sub_path),
2511 .cwd_relative => |p| try w.writeAll(p),
2512 .generated => try w.writeAll("generated"),
2513 .dependency => try w.writeAll("dependency"),
2514 .relative => |r| try w.print("{t} {s}", .{ r.base, r.sub_path }),
24702515 }
24712516 }
24722517
2473 /// Deprecated, see `getPath4`.
2474 pub fn getPath(lazy_path: LazyPath, src_builder: *Build) []const u8 {
2475 return getPath2(lazy_path, src_builder, null);
2476 }
2477
2478 /// Deprecated, see `getPath4`.
2479 pub fn getPath2(lazy_path: LazyPath, src_builder: *Build, asking_step: ?*Step) []const u8 {
2480 const p = getPath3(lazy_path, src_builder, asking_step);
2481 return src_builder.pathResolve(&.{ p.root_dir.path orelse ".", p.sub_path });
2482 }
2483
2484 /// Deprecated, see `getPath4`.
2485 pub fn getPath3(lazy_path: LazyPath, src_builder: *Build, asking_step: ?*Step) Cache.Path {
2486 return getPath4(lazy_path, src_builder, asking_step) catch |err| switch (err) {
2487 error.Canceled => std.process.exit(1),
2488 };
2489 }
2490
2491 /// Intended to be used during the make phase only.
2492 ///
2493 /// `asking_step` is only used for debugging purposes; it's the step being
2494 /// run that is asking for the path.
2495 pub fn getPath4(lazy_path: LazyPath, src_builder: *Build, asking_step: ?*Step) Io.Cancelable!Cache.Path {
2518 /// Adds dependencies this file source implies to the given step.
2519 pub fn addStepDependencies(lazy_path: LazyPath, other_step: *Step) void {
24962520 switch (lazy_path) {
2497 .src_path => |sp| return .{
2498 .root_dir = sp.owner.build_root,
2499 .sub_path = sp.sub_path,
2500 },
2501 .cwd_relative => |sub_path| return .{
2502 .root_dir = Cache.Directory.cwd(),
2503 .sub_path = sub_path,
2504 },
2521 .src_path, .cwd_relative, .relative, .dependency => {},
25052522 .generated => |gen| {
2506 // TODO make gen.file.path not be absolute and use that as the
2507 // basis for not traversing up too many directories.
2508
2509 const graph = src_builder.graph;
2510
2511 var file_path: Cache.Path = .{
2512 .root_dir = Cache.Directory.cwd(),
2513 .sub_path = gen.file.path orelse {
2514 const io = graph.io;
2515 const stderr = try io.lockStderr(&.{}, graph.stderr_mode);
2516 dumpBadGetPathHelp(gen.file.step, stderr.terminal(), src_builder, asking_step) catch {};
2517 io.unlockStderr();
2518 @panic("misconfigured build script");
2519 },
2520 };
2521
2522 if (gen.up > 0) {
2523 const cache_root_path = src_builder.cache_root.path orelse
2524 (src_builder.cache_root.join(src_builder.allocator, &.{"."}) catch @panic("OOM"));
2525
2526 for (0..gen.up) |_| {
2527 if (mem.eql(u8, file_path.sub_path, cache_root_path)) {
2528 // If we hit the cache root and there's still more to go,
2529 // the script attempted to go too far.
2530 dumpBadDirnameHelp(gen.file.step, asking_step,
2531 \\dirname() attempted to traverse outside the cache root.
2532 \\This is not allowed.
2533 \\
2534 , .{}) catch {};
2535 @panic("misconfigured build script");
2536 }
2537
2538 // path is absolute.
2539 // dirname will return null only if we're at root.
2540 // Typically, we'll stop well before that at the cache root.
2541 file_path.sub_path = fs.path.dirname(file_path.sub_path) orelse {
2542 dumpBadDirnameHelp(gen.file.step, asking_step,
2543 \\dirname() reached root.
2544 \\No more directories left to go up.
2545 \\
2546 , .{}) catch {};
2547 @panic("misconfigured build script");
2548 };
2549 }
2550 }
2551
2552 return file_path.join(src_builder.allocator, gen.sub_path) catch @panic("OOM");
2553 },
2554 .dependency => |dep| return .{
2555 .root_dir = dep.dependency.builder.build_root,
2556 .sub_path = dep.sub_path,
2523 const graph = other_step.owner.graph;
2524 const generated_owner_step = graph.generated_files.items[@intFromEnum(gen.index)];
2525 other_step.dependOn(generated_owner_step);
25572526 },
25582527 }
25592528 }
25602529
2561 pub fn basename(lazy_path: LazyPath, src_builder: *Build, asking_step: ?*Step) []const u8 {
2562 return fs.path.basename(switch (lazy_path) {
2563 .src_path => |sp| sp.sub_path,
2564 .cwd_relative => |sub_path| sub_path,
2565 .generated => |gen| if (gen.sub_path.len > 0)
2566 gen.sub_path
2567 else
2568 gen.file.getPath2(src_builder, asking_step),
2569 .dependency => |dep| dep.sub_path,
2570 });
2571 }
2572
25732530 /// Copies the internal strings.
25742531 ///
2575 /// The `b` parameter is only used for its allocator. All *Build instances
2576 /// share the same allocator.
2577 pub fn dupe(lazy_path: LazyPath, b: *Build) LazyPath {
2578 return lazy_path.dupeInner(b.allocator);
2532 /// The `graph` parameter is only used for the global arena allocator.
2533 pub fn dupe(lazy_path: LazyPath, graph: *const Graph) LazyPath {
2534 return dupeInner(lazy_path, graph.arena);
25792535 }
25802536
2581 fn dupeInner(lazy_path: LazyPath, allocator: std.mem.Allocator) LazyPath {
2537 fn dupeInner(lazy_path: LazyPath, arena: Allocator) LazyPath {
25822538 return switch (lazy_path) {
2583 .src_path => |sp| .{ .src_path = .{
2584 .owner = sp.owner,
2585 .sub_path = sp.owner.dupePath(sp.sub_path),
2586 } },
2587 .cwd_relative => |p| .{ .cwd_relative = dupePathInner(allocator, p) },
2539 .src_path => |sp| .{ .src_path = .{ .owner = sp.owner, .sub_path = sp.owner.dupePath(sp.sub_path) } },
2540 .cwd_relative => |p| .{ .cwd_relative = Graph.dupePathInner(arena, p) },
2541 .relative => |r| .{ .relative = r },
25882542 .generated => |gen| .{ .generated = .{
2589 .file = gen.file,
2543 .index = gen.index,
25902544 .up = gen.up,
2591 .sub_path = dupePathInner(allocator, gen.sub_path),
2545 .sub_path = Graph.dupePathInner(arena, gen.sub_path),
25922546 } },
25932547 .dependency => |dep| .{ .dependency = .{
25942548 .dependency = dep.dependency,
2595 .sub_path = dupePathInner(allocator, dep.sub_path),
2549 .sub_path = Graph.dupePathInner(arena, dep.sub_path),
25962550 } },
25972551 };
25982552 }
......@@ -2631,36 +2585,6 @@ fn dumpBadDirnameHelp(
26312585 stderr.setColor(.reset) catch {};
26322586}
26332587
2634/// In this function the stderr mutex has already been locked.
2635pub fn dumpBadGetPathHelp(s: *Step, t: Io.Terminal, src_builder: *Build, asking_step: ?*Step) anyerror!void {
2636 const w = t.writer;
2637 try w.print(
2638 \\getPath() was called on a GeneratedFile that wasn't built yet.
2639 \\ source package path: {s}
2640 \\ Is there a missing Step dependency on step '{s}'?
2641 \\
2642 , .{
2643 src_builder.build_root.path orelse ".",
2644 s.name,
2645 });
2646
2647 t.setColor(.red) catch {};
2648 try w.writeAll(" The step was created by this stack trace:\n");
2649 t.setColor(.reset) catch {};
2650
2651 s.dump(t);
2652 if (asking_step) |as| {
2653 t.setColor(.red) catch {};
2654 try w.print(" The step '{s}' that is missing a dependency on the above step was created by this stack trace:\n", .{as.name});
2655 t.setColor(.reset) catch {};
2656
2657 as.dump(t);
2658 }
2659 t.setColor(.red) catch {};
2660 try w.writeAll(" Proceeding to panic.\n");
2661 t.setColor(.reset) catch {};
2662}
2663
26642588pub const InstallDir = union(enum) {
26652589 prefix: void,
26662590 lib: void,
......@@ -2670,18 +2594,18 @@ pub const InstallDir = union(enum) {
26702594 custom: []const u8,
26712595
26722596 /// Duplicates the install directory including the path if set to custom.
2673 pub fn dupe(dir: InstallDir, builder: *Build) InstallDir {
2597 pub fn dupe(dir: InstallDir, graph: *const Graph) InstallDir {
26742598 if (dir == .custom) {
2675 return .{ .custom = builder.dupe(dir.custom) };
2599 return .{ .custom = graph.dupeString(dir.custom) };
26762600 } else {
26772601 return dir;
26782602 }
26792603 }
26802604};
26812605
2682/// Creates a path leading to a directory inside "tmp" subdirectory of
2683/// `cache_root` which is created on demand and cleaned up by the build runner
2684/// upon success.
2606/// Creates a path leading to a directory inside "tmp" subdirectory of local
2607/// cache which is created on demand and cleaned up by the build runner upon
2608/// success.
26852609pub fn tmpPath(b: *Build) LazyPath {
26862610 const wf = b.addTempFiles();
26872611 return wf.getDirectory();
......@@ -2725,7 +2649,9 @@ pub fn systemIntegrationOption(
27252649 name: []const u8,
27262650 config: SystemIntegrationOptionConfig,
27272651) bool {
2728 const gop = b.graph.system_library_options.getOrPut(b.allocator, name) catch @panic("OOM");
2652 const graph = b.graph;
2653 const arena = graph.arena;
2654 const gop = graph.system_integration_options.getOrPut(arena, name) catch @panic("OOM");
27292655 if (gop.found_existing) switch (gop.value_ptr.*) {
27302656 .user_disabled => {
27312657 gop.value_ptr.* = .declared_disabled;
......@@ -2738,8 +2664,8 @@ pub fn systemIntegrationOption(
27382664 .declared_disabled => return false,
27392665 .declared_enabled => return true,
27402666 } else {
2741 gop.key_ptr.* = b.dupe(name);
2742 if (config.default orelse b.graph.system_package_mode) {
2667 gop.key_ptr.* = graph.dupeString(name);
2668 if (config.default orelse graph.system_package_mode) {
27432669 gop.value_ptr.* = .declared_enabled;
27442670 return true;
27452671 } else {
lib/std/Build/Cache.zig+10-1
......@@ -189,12 +189,15 @@ pub const File = struct {
189189pub const HashHelper = struct {
190190 hasher: Hasher = hasher_init,
191191
192 /// Record a slice of bytes as a dependency of the process being cached.
193192 pub fn addBytes(hh: *HashHelper, bytes: []const u8) void {
194193 hh.hasher.update(mem.asBytes(&bytes.len));
195194 hh.hasher.update(bytes);
196195 }
197196
197 pub fn addBytesZ(hh: *HashHelper, bytes: [:0]const u8) void {
198 hh.hasher.update(mem.absorbSentinel(bytes));
199 }
200
198201 pub fn addOptionalBytes(hh: *HashHelper, optional_bytes: ?[]const u8) void {
199202 hh.add(optional_bytes != null);
200203 hh.addBytes(optional_bytes orelse return);
......@@ -1024,6 +1027,12 @@ pub const Manifest = struct {
10241027 try self.populateFileHash(gop.key_ptr);
10251028 }
10261029
1030 pub fn addPathPost(man: *Manifest, path: Path) !void {
1031 _ = man;
1032 _ = path;
1033 @panic("TODO");
1034 }
1035
10271036 /// Like `addFilePost` but when the file contents have already been loaded from disk.
10281037 pub fn addFilePostContents(
10291038 self: *Manifest,
lib/std/Build/Cache/Path.zig+8-1
......@@ -24,7 +24,7 @@ pub fn cwd() Path {
2424}
2525
2626pub fn initCwd(sub_path: []const u8) Path {
27 return .{ .root_dir = Cache.Directory.cwd(), .sub_path = sub_path };
27 return .{ .root_dir = .cwd(), .sub_path = sub_path };
2828}
2929
3030pub fn join(p: Path, arena: Allocator, sub_path: []const u8) Allocator.Error!Path {
......@@ -213,6 +213,13 @@ pub fn stem(p: Path) []const u8 {
213213 return fs.path.stem(p.sub_path);
214214}
215215
216pub fn dirname(p: Path) ?Path {
217 return .{
218 .root_dir = p.root_dir,
219 .sub_path = fs.path.dirname(p.subPathOpt() orelse return null) orelse "",
220 };
221}
222
216223pub fn basename(p: Path) []const u8 {
217224 return fs.path.basename(p.sub_path);
218225}
lib/std/Build/Configuration.zig created+3436
......@@ -0,0 +1,3436 @@
1const Configuration = @This();
2
3const std = @import("../std.zig");
4const Io = std.Io;
5const Allocator = std.mem.Allocator;
6const assert = std.debug.assert;
7const max_u32 = std.math.maxInt(u32);
8
9string_bytes: []u8,
10steps: []Step,
11path_deps_base: []Path.Base,
12path_deps_sub: []String,
13unlazy_deps: []String,
14system_integrations: []SystemIntegration,
15available_options: []AvailableOption,
16search_prefixes: []String,
17extra: []u32,
18default_step: Step.Index,
19generated_files_len: u32,
20poisoned: bool,
21
22/// The field order here matches `Configuration` which documents the order in
23/// the serialized format.
24pub const Header = extern struct {
25 string_bytes_len: u32,
26 steps_len: u32,
27 path_deps_len: u32,
28 unlazy_deps_len: u32,
29 system_integrations_len: u32,
30 available_options_len: u32,
31 search_prefixes_len: u32,
32 extra_len: u32,
33
34 default_step: Step.Index,
35 /// There is not actually any data stored for this - it just provides a way
36 /// for maker process to preallocate an array for these.
37 generated_files_len: u32,
38 flags: Flags,
39
40 pub const Flags = packed struct(u32) {
41 poisoned: bool,
42 _: u31 = 0,
43 };
44};
45
46pub const Wip = struct {
47 gpa: Allocator,
48 string_table: StringTable = .empty,
49 /// De-duplicates an array inside `extra`.
50 dedupe_table: DedupeTable = .empty,
51 targets_table: TargetsTable = .empty,
52
53 string_bytes: std.ArrayList(u8) = .empty,
54 unlazy_deps: std.ArrayList(String) = .empty,
55 system_integrations: std.ArrayList(SystemIntegration) = .empty,
56 available_options: std.ArrayList(AvailableOption) = .empty,
57 steps: std.ArrayList(Step) = .empty,
58 path_deps: std.MultiArrayList(Path) = .empty,
59 search_prefixes: std.ArrayList(String) = .empty,
60 extra: std.ArrayList(u32) = .empty,
61 next_generated_file_index: u32 = 0,
62 cache_poison: bool = false,
63
64 const DedupeTable = std.HashMapUnmanaged(ExtraSlice, void, ExtraSlice.Context, std.hash_map.default_max_load_percentage);
65 const TargetsTable = std.HashMapUnmanaged(TargetQuery.Index, void, TargetsTableContext, std.hash_map.default_max_load_percentage);
66
67 const ExtraSlice = struct {
68 index: u32,
69 len: u32,
70
71 const Context = struct {
72 extra: []const u32,
73
74 pub fn eql(ctx: @This(), a: ExtraSlice, b: ExtraSlice) bool {
75 const slice_a = ctx.extra[a.index..][0..a.len];
76 const slice_b = ctx.extra[b.index..][0..b.len];
77 return std.mem.eql(u32, slice_a, slice_b);
78 }
79
80 pub fn hash(ctx: @This(), key: ExtraSlice) u64 {
81 const slice = ctx.extra[key.index..][0..key.len];
82 return std.hash_map.hashString(@ptrCast(slice));
83 }
84 };
85 };
86
87 const TargetsTableContext = struct {
88 extra: []const u32,
89
90 pub fn eql(ctx: @This(), a: TargetQuery.Index, b: TargetQuery.Index) bool {
91 const slice_a = a.extraSlice(ctx.extra);
92 const slice_b = b.extraSlice(ctx.extra);
93 return std.mem.eql(u32, slice_a, slice_b);
94 }
95
96 pub fn hash(ctx: @This(), key: TargetQuery.Index) u64 {
97 const slice = key.extraSlice(ctx.extra);
98 return std.hash_map.hashString(@ptrCast(slice));
99 }
100 };
101
102 const StringTable = std.HashMapUnmanaged(String, void, StringTableContext, std.hash_map.default_max_load_percentage);
103 const StringTableContext = struct {
104 bytes: []const u8,
105
106 pub fn eql(_: @This(), a: String, b: String) bool {
107 return a == b;
108 }
109
110 pub fn hash(ctx: @This(), key: String) u64 {
111 return std.hash_map.hashString(std.mem.sliceTo(ctx.bytes[@intFromEnum(key)..], 0));
112 }
113 };
114
115 const StringTableIndexAdapter = struct {
116 bytes: []const u8,
117
118 pub fn eql(ctx: @This(), a: []const u8, b: String) bool {
119 return std.mem.eql(u8, a, std.mem.sliceTo(ctx.bytes[@intFromEnum(b)..], 0));
120 }
121
122 pub fn hash(_: @This(), adapted_key: []const u8) u64 {
123 assert(std.mem.indexOfScalar(u8, adapted_key, 0) == null);
124 return std.hash_map.hashString(adapted_key);
125 }
126 };
127
128 pub fn init(gpa: Allocator) Wip {
129 return .{ .gpa = gpa };
130 }
131
132 pub fn deinit(wip: *Wip) void {
133 const gpa = wip.gpa;
134 wip.string_bytes.deinit(gpa);
135 wip.unlazy_deps.deinit(gpa);
136 wip.system_integrations.deinit(gpa);
137 wip.available_options.deinit(gpa);
138 wip.steps.deinit(gpa);
139 wip.path_deps.deinit(gpa);
140 wip.search_prefixes.deinit(gpa);
141 wip.extra.deinit(gpa);
142 wip.* = undefined;
143 }
144
145 pub const Static = struct {
146 default_step: Step.Index,
147 generated_files_len: u32,
148 poisoned: bool,
149 };
150
151 pub fn write(wip: *Wip, w: *Io.Writer, static: Static) Io.Writer.Error!void {
152 const header: Header = .{
153 .string_bytes_len = @intCast(wip.string_bytes.items.len),
154 .steps_len = @intCast(wip.steps.items.len),
155 .path_deps_len = @intCast(wip.path_deps.len),
156 .unlazy_deps_len = @intCast(wip.unlazy_deps.items.len),
157 .system_integrations_len = @intCast(wip.system_integrations.items.len),
158 .available_options_len = @intCast(wip.available_options.items.len),
159 .search_prefixes_len = @intCast(wip.search_prefixes.items.len),
160 .extra_len = @intCast(wip.extra.items.len),
161
162 .default_step = static.default_step,
163 .generated_files_len = static.generated_files_len,
164 .flags = .{
165 .poisoned = static.poisoned,
166 },
167 };
168 var buffers = [_][]const u8{
169 @ptrCast(&header),
170 wip.string_bytes.items,
171 @ptrCast(wip.steps.items),
172 @ptrCast(wip.path_deps.items(.base)),
173 @ptrCast(wip.path_deps.items(.sub)),
174 @ptrCast(wip.unlazy_deps.items),
175 @ptrCast(wip.system_integrations.items),
176 @ptrCast(wip.available_options.items),
177 @ptrCast(wip.search_prefixes.items),
178 @ptrCast(wip.extra.items),
179 };
180 try w.writeVecAll(&buffers);
181 }
182
183 pub fn addString(wip: *Wip, bytes: []const u8) Allocator.Error!String {
184 const gpa = wip.gpa;
185 assert(std.mem.indexOfScalar(u8, bytes, 0) == null);
186 const gop = try wip.string_table.getOrPutContextAdapted(
187 gpa,
188 @as([]const u8, bytes),
189 @as(StringTableIndexAdapter, .{ .bytes = wip.string_bytes.items }),
190 @as(StringTableContext, .{ .bytes = wip.string_bytes.items }),
191 );
192 if (gop.found_existing) return gop.key_ptr.*;
193
194 try wip.string_bytes.ensureUnusedCapacity(gpa, bytes.len + 1);
195 const new_off: String = @enumFromInt(wip.string_bytes.items.len);
196
197 wip.string_bytes.appendSliceAssumeCapacity(bytes);
198 wip.string_bytes.appendAssumeCapacity(0);
199
200 gop.key_ptr.* = new_off;
201
202 return new_off;
203 }
204
205 pub fn addOptionalString(wip: *Wip, bytes: ?[]const u8) Allocator.Error!OptionalString {
206 return .init(try addString(wip, bytes orelse return .none));
207 }
208
209 pub fn addStringList(wip: *Wip, list: []const []const u8) Allocator.Error!StringList {
210 // Increase size of extra to support the list. Add the string list
211 // there. Then check for duplicate, reverting list if already found.
212 const gpa = wip.gpa;
213 const revert_index: u32 = @intCast(wip.extra.items.len);
214 const added = try wip.extra.addManyAsSlice(gpa, list.len + 1);
215 added[0] = @intCast(list.len);
216 for (added[1..], list) |*d, s| d.* = @intFromEnum(try addString(wip, s));
217 const gop = try wip.dedupe_table.getOrPutContext(gpa, .{
218 .index = revert_index,
219 .len = @intCast(added.len),
220 }, @as(ExtraSlice.Context, .{ .extra = wip.extra.items }));
221
222 if (gop.found_existing) {
223 wip.extra.items.len = revert_index;
224 return @enumFromInt(gop.key_ptr.index);
225 }
226
227 return @enumFromInt(revert_index);
228 }
229
230 pub fn addBytes(wip: *Wip, bytes: []const u8) Allocator.Error!Bytes {
231 try wip.string_bytes.appendSlice(wip.gpa, bytes);
232 return .{
233 .index = @intCast(wip.string_bytes.items.len - bytes.len),
234 .len = @intCast(bytes.len),
235 };
236 }
237
238 pub fn addSemVer(wip: *Wip, sv: std.SemanticVersion) Allocator.Error!String {
239 var buffer: [256]u8 = undefined;
240 var writer: std.Io.Writer = .fixed(&buffer);
241 sv.format(&writer) catch return error.OutOfMemory;
242 return addString(wip, writer.buffered());
243 }
244
245 pub fn addTargetQuery(wip: *Wip, q: *const std.Target.Query) !TargetQuery.OptionalIndex {
246 if (q.isNative()) return .none;
247 const gpa = wip.gpa;
248 const cpu_name: ?String = switch (q.cpu_model) {
249 .native, .baseline, .determined_by_arch_os => null,
250 .explicit => |model| try wip.addString(model.name),
251 };
252 const os_version_min: TargetQuery.OsVersion = if (q.os_version_min) |ver| switch (ver) {
253 .none => .none,
254 .semver => |sem_ver| .{ .semver = try wip.addSemVer(sem_ver) },
255 .windows => |win_ver| .{ .windows = win_ver },
256 } else .default;
257 const os_version_max: TargetQuery.OsVersion = if (q.os_version_max) |ver| switch (ver) {
258 .none => .none,
259 .semver => |sem_ver| .{ .semver = try wip.addSemVer(sem_ver) },
260 .windows => |win_ver| .{ .windows = win_ver },
261 } else .default;
262 const glibc_version: ?String = if (q.glibc_version) |sem_ver| try wip.addSemVer(sem_ver) else null;
263 const dynamic_linker: ?String = if (q.dynamic_linker) |*dl|
264 if (dl.get()) |s| try wip.addString(s) else .empty
265 else
266 null;
267 const cpu_features_add_empty = q.cpu_features_add.isEmpty();
268 const cpu_features_sub_empty = q.cpu_features_sub.isEmpty();
269 const result_index: TargetQuery.Index = try wip.addExtra(TargetQuery, .{
270 .flags = .{
271 .cpu_arch = .init(q.cpu_arch),
272 .cpu_model = .init(q.cpu_model),
273 .cpu_features_add = !cpu_features_add_empty,
274 .cpu_features_sub = !cpu_features_sub_empty,
275 .os_tag = .init(q.os_tag),
276 .abi = .init(q.abi),
277 .object_format = .init(q.ofmt),
278 .os_version_min = os_version_min,
279 .os_version_max = os_version_max,
280 .glibc_version = glibc_version != null,
281 .android_api_level = q.android_api_level != null,
282 .dynamic_linker = dynamic_linker != null,
283 },
284 .cpu_features_add = .{ .value = if (cpu_features_add_empty) null else q.cpu_features_add },
285 .cpu_features_sub = .{ .value = if (cpu_features_sub_empty) null else q.cpu_features_sub },
286 .glibc_version = .{ .value = glibc_version },
287 .android_api_level = .{ .value = q.android_api_level },
288 .dynamic_linker = .{ .value = dynamic_linker },
289 .cpu_name = .{ .value = cpu_name },
290 .os_version_min = .{ .u = os_version_min },
291 .os_version_max = .{ .u = os_version_max },
292 });
293
294 // Deduplicate.
295 const gop = try wip.targets_table.getOrPutContext(gpa, result_index, @as(TargetsTableContext, .{
296 .extra = wip.extra.items,
297 }));
298 if (gop.found_existing) {
299 wip.extra.items.len = @intFromEnum(result_index);
300 return .init(gop.key_ptr.*);
301 } else {
302 return .init(result_index);
303 }
304 }
305
306 pub fn addTarget(wip: *Wip, t: std.Target) !TargetQuery.Index {
307 const gpa = wip.gpa;
308 const cpu_name: String = try wip.addString(t.cpu.model.name);
309
310 const os_version_min: TargetQuery.OsVersion, const os_version_max: TargetQuery.OsVersion, const glibc_version: ?String, const android_api_level: ?u32 = switch (t.os.versionRange()) {
311 .none => .{
312 .none,
313 .none,
314 null,
315 null,
316 },
317 .semver => |range| .{
318 .{ .semver = try wip.addSemVer(range.min) },
319 .{ .semver = try wip.addSemVer(range.max) },
320 null,
321 null,
322 },
323 .hurd => |hurd| .{
324 .{ .semver = try wip.addSemVer(hurd.range.min) },
325 .{ .semver = try wip.addSemVer(hurd.range.max) },
326 try wip.addSemVer(hurd.glibc),
327 null,
328 },
329 .linux => |linux| .{
330 .{ .semver = try wip.addSemVer(linux.range.min) },
331 .{ .semver = try wip.addSemVer(linux.range.max) },
332 try wip.addSemVer(linux.glibc),
333 linux.android,
334 },
335 .windows => |range| .{
336 .{ .windows = range.min },
337 .{ .windows = range.max },
338 null,
339 null,
340 },
341 };
342 const dynamic_linker: ?String = if (t.dynamic_linker.get()) |dl| try wip.addString(dl) else null;
343 const cpu_features_add_empty = t.cpu.features.isEmpty();
344 const result_index = try wip.addExtra(TargetQuery, .{
345 .flags = .{
346 .cpu_arch = .init(t.cpu.arch),
347 .cpu_model = .explicit,
348 .cpu_features_add = !cpu_features_add_empty,
349 .cpu_features_sub = false,
350 .os_tag = .init(t.os.tag),
351 .abi = .init(t.abi),
352 .object_format = .init(t.ofmt),
353 .os_version_min = os_version_min,
354 .os_version_max = os_version_max,
355 .glibc_version = glibc_version != null,
356 .android_api_level = android_api_level != null,
357 .dynamic_linker = dynamic_linker != null,
358 },
359 .cpu_features_add = .{ .value = if (cpu_features_add_empty) null else t.cpu.features },
360 .cpu_features_sub = .{ .value = null },
361 .glibc_version = .{ .value = glibc_version },
362 .android_api_level = .{ .value = android_api_level },
363 .dynamic_linker = .{ .value = dynamic_linker },
364 .cpu_name = .{ .value = cpu_name },
365 .os_version_min = .{ .u = os_version_min },
366 .os_version_max = .{ .u = os_version_max },
367 });
368
369 // Deduplicate.
370 const gop = try wip.targets_table.getOrPutContext(gpa, result_index, @as(TargetsTableContext, .{
371 .extra = wip.extra.items,
372 }));
373 if (gop.found_existing) {
374 wip.extra.items.len = @intFromEnum(result_index);
375 return gop.key_ptr.*;
376 } else {
377 return result_index;
378 }
379 }
380
381 pub fn addExtra(wip: *Wip, comptime T: type, v: T) Allocator.Error!T.Index {
382 const extra_len = Storage.extraLen(v);
383 try wip.extra.ensureUnusedCapacity(wip.gpa, extra_len);
384 return addExtraReserved(wip, T, v);
385 }
386
387 pub fn addExtraErased(wip: *Wip, comptime T: type, v: T) Allocator.Error!u32 {
388 const extra_len = Storage.extraLen(v);
389 try wip.extra.ensureUnusedCapacity(wip.gpa, extra_len);
390 return addExtraReservedErased(wip, T, v);
391 }
392
393 /// Same as `addExtra` but uses a hash map to possibly return an already
394 /// existing index instead of appending to `extra`.
395 pub fn addDeduped(wip: *Wip, comptime T: type, v: T) Allocator.Error!T.Index {
396 const gpa = wip.gpa;
397 const revert_index = wip.extra.items.len;
398 const upper_bound_len = Storage.extraLen(v);
399 try wip.extra.ensureUnusedCapacity(gpa, upper_bound_len);
400 try wip.dedupe_table.ensureUnusedCapacityContext(gpa, 1, @as(ExtraSlice.Context, .{
401 .extra = wip.extra.items,
402 }));
403 const new_index = addExtraReservedErased(wip, T, v);
404 const len: u32 = @intCast(wip.extra.items.len - new_index);
405 assert(len != 0);
406 const gop = wip.dedupe_table.getOrPutAssumeCapacityContext(.{
407 .index = new_index,
408 .len = len,
409 }, @as(ExtraSlice.Context, .{ .extra = wip.extra.items }));
410
411 if (gop.found_existing) {
412 wip.extra.items.len = revert_index;
413 return @enumFromInt(gop.key_ptr.index);
414 }
415
416 return @enumFromInt(new_index);
417 }
418
419 pub fn addExtraReserved(wip: *Wip, comptime T: type, v: T) T.Index {
420 return @enumFromInt(addExtraReservedErased(wip, T, v));
421 }
422
423 pub fn addExtraReservedErased(wip: *Wip, comptime T: type, v: T) u32 {
424 const result: u32 = @intCast(wip.extra.items.len);
425 wip.extra.items.len = Storage.setExtra(wip.extra.allocatedSlice(), result, v);
426 return result;
427 }
428
429 fn addExtraOptionalStringAssumeCapacity(wip: *Wip, optional_string: ?String) void {
430 const string = optional_string orelse return;
431 wip.extra.appendAssumeCapacity(@intFromEnum(string));
432 }
433
434 pub fn addGeneratedFile(wip: *Wip) GeneratedFileIndex {
435 defer wip.next_generated_file_index += 1;
436 return @enumFromInt(wip.next_generated_file_index);
437 }
438
439 /// Returned slice expires upon next append to the configuration.
440 pub fn stringSlice(wip: *const Wip, s: String) [:0]const u8 {
441 const start_slice = wip.string_bytes.items[@intFromEnum(s)..];
442 return start_slice[0..std.mem.indexOfScalar(u8, start_slice, 0).? :0];
443 }
444};
445
446pub const SystemIntegration = extern struct {
447 name: String,
448 status: Status,
449
450 pub const Status = enum(u32) {
451 disabled = 0,
452 enabled = 1,
453 };
454};
455
456pub const AvailableOption = extern struct {
457 name: String,
458 description: String,
459 type: Type,
460 /// If the `type_id` is `enum` or `enum_list` this provides the list of enum options
461 enum_options: OptionalStringList,
462
463 pub const Type = enum(u8) {
464 bool,
465 int,
466 float,
467 @"enum",
468 enum_list,
469 string,
470 list,
471 build_id,
472 lazy_path,
473 lazy_path_list,
474 };
475};
476
477pub const Step = extern struct {
478 name: String,
479 owner: Package.Index,
480 deps: Deps.Index,
481 max_rss: MaxRss,
482 extended: Storage.Extended(Flags, union(Tag) {
483 check_file: CheckFile,
484 compile: Compile,
485 config_header: ConfigHeader,
486 fail: Fail,
487 find_program: FindProgram,
488 fmt: Fmt,
489 install_artifact: InstallArtifact,
490 install_dir: InstallDir,
491 install_file: InstallFile,
492 obj_copy: ObjCopy,
493 options: Options,
494 run: Run,
495 top_level: TopLevel,
496 translate_c: TranslateC,
497 update_source_files: UpdateSourceFiles,
498 write_file: WriteFile,
499 }),
500
501 /// Points into `steps`.
502 pub const Index = enum(u32) {
503 _,
504
505 pub fn ptr(i: Index, c: *const Configuration) *const Step {
506 return &c.steps[@intFromEnum(i)];
507 }
508 };
509
510 /// Shared by all steps.
511 pub const Flags = packed struct(u32) {
512 tag: Tag,
513 _: u27 = 0,
514 };
515
516 pub const Tag = enum(u5) {
517 check_file,
518 compile,
519 config_header,
520 fail,
521 find_program,
522 fmt,
523 install_artifact,
524 install_dir,
525 install_file,
526 obj_copy,
527 options,
528 run,
529 top_level,
530 translate_c,
531 update_source_files,
532 write_file,
533 };
534
535 pub const TopLevel = struct {
536 flags: @This().Flags = .{},
537 description: String,
538
539 pub const Flags = packed struct(u32) {
540 tag: Tag = .top_level,
541 _: u27 = 0,
542 };
543 };
544
545 /// The first dependency step index will be the compile step whose
546 /// artifacts are being installed with this step.
547 pub const InstallArtifact = struct {
548 flags: @This().Flags,
549 bin_dir: Storage.FlagOptional(.flags, .bin_dir, InstallDestDir),
550 implib_dir: Storage.FlagOptional(.flags, .implib_dir, InstallDestDir),
551 pdb_dir: Storage.FlagOptional(.flags, .pdb_dir, InstallDestDir),
552 h_dir: Storage.FlagOptional(.flags, .h_dir, InstallDestDir),
553 bin_sub_path: Storage.FlagOptional(.flags, .bin_sub_path, String),
554
555 pub const Flags = packed struct(u32) {
556 tag: Tag = .install_artifact,
557 dylib_symlinks: bool,
558 bin_dir: bool,
559 implib_dir: bool,
560 pdb_dir: bool,
561 h_dir: bool,
562 bin_sub_path: bool,
563 _: u21 = 0,
564 };
565 };
566
567 pub const Run = struct {
568 flags: @This().Flags,
569 flags2: Flags2,
570 args: Storage.LengthPrefixedList(Arg.Index),
571 cwd: Storage.FlagOptional(.flags, .cwd, LazyPath.Index),
572 captured_stdout: Storage.FlagOptional(.flags, .captured_stdout, CapturedStream),
573 captured_stderr: Storage.FlagOptional(.flags, .captured_stderr, CapturedStream),
574 file_inputs: Storage.LengthPrefixedList(LazyPath.Index),
575 stdio_limit: Storage.FlagOptional(.flags, .stdio_limit, u64),
576 /// Always a compile step.
577 producer: Storage.FlagOptional(.flags, .producer, Step.Index),
578 /// First half is keys, second half is values.
579 environ_map: Storage.FlagOptional(.flags, .environ_map, EnvironMap.Index),
580 stdin: Storage.FlagUnion(.flags, .stdin, StdIn),
581 expect_stderr_exact: Storage.FlagOptional(.flags2, .expect_stderr_exact, Bytes),
582 expect_stdout_exact: Storage.FlagOptional(.flags2, .expect_stdout_exact, Bytes),
583 expect_stderr_match: Storage.FlagLengthPrefixedList(.flags2, .expect_stderr_match, Bytes),
584 expect_stdout_match: Storage.FlagLengthPrefixedList(.flags2, .expect_stdout_match, Bytes),
585 expect_term_value: Storage.FlagOptional(.flags2, .expect_term, u32),
586
587 pub const CapturedStream = extern struct {
588 generated_file: GeneratedFileIndex,
589 basename: String,
590 };
591
592 pub const Arg = struct {
593 flags: @This().Flags,
594 prefix: Storage.FlagOptional(.flags, .prefix, String),
595 suffix: Storage.FlagOptional(.flags, .suffix, String),
596 basename: Storage.FlagOptional(.flags, .basename, String),
597 path: Storage.FlagOptional(.flags, .path, LazyPath.Index),
598 /// Always a compile step.
599 producer: Storage.FlagOptional(.flags, .producer, Step.Index),
600 generated: Storage.FlagOptional(.flags, .generated, GeneratedFileIndex),
601
602 pub const Flags = packed struct(u32) {
603 tag: Arg.Tag,
604 prefix: bool,
605 suffix: bool,
606 basename: bool,
607 path: bool,
608 producer: bool,
609 generated: bool,
610 dep_file: bool,
611 _: u21 = 0,
612 };
613
614 pub const Tag = enum(u4) {
615 artifact,
616 /// `path` contains the file.
617 path_file,
618 path_directory,
619 /// `prefix` contains the string.
620 string,
621 file_content,
622 output_file,
623 output_directory,
624 passthru,
625 };
626
627 pub const Index = IndexType(@This());
628 };
629
630 pub const Color = enum(u4) {
631 /// `CLICOLOR_FORCE` is set, and `NO_COLOR` is unset.
632 enable,
633 /// `NO_COLOR` is set, and `CLICOLOR_FORCE` is unset.
634 disable,
635 /// If the build runner is using color, equivalent to `.enable`. Otherwise, equivalent to `.disable`.
636 inherit,
637 /// If stderr is captured or checked, equivalent to `.disable`. Otherwise, equivalent to `.inherit`.
638 auto,
639 /// The build runner does not modify the `CLICOLOR_FORCE` or `NO_COLOR` environment variables.
640 /// They are treated like normal variables, so can be controlled through `setEnvironmentVariable`.
641 manual,
642 };
643
644 pub const StdIn = union(@This().Tag) {
645 none: void,
646 bytes: Bytes,
647 lazy_path: LazyPath.Index,
648
649 pub const Tag = enum(u2) { none, bytes, lazy_path };
650 };
651 pub const TrimWhitespace = enum(u2) { none, all, leading, trailing };
652 pub const StdIo = enum(u2) { infer_from_args, inherit, check, zig_test };
653
654 pub const ExpectTermStatus = enum(u2) { exited, signal, stopped, unknown };
655
656 pub const Flags = packed struct(u32) {
657 tag: Tag = .run,
658 disable_zig_progress: bool,
659 skip_foreign_checks: bool,
660 failing_to_execute_foreign_is_an_error: bool,
661 has_side_effects: bool,
662 test_runner_mode: bool,
663 color: Color,
664 stdin: StdIn.Tag,
665 stdio: StdIo,
666 stdout_trim_whitespace: TrimWhitespace,
667 stderr_trim_whitespace: TrimWhitespace,
668 stdio_limit: bool,
669 producer: bool,
670 cwd: bool,
671 captured_stdout: bool,
672 captured_stderr: bool,
673 environ_map: bool,
674 _: u4 = 0,
675 };
676
677 pub const Flags2 = packed struct(u32) {
678 expect_stderr_exact: bool,
679 expect_stdout_exact: bool,
680 expect_stderr_match: bool,
681 expect_stdout_match: bool,
682 expect_term: bool,
683 expect_term_status: ExpectTermStatus,
684 _: u25 = 0,
685 };
686 };
687
688 pub const Compile = struct {
689 flags: @This().Flags,
690 flags2: Flags2,
691 flags3: Flags3,
692 flags4: Flags4,
693
694 root_module: Module.Index,
695 root_name: String,
696
697 filters: Storage.FlagLengthPrefixedList(.flags, .filters_len, String),
698 exec_cmd_args: Storage.FlagLengthPrefixedList(.flags, .exec_cmd_args_len, OptionalString),
699 installed_headers: Storage.FlagLengthPrefixedList(.flags, .installed_headers_len, Storage.Extended(InstalledHeader.Flags, InstalledHeader)),
700 force_undefined_symbols: Storage.FlagLengthPrefixedList(.flags, .force_undefined_symbols_len, String),
701 expect_errors: Storage.FlagUnion(.flags4, .expect_errors, ExpectErrors),
702 linker_script: Storage.FlagOptional(.flags4, .linker_script, LazyPath.Index),
703 version_script: Storage.FlagOptional(.flags4, .version_script, LazyPath.Index),
704 zig_lib_dir: Storage.FlagOptional(.flags3, .zig_lib_dir, LazyPath.Index),
705 libc_file: Storage.FlagOptional(.flags4, .libc_file, LazyPath.Index),
706 win32_manifest: Storage.FlagOptional(.flags3, .win32_manifest, LazyPath.Index),
707 win32_module_definition: Storage.FlagOptional(.flags3, .win32_module_definition, LazyPath.Index),
708 entitlements: Storage.FlagOptional(.flags4, .entitlements, LazyPath.Index),
709 version: Storage.FlagOptional(.flags3, .version, String), // semantic version string
710 entry: Storage.EnumOptional(.flags3, .entry, .symbol_name, String),
711 install_name: Storage.FlagOptional(.flags4, .install_name, String),
712 initial_memory: Storage.FlagOptional(.flags3, .initial_memory, u64),
713 max_memory: Storage.FlagOptional(.flags3, .max_memory, u64),
714 global_base: Storage.FlagOptional(.flags3, .global_base, u64),
715 image_base: Storage.FlagOptional(.flags3, .image_base, u64),
716 link_z_common_page_size: Storage.FlagOptional(.flags4, .link_z_common_page_size, u64),
717 link_z_max_page_size: Storage.FlagOptional(.flags4, .link_z_max_page_size, u64),
718 pagezero_size: Storage.FlagOptional(.flags4, .pagezero_size, u64),
719 stack_size: Storage.FlagOptional(.flags4, .stack_size, u64),
720 headerpad_size: Storage.FlagOptional(.flags4, .headerpad_size, u32),
721 error_limit: Storage.FlagOptional(.flags4, .error_limit, u32),
722 build_id: Storage.EnumOptional(.flags3, .build_id, .hexstring, String),
723 test_runner: Storage.FlagUnion(.flags3, .test_runner, TestRunner),
724
725 emit_directory: Storage.FlagOptional(.flags4, .emit_directory, GeneratedFileIndex),
726 generated_docs: Storage.FlagOptional(.flags4, .generated_docs, GeneratedFileIndex),
727 generated_asm: Storage.FlagOptional(.flags4, .generated_asm, GeneratedFileIndex),
728 generated_bin: Storage.FlagOptional(.flags4, .generated_bin, GeneratedFileIndex),
729 generated_pdb: Storage.FlagOptional(.flags4, .generated_pdb, GeneratedFileIndex),
730 generated_implib: Storage.FlagOptional(.flags4, .generated_implib, GeneratedFileIndex),
731 generated_llvm_bc: Storage.FlagOptional(.flags4, .generated_llvm_bc, GeneratedFileIndex),
732 generated_llvm_ir: Storage.FlagOptional(.flags4, .generated_llvm_ir, GeneratedFileIndex),
733 generated_h: Storage.FlagOptional(.flags4, .generated_h, GeneratedFileIndex),
734
735 pub const InstalledHeader = union(@This().Tag) {
736 file: File,
737 directory: Directory,
738
739 pub const Flags = packed struct(u32) {
740 tag: InstalledHeader.Tag,
741 _: u24 = 0,
742 };
743
744 pub const Tag = enum(u8) {
745 file,
746 directory,
747 };
748
749 pub const File = struct {
750 flags: @This().Flags = .{},
751 source: LazyPath.Index,
752 dest_sub_path: String,
753
754 pub const Flags = packed struct(u32) {
755 tag: InstalledHeader.Tag = .file,
756 _: u24 = 0,
757 };
758 };
759
760 pub const Directory = struct {
761 flags: @This().Flags,
762 source: LazyPath.Index,
763 dest_sub_path: String,
764 exclude_extensions: Storage.FlagLengthPrefixedList(.flags, .exclude_extensions, String),
765 include_extensions: Storage.FlagLengthPrefixedList(.flags, .include_extensions, String),
766
767 pub const Flags = packed struct(u32) {
768 tag: InstalledHeader.Tag = .directory,
769 exclude_extensions: bool,
770 include_extensions: bool,
771 _: u22 = 0,
772 };
773 };
774 };
775 pub const ExpectErrors = union(@This().Tag) {
776 pub const Tag = enum(u3) { contains, exact, starts_with, stderr_contains, none };
777
778 contains: String,
779 exact: Storage.LengthPrefixedList(String),
780 starts_with: String,
781 stderr_contains: String,
782 none: void,
783 };
784 pub const TestRunner = union(@This().Tag) {
785 pub const Tag = enum(u2) { default, simple, server };
786
787 default: void,
788 simple: LazyPath.Index,
789 server: LazyPath.Index,
790 };
791 pub const Entry = enum(u2) { default, disabled, enabled, symbol_name };
792
793 pub const Lto = enum(u2) {
794 none,
795 full,
796 thin,
797 default,
798
799 pub fn init(lto: ?std.zig.LtoMode) Lto {
800 return switch (lto orelse return .default) {
801 .none => .none,
802 .full => .full,
803 .thin => .thin,
804 };
805 }
806 };
807
808 pub const BuildId = enum(u3) {
809 none,
810 fast,
811 uuid,
812 sha1,
813 md5,
814 hexstring,
815 default,
816
817 pub fn init(build_id: ?std.zig.BuildId) BuildId {
818 return switch (build_id orelse return .default) {
819 .none => .none,
820 .fast => .fast,
821 .uuid => .uuid,
822 .sha1 => .sha1,
823 .md5 => .md5,
824 .hexstring => .hexstring,
825 };
826 }
827
828 pub fn unwrap(this: @This(), hexstring: ?String, c: *const Configuration) ?std.zig.BuildId {
829 if (hexstring) |h| {
830 assert(this == .hexstring);
831 return .initHexString(h.slice(c));
832 }
833 return switch (this) {
834 .none => .none,
835 .fast => .fast,
836 .uuid => .uuid,
837 .sha1 => .sha1,
838 .md5 => .md5,
839 .hexstring => unreachable,
840 .default => null,
841 };
842 }
843 };
844 pub const WasiExecModel = enum(u2) {
845 default,
846 command,
847 reactor,
848
849 pub fn init(wasi_exec_model: ?std.builtin.WasiExecModel) WasiExecModel {
850 return switch (wasi_exec_model orelse return .default) {
851 .command => .command,
852 .reactor => .reactor,
853 };
854 }
855 };
856 pub const Linkage = enum(u2) {
857 static,
858 dynamic,
859 default,
860
861 pub fn init(link_mode: ?std.builtin.LinkMode) Linkage {
862 return switch (link_mode orelse return .default) {
863 .static => .static,
864 .dynamic => .dynamic,
865 };
866 }
867
868 pub fn unwrap(this: @This()) ?std.builtin.LinkMode {
869 return switch (this) {
870 .static => .static,
871 .dynamic => .dynamic,
872 .default => null,
873 };
874 }
875 };
876 pub const Kind = enum(u3) {
877 exe,
878 lib,
879 obj,
880 @"test",
881 test_obj,
882
883 pub fn isTest(kind: Kind) bool {
884 return switch (kind) {
885 .exe, .lib, .obj => false,
886 .@"test", .test_obj => true,
887 };
888 }
889
890 pub fn toOutputMode(kind: Kind) std.builtin.OutputMode {
891 return switch (kind) {
892 .exe, .@"test" => .Exe,
893 .lib => .Lib,
894 .obj, .test_obj => .Obj,
895 };
896 }
897 };
898 pub const Subsystem = enum(u4) {
899 console,
900 windows,
901 posix,
902 native,
903 efi_application,
904 efi_boot_service_driver,
905 efi_rom,
906 efi_runtime_driver,
907 default,
908
909 pub fn init(subsystem: ?std.zig.Subsystem) Subsystem {
910 return switch (subsystem orelse return .default) {
911 .console => .console,
912 .windows => .windows,
913 .posix => .posix,
914 .native => .native,
915 .efi_application => .efi_application,
916 .efi_boot_service_driver => .efi_boot_service_driver,
917 .efi_rom => .efi_rom,
918 .efi_runtime_driver => .efi_runtime_driver,
919 };
920 }
921 };
922
923 pub const Flags = packed struct(u32) {
924 tag: Tag = .compile,
925
926 filters_len: bool,
927 exec_cmd_args_len: bool,
928 installed_headers_len: bool,
929 force_undefined_symbols_len: bool,
930
931 verbose_link: bool,
932 verbose_cc: bool,
933 rdynamic: bool,
934 import_memory: bool,
935 export_memory: bool,
936 import_symbols: bool,
937 import_table: bool,
938 export_table: bool,
939 shared_memory: bool,
940 link_eh_frame_hdr: bool,
941 link_emit_relocs: bool,
942 link_function_sections: bool,
943 link_data_sections: bool,
944 linker_dynamicbase: bool,
945 link_z_notext: bool,
946 link_z_relro: bool,
947 link_z_lazy: bool,
948 link_z_defs: bool,
949 headerpad_max_install_names: bool,
950 dead_strip_dylibs: bool,
951 force_load_objc: bool,
952 discard_local_symbols: bool,
953 mingw_unicode_entry_point: bool,
954 };
955
956 pub const Flags2 = packed struct(u32) {
957 pie: DefaultingBool,
958 formatted_panics: DefaultingBool,
959 bundle_compiler_rt: DefaultingBool,
960 bundle_ubsan_rt: DefaultingBool,
961 each_lib_rpath: DefaultingBool,
962 link_gc_sections: DefaultingBool,
963 linker_allow_shlib_undefined: DefaultingBool,
964 linker_allow_undefined_version: DefaultingBool,
965 linker_enable_new_dtags: DefaultingBool,
966 dll_export_fns: DefaultingBool,
967 use_llvm: DefaultingBool,
968 use_lld: DefaultingBool,
969 use_new_linker: DefaultingBool,
970 allow_so_scripts: DefaultingBool,
971 sanitize_coverage_trace_pc_guard: DefaultingBool,
972 linkage: Linkage,
973 };
974
975 pub const Flags3 = packed struct(u32) {
976 is_linking_libc: bool,
977 is_linking_libcpp: bool,
978 version: bool,
979 initial_memory: bool,
980 max_memory: bool,
981 kind: Kind,
982 compress_debug_sections: std.zig.CompressDebugSections,
983 global_base: bool,
984 test_runner: TestRunner.Tag,
985 wasi_exec_model: WasiExecModel,
986 win32_manifest: bool,
987 win32_module_definition: bool,
988 zig_lib_dir: bool,
989 rc_includes: std.zig.RcIncludes,
990 image_base: bool,
991 build_id: BuildId,
992 entry: Entry,
993 lto: Lto,
994 subsystem: Subsystem,
995 };
996
997 pub const Flags4 = packed struct(u32) {
998 libc_file: bool,
999 link_z_common_page_size: bool,
1000 link_z_max_page_size: bool,
1001 pagezero_size: bool,
1002 stack_size: bool,
1003 headerpad_size: bool,
1004 error_limit: bool,
1005 install_name: bool,
1006 entitlements: bool,
1007 expect_errors: ExpectErrors.Tag,
1008 linker_script: bool,
1009 version_script: bool,
1010 emit_directory: bool,
1011 generated_docs: bool,
1012 generated_asm: bool,
1013 generated_bin: bool,
1014 generated_pdb: bool,
1015 generated_implib: bool,
1016 generated_llvm_bc: bool,
1017 generated_llvm_ir: bool,
1018 generated_h: bool,
1019 _: u9 = 0,
1020 };
1021
1022 pub fn isDynamicLibrary(compile: *const Compile) bool {
1023 return compile.flags3.kind == .lib and compile.flags2.linkage == .dynamic;
1024 }
1025
1026 pub fn isStaticLibrary(compile: *const Compile) bool {
1027 return compile.flags3.kind == .lib and compile.flags2.linkage != .dynamic;
1028 }
1029
1030 pub fn producesImplib(compile: *const Compile, c: *const Configuration) bool {
1031 return isDll(compile, c);
1032 }
1033
1034 pub fn isDll(compile: *const Compile, c: *const Configuration) bool {
1035 return isDynamicLibrary(compile) and rootModuleTarget(compile, c).flags.os_tag == .windows;
1036 }
1037
1038 pub fn rootModuleTarget(compile: *const Compile, c: *const Configuration) TargetQuery {
1039 return compile.root_module.get(c).resolved_target.get(c).?.result.get(c);
1040 }
1041 };
1042
1043 pub const CheckFile = struct {
1044 flags: @This().Flags,
1045 file: LazyPath.Index,
1046 expected_exact: Storage.FlagOptional(.flags, .expected_exact, Bytes),
1047 expected_matches: Storage.FlagLengthPrefixedList(.flags, .expected_matches, Bytes),
1048 max_bytes: Storage.FlagOptional(.flags, .max_bytes, u32),
1049
1050 pub const Flags = packed struct(u32) {
1051 tag: Tag = .check_file,
1052 expected_exact: bool,
1053 expected_matches: bool,
1054 max_bytes: bool,
1055 _: u24 = 0,
1056 };
1057 };
1058
1059 pub const ConfigHeader = struct {
1060 flags: @This().Flags,
1061 template_file: Storage.FlagOptional(.flags, .template_file, LazyPath.Index),
1062 generated_dir: GeneratedFileIndex,
1063 input_size_limit: Storage.FlagOptional(.flags, .input_size_limit, u64),
1064 include_path: String,
1065 include_guard: Storage.FlagOptional(.flags, .include_guard, String),
1066 values: Storage.LengthPrefixedList(Value.Pair),
1067
1068 pub const Style = enum(u3) {
1069 autoconf_undef,
1070 autoconf_at,
1071 cmake,
1072 blank,
1073 nasm,
1074
1075 pub fn init(s: std.Build.Step.ConfigHeader.Style) Style {
1076 return switch (s) {
1077 .autoconf_undef => .autoconf_undef,
1078 .autoconf_at => .autoconf_at,
1079 .cmake => .cmake,
1080 .blank => .blank,
1081 .nasm => .nasm,
1082 };
1083 }
1084 };
1085
1086 pub const Value = struct {
1087 flags: @This().Flags,
1088 i64: Storage.EnumOptional(.flags, .tag, .i64, i64),
1089 u64: Storage.EnumOptional(.flags, .tag, .u64, u64),
1090 ident: Storage.EnumOptional(.flags, .tag, .ident, String),
1091 string: Storage.EnumOptional(.flags, .tag, .string, String),
1092
1093 pub const Flags = packed struct(u32) {
1094 tag: Value.Tag,
1095 small: u29,
1096 };
1097
1098 pub const Tag = enum(u3) {
1099 ident,
1100 string,
1101 small_unsigned,
1102 small_signed,
1103 i64,
1104 u64,
1105 };
1106
1107 pub const Pair = extern struct {
1108 key: String,
1109 index: Value.Index,
1110 };
1111
1112 pub const Index = enum(u32) {
1113 int_0 = max_u32 - 5,
1114 int_1 = max_u32 - 4,
1115 bool_false = max_u32 - 3,
1116 bool_true = max_u32 - 2,
1117 undef = max_u32 - 1,
1118 defined = max_u32,
1119 _,
1120
1121 pub fn unpack(this: @This(), c: *const Configuration) Unpacked {
1122 return switch (this) {
1123 .int_0 => .{ .u64 = 0 },
1124 .int_1 => .{ .u64 = 1 },
1125 .bool_false => .{ .bool = false },
1126 .bool_true => .{ .bool = true },
1127 .undef => .undef,
1128 .defined => .defined,
1129 _ => {
1130 const value = extraData(c, Value, @intFromEnum(this));
1131 return switch (value.flags.tag) {
1132 .ident => .{ .ident = value.ident.value.?.slice(c) },
1133 .string => .{ .string = value.string.value.?.slice(c) },
1134 .small_unsigned => .{ .u64 = value.flags.small },
1135 .small_signed => .{ .i64 = @as(i29, @bitCast(value.flags.small)) },
1136 .i64 => .{ .i64 = value.i64.value.? },
1137 .u64 => .{ .u64 = value.u64.value.? },
1138 };
1139 },
1140 };
1141 }
1142 };
1143
1144 pub const Unpacked = union(enum) {
1145 bool: bool,
1146 undef,
1147 defined,
1148 i64: i64,
1149 u64: u64,
1150 ident: []const u8,
1151 string: []const u8,
1152 };
1153
1154 pub fn initSigned(x: i64) @This() {
1155 return switch (x) {
1156 0 => unreachable, // should have been an Index
1157 1 => unreachable, // should have been an Index
1158 2...std.math.maxInt(u29) => .{
1159 .flags = .{
1160 .tag = .small_unsigned,
1161 .small = @intCast(x),
1162 },
1163 .i64 = .{ .value = null },
1164 .u64 = .{ .value = null },
1165 .ident = .{ .value = null },
1166 .string = .{ .value = null },
1167 },
1168 std.math.minInt(i29)...-1 => .{
1169 .flags = .{
1170 .tag = .small_signed,
1171 .small = @bitCast(@as(i29, @intCast(x))),
1172 },
1173 .i64 = .{ .value = null },
1174 .u64 = .{ .value = null },
1175 .ident = .{ .value = null },
1176 .string = .{ .value = null },
1177 },
1178 else => .{
1179 .flags = .{
1180 .tag = .i64,
1181 .small = 0,
1182 },
1183 .i64 = .{ .value = x },
1184 .u64 = .{ .value = null },
1185 .ident = .{ .value = null },
1186 .string = .{ .value = null },
1187 },
1188 };
1189 }
1190 };
1191
1192 pub const Flags = packed struct(u32) {
1193 tag: Tag = .config_header,
1194 template_file: bool,
1195 style: Style,
1196 input_size_limit: bool,
1197 include_guard: bool,
1198 _: u21 = 0,
1199 };
1200 };
1201
1202 pub const Fail = struct {
1203 flags: @This().Flags = .{},
1204 msg: String,
1205
1206 pub const Flags = packed struct(u32) {
1207 tag: Tag = .fail,
1208 _: u27 = 0,
1209 };
1210 };
1211
1212 pub const Fmt = struct {
1213 flags: @This().Flags,
1214 paths: Storage.FlagLengthPrefixedList(.flags, .paths, LazyPath.Index),
1215 exclude_paths: Storage.FlagLengthPrefixedList(.flags, .exclude_paths, LazyPath.Index),
1216
1217 pub const Flags = packed struct(u32) {
1218 tag: Tag = .fmt,
1219 paths: bool,
1220 exclude_paths: bool,
1221 check: bool,
1222 _: u24 = 0,
1223 };
1224 };
1225
1226 pub const FindProgram = struct {
1227 flags: @This().Flags = .{},
1228 names: StringList,
1229 found_path: GeneratedFileIndex,
1230
1231 pub const Flags = packed struct(u32) {
1232 tag: Tag = .find_program,
1233 _: u27 = 0,
1234 };
1235 };
1236
1237 pub const InstallDir = struct {
1238 flags: @This().Flags,
1239 source_dir: LazyPath.Index,
1240 dest_dir: InstallDestDir,
1241 dest_sub_path: Storage.FlagOptional(.flags, .dest_sub_path, String),
1242 exclude_extensions: Storage.FlagLengthPrefixedList(.flags, .exclude_extensions, String),
1243 include_extensions: Storage.FlagLengthPrefixedList(.flags, .include_extensions, String),
1244 blank_extensions: Storage.FlagLengthPrefixedList(.flags, .blank_extensions, String),
1245
1246 pub const Flags = packed struct(u32) {
1247 tag: Tag = .install_dir,
1248 dest_sub_path: bool,
1249 exclude_extensions: bool,
1250 include_extensions: bool,
1251 include_extensions_active: bool,
1252 blank_extensions: bool,
1253 _: u22 = 0,
1254 };
1255 };
1256
1257 pub const InstallFile = struct {
1258 flags: @This().Flags = .{},
1259 source: LazyPath.Index,
1260 dest_dir: InstallDestDir,
1261 dest_sub_path: String,
1262
1263 pub const Flags = packed struct(u32) {
1264 tag: Tag = .install_file,
1265 _: u27 = 0,
1266 };
1267 };
1268
1269 pub const ObjCopy = struct {
1270 flags: @This().Flags,
1271 input_file: LazyPath.Index,
1272 output_file: GeneratedFileIndex,
1273 basename: Storage.FlagOptional(.flags, .basename, String),
1274 debug_file: Storage.FlagOptional(.flags, .debug_file, GeneratedFileIndex),
1275 debug_basename: Storage.FlagOptional(.flags, .debug_basename, String),
1276 only_section: Storage.FlagOptional(.flags, .only_section, String),
1277 pad_to: Storage.FlagOptional(.flags, .pad_to, u64),
1278 add_section: Storage.FlagLengthPrefixedList(.flags, .add_section, AddSection),
1279 update_section: Storage.FlagLengthPrefixedList(.flags, .update_section, UpdateSection),
1280
1281 pub const Format = enum(u2) {
1282 binary,
1283 hex,
1284 elf,
1285 default,
1286
1287 pub fn init(f: ?std.Build.Step.ObjCopy.Format) @This() {
1288 return switch (f orelse return .default) {
1289 .binary => .binary,
1290 .hex => .hex,
1291 .elf => .elf,
1292 };
1293 }
1294 };
1295
1296 pub const Strip = enum(u2) {
1297 none,
1298 debug,
1299 debug_and_symbols,
1300 };
1301
1302 pub const AddSection = extern struct {
1303 section_name: String,
1304 file_path: LazyPath.Index,
1305 };
1306
1307 pub const UpdateSection = extern struct {
1308 section_name: String,
1309 flags: @This().Flags,
1310
1311 pub const Flags = packed struct(u32) {
1312 section_flags: SectionFlags,
1313 alignment: Alignment,
1314 _: u17 = 0,
1315 };
1316 };
1317
1318 pub const SectionFlags = packed struct(u9) {
1319 /// add SHF_ALLOC
1320 alloc: bool = false,
1321 /// if section is SHT_NOBITS, set SHT_PROGBITS, otherwise do nothing
1322 contents: bool = false,
1323 /// if section is SHT_NOBITS, set SHT_PROGBITS, otherwise do nothing (same as contents)
1324 load: bool = false,
1325 /// readonly: clear default SHF_WRITE flag
1326 readonly: bool = false,
1327 /// add SHF_EXECINSTR
1328 code: bool = false,
1329 /// add SHF_EXCLUDE
1330 exclude: bool = false,
1331 /// add SHF_X86_64_LARGE. Fatal error if target is not x86_64
1332 large: bool = false,
1333 /// add SHF_MERGE
1334 merge: bool = false,
1335 /// add SHF_STRINGS
1336 strings: bool = false,
1337
1338 pub const default: @This() = .{};
1339 };
1340
1341 pub const Flags = packed struct(u32) {
1342 tag: Tag = .obj_copy,
1343 basename: bool,
1344 debug_file: bool,
1345 debug_basename: bool,
1346 format: Format,
1347 strip: Strip,
1348 compress_debug: bool,
1349 only_section: bool,
1350 pad_to: bool,
1351 add_section: bool,
1352 update_section: bool,
1353 _: u15 = 0,
1354 };
1355 };
1356
1357 pub const Options = struct {
1358 flags: @This().Flags,
1359 generated_file: GeneratedFileIndex,
1360 contents: Bytes,
1361 args: Storage.FlagLengthPrefixedList(.flags, .args, Arg),
1362
1363 pub const Arg = extern struct {
1364 name: String,
1365 path: LazyPath.Index,
1366 };
1367
1368 pub const Flags = packed struct(u32) {
1369 tag: Tag = .options,
1370 args: bool,
1371 _: u26 = 0,
1372 };
1373 };
1374
1375 pub const TranslateC = struct {
1376 flags: @This().Flags,
1377 src_path: LazyPath.Index,
1378 output_file: GeneratedFileIndex,
1379 include_dirs: Storage.UnionList(.flags, .include_dirs, Module.IncludeDir),
1380 system_libs: Storage.FlagLengthPrefixedList(.flags, .system_libs, SystemLib.Index),
1381 c_macros: Storage.FlagLengthPrefixedList(.flags, .c_macros, String),
1382 target: ResolvedTarget.OptionalIndex,
1383
1384 pub const Flags = packed struct(u32) {
1385 tag: Tag = .translate_c,
1386 include_dirs: bool,
1387 system_libs: bool,
1388 c_macros: bool,
1389 link_libc: bool,
1390 optimize: Module.Optimize,
1391 _: u20 = 0,
1392 };
1393 };
1394
1395 pub const UpdateSourceFiles = struct {
1396 flags: @This().Flags,
1397 embeds: Storage.FlagLengthPrefixedList(.flags, .embeds, Embed),
1398 copies: Storage.FlagLengthPrefixedList(.flags, .copies, Copy),
1399
1400 pub const Embed = WriteFile.Embed;
1401 pub const Copy = WriteFile.Copy;
1402
1403 pub const Flags = packed struct(u32) {
1404 tag: Tag = .update_source_files,
1405 embeds: bool,
1406 copies: bool,
1407 _: u25 = 0,
1408 };
1409 };
1410
1411 pub const WriteFile = struct {
1412 flags: @This().Flags,
1413 generated_directory: GeneratedFileIndex,
1414 embeds: Storage.FlagLengthPrefixedList(.flags, .embeds, Embed),
1415 copies: Storage.FlagLengthPrefixedList(.flags, .copies, Copy),
1416 directories: Storage.FlagLengthPrefixedList(.flags, .directories, Directory),
1417 mutate_path: Storage.EnumOptional(.flags, .mode, .mutate, LazyPath.Index),
1418
1419 pub const Embed = extern struct {
1420 sub_path: String,
1421 contents: Bytes,
1422 };
1423
1424 pub const Copy = extern struct {
1425 sub_path: String,
1426 src_file: LazyPath.Index,
1427 };
1428
1429 pub const Directory = extern struct {
1430 sub_path: String,
1431 src_path: LazyPath.Index,
1432 exclude_extensions: OptionalStringList,
1433 include_extensions: OptionalStringList,
1434 };
1435
1436 pub const Mode = enum(u2) {
1437 whole_cached,
1438 tmp,
1439 mutate,
1440 };
1441
1442 pub const Flags = packed struct(u32) {
1443 tag: Tag = .write_file,
1444 embeds: bool,
1445 copies: bool,
1446 directories: bool,
1447 mode: Mode,
1448 _: u22 = 0,
1449 };
1450 };
1451
1452 pub fn flags(s: *const Step, c: *const Configuration) Flags {
1453 return @bitCast(c.extra[@intFromEnum(s.extended)]);
1454 }
1455};
1456
1457pub const MaxRss = enum(u32) {
1458 none = 0,
1459 _,
1460
1461 pub fn toBytes(mr: MaxRss) usize {
1462 const x: usize = @intFromEnum(mr);
1463 return x << 8;
1464 }
1465
1466 pub fn fromBytes(bytes: usize) MaxRss {
1467 return @enumFromInt(bytes >> 8);
1468 }
1469};
1470
1471pub const LazyPath = union(@This().Tag) {
1472 source_path: SourcePath,
1473 relative: Relative,
1474 generated: Generated,
1475
1476 pub const Tag = enum(u8) {
1477 /// A source file path relative to build root.
1478 source_path,
1479 /// Relative to the directory indicated in flags.
1480 relative,
1481 /// Path is available only after it is populated by its owning step.
1482 generated,
1483 };
1484
1485 pub const Flags = packed struct(u32) {
1486 tag: Tag,
1487 _: u24 = 0,
1488 };
1489
1490 /// An index into `extra`.
1491 pub const Index = enum(u32) {
1492 _,
1493
1494 pub fn get(this: @This(), c: *const Configuration) LazyPath {
1495 return extraData(c, LazyPath, @intFromEnum(this));
1496 }
1497 };
1498
1499 /// An index into `extra`, or `null`.
1500 pub const OptionalIndex = enum(u32) {
1501 none = max_u32,
1502 _,
1503
1504 pub fn unwrap(this: @This()) ?Index {
1505 return switch (this) {
1506 .none => null,
1507 else => @enumFromInt(@intFromEnum(this)),
1508 };
1509 }
1510 };
1511
1512 pub const SourcePath = struct {
1513 flags: @This().Flags = .{},
1514 owner: Package.Index,
1515 sub_path: String,
1516
1517 pub const Flags = packed struct(u32) {
1518 tag: Tag = .source_path,
1519 _: u24 = 0,
1520 };
1521 };
1522
1523 pub const Generated = struct {
1524 flags: @This().Flags = .{},
1525 index: GeneratedFileIndex,
1526 /// Applied after `up`.
1527 sub_path: String = .empty,
1528
1529 pub const Flags = packed struct(u32) {
1530 tag: Tag = .generated,
1531 /// The number of parent directories to go up.
1532 /// 0 means the generated file itself.
1533 /// 1 means the directory of the generated file.
1534 /// 2 means the parent of that directory, and so on.
1535 up: u24 = 0,
1536 };
1537 };
1538
1539 pub const Relative = struct {
1540 flags: @This().Flags,
1541 sub_path: String,
1542
1543 pub const Flags = packed struct(u32) {
1544 tag: Tag = .relative,
1545 base: Path.Base,
1546 _: u16 = 0,
1547 };
1548 };
1549};
1550
1551pub const GeneratedFileIndex = enum(u32) {
1552 _,
1553};
1554
1555pub const OptionalGeneratedFileIndex = enum(u32) {
1556 none = max_u32,
1557 _,
1558
1559 pub fn init(i: ?GeneratedFileIndex) OptionalGeneratedFileIndex {
1560 return @enumFromInt(@intFromEnum(i orelse return .none));
1561 }
1562
1563 pub fn unwrap(this: @This()) ?GeneratedFileIndex {
1564 return switch (this) {
1565 .none => null,
1566 else => @enumFromInt(@intFromEnum(this)),
1567 };
1568 }
1569};
1570
1571pub const Package = struct {
1572 dep_prefix: String,
1573 hash: String,
1574 root_path: String,
1575
1576 pub const Index = enum(u32) {
1577 root = max_u32,
1578 _,
1579
1580 /// Returns `null` for root package.
1581 pub fn get(i: @This(), c: *const Configuration) ?Package {
1582 if (i == .root) return null;
1583 return extraData(c, Package, @intFromEnum(i));
1584 }
1585
1586 pub fn depPrefixSlice(i: @This(), c: *const Configuration) [:0]const u8 {
1587 const package = get(i, c) orelse return "";
1588 return package.dep_prefix.slice(c);
1589 }
1590 };
1591};
1592
1593pub const Module = struct {
1594 flags: Flags,
1595 flags2: Flags2,
1596 import_table: ImportTable.Index,
1597 owner: Package.Index,
1598 root_source_file: LazyPath.OptionalIndex,
1599 resolved_target: ResolvedTarget.OptionalIndex,
1600 c_macros: Storage.FlagLengthPrefixedList(.flags, .c_macros, String),
1601 lib_paths: Storage.FlagLengthPrefixedList(.flags, .lib_paths, LazyPath.Index),
1602 export_symbol_names: Storage.FlagLengthPrefixedList(.flags, .export_symbol_names, String),
1603 include_dirs: Storage.UnionList(.flags, .include_dirs, IncludeDir),
1604 rpaths: Storage.UnionList(.flags, .rpaths, RPath),
1605 link_objects: Storage.UnionList(.flags, .link_objects, LinkObject),
1606 frameworks: Storage.FlagLengthPrefixedList(.flags, .frameworks, Framework),
1607
1608 pub const Optimize = enum(u3) {
1609 debug,
1610 safe,
1611 fast,
1612 small,
1613 default,
1614
1615 pub fn init(o: ?std.builtin.OptimizeMode) Optimize {
1616 return switch (o orelse return .default) {
1617 .Debug => .debug,
1618 .ReleaseSafe => .safe,
1619 .ReleaseFast => .fast,
1620 .ReleaseSmall => .small,
1621 };
1622 }
1623 };
1624
1625 pub const UnwindTables = enum(u2) {
1626 none,
1627 sync,
1628 async,
1629 default,
1630
1631 pub fn init(ut: ?std.builtin.UnwindTables) UnwindTables {
1632 return switch (ut orelse return .default) {
1633 .none => .none,
1634 .sync => .sync,
1635 .async => .async,
1636 };
1637 }
1638 };
1639
1640 pub const SanitizeC = enum(u2) {
1641 off,
1642 trap,
1643 full,
1644 default,
1645
1646 pub fn init(sc: ?std.zig.SanitizeC) SanitizeC {
1647 return switch (sc orelse return .default) {
1648 .off => .off,
1649 .trap => .trap,
1650 .full => .full,
1651 };
1652 }
1653 };
1654
1655 pub const DwarfFormat = enum(u2) {
1656 @"32",
1657 @"64",
1658 default,
1659
1660 pub fn init(df: ?std.dwarf.Format) DwarfFormat {
1661 return switch (df orelse return .default) {
1662 .@"32" => .@"32",
1663 .@"64" => .@"64",
1664 };
1665 }
1666 };
1667
1668 pub const Index = enum(u32) {
1669 _,
1670
1671 pub fn get(this: @This(), c: *const Configuration) Module {
1672 return extraData(c, Module, @intFromEnum(this));
1673 }
1674 };
1675
1676 pub const Flags = packed struct(u32) {
1677 optimize: Optimize,
1678 strip: DefaultingBool,
1679 unwind_tables: UnwindTables,
1680 dwarf_format: DwarfFormat,
1681 single_threaded: DefaultingBool,
1682 stack_protector: DefaultingBool,
1683 stack_check: DefaultingBool,
1684 sanitize_c: SanitizeC,
1685 sanitize_thread: DefaultingBool,
1686 fuzz: DefaultingBool,
1687 code_model: std.builtin.CodeModel,
1688 c_macros: bool,
1689 include_dirs: bool,
1690 lib_paths: bool,
1691 rpaths: bool,
1692 frameworks: bool,
1693 link_objects: bool,
1694 export_symbol_names: bool,
1695 };
1696
1697 pub const Flags2 = packed struct(u32) {
1698 valgrind: DefaultingBool,
1699 pic: DefaultingBool,
1700 red_zone: DefaultingBool,
1701 omit_frame_pointer: DefaultingBool,
1702 error_tracing: DefaultingBool,
1703 link_libc: DefaultingBool,
1704 link_libcpp: DefaultingBool,
1705 no_builtin: DefaultingBool,
1706 _: u16 = 0,
1707 };
1708
1709 pub const IncludeDir = union(enum(u3)) {
1710 path: LazyPath.Index,
1711 path_system: LazyPath.Index,
1712 path_after: LazyPath.Index,
1713 framework_path: LazyPath.Index,
1714 framework_path_system: LazyPath.Index,
1715 /// Always `Step.Tag.config_header`.
1716 config_header_step: Step.Index,
1717 embed_path: LazyPath.Index,
1718 };
1719
1720 pub const RPath = union(enum(u1)) {
1721 lazy_path: LazyPath.Index,
1722 special: String,
1723 };
1724
1725 pub const LinkObject = union(enum(u3)) {
1726 static_path: LazyPath.Index,
1727 /// Always `Step.Tag.compile`.
1728 other_step: Step.Index,
1729 system_lib: SystemLib.Index,
1730 assembly_file: LazyPath.Index,
1731 c_source_file: CSourceFile.Index,
1732 c_source_files: CSourceFiles.Index,
1733 win32_resource_file: RcSourceFile.Index,
1734 };
1735
1736 pub const Framework = extern struct {
1737 flags: @This().Flags,
1738 name: String,
1739
1740 pub const Flags = packed struct(u32) {
1741 needed: bool,
1742 weak: bool,
1743 _: u30 = 0,
1744 };
1745 };
1746};
1747
1748pub const ImportTable = struct {
1749 imports: Storage.MultiList(Import),
1750
1751 pub const Import = struct {
1752 name: String,
1753 module: Module.Index,
1754 };
1755
1756 /// Points into `extra`.
1757 pub const Index = enum(u32) {
1758 invalid = max_u32,
1759 _,
1760
1761 pub fn get(this: @This(), c: *const Configuration) ImportTable {
1762 return switch (this) {
1763 .invalid => unreachable,
1764 _ => extraData(c, ImportTable, @intFromEnum(this)),
1765 };
1766 }
1767 };
1768};
1769
1770pub const Deps = struct {
1771 steps: Storage.LengthPrefixedList(Step.Index),
1772
1773 pub const Index = enum(u32) {
1774 _,
1775
1776 pub fn get(this: @This(), c: *const Configuration) Deps {
1777 return extraData(c, Deps, @intFromEnum(this));
1778 }
1779
1780 pub fn slice(this: @This(), c: *const Configuration) []const Step.Index {
1781 return get(this, c).steps.slice;
1782 }
1783 };
1784};
1785
1786pub const EnvironMap = struct {
1787 keys: StringList,
1788 values: StringList,
1789
1790 pub const Index = IndexType(@This());
1791};
1792
1793/// Points into `extra`, where the first element is count of strings, following
1794/// elements is `String` per count.
1795///
1796/// Stored identically to `Deps`.
1797pub const StringList = enum(u32) {
1798 _,
1799
1800 pub fn slice(this: @This(), c: *const Configuration) []const String {
1801 const len = c.extra[@intFromEnum(this)];
1802 return @ptrCast(c.extra[@intFromEnum(this) + 1 ..][0..len]);
1803 }
1804};
1805
1806pub const OptionalStringList = enum(u32) {
1807 none = max_u32,
1808 _,
1809
1810 pub fn init(opt_string_list: ?StringList) OptionalStringList {
1811 const sl = opt_string_list orelse return .none;
1812 const result: OptionalStringList = @enumFromInt(@intFromEnum(sl));
1813 assert(result != .none);
1814 return result;
1815 }
1816
1817 pub fn unwrap(this: @This()) ?StringList {
1818 if (this == .none) return null;
1819 return @enumFromInt(@intFromEnum(this));
1820 }
1821
1822 pub fn slice(this: @This(), c: *const Configuration) ?[]const String {
1823 return (unwrap(this) orelse return null).slice(c);
1824 }
1825};
1826
1827pub const Path = extern struct {
1828 base: Base,
1829 sub: String,
1830
1831 pub const Base = enum(u8) {
1832 cwd,
1833 local_cache,
1834 global_cache,
1835 build_root,
1836 zig_exe,
1837 zig_lib,
1838 install_prefix,
1839 install_lib,
1840 install_bin,
1841 install_include,
1842 };
1843
1844 pub fn toCachePath(path: Path, c: *const Configuration, arena: Allocator) std.Build.Cache.Path {
1845 _ = c;
1846 _ = arena;
1847 _ = path;
1848 @panic("TODO");
1849 }
1850};
1851
1852pub const InstallDestDir = enum(u32) {
1853 none = max_u32 - 4,
1854 prefix = max_u32 - 3,
1855 lib = max_u32 - 2,
1856 bin = max_u32 - 1,
1857 header = max_u32,
1858 /// A `String` path relative to the prefix.
1859 _,
1860
1861 pub fn initCustom(sub_path: String) InstallDestDir {
1862 assert(@intFromEnum(sub_path) < @intFromEnum(InstallDestDir.none));
1863 return @enumFromInt(@intFromEnum(sub_path));
1864 }
1865
1866 pub const Unpacked = union(enum) {
1867 prefix,
1868 lib,
1869 bin,
1870 header,
1871 sub_path: String,
1872 };
1873
1874 pub fn unpack(this: @This()) ?Unpacked {
1875 return switch (this) {
1876 .none => null,
1877 .prefix => .prefix,
1878 .lib => .lib,
1879 .bin => .bin,
1880 .header => .header,
1881 _ => .{ .sub_path = @enumFromInt(@intFromEnum(this)) },
1882 };
1883 }
1884};
1885
1886/// Points into `string_bytes`, null-terminated.
1887pub const OptionalString = enum(u32) {
1888 empty = 0,
1889 /// The string "root".
1890 root = 1,
1891 none = max_u32,
1892 _,
1893
1894 pub fn init(s: String) OptionalString {
1895 const result: OptionalString = @enumFromInt(@intFromEnum(s));
1896 assert(result != .none);
1897 return result;
1898 }
1899
1900 pub fn unwrap(this: @This()) ?String {
1901 if (this == .none) return null;
1902 return @enumFromInt(@intFromEnum(this));
1903 }
1904
1905 pub fn slice(this: @This(), c: *const Configuration) ?[:0]const u8 {
1906 return (unwrap(this) orelse return null).slice(c);
1907 }
1908};
1909
1910/// Points into `string_bytes`, null-terminated.
1911pub const String = enum(u32) {
1912 empty = 0,
1913 /// The string "root".
1914 root = 1,
1915 _,
1916
1917 pub fn slice(index: String, c: *const Configuration) [:0]const u8 {
1918 const start_slice = c.string_bytes[@intFromEnum(index)..];
1919 return start_slice[0..std.mem.indexOfScalar(u8, start_slice, 0).? :0];
1920 }
1921};
1922
1923/// Arbitrary sequence of bytes that may contain null bytes.
1924pub const Bytes = extern struct {
1925 /// Points into `string_bytes`.
1926 index: u32,
1927 len: u32,
1928
1929 pub fn slice(bytes: Bytes, c: *const Configuration) []const u8 {
1930 return c.string_bytes[bytes.index..][0..bytes.len];
1931 }
1932};
1933
1934/// Stored as a power-of-two, with one special value to indicate none.
1935pub const Alignment = enum(u6) {
1936 @"1" = 0,
1937 @"2" = 1,
1938 @"4" = 2,
1939 @"8" = 3,
1940 @"16" = 4,
1941 @"32" = 5,
1942 @"64" = 6,
1943 none = std.math.maxInt(u6),
1944 _,
1945
1946 pub fn init(optional_alignment: ?std.mem.Alignment) @This() {
1947 const a = optional_alignment orelse return .none;
1948 return @enumFromInt(@intFromEnum(a));
1949 }
1950
1951 pub fn toBytes(a: @This()) ?u64 {
1952 return switch (a) {
1953 .none => null,
1954 else => @as(u64, 1) << @intFromEnum(a),
1955 };
1956 }
1957};
1958
1959pub const DefaultingBool = enum(u2) {
1960 false,
1961 true,
1962 default,
1963
1964 pub fn init(b: ?bool) DefaultingBool {
1965 return switch (b orelse return .default) {
1966 false => .false,
1967 true => .true,
1968 };
1969 }
1970
1971 pub fn toBool(db: DefaultingBool) ?bool {
1972 return switch (db) {
1973 .false => false,
1974 .true => true,
1975 .default => null,
1976 };
1977 }
1978};
1979
1980pub const SystemLib = struct {
1981 name: String,
1982 flags: Flags,
1983
1984 pub const Index = enum(u32) {
1985 _,
1986
1987 pub fn get(this: @This(), c: *const Configuration) SystemLib {
1988 return extraData(c, SystemLib, @intFromEnum(this));
1989 }
1990 };
1991
1992 pub const UsePkgConfig = enum(u2) {
1993 /// Don't use pkg-config, just pass -lfoo where foo is name.
1994 no,
1995 /// Try to get information on how to link the library from pkg-config.
1996 /// If that fails, fall back to passing -lfoo where foo is name.
1997 yes,
1998 /// Try to get information on how to link the library from pkg-config.
1999 /// If that fails, error out.
2000 force,
2001 };
2002
2003 pub const LinkMode = std.builtin.LinkMode;
2004
2005 pub const Flags = packed struct(u32) {
2006 needed: bool,
2007 weak: bool,
2008 use_pkg_config: UsePkgConfig,
2009 preferred_link_mode: LinkMode,
2010 search_strategy: SearchStrategy,
2011 _: u25 = 0,
2012 };
2013
2014 pub const SearchStrategy = enum(u2) { paths_first, mode_first, no_fallback };
2015};
2016
2017pub const CSourceFiles = struct {
2018 flags: Flags,
2019 root: LazyPath.Index,
2020 args: Storage.FlagList(.flags, .args_len, String),
2021 sub_paths: Storage.LengthPrefixedList(String),
2022
2023 pub const Index = enum(u32) {
2024 _,
2025
2026 pub fn get(this: @This(), c: *const Configuration) CSourceFiles {
2027 return extraData(c, CSourceFiles, @intFromEnum(this));
2028 }
2029 };
2030
2031 pub const Flags = packed struct(u32) {
2032 /// C compiler CLI flags.
2033 args_len: u29,
2034 lang: OptionalCSourceLanguage,
2035 };
2036};
2037
2038pub const CSourceFile = struct {
2039 flags: Flags,
2040 file: LazyPath.Index,
2041 args: Storage.FlagList(.flags, .args_len, String),
2042
2043 pub const Index = enum(u32) {
2044 _,
2045
2046 pub fn get(this: @This(), c: *const Configuration) CSourceFile {
2047 return extraData(c, CSourceFile, @intFromEnum(this));
2048 }
2049 };
2050
2051 pub const Flags = packed struct(u32) {
2052 /// C compiler CLI flags.
2053 args_len: u29,
2054 lang: OptionalCSourceLanguage,
2055 };
2056};
2057
2058pub const RcSourceFile = struct {
2059 flags: Flags,
2060 file: LazyPath.Index,
2061 args: Storage.FlagList(.flags, .args_len, String),
2062 include_paths: Storage.FlagLengthPrefixedList(.flags, .include_paths, LazyPath.Index),
2063
2064 pub const Index = enum(u32) {
2065 _,
2066
2067 pub fn get(this: @This(), c: *const Configuration) RcSourceFile {
2068 return extraData(c, RcSourceFile, @intFromEnum(this));
2069 }
2070 };
2071
2072 pub const Flags = packed struct(u32) {
2073 /// C compiler CLI flags.
2074 args_len: u31,
2075 include_paths: bool,
2076 };
2077};
2078
2079pub const OptionalCSourceLanguage = enum(u3) {
2080 c,
2081 cpp,
2082 objective_c,
2083 objective_cpp,
2084 assembly,
2085 assembly_with_preprocessor,
2086 default,
2087
2088 pub fn init(x: ?std.Build.Module.CSourceLanguage) @This() {
2089 return switch (x orelse return .default) {
2090 .c => .c,
2091 .cpp => .cpp,
2092 .objective_c => .objective_c,
2093 .objective_cpp => .objective_cpp,
2094 .assembly => .assembly,
2095 .assembly_with_preprocessor => .assembly_with_preprocessor,
2096 };
2097 }
2098
2099 pub fn get(this: @This()) ?std.Build.Module.CSourceLanguage {
2100 return switch (this) {
2101 .c => .c,
2102 .cpp => .cpp,
2103 .objective_c => .objective_c,
2104 .objective_cpp => .objective_cpp,
2105 .assembly => .assembly,
2106 .assembly_with_preprocessor => .assembly_with_preprocessor,
2107 .default => null,
2108 };
2109 }
2110};
2111
2112pub const ResolvedTarget = struct {
2113 /// none indicates host.
2114 query: TargetQuery.OptionalIndex,
2115 /// defaults will be resolved.
2116 result: TargetQuery.Index,
2117
2118 pub const Index = enum(u32) {
2119 _,
2120
2121 pub fn get(this: @This(), c: *const Configuration) ResolvedTarget {
2122 return extraData(c, ResolvedTarget, @intFromEnum(this));
2123 }
2124 };
2125
2126 pub const OptionalIndex = enum(u32) {
2127 none = max_u32,
2128 _,
2129
2130 pub fn init(i: Index) OptionalIndex {
2131 const result: OptionalIndex = @enumFromInt(@intFromEnum(i));
2132 assert(result != .none);
2133 return result;
2134 }
2135
2136 pub fn unwrap(this: @This()) ?Index {
2137 return switch (this) {
2138 .none => null,
2139 _ => @enumFromInt(@intFromEnum(this)),
2140 };
2141 }
2142
2143 pub fn get(this: @This(), c: *const Configuration) ?ResolvedTarget {
2144 return (unwrap(this) orelse return null).get(c);
2145 }
2146 };
2147};
2148
2149pub const TargetQuery = struct {
2150 flags: Flags,
2151
2152 cpu_features_add: Storage.FlagOptional(.flags, .cpu_features_add, std.Target.Cpu.Feature.Set),
2153 cpu_features_sub: Storage.FlagOptional(.flags, .cpu_features_sub, std.Target.Cpu.Feature.Set),
2154 cpu_name: Storage.EnumOptional(.flags, .cpu_model, .explicit, String),
2155 os_version_min: Storage.FlagUnion(.flags, .os_version_min, OsVersion),
2156 os_version_max: Storage.FlagUnion(.flags, .os_version_max, OsVersion),
2157 glibc_version: Storage.FlagOptional(.flags, .glibc_version, String),
2158 android_api_level: Storage.FlagOptional(.flags, .android_api_level, u32),
2159 dynamic_linker: Storage.FlagOptional(.flags, .dynamic_linker, String),
2160
2161 pub const Index = enum(u32) {
2162 _,
2163
2164 pub fn extraSlice(i: Index, extra: []const u32) []const u32 {
2165 return extra[@intFromEnum(i)..][0..length(i, extra)];
2166 }
2167
2168 pub fn length(i: Index, extra: []const u32) usize {
2169 return Storage.dataLength(extra, @intFromEnum(i), TargetQuery);
2170 }
2171
2172 pub fn get(this: @This(), c: *const Configuration) TargetQuery {
2173 return extraData(c, TargetQuery, @intFromEnum(this));
2174 }
2175 };
2176
2177 pub const OptionalIndex = enum(u32) {
2178 none = max_u32,
2179 _,
2180
2181 pub fn init(i: Index) OptionalIndex {
2182 const result: OptionalIndex = @enumFromInt(@intFromEnum(i));
2183 assert(result != .none);
2184 return result;
2185 }
2186
2187 pub fn unwrap(this: @This()) ?Index {
2188 return switch (this) {
2189 .none => null,
2190 _ => @enumFromInt(@intFromEnum(this)),
2191 };
2192 }
2193
2194 pub fn get(this: @This(), c: *const Configuration) ?TargetQuery {
2195 return (this.unwrap() orelse return null).get(c);
2196 }
2197 };
2198
2199 pub const CpuModel = enum(u2) {
2200 native,
2201 baseline,
2202 determined_by_arch_os,
2203 explicit,
2204
2205 pub fn init(x: std.Target.Query.CpuModel) @This() {
2206 return switch (x) {
2207 .native => .native,
2208 .baseline => .baseline,
2209 .determined_by_arch_os => .determined_by_arch_os,
2210 .explicit => .explicit,
2211 };
2212 }
2213 };
2214 pub const OsVersion = union(@This().Tag) {
2215 pub const Tag = enum(u2) { none, semver, windows, default };
2216
2217 none: void,
2218 semver: String,
2219 windows: std.Target.Os.WindowsVersion,
2220 default: void,
2221
2222 pub fn init(x: ?std.Target.Query.OsVersion) @This() {
2223 return switch (x orelse return .default) {
2224 .none => .none,
2225 .semver => .semver,
2226 .windows => .windows,
2227 };
2228 }
2229
2230 pub fn unwrap(this: @This(), c: *const Configuration) ?std.Target.Query.OsVersion {
2231 return switch (this) {
2232 .none => .none,
2233 .semver => |sv| .{ .semver = std.SemanticVersion.parse(sv.slice(c)) catch unreachable },
2234 .windows => |wv| .{ .windows = wv },
2235 .default => null,
2236 };
2237 }
2238 };
2239
2240 pub const Abi = enum(u5) {
2241 none,
2242 gnu,
2243 gnuabin32,
2244 gnuabi64,
2245 gnueabi,
2246 gnueabihf,
2247 gnuf32,
2248 gnusf,
2249 gnux32,
2250 eabi,
2251 eabihf,
2252 ilp32,
2253 android,
2254 androideabi,
2255 musl,
2256 muslabin32,
2257 muslabi64,
2258 musleabi,
2259 musleabihf,
2260 muslf32,
2261 muslsf,
2262 muslx32,
2263 msvc,
2264 itanium,
2265 simulator,
2266 ohos,
2267 ohoseabi,
2268 call0,
2269
2270 default,
2271
2272 pub fn init(x: ?std.Target.Abi) @This() {
2273 return switch (x orelse return .default) {
2274 .none => .none,
2275 .gnu => .gnu,
2276 .gnuabin32 => .gnuabin32,
2277 .gnuabi64 => .gnuabi64,
2278 .gnueabi => .gnueabi,
2279 .gnueabihf => .gnueabihf,
2280 .gnuf32 => .gnuf32,
2281 .gnusf => .gnusf,
2282 .gnux32 => .gnux32,
2283 .eabi => .eabi,
2284 .eabihf => .eabihf,
2285 .ilp32 => .ilp32,
2286 .android => .android,
2287 .androideabi => .androideabi,
2288 .musl => .musl,
2289 .muslabin32 => .muslabin32,
2290 .muslabi64 => .muslabi64,
2291 .musleabi => .musleabi,
2292 .musleabihf => .musleabihf,
2293 .muslf32 => .muslf32,
2294 .muslsf => .muslsf,
2295 .muslx32 => .muslx32,
2296 .msvc => .msvc,
2297 .itanium => .itanium,
2298 .simulator => .simulator,
2299 .ohos => .ohos,
2300 .ohoseabi => .ohoseabi,
2301 .call0 => .call0,
2302 };
2303 }
2304
2305 pub fn unwrap(this: @This()) ?std.Target.Abi {
2306 return switch (this) {
2307 .none => .none,
2308 .gnu => .gnu,
2309 .gnuabin32 => .gnuabin32,
2310 .gnuabi64 => .gnuabi64,
2311 .gnueabi => .gnueabi,
2312 .gnueabihf => .gnueabihf,
2313 .gnuf32 => .gnuf32,
2314 .gnusf => .gnusf,
2315 .gnux32 => .gnux32,
2316 .eabi => .eabi,
2317 .eabihf => .eabihf,
2318 .ilp32 => .ilp32,
2319 .android => .android,
2320 .androideabi => .androideabi,
2321 .musl => .musl,
2322 .muslabin32 => .muslabin32,
2323 .muslabi64 => .muslabi64,
2324 .musleabi => .musleabi,
2325 .musleabihf => .musleabihf,
2326 .muslf32 => .muslf32,
2327 .muslsf => .muslsf,
2328 .muslx32 => .muslx32,
2329 .msvc => .msvc,
2330 .itanium => .itanium,
2331 .simulator => .simulator,
2332 .ohos => .ohos,
2333 .ohoseabi => .ohoseabi,
2334 .call0 => .call0,
2335 .default => null,
2336 };
2337 }
2338 };
2339
2340 pub const CpuArch = enum(u6) {
2341 aarch64,
2342 aarch64_be,
2343 alpha,
2344 amdgcn,
2345 arc,
2346 arceb,
2347 arm,
2348 armeb,
2349 avr,
2350 bpfeb,
2351 bpfel,
2352 csky,
2353 ez80,
2354 hexagon,
2355 hppa,
2356 hppa64,
2357 kalimba,
2358 kvx,
2359 lanai,
2360 loongarch32,
2361 loongarch64,
2362 m68k,
2363 m88k,
2364 microblaze,
2365 microblazeel,
2366 mips,
2367 mipsel,
2368 mips64,
2369 mips64el,
2370 msp430,
2371 nvptx,
2372 nvptx64,
2373 or1k,
2374 powerpc,
2375 powerpcle,
2376 powerpc64,
2377 powerpc64le,
2378 propeller,
2379 riscv32,
2380 riscv32be,
2381 riscv64,
2382 riscv64be,
2383 s390x,
2384 sh,
2385 sheb,
2386 sparc,
2387 sparc64,
2388 spirv32,
2389 spirv64,
2390 thumb,
2391 thumbeb,
2392 ve,
2393 wasm32,
2394 wasm64,
2395 x86_16,
2396 x86,
2397 x86_64,
2398 xcore,
2399 xtensa,
2400 xtensaeb,
2401
2402 default,
2403
2404 pub fn init(x: ?std.Target.Cpu.Arch) @This() {
2405 return switch (x orelse return .default) {
2406 .aarch64 => .aarch64,
2407 .aarch64_be => .aarch64_be,
2408 .alpha => .alpha,
2409 .amdgcn => .amdgcn,
2410 .arc => .arc,
2411 .arceb => .arceb,
2412 .arm => .arm,
2413 .armeb => .armeb,
2414 .avr => .avr,
2415 .bpfeb => .bpfeb,
2416 .bpfel => .bpfel,
2417 .csky => .csky,
2418 .ez80 => .ez80,
2419 .hexagon => .hexagon,
2420 .hppa => .hppa,
2421 .hppa64 => .hppa64,
2422 .kalimba => .kalimba,
2423 .kvx => .kvx,
2424 .lanai => .lanai,
2425 .loongarch32 => .loongarch32,
2426 .loongarch64 => .loongarch64,
2427 .m68k => .m68k,
2428 .m88k => .m88k,
2429 .microblaze => .microblaze,
2430 .microblazeel => .microblazeel,
2431 .mips => .mips,
2432 .mipsel => .mipsel,
2433 .mips64 => .mips64,
2434 .mips64el => .mips64el,
2435 .msp430 => .msp430,
2436 .nvptx => .nvptx,
2437 .nvptx64 => .nvptx64,
2438 .or1k => .or1k,
2439 .powerpc => .powerpc,
2440 .powerpcle => .powerpcle,
2441 .powerpc64 => .powerpc64,
2442 .powerpc64le => .powerpc64le,
2443 .propeller => .propeller,
2444 .riscv32 => .riscv32,
2445 .riscv32be => .riscv32be,
2446 .riscv64 => .riscv64,
2447 .riscv64be => .riscv64be,
2448 .s390x => .s390x,
2449 .sh => .sh,
2450 .sheb => .sheb,
2451 .sparc => .sparc,
2452 .sparc64 => .sparc64,
2453 .spirv32 => .spirv32,
2454 .spirv64 => .spirv64,
2455 .thumb => .thumb,
2456 .thumbeb => .thumbeb,
2457 .ve => .ve,
2458 .wasm32 => .wasm32,
2459 .wasm64 => .wasm64,
2460 .x86_16 => .x86_16,
2461 .x86 => .x86,
2462 .x86_64 => .x86_64,
2463 .xcore => .xcore,
2464 .xtensa => .xtensa,
2465 .xtensaeb => .xtensaeb,
2466 };
2467 }
2468
2469 pub fn unwrap(this: @This()) ?std.Target.Cpu.Arch {
2470 return switch (this) {
2471 .aarch64 => .aarch64,
2472 .aarch64_be => .aarch64_be,
2473 .alpha => .alpha,
2474 .amdgcn => .amdgcn,
2475 .arc => .arc,
2476 .arceb => .arceb,
2477 .arm => .arm,
2478 .armeb => .armeb,
2479 .avr => .avr,
2480 .bpfeb => .bpfeb,
2481 .bpfel => .bpfel,
2482 .csky => .csky,
2483 .ez80 => .ez80,
2484 .hexagon => .hexagon,
2485 .hppa => .hppa,
2486 .hppa64 => .hppa64,
2487 .kalimba => .kalimba,
2488 .kvx => .kvx,
2489 .lanai => .lanai,
2490 .loongarch32 => .loongarch32,
2491 .loongarch64 => .loongarch64,
2492 .m68k => .m68k,
2493 .m88k => .m88k,
2494 .microblaze => .microblaze,
2495 .microblazeel => .microblazeel,
2496 .mips => .mips,
2497 .mipsel => .mipsel,
2498 .mips64 => .mips64,
2499 .mips64el => .mips64el,
2500 .msp430 => .msp430,
2501 .nvptx => .nvptx,
2502 .nvptx64 => .nvptx64,
2503 .or1k => .or1k,
2504 .powerpc => .powerpc,
2505 .powerpcle => .powerpcle,
2506 .powerpc64 => .powerpc64,
2507 .powerpc64le => .powerpc64le,
2508 .propeller => .propeller,
2509 .riscv32 => .riscv32,
2510 .riscv32be => .riscv32be,
2511 .riscv64 => .riscv64,
2512 .riscv64be => .riscv64be,
2513 .s390x => .s390x,
2514 .sh => .sh,
2515 .sheb => .sheb,
2516 .sparc => .sparc,
2517 .sparc64 => .sparc64,
2518 .spirv32 => .spirv32,
2519 .spirv64 => .spirv64,
2520 .thumb => .thumb,
2521 .thumbeb => .thumbeb,
2522 .ve => .ve,
2523 .wasm32 => .wasm32,
2524 .wasm64 => .wasm64,
2525 .x86_16 => .x86_16,
2526 .x86 => .x86,
2527 .x86_64 => .x86_64,
2528 .xcore => .xcore,
2529 .xtensa => .xtensa,
2530 .xtensaeb => .xtensaeb,
2531
2532 .default => null,
2533 };
2534 }
2535 };
2536
2537 pub const OsTag = enum(u6) {
2538 freestanding,
2539 other,
2540 contiki,
2541 fuchsia,
2542 hermit,
2543 managarm,
2544 haiku,
2545 hurd,
2546 illumos,
2547 linux,
2548 plan9,
2549 rtems,
2550 serenity,
2551 dragonfly,
2552 freebsd,
2553 netbsd,
2554 openbsd,
2555 driverkit,
2556 ios,
2557 maccatalyst,
2558 macos,
2559 tvos,
2560 visionos,
2561 watchos,
2562 windows,
2563 uefi,
2564 @"3ds",
2565 ps3,
2566 ps4,
2567 ps5,
2568 psp,
2569 vita,
2570 emscripten,
2571 wasi,
2572 amdhsa,
2573 amdpal,
2574 cuda,
2575 mesa3d,
2576 nvcl,
2577 opencl,
2578 opengl,
2579 vulkan,
2580 tios,
2581
2582 default,
2583
2584 pub fn init(x: ?std.Target.Os.Tag) @This() {
2585 return switch (x orelse return .default) {
2586 .freestanding => .freestanding,
2587 .other => .other,
2588 .contiki => .contiki,
2589 .fuchsia => .fuchsia,
2590 .hermit => .hermit,
2591 .managarm => .managarm,
2592 .haiku => .haiku,
2593 .hurd => .hurd,
2594 .illumos => .illumos,
2595 .linux => .linux,
2596 .plan9 => .plan9,
2597 .rtems => .rtems,
2598 .serenity => .serenity,
2599 .dragonfly => .dragonfly,
2600 .freebsd => .freebsd,
2601 .netbsd => .netbsd,
2602 .openbsd => .openbsd,
2603 .driverkit => .driverkit,
2604 .ios => .ios,
2605 .maccatalyst => .maccatalyst,
2606 .macos => .macos,
2607 .tvos => .tvos,
2608 .visionos => .visionos,
2609 .watchos => .watchos,
2610 .windows => .windows,
2611 .uefi => .uefi,
2612 .@"3ds" => .@"3ds",
2613 .ps3 => .ps3,
2614 .ps4 => .ps4,
2615 .ps5 => .ps5,
2616 .psp => .psp,
2617 .vita => .vita,
2618 .emscripten => .emscripten,
2619 .wasi => .wasi,
2620 .amdhsa => .amdhsa,
2621 .amdpal => .amdpal,
2622 .cuda => .cuda,
2623 .mesa3d => .mesa3d,
2624 .nvcl => .nvcl,
2625 .opencl => .opencl,
2626 .opengl => .opengl,
2627 .vulkan => .vulkan,
2628 .tios => .tios,
2629 };
2630 }
2631
2632 pub fn unwrap(this: @This()) ?std.Target.Os.Tag {
2633 return switch (this) {
2634 .freestanding => .freestanding,
2635 .other => .other,
2636 .contiki => .contiki,
2637 .fuchsia => .fuchsia,
2638 .hermit => .hermit,
2639 .managarm => .managarm,
2640 .haiku => .haiku,
2641 .hurd => .hurd,
2642 .illumos => .illumos,
2643 .linux => .linux,
2644 .plan9 => .plan9,
2645 .rtems => .rtems,
2646 .serenity => .serenity,
2647 .dragonfly => .dragonfly,
2648 .freebsd => .freebsd,
2649 .netbsd => .netbsd,
2650 .openbsd => .openbsd,
2651 .driverkit => .driverkit,
2652 .ios => .ios,
2653 .maccatalyst => .maccatalyst,
2654 .macos => .macos,
2655 .tvos => .tvos,
2656 .visionos => .visionos,
2657 .watchos => .watchos,
2658 .windows => .windows,
2659 .uefi => .uefi,
2660 .@"3ds" => .@"3ds",
2661 .ps3 => .ps3,
2662 .ps4 => .ps4,
2663 .ps5 => .ps5,
2664 .psp => .psp,
2665 .vita => .vita,
2666 .emscripten => .emscripten,
2667 .wasi => .wasi,
2668 .amdhsa => .amdhsa,
2669 .amdpal => .amdpal,
2670 .cuda => .cuda,
2671 .mesa3d => .mesa3d,
2672 .nvcl => .nvcl,
2673 .opencl => .opencl,
2674 .opengl => .opengl,
2675 .vulkan => .vulkan,
2676 .tios => .tios,
2677
2678 .default => null,
2679 };
2680 }
2681 };
2682
2683 pub const ObjectFormat = enum(u4) {
2684 c,
2685 coff,
2686 elf,
2687 hex,
2688 macho,
2689 plan9,
2690 raw,
2691 spirv,
2692 wasm,
2693
2694 default,
2695
2696 pub fn init(x: ?std.Target.ObjectFormat) @This() {
2697 return switch (x orelse return .default) {
2698 .c => .c,
2699 .coff => .coff,
2700 .elf => .elf,
2701 .hex => .hex,
2702 .macho => .macho,
2703 .plan9 => .plan9,
2704 .raw => .raw,
2705 .spirv => .spirv,
2706 .wasm => .wasm,
2707 };
2708 }
2709
2710 pub fn unwrap(this: @This()) ?std.Target.ObjectFormat {
2711 return switch (this) {
2712 .c => .c,
2713 .coff => .coff,
2714 .elf => .elf,
2715 .hex => .hex,
2716 .macho => .macho,
2717 .plan9 => .plan9,
2718 .raw => .raw,
2719 .spirv => .spirv,
2720 .wasm => .wasm,
2721
2722 .default => null,
2723 };
2724 }
2725 };
2726
2727 pub const Flags = packed struct(u32) {
2728 cpu_arch: CpuArch,
2729 cpu_model: CpuModel,
2730 cpu_features_add: bool,
2731 cpu_features_sub: bool,
2732 os_tag: OsTag,
2733 abi: Abi,
2734 object_format: ObjectFormat,
2735 os_version_min: OsVersion.Tag,
2736 os_version_max: OsVersion.Tag,
2737 glibc_version: bool,
2738 android_api_level: bool,
2739 dynamic_linker: bool,
2740 };
2741
2742 pub fn unwrap(tq: *const TargetQuery, c: *const Configuration) std.Target.Query {
2743 const cpu_arch = tq.flags.cpu_arch.unwrap();
2744 return .{
2745 .cpu_arch = cpu_arch,
2746 .cpu_model = switch (tq.flags.cpu_model) {
2747 .native => .native,
2748 .baseline => .baseline,
2749 .determined_by_arch_os => .determined_by_arch_os,
2750 .explicit => .{ .explicit = cpu_arch.?.parseCpuModel(tq.cpu_name.value.?.slice(c)).? },
2751 },
2752 .cpu_features_add = tq.cpu_features_add.value orelse .empty,
2753 .cpu_features_sub = tq.cpu_features_sub.value orelse .empty,
2754 .os_tag = tq.flags.os_tag.unwrap(),
2755 .os_version_min = tq.os_version_min.u.unwrap(c),
2756 .os_version_max = tq.os_version_max.u.unwrap(c),
2757 .glibc_version = if (tq.glibc_version.value) |s|
2758 std.SemanticVersion.parse(s.slice(c)) catch unreachable
2759 else
2760 null,
2761 .android_api_level = tq.android_api_level.value,
2762 .abi = tq.flags.abi.unwrap(),
2763 .dynamic_linker = if (tq.dynamic_linker.value) |s| .init(s.slice(c)) else null,
2764 .ofmt = tq.flags.object_format.unwrap(),
2765 };
2766 }
2767};
2768
2769pub const Storage = enum {
2770 flag_optional,
2771 enum_optional,
2772 extended,
2773 length_prefixed_list,
2774 flag_length_prefixed_list,
2775 union_list,
2776 flag_union,
2777 multi_list,
2778 flag_list,
2779
2780 /// The presence of the field is determined by a boolean within a packed
2781 /// struct.
2782 pub fn FlagOptional(
2783 comptime flags_arg: @EnumLiteral(),
2784 comptime flag_arg: @EnumLiteral(),
2785 comptime ValueArg: type,
2786 ) type {
2787 return struct {
2788 value: ?Value,
2789
2790 pub const storage: Storage = .flag_optional;
2791 pub const flags = flags_arg;
2792 pub const flag = flag_arg;
2793 pub const Value = ValueArg;
2794 };
2795 }
2796
2797 /// The type of the field is determined by an enum within a packed struct.
2798 pub fn FlagUnion(
2799 comptime flags_arg: @EnumLiteral(),
2800 comptime flag_arg: @EnumLiteral(),
2801 comptime UnionArg: type,
2802 ) type {
2803 return struct {
2804 u: Union,
2805
2806 pub const storage: Storage = .flag_union;
2807 pub const flags = flags_arg;
2808 pub const flag = flag_arg;
2809 pub const Union = UnionArg;
2810
2811 pub const Tag = @typeInfo(Union).@"union".tag_type.?;
2812 };
2813 }
2814
2815 /// The field is present if an enum tag from flags matches a specific value.
2816 pub fn EnumOptional(
2817 comptime flags_arg: @EnumLiteral(),
2818 comptime flag_arg: @EnumLiteral(),
2819 comptime tag_arg: @EnumLiteral(),
2820 comptime ValueArg: type,
2821 ) type {
2822 return struct {
2823 value: ?Value,
2824
2825 pub const storage: Storage = .enum_optional;
2826 pub const flags = flags_arg;
2827 pub const flag = flag_arg;
2828 pub const tag = tag_arg;
2829 pub const Value = ValueArg;
2830 };
2831 }
2832
2833 /// The field indexes into an auxilary buffer, with the first element being
2834 /// a packed struct that contains the tag.
2835 pub fn Extended(comptime BaseFlags: type, comptime U: type) type {
2836 return enum(u32) {
2837 _,
2838
2839 pub const storage: Storage = .extended;
2840
2841 pub fn tag(this: @This(), c: *const Configuration) @FieldType(BaseFlags, "tag") {
2842 const base_flags: BaseFlags = @bitCast(c.extra[@intFromEnum(this)]);
2843 return base_flags.tag;
2844 }
2845
2846 pub fn cast(this: @This(), c: *const Configuration, comptime S: type) ?S {
2847 const wanted_tag = @typeInfo(S.Flags).@"struct".fields[0].defaultValue().?;
2848 const base_flags: BaseFlags = @bitCast(c.extra[@intFromEnum(this)]);
2849 if (base_flags.tag != wanted_tag) return null;
2850 var i: usize = @intFromEnum(this);
2851 return data(c.extra, &i, S);
2852 }
2853
2854 pub fn get(this: @This(), buffer: []const u32) U {
2855 var i: usize = @intFromEnum(this);
2856 const base_flags: BaseFlags = @bitCast(buffer[i]);
2857 return switch (base_flags.tag) {
2858 inline else => |t| @unionInit(U, @tagName(t), data(buffer, &i, @FieldType(U, @tagName(t)))),
2859 };
2860 }
2861 };
2862 }
2863
2864 /// A field in flags determines whether the length is zero or nonzero. If
2865 /// the length is nonzero, then there is a length field followed by the
2866 /// list. The elements need well-defined memory layout but can otherwise be
2867 /// any multiple of u32 length. The length is the number of elements, not
2868 /// the number of u32s.
2869 pub fn FlagLengthPrefixedList(
2870 comptime flags_arg: @EnumLiteral(),
2871 comptime flag_arg: @EnumLiteral(),
2872 comptime ElemArg: type,
2873 ) type {
2874 return struct {
2875 slice: []const Elem,
2876
2877 pub const storage: Storage = .flag_length_prefixed_list;
2878 pub const flags = flags_arg;
2879 pub const flag = flag_arg;
2880 pub const Elem = ElemArg;
2881
2882 pub fn initErased(s: []const u32) @This() {
2883 return .{ .slice = @ptrCast(s) };
2884 }
2885 };
2886 }
2887
2888 /// The field contains a u32 length followed by that many items. Each
2889 /// element needs well-defined memory layout but can otherwise be any
2890 /// multiple of u32 length. The length is number of elements, not the
2891 /// number of u32s.
2892 pub fn LengthPrefixedList(comptime ElemArg: type) type {
2893 return struct {
2894 slice: []const Elem,
2895
2896 pub const storage: Storage = .length_prefixed_list;
2897 pub const Elem = ElemArg;
2898
2899 pub fn initErased(s: []const u32) @This() {
2900 return .{ .slice = @ptrCast(s) };
2901 }
2902 };
2903 }
2904
2905 /// The field is a list whose length is an integer inside flags.
2906 pub fn FlagList(
2907 comptime flags_arg: @EnumLiteral(),
2908 comptime flag_arg: @EnumLiteral(),
2909 comptime ElemArg: type,
2910 ) type {
2911 return struct {
2912 slice: []const Elem,
2913
2914 pub const storage: Storage = .flag_list;
2915 pub const flags = flags_arg;
2916 pub const flag = flag_arg;
2917 pub const Elem = ElemArg;
2918
2919 pub fn initErased(s: []const u32) @This() {
2920 return .{ .slice = @ptrCast(s) };
2921 }
2922 };
2923 }
2924
2925 /// The field contains a u32 length followed by that many items for the
2926 /// first field, that many items for the second field, etc.
2927 pub fn MultiList(comptime ElemArg: type) type {
2928 return struct {
2929 mal: std.MultiArrayList(Elem),
2930
2931 pub const storage: Storage = .multi_list;
2932 pub const Elem = ElemArg;
2933 };
2934 }
2935
2936 /// `UnionArg` is a tagged union with a small integer for the enum tag.
2937 ///
2938 /// A field in flags determines whether the metadata is present.
2939 ///
2940 /// The metadata is bit-packed consecutive packed struct which is the
2941 /// `UnionArg` enum tag combined with a "last" marker boolean field.
2942 /// When "last" is true, the element is the last one, providing
2943 /// the length of the list.
2944 ///
2945 /// Following is each element of the list; each bitcastable to u32.
2946 pub fn UnionList(
2947 comptime flags_arg: @EnumLiteral(),
2948 comptime flag_arg: @EnumLiteral(),
2949 comptime UnionArg: type,
2950 ) type {
2951 return struct {
2952 /// When serializing it is UnionArg slice pointer.
2953 /// When deserializing it is extra index of first UnionArg element.
2954 data: ?*const anyopaque,
2955 len: usize,
2956
2957 pub const storage: Storage = .union_list;
2958 pub const flags = flags_arg;
2959 pub const flag = flag_arg;
2960 pub const Union = UnionArg;
2961
2962 pub const Tag = @typeInfo(Union).@"union".tag_type.?;
2963 pub const MetaInt = @Int(.unsigned, @bitSizeOf(Tag) + 1);
2964 pub const Meta = packed struct(MetaInt) {
2965 tag: Tag,
2966 last: bool,
2967 };
2968
2969 /// Valid to call only when serializing.
2970 pub fn init(s: []const Union) @This() {
2971 return .{ .data = s.ptr, .len = s.len };
2972 }
2973
2974 /// Valid to call only when deserializing.
2975 pub fn slice(this: *const @This(), extra: []const u32) []const u32 {
2976 return extra[@intFromPtr(this.data)..][0..this.len];
2977 }
2978
2979 /// Valid to call only when deserializing.
2980 pub fn get(this: *const @This(), extra: []const u32, i: usize) Union {
2981 const elem = slice(this, extra)[i];
2982 return switch (this.tag(extra, i)) {
2983 inline else => |comptime_tag| @unionInit(Union, @tagName(comptime_tag), @enumFromInt(elem)),
2984 };
2985 }
2986
2987 /// Valid to call only when deserializing.
2988 pub fn tag(this: *const @This(), extra: []const u32, i: usize) Tag {
2989 const start = @intFromPtr(this.data);
2990 const meta_start = start - (this.len * @bitSizeOf(Meta) + 31) / 32;
2991 return loadBits(u32, extra[meta_start..], i * @bitSizeOf(Meta), Meta).tag;
2992 }
2993
2994 fn extraLen(len: usize) usize {
2995 return len + (len * @bitSizeOf(Meta) + 31) / 32;
2996 }
2997 };
2998 }
2999
3000 pub fn dataLength(buffer: []const u32, i: usize, comptime S: type) usize {
3001 var end = i;
3002 _ = data(buffer, &end, S);
3003 return end - i;
3004 }
3005
3006 pub fn data(buffer: []const u32, i: *usize, comptime T: type) T {
3007 switch (@typeInfo(T)) {
3008 .@"struct" => |info| {
3009 var result: T = undefined;
3010 inline for (info.fields) |field| {
3011 @field(result, field.name) = dataField(buffer, i, &result, field.type);
3012 }
3013 return result;
3014 },
3015 .@"union" => |info| {
3016 const flags: T.Flags = @bitCast(buffer[i.*]);
3017 return switch (flags.tag) {
3018 inline else => |comptime_tag| @unionInit(
3019 T,
3020 @tagName(comptime_tag),
3021 data(buffer, i, info.fields[@intFromEnum(comptime_tag)].type),
3022 ),
3023 };
3024 },
3025 else => comptime unreachable,
3026 }
3027 }
3028
3029 fn dataField(buffer: []const u32, i: *usize, container: anytype, comptime Field: type) Field {
3030 switch (@typeInfo(Field)) {
3031 .void => return {},
3032 .int => |info| switch (info.bits) {
3033 32 => {
3034 defer i.* += 1;
3035 return buffer[i.*];
3036 },
3037 64 => {
3038 defer i.* += 2;
3039 return @bitCast(buffer[i.*..][0..2].*);
3040 },
3041 else => comptime unreachable,
3042 },
3043 .@"enum" => {
3044 defer i.* += 1;
3045 return @enumFromInt(buffer[i.*]);
3046 },
3047 .@"struct" => |info| switch (info.layout) {
3048 .@"packed" => switch (info.backing_integer.?) {
3049 u32 => {
3050 defer i.* += 1;
3051 return @bitCast(buffer[i.*]);
3052 },
3053 u64 => {
3054 defer i.* += 2;
3055 return @bitCast(buffer[i.*..][0..2].*);
3056 },
3057 else => comptime unreachable,
3058 },
3059 .auto => switch (Field) {
3060 std.Target.Cpu.Feature.Set => {
3061 const u32_count = (Field.usize_count * @sizeOf(usize)) / @sizeOf(u32);
3062 defer i.* += u32_count;
3063 return .{ .ints = @as(
3064 *align(@alignOf(u32)) const [Field.usize_count]usize,
3065 @ptrCast(buffer[i.*..][0..u32_count]),
3066 ).* };
3067 },
3068 else => switch (Field.storage) {
3069 .flag_optional => {
3070 const flags = @field(container, @tagName(Field.flags));
3071 const flag = @field(flags, @tagName(Field.flag));
3072 return .{
3073 .value = if (flag) dataField(buffer, i, container, Field.Value) else null,
3074 };
3075 },
3076 .flag_union => {
3077 const flags = @field(container, @tagName(Field.flags));
3078 const tag: Field.Tag = @field(flags, @tagName(Field.flag));
3079 return .{
3080 .u = switch (tag) {
3081 inline else => |comptime_tag| @unionInit(
3082 Field.Union,
3083 @tagName(comptime_tag),
3084 dataField(
3085 buffer,
3086 i,
3087 container,
3088 @typeInfo(Field.Union).@"union".fields[@intFromEnum(comptime_tag)].type,
3089 ),
3090 ),
3091 },
3092 };
3093 },
3094 .enum_optional => {
3095 const flags = @field(container, @tagName(Field.flags));
3096 const tag = @field(flags, @tagName(Field.flag));
3097 const match = tag == Field.tag;
3098 return .{
3099 .value = if (match) dataField(buffer, i, container, Field.Value) else null,
3100 };
3101 },
3102 .extended => @compileError("unimplemented"),
3103 .length_prefixed_list => {
3104 const n = @divExact(@sizeOf(Field.Elem), @sizeOf(u32));
3105 const data_start = i.* + 1;
3106 const buf_len = buffer[data_start - 1] * n;
3107 defer i.* = data_start + buf_len;
3108 return .{ .slice = @ptrCast(buffer[data_start..][0..buf_len]) };
3109 },
3110 .flag_length_prefixed_list => {
3111 const flags = @field(container, @tagName(Field.flags));
3112 const flag = @field(flags, @tagName(Field.flag));
3113 if (!flag) return .{ .slice = &.{} };
3114 const n = @divExact(@sizeOf(Field.Elem), @sizeOf(u32));
3115 const data_start = i.* + 1;
3116 const buf_len = buffer[data_start - 1] * n;
3117 defer i.* = data_start + buf_len;
3118 return .{ .slice = @ptrCast(buffer[data_start..][0..buf_len]) };
3119 },
3120 .flag_list => {
3121 const flags = @field(container, @tagName(Field.flags));
3122 const len: u32 = @field(flags, @tagName(Field.flag));
3123 const data_start = i.*;
3124 defer i.* = data_start + len;
3125 return .{ .slice = @ptrCast(buffer[data_start..][0..len]) };
3126 },
3127 .multi_list => {
3128 const data_start = i.* + 1;
3129 const len = buffer[data_start - 1];
3130 defer i.* = data_start + len * @typeInfo(Field.Elem).@"struct".fields.len;
3131 return .{ .mal = .{
3132 .bytes = @ptrCast(@constCast(buffer[data_start..][0..len])),
3133 .len = len,
3134 .capacity = len,
3135 } };
3136 },
3137 .union_list => {
3138 const flags = @field(container, @tagName(Field.flags));
3139 const flag = @field(flags, @tagName(Field.flag));
3140 if (!flag) return .{ .data = null, .len = 0 };
3141 const meta_start = i.*;
3142 const meta_buffer = buffer[meta_start..];
3143 var len: u32 = 0;
3144 var bit_offset: usize = 0;
3145 while (true) : (bit_offset += @bitSizeOf(Field.Meta)) {
3146 const meta = loadBits(u32, meta_buffer, bit_offset, Field.Meta);
3147 len += 1;
3148 if (meta.last) break;
3149 }
3150 const end = meta_start + Field.extraLen(len);
3151 i.* = end;
3152 return .{ .data = @ptrFromInt(end - len), .len = len };
3153 },
3154 },
3155 },
3156 .@"extern" => {
3157 const n = @divExact(@sizeOf(Field), @sizeOf(u32));
3158 defer i.* += n;
3159 return @bitCast(buffer[i.*..][0..n].*);
3160 },
3161 },
3162 else => comptime unreachable,
3163 }
3164 }
3165
3166 /// Returns new end index.
3167 fn setExtra(buffer: []u32, index: usize, extra: anytype) usize {
3168 const fields = @typeInfo(@TypeOf(extra)).@"struct".fields;
3169 var i = index;
3170 inline for (fields) |field| {
3171 i += setExtraField(buffer, i, field.type, @field(extra, field.name));
3172 }
3173 return i;
3174 }
3175
3176 fn extraFieldLen(field: anytype) usize {
3177 const Field = @TypeOf(field);
3178 return switch (@typeInfo(Field)) {
3179 .void => 0,
3180 .int => |info| switch (info.bits) {
3181 32 => 1,
3182 64 => 2,
3183 else => comptime unreachable,
3184 },
3185 .@"enum" => 1,
3186 .@"struct" => |info| switch (info.layout) {
3187 .@"packed" => switch (info.backing_integer.?) {
3188 u32 => 1,
3189 u64 => 2,
3190 else => comptime unreachable,
3191 },
3192 .auto => switch (Field.storage) {
3193 .flag_optional, .enum_optional => (@sizeOf(Field.Value) + 3) / 4,
3194 .extended => 1,
3195 .length_prefixed_list,
3196 .flag_length_prefixed_list,
3197 .flag_list,
3198 => 1 + @divExact(@sizeOf(Field.Elem), @sizeOf(u32)) * field.slice.len,
3199 .multi_list => 1 + field.mal.len * @typeInfo(Field.Elem).@"struct".fields.len,
3200 .union_list => Field.extraLen(field.len),
3201 .flag_union => switch (field.u) {
3202 inline else => |v| extraFieldLen(v),
3203 },
3204 },
3205 .@"extern" => @divExact(@sizeOf(Field), @sizeOf(u32)),
3206 },
3207 else => @compileError("bad type: " ++ @typeName(Field)),
3208 };
3209 }
3210
3211 fn extraLen(extra: anytype) usize {
3212 const fields = @typeInfo(@TypeOf(extra)).@"struct".fields;
3213 var i: usize = 0;
3214 inline for (fields) |field| {
3215 i += Storage.extraFieldLen(@field(extra, field.name));
3216 }
3217 return i;
3218 }
3219
3220 inline fn setExtraField(buffer: []u32, i: usize, comptime Field: type, value: anytype) usize {
3221 switch (@typeInfo(Field)) {
3222 .void => return 0,
3223 .int => |info| switch (info.bits) {
3224 32 => {
3225 buffer[i] = value;
3226 return 1;
3227 },
3228 64 => {
3229 buffer[i..][0..2].* = @bitCast(value);
3230 return 2;
3231 },
3232 else => comptime unreachable,
3233 },
3234 .@"enum" => {
3235 buffer[i] = @intFromEnum(value);
3236 return 1;
3237 },
3238 .@"struct" => |info| switch (info.layout) {
3239 .@"packed" => switch (info.backing_integer.?) {
3240 u32 => {
3241 buffer[i] = @bitCast(value);
3242 return 1;
3243 },
3244 u64 => {
3245 buffer[i..][0..2].* = @bitCast(value);
3246 return 2;
3247 },
3248 else => comptime unreachable,
3249 },
3250 .auto => switch (Field) {
3251 std.Target.Cpu.Feature.Set => {
3252 const casted: []const u32 = @ptrCast(&value.ints);
3253 @memcpy(buffer[i..][0..casted.len], casted);
3254 return casted.len;
3255 },
3256 else => switch (Field.storage) {
3257 .flag_optional, .enum_optional => {
3258 return if (value.value) |v| setExtraField(buffer, i, Field.Value, v) else 0;
3259 },
3260 .flag_union => return switch (value.u) {
3261 inline else => |x| setExtraField(buffer, i, @TypeOf(x), x),
3262 },
3263 .extended => @compileError("unimplemented"),
3264 .flag_length_prefixed_list => {
3265 const len: u32 = @intCast(value.slice.len);
3266 if (len == 0) return 0; // Flag bit hides the length prefix.
3267 buffer[i] = len;
3268 const buf_len = len * @divExact(@sizeOf(Field.Elem), @sizeOf(u32));
3269 @memcpy(buffer[i + 1 ..][0..buf_len], @as([]const u32, @ptrCast(value.slice)));
3270 return 1 + buf_len;
3271 },
3272 .length_prefixed_list => {
3273 const len: u32 = @intCast(value.slice.len);
3274 buffer[i] = len;
3275 const buf_len = len * @divExact(@sizeOf(Field.Elem), @sizeOf(u32));
3276 @memcpy(buffer[i + 1 ..][0..buf_len], @as([]const u32, @ptrCast(value.slice)));
3277 return 1 + buf_len;
3278 },
3279 .flag_list => {
3280 const len: u32 = @intCast(value.slice.len);
3281 @memcpy(buffer[i..][0..len], @as([]const u32, @ptrCast(value.slice)));
3282 return len;
3283 },
3284 .multi_list => {
3285 const len: u32 = @intCast(value.mal.len);
3286 buffer[i] = len;
3287 const fields = @typeInfo(Field.Elem).@"struct".fields;
3288 inline for (0..fields.len) |field_i| @memcpy(
3289 buffer[i + 1 + field_i * len ..][0..len],
3290 @as([]const u32, @ptrCast(value.mal.items(@enumFromInt(field_i)))),
3291 );
3292 return 1 + fields.len * len;
3293 },
3294 .union_list => {
3295 if (value.len == 0) return 0;
3296 const Tag = @typeInfo(Field.Union).@"union".tag_type.?;
3297 const slice_ptr: [*]const Field.Union = @ptrCast(@alignCast(value.data));
3298 const slice = slice_ptr[0..value.len];
3299 const meta_buffer = buffer[i..][0 .. (slice.len * @bitSizeOf(Field.Meta) + 31) / 32];
3300 for (slice[0 .. slice.len - 1], 0..) |elem, elem_index| {
3301 const union_tag: Tag = elem;
3302 storeBits(u32, meta_buffer, elem_index * @bitSizeOf(Field.Meta), @as(Field.Meta, .{
3303 .tag = union_tag,
3304 .last = false,
3305 }));
3306 } else {
3307 const elem_index = slice.len - 1;
3308 const elem = slice[elem_index];
3309 const union_tag: Tag = elem;
3310 storeBits(u32, meta_buffer, elem_index * @bitSizeOf(Field.Meta), @as(Field.Meta, .{
3311 .tag = union_tag,
3312 .last = true,
3313 }));
3314 }
3315 var total: usize = meta_buffer.len;
3316 for (i + meta_buffer.len.., slice) |elem_index, src| switch (src) {
3317 inline else => |x| total += setExtraField(buffer, elem_index, @TypeOf(x), x),
3318 };
3319 return total;
3320 },
3321 },
3322 },
3323 .@"extern" => {
3324 const n = @divExact(@sizeOf(Field), @sizeOf(u32));
3325 buffer[i..][0..n].* = @bitCast(value);
3326 return n;
3327 },
3328 },
3329 else => @compileError("bad field type: " ++ @typeName(Field)),
3330 }
3331 }
3332};
3333
3334fn IndexType(comptime T: type) type {
3335 return enum(u32) {
3336 _,
3337
3338 pub fn get(this: @This(), c: *const Configuration) T {
3339 return extraData(c, T, @intFromEnum(this));
3340 }
3341 };
3342}
3343
3344pub fn extraData(c: *const Configuration, comptime T: type, index: usize) T {
3345 var i: usize = index;
3346 return Storage.data(c.extra, &i, T);
3347}
3348
3349pub const LoadFileError = Io.File.Reader.Error || Allocator.Error || error{EndOfStream};
3350
3351pub fn loadFile(arena: Allocator, io: Io, file: Io.File) LoadFileError!Configuration {
3352 var buffer: [2000]u8 = undefined;
3353 var fr = file.reader(io, &buffer);
3354 return load(arena, &fr.interface) catch |err| switch (err) {
3355 error.ReadFailed => return fr.err.?,
3356 else => |e| return e,
3357 };
3358}
3359
3360pub const LoadError = Io.Reader.Error || Allocator.Error;
3361
3362pub fn load(arena: Allocator, reader: *Io.Reader) LoadError!Configuration {
3363 const header = try reader.takeStruct(Header, .native);
3364 const result: Configuration = .{
3365 .string_bytes = try arena.alloc(u8, header.string_bytes_len),
3366 .steps = try arena.alloc(Step, header.steps_len),
3367 .path_deps_sub = try arena.alloc(String, header.path_deps_len),
3368 .path_deps_base = try arena.alloc(Path.Base, header.path_deps_len),
3369 .unlazy_deps = try arena.alloc(String, header.unlazy_deps_len),
3370 .system_integrations = try arena.alloc(SystemIntegration, header.system_integrations_len),
3371 .available_options = try arena.alloc(AvailableOption, header.available_options_len),
3372 .search_prefixes = try arena.alloc(String, header.search_prefixes_len),
3373 .extra = try arena.alloc(u32, header.extra_len),
3374 .default_step = header.default_step,
3375 .generated_files_len = header.generated_files_len,
3376 .poisoned = header.flags.poisoned,
3377 };
3378 var vecs = [_][]u8{
3379 result.string_bytes,
3380 @ptrCast(result.steps),
3381 @ptrCast(result.path_deps_base),
3382 @ptrCast(result.path_deps_sub),
3383 @ptrCast(result.unlazy_deps),
3384 @ptrCast(result.system_integrations),
3385 @ptrCast(result.available_options),
3386 @ptrCast(result.search_prefixes),
3387 @ptrCast(result.extra),
3388 };
3389 try reader.readVecAll(&vecs);
3390 return result;
3391}
3392
3393pub fn loadBits(comptime Int: type, buffer: []const Int, bit_offset: usize, comptime Result: type) Result {
3394 const index = bit_offset / @bitSizeOf(Int);
3395 const small_bit_offset = bit_offset % @bitSizeOf(Int);
3396 const ResultInt = @Int(.unsigned, @bitSizeOf(Result));
3397 const result: ResultInt = @truncate(buffer[index] >> @intCast(small_bit_offset));
3398 const available_bits = @bitSizeOf(Int) - small_bit_offset;
3399 if (available_bits >= @bitSizeOf(ResultInt)) return @bitCast(result);
3400 const missing_bits = @bitSizeOf(ResultInt) - available_bits;
3401 const upper: ResultInt = @truncate(buffer[index + 1] & ((@as(usize, 1) << @intCast(missing_bits)) - 1));
3402 return @bitCast(result | (upper << @intCast(available_bits)));
3403}
3404
3405pub fn storeBits(comptime Int: type, buffer: []Int, bit_offset: usize, value: anytype) void {
3406 const Value = @TypeOf(value);
3407 const ValueInt = @Int(.unsigned, @bitSizeOf(Value));
3408 const value_int: ValueInt = @bitCast(value);
3409 const index = bit_offset / @bitSizeOf(Int);
3410 const small_bit_offset = bit_offset % @bitSizeOf(Int);
3411 const available_bits = @bitSizeOf(Int) - small_bit_offset;
3412 if (available_bits >= @bitSizeOf(ValueInt)) {
3413 buffer[index] &= ~(((@as(Int, 1) << @intCast(@bitSizeOf(Value))) - 1) << @intCast(small_bit_offset));
3414 buffer[index] |= @as(Int, value_int) << @intCast(small_bit_offset);
3415 } else {
3416 const DoubleInt = @Int(.unsigned, @bitSizeOf(Int) * 2);
3417 const ptr: *align(@alignOf(Int)) DoubleInt = @ptrCast(buffer[index..][0..2]);
3418 ptr.* &= ~(((@as(DoubleInt, 1) << @intCast(@bitSizeOf(Value))) - 1) << @intCast(small_bit_offset));
3419 ptr.* |= @as(DoubleInt, value_int) << @intCast(small_bit_offset);
3420 }
3421}
3422
3423test "loadBits and storeBits" {
3424 var buffer: [2]u32 = .{
3425 0b01111111000000001111111100000000,
3426 0b11111111000000001111111100000100,
3427 };
3428 try std.testing.expectEqual(0b100, loadBits(u32, &buffer, 6, u3));
3429 try std.testing.expectEqual(0b100011, loadBits(u32, &buffer, 29, u6));
3430
3431 storeBits(u32, &buffer, 6, @as(u3, 0b010));
3432 storeBits(u32, &buffer, 29, @as(u6, 0b010010));
3433
3434 try std.testing.expectEqual(0b010, loadBits(u32, &buffer, 6, u3));
3435 try std.testing.expectEqual(0b010010, loadBits(u32, &buffer, 29, u6));
3436}
lib/std/Build/Fuzz.zig deleted-597
......@@ -1,597 +0,0 @@
1const std = @import("../std.zig");
2const Io = std.Io;
3const Build = std.Build;
4const Cache = Build.Cache;
5const Step = std.Build.Step;
6const assert = std.debug.assert;
7const fatal = std.process.fatal;
8const Allocator = std.mem.Allocator;
9const log = std.log;
10const Coverage = std.debug.Coverage;
11const abi = Build.abi.fuzz;
12
13const Fuzz = @This();
14const build_runner = @import("root");
15
16gpa: Allocator,
17io: Io,
18mode: Mode,
19
20/// Allocated into `gpa`.
21run_steps: []const *Step.Run,
22
23group: Io.Group,
24root_prog_node: std.Progress.Node,
25prog_node: std.Progress.Node,
26
27/// Protects `coverage_files`.
28coverage_mutex: Io.Mutex,
29coverage_files: std.AutoArrayHashMapUnmanaged(u64, CoverageMap),
30
31queue_mutex: Io.Mutex,
32queue_cond: Io.Condition,
33msg_queue: std.ArrayList(Msg),
34
35pub const Mode = union(enum) {
36 forever: struct { ws: *Build.WebServer },
37 limit: Limited,
38
39 pub const Limited = struct {
40 amount: u64,
41 };
42};
43
44const Msg = union(enum) {
45 coverage: struct {
46 id: u64,
47 cumulative: struct {
48 runs: u64,
49 unique: u64,
50 coverage: u64,
51 },
52 run: *Step.Run,
53 },
54 entry_point: struct {
55 coverage_id: u64,
56 addr: u64,
57 },
58};
59
60const CoverageMap = struct {
61 mapped_memory: []align(std.heap.page_size_min) const u8,
62 coverage: Coverage,
63 source_locations: []Coverage.SourceLocation,
64 /// Elements are indexes into `source_locations` pointing to the unit tests that are being fuzz tested.
65 entry_points: std.ArrayList(u32),
66 start_timestamp: i64,
67 start_n_runs: u64,
68
69 fn deinit(cm: *CoverageMap, gpa: Allocator) void {
70 std.posix.munmap(cm.mapped_memory);
71 cm.coverage.deinit(gpa);
72 cm.* = undefined;
73 }
74};
75
76pub fn init(
77 gpa: Allocator,
78 io: Io,
79 all_steps: []const *Build.Step,
80 root_prog_node: std.Progress.Node,
81 mode: Mode,
82) error{ OutOfMemory, Canceled }!Fuzz {
83 const run_steps: []const *Step.Run = steps: {
84 var steps: std.ArrayList(*Step.Run) = .empty;
85 defer steps.deinit(gpa);
86 const rebuild_node = root_prog_node.start("Rebuilding Unit Tests", 0);
87 defer rebuild_node.end();
88 var rebuild_group: Io.Group = .init;
89 defer rebuild_group.cancel(io);
90
91 for (all_steps) |step| {
92 const run = step.cast(Step.Run) orelse continue;
93 if (run.producer == null) continue;
94 if (run.fuzz_tests.items.len == 0) continue;
95 try steps.append(gpa, run);
96 rebuild_group.async(io, rebuildTestsWorkerRun, .{ run, gpa, rebuild_node });
97 }
98
99 if (steps.items.len == 0) fatal("no fuzz tests found", .{});
100 rebuild_node.setEstimatedTotalItems(steps.items.len);
101 const run_steps = try gpa.dupe(*Step.Run, steps.items);
102 try rebuild_group.await(io);
103 break :steps run_steps;
104 };
105 errdefer gpa.free(run_steps);
106
107 for (run_steps) |run| {
108 assert(run.fuzz_tests.items.len > 0);
109 if (run.rebuilt_executable == null)
110 fatal("one or more unit tests failed to be rebuilt in fuzz mode", .{});
111 }
112
113 return .{
114 .gpa = gpa,
115 .io = io,
116 .mode = mode,
117 .run_steps = run_steps,
118 .group = .init,
119 .root_prog_node = root_prog_node,
120 .prog_node = .none,
121 .coverage_files = .empty,
122 .coverage_mutex = .init,
123 .queue_mutex = .init,
124 .queue_cond = .init,
125 .msg_queue = .empty,
126 };
127}
128
129pub fn start(fuzz: *Fuzz) void {
130 const io = fuzz.io;
131 fuzz.prog_node = fuzz.root_prog_node.start("Fuzzing", 0);
132
133 if (fuzz.mode == .forever) {
134 // For polling messages and sending updates to subscribers.
135 fuzz.group.concurrent(io, coverageRun, .{fuzz}) catch |err|
136 fatal("unable to spawn coverage task: {t}", .{err});
137 }
138
139 for (fuzz.run_steps) |run| {
140 assert(run.rebuilt_executable != null);
141 fuzz.group.async(io, fuzzWorkerRun, .{ fuzz, run });
142 }
143}
144
145pub fn deinit(fuzz: *Fuzz) void {
146 const io = fuzz.io;
147 fuzz.group.cancel(io);
148 fuzz.prog_node.end();
149 fuzz.gpa.free(fuzz.run_steps);
150}
151
152fn rebuildTestsWorkerRun(run: *Step.Run, gpa: Allocator, parent_prog_node: std.Progress.Node) void {
153 rebuildTestsWorkerRunFallible(run, gpa, parent_prog_node) catch |err| {
154 const compile = run.producer.?;
155 log.err("step '{s}': failed to rebuild in fuzz mode: {t}", .{ compile.step.name, err });
156 };
157}
158
159fn rebuildTestsWorkerRunFallible(run: *Step.Run, gpa: Allocator, parent_prog_node: std.Progress.Node) !void {
160 const graph = run.step.owner.graph;
161 const io = graph.io;
162 const compile = run.producer.?;
163 const prog_node = parent_prog_node.start(compile.step.name, 0);
164 defer prog_node.end();
165
166 const result = compile.rebuildInFuzzMode(gpa, prog_node);
167
168 const show_compile_errors = compile.step.result_error_bundle.errorMessageCount() > 0;
169 const show_error_msgs = compile.step.result_error_msgs.items.len > 0;
170 const show_stderr = compile.step.result_stderr.len > 0;
171
172 if (show_error_msgs or show_compile_errors or show_stderr) {
173 var buf: [256]u8 = undefined;
174 const stderr = try io.lockStderr(&buf, graph.stderr_mode);
175 defer io.unlockStderr();
176 build_runner.printErrorMessages(gpa, &compile.step, .{}, stderr.terminal(), .verbose, .indent) catch {};
177 }
178
179 const rebuilt_bin_path = result catch |err| switch (err) {
180 error.MakeFailed => return,
181 else => |other| return other,
182 };
183 run.rebuilt_executable = try rebuilt_bin_path.join(gpa, compile.out_filename);
184}
185
186fn fuzzWorkerRun(fuzz: *Fuzz, run: *Step.Run) void {
187 const owner = run.step.owner;
188 const gpa = owner.allocator;
189 const graph = owner.graph;
190 const io = graph.io;
191
192 run.rerunInFuzzMode(fuzz, fuzz.prog_node) catch |err| switch (err) {
193 error.MakeFailed => {
194 var buf: [256]u8 = undefined;
195 const stderr = io.lockStderr(&buf, graph.stderr_mode) catch |e| switch (e) {
196 error.Canceled => return,
197 };
198 defer io.unlockStderr();
199 build_runner.printErrorMessages(gpa, &run.step, .{}, stderr.terminal(), .verbose, .indent) catch {};
200 return;
201 },
202 else => {
203 log.err("step '{s}': failed to rerun in fuzz mode: {t}", .{ run.step.name, err });
204 return;
205 },
206 };
207}
208
209pub fn serveSourcesTar(fuzz: *Fuzz, req: *std.http.Server.Request) !void {
210 assert(fuzz.mode == .forever);
211
212 var arena_state: std.heap.ArenaAllocator = .init(fuzz.gpa);
213 defer arena_state.deinit();
214 const arena = arena_state.allocator();
215
216 const DedupTable = std.ArrayHashMapUnmanaged(Build.Cache.Path, void, Build.Cache.Path.TableAdapter, false);
217 var dedup_table: DedupTable = .empty;
218 defer dedup_table.deinit(fuzz.gpa);
219
220 for (fuzz.run_steps) |run_step| {
221 const compile_inputs = run_step.producer.?.step.inputs.table;
222 for (compile_inputs.keys(), compile_inputs.values()) |dir_path, *file_list| {
223 try dedup_table.ensureUnusedCapacity(fuzz.gpa, file_list.items.len);
224 for (file_list.items) |sub_path| {
225 if (!std.mem.endsWith(u8, sub_path, ".zig")) continue;
226 const joined_path = try dir_path.join(arena, sub_path);
227 dedup_table.putAssumeCapacity(joined_path, {});
228 }
229 }
230 }
231
232 const deduped_paths = dedup_table.keys();
233 const SortContext = struct {
234 pub fn lessThan(this: @This(), lhs: Build.Cache.Path, rhs: Build.Cache.Path) bool {
235 _ = this;
236 return switch (std.mem.order(u8, lhs.root_dir.path orelse ".", rhs.root_dir.path orelse ".")) {
237 .lt => true,
238 .gt => false,
239 .eq => std.mem.lessThan(u8, lhs.sub_path, rhs.sub_path),
240 };
241 }
242 };
243 std.mem.sortUnstable(Build.Cache.Path, deduped_paths, SortContext{}, SortContext.lessThan);
244 return fuzz.mode.forever.ws.serveTarFile(req, deduped_paths);
245}
246
247pub const Previous = struct {
248 unique_runs: usize,
249 entry_points: usize,
250 sent_source_index: bool,
251 pub const init: Previous = .{
252 .unique_runs = 0,
253 .entry_points = 0,
254 .sent_source_index = false,
255 };
256};
257pub fn sendUpdate(
258 fuzz: *Fuzz,
259 socket: *std.http.Server.WebSocket,
260 prev: *Previous,
261) !void {
262 const io = fuzz.io;
263
264 try fuzz.coverage_mutex.lock(io);
265 defer fuzz.coverage_mutex.unlock(io);
266
267 const coverage_maps = fuzz.coverage_files.values();
268 if (coverage_maps.len == 0) return;
269 // TODO: handle multiple fuzz steps in the WebSocket packets
270 const coverage_map = &coverage_maps[0];
271 const cov_header: *const abi.SeenPcsHeader = @ptrCast(coverage_map.mapped_memory[0..@sizeOf(abi.SeenPcsHeader)]);
272 // TODO: this isn't sound! We need to do volatile reads of these bits rather than handing the
273 // buffer off to the kernel, because we might race with the fuzzer process[es]. This brings the
274 // whole mmap strategy into question. Incidentally, I wonder if post-writergate we could pass
275 // this data straight to the socket with sendfile...
276 const seen_pcs = cov_header.seenBits();
277 const n_runs = @atomicLoad(usize, &cov_header.n_runs, .monotonic);
278 const unique_runs = @atomicLoad(usize, &cov_header.unique_runs, .monotonic);
279 {
280 if (!prev.sent_source_index) {
281 prev.sent_source_index = true;
282 // We need to send initial context.
283 const header: abi.SourceIndexHeader = .{
284 .directories_len = @intCast(coverage_map.coverage.directories.entries.len),
285 .files_len = @intCast(coverage_map.coverage.files.entries.len),
286 .source_locations_len = @intCast(coverage_map.source_locations.len),
287 .string_bytes_len = @intCast(coverage_map.coverage.string_bytes.items.len),
288 .start_timestamp = coverage_map.start_timestamp,
289 .start_n_runs = coverage_map.start_n_runs,
290 };
291 var iovecs: [5][]const u8 = .{
292 @ptrCast(&header),
293 @ptrCast(coverage_map.coverage.directories.keys()),
294 @ptrCast(coverage_map.coverage.files.keys()),
295 @ptrCast(coverage_map.source_locations),
296 coverage_map.coverage.string_bytes.items,
297 };
298 try socket.writeMessageVec(&iovecs, .binary);
299 }
300
301 const header: abi.CoverageUpdateHeader = .{
302 .n_runs = n_runs,
303 .unique_runs = unique_runs,
304 };
305 var iovecs: [2][]const u8 = .{
306 @ptrCast(&header),
307 @ptrCast(seen_pcs),
308 };
309 try socket.writeMessageVec(&iovecs, .binary);
310
311 prev.unique_runs = unique_runs;
312 }
313
314 if (prev.entry_points != coverage_map.entry_points.items.len) {
315 const header: abi.EntryPointHeader = .init(@intCast(coverage_map.entry_points.items.len));
316 var iovecs: [2][]const u8 = .{
317 @ptrCast(&header),
318 @ptrCast(coverage_map.entry_points.items),
319 };
320 try socket.writeMessageVec(&iovecs, .binary);
321
322 prev.entry_points = coverage_map.entry_points.items.len;
323 }
324}
325
326fn coverageRun(fuzz: *Fuzz) void {
327 coverageRunCancelable(fuzz) catch |err| switch (err) {
328 error.Canceled => return,
329 };
330}
331
332fn coverageRunCancelable(fuzz: *Fuzz) Io.Cancelable!void {
333 const io = fuzz.io;
334
335 try fuzz.queue_mutex.lock(io);
336 defer fuzz.queue_mutex.unlock(io);
337
338 while (true) {
339 try fuzz.queue_cond.wait(io, &fuzz.queue_mutex);
340 for (fuzz.msg_queue.items) |msg| switch (msg) {
341 .coverage => |coverage| prepareTables(fuzz, coverage.run, coverage.id) catch |err| switch (err) {
342 error.AlreadyReported => continue,
343 error.Canceled => return,
344 else => |e| log.err("failed to prepare code coverage tables: {t}", .{e}),
345 },
346 .entry_point => |entry_point| addEntryPoint(fuzz, entry_point.coverage_id, entry_point.addr) catch |err| switch (err) {
347 error.AlreadyReported => continue,
348 error.Canceled => return,
349 else => |e| log.err("failed to prepare code coverage tables: {t}", .{e}),
350 },
351 };
352 fuzz.msg_queue.clearRetainingCapacity();
353 }
354}
355fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutOfMemory, AlreadyReported, Canceled }!void {
356 assert(fuzz.mode == .forever);
357 const ws = fuzz.mode.forever.ws;
358 const gpa = fuzz.gpa;
359 const io = fuzz.io;
360
361 try fuzz.coverage_mutex.lock(io);
362 defer fuzz.coverage_mutex.unlock(io);
363
364 const gop = try fuzz.coverage_files.getOrPut(gpa, coverage_id);
365 if (gop.found_existing) {
366 // We are fuzzing the same executable with multiple threads.
367 // Perhaps the same unit test; perhaps a different one. In any
368 // case, since the coverage file is the same, we only have to
369 // notice changes to that one file in order to learn coverage for
370 // this particular executable.
371 return;
372 }
373 errdefer _ = fuzz.coverage_files.pop();
374
375 gop.value_ptr.* = .{
376 .coverage = std.debug.Coverage.init,
377 .mapped_memory = undefined, // populated below
378 .source_locations = undefined, // populated below
379 .entry_points = .empty,
380 .start_timestamp = ws.now(),
381 .start_n_runs = undefined, // populated below
382 };
383 errdefer gop.value_ptr.coverage.deinit(gpa);
384
385 const rebuilt_exe_path = run_step.rebuilt_executable.?;
386 const target = run_step.producer.?.rootModuleTarget();
387 var debug_info = std.debug.Info.load(
388 gpa,
389 io,
390 rebuilt_exe_path,
391 &gop.value_ptr.coverage,
392 target.ofmt,
393 target.cpu.arch,
394 ) catch |err| {
395 log.err("step '{s}': failed to load debug information for '{f}': {t}", .{
396 run_step.step.name, rebuilt_exe_path, err,
397 });
398 return error.AlreadyReported;
399 };
400 defer debug_info.deinit(gpa);
401
402 const coverage_file_path: Build.Cache.Path = .{
403 .root_dir = run_step.step.owner.cache_root,
404 .sub_path = "v/" ++ std.fmt.hex(coverage_id),
405 };
406 var coverage_file = coverage_file_path.root_dir.handle.openFile(io, coverage_file_path.sub_path, .{}) catch |err| {
407 log.err("step '{s}': failed to load coverage file '{f}': {t}", .{
408 run_step.step.name, coverage_file_path, err,
409 });
410 return error.AlreadyReported;
411 };
412 defer coverage_file.close(io);
413
414 const file_size = coverage_file.length(io) catch |err| {
415 log.err("unable to check len of coverage file '{f}': {t}", .{ coverage_file_path, err });
416 return error.AlreadyReported;
417 };
418
419 const mapped_memory = std.posix.mmap(
420 null,
421 file_size,
422 .{ .READ = true },
423 .{ .TYPE = .SHARED },
424 coverage_file.handle,
425 0,
426 ) catch |err| {
427 log.err("failed to map coverage file '{f}': {t}", .{ coverage_file_path, err });
428 return error.AlreadyReported;
429 };
430 gop.value_ptr.mapped_memory = mapped_memory;
431
432 const header: *const abi.SeenPcsHeader = @ptrCast(mapped_memory[0..@sizeOf(abi.SeenPcsHeader)]);
433 const pcs = header.pcAddrs();
434 const source_locations = try gpa.alloc(Coverage.SourceLocation, pcs.len);
435 errdefer gpa.free(source_locations);
436
437 // Unfortunately the PCs array that LLVM gives us from the 8-bit PC
438 // counters feature is not sorted.
439 var sorted_pcs: std.MultiArrayList(struct { pc: u64, index: u32, sl: Coverage.SourceLocation }) = .empty;
440 defer sorted_pcs.deinit(gpa);
441 try sorted_pcs.resize(gpa, pcs.len);
442 @memcpy(sorted_pcs.items(.pc), pcs);
443 for (sorted_pcs.items(.index), 0..) |*v, i| v.* = @intCast(i);
444 sorted_pcs.sortUnstable(struct {
445 addrs: []const u64,
446
447 pub fn lessThan(ctx: @This(), a_index: usize, b_index: usize) bool {
448 return ctx.addrs[a_index] < ctx.addrs[b_index];
449 }
450 }{ .addrs = sorted_pcs.items(.pc) });
451
452 debug_info.resolveAddresses(gpa, io, sorted_pcs.items(.pc), sorted_pcs.items(.sl)) catch |err| {
453 log.err("failed to resolve addresses to source locations: {t}", .{err});
454 return error.AlreadyReported;
455 };
456
457 for (sorted_pcs.items(.index), sorted_pcs.items(.sl)) |i, sl| source_locations[i] = sl;
458 gop.value_ptr.source_locations = source_locations;
459 gop.value_ptr.start_n_runs = header.n_runs;
460
461 ws.notifyUpdate();
462}
463
464fn addEntryPoint(fuzz: *Fuzz, coverage_id: u64, addr: u64) error{ AlreadyReported, OutOfMemory, Canceled }!void {
465 const io = fuzz.io;
466
467 try fuzz.coverage_mutex.lock(io);
468 defer fuzz.coverage_mutex.unlock(io);
469
470 const coverage_map = fuzz.coverage_files.getPtr(coverage_id).?;
471 const header: *const abi.SeenPcsHeader = @ptrCast(coverage_map.mapped_memory[0..@sizeOf(abi.SeenPcsHeader)]);
472 const pcs = header.pcAddrs();
473
474 // Since this pcs list is unsorted, we must linear scan for the best index.
475 const index = i: {
476 var best: usize = 0;
477 for (pcs[1..], 1..) |elem_addr, i| {
478 if (elem_addr == addr) break :i i;
479 if (elem_addr > addr) continue;
480 if (elem_addr > pcs[best]) best = i;
481 }
482 break :i best;
483 };
484 if (index >= pcs.len) {
485 log.err("unable to find unit test entry address 0x{x} in source locations (range: 0x{x} to 0x{x})", .{
486 addr, pcs[0], pcs[pcs.len - 1],
487 });
488 return error.AlreadyReported;
489 }
490 if (false) {
491 const sl = coverage_map.source_locations[index];
492 const file_name = coverage_map.coverage.stringAt(coverage_map.coverage.fileAt(sl.file).basename);
493 if (pcs.len == 1) {
494 log.debug("server found entry point for 0x{x} at {s}:{d}:{d} - index 0 (final)", .{
495 addr, file_name, sl.line, sl.column,
496 });
497 } else if (index == 0) {
498 log.debug("server found entry point for 0x{x} at {s}:{d}:{d} - index 0 before {x}", .{
499 addr, file_name, sl.line, sl.column, pcs[index + 1],
500 });
501 } else if (index == pcs.len - 1) {
502 log.debug("server found entry point for 0x{x} at {s}:{d}:{d} - index {d} (final) after {x}", .{
503 addr, file_name, sl.line, sl.column, index, pcs[index - 1],
504 });
505 } else {
506 log.debug("server found entry point for 0x{x} at {s}:{d}:{d} - index {d} between {x} and {x}", .{
507 addr, file_name, sl.line, sl.column, index, pcs[index - 1], pcs[index + 1],
508 });
509 }
510 }
511 try coverage_map.entry_points.append(fuzz.gpa, @intCast(index));
512}
513
514pub fn waitAndPrintReport(fuzz: *Fuzz) Io.Cancelable!void {
515 assert(fuzz.mode == .limit);
516 const io = fuzz.io;
517
518 try fuzz.group.await(io);
519 fuzz.group = .init;
520
521 std.debug.print("======= FUZZING REPORT =======\n", .{});
522 for (fuzz.msg_queue.items) |msg| {
523 if (msg != .coverage) continue;
524
525 const cov = msg.coverage;
526 const coverage_file_path: std.Build.Cache.Path = .{
527 .root_dir = cov.run.step.owner.cache_root,
528 .sub_path = "v/" ++ std.fmt.hex(cov.id),
529 };
530 var coverage_file = coverage_file_path.root_dir.handle.openFile(io, coverage_file_path.sub_path, .{}) catch |err| {
531 fatal("step '{s}': failed to load coverage file '{f}': {t}", .{
532 cov.run.step.name, coverage_file_path, err,
533 });
534 };
535 defer coverage_file.close(io);
536
537 const fuzz_abi = std.Build.abi.fuzz;
538 var rbuf: [0x1000]u8 = undefined;
539 var r = coverage_file.reader(io, &rbuf);
540
541 var header: fuzz_abi.SeenPcsHeader = undefined;
542 r.interface.readSliceAll(std.mem.asBytes(&header)) catch |err| {
543 fatal("step '{s}': failed to read from coverage file '{f}': {t}", .{
544 cov.run.step.name, coverage_file_path, err,
545 });
546 };
547
548 if (header.pcs_len == 0) {
549 fatal("step '{s}': corrupted coverage file '{f}': pcs_len was zero", .{
550 cov.run.step.name, coverage_file_path,
551 });
552 }
553
554 var seen_count: usize = 0;
555 const chunk_count = fuzz_abi.SeenPcsHeader.seenElemsLen(header.pcs_len);
556 for (0..chunk_count) |_| {
557 const seen = r.interface.takeInt(usize, .little) catch |err| {
558 fatal("step '{s}': failed to read from coverage file '{f}': {t}", .{
559 cov.run.step.name, coverage_file_path, err,
560 });
561 };
562 seen_count += @popCount(seen);
563 }
564
565 const seen_f: f64 = @floatFromInt(seen_count);
566 const total_f: f64 = @floatFromInt(header.pcs_len);
567 const ratio = seen_f / total_f;
568 std.debug.print(
569 \\Step: {s}
570 \\Fuzz test: "{s}" ({x})
571 \\Runs: {} -> {}
572 \\Unique runs: {} -> {}
573 \\Coverage: {}/{} -> {}/{} ({:.02}%)
574 \\
575 , .{
576 cov.run.step.name,
577 cov.run.fuzz_tests.items[0],
578 cov.id,
579 cov.cumulative.runs,
580 header.n_runs,
581 cov.cumulative.unique,
582 header.unique_runs,
583 cov.cumulative.coverage,
584 header.pcs_len,
585 seen_count,
586 header.pcs_len,
587 ratio * 100,
588 });
589
590 std.debug.print("------------------------------\n", .{});
591 }
592 std.debug.print(
593 \\Values are accumulated across multiple runs when preserving the cache.
594 \\==============================
595 \\
596 , .{});
597}
lib/std/Build/Module.zig+99-225
......@@ -1,3 +1,11 @@
1const Module = @This();
2
3const std = @import("std");
4const assert = std.debug.assert;
5const LazyPath = std.Build.LazyPath;
6const Step = std.Build.Step;
7const ArrayList = std.ArrayList;
8
19/// The one responsible for creating this module.
210owner: *std.Build,
311root_source_file: ?LazyPath,
......@@ -65,18 +73,8 @@ pub const SystemLib = struct {
6573 preferred_link_mode: std.builtin.LinkMode,
6674 search_strategy: SystemLib.SearchStrategy,
6775
68 pub const UsePkgConfig = enum {
69 /// Don't use pkg-config, just pass -lfoo where foo is name.
70 no,
71 /// Try to get information on how to link the library from pkg-config.
72 /// If that fails, fall back to passing -lfoo where foo is name.
73 yes,
74 /// Try to get information on how to link the library from pkg-config.
75 /// If that fails, error out.
76 force,
77 };
78
79 pub const SearchStrategy = enum { paths_first, mode_first, no_fallback };
76 pub const UsePkgConfig = std.Build.Configuration.SystemLib.UsePkgConfig;
77 pub const SearchStrategy = std.Build.Configuration.SystemLib.SearchStrategy;
8078};
8179
8280pub const CSourceLanguage = enum {
......@@ -91,7 +89,8 @@ pub const CSourceLanguage = enum {
9189 /// Assembly with the C preprocessor
9290 assembly_with_preprocessor,
9391
94 pub fn internalIdentifier(self: CSourceLanguage) []const u8 {
92 /// The value passed to "-x" CLI flag of Clang.
93 pub fn clangIdentifier(self: CSourceLanguage) [:0]const u8 {
9594 return switch (self) {
9695 .c => "c",
9796 .cpp => "c++",
......@@ -119,10 +118,10 @@ pub const CSourceFile = struct {
119118 /// By default, determines language of each file individually based on its file extension
120119 language: ?CSourceLanguage = null,
121120
122 pub fn dupe(file: CSourceFile, b: *std.Build) CSourceFile {
121 pub fn dupe(file: CSourceFile, graph: *const std.Build.Graph) CSourceFile {
123122 return .{
124 .file = file.file.dupe(b),
125 .flags = b.dupeStrings(file.flags),
123 .file = file.file.dupe(graph),
124 .flags = graph.dupeStrings(file.flags),
126125 .language = file.language,
127126 };
128127 }
......@@ -146,12 +145,13 @@ pub const RcSourceFile = struct {
146145 /// as `/I <resolved path>`.
147146 include_paths: []const LazyPath = &.{},
148147
149 pub fn dupe(file: RcSourceFile, b: *std.Build) RcSourceFile {
150 const include_paths = b.allocator.alloc(LazyPath, file.include_paths.len) catch @panic("OOM");
151 for (include_paths, file.include_paths) |*dest, lazy_path| dest.* = lazy_path.dupe(b);
148 pub fn dupe(file: RcSourceFile, graph: *const std.Build.Graph) RcSourceFile {
149 const arena = graph.arena;
150 const include_paths = arena.alloc(LazyPath, file.include_paths.len) catch @panic("OOM");
151 for (include_paths, file.include_paths) |*dest, lazy_path| dest.* = lazy_path.dupe(graph);
152152 return .{
153 .file = file.file.dupe(b),
154 .flags = b.dupeStrings(file.flags),
153 .file = file.file.dupe(graph),
154 .flags = graph.dupeStrings(file.flags),
155155 .include_paths = include_paths,
156156 };
157157 }
......@@ -166,33 +166,6 @@ pub const IncludeDir = union(enum) {
166166 other_step: *Step.Compile,
167167 config_header_step: *Step.ConfigHeader,
168168 embed_path: LazyPath,
169
170 pub fn appendZigProcessFlags(
171 include_dir: IncludeDir,
172 b: *std.Build,
173 zig_args: *std.array_list.Managed([]const u8),
174 asking_step: ?*Step,
175 ) !void {
176 const flag: []const u8, const lazy_path: LazyPath = switch (include_dir) {
177 // zig fmt: off
178 .path => |lp| .{ "-I", lp },
179 .path_system => |lp| .{ "-isystem", lp },
180 .path_after => |lp| .{ "-idirafter", lp },
181 .framework_path => |lp| .{ "-F", lp },
182 .framework_path_system => |lp| .{ "-iframework", lp },
183 .config_header_step => |ch| .{ "-I", ch.getOutputDir() },
184 .other_step => |comp| .{ "-I", comp.installed_headers_include_tree.?.getDirectory() },
185 // zig fmt: on
186 .embed_path => |lazy_path| {
187 // Special case: this is a single arg.
188 const resolved = lazy_path.getPath3(b, asking_step);
189 const arg = b.fmt("--embed-dir={f}", .{resolved});
190 return zig_args.append(arg);
191 },
192 };
193 const resolved_str = try lazy_path.getPath3(b, asking_step).toString(b.graph.arena);
194 return zig_args.appendSlice(&.{ flag, resolved_str });
195 }
196169};
197170
198171pub const LinkFrameworkOptions = struct {
......@@ -268,13 +241,14 @@ pub fn init(
268241 owner: *std.Build,
269242 value: union(enum) { options: CreateOptions, existing: *const Module },
270243) void {
271 const allocator = owner.allocator;
244 const graph = owner.graph;
245 const arena = graph.arena;
272246
273247 switch (value) {
274248 .options => |options| {
275249 m.* = .{
276250 .owner = owner,
277 .root_source_file = if (options.root_source_file) |lp| lp.dupe(owner) else null,
251 .root_source_file = if (options.root_source_file) |lp| lp.dupe(graph) else null,
278252 .import_table = .empty,
279253 .resolved_target = options.target,
280254 .optimize = options.optimize,
......@@ -305,7 +279,7 @@ pub fn init(
305279 .no_builtin = options.no_builtin,
306280 };
307281
308 m.import_table.ensureUnusedCapacity(allocator, options.imports.len) catch @panic("OOM");
282 m.import_table.ensureUnusedCapacity(arena, options.imports.len) catch @panic("OOM");
309283 for (options.imports) |dep| {
310284 m.import_table.putAssumeCapacity(dep.name, dep.module);
311285 }
......@@ -317,15 +291,18 @@ pub fn init(
317291}
318292
319293pub fn create(owner: *std.Build, options: CreateOptions) *Module {
320 const m = owner.allocator.create(Module) catch @panic("OOM");
294 const graph = owner.graph;
295 const arena = graph.arena;
296 const m = arena.create(Module) catch @panic("OOM");
321297 m.init(owner, .{ .options = options });
322298 return m;
323299}
324300
325301/// Adds an existing module to be used with `@import`.
326302pub fn addImport(m: *Module, name: []const u8, module: *Module) void {
327 const b = m.owner;
328 m.import_table.put(b.allocator, b.dupe(name), module) catch @panic("OOM");
303 const graph = m.owner.graph;
304 const arena = graph.arena;
305 m.import_table.put(arena, graph.dupeString(name), module) catch @panic("OOM");
329306}
330307
331308/// Creates a new module and adds it to be used with `@import`.
......@@ -365,7 +342,8 @@ pub fn linkSystemLibrary(
365342 name: []const u8,
366343 options: LinkSystemLibraryOptions,
367344) void {
368 const b = m.owner;
345 const graph = m.owner.graph;
346 const arena = graph.arena;
369347
370348 const target = m.requireKnownTarget();
371349 if (std.zig.target.isLibCLibName(target, name)) {
......@@ -377,9 +355,9 @@ pub fn linkSystemLibrary(
377355 return;
378356 }
379357
380 m.link_objects.append(b.allocator, .{
358 m.link_objects.append(arena, .{
381359 .system_lib = .{
382 .name = b.dupe(name),
360 .name = graph.dupeString(name),
383361 .needed = options.needed,
384362 .weak = options.weak,
385363 .use_pkg_config = options.use_pkg_config,
......@@ -390,8 +368,9 @@ pub fn linkSystemLibrary(
390368}
391369
392370pub fn linkFramework(m: *Module, name: []const u8, options: LinkFrameworkOptions) void {
393 const b = m.owner;
394 m.frameworks.put(b.allocator, b.dupe(name), options) catch @panic("OOM");
371 const graph = m.owner.graph;
372 const arena = graph.arena;
373 m.frameworks.put(arena, graph.dupeString(name), options) catch @panic("OOM");
395374}
396375
397376pub const AddCSourceFilesOptions = struct {
......@@ -407,7 +386,8 @@ pub const AddCSourceFilesOptions = struct {
407386/// Handy when you have many non-Zig source files and want them all to have the same flags.
408387pub fn addCSourceFiles(m: *Module, options: AddCSourceFilesOptions) void {
409388 const b = m.owner;
410 const allocator = b.allocator;
389 const graph = m.owner.graph;
390 const arena = graph.arena;
411391
412392 for (options.files) |path| {
413393 if (std.fs.path.isAbsolute(path)) {
......@@ -418,48 +398,50 @@ pub fn addCSourceFiles(m: *Module, options: AddCSourceFilesOptions) void {
418398 }
419399 }
420400
421 const c_source_files = allocator.create(CSourceFiles) catch @panic("OOM");
401 const c_source_files = arena.create(CSourceFiles) catch @panic("OOM");
422402 c_source_files.* = .{
423403 .root = options.root orelse b.path(""),
424404 .files = b.dupeStrings(options.files),
425405 .flags = b.dupeStrings(options.flags),
426406 .language = options.language,
427407 };
428 m.link_objects.append(allocator, .{ .c_source_files = c_source_files }) catch @panic("OOM");
408 m.link_objects.append(arena, .{ .c_source_files = c_source_files }) catch @panic("OOM");
429409}
430410
431411pub fn addCSourceFile(m: *Module, source: CSourceFile) void {
432 const b = m.owner;
433 const allocator = b.allocator;
434 const c_source_file = allocator.create(CSourceFile) catch @panic("OOM");
435 c_source_file.* = source.dupe(b);
436 m.link_objects.append(allocator, .{ .c_source_file = c_source_file }) catch @panic("OOM");
412 const graph = m.owner.graph;
413 const arena = graph.arena;
414 const c_source_file = arena.create(CSourceFile) catch @panic("OOM");
415 c_source_file.* = source.dupe(graph);
416 m.link_objects.append(arena, .{ .c_source_file = c_source_file }) catch @panic("OOM");
437417}
438418
439419/// Resource files must have the extension `.rc`.
440420/// Can be called regardless of target. The .rc file will be ignored
441421/// if the target object format does not support embedded resources.
442422pub fn addWin32ResourceFile(m: *Module, source: RcSourceFile) void {
443 const b = m.owner;
444 const allocator = b.allocator;
423 const graph = m.owner.graph;
424 const arena = graph.arena;
445425 const target = m.requireKnownTarget();
446426 // Only the PE/COFF format has a Resource Table, so for any other target
447427 // the resource file is ignored.
448428 if (target.ofmt != .coff) return;
449429
450 const rc_source_file = allocator.create(RcSourceFile) catch @panic("OOM");
451 rc_source_file.* = source.dupe(b);
452 m.link_objects.append(allocator, .{ .win32_resource_file = rc_source_file }) catch @panic("OOM");
430 const rc_source_file = arena.create(RcSourceFile) catch @panic("OOM");
431 rc_source_file.* = source.dupe(graph);
432 m.link_objects.append(arena, .{ .win32_resource_file = rc_source_file }) catch @panic("OOM");
453433}
454434
455435pub fn addAssemblyFile(m: *Module, source: LazyPath) void {
456 const b = m.owner;
457 m.link_objects.append(b.allocator, .{ .assembly_file = source.dupe(b) }) catch @panic("OOM");
436 const graph = m.owner.graph;
437 const arena = graph.arena;
438 m.link_objects.append(arena, .{ .assembly_file = source.dupe(graph) }) catch @panic("OOM");
458439}
459440
460441pub fn addObjectFile(m: *Module, object: LazyPath) void {
461 const b = m.owner;
462 m.link_objects.append(b.allocator, .{ .static_path = object.dupe(b) }) catch @panic("OOM");
442 const graph = m.owner.graph;
443 const arena = graph.arena;
444 m.link_objects.append(arena, .{ .static_path = object.dupe(graph) }) catch @panic("OOM");
463445}
464446
465447pub fn addObject(m: *Module, object: *Step.Compile) void {
......@@ -473,55 +455,63 @@ pub fn linkLibrary(m: *Module, library: *Step.Compile) void {
473455}
474456
475457pub fn addAfterIncludePath(m: *Module, lazy_path: LazyPath) void {
476 const b = m.owner;
477 m.include_dirs.append(b.allocator, .{ .path_after = lazy_path.dupe(b) }) catch @panic("OOM");
458 const graph = m.owner.graph;
459 const arena = graph.arena;
460 m.include_dirs.append(arena, .{ .path_after = lazy_path.dupe(graph) }) catch @panic("OOM");
478461}
479462
480463pub fn addSystemIncludePath(m: *Module, lazy_path: LazyPath) void {
481 const b = m.owner;
482 m.include_dirs.append(b.allocator, .{ .path_system = lazy_path.dupe(b) }) catch @panic("OOM");
464 const graph = m.owner.graph;
465 const arena = graph.arena;
466 m.include_dirs.append(arena, .{ .path_system = lazy_path.dupe(graph) }) catch @panic("OOM");
483467}
484468
485469pub fn addIncludePath(m: *Module, lazy_path: LazyPath) void {
486 const b = m.owner;
487 m.include_dirs.append(b.allocator, .{ .path = lazy_path.dupe(b) }) catch @panic("OOM");
470 const graph = m.owner.graph;
471 const arena = graph.arena;
472 m.include_dirs.append(arena, .{ .path = lazy_path.dupe(graph) }) catch @panic("OOM");
488473}
489474
490475pub fn addConfigHeader(m: *Module, config_header: *Step.ConfigHeader) void {
491 const allocator = m.owner.allocator;
492 m.include_dirs.append(allocator, .{ .config_header_step = config_header }) catch @panic("OOM");
476 const graph = m.owner.graph;
477 const arena = graph.arena;
478 m.include_dirs.append(arena, .{ .config_header_step = config_header }) catch @panic("OOM");
493479}
494480
495481pub fn addSystemFrameworkPath(m: *Module, directory_path: LazyPath) void {
496 const b = m.owner;
497 m.include_dirs.append(b.allocator, .{ .framework_path_system = directory_path.dupe(b) }) catch
498 @panic("OOM");
482 const graph = m.owner.graph;
483 const arena = graph.arena;
484 m.include_dirs.append(arena, .{ .framework_path_system = directory_path.dupe(graph) }) catch @panic("OOM");
499485}
500486
501487pub fn addFrameworkPath(m: *Module, directory_path: LazyPath) void {
502 const b = m.owner;
503 m.include_dirs.append(b.allocator, .{ .framework_path = directory_path.dupe(b) }) catch
504 @panic("OOM");
488 const graph = m.owner.graph;
489 const arena = graph.arena;
490 m.include_dirs.append(arena, .{ .framework_path = directory_path.dupe(graph) }) catch @panic("OOM");
505491}
506492
507493pub fn addEmbedPath(m: *Module, lazy_path: LazyPath) void {
508 const b = m.owner;
509 m.include_dirs.append(b.allocator, .{ .embed_path = lazy_path.dupe(b) }) catch @panic("OOM");
494 const graph = m.owner.graph;
495 const arena = graph.arena;
496 m.include_dirs.append(arena, .{ .embed_path = lazy_path.dupe(graph) }) catch @panic("OOM");
510497}
511498
512499pub fn addLibraryPath(m: *Module, directory_path: LazyPath) void {
513 const b = m.owner;
514 m.lib_paths.append(b.allocator, directory_path.dupe(b)) catch @panic("OOM");
500 const graph = m.owner.graph;
501 const arena = graph.arena;
502 m.lib_paths.append(arena, directory_path.dupe(graph)) catch @panic("OOM");
515503}
516504
517505pub fn addRPath(m: *Module, directory_path: LazyPath) void {
518 const b = m.owner;
519 m.rpaths.append(b.allocator, .{ .lazy_path = directory_path.dupe(b) }) catch @panic("OOM");
506 const graph = m.owner.graph;
507 const arena = graph.arena;
508 m.rpaths.append(arena, .{ .lazy_path = directory_path.dupe(graph) }) catch @panic("OOM");
520509}
521510
522511pub fn addRPathSpecial(m: *Module, bytes: []const u8) void {
523 const b = m.owner;
524 m.rpaths.append(b.allocator, .{ .special = b.dupe(bytes) }) catch @panic("OOM");
512 const graph = m.owner.graph;
513 const arena = graph.arena;
514 m.rpaths.append(arena, .{ .special = graph.dupeString(bytes) }) catch @panic("OOM");
525515}
526516
527517/// Equvialent to the following C code, applied to all C source files owned by
......@@ -532,130 +522,23 @@ pub fn addRPathSpecial(m: *Module, bytes: []const u8) void {
532522/// `name` and `value` need not live longer than the function call.
533523pub fn addCMacro(m: *Module, name: []const u8, value: []const u8) void {
534524 const b = m.owner;
535 m.c_macros.append(b.allocator, b.fmt("-D{s}={s}", .{ name, value })) catch @panic("OOM");
536}
537
538pub fn appendZigProcessFlags(
539 m: *Module,
540 zig_args: *std.array_list.Managed([]const u8),
541 asking_step: ?*Step,
542) !void {
543 const b = m.owner;
544
545 try addFlag(zig_args, m.strip, "-fstrip", "-fno-strip");
546 try addFlag(zig_args, m.single_threaded, "-fsingle-threaded", "-fno-single-threaded");
547 try addFlag(zig_args, m.stack_check, "-fstack-check", "-fno-stack-check");
548 try addFlag(zig_args, m.stack_protector, "-fstack-protector", "-fno-stack-protector");
549 try addFlag(zig_args, m.omit_frame_pointer, "-fomit-frame-pointer", "-fno-omit-frame-pointer");
550 try addFlag(zig_args, m.error_tracing, "-ferror-tracing", "-fno-error-tracing");
551 try addFlag(zig_args, m.sanitize_thread, "-fsanitize-thread", "-fno-sanitize-thread");
552 try addFlag(zig_args, m.fuzz, "-ffuzz", "-fno-fuzz");
553 try addFlag(zig_args, m.valgrind, "-fvalgrind", "-fno-valgrind");
554 try addFlag(zig_args, m.pic, "-fPIC", "-fno-PIC");
555 try addFlag(zig_args, m.red_zone, "-mred-zone", "-mno-red-zone");
556 try addFlag(zig_args, m.no_builtin, "-fno-builtin", "-fbuiltin");
557
558 if (m.sanitize_c) |sc| switch (sc) {
559 .off => try zig_args.append("-fno-sanitize-c"),
560 .trap => try zig_args.append("-fsanitize-c=trap"),
561 .full => try zig_args.append("-fsanitize-c=full"),
562 };
563
564 if (m.dwarf_format) |dwarf_format| {
565 try zig_args.append(switch (dwarf_format) {
566 .@"32" => "-gdwarf32",
567 .@"64" => "-gdwarf64",
568 });
569 }
570
571 if (m.unwind_tables) |unwind_tables| {
572 try zig_args.append(switch (unwind_tables) {
573 .none => "-fno-unwind-tables",
574 .sync => "-funwind-tables",
575 .async => "-fasync-unwind-tables",
576 });
577 }
578
579 try zig_args.ensureUnusedCapacity(1);
580 if (m.optimize) |optimize| switch (optimize) {
581 .Debug => zig_args.appendAssumeCapacity("-ODebug"),
582 .ReleaseSmall => zig_args.appendAssumeCapacity("-OReleaseSmall"),
583 .ReleaseFast => zig_args.appendAssumeCapacity("-OReleaseFast"),
584 .ReleaseSafe => zig_args.appendAssumeCapacity("-OReleaseSafe"),
585 };
586
587 if (m.code_model != .default) {
588 try zig_args.append("-mcmodel");
589 try zig_args.append(@tagName(m.code_model));
590 }
591
592 if (m.resolved_target) |*target| {
593 // Communicate the query via CLI since it's more compact.
594 if (!target.query.isNative()) {
595 try zig_args.appendSlice(&.{
596 "-target", try target.query.zigTriple(b.allocator),
597 "-mcpu", try target.query.serializeCpuAlloc(b.allocator),
598 });
599 if (target.query.dynamic_linker) |*dynamic_linker| {
600 if (dynamic_linker.get()) |dynamic_linker_path| {
601 try zig_args.append("--dynamic-linker");
602 try zig_args.append(dynamic_linker_path);
603 } else {
604 try zig_args.append("--no-dynamic-linker");
605 }
606 }
607 }
608 }
609
610 for (m.export_symbol_names) |symbol_name| {
611 try zig_args.append(b.fmt("--export={s}", .{symbol_name}));
612 }
613
614 for (m.include_dirs.items) |include_dir| {
615 try include_dir.appendZigProcessFlags(b, zig_args, asking_step);
616 }
617
618 try zig_args.appendSlice(m.c_macros.items);
619
620 try zig_args.ensureUnusedCapacity(2 * m.lib_paths.items.len);
621 for (m.lib_paths.items) |lib_path| {
622 zig_args.appendAssumeCapacity("-L");
623 zig_args.appendAssumeCapacity(lib_path.getPath2(b, asking_step));
624 }
625
626 try zig_args.ensureUnusedCapacity(2 * m.rpaths.items.len);
627 for (m.rpaths.items) |rpath| switch (rpath) {
628 .lazy_path => |lp| {
629 zig_args.appendAssumeCapacity("-rpath");
630 zig_args.appendAssumeCapacity(lp.getPath2(b, asking_step));
631 },
632 .special => |bytes| {
633 zig_args.appendAssumeCapacity("-rpath");
634 zig_args.appendAssumeCapacity(bytes);
635 },
636 };
637}
638
639fn addFlag(
640 args: *std.array_list.Managed([]const u8),
641 opt: ?bool,
642 then_name: []const u8,
643 else_name: []const u8,
644) !void {
645 const cond = opt orelse return;
646 return args.append(if (cond) then_name else else_name);
525 const graph = m.owner.graph;
526 const arena = graph.arena;
527 m.c_macros.append(arena, b.fmt("-D{s}={s}", .{ name, value })) catch @panic("OOM");
647528}
648529
649530fn linkLibraryOrObject(m: *Module, other: *Step.Compile) void {
650 const allocator = m.owner.allocator;
531 const graph = m.owner.graph;
532 const arena = graph.arena;
533
651534 _ = other.getEmittedBin(); // Indicate there is a dependency on the outputted binary.
652535
653536 if (other.rootModuleTarget().os.tag == .windows and other.isDynamicLibrary()) {
654537 _ = other.getEmittedImplib(); // Indicate dependency on the outputted implib.
655538 }
656539
657 m.link_objects.append(allocator, .{ .other_step = other }) catch @panic("OOM");
658 m.include_dirs.append(allocator, .{ .other_step = other }) catch @panic("OOM");
540 m.link_objects.append(arena, .{ .other_step = other }) catch @panic("OOM");
541 m.include_dirs.append(arena, .{ .other_step = other }) catch @panic("OOM");
659542}
660543
661544fn requireKnownTarget(m: *Module) *const std.Target {
......@@ -670,11 +553,9 @@ pub const Graph = struct {
670553 names: []const []const u8,
671554};
672555
673/// Intended to be used during the make phase only.
674///
675/// Given that `root` is the root `Module` of a compilation, return all `Module`s
676/// in the module graph, including `root` itself. `root` is guaranteed to be the
677/// first module in the returned slice.
556/// Given that `root` is the root `Module` of a compilation, return all
557/// `Module` in the module graph, including `root` itself. `root` is guaranteed
558/// to be the first module in the returned slice.
678559pub fn getGraph(root: *Module) Graph {
679560 if (root.cached_graph.modules.len != 0) {
680561 return root.cached_graph;
......@@ -703,10 +584,3 @@ pub fn getGraph(root: *Module) Graph {
703584 root.cached_graph = result;
704585 return result;
705586}
706
707const Module = @This();
708const std = @import("std");
709const assert = std.debug.assert;
710const LazyPath = std.Build.LazyPath;
711const Step = std.Build.Step;
712const ArrayList = std.ArrayList;
lib/std/Build/Step.zig+43-910
......@@ -1,33 +1,15 @@
11const Step = @This();
2const builtin = @import("builtin");
32
43const std = @import("../std.zig");
5const Io = std.Io;
64const Build = std.Build;
7const Allocator = std.mem.Allocator;
85const assert = std.debug.assert;
9const Cache = Build.Cache;
10const Path = Cache.Path;
11const ArrayList = std.ArrayList;
6const Configuration = std.Build.Configuration;
127
13id: Id,
8tag: Configuration.Step.Tag,
149name: []const u8,
1510owner: *Build,
16makeFn: MakeFn,
1711
18dependencies: std.array_list.Managed(*Step),
19/// This field is empty during execution of the user's build script, and
20/// then populated during dependency loop checking in the build runner.
21dependants: ArrayList(*Step),
22/// Collects the set of files that retrigger this step to run.
23///
24/// This is used by the build system's implementation of `--watch` but it can
25/// also be potentially useful for IDEs to know what effects editing a
26/// particular file has.
27///
28/// Populated within `make`. Implementation may choose to clear and repopulate,
29/// retain previous value, or update.
30inputs: Inputs,
12dependencies: std.ArrayList(*Step),
3113
3214/// Set this field to declare an upper bound on the amount of bytes of memory it will
3315/// take to run the step. Zero means no limit.
......@@ -50,181 +32,60 @@ inputs: Inputs,
5032/// total system memory available.
5133max_rss: usize,
5234
53state: State,
54pending_deps: u32,
55
56result_error_msgs: ArrayList([]const u8),
57result_error_bundle: std.zig.ErrorBundle,
58result_stderr: []const u8,
59result_cached: bool,
60result_duration_ns: ?u64,
61/// 0 means unavailable or not reported.
62result_peak_rss: usize,
63/// If the step is failed and this field is populated, this is the command which failed.
64/// This field may be populated even if the step succeeded.
65result_failed_command: ?[]const u8,
66test_results: TestResults,
67
6835/// The return address associated with creation of this step that can be useful
6936/// to print along with debugging messages.
7037debug_stack_trace: std.debug.StackTrace,
7138
72pub const TestResults = struct {
73 /// The total number of tests in the step. Every test has a "status" from the following:
74 /// * passed
75 /// * skipped
76 /// * failed cleanly
77 /// * crashed
78 /// * timed out
79 test_count: u32 = 0,
80
81 /// The number of tests which were skipped (`error.SkipZigTest`).
82 skip_count: u32 = 0,
83 /// The number of tests which failed cleanly.
84 fail_count: u32 = 0,
85 /// The number of tests which terminated unexpectedly, i.e. crashed.
86 crash_count: u32 = 0,
87 /// The number of tests which timed out.
88 timeout_count: u32 = 0,
89
90 /// The number of detected memory leaks. The associated test may still have passed; indeed, *all*
91 /// individual tests may have passed. However, the step as a whole fails if any test has leaks.
92 leak_count: u32 = 0,
93 /// The number of detected error logs. The associated test may still have passed; indeed, *all*
94 /// individual tests may have passed. However, the step as a whole fails if any test logs errors.
95 log_err_count: u32 = 0,
96
97 pub fn isSuccess(tr: TestResults) bool {
98 // all steps are success or skip
99 return tr.fail_count == 0 and
100 tr.crash_count == 0 and
101 tr.timeout_count == 0 and
102 // no (otherwise successful) step leaked memory or logged errors
103 tr.leak_count == 0 and
104 tr.log_err_count == 0;
105 }
106
107 /// Computes the number of tests which passed from the other values.
108 pub fn passCount(tr: TestResults) u32 {
109 return tr.test_count - tr.skip_count - tr.fail_count - tr.crash_count - tr.timeout_count;
110 }
111};
112
113pub const MakeOptions = struct {
114 progress_node: std.Progress.Node,
115 watch: bool,
116 web_server: ?*Build.WebServer,
117 /// If set, this is a timeout to enforce on all individual unit tests, in nanoseconds.
118 unit_test_timeout_ns: ?u64,
119 /// Not to be confused with `Build.allocator`, which is an alias of `Build.graph.arena`.
120 gpa: Allocator,
121};
122
123pub const MakeFn = *const fn (step: *Step, options: MakeOptions) anyerror!void;
124
125pub const State = enum {
126 precheck_unstarted,
127 precheck_started,
128 /// This is also used to indicate "dirty" steps that have been modified
129 /// after a previous build completed, in which case, the step may or may
130 /// not have been completed before. Either way, one or more of its direct
131 /// file system inputs have been modified, meaning that the step needs to
132 /// be re-evaluated.
133 precheck_done,
134 dependency_failure,
135 success,
136 failure,
137 /// This state indicates that the step did not complete, however, it also did not fail,
138 /// and it is safe to continue executing its dependencies.
139 skipped,
140 /// This step was skipped because it specified a max_rss that exceeded the runner's maximum.
141 /// It is not safe to run its dependencies.
142 skipped_oom,
143};
144
145pub const Id = enum {
146 top_level,
147 compile,
148 install_artifact,
149 install_file,
150 install_dir,
151 remove_dir,
152 fail,
153 fmt,
154 translate_c,
155 write_file,
156 update_source_files,
157 run,
158 check_file,
159 check_object,
160 config_header,
161 objcopy,
162 options,
163 custom,
164
165 pub fn Type(comptime id: Id) type {
166 return switch (id) {
167 .top_level => Build.TopLevelStep,
168 .compile => Compile,
169 .install_artifact => InstallArtifact,
170 .install_file => InstallFile,
171 .install_dir => InstallDir,
172 .fail => Fail,
173 .fmt => Fmt,
174 .translate_c => TranslateC,
175 .write_file => WriteFile,
176 .update_source_files => UpdateSourceFiles,
177 .run => Run,
178 .check_file => CheckFile,
179 .config_header => ConfigHeader,
180 .objcopy => ObjCopy,
181 .options => Options,
182 .custom => @compileError("no type available for custom step"),
183 };
184 }
185};
39pub const Tag = Configuration.Step.Tag;
40
41pub fn Type(comptime tag: Tag) type {
42 return switch (tag) {
43 .check_file => CheckFile,
44 .compile => Compile,
45 .config_header => ConfigHeader,
46 .fail => Fail,
47 .find_program => FindProgram,
48 .fmt => Fmt,
49 .install_artifact => InstallArtifact,
50 .install_dir => InstallDir,
51 .install_file => InstallFile,
52 .obj_copy => ObjCopy,
53 .options => Options,
54 .run => Run,
55 .top_level => TopLevel,
56 .translate_c => TranslateC,
57 .update_source_files => UpdateSourceFiles,
58 .write_file => WriteFile,
59 };
60}
18661
18762pub const CheckFile = @import("Step/CheckFile.zig");
63pub const Compile = @import("Step/Compile.zig");
18864pub const ConfigHeader = @import("Step/ConfigHeader.zig");
18965pub const Fail = @import("Step/Fail.zig");
66pub const FindProgram = @import("Step/FindProgram.zig");
19067pub const Fmt = @import("Step/Fmt.zig");
19168pub const InstallArtifact = @import("Step/InstallArtifact.zig");
19269pub const InstallDir = @import("Step/InstallDir.zig");
19370pub const InstallFile = @import("Step/InstallFile.zig");
19471pub const ObjCopy = @import("Step/ObjCopy.zig");
195pub const Compile = @import("Step/Compile.zig");
19672pub const Options = @import("Step/Options.zig");
19773pub const Run = @import("Step/Run.zig");
19874pub const TranslateC = @import("Step/TranslateC.zig");
199pub const WriteFile = @import("Step/WriteFile.zig");
20075pub const UpdateSourceFiles = @import("Step/UpdateSourceFiles.zig");
76pub const WriteFile = @import("Step/WriteFile.zig");
20177
202pub const Inputs = struct {
203 table: Table,
204
205 pub const init: Inputs = .{
206 .table = .{},
207 };
208
209 pub const Table = std.ArrayHashMapUnmanaged(Build.Cache.Path, Files, Build.Cache.Path.TableAdapter, false);
210 /// The special file name "." means any changes inside the directory.
211 pub const Files = ArrayList([]const u8);
212
213 pub fn populated(inputs: *Inputs) bool {
214 return inputs.table.count() != 0;
215 }
78pub const TopLevel = struct {
79 pub const base_tag: Step.Tag = .top_level;
21680
217 pub fn clear(inputs: *Inputs, gpa: Allocator) void {
218 for (inputs.table.values()) |*files| files.deinit(gpa);
219 inputs.table.clearRetainingCapacity();
220 }
81 step: Step,
82 description: []const u8,
22183};
22284
22385pub const StepOptions = struct {
224 id: Id,
86 tag: Tag,
22587 name: []const u8,
22688 owner: *Build,
227 makeFn: MakeFn = makeNoOp,
22889 first_ret_addr: ?usize = null,
22990 max_rss: usize = 0,
23091};
......@@ -233,97 +94,31 @@ pub fn init(options: StepOptions) Step {
23394 const arena = options.owner.allocator;
23495
23596 return .{
236 .id = options.id,
97 .tag = options.tag,
23798 .name = arena.dupe(u8, options.name) catch @panic("OOM"),
23899 .owner = options.owner,
239 .makeFn = options.makeFn,
240 .dependencies = std.array_list.Managed(*Step).init(arena),
241 .dependants = .empty,
242 .inputs = Inputs.init,
243 .state = .precheck_unstarted,
244 .pending_deps = undefined, // initialized by build runner
100 .dependencies = .empty,
245101 .max_rss = options.max_rss,
246102 .debug_stack_trace = blk: {
247103 const addr_buf = arena.alloc(usize, options.owner.debug_stack_frames_count) catch @panic("OOM");
248104 const first_ret_addr = options.first_ret_addr orelse @returnAddress();
249105 break :blk std.debug.captureCurrentStackTrace(.{ .first_address = first_ret_addr }, addr_buf);
250106 },
251 .result_error_msgs = .empty,
252 .result_error_bundle = std.zig.ErrorBundle.empty,
253 .result_stderr = "",
254 .result_cached = false,
255 .result_duration_ns = null,
256 .result_peak_rss = 0,
257 .result_failed_command = null,
258 .test_results = .{},
259 };
260}
261
262/// If the Step's `make` function reports `error.MakeFailed`, it indicates they
263/// have already reported the error. Otherwise, we add a simple error report
264/// here.
265pub fn make(s: *Step, options: MakeOptions) error{ MakeFailed, MakeSkipped }!void {
266 const arena = s.owner.allocator;
267 const graph = s.owner.graph;
268 const io = graph.io;
269
270 var start_ts: ?Io.Timestamp = t: {
271 if (!graph.time_report) break :t null;
272 if (s.id == .compile) break :t null;
273 if (s.id == .run and s.cast(Run).?.stdio == .zig_test) break :t null;
274 break :t Io.Clock.awake.now(io);
275 };
276 const make_result = s.makeFn(s, options);
277 if (start_ts) |*ts| {
278 const duration = ts.untilNow(io, .awake);
279 options.web_server.?.updateTimeReportGeneric(s, duration);
280 }
281
282 make_result catch |err| switch (err) {
283 error.MakeFailed, error.MakeSkipped => |e| return e,
284 else => {
285 s.result_error_msgs.append(arena, @errorName(err)) catch @panic("OOM");
286 return error.MakeFailed;
287 },
288107 };
289
290 if (!s.test_results.isSuccess()) {
291 return error.MakeFailed;
292 }
293
294 if (s.max_rss != 0 and s.result_peak_rss > s.max_rss) {
295 const msg = std.fmt.allocPrint(arena, "memory usage peaked at {0B:.2} ({0d} bytes), exceeding the declared upper bound of {1B:.2} ({1d} bytes)", .{
296 s.result_peak_rss, s.max_rss,
297 }) catch @panic("OOM");
298 s.result_error_msgs.append(arena, msg) catch @panic("OOM");
299 }
300108}
301109
302110pub fn dependOn(step: *Step, other: *Step) void {
303 step.dependencies.append(other) catch @panic("OOM");
304}
305
306fn makeNoOp(step: *Step, options: MakeOptions) anyerror!void {
307 _ = options;
308
309 var all_cached = true;
310
311 for (step.dependencies.items) |dep| {
312 all_cached = all_cached and dep.result_cached;
313 }
314
315 step.result_cached = all_cached;
111 const arena = step.owner.allocator;
112 step.dependencies.append(arena, other) catch @panic("OOM");
316113}
317114
318115pub fn cast(step: *Step, comptime T: type) ?*T {
319 if (step.id == T.base_id) {
320 return @fieldParentPtr("step", step);
321 }
116 if (step.tag == T.base_tag) return @fieldParentPtr("step", step);
322117 return null;
323118}
324119
325120/// For debugging purposes, prints identifying information about this Step.
326pub fn dump(step: *Step, t: Io.Terminal) void {
121pub fn dump(step: *Step, t: std.Io.Terminal) void {
327122 const w = t.writer;
328123 if (step.debug_stack_trace.return_addresses.len > 0) {
329124 w.print("name: '{s}'. creation stack trace:\n", .{step.name}) catch {};
......@@ -337,682 +132,20 @@ pub fn dump(step: *Step, t: Io.Terminal) void {
337132 }
338133}
339134
340/// Populates `s.result_failed_command`.
341pub fn captureChildProcess(
342 s: *Step,
343 gpa: Allocator,
344 progress_node: std.Progress.Node,
345 argv: []const []const u8,
346) !std.process.RunResult {
347 const graph = s.owner.graph;
348 const arena = graph.arena;
349 const io = graph.io;
350
351 // If an error occurs, it's happened in this command:
352 assert(s.result_failed_command == null);
353 s.result_failed_command = try allocPrintCmd(gpa, .inherit, null, argv);
354
355 try handleChildProcUnsupported(s);
356 try handleVerbose(s.owner, .inherit, argv);
357
358 const result = std.process.run(arena, io, .{
359 .argv = argv,
360 .environ_map = &graph.environ_map,
361 .progress_node = progress_node,
362 }) catch |err| return s.fail("failed to run {s}: {t}", .{ argv[0], err });
363
364 if (result.stderr.len > 0) {
365 try s.result_error_msgs.append(arena, result.stderr);
366 }
367
368 return result;
369}
370
371pub fn fail(step: *Step, comptime fmt: []const u8, args: anytype) error{ OutOfMemory, MakeFailed } {
372 try step.addError(fmt, args);
373 return error.MakeFailed;
374}
375
376pub fn addError(step: *Step, comptime fmt: []const u8, args: anytype) error{OutOfMemory}!void {
377 const arena = step.owner.allocator;
378 const msg = try std.fmt.allocPrint(arena, fmt, args);
379 try step.result_error_msgs.append(arena, msg);
380}
381
382pub const ZigProcess = struct {
383 child: std.process.Child,
384 multi_reader_buffer: Io.File.MultiReader.Buffer(2),
385 multi_reader: Io.File.MultiReader,
386 progress_ipc_index: ?if (std.Progress.have_ipc) std.Progress.Ipc.Index else noreturn,
387
388 pub const StreamEnum = enum { stdout, stderr };
389
390 pub fn saveState(zp: *ZigProcess, prog_node: std.Progress.Node) void {
391 zp.progress_ipc_index = if (std.Progress.have_ipc) prog_node.takeIpcIndex() else null;
392 }
393
394 pub fn deinit(zp: *ZigProcess, io: Io) void {
395 zp.child.kill(io);
396 zp.multi_reader.deinit();
397 zp.* = undefined;
398 }
399};
400
401/// Assumes that argv contains `--listen=-` and that the process being spawned
402/// is the zig compiler - the same version that compiled the build runner.
403/// Populates `s.result_failed_command`.
404pub fn evalZigProcess(
405 s: *Step,
406 argv: []const []const u8,
407 prog_node: std.Progress.Node,
408 watch: bool,
409 web_server: ?*Build.WebServer,
410 gpa: Allocator,
411) !?Path {
412 const b = s.owner;
413 const io = b.graph.io;
414
415 // If an error occurs, it's happened in this command:
416 assert(s.result_failed_command == null);
417 s.result_failed_command = try allocPrintCmd(gpa, .inherit, null, argv);
418
419 if (s.getZigProcess()) |zp| update: {
420 assert(watch);
421 if (zp.progress_ipc_index) |ipc_index| prog_node.setIpcIndex(ipc_index);
422 zp.progress_ipc_index = null;
423 var exited = false;
424 defer if (exited) {
425 s.cast(Compile).?.zig_process = null;
426 zp.deinit(io);
427 gpa.destroy(zp);
428 } else zp.saveState(prog_node);
429 const result = zigProcessUpdate(s, zp, watch, web_server, gpa) catch |err| switch (err) {
430 error.BrokenPipe, error.EndOfStream => |reason| {
431 std.log.info("{s} restart required: {t}", .{ argv[0], reason });
432 // Process restart required.
433 const term = zp.child.wait(io) catch |e| {
434 return s.fail("unable to wait for {s}: {t}", .{ argv[0], e });
435 };
436 _ = term;
437 exited = true;
438 break :update;
439 },
440 else => |e| return e,
441 };
442
443 if (s.result_error_bundle.errorMessageCount() > 0) {
444 return s.fail("{d} compilation errors", .{s.result_error_bundle.errorMessageCount()});
445 }
446
447 if (s.result_error_msgs.items.len > 0 and result == null) {
448 // Crash detected.
449 const term = zp.child.wait(io) catch |e| {
450 return s.fail("unable to wait for {s}: {t}", .{ argv[0], e });
451 };
452 s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0;
453 exited = true;
454 try handleChildProcessTerm(s, term);
455 return error.MakeFailed;
456 }
457
458 return result;
459 }
460 assert(argv.len != 0);
461
462 try handleChildProcUnsupported(s);
463 try handleVerbose(s.owner, .inherit, argv);
464
465 const zp = try gpa.create(ZigProcess);
466 defer if (!watch) gpa.destroy(zp);
467
468 zp.child = std.process.spawn(io, .{
469 .argv = argv,
470 .environ_map = &b.graph.environ_map,
471 .stdin = .pipe,
472 .stdout = .pipe,
473 .stderr = .pipe,
474 .request_resource_usage_statistics = true,
475 .progress_node = prog_node,
476 }) catch |err| return s.fail("failed to spawn zig compiler {s}: {t}", .{ argv[0], err });
477
478 zp.multi_reader.init(gpa, io, zp.multi_reader_buffer.toStreams(), &.{
479 zp.child.stdout.?, zp.child.stderr.?,
480 });
481 if (watch) s.cast(Compile).?.zig_process = zp;
482 defer if (!watch) zp.deinit(io);
483
484 const result = result: {
485 defer if (watch) zp.saveState(prog_node);
486 break :result try zigProcessUpdate(s, zp, watch, web_server, gpa);
487 };
488
489 if (!watch) {
490 // Send EOF to stdin.
491 zp.child.stdin.?.close(io);
492 zp.child.stdin = null;
493
494 const term = zp.child.wait(io) catch |err| {
495 return s.fail("unable to wait for {s}: {t}", .{ argv[0], err });
496 };
497 s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0;
498
499 // Special handling for Compile step that is expecting compile errors.
500 if (s.cast(Compile)) |compile| switch (term) {
501 .exited => {
502 // Note that the exit code may be 0 in this case due to the
503 // compiler server protocol.
504 if (compile.expect_errors != null) {
505 return error.NeedCompileErrorCheck;
506 }
507 },
508 else => {},
509 };
510
511 try handleChildProcessTerm(s, term);
512 }
513
514 if (s.result_error_bundle.errorMessageCount() > 0) {
515 return s.fail("{d} compilation errors", .{s.result_error_bundle.errorMessageCount()});
516 }
517
518 return result;
519}
520
521/// Wrapper around `Io.Dir.updateFile` that handles verbose and error output.
522pub fn installFile(s: *Step, src_lazy_path: Build.LazyPath, dest_path: []const u8) !Io.Dir.PrevStatus {
523 const b = s.owner;
524 const io = b.graph.io;
525 const src_path = src_lazy_path.getPath3(b, s);
526 try handleVerbose(b, .inherit, &.{ "install", "-C", b.fmt("{f}", .{src_path}), dest_path });
527 return Io.Dir.updateFile(src_path.root_dir.handle, io, src_path.sub_path, .cwd(), dest_path, .{}) catch |err|
528 return s.fail("unable to update file from '{f}' to '{s}': {t}", .{ src_path, dest_path, err });
529}
530
531/// Wrapper around `Io.Dir.createDirPathStatus` that handles verbose and error output.
532pub fn installDir(s: *Step, dest_path: []const u8) !Io.Dir.CreatePathStatus {
533 const b = s.owner;
534 const io = b.graph.io;
535 try handleVerbose(b, .inherit, &.{ "install", "-d", dest_path });
536 return Io.Dir.cwd().createDirPathStatus(io, dest_path, .default_dir) catch |err|
537 return s.fail("unable to create dir '{s}': {t}", .{ dest_path, err });
538}
539
540fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build.WebServer, gpa: Allocator) !?Path {
541 const b = s.owner;
542 const arena = b.allocator;
543 const io = b.graph.io;
544
545 const start_ts = Io.Clock.awake.now(io);
546
547 try sendMessage(io, zp.child.stdin.?, .update);
548 if (!watch) try sendMessage(io, zp.child.stdin.?, .exit);
549
550 var result: ?Path = null;
551 var eos_err: error{EndOfStream}!void = {};
552
553 const stdout = zp.multi_reader.fileReader(0);
554
555 while (true) {
556 const Header = std.zig.Server.Message.Header;
557 const header = stdout.interface.takeStruct(Header, .little) catch |err| switch (err) {
558 error.EndOfStream => break,
559 error.ReadFailed => return stdout.err.?,
560 };
561 const body = stdout.interface.take(header.bytes_len) catch |err| switch (err) {
562 error.EndOfStream => |e| {
563 // Better to report the crash with stderr below, but we set
564 // this in case the child exits successfully while violating
565 // this protocol.
566 eos_err = e;
567 break;
568 },
569 error.ReadFailed => return stdout.err.?,
570 };
571 switch (header.tag) {
572 .zig_version => {
573 if (!std.mem.eql(u8, builtin.zig_version_string, body)) {
574 return s.fail(
575 "zig version mismatch build runner vs compiler: '{s}' vs '{s}'",
576 .{ builtin.zig_version_string, body },
577 );
578 }
579 },
580 .error_bundle => {
581 s.result_error_bundle = try std.zig.Server.allocErrorBundle(gpa, body);
582 // This message indicates the end of the update.
583 if (watch) break;
584 },
585 .emit_digest => {
586 const EmitDigest = std.zig.Server.Message.EmitDigest;
587 const emit_digest: *align(1) const EmitDigest = @ptrCast(body);
588 s.result_cached = emit_digest.flags.cache_hit;
589 const digest = body[@sizeOf(EmitDigest)..][0..Cache.bin_digest_len];
590 result = .{
591 .root_dir = b.cache_root,
592 .sub_path = try arena.dupe(u8, "o" ++ std.fs.path.sep_str ++ Cache.binToHex(digest.*)),
593 };
594 },
595 .file_system_inputs => {
596 s.clearWatchInputs();
597 var it = std.mem.splitScalar(u8, body, 0);
598 while (it.next()) |prefixed_path| {
599 const prefix_index: std.zig.Server.Message.PathPrefix = @enumFromInt(prefixed_path[0] - 1);
600 const sub_path = try arena.dupe(u8, prefixed_path[1..]);
601 const sub_path_dirname = std.fs.path.dirname(sub_path) orelse "";
602 switch (prefix_index) {
603 .cwd => {
604 const path: Build.Cache.Path = .{
605 .root_dir = Build.Cache.Directory.cwd(),
606 .sub_path = sub_path_dirname,
607 };
608 try addWatchInputFromPath(s, path, std.fs.path.basename(sub_path));
609 },
610 .zig_lib => zl: {
611 if (s.cast(Step.Compile)) |compile| {
612 if (compile.zig_lib_dir) |zig_lib_dir| {
613 const lp = try zig_lib_dir.join(arena, sub_path);
614 try addWatchInput(s, lp);
615 break :zl;
616 }
617 }
618 const path: Build.Cache.Path = .{
619 .root_dir = s.owner.graph.zig_lib_directory,
620 .sub_path = sub_path_dirname,
621 };
622 try addWatchInputFromPath(s, path, std.fs.path.basename(sub_path));
623 },
624 .local_cache => {
625 const path: Build.Cache.Path = .{
626 .root_dir = b.cache_root,
627 .sub_path = sub_path_dirname,
628 };
629 try addWatchInputFromPath(s, path, std.fs.path.basename(sub_path));
630 },
631 .global_cache => {
632 const path: Build.Cache.Path = .{
633 .root_dir = s.owner.graph.global_cache_root,
634 .sub_path = sub_path_dirname,
635 };
636 try addWatchInputFromPath(s, path, std.fs.path.basename(sub_path));
637 },
638 }
639 }
640 },
641 .time_report => if (web_server) |ws| {
642 const TimeReport = std.zig.Server.Message.TimeReport;
643 const tr: *align(1) const TimeReport = @ptrCast(body[0..@sizeOf(TimeReport)]);
644 ws.updateTimeReportCompile(.{
645 .compile = s.cast(Step.Compile).?,
646 .use_llvm = tr.flags.use_llvm,
647 .stats = tr.stats,
648 .ns_total = @intCast(start_ts.untilNow(io, .awake).toNanoseconds()),
649 .llvm_pass_timings_len = tr.llvm_pass_timings_len,
650 .files_len = tr.files_len,
651 .decls_len = tr.decls_len,
652 .trailing = body[@sizeOf(TimeReport)..],
653 });
654 },
655 else => {}, // ignore other messages
656 }
657 }
658
659 s.result_duration_ns = @intCast(start_ts.untilNow(io, .awake).toNanoseconds());
660
661 const stderr_contents = zp.multi_reader.reader(1).buffered();
662 if (stderr_contents.len > 0) {
663 try s.result_error_msgs.append(arena, try arena.dupe(u8, stderr_contents));
664 }
665
666 try eos_err;
667
668 return result;
669}
670
671pub fn getZigProcess(s: *Step) ?*ZigProcess {
672 return switch (s.id) {
673 .compile => s.cast(Compile).?.zig_process,
674 else => null,
675 };
676}
677
678fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void {
679 const header: std.zig.Client.Message.Header = .{
680 .tag = tag,
681 .bytes_len = 0,
682 };
683 var w = file.writer(io, &.{});
684 w.interface.writeStruct(header, .little) catch |err| switch (err) {
685 error.WriteFailed => return w.err.?,
686 };
687}
688
689pub fn handleVerbose(
690 b: *Build,
691 cwd: std.process.Child.Cwd,
692 argv: []const []const u8,
693) error{OutOfMemory}!void {
694 return handleVerbose2(b, cwd, null, argv);
695}
696
697pub fn handleVerbose2(
698 b: *Build,
699 cwd: std.process.Child.Cwd,
700 opt_env: ?*const std.process.Environ.Map,
701 argv: []const []const u8,
702) error{OutOfMemory}!void {
703 if (b.verbose) {
704 const graph = b.graph;
705 // Intention of verbose is to print all sub-process command lines to
706 // stderr before spawning them.
707 const text = try allocPrintCmd(b.allocator, cwd, if (opt_env) |env| .{
708 .child = env,
709 .parent = &graph.environ_map,
710 } else null, argv);
711 std.debug.print("{s}\n", .{text});
712 }
713}
714
715/// Asserts that the caller has already populated `s.result_failed_command`.
716pub inline fn handleChildProcUnsupported(s: *Step) error{ OutOfMemory, MakeFailed }!void {
717 if (!std.process.can_spawn) {
718 return s.fail("unable to spawn process: host cannot spawn child processes", .{});
719 }
720}
721
722/// Asserts that the caller has already populated `s.result_failed_command`.
723pub fn handleChildProcessTerm(s: *Step, term: std.process.Child.Term) error{ MakeFailed, OutOfMemory }!void {
724 assert(s.result_failed_command != null);
725 return switch (term) {
726 .exited => |code| if (code != 0) s.fail("process exited with error code {d}", .{code}),
727 .signal => |sig| s.fail("process terminated with signal {t}", .{sig}),
728 .stopped => |sig| s.fail("process stopped with signal {t}", .{sig}),
729 .unknown => s.fail("process terminated unexpectedly", .{}),
730 };
731}
732
733pub fn allocPrintCmd(
734 gpa: Allocator,
735 cwd: std.process.Child.Cwd,
736 opt_env: ?struct {
737 child: *const std.process.Environ.Map,
738 parent: *const std.process.Environ.Map,
739 },
740 argv: []const []const u8,
741) Allocator.Error![]u8 {
742 const shell = struct {
743 fn escape(writer: *Io.Writer, string: []const u8, is_argv0: bool) !void {
744 for (string) |c| {
745 if (switch (c) {
746 else => true,
747 '%', '+'...':', '@'...'Z', '_', 'a'...'z' => false,
748 '=' => is_argv0,
749 }) break;
750 } else return writer.writeAll(string);
751
752 try writer.writeByte('"');
753 for (string) |c| {
754 if (switch (c) {
755 std.ascii.control_code.nul => break,
756 '!', '"', '$', '\\', '`' => true,
757 else => !std.ascii.isPrint(c),
758 }) try writer.writeByte('\\');
759 switch (c) {
760 std.ascii.control_code.nul => unreachable,
761 std.ascii.control_code.bel => try writer.writeByte('a'),
762 std.ascii.control_code.bs => try writer.writeByte('b'),
763 std.ascii.control_code.ht => try writer.writeByte('t'),
764 std.ascii.control_code.lf => try writer.writeByte('n'),
765 std.ascii.control_code.vt => try writer.writeByte('v'),
766 std.ascii.control_code.ff => try writer.writeByte('f'),
767 std.ascii.control_code.cr => try writer.writeByte('r'),
768 std.ascii.control_code.esc => try writer.writeByte('E'),
769 ' '...'~' => try writer.writeByte(c),
770 else => try writer.print("{o:0>3}", .{c}),
771 }
772 }
773 try writer.writeByte('"');
774 }
775 };
776
777 var aw: Io.Writer.Allocating = .init(gpa);
778 defer aw.deinit();
779 const writer = &aw.writer;
780 switch (cwd) {
781 .inherit => {},
782 .path => |path| writer.print("cd {s} && ", .{path}) catch return error.OutOfMemory,
783 .dir => @panic("TODO"),
784 }
785 if (opt_env) |env| {
786 var it = env.child.iterator();
787 while (it.next()) |entry| {
788 const key = entry.key_ptr.*;
789 const value = entry.value_ptr.*;
790 if (env.parent.get(key)) |process_value| {
791 if (std.mem.eql(u8, value, process_value)) continue;
792 }
793 writer.print("{s}=", .{key}) catch return error.OutOfMemory;
794 shell.escape(writer, value, false) catch return error.OutOfMemory;
795 writer.writeByte(' ') catch return error.OutOfMemory;
796 }
797 }
798 shell.escape(writer, argv[0], true) catch return error.OutOfMemory;
799 for (argv[1..]) |arg| {
800 writer.writeByte(' ') catch return error.OutOfMemory;
801 shell.escape(writer, arg, false) catch return error.OutOfMemory;
802 }
803 return aw.toOwnedSlice();
804}
805
806/// Prefer `cacheHitAndWatch` unless you already added watch inputs
807/// separately from using the cache system.
808pub fn cacheHit(s: *Step, man: *Build.Cache.Manifest) !bool {
809 s.result_cached = man.hit() catch |err| return failWithCacheError(s, man, err);
810 return s.result_cached;
811}
812
813/// Clears previous watch inputs, if any, and then populates watch inputs from
814/// the full set of files picked up by the cache manifest.
815///
816/// Must be accompanied with `writeManifestAndWatch`.
817pub fn cacheHitAndWatch(s: *Step, man: *Build.Cache.Manifest) !bool {
818 const is_hit = man.hit() catch |err| return failWithCacheError(s, man, err);
819 s.result_cached = is_hit;
820 // The above call to hit() populates the manifest with files, so in case of
821 // a hit, we need to populate watch inputs.
822 if (is_hit) try setWatchInputsFromManifest(s, man);
823 return is_hit;
824}
825
826fn failWithCacheError(
827 s: *Step,
828 man: *const Build.Cache.Manifest,
829 err: Build.Cache.Manifest.HitError,
830) error{ OutOfMemory, Canceled, MakeFailed } {
831 switch (err) {
832 error.CacheCheckFailed => switch (man.diagnostic) {
833 .none => unreachable,
834 .manifest_create, .manifest_read, .manifest_lock => |e| return s.fail("failed to check cache: {t} {t}", .{
835 man.diagnostic, e,
836 }),
837 .file_open, .file_stat, .file_read, .file_hash => |op| {
838 const pp = man.files.keys()[op.file_index].prefixed_path;
839 const prefix = man.cache.prefixes()[pp.prefix].path orelse "";
840 return s.fail("failed to check cache: '{s}{c}{s}' {t} {t}", .{
841 prefix, std.fs.path.sep, pp.sub_path, man.diagnostic, op.err,
842 });
843 },
844 },
845 error.OutOfMemory, error.Canceled => |e| return e,
846 error.InvalidFormat => return s.fail("failed to check cache: invalid manifest file format", .{}),
847 }
848}
849
850/// Prefer `writeManifestAndWatch` unless you already added watch inputs
851/// separately from using the cache system.
852pub fn writeManifest(s: *Step, man: *Build.Cache.Manifest) !void {
853 if (s.test_results.isSuccess()) {
854 man.writeManifest() catch |err| {
855 try s.addError("unable to write cache manifest: {t}", .{err});
856 };
857 }
858}
859
860/// Clears previous watch inputs, if any, and then populates watch inputs from
861/// the full set of files picked up by the cache manifest.
862///
863/// Must be accompanied with `cacheHitAndWatch`.
864pub fn writeManifestAndWatch(s: *Step, man: *Build.Cache.Manifest) !void {
865 try writeManifest(s, man);
866 try setWatchInputsFromManifest(s, man);
867}
868
869fn setWatchInputsFromManifest(s: *Step, man: *Build.Cache.Manifest) !void {
870 const arena = s.owner.allocator;
871 const prefixes = man.cache.prefixes();
872 clearWatchInputs(s);
873 for (man.files.keys()) |file| {
874 // The file path data is freed when the cache manifest is cleaned up at the end of `make`.
875 const sub_path = try arena.dupe(u8, file.prefixed_path.sub_path);
876 try addWatchInputFromPath(s, .{
877 .root_dir = prefixes[file.prefixed_path.prefix],
878 .sub_path = std.fs.path.dirname(sub_path) orelse "",
879 }, std.fs.path.basename(sub_path));
880 }
881}
882
883/// For steps that have a single input that never changes when re-running `make`.
884pub fn singleUnchangingWatchInput(step: *Step, lazy_path: Build.LazyPath) Allocator.Error!void {
885 if (!step.inputs.populated()) try step.addWatchInput(lazy_path);
886}
887
888pub fn clearWatchInputs(step: *Step) void {
889 const gpa = step.owner.allocator;
890 step.inputs.clear(gpa);
891}
892
893/// Places a *file* dependency on the path.
894pub fn addWatchInput(step: *Step, lazy_file: Build.LazyPath) Allocator.Error!void {
895 switch (lazy_file) {
896 .src_path => |src_path| try addWatchInputFromBuilder(step, src_path.owner, src_path.sub_path),
897 .dependency => |d| try addWatchInputFromBuilder(step, d.dependency.builder, d.sub_path),
898 .cwd_relative => |path_string| {
899 try addWatchInputFromPath(step, .{
900 .root_dir = .{
901 .path = null,
902 .handle = Io.Dir.cwd(),
903 },
904 .sub_path = std.fs.path.dirname(path_string) orelse "",
905 }, std.fs.path.basename(path_string));
906 },
907 // Nothing to watch because this dependency edge is modeled instead via `dependants`.
908 .generated => {},
909 }
910}
911
912/// Any changes inside the directory will trigger invalidation.
913///
914/// See also `addDirectoryWatchInputFromPath` which takes a `Build.Cache.Path` instead.
915///
916/// Paths derived from this directory should also be manually added via
917/// `addDirectoryWatchInputFromPath` if and only if this function returns
918/// `true`.
919pub fn addDirectoryWatchInput(step: *Step, lazy_directory: Build.LazyPath) Allocator.Error!bool {
920 switch (lazy_directory) {
921 .src_path => |src_path| try addDirectoryWatchInputFromBuilder(step, src_path.owner, src_path.sub_path),
922 .dependency => |d| try addDirectoryWatchInputFromBuilder(step, d.dependency.builder, d.sub_path),
923 .cwd_relative => |path_string| {
924 try addDirectoryWatchInputFromPath(step, .{
925 .root_dir = .{
926 .path = null,
927 .handle = Io.Dir.cwd(),
928 },
929 .sub_path = path_string,
930 });
931 },
932 // Nothing to watch because this dependency edge is modeled instead via `dependants`.
933 .generated => return false,
934 }
935 return true;
936}
937
938/// Any changes inside the directory will trigger invalidation.
939///
940/// See also `addDirectoryWatchInput` which takes a `Build.LazyPath` instead.
941///
942/// This function should only be called when it has been verified that the
943/// dependency on `path` is not already accounted for by a `Step` dependency.
944/// In other words, before calling this function, first check that the
945/// `Build.LazyPath` which this `path` is derived from is not `generated`.
946pub fn addDirectoryWatchInputFromPath(step: *Step, path: Build.Cache.Path) !void {
947 return addWatchInputFromPath(step, path, ".");
948}
949
950fn addWatchInputFromBuilder(step: *Step, builder: *Build, sub_path: []const u8) !void {
951 return addWatchInputFromPath(step, .{
952 .root_dir = builder.build_root,
953 .sub_path = std.fs.path.dirname(sub_path) orelse "",
954 }, std.fs.path.basename(sub_path));
955}
956
957fn addDirectoryWatchInputFromBuilder(step: *Step, builder: *Build, sub_path: []const u8) !void {
958 return addDirectoryWatchInputFromPath(step, .{
959 .root_dir = builder.build_root,
960 .sub_path = sub_path,
961 });
962}
963
964fn addWatchInputFromPath(step: *Step, path: Build.Cache.Path, basename: []const u8) !void {
965 const gpa = step.owner.allocator;
966 const gop = try step.inputs.table.getOrPut(gpa, path);
967 if (!gop.found_existing) gop.value_ptr.* = .empty;
968 try gop.value_ptr.append(gpa, basename);
969}
970
971/// Implementation detail of file watching and forced rebuilds. Prepares the step for being re-evaluated.
972pub fn reset(step: *Step, gpa: Allocator) void {
973 assert(step.state == .precheck_done);
974
975 if (step.result_failed_command) |cmd| gpa.free(cmd);
976
977 step.result_error_msgs.clearRetainingCapacity();
978 step.result_stderr = "";
979 step.result_cached = false;
980 step.result_duration_ns = null;
981 step.result_peak_rss = 0;
982 step.result_failed_command = null;
983 step.test_results = .{};
984 step.clearWatchInputs();
985
986 step.result_error_bundle.deinit(gpa);
987 step.result_error_bundle = std.zig.ErrorBundle.empty;
988}
989
990/// Implementation detail of file watching. Prepares the step for being re-evaluated.
991/// Returns `true` if the step was newly invalidated, `false` if it was already invalidated.
992pub fn invalidateResult(step: *Step, gpa: Allocator) bool {
993 if (step.state == .precheck_done) return false;
994 assert(step.pending_deps == 0);
995 step.state = .precheck_done;
996 step.reset(gpa);
997 for (step.dependants.items) |dependant| {
998 _ = dependant.invalidateResult(gpa);
999 dependant.pending_deps += 1;
1000 }
1001 return true;
1002}
1003
1004135test {
1005136 _ = CheckFile;
137 _ = Compile;
138 _ = ConfigHeader;
1006139 _ = Fail;
140 _ = FindProgram;
1007141 _ = Fmt;
1008142 _ = InstallArtifact;
1009143 _ = InstallDir;
1010144 _ = InstallFile;
1011145 _ = ObjCopy;
1012 _ = Compile;
1013146 _ = Options;
1014147 _ = Run;
1015148 _ = TranslateC;
1016 _ = WriteFile;
1017149 _ = UpdateSourceFiles;
150 _ = WriteFile;
1018151}
lib/std/Build/Step/CheckFile.zig+17-63
......@@ -1,7 +1,4 @@
11//! Fail the build step if a file does not match certain checks.
2//! TODO: make this more flexible, supporting more kinds of checks.
3//! TODO: generalize the code in std.testing.expectEqualStrings and make this
4//! CheckFile step produce those helpful diagnostics when there is not a match.
52const CheckFile = @This();
63
74const std = @import("std");
......@@ -9,83 +6,40 @@ const Io = std.Io;
96const Step = std.Build.Step;
107const fs = std.fs;
118const mem = std.mem;
9const Configuration = std.Build.Configuration;
1210
1311step: Step,
14expected_matches: []const []const u8,
15expected_exact: ?[]const u8,
16source: std.Build.LazyPath,
17max_bytes: usize = 20 * 1024 * 1024,
12file: std.Build.LazyPath,
13expected_matches: []const Configuration.Bytes,
14expected_exact: ?Configuration.Bytes,
15max_bytes: ?u32,
1816
19pub const base_id: Step.Id = .check_file;
17pub const base_tag: Step.Tag = .check_file;
2018
2119pub const Options = struct {
2220 expected_matches: []const []const u8 = &.{},
2321 expected_exact: ?[]const u8 = null,
22 max_bytes: ?u32 = null,
2423};
2524
26pub fn create(
27 owner: *std.Build,
28 source: std.Build.LazyPath,
29 options: Options,
30) *CheckFile {
31 const check_file = owner.allocator.create(CheckFile) catch @panic("OOM");
25pub fn create(owner: *std.Build, file: std.Build.LazyPath, options: Options) *CheckFile {
26 const graph = owner.graph;
27 const check_file = graph.create(CheckFile);
3228 check_file.* = .{
33 .step = Step.init(.{
34 .id = base_id,
29 .step = .init(.{
30 .tag = base_tag,
3531 .name = "CheckFile",
3632 .owner = owner,
37 .makeFn = make,
3833 }),
39 .source = source.dupe(owner),
40 .expected_matches = owner.dupeStrings(options.expected_matches),
41 .expected_exact = options.expected_exact,
34 .file = file.dupe(graph),
35 .expected_matches = graph.addBytesList(options.expected_matches),
36 .expected_exact = if (options.expected_exact) |b| graph.addBytes(b) else null,
37 .max_bytes = options.max_bytes,
4238 };
43 check_file.source.addStepDependencies(&check_file.step);
39 file.addStepDependencies(&check_file.step);
4440 return check_file;
4541}
4642
4743pub fn setName(check_file: *CheckFile, name: []const u8) void {
4844 check_file.step.name = name;
4945}
50
51fn make(step: *Step, options: Step.MakeOptions) !void {
52 _ = options;
53 const b = step.owner;
54 const io = b.graph.io;
55 const check_file: *CheckFile = @fieldParentPtr("step", step);
56 try step.singleUnchangingWatchInput(check_file.source);
57
58 const src_path = check_file.source.getPath2(b, step);
59 const contents = Io.Dir.cwd().readFileAlloc(io, src_path, b.allocator, .limited(check_file.max_bytes)) catch |err| {
60 return step.fail("unable to read '{s}': {s}", .{
61 src_path, @errorName(err),
62 });
63 };
64
65 for (check_file.expected_matches) |expected_match| {
66 if (mem.find(u8, contents, expected_match) == null) {
67 return step.fail(
68 \\
69 \\========= expected to find: ===================
70 \\{s}
71 \\========= but file does not contain it: =======
72 \\{s}
73 \\===============================================
74 , .{ expected_match, contents });
75 }
76 }
77
78 if (check_file.expected_exact) |expected_exact| {
79 if (!mem.eql(u8, expected_exact, contents)) {
80 return step.fail(
81 \\
82 \\========= expected: =====================
83 \\{s}
84 \\========= but found: ====================
85 \\{s}
86 \\========= from the following file: ======
87 \\{s}
88 , .{ expected_exact, contents, src_path });
89 }
90 }
91}
lib/std/Build/Step/Compile.zig+96-1418
......@@ -1,4 +1,5 @@
11const Compile = @This();
2
23const builtin = @import("builtin");
34
45const std = @import("std");
......@@ -8,19 +9,15 @@ const fs = std.fs;
89const assert = std.debug.assert;
910const panic = std.debug.panic;
1011const StringHashMap = std.StringHashMap;
11const Sha256 = std.crypto.hash.sha2.Sha256;
1212const Allocator = std.mem.Allocator;
1313const Step = std.Build.Step;
1414const LazyPath = std.Build.LazyPath;
15const PkgConfigPkg = std.Build.PkgConfigPkg;
16const PkgConfigError = std.Build.PkgConfigError;
17const RunError = std.Build.RunError;
1815const Module = std.Build.Module;
1916const InstallDir = std.Build.InstallDir;
20const GeneratedFile = std.Build.GeneratedFile;
2117const Path = std.Build.Cache.Path;
18const Configuration = std.Build.Configuration;
2219
23pub const base_id: Step.Id = .compile;
20pub const base_tag: Step.Tag = .compile;
2421
2522step: Step,
2623root_module: *Module,
......@@ -28,13 +25,11 @@ root_module: *Module,
2825name: []const u8,
2926linker_script: ?LazyPath = null,
3027version_script: ?LazyPath = null,
28/// Deprecated.
3129out_filename: []const u8,
32out_lib_filename: []const u8,
3330linkage: ?std.builtin.LinkMode = null,
3431version: ?std.SemanticVersion,
3532kind: Kind,
36major_only_filename: ?[]const u8,
37name_only_filename: ?[]const u8,
3833formatted_panics: ?bool = null,
3934compress_debug_sections: std.zig.CompressDebugSections = .none,
4035verbose_link: bool,
......@@ -47,6 +42,7 @@ export_memory: bool = false,
4742/// For WebAssembly targets, this will allow for undefined symbols to
4843/// be imported from the host environment.
4944import_symbols: bool = false,
45/// (WebAssembly) import function table from the host environment
5046import_table: bool = false,
5147export_table: bool = false,
5248initial_memory: ?u64 = null,
......@@ -60,7 +56,7 @@ filters: []const []const u8,
6056test_runner: ?TestRunner,
6157wasi_exec_model: ?std.builtin.WasiExecModel = null,
6258
63installed_headers: std.array_list.Managed(HeaderInstallation),
59installed_headers: std.ArrayList(HeaderInstallation),
6460
6561/// This step is used to create an include tree that dependent modules can add to their include
6662/// search paths. Installed headers are copied to this step.
......@@ -83,8 +79,6 @@ win32_manifest: ?LazyPath = null,
8379/// Set via options; intended to be read-only after that.
8480win32_module_definition: ?LazyPath = null,
8581
86installed_path: ?[]const u8,
87
8882/// Base address for an executable image.
8983image_base: ?u64 = null,
9084
......@@ -93,9 +87,13 @@ libc_file: ?LazyPath = null,
9387each_lib_rpath: ?bool = null,
9488/// On ELF targets, this will emit a link section called ".note.gnu.build-id"
9589/// which can be used to coordinate a stripped binary with its debug symbols.
90///
9691/// As an example, the bloaty project refuses to work unless its inputs have
9792/// build ids, in order to prevent accidental mismatches.
93///
9894/// The default is to not include this section because it slows down linking.
95///
96/// This option overrides the CLI argument passed to `zig build`.
9997build_id: ?std.zig.BuildId = null,
10098
10199/// Create a .eh_frame_hdr section and a PT_GNU_EH_FRAME segment in the ELF
......@@ -147,8 +145,8 @@ link_z_defs: bool = false,
147145/// (Darwin) Install name for the dylib
148146install_name: ?[]const u8 = null,
149147
150/// (Darwin) Path to entitlements file
151entitlements: ?[]const u8 = null,
148/// Must be passed in via `Options`.
149entitlements: ?LazyPath = null,
152150
153151/// (Darwin) Size of the pagezero segment.
154152pagezero_size: ?u64 = null,
......@@ -189,7 +187,7 @@ entry: Entry = .default,
189187/// List of symbols forced as undefined in the symbol table
190188/// thus forcing their resolution by the linker.
191189/// Corresponds to `-u <symbol>` for ELF/MachO and `/include:<symbol>` for COFF/PE.
192force_undefined_symbols: std.StringHashMap(void),
190force_undefined_symbols: std.StringArrayHashMapUnmanaged(void),
193191
194192/// Overrides the default stack size
195193stack_size: ?u64 = null,
......@@ -213,21 +211,8 @@ allow_so_scripts: ?bool = null,
213211/// otherwise.
214212expect_errors: ?ExpectedCompileErrors = null,
215213
216emit_directory: ?*GeneratedFile,
217
218generated_docs: ?*GeneratedFile,
219generated_asm: ?*GeneratedFile,
220generated_bin: ?*GeneratedFile,
221generated_pdb: ?*GeneratedFile,
222// hack for stage2_x86_64 + coff
223generated_compiler_rt_dyn_lib: ?*GeneratedFile,
224generated_implib: ?*GeneratedFile,
225generated_llvm_bc: ?*GeneratedFile,
226generated_llvm_ir: ?*GeneratedFile,
227generated_h: ?*GeneratedFile,
228
229/// The maximum number of distinct errors within a compilation step
230/// Defaults to `std.math.maxInt(u16)`
214/// The maximum number of distinct errors within a compilation step Defaults to
215/// `std.math.maxInt(u16)`. Overrides the argument passed to `zig build`.
231216error_limit: ?u32 = null,
232217
233218/// Computed during make().
......@@ -235,10 +220,6 @@ is_linking_libc: bool = false,
235220/// Computed during make().
236221is_linking_libcpp: bool = false,
237222
238/// Populated during the make phase when there is a long-lived compiler process.
239/// Managed by the build runner, not user build script.
240zig_process: ?*Step.ZigProcess,
241
242223/// Enables coverage instrumentation that is only useful if you are using third
243224/// party fuzzers that depend on it. Otherwise, slows down the instrumented
244225/// binary with unnecessary function calls.
......@@ -253,6 +234,16 @@ zig_process: ?*Step.ZigProcess,
253234/// builtin fuzzer, see the `fuzz` flag in `Module`.
254235sanitize_coverage_trace_pc_guard: ?bool = null,
255236
237emit_directory: Configuration.OptionalGeneratedFileIndex = .none,
238generated_docs: Configuration.OptionalGeneratedFileIndex = .none,
239generated_asm: Configuration.OptionalGeneratedFileIndex = .none,
240generated_bin: Configuration.OptionalGeneratedFileIndex = .none,
241generated_pdb: Configuration.OptionalGeneratedFileIndex = .none,
242generated_implib: Configuration.OptionalGeneratedFileIndex = .none,
243generated_llvm_bc: Configuration.OptionalGeneratedFileIndex = .none,
244generated_llvm_ir: Configuration.OptionalGeneratedFileIndex = .none,
245generated_h: Configuration.OptionalGeneratedFileIndex = .none,
246
256247pub const ExpectedCompileErrors = union(enum) {
257248 contains: []const u8,
258249 exact: []const []const u8,
......@@ -292,22 +283,11 @@ pub const Options = struct {
292283 win32_manifest: ?LazyPath = null,
293284 /// Win32 module definition file.
294285 win32_module_definition: ?LazyPath = null,
286 /// (Darwin) Path to entitlements file
287 entitlements: ?LazyPath = null,
295288};
296289
297pub const Kind = enum {
298 exe,
299 lib,
300 obj,
301 @"test",
302 test_obj,
303
304 pub fn isTest(kind: Kind) bool {
305 return switch (kind) {
306 .exe, .lib, .obj => false,
307 .@"test", .test_obj => true,
308 };
309 }
310};
290pub const Kind = Configuration.Step.Compile.Kind;
311291
312292pub const HeaderInstallation = union(enum) {
313293 file: File,
......@@ -317,10 +297,10 @@ pub const HeaderInstallation = union(enum) {
317297 source: LazyPath,
318298 dest_rel_path: []const u8,
319299
320 pub fn dupe(file: File, b: *std.Build) File {
300 pub fn dupe(file: File, graph: *const std.Build.Graph) File {
321301 return .{
322 .source = file.source.dupe(b),
323 .dest_rel_path = b.dupePath(file.dest_rel_path),
302 .source = file.source.dupe(graph),
303 .dest_rel_path = graph.dupePath(file.dest_rel_path),
324304 };
325305 }
326306 };
......@@ -338,19 +318,19 @@ pub const HeaderInstallation = union(enum) {
338318 /// `exclude_extensions` takes precedence over `include_extensions`.
339319 include_extensions: ?[]const []const u8 = &.{".h"},
340320
341 pub fn dupe(opts: Directory.Options, b: *std.Build) Directory.Options {
321 pub fn dupe(opts: Directory.Options, graph: *const std.Build.Graph) Directory.Options {
342322 return .{
343 .exclude_extensions = b.dupeStrings(opts.exclude_extensions),
344 .include_extensions = if (opts.include_extensions) |incs| b.dupeStrings(incs) else null,
323 .exclude_extensions = graph.dupeStrings(opts.exclude_extensions),
324 .include_extensions = if (opts.include_extensions) |incs| graph.dupeStrings(incs) else null,
345325 };
346326 }
347327 };
348328
349 pub fn dupe(dir: Directory, b: *std.Build) Directory {
329 pub fn dupe(dir: Directory, graph: *const std.Build.Graph) Directory {
350330 return .{
351 .source = dir.source.dupe(b),
352 .dest_rel_path = b.dupePath(dir.dest_rel_path),
353 .options = dir.options.dupe(b),
331 .source = dir.source.dupe(graph),
332 .dest_rel_path = graph.dupePath(dir.dest_rel_path),
333 .options = dir.options.dupe(graph),
354334 };
355335 }
356336 };
......@@ -361,10 +341,10 @@ pub const HeaderInstallation = union(enum) {
361341 };
362342 }
363343
364 pub fn dupe(installation: HeaderInstallation, b: *std.Build) HeaderInstallation {
344 pub fn dupe(installation: HeaderInstallation, graph: *const std.Build.Graph) HeaderInstallation {
365345 return switch (installation) {
366 .file => |f| .{ .file = f.dupe(b) },
367 .directory => |d| .{ .directory = d.dupe(b) },
346 .file => |f| .{ .file = f.dupe(graph) },
347 .directory => |d| .{ .directory = d.dupe(graph) },
368348 };
369349 }
370350};
......@@ -378,6 +358,9 @@ pub const TestRunner = struct {
378358};
379359
380360pub fn create(owner: *std.Build, options: Options) *Compile {
361 const graph = owner.graph;
362 const arena = graph.arena;
363
381364 const name = owner.dupe(options.name);
382365 if (mem.find(u8, name, "/") != null or mem.find(u8, name, "\\") != null) {
383366 panic("invalid name: '{s}'. It looks like a file path, but it is supposed to be the library or application name.", .{name});
......@@ -392,14 +375,17 @@ pub fn create(owner: *std.Build, options: Options) *Compile {
392375 if (options.kind.isTest() and mem.eql(u8, name, "test"))
393376 @tagName(options.kind)
394377 else
395 owner.fmt("{s} {s}", .{ @tagName(options.kind), name }),
378 owner.fmt("{t} {s}", .{ options.kind, name }),
396379 @tagName(options.root_module.optimize orelse .Debug),
397 resolved_target.query.zigTriple(owner.allocator) catch @panic("OOM"),
380 resolved_target.query.zigTriple(arena) catch @panic("OOM"),
398381 });
399382
400 const out_filename = std.zig.binNameAlloc(owner.allocator, .{
383 const out_filename = std.zig.binNameAlloc(arena, .{
401384 .root_name = name,
402 .target = target,
385 .cpu_arch = target.cpu.arch,
386 .os_tag = target.os.tag,
387 .ofmt = target.ofmt,
388 .abi = target.abi,
403389 .output_mode = switch (options.kind) {
404390 .lib => .Lib,
405391 .obj, .test_obj => .Obj,
......@@ -409,7 +395,7 @@ pub fn create(owner: *std.Build, options: Options) *Compile {
409395 .version = options.version,
410396 }) catch @panic("OOM");
411397
412 const compile = owner.allocator.create(Compile) catch @panic("OOM");
398 const compile = arena.create(Compile) catch @panic("OOM");
413399 compile.* = .{
414400 .root_module = options.root_module,
415401 .verbose_link = false,
......@@ -418,52 +404,34 @@ pub fn create(owner: *std.Build, options: Options) *Compile {
418404 .kind = options.kind,
419405 .name = name,
420406 .step = .init(.{
421 .id = base_id,
407 .tag = base_tag,
422408 .name = step_name,
423409 .owner = owner,
424 .makeFn = make,
425410 .max_rss = options.max_rss,
426411 }),
427412 .version = options.version,
428413 .out_filename = out_filename,
429 .out_lib_filename = undefined,
430 .major_only_filename = null,
431 .name_only_filename = null,
432 .installed_headers = std.array_list.Managed(HeaderInstallation).init(owner.allocator),
414 .installed_headers = .empty,
433415 .zig_lib_dir = null,
434416 .exec_cmd_args = null,
435417 .filters = options.filters,
436418 .test_runner = null, // set below
437419 .rdynamic = false,
438 .installed_path = null,
439 .force_undefined_symbols = StringHashMap(void).init(owner.allocator),
440
441 .emit_directory = null,
442 .generated_docs = null,
443 .generated_asm = null,
444 .generated_bin = null,
445 .generated_pdb = null,
446 .generated_compiler_rt_dyn_lib = null,
447 .generated_implib = null,
448 .generated_llvm_bc = null,
449 .generated_llvm_ir = null,
450 .generated_h = null,
420 .force_undefined_symbols = .empty,
451421
452422 .use_llvm = options.use_llvm,
453423 .use_lld = options.use_lld,
454424 .use_new_linker = null,
455
456 .zig_process = null,
457425 };
458426
459427 if (options.zig_lib_dir) |lp| {
460 compile.zig_lib_dir = lp.dupe(compile.step.owner);
428 compile.zig_lib_dir = lp.dupe(graph);
461429 lp.addStepDependencies(&compile.step);
462430 }
463431
464432 if (options.test_runner) |runner| {
465433 compile.test_runner = .{
466 .path = runner.path.dupe(compile.step.owner),
434 .path = runner.path.dupe(graph),
467435 .mode = runner.mode,
468436 };
469437 runner.path.addStepDependencies(&compile.step);
......@@ -473,45 +441,21 @@ pub fn create(owner: *std.Build, options: Options) *Compile {
473441 // gets embedded, so for any other target the manifest file is just ignored.
474442 if (target.ofmt == .coff) {
475443 if (options.win32_manifest) |lp| {
476 compile.win32_manifest = lp.dupe(compile.step.owner);
444 compile.win32_manifest = lp.dupe(graph);
477445 lp.addStepDependencies(&compile.step);
478446 }
479447 if (compile.kind == .lib and compile.linkage != null and compile.linkage.? == .dynamic) {
480448 // Building a Win32 DLL, check for win32 .def file.
481449 if (options.win32_module_definition) |lp| {
482 compile.win32_module_definition = lp.dupe(compile.step.owner);
450 compile.win32_module_definition = lp.dupe(graph);
483451 lp.addStepDependencies(&compile.step);
484452 }
485453 }
486454 }
487455
488 if (compile.kind == .lib) {
489 if (compile.linkage != null and compile.linkage.? == .static) {
490 compile.out_lib_filename = compile.out_filename;
491 } else if (compile.version) |version| {
492 if (target.os.tag.isDarwin()) {
493 compile.major_only_filename = owner.fmt("lib{s}.{d}.dylib", .{
494 compile.name,
495 version.major,
496 });
497 compile.name_only_filename = owner.fmt("lib{s}.dylib", .{compile.name});
498 compile.out_lib_filename = compile.out_filename;
499 } else if (target.os.tag == .windows) {
500 compile.out_lib_filename = owner.fmt("{s}.lib", .{compile.name});
501 } else {
502 compile.major_only_filename = owner.fmt("lib{s}.so.{d}", .{ compile.name, version.major });
503 compile.name_only_filename = owner.fmt("lib{s}.so", .{compile.name});
504 compile.out_lib_filename = compile.out_filename;
505 }
506 } else {
507 if (target.os.tag.isDarwin()) {
508 compile.out_lib_filename = compile.out_filename;
509 } else if (target.os.tag == .windows) {
510 compile.out_lib_filename = owner.fmt("{s}.lib", .{compile.name});
511 } else {
512 compile.out_lib_filename = compile.out_filename;
513 }
514 }
456 if (options.entitlements) |lp| {
457 compile.entitlements = lp.dupe(graph);
458 lp.addStepDependencies(&compile.step);
515459 }
516460
517461 return compile;
......@@ -521,12 +465,13 @@ pub fn create(owner: *std.Build, options: Options) *Compile {
521465/// When a module links with this artifact, all headers marked for installation are added to that
522466/// module's include search path.
523467pub fn installHeader(cs: *Compile, source: LazyPath, dest_rel_path: []const u8) void {
524 const b = cs.step.owner;
468 const graph = cs.step.owner.graph;
469 const arena = graph.arena;
525470 const installation: HeaderInstallation = .{ .file = .{
526 .source = source.dupe(b),
527 .dest_rel_path = b.dupePath(dest_rel_path),
471 .source = source.dupe(graph),
472 .dest_rel_path = graph.dupePath(dest_rel_path),
528473 } };
529 cs.installed_headers.append(installation) catch @panic("OOM");
474 cs.installed_headers.append(arena, installation) catch @panic("OOM");
530475 cs.addHeaderInstallationToIncludeTree(installation);
531476 installation.getSource().addStepDependencies(&cs.step);
532477}
......@@ -540,13 +485,14 @@ pub fn installHeadersDirectory(
540485 dest_rel_path: []const u8,
541486 options: HeaderInstallation.Directory.Options,
542487) void {
543 const b = cs.step.owner;
488 const graph = cs.step.owner.graph;
489 const arena = graph.arena;
544490 const installation: HeaderInstallation = .{ .directory = .{
545 .source = source.dupe(b),
546 .dest_rel_path = b.dupePath(dest_rel_path),
547 .options = options.dupe(b),
491 .source = source.dupe(graph),
492 .dest_rel_path = graph.dupePath(dest_rel_path),
493 .options = options.dupe(graph),
548494 } };
549 cs.installed_headers.append(installation) catch @panic("OOM");
495 cs.installed_headers.append(arena, installation) catch @panic("OOM");
550496 cs.addHeaderInstallationToIncludeTree(installation);
551497 installation.getSource().addStepDependencies(&cs.step);
552498}
......@@ -563,9 +509,11 @@ pub fn installConfigHeader(cs: *Compile, config_header: *Step.ConfigHeader) void
563509/// module's include search path.
564510pub fn installLibraryHeaders(cs: *Compile, lib: *Compile) void {
565511 assert(lib.kind == .lib);
512 const graph = cs.step.owner.graph;
513 const arena = graph.arena;
566514 for (lib.installed_headers.items) |installation| {
567 const installation_copy = installation.dupe(lib.step.owner);
568 cs.installed_headers.append(installation_copy) catch @panic("OOM");
515 const installation_copy = installation.dupe(graph);
516 cs.installed_headers.append(arena, installation_copy) catch @panic("OOM");
569517 cs.addHeaderInstallationToIncludeTree(installation_copy);
570518 installation_copy.getSource().addStepDependencies(&cs.step);
571519 }
......@@ -612,20 +560,21 @@ pub fn addObjCopy(cs: *Compile, options: Step.ObjCopy.Options) *Step.ObjCopy {
612560}
613561
614562pub fn setLinkerScript(compile: *Compile, source: LazyPath) void {
615 const b = compile.step.owner;
616 compile.linker_script = source.dupe(b);
563 const graph = compile.step.owner.graph;
564 compile.linker_script = source.dupe(graph);
617565 source.addStepDependencies(&compile.step);
618566}
619567
620568pub fn setVersionScript(compile: *Compile, source: LazyPath) void {
621 const b = compile.step.owner;
622 compile.version_script = source.dupe(b);
569 const graph = compile.step.owner.graph;
570 compile.version_script = source.dupe(graph);
623571 source.addStepDependencies(&compile.step);
624572}
625573
626574pub fn forceUndefinedSymbol(compile: *Compile, symbol_name: []const u8) void {
627 const b = compile.step.owner;
628 compile.force_undefined_symbols.put(b.dupe(symbol_name), {}) catch @panic("OOM");
575 const graph = compile.step.owner.graph;
576 const arena = graph.allocator;
577 compile.force_undefined_symbols.put(arena, graph.dupeString(symbol_name), {}) catch @panic("OOM");
629578}
630579
631580/// Returns whether the library, executable, or object depends on a particular system library.
......@@ -701,122 +650,6 @@ pub fn producesImplib(compile: *Compile) bool {
701650 return compile.isDll();
702651}
703652
704const PkgConfigResult = struct {
705 cflags: []const []const u8,
706 libs: []const []const u8,
707};
708
709/// Run pkg-config for the given library name and parse the output, returning the arguments
710/// that should be passed to zig to link the given library.
711pub fn runPkgConfig(step: *Step, lib_name: []const u8) !PkgConfigResult {
712 const wl_rpath_prefix = "-Wl,-rpath,";
713
714 const b = step.owner;
715 const pkg_name = match: {
716 // First we have to map the library name to pkg config name. Unfortunately,
717 // there are several examples where this is not straightforward:
718 // -lSDL2 -> pkg-config sdl2
719 // -lgdk-3 -> pkg-config gdk-3.0
720 // -latk-1.0 -> pkg-config atk
721 // -lpulse -> pkg-config libpulse
722 const pkgs = try getPkgConfigList(b);
723
724 // Exact match means instant winner.
725 for (pkgs) |pkg| {
726 if (mem.eql(u8, pkg.name, lib_name)) {
727 break :match pkg.name;
728 }
729 }
730
731 // Next we'll try ignoring case.
732 for (pkgs) |pkg| {
733 if (std.ascii.eqlIgnoreCase(pkg.name, lib_name)) {
734 break :match pkg.name;
735 }
736 }
737
738 // Prefixed "lib" or suffixed ".0".
739 for (pkgs) |pkg| {
740 if (std.ascii.findIgnoreCase(pkg.name, lib_name)) |pos| {
741 const prefix = pkg.name[0..pos];
742 const suffix = pkg.name[pos + lib_name.len ..];
743 if (prefix.len > 0 and !mem.eql(u8, prefix, "lib")) continue;
744 if (suffix.len > 0 and !mem.eql(u8, suffix, ".0")) continue;
745 break :match pkg.name;
746 }
747 }
748
749 // Trimming "-1.0".
750 if (mem.endsWith(u8, lib_name, "-1.0")) {
751 const trimmed_lib_name = lib_name[0 .. lib_name.len - "-1.0".len];
752 for (pkgs) |pkg| {
753 if (std.ascii.eqlIgnoreCase(pkg.name, trimmed_lib_name)) {
754 break :match pkg.name;
755 }
756 }
757 }
758
759 return error.PackageNotFound;
760 };
761
762 var code: u8 = undefined;
763 const pkg_config_exe = b.graph.environ_map.get("PKG_CONFIG") orelse "pkg-config";
764 const stdout = if (b.runAllowFail(&[_][]const u8{
765 pkg_config_exe,
766 pkg_name,
767 "--cflags",
768 "--libs",
769 }, &code, .ignore)) |stdout| stdout else |err| switch (err) {
770 error.ProcessTerminated => return error.PkgConfigCrashed,
771 error.ExecNotSupported => return error.PkgConfigFailed,
772 error.ExitCodeFailure => return error.PkgConfigFailed,
773 error.FileNotFound => return error.PkgConfigNotInstalled,
774 else => return err,
775 };
776
777 var zig_cflags: std.ArrayList([]const u8) = .empty;
778 defer zig_cflags.deinit(b.allocator);
779 var zig_libs: std.ArrayList([]const u8) = .empty;
780 defer zig_libs.deinit(b.allocator);
781
782 var arg_it = mem.tokenizeAny(u8, stdout, " \r\n\t");
783 while (arg_it.next()) |arg| {
784 if (mem.eql(u8, arg, "-I")) {
785 const dir = arg_it.next() orelse return error.PkgConfigInvalidOutput;
786 try zig_cflags.appendSlice(b.allocator, &.{ "-I", dir });
787 } else if (mem.startsWith(u8, arg, "-I")) {
788 try zig_cflags.append(b.allocator, arg);
789 } else if (mem.eql(u8, arg, "-L")) {
790 const dir = arg_it.next() orelse return error.PkgConfigInvalidOutput;
791 try zig_libs.appendSlice(b.allocator, &.{ "-L", dir });
792 } else if (mem.startsWith(u8, arg, "-L")) {
793 try zig_libs.append(b.allocator, arg);
794 } else if (mem.eql(u8, arg, "-l")) {
795 const lib = arg_it.next() orelse return error.PkgConfigInvalidOutput;
796 try zig_libs.appendSlice(b.allocator, &.{ "-l", lib });
797 } else if (mem.startsWith(u8, arg, "-l")) {
798 try zig_libs.append(b.allocator, arg);
799 } else if (mem.eql(u8, arg, "-D")) {
800 const macro = arg_it.next() orelse return error.PkgConfigInvalidOutput;
801 try zig_cflags.appendSlice(b.allocator, &.{ "-D", macro });
802 } else if (mem.startsWith(u8, arg, "-D")) {
803 try zig_cflags.append(b.allocator, arg);
804 } else if (mem.startsWith(u8, arg, wl_rpath_prefix)) {
805 try zig_cflags.appendSlice(b.allocator, &.{ "-rpath", arg[wl_rpath_prefix.len..] });
806 } else if (b.debug_pkg_config) {
807 return step.fail("unknown pkg-config flag '{s}'", .{arg});
808 }
809 }
810
811 try zig_cflags.shrinkToLen(b.allocator);
812 try zig_libs.shrinkToLen(b.allocator);
813
814 return .{
815 .cflags = zig_cflags.toOwnedSliceAssert(),
816 .libs = zig_libs.toOwnedSliceAssert(),
817 };
818}
819
820653pub fn setVerboseLink(compile: *Compile, value: bool) void {
821654 compile.verbose_link = value;
822655}
......@@ -826,22 +659,21 @@ pub fn setVerboseCC(compile: *Compile, value: bool) void {
826659}
827660
828661pub fn setLibCFile(compile: *Compile, libc_file: ?LazyPath) void {
829 const b = compile.step.owner;
662 const graph = compile.step.owner.graph;
830663 if (libc_file) |f| {
831 compile.libc_file = f.dupe(b);
664 compile.libc_file = f.dupe(graph);
832665 f.addStepDependencies(&compile.step);
833666 } else {
834667 compile.libc_file = null;
835668 }
836669}
837670
838fn getEmittedFileGeneric(compile: *Compile, output_file: *?*GeneratedFile) LazyPath {
839 if (output_file.*) |file| return .{ .generated = .{ .file = file } };
840 const arena = compile.step.owner.allocator;
841 const generated_file = arena.create(GeneratedFile) catch @panic("OOM");
842 generated_file.* = .{ .step = &compile.step };
843 output_file.* = generated_file;
844 return .{ .generated = .{ .file = generated_file } };
671fn getEmittedFileGeneric(compile: *Compile, output_file: *Configuration.OptionalGeneratedFileIndex) LazyPath {
672 if (output_file.unwrap()) |index| return .{ .generated = .{ .index = index } };
673 const graph = compile.step.owner.graph;
674 const index = graph.addGeneratedFile(&compile.step);
675 output_file.* = .init(index);
676 return .{ .generated = .{ .index = index } };
845677}
846678
847679/// Returns the path to the directory that contains the emitted binary file.
......@@ -905,1175 +737,21 @@ pub fn getEmittedLlvmBc(compile: *Compile) LazyPath {
905737}
906738
907739pub fn setExecCmd(compile: *Compile, args: []const ?[]const u8) void {
908 const b = compile.step.owner;
740 const graph = compile.step.owner.graph;
741 const arena = graph.arena;
909742 assert(compile.kind == .@"test");
910 const duped_args = b.allocator.alloc(?[]u8, args.len) catch @panic("OOM");
743 const duped_args = arena.alloc(?[]u8, args.len) catch @panic("OOM");
911744 for (args, 0..) |arg, i| {
912 duped_args[i] = if (arg) |a| b.dupe(a) else null;
745 duped_args[i] = if (arg) |a| graph.dupeString(a) else null;
913746 }
914747 compile.exec_cmd_args = duped_args;
915748}
916749
917const CliNamedModules = struct {
918 modules: std.AutoArrayHashMapUnmanaged(*Module, void),
919 names: std.StringArrayHashMapUnmanaged(void),
920
921 /// Traverse the whole dependency graph and give every module a unique
922 /// name, ideally one named after what it's called somewhere in the graph.
923 /// It will help here to have both a mapping from module to name and a set
924 /// of all the currently-used names.
925 fn init(arena: Allocator, root_module: *Module) Allocator.Error!CliNamedModules {
926 var compile: CliNamedModules = .{
927 .modules = .{},
928 .names = .{},
929 };
930 const graph = root_module.getGraph();
931 {
932 assert(graph.modules[0] == root_module);
933 try compile.modules.put(arena, root_module, {});
934 try compile.names.put(arena, "root", {});
935 }
936 for (graph.modules[1..], graph.names[1..]) |mod, orig_name| {
937 var name = orig_name;
938 var n: usize = 0;
939 while (true) {
940 const gop = try compile.names.getOrPut(arena, name);
941 if (!gop.found_existing) {
942 try compile.modules.putNoClobber(arena, mod, {});
943 break;
944 }
945 name = try std.fmt.allocPrint(arena, "{s}{d}", .{ orig_name, n });
946 n += 1;
947 }
948 }
949 return compile;
950 }
951};
952
953fn getGeneratedFilePath(compile: *Compile, comptime tag_name: []const u8, asking_step: ?*Step) ![]const u8 {
954 const step = &compile.step;
955 const b = step.owner;
956 const graph = b.graph;
957 const io = graph.io;
958 const maybe_path: ?*GeneratedFile = @field(compile, tag_name);
959
960 const generated_file = maybe_path orelse {
961 const stderr = try io.lockStderr(&.{}, graph.stderr_mode);
962 std.Build.dumpBadGetPathHelp(&compile.step, stderr.terminal(), compile.step.owner, asking_step) catch {};
963 io.unlockStderr();
964 @panic("missing emit option for " ++ tag_name);
965 };
966
967 const path = generated_file.path orelse {
968 const stderr = try io.lockStderr(&.{}, graph.stderr_mode);
969 std.Build.dumpBadGetPathHelp(&compile.step, stderr.terminal(), compile.step.owner, asking_step) catch {};
970 io.unlockStderr();
971 @panic(tag_name ++ " is null. Is there a missing step dependency?");
972 };
973
974 return path;
975}
976
977fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
978 const step = &compile.step;
979 const b = step.owner;
980 const arena = b.allocator;
981
982 var zig_args = std.array_list.Managed([]const u8).init(arena);
983 defer zig_args.deinit();
984
985 try zig_args.append(b.graph.zig_exe);
986
987 const cmd = switch (compile.kind) {
988 .lib => "build-lib",
989 .exe => "build-exe",
990 .obj => "build-obj",
991 .@"test" => "test",
992 .test_obj => "test-obj",
993 };
994 try zig_args.append(cmd);
995
996 if (b.reference_trace) |some| {
997 try zig_args.append(try std.fmt.allocPrint(arena, "-freference-trace={d}", .{some}));
998 }
999 try addFlag(&zig_args, "allow-so-scripts", compile.allow_so_scripts orelse b.graph.allow_so_scripts);
1000
1001 try addFlag(&zig_args, "llvm", compile.use_llvm);
1002 try addFlag(&zig_args, "lld", compile.use_lld);
1003 try addFlag(&zig_args, "new-linker", compile.use_new_linker);
1004
1005 if (compile.root_module.resolved_target.?.query.ofmt) |ofmt| {
1006 try zig_args.append(try std.fmt.allocPrint(arena, "-ofmt={s}", .{@tagName(ofmt)}));
1007 }
1008
1009 switch (compile.entry) {
1010 .default => {},
1011 .disabled => try zig_args.append("-fno-entry"),
1012 .enabled => try zig_args.append("-fentry"),
1013 .symbol_name => |entry_name| {
1014 try zig_args.append(try std.fmt.allocPrint(arena, "-fentry={s}", .{entry_name}));
1015 },
1016 }
1017
1018 {
1019 var symbol_it = compile.force_undefined_symbols.keyIterator();
1020 while (symbol_it.next()) |symbol_name| {
1021 try zig_args.append("--force_undefined");
1022 try zig_args.append(symbol_name.*);
1023 }
1024 }
1025
1026 if (compile.stack_size) |stack_size| {
1027 try zig_args.append("--stack");
1028 try zig_args.append(try std.fmt.allocPrint(arena, "{}", .{stack_size}));
1029 }
1030
1031 if (fuzz) {
1032 try zig_args.append("-ffuzz");
1033 }
1034
1035 {
1036 // Stores system libraries that have already been seen for at least one
1037 // module, along with any arguments that need to be passed to the
1038 // compiler for each module individually.
1039 var seen_system_libs: std.StringHashMapUnmanaged([]const []const u8) = .empty;
1040 var frameworks: std.StringArrayHashMapUnmanaged(Module.LinkFrameworkOptions) = .empty;
1041
1042 var prev_has_cflags = false;
1043 var prev_has_rcflags = false;
1044 var prev_search_strategy: Module.SystemLib.SearchStrategy = .paths_first;
1045 var prev_preferred_link_mode: std.builtin.LinkMode = .dynamic;
1046 // Track the number of positional arguments so that a nice error can be
1047 // emitted if there is nothing to link.
1048 var total_linker_objects: usize = @intFromBool(compile.root_module.root_source_file != null);
1049
1050 // Fully recursive iteration including dynamic libraries to detect
1051 // libc and libc++ linkage.
1052 for (compile.getCompileDependencies(true)) |some_compile| {
1053 for (some_compile.root_module.getGraph().modules) |mod| {
1054 if (mod.link_libc == true) compile.is_linking_libc = true;
1055 if (mod.link_libcpp == true) compile.is_linking_libcpp = true;
1056 }
1057 }
1058
1059 var cli_named_modules = try CliNamedModules.init(arena, compile.root_module);
1060
1061 // For this loop, don't chase dynamic libraries because their link
1062 // objects are already linked.
1063 for (compile.getCompileDependencies(false)) |dep_compile| {
1064 for (dep_compile.root_module.getGraph().modules) |mod| {
1065 // While walking transitive dependencies, if a given link object is
1066 // already included in a library, it should not redundantly be
1067 // placed on the linker line of the dependee.
1068 const my_responsibility = dep_compile == compile;
1069 const already_linked = !my_responsibility and dep_compile.isDynamicLibrary();
1070
1071 // Inherit dependencies on darwin frameworks.
1072 if (!already_linked) {
1073 for (mod.frameworks.keys(), mod.frameworks.values()) |name, info| {
1074 try frameworks.put(arena, name, info);
1075 }
1076 }
1077
1078 // Inherit dependencies on system libraries and static libraries.
1079 for (mod.link_objects.items) |link_object| {
1080 switch (link_object) {
1081 .static_path => |static_path| {
1082 if (my_responsibility) {
1083 try zig_args.append(static_path.getPath2(mod.owner, step));
1084 total_linker_objects += 1;
1085 }
1086 },
1087 .system_lib => |system_lib| {
1088 const system_lib_gop = try seen_system_libs.getOrPut(arena, system_lib.name);
1089 if (system_lib_gop.found_existing) {
1090 try zig_args.appendSlice(system_lib_gop.value_ptr.*);
1091 continue;
1092 } else {
1093 system_lib_gop.value_ptr.* = &.{};
1094 }
1095
1096 if (already_linked)
1097 continue;
1098
1099 if ((system_lib.search_strategy != prev_search_strategy or
1100 system_lib.preferred_link_mode != prev_preferred_link_mode) and
1101 compile.linkage != .static)
1102 {
1103 switch (system_lib.search_strategy) {
1104 .no_fallback => switch (system_lib.preferred_link_mode) {
1105 .dynamic => try zig_args.append("-search_dylibs_only"),
1106 .static => try zig_args.append("-search_static_only"),
1107 },
1108 .paths_first => switch (system_lib.preferred_link_mode) {
1109 .dynamic => try zig_args.append("-search_paths_first"),
1110 .static => try zig_args.append("-search_paths_first_static"),
1111 },
1112 .mode_first => switch (system_lib.preferred_link_mode) {
1113 .dynamic => try zig_args.append("-search_dylibs_first"),
1114 .static => try zig_args.append("-search_static_first"),
1115 },
1116 }
1117 prev_search_strategy = system_lib.search_strategy;
1118 prev_preferred_link_mode = system_lib.preferred_link_mode;
1119 }
1120
1121 const prefix: []const u8 = prefix: {
1122 if (system_lib.needed) break :prefix "-needed-l";
1123 if (system_lib.weak) break :prefix "-weak-l";
1124 break :prefix "-l";
1125 };
1126 switch (system_lib.use_pkg_config) {
1127 .no => try zig_args.append(b.fmt("{s}{s}", .{ prefix, system_lib.name })),
1128 .yes, .force => {
1129 if (runPkgConfig(&compile.step, system_lib.name)) |result| {
1130 try zig_args.appendSlice(result.cflags);
1131 try zig_args.appendSlice(result.libs);
1132 try seen_system_libs.put(arena, system_lib.name, result.cflags);
1133 } else |err| switch (err) {
1134 error.PkgConfigInvalidOutput,
1135 error.PkgConfigCrashed,
1136 error.PkgConfigFailed,
1137 error.PkgConfigNotInstalled,
1138 error.PackageNotFound,
1139 => switch (system_lib.use_pkg_config) {
1140 .yes => {
1141 // pkg-config failed, so fall back to linking the library
1142 // by name directly.
1143 try zig_args.append(b.fmt("{s}{s}", .{
1144 prefix,
1145 system_lib.name,
1146 }));
1147 },
1148 .force => {
1149 panic("pkg-config failed for library {s}", .{system_lib.name});
1150 },
1151 .no => unreachable,
1152 },
1153
1154 else => |e| return e,
1155 }
1156 },
1157 }
1158 },
1159 .other_step => |other| {
1160 switch (other.kind) {
1161 .exe => return step.fail("cannot link with an executable build artifact", .{}),
1162 .@"test" => return step.fail("cannot link with a test", .{}),
1163 .obj, .test_obj => {
1164 const included_in_lib_or_obj = !my_responsibility and
1165 (dep_compile.kind == .lib or dep_compile.kind == .obj or dep_compile.kind == .test_obj);
1166 if (!already_linked and !included_in_lib_or_obj) {
1167 try zig_args.append(other.getEmittedBin().getPath2(b, step));
1168 total_linker_objects += 1;
1169 }
1170 },
1171 .lib => l: {
1172 const other_produces_implib = other.producesImplib();
1173 const other_is_static = other_produces_implib or other.isStaticLibrary();
1174
1175 if (compile.isStaticLibrary() and other_is_static) {
1176 // Avoid putting a static library inside a static library.
1177 break :l;
1178 }
1179
1180 // For DLLs, we must link against the implib.
1181 // For everything else, we directly link
1182 // against the library file.
1183 const full_path_lib = if (other_produces_implib)
1184 try other.getGeneratedFilePath("generated_implib", &compile.step)
1185 else
1186 try other.getGeneratedFilePath("generated_bin", &compile.step);
1187
1188 try zig_args.append(full_path_lib);
1189 total_linker_objects += 1;
1190
1191 if (other.linkage == .dynamic and
1192 compile.rootModuleTarget().os.tag != .windows)
1193 {
1194 if (fs.path.dirname(full_path_lib)) |dirname| {
1195 try zig_args.append("-rpath");
1196 try zig_args.append(dirname);
1197 }
1198 }
1199 },
1200 }
1201 },
1202 .assembly_file => |asm_file| l: {
1203 if (!my_responsibility) break :l;
1204
1205 if (prev_has_cflags) {
1206 try zig_args.append("-cflags");
1207 try zig_args.append("--");
1208 prev_has_cflags = false;
1209 }
1210 try zig_args.append(asm_file.getPath2(mod.owner, step));
1211 total_linker_objects += 1;
1212 },
1213
1214 .c_source_file => |c_source_file| l: {
1215 if (!my_responsibility) break :l;
1216
1217 if (prev_has_cflags or c_source_file.flags.len != 0) {
1218 try zig_args.append("-cflags");
1219 for (c_source_file.flags) |arg| {
1220 try zig_args.append(arg);
1221 }
1222 try zig_args.append("--");
1223 }
1224 prev_has_cflags = (c_source_file.flags.len != 0);
1225
1226 if (c_source_file.language) |lang| {
1227 try zig_args.append("-x");
1228 try zig_args.append(lang.internalIdentifier());
1229 }
1230
1231 try zig_args.append(c_source_file.file.getPath2(mod.owner, step));
1232
1233 if (c_source_file.language != null) {
1234 try zig_args.append("-x");
1235 try zig_args.append("none");
1236 }
1237 total_linker_objects += 1;
1238 },
1239
1240 .c_source_files => |c_source_files| l: {
1241 if (!my_responsibility) break :l;
1242
1243 if (prev_has_cflags or c_source_files.flags.len != 0) {
1244 try zig_args.append("-cflags");
1245 for (c_source_files.flags) |arg| {
1246 try zig_args.append(arg);
1247 }
1248 try zig_args.append("--");
1249 }
1250 prev_has_cflags = (c_source_files.flags.len != 0);
1251
1252 if (c_source_files.language) |lang| {
1253 try zig_args.append("-x");
1254 try zig_args.append(lang.internalIdentifier());
1255 }
1256
1257 const root_path = c_source_files.root.getPath2(mod.owner, step);
1258 for (c_source_files.files) |file| {
1259 try zig_args.append(b.pathJoin(&.{ root_path, file }));
1260 }
1261
1262 if (c_source_files.language != null) {
1263 try zig_args.append("-x");
1264 try zig_args.append("none");
1265 }
1266
1267 total_linker_objects += c_source_files.files.len;
1268 },
1269
1270 .win32_resource_file => |rc_source_file| l: {
1271 if (!my_responsibility) break :l;
1272
1273 if (rc_source_file.flags.len == 0 and rc_source_file.include_paths.len == 0) {
1274 if (prev_has_rcflags) {
1275 try zig_args.append("-rcflags");
1276 try zig_args.append("--");
1277 prev_has_rcflags = false;
1278 }
1279 } else {
1280 try zig_args.append("-rcflags");
1281 for (rc_source_file.flags) |arg| {
1282 try zig_args.append(arg);
1283 }
1284 for (rc_source_file.include_paths) |include_path| {
1285 try zig_args.append("/I");
1286 try zig_args.append(include_path.getPath2(mod.owner, step));
1287 }
1288 try zig_args.append("--");
1289 prev_has_rcflags = true;
1290 }
1291 try zig_args.append(rc_source_file.file.getPath2(mod.owner, step));
1292 total_linker_objects += 1;
1293 },
1294 }
1295 }
1296
1297 // We need to emit the --mod argument here so that the above link objects
1298 // have the correct parent module, but only if the module is part of
1299 // this compilation.
1300 if (!my_responsibility) continue;
1301 if (cli_named_modules.modules.getIndex(mod)) |module_cli_index| {
1302 const module_cli_name = cli_named_modules.names.keys()[module_cli_index];
1303 try mod.appendZigProcessFlags(&zig_args, step);
1304
1305 // --dep arguments
1306 try zig_args.ensureUnusedCapacity(mod.import_table.count() * 2);
1307 for (mod.import_table.keys(), mod.import_table.values()) |name, import| {
1308 const import_index = cli_named_modules.modules.getIndex(import).?;
1309 const import_cli_name = cli_named_modules.names.keys()[import_index];
1310 zig_args.appendAssumeCapacity("--dep");
1311 if (std.mem.eql(u8, import_cli_name, name)) {
1312 zig_args.appendAssumeCapacity(import_cli_name);
1313 } else {
1314 zig_args.appendAssumeCapacity(b.fmt("{s}={s}", .{ name, import_cli_name }));
1315 }
1316 }
1317
1318 // When the CLI sees a -M argument, it determines whether it
1319 // implies the existence of a Zig compilation unit based on
1320 // whether there is a root source file. If there is no root
1321 // source file, then this is not a zig compilation unit - it is
1322 // perhaps a set of linker objects, or C source files instead.
1323 // Linker objects are added to the CLI globally, while C source
1324 // files must have a module parent.
1325 if (mod.root_source_file) |lp| {
1326 const src = lp.getPath2(mod.owner, step);
1327 try zig_args.append(b.fmt("-M{s}={s}", .{ module_cli_name, src }));
1328 } else if (moduleNeedsCliArg(mod)) {
1329 try zig_args.append(b.fmt("-M{s}", .{module_cli_name}));
1330 }
1331 }
1332 }
1333 }
1334
1335 if (total_linker_objects == 0) {
1336 return step.fail("the linker needs one or more objects to link", .{});
1337 }
1338
1339 for (frameworks.keys(), frameworks.values()) |name, info| {
1340 if (info.needed) {
1341 try zig_args.append("-needed_framework");
1342 } else if (info.weak) {
1343 try zig_args.append("-weak_framework");
1344 } else {
1345 try zig_args.append("-framework");
1346 }
1347 try zig_args.append(name);
1348 }
1349
1350 if (compile.is_linking_libcpp) {
1351 try zig_args.append("-lc++");
1352 }
1353
1354 if (compile.is_linking_libc) {
1355 try zig_args.append("-lc");
1356 }
1357 }
1358
1359 if (compile.win32_manifest) |manifest_file| {
1360 try zig_args.append(manifest_file.getPath2(b, step));
1361 }
1362
1363 if (compile.win32_module_definition) |module_file| {
1364 try zig_args.append(module_file.getPath2(b, step));
1365 }
1366
1367 if (compile.image_base) |image_base| {
1368 try zig_args.append("--image-base");
1369 try zig_args.append(b.fmt("0x{x}", .{image_base}));
1370 }
1371
1372 for (compile.filters) |filter| {
1373 try zig_args.append("--test-filter");
1374 try zig_args.append(filter);
1375 }
1376
1377 if (compile.test_runner) |test_runner| {
1378 try zig_args.append("--test-runner");
1379 try zig_args.append(test_runner.path.getPath2(b, step));
1380 }
1381
1382 for (b.debug_log_scopes) |log_scope| {
1383 try zig_args.append("--debug-log");
1384 try zig_args.append(log_scope);
1385 }
1386
1387 if (b.debug_compile_errors) {
1388 try zig_args.append("--debug-compile-errors");
1389 }
1390
1391 if (b.debug_incremental) {
1392 try zig_args.append("--debug-incremental");
1393 }
1394
1395 if (b.verbose_air) try zig_args.append("--verbose-air");
1396 if (b.verbose_llvm_ir) |path| try zig_args.append(b.fmt("--verbose-llvm-ir={s}", .{path}));
1397 if (b.verbose_llvm_bc) |path| try zig_args.append(b.fmt("--verbose-llvm-bc={s}", .{path}));
1398 if (b.verbose_link or compile.verbose_link) try zig_args.append("--verbose-link");
1399 if (b.verbose_cc or compile.verbose_cc) try zig_args.append("--verbose-cc");
1400 if (b.verbose_llvm_cpu_features) try zig_args.append("--verbose-llvm-cpu-features");
1401 if (b.graph.time_report) try zig_args.append("--time-report");
1402
1403 if (compile.generated_asm != null) try zig_args.append("-femit-asm");
1404 if (compile.generated_bin == null) try zig_args.append("-fno-emit-bin");
1405 if (compile.generated_docs != null) try zig_args.append("-femit-docs");
1406 if (compile.generated_implib != null) try zig_args.append("-femit-implib");
1407 if (compile.generated_llvm_bc != null) try zig_args.append("-femit-llvm-bc");
1408 if (compile.generated_llvm_ir != null) try zig_args.append("-femit-llvm-ir");
1409 if (compile.generated_h != null) try zig_args.append("-femit-h");
1410
1411 try addFlag(&zig_args, "formatted-panics", compile.formatted_panics);
1412
1413 switch (compile.compress_debug_sections) {
1414 .none => {},
1415 .zlib => try zig_args.append("--compress-debug-sections=zlib"),
1416 .zstd => try zig_args.append("--compress-debug-sections=zstd"),
1417 }
1418
1419 if (compile.link_eh_frame_hdr) {
1420 try zig_args.append("--eh-frame-hdr");
1421 }
1422 if (compile.link_emit_relocs) {
1423 try zig_args.append("--emit-relocs");
1424 }
1425 if (compile.link_function_sections) {
1426 try zig_args.append("-ffunction-sections");
1427 }
1428 if (compile.link_data_sections) {
1429 try zig_args.append("-fdata-sections");
1430 }
1431 if (compile.link_gc_sections) |x| {
1432 try zig_args.append(if (x) "--gc-sections" else "--no-gc-sections");
1433 }
1434 if (!compile.linker_dynamicbase) {
1435 try zig_args.append("--no-dynamicbase");
1436 }
1437 if (compile.linker_allow_shlib_undefined) |x| {
1438 try zig_args.append(if (x) "-fallow-shlib-undefined" else "-fno-allow-shlib-undefined");
1439 }
1440 if (compile.link_z_notext) {
1441 try zig_args.append("-z");
1442 try zig_args.append("notext");
1443 }
1444 if (!compile.link_z_relro) {
1445 try zig_args.append("-z");
1446 try zig_args.append("norelro");
1447 }
1448 if (compile.link_z_lazy) {
1449 try zig_args.append("-z");
1450 try zig_args.append("lazy");
1451 }
1452 if (compile.link_z_common_page_size) |size| {
1453 try zig_args.append("-z");
1454 try zig_args.append(b.fmt("common-page-size={d}", .{size}));
1455 }
1456 if (compile.link_z_max_page_size) |size| {
1457 try zig_args.append("-z");
1458 try zig_args.append(b.fmt("max-page-size={d}", .{size}));
1459 }
1460 if (compile.link_z_defs) {
1461 try zig_args.append("-z");
1462 try zig_args.append("defs");
1463 }
1464
1465 if (compile.libc_file) |libc_file| {
1466 try zig_args.append("--libc");
1467 try zig_args.append(libc_file.getPath2(b, step));
1468 } else if (b.libc_file) |libc_file| {
1469 try zig_args.append("--libc");
1470 try zig_args.append(libc_file);
1471 }
1472
1473 try zig_args.append("--cache-dir");
1474 try zig_args.append(b.cache_root.path orelse ".");
1475
1476 try zig_args.append("--global-cache-dir");
1477 try zig_args.append(b.graph.global_cache_root.path orelse ".");
1478
1479 if (b.graph.debug_compiler_runtime_libs) |mode|
1480 try zig_args.append(b.fmt("--debug-rt={t}", .{mode}));
1481
1482 try zig_args.append("--name");
1483 try zig_args.append(compile.name);
1484
1485 if (compile.linkage) |some| switch (some) {
1486 .dynamic => try zig_args.append("-dynamic"),
1487 .static => try zig_args.append("-static"),
1488 };
1489 if (compile.kind == .lib and compile.linkage != null and compile.linkage.? == .dynamic) {
1490 if (compile.version) |version| {
1491 try zig_args.append("--version");
1492 try zig_args.append(b.fmt("{f}", .{version}));
1493 }
1494
1495 if (compile.rootModuleTarget().os.tag.isDarwin()) {
1496 const install_name = compile.install_name orelse b.fmt("@rpath/{s}{s}{s}", .{
1497 compile.rootModuleTarget().libPrefix(),
1498 compile.name,
1499 compile.rootModuleTarget().dynamicLibSuffix(),
1500 });
1501 try zig_args.append("-install_name");
1502 try zig_args.append(install_name);
1503 }
1504 }
1505
1506 if (compile.entitlements) |entitlements| {
1507 try zig_args.appendSlice(&[_][]const u8{ "--entitlements", entitlements });
1508 }
1509 if (compile.pagezero_size) |pagezero_size| {
1510 const size = try std.fmt.allocPrint(arena, "{x}", .{pagezero_size});
1511 try zig_args.appendSlice(&[_][]const u8{ "-pagezero_size", size });
1512 }
1513 if (compile.headerpad_size) |headerpad_size| {
1514 const size = try std.fmt.allocPrint(arena, "{x}", .{headerpad_size});
1515 try zig_args.appendSlice(&[_][]const u8{ "-headerpad", size });
1516 }
1517 if (compile.headerpad_max_install_names) {
1518 try zig_args.append("-headerpad_max_install_names");
1519 }
1520 if (compile.dead_strip_dylibs) {
1521 try zig_args.append("-dead_strip_dylibs");
1522 }
1523 if (compile.force_load_objc) {
1524 try zig_args.append("-ObjC");
1525 }
1526 if (compile.discard_local_symbols) {
1527 try zig_args.append("--discard-all");
1528 }
1529
1530 try addFlag(&zig_args, "compiler-rt", compile.bundle_compiler_rt);
1531 try addFlag(&zig_args, "ubsan-rt", compile.bundle_ubsan_rt);
1532 try addFlag(&zig_args, "dll-export-fns", compile.dll_export_fns);
1533 if (compile.rdynamic) {
1534 try zig_args.append("-rdynamic");
1535 }
1536 if (compile.import_memory) {
1537 try zig_args.append("--import-memory");
1538 }
1539 if (compile.export_memory) {
1540 try zig_args.append("--export-memory");
1541 }
1542 if (compile.import_symbols) {
1543 try zig_args.append("--import-symbols");
1544 }
1545 if (compile.import_table) {
1546 try zig_args.append("--import-table");
1547 }
1548 if (compile.export_table) {
1549 try zig_args.append("--export-table");
1550 }
1551 if (compile.initial_memory) |initial_memory| {
1552 try zig_args.append(b.fmt("--initial-memory={d}", .{initial_memory}));
1553 }
1554 if (compile.max_memory) |max_memory| {
1555 try zig_args.append(b.fmt("--max-memory={d}", .{max_memory}));
1556 }
1557 if (compile.shared_memory) {
1558 try zig_args.append("--shared-memory");
1559 }
1560 if (compile.global_base) |global_base| {
1561 try zig_args.append(b.fmt("--global-base={d}", .{global_base}));
1562 }
1563
1564 if (compile.wasi_exec_model) |model| {
1565 try zig_args.append(b.fmt("-mexec-model={s}", .{@tagName(model)}));
1566 }
1567 if (compile.linker_script) |linker_script| {
1568 try zig_args.append("--script");
1569 try zig_args.append(linker_script.getPath2(b, step));
1570 }
1571
1572 if (compile.version_script) |version_script| {
1573 try zig_args.append("--version-script");
1574 try zig_args.append(version_script.getPath2(b, step));
1575 }
1576 if (compile.linker_allow_undefined_version) |x| {
1577 try zig_args.append(if (x) "--undefined-version" else "--no-undefined-version");
1578 }
1579
1580 if (compile.linker_enable_new_dtags) |enabled| {
1581 try zig_args.append(if (enabled) "--enable-new-dtags" else "--disable-new-dtags");
1582 }
1583
1584 if (compile.kind == .@"test") {
1585 if (compile.exec_cmd_args) |exec_cmd_args| {
1586 for (exec_cmd_args) |cmd_arg| {
1587 if (cmd_arg) |arg| {
1588 try zig_args.append("--test-cmd");
1589 try zig_args.append(arg);
1590 } else {
1591 try zig_args.append("--test-cmd-bin");
1592 }
1593 }
1594 }
1595 }
1596
1597 if (b.sysroot) |sysroot| {
1598 try zig_args.appendSlice(&[_][]const u8{ "--sysroot", sysroot });
1599 }
1600
1601 // -I and -L arguments that appear after the last --mod argument apply to all modules.
1602 const cwd: Io.Dir = .cwd();
1603 const io = b.graph.io;
1604
1605 for (b.search_prefixes.items) |search_prefix| {
1606 var prefix_dir = cwd.openDir(io, search_prefix, .{}) catch |err| {
1607 return step.fail("unable to open prefix directory '{s}': {s}", .{
1608 search_prefix, @errorName(err),
1609 });
1610 };
1611 defer prefix_dir.close(io);
1612
1613 // Avoid passing -L and -I flags for nonexistent directories.
1614 // This prevents a warning, that should probably be upgraded to an error in Zig's
1615 // CLI parsing code, when the linker sees an -L directory that does not exist.
1616
1617 if (prefix_dir.access(io, "lib", .{})) |_| {
1618 try zig_args.appendSlice(&.{
1619 "-L", b.pathJoin(&.{ search_prefix, "lib" }),
1620 });
1621 } else |err| switch (err) {
1622 error.FileNotFound => {},
1623 else => |e| return step.fail("unable to access '{s}/lib' directory: {s}", .{
1624 search_prefix, @errorName(e),
1625 }),
1626 }
1627
1628 if (prefix_dir.access(io, "include", .{})) |_| {
1629 try zig_args.appendSlice(&.{
1630 "-I", b.pathJoin(&.{ search_prefix, "include" }),
1631 });
1632 } else |err| switch (err) {
1633 error.FileNotFound => {},
1634 else => |e| return step.fail("unable to access '{s}/include' directory: {s}", .{
1635 search_prefix, @errorName(e),
1636 }),
1637 }
1638 }
1639
1640 if (compile.rc_includes != .any) {
1641 try zig_args.append("-rcincludes");
1642 try zig_args.append(@tagName(compile.rc_includes));
1643 }
1644
1645 try addFlag(&zig_args, "each-lib-rpath", compile.each_lib_rpath);
1646
1647 if (compile.build_id orelse b.build_id) |build_id| {
1648 try zig_args.append(switch (build_id) {
1649 .hexstring => |hs| b.fmt("--build-id=0x{x}", .{hs.toSlice()}),
1650 .none, .fast, .uuid, .sha1, .md5 => b.fmt("--build-id={s}", .{@tagName(build_id)}),
1651 });
1652 }
1653
1654 const opt_zig_lib_dir = if (compile.zig_lib_dir) |dir|
1655 dir.getPath2(b, step)
1656 else if (b.graph.zig_lib_directory.path) |_|
1657 b.fmt("{f}", .{b.graph.zig_lib_directory})
1658 else
1659 null;
1660
1661 if (opt_zig_lib_dir) |zig_lib_dir| {
1662 try zig_args.append("--zig-lib-dir");
1663 try zig_args.append(zig_lib_dir);
1664 }
1665
1666 try addFlag(&zig_args, "PIE", compile.pie);
1667
1668 if (compile.lto) |lto| {
1669 try zig_args.append(switch (lto) {
1670 .full => "-flto=full",
1671 .thin => "-flto=thin",
1672 .none => "-fno-lto",
1673 });
1674 }
1675
1676 try addFlag(&zig_args, "sanitize-coverage-trace-pc-guard", compile.sanitize_coverage_trace_pc_guard);
1677
1678 if (compile.subsystem) |subsystem| {
1679 try zig_args.append("--subsystem");
1680 try zig_args.append(@tagName(subsystem));
1681 }
1682
1683 if (compile.mingw_unicode_entry_point) {
1684 try zig_args.append("-municode");
1685 }
1686
1687 if (compile.error_limit) |err_limit| try zig_args.appendSlice(&.{
1688 "--error-limit", b.fmt("{d}", .{err_limit}),
1689 });
1690
1691 try addFlag(&zig_args, "incremental", b.graph.incremental);
1692
1693 try zig_args.append("--listen=-");
1694
1695 // Windows has an argument length limit of 32,766 characters, macOS 262,144 and Linux
1696 // 2,097,152. If our args exceed 30 KiB, we instead write them to a "response file" and
1697 // pass that to zig, e.g. via 'zig build-lib @args.rsp'
1698 // See @file syntax here: https://gcc.gnu.org/onlinedocs/gcc/Overall-Options.html
1699 var args_length: usize = 0;
1700 for (zig_args.items) |arg| {
1701 args_length += arg.len + 1; // +1 to account for null terminator
1702 }
1703 if (args_length >= 30 * 1024) {
1704 try b.cache_root.handle.createDirPath(io, "args");
1705
1706 const args_to_escape = zig_args.items[2..];
1707 var escaped_args = try std.array_list.Managed([]const u8).initCapacity(arena, args_to_escape.len);
1708 arg_blk: for (args_to_escape) |arg| {
1709 for (arg, 0..) |c, arg_idx| {
1710 if (c == '\\' or c == '"') {
1711 // Slow path for arguments that need to be escaped. We'll need to allocate and copy
1712 var escaped: std.ArrayList(u8) = .empty;
1713 try escaped.ensureTotalCapacityPrecise(arena, arg.len + 1);
1714 try escaped.appendSlice(arena, arg[0..arg_idx]);
1715 for (arg[arg_idx..]) |to_escape| {
1716 if (to_escape == '\\' or to_escape == '"') try escaped.append(arena, '\\');
1717 try escaped.append(arena, to_escape);
1718 }
1719 escaped_args.appendAssumeCapacity(escaped.items);
1720 continue :arg_blk;
1721 }
1722 }
1723 escaped_args.appendAssumeCapacity(arg); // no escaping needed so just use original argument
1724 }
1725
1726 // Write the args to zig-cache/args/<SHA256 hash of args> to avoid conflicts with
1727 // other zig build commands running in parallel.
1728 const partially_quoted = try std.mem.join(arena, "\" \"", escaped_args.items);
1729 const args = try std.mem.concat(arena, u8, &[_][]const u8{ "\"", partially_quoted, "\"" });
1730
1731 var args_hash: [Sha256.digest_length]u8 = undefined;
1732 Sha256.hash(args, &args_hash, .{});
1733 var args_hex_hash: [Sha256.digest_length * 2]u8 = undefined;
1734 _ = try std.fmt.bufPrint(&args_hex_hash, "{x}", .{&args_hash});
1735
1736 const args_file = "args" ++ fs.path.sep_str ++ args_hex_hash;
1737 if (b.cache_root.handle.access(io, args_file, .{})) |_| {
1738 // The args file is already present from a previous run.
1739 } else |err| switch (err) {
1740 error.FileNotFound => {
1741 var af = b.cache_root.handle.createFileAtomic(io, args_file, .{
1742 .replace = false,
1743 .make_path = true,
1744 }) catch |e| return step.fail("failed creating tmp args file {f}{s}: {t}", .{
1745 b.cache_root, args_file, e,
1746 });
1747 defer af.deinit(io);
1748
1749 af.file.writeStreamingAll(io, args) catch |e| {
1750 return step.fail("failed writing args data to tmp file {f}{s}: {t}", .{
1751 b.cache_root, args_file, e,
1752 });
1753 };
1754 // Note we can't clean up this file, not even after build
1755 // success, because that might interfere with another build
1756 // process that needs the same file.
1757 af.link(io) catch |e| switch (e) {
1758 error.PathAlreadyExists => {
1759 // The args file was created by another concurrent build process.
1760 },
1761 else => |other_err| return step.fail("failed linking tmp file {f}{s}: {t}", .{
1762 b.cache_root, args_file, other_err,
1763 }),
1764 };
1765 },
1766 else => |other_err| return other_err,
1767 }
1768
1769 const resolved_args_file = try mem.concat(arena, u8, &.{
1770 "@",
1771 try b.cache_root.join(arena, &.{args_file}),
1772 });
1773
1774 zig_args.shrinkRetainingCapacity(2);
1775 try zig_args.append(resolved_args_file);
1776 }
1777
1778 return try zig_args.toOwnedSlice();
1779}
1780
1781fn make(step: *Step, options: Step.MakeOptions) !void {
1782 const b = step.owner;
1783 const compile: *Compile = @fieldParentPtr("step", step);
1784
1785 const zig_args = try getZigArgs(compile, false);
1786
1787 const maybe_output_dir = step.evalZigProcess(
1788 zig_args,
1789 options.progress_node,
1790 (b.graph.incremental == true) and (options.watch or options.web_server != null),
1791 options.web_server,
1792 options.gpa,
1793 ) catch |err| switch (err) {
1794 error.NeedCompileErrorCheck => {
1795 assert(compile.expect_errors != null);
1796 try checkCompileErrors(compile);
1797 return;
1798 },
1799 else => |e| return e,
1800 };
1801
1802 // Update generated files
1803 if (maybe_output_dir) |output_dir| {
1804 if (compile.emit_directory) |lp| {
1805 lp.path = b.fmt("{f}", .{output_dir});
1806 }
1807
1808 // zig fmt: off
1809 if (compile.generated_bin) |lp| lp.path = compile.outputPath(output_dir, .bin);
1810 if (compile.generated_pdb) |lp| lp.path = compile.outputPath(output_dir, .pdb);
1811 // hack for stage2_x86_64 + coff
1812 if (compile.generated_compiler_rt_dyn_lib) |lp| lp.path = compile.outputPath(output_dir, .compiler_rt_dyn_lib);
1813 if (compile.generated_implib) |lp| lp.path = compile.outputPath(output_dir, .implib);
1814 if (compile.generated_h) |lp| lp.path = compile.outputPath(output_dir, .h);
1815 if (compile.generated_docs) |lp| lp.path = compile.outputPath(output_dir, .docs);
1816 if (compile.generated_asm) |lp| lp.path = compile.outputPath(output_dir, .@"asm");
1817 if (compile.generated_llvm_ir) |lp| lp.path = compile.outputPath(output_dir, .llvm_ir);
1818 if (compile.generated_llvm_bc) |lp| lp.path = compile.outputPath(output_dir, .llvm_bc);
1819 // zig fmt: on
1820 }
1821
1822 if (compile.kind == .lib and compile.linkage != null and compile.linkage.? == .dynamic and
1823 compile.version != null and compile.generated_bin != null and
1824 std.Build.wantSharedLibSymLinks(compile.rootModuleTarget()))
1825 {
1826 try doAtomicSymLinks(
1827 step,
1828 compile.getEmittedBin().getPath2(b, step),
1829 compile.major_only_filename.?,
1830 compile.name_only_filename.?,
1831 );
1832 }
1833}
1834fn outputPath(c: *Compile, out_dir: std.Build.Cache.Path, ea: std.zig.EmitArtifact) []const u8 {
1835 const arena = c.step.owner.graph.arena;
1836 const name = ea.cacheName(arena, .{
1837 .root_name = c.name,
1838 .target = &c.root_module.resolved_target.?.result,
1839 .output_mode = switch (c.kind) {
1840 .lib => .Lib,
1841 .obj, .test_obj => .Obj,
1842 .exe, .@"test" => .Exe,
1843 },
1844 .link_mode = c.linkage,
1845 .version = c.version,
1846 }) catch @panic("OOM");
1847 return out_dir.joinString(arena, name) catch @panic("OOM");
1848}
1849
1850pub fn rebuildInFuzzMode(c: *Compile, gpa: Allocator, progress_node: std.Progress.Node) !Path {
1851 c.step.result_error_msgs.clearRetainingCapacity();
1852 c.step.result_stderr = "";
1853
1854 c.step.result_error_bundle.deinit(gpa);
1855 c.step.result_error_bundle = std.zig.ErrorBundle.empty;
1856
1857 if (c.step.result_failed_command) |cmd| {
1858 gpa.free(cmd);
1859 c.step.result_failed_command = null;
1860 }
1861
1862 const zig_args = try getZigArgs(c, true);
1863 const maybe_output_bin_path = try c.step.evalZigProcess(zig_args, progress_node, false, null, gpa);
1864 return maybe_output_bin_path.?;
1865}
1866
1867pub fn doAtomicSymLinks(
1868 step: *Step,
1869 output_path: []const u8,
1870 filename_major_only: []const u8,
1871 filename_name_only: []const u8,
1872) !void {
1873 const b = step.owner;
1874 const io = b.graph.io;
1875 const out_dir = fs.path.dirname(output_path) orelse ".";
1876 const out_basename = fs.path.basename(output_path);
1877 // sym link for libfoo.so.1 to libfoo.so.1.2.3
1878 const major_only_path = b.pathJoin(&.{ out_dir, filename_major_only });
1879 const cwd: Io.Dir = .cwd();
1880 cwd.symLinkAtomic(io, out_basename, major_only_path, .{}) catch |err| {
1881 return step.fail("unable to symlink {s} -> {s}: {s}", .{
1882 major_only_path, out_basename, @errorName(err),
1883 });
1884 };
1885 // sym link for libfoo.so to libfoo.so.1
1886 const name_only_path = b.pathJoin(&.{ out_dir, filename_name_only });
1887 cwd.symLinkAtomic(io, filename_major_only, name_only_path, .{}) catch |err| {
1888 return step.fail("Unable to symlink {s} -> {s}: {s}", .{
1889 name_only_path, filename_major_only, @errorName(err),
1890 });
1891 };
1892}
1893
1894fn execPkgConfigList(b: *std.Build, out_code: *u8) (PkgConfigError || RunError)![]const PkgConfigPkg {
1895 const pkg_config_exe = b.graph.environ_map.get("PKG_CONFIG") orelse "pkg-config";
1896 const stdout = try b.runAllowFail(&[_][]const u8{ pkg_config_exe, "--list-all" }, out_code, .ignore);
1897 var list = std.array_list.Managed(PkgConfigPkg).init(b.allocator);
1898 errdefer list.deinit();
1899 var line_it = mem.tokenizeAny(u8, stdout, "\r\n");
1900 while (line_it.next()) |line| {
1901 if (mem.trim(u8, line, " \t").len == 0) continue;
1902 var tok_it = mem.tokenizeAny(u8, line, " \t");
1903 try list.append(PkgConfigPkg{
1904 .name = tok_it.next() orelse return error.PkgConfigInvalidOutput,
1905 .desc = tok_it.rest(),
1906 });
1907 }
1908 return list.toOwnedSlice();
1909}
1910
1911fn getPkgConfigList(b: *std.Build) ![]const PkgConfigPkg {
1912 if (b.pkg_config_pkg_list) |res| {
1913 return res;
1914 }
1915 var code: u8 = undefined;
1916 if (execPkgConfigList(b, &code)) |list| {
1917 b.pkg_config_pkg_list = list;
1918 return list;
1919 } else |err| {
1920 const result = switch (err) {
1921 error.ProcessTerminated => error.PkgConfigCrashed,
1922 error.ExecNotSupported => error.PkgConfigFailed,
1923 error.ExitCodeFailure => error.PkgConfigFailed,
1924 error.FileNotFound => error.PkgConfigNotInstalled,
1925 error.InvalidName => error.PkgConfigNotInstalled,
1926 error.PkgConfigInvalidOutput => error.PkgConfigInvalidOutput,
1927 else => return err,
1928 };
1929 b.pkg_config_pkg_list = result;
1930 return result;
1931 }
1932}
1933
1934fn addFlag(args: *std.array_list.Managed([]const u8), comptime name: []const u8, opt: ?bool) !void {
1935 const cond = opt orelse return;
1936 try args.ensureUnusedCapacity(1);
1937 if (cond) {
1938 args.appendAssumeCapacity("-f" ++ name);
1939 } else {
1940 args.appendAssumeCapacity("-fno-" ++ name);
1941 }
1942}
1943
1944fn checkCompileErrors(compile: *Compile) !void {
1945 // Clear this field so that it does not get printed by the build runner.
1946 const actual_eb = compile.step.result_error_bundle;
1947 compile.step.result_error_bundle = .empty;
1948
1949 const arena = compile.step.owner.allocator;
1950
1951 const actual_errors = ae: {
1952 var aw: std.Io.Writer.Allocating = .init(arena);
1953 defer aw.deinit();
1954 try actual_eb.renderToWriter(.{
1955 .include_reference_trace = false,
1956 .include_source_line = false,
1957 }, &aw.writer);
1958 break :ae try aw.toOwnedSlice();
1959 };
1960
1961 // Render the expected lines into a string that we can compare verbatim.
1962 var expected_generated: std.ArrayList(u8) = .empty;
1963 const expect_errors = compile.expect_errors.?;
1964
1965 var actual_line_it = mem.splitScalar(u8, actual_errors, '\n');
1966
1967 // TODO merge this with the testing.expectEqualStrings logic, and also CheckFile
1968 switch (expect_errors) {
1969 .starts_with => |expect_starts_with| {
1970 if (std.mem.startsWith(u8, actual_errors, expect_starts_with)) return;
1971 return compile.step.fail(
1972 \\
1973 \\========= should start with: ============
1974 \\{s}
1975 \\========= but not found: ================
1976 \\{s}
1977 \\=========================================
1978 , .{ expect_starts_with, actual_errors });
1979 },
1980 .contains => |expect_line| {
1981 while (actual_line_it.next()) |actual_line| {
1982 if (!matchCompileError(actual_line, expect_line)) continue;
1983 return;
1984 }
1985
1986 return compile.step.fail(
1987 \\
1988 \\========= should contain: ===============
1989 \\{s}
1990 \\========= but not found: ================
1991 \\{s}
1992 \\=========================================
1993 , .{ expect_line, actual_errors });
1994 },
1995 .stderr_contains => |expect_line| {
1996 const actual_stderr: []const u8 = if (compile.step.result_error_msgs.items.len > 0)
1997 compile.step.result_error_msgs.items[0]
1998 else
1999 &.{};
2000 compile.step.result_error_msgs.clearRetainingCapacity();
2001
2002 var stderr_line_it = mem.splitScalar(u8, actual_stderr, '\n');
2003
2004 while (stderr_line_it.next()) |actual_line| {
2005 if (!matchCompileError(actual_line, expect_line)) continue;
2006 return;
2007 }
2008
2009 return compile.step.fail(
2010 \\
2011 \\========= should contain: ===============
2012 \\{s}
2013 \\========= but not found: ================
2014 \\{s}
2015 \\=========================================
2016 , .{ expect_line, actual_stderr });
2017 },
2018 .exact => |expect_lines| {
2019 for (expect_lines) |expect_line| {
2020 const actual_line = actual_line_it.next() orelse {
2021 try expected_generated.appendSlice(arena, expect_line);
2022 try expected_generated.append(arena, '\n');
2023 continue;
2024 };
2025 if (matchCompileError(actual_line, expect_line)) {
2026 try expected_generated.appendSlice(arena, actual_line);
2027 try expected_generated.append(arena, '\n');
2028 continue;
2029 }
2030 try expected_generated.appendSlice(arena, expect_line);
2031 try expected_generated.append(arena, '\n');
2032 }
2033
2034 if (mem.eql(u8, expected_generated.items, actual_errors)) return;
2035
2036 return compile.step.fail(
2037 \\
2038 \\========= expected: =====================
2039 \\{s}
2040 \\========= but found: ====================
2041 \\{s}
2042 \\=========================================
2043 , .{ expected_generated.items, actual_errors });
2044 },
2045 }
2046}
2047
2048fn matchCompileError(actual: []const u8, expected: []const u8) bool {
2049 if (mem.endsWith(u8, actual, expected)) return true;
2050 if (mem.startsWith(u8, expected, ":?:?: ")) {
2051 if (mem.endsWith(u8, actual, expected[":?:?: ".len..])) return true;
2052 }
2053 // We scan for /?/ in expected line and if there is a match, we match everything
2054 // up to and after /?/.
2055 const expected_trim = mem.trim(u8, expected, " ");
2056 if (mem.find(u8, expected_trim, "/?/")) |index| {
2057 const actual_trim = mem.trim(u8, actual, " ");
2058 const lhs = expected_trim[0..index];
2059 const rhs = expected_trim[index + "/?/".len ..];
2060 if (mem.startsWith(u8, actual_trim, lhs) and mem.endsWith(u8, actual_trim, rhs)) return true;
2061 }
2062 return false;
2063}
2064
2065750pub fn rootModuleTarget(c: *Compile) std.Target {
2066751 // The root module is always given a target, so we know this to be non-null.
2067752 return c.root_module.resolved_target.?.result;
2068753}
2069754
2070fn moduleNeedsCliArg(mod: *const Module) bool {
2071 return for (mod.link_objects.items) |o| switch (o) {
2072 .c_source_file, .c_source_files, .assembly_file, .win32_resource_file => break true,
2073 else => continue,
2074 } else false;
2075}
2076
2077755/// Return the full set of `Step.Compile` which `start` depends on, recursively. `start` itself is
2078756/// always returned as the first element. If `chase_dynamic` is `false`, then dynamic libraries are
2079757/// not included, and their dependencies are not considered; if `chase_dynamic` is `true`, dynamic
lib/std/Build/Step/ConfigHeader.zig+55-944
......@@ -4,7 +4,20 @@ const std = @import("std");
44const Io = std.Io;
55const Step = std.Build.Step;
66const Allocator = std.mem.Allocator;
7const Writer = std.Io.Writer;
7const Configuration = std.Build.Configuration;
8const allocPrint = std.fmt.allocPrint;
9
10step: Step,
11values: std.array_hash_map.String(Value) = .empty,
12/// This directory contains the generated file under the name `include_path`.
13generated_dir: Configuration.GeneratedFileIndex,
14
15style: Style,
16input_size_limit: ?u64,
17include_path: []const u8,
18include_guard: Configuration.OptionalString,
19
20pub const base_tag: Step.Tag = .config_header;
821
922pub const Style = union(enum) {
1023 /// A configure format supported by autotools that uses `#undef foo` to
......@@ -37,70 +50,60 @@ pub const Value = union(enum) {
3750 string: []const u8,
3851};
3952
40step: Step,
41values: std.array_hash_map.String(Value),
42/// This directory contains the generated file under the name `include_path`.
43generated_dir: std.Build.GeneratedFile,
44
45style: Style,
46max_bytes: usize,
47include_path: []const u8,
48include_guard_override: ?[]const u8,
49
50pub const base_id: Step.Id = .config_header;
51
5253pub const Options = struct {
5354 style: Style = .blank,
54 max_bytes: usize = 2 * 1024 * 1024,
55 max_bytes: ?u64 = null,
5556 include_path: ?[]const u8 = null,
57 include_guard: ?[]const u8 = null,
5658 first_ret_addr: ?usize = null,
57 include_guard_override: ?[]const u8 = null,
5859};
5960
6061pub fn create(owner: *std.Build, options: Options) *ConfigHeader {
61 const config_header = owner.allocator.create(ConfigHeader) catch @panic("OOM");
62
63 var include_path: []const u8 = "config.h";
64
65 if (options.style.getPath()) |s| default_include_path: {
66 const sub_path = switch (s) {
67 .src_path => |sp| sp.sub_path,
68 .generated => break :default_include_path,
69 .cwd_relative => |sub_path| sub_path,
70 .dependency => |dependency| dependency.sub_path,
71 };
72 const basename = std.fs.path.basename(sub_path);
73 if (std.mem.endsWith(u8, basename, ".h.in")) {
74 include_path = basename[0 .. basename.len - 3];
62 const graph = owner.graph;
63 const arena = graph.arena;
64 const wc = &graph.wip_configuration;
65 const config_header = graph.create(ConfigHeader);
66
67 const include_path: []const u8 = p: {
68 if (options.include_path) |p|
69 break :p graph.dupeString(p);
70
71 if (options.style.getPath()) |s| default: {
72 const sub_path = switch (s) {
73 .src_path => |sp| sp.sub_path,
74 .generated => break :default,
75 .cwd_relative => |sub_path| sub_path,
76 .relative => |r| r.sub_path,
77 .dependency => |dependency| dependency.sub_path,
78 };
79 const basename = Io.Dir.path.basename(sub_path);
80 if (std.mem.endsWith(u8, basename, ".h.in"))
81 break :p graph.dupeString(basename[0 .. basename.len - 3]);
7582 }
76 }
77
78 if (options.include_path) |p| {
79 include_path = p;
80 }
83 break :p "config.h";
84 };
8185
8286 const name = if (options.style.getPath()) |s|
83 owner.fmt("configure {s} header {s} to {s}", .{
84 @tagName(options.style), s.getDisplayName(), include_path,
85 })
87 allocPrint(arena, "configure {t} header {f} to {s}", .{
88 options.style, s, include_path,
89 }) catch @panic("OOM")
8690 else
87 owner.fmt("configure {s} header to {s}", .{ @tagName(options.style), include_path });
91 allocPrint(arena, "configure {t} header to {s}", .{
92 options.style, include_path,
93 }) catch @panic("OOM");
8894
8995 config_header.* = .{
9096 .step = .init(.{
91 .id = base_id,
97 .tag = base_tag,
9298 .name = name,
9399 .owner = owner,
94 .makeFn = make,
95100 .first_ret_addr = options.first_ret_addr orelse @returnAddress(),
96101 }),
97102 .style = options.style,
98 .values = .empty,
99
100 .max_bytes = options.max_bytes,
103 .input_size_limit = options.max_bytes,
101104 .include_path = include_path,
102 .include_guard_override = options.include_guard_override,
103 .generated_dir = .{ .step = &config_header.step },
105 .include_guard = if (options.include_guard) |s| .init(wc.addString(s) catch @panic("OOM")) else .none,
106 .generated_dir = graph.addGeneratedFile(&config_header.step),
104107 };
105108
106109 if (options.style.getPath()) |s| {
......@@ -118,19 +121,6 @@ pub fn addValue(config_header: *ConfigHeader, name: []const u8, comptime T: type
118121 return addValueInner(config_header, name, T, value) catch @panic("OOM");
119122}
120123
121pub fn addValues(config_header: *ConfigHeader, values: anytype) void {
122 inline for (@typeInfo(@TypeOf(values)).@"struct".fields) |field| {
123 addValue(config_header, field.name, field.type, @field(values, field.name));
124 }
125}
126
127pub fn getOutputDir(ch: *ConfigHeader) std.Build.LazyPath {
128 return .{ .generated = .{ .file = &ch.generated_dir } };
129}
130pub fn getOutputFile(ch: *ConfigHeader) std.Build.LazyPath {
131 return ch.getOutputDir().path(ch.step.owner, ch.include_path);
132}
133
134124fn addValueInner(config_header: *ConfigHeader, name: []const u8, comptime T: type, value: T) !void {
135125 const arena = config_header.step.owner.allocator;
136126 switch (@typeInfo(T)) {
......@@ -182,895 +172,16 @@ fn addValueInner(config_header: *ConfigHeader, name: []const u8, comptime T: typ
182172 }
183173}
184174
185fn make(step: *Step, options: Step.MakeOptions) !void {
186 _ = options;
187 const b = step.owner;
188 const config_header: *ConfigHeader = @fieldParentPtr("step", step);
189 if (config_header.style.getPath()) |lp| try step.singleUnchangingWatchInput(lp);
190
191 const gpa = b.allocator;
192 const arena = b.allocator;
193 const io = b.graph.io;
194
195 var man = b.graph.cache.obtain();
196 defer man.deinit();
197
198 // Random bytes to make ConfigHeader unique. Refresh this with new
199 // random bytes when ConfigHeader implementation is modified in a
200 // non-backwards-compatible way.
201 man.hash.add(@as(u32, 0xdef08d23));
202 man.hash.addBytes(config_header.include_path);
203 man.hash.addOptionalBytes(config_header.include_guard_override);
204
205 var aw: Writer.Allocating = .init(gpa);
206 defer aw.deinit();
207 const bw = &aw.writer;
208
209 const header_text = "This file was generated by ConfigHeader using the Zig Build System.";
210 const c_generated_line = "/* " ++ header_text ++ " */\n";
211 const asm_generated_line = "; " ++ header_text ++ "\n";
212
213 switch (config_header.style) {
214 .autoconf_undef, .autoconf_at => |file_source| {
215 try bw.writeAll(c_generated_line);
216 const src_path = file_source.getPath2(b, step);
217 const contents = Io.Dir.cwd().readFileAlloc(io, src_path, arena, .limited(config_header.max_bytes)) catch |err| {
218 return step.fail("unable to read autoconf input file '{s}': {s}", .{
219 src_path, @errorName(err),
220 });
221 };
222 switch (config_header.style) {
223 .autoconf_undef => try render_autoconf_undef(step, contents, bw, &config_header.values, src_path),
224 .autoconf_at => try render_autoconf_at(step, contents, &aw, &config_header.values, src_path),
225 else => unreachable,
226 }
227 },
228 .cmake => |file_source| {
229 try bw.writeAll(c_generated_line);
230 const src_path = file_source.getPath2(b, step);
231 const contents = Io.Dir.cwd().readFileAlloc(io, src_path, arena, .limited(config_header.max_bytes)) catch |err| {
232 return step.fail("unable to read cmake input file '{s}': {s}", .{
233 src_path, @errorName(err),
234 });
235 };
236 try render_cmake(step, contents, bw, config_header.values, src_path);
237 },
238 .blank => {
239 try bw.writeAll(c_generated_line);
240 try render_blank(gpa, bw, config_header.values, config_header.include_path, config_header.include_guard_override);
241 },
242 .nasm => {
243 try bw.writeAll(asm_generated_line);
244 try render_nasm(bw, config_header.values);
245 },
246 }
247
248 const output = aw.written();
249 man.hash.addBytes(output);
250
251 if (try step.cacheHit(&man)) {
252 const digest = man.final();
253 config_header.generated_dir.path = try b.cache_root.join(arena, &.{ "o", &digest });
254 return;
255 }
256
257 const digest = man.final();
258
259 // If output_path has directory parts, deal with them. Example:
260 // output_dir is zig-cache/o/HASH
261 // output_path is libavutil/avconfig.h
262 // We want to open directory zig-cache/o/HASH/libavutil/
263 // but keep output_dir as zig-cache/o/HASH for -I include
264 const sub_path = b.pathJoin(&.{ "o", &digest, config_header.include_path });
265 const sub_path_dirname = std.fs.path.dirname(sub_path).?;
266
267 b.cache_root.handle.createDirPath(io, sub_path_dirname) catch |err| {
268 return step.fail("unable to make path '{f}{s}': {s}", .{
269 b.cache_root, sub_path_dirname, @errorName(err),
270 });
271 };
272
273 b.cache_root.handle.writeFile(io, .{ .sub_path = sub_path, .data = output }) catch |err| {
274 return step.fail("unable to write file '{f}{s}': {s}", .{
275 b.cache_root, sub_path, @errorName(err),
276 });
277 };
278
279 config_header.generated_dir.path = try b.cache_root.join(arena, &.{ "o", &digest });
280 try man.writeManifest();
281}
282
283fn render_autoconf_undef(
284 step: *Step,
285 contents: []const u8,
286 bw: *Writer,
287 values: *const std.array_hash_map.String(Value),
288 src_path: []const u8,
289) !void {
290 const build = step.owner;
291 const allocator = build.allocator;
292
293 var is_used: std.bit_set.Dynamic = try .initEmpty(allocator, values.count());
294 defer is_used.deinit(allocator);
295
296 var any_errors = false;
297 var line_index: u32 = 0;
298 var line_it = std.mem.splitScalar(u8, contents, '\n');
299 while (line_it.next()) |line| : (line_index += 1) {
300 if (!std.mem.startsWith(u8, line, "#")) {
301 try bw.writeAll(line);
302 try bw.writeByte('\n');
303 continue;
304 }
305 var it = std.mem.tokenizeAny(u8, line[1..], " \t\r");
306 const undef = it.next().?;
307 if (!std.mem.eql(u8, undef, "undef")) {
308 try bw.writeAll(line);
309 try bw.writeByte('\n');
310 continue;
311 }
312 const name = it.next().?;
313 const index = values.getIndex(name) orelse {
314 try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{
315 src_path, line_index + 1, name,
316 });
317 any_errors = true;
318 continue;
319 };
320 is_used.set(index);
321 try renderValueC(bw, name, values.values()[index]);
322 }
323
324 var unused_value_it = is_used.iterator(.{ .kind = .unset });
325 while (unused_value_it.next()) |index| {
326 try step.addError("{s}: error: config header value unused: '{s}'", .{ src_path, values.keys()[index] });
327 any_errors = true;
328 }
329
330 if (any_errors) {
331 return error.MakeFailed;
332 }
333}
334
335fn render_autoconf_at(
336 step: *Step,
337 contents: []const u8,
338 aw: *Writer.Allocating,
339 values: *const std.array_hash_map.String(Value),
340 src_path: []const u8,
341) !void {
342 const build = step.owner;
343 const allocator = build.allocator;
344 const bw = &aw.writer;
345
346 const used = allocator.alloc(bool, values.count()) catch @panic("OOM");
347 for (used) |*u| u.* = false;
348 defer allocator.free(used);
349
350 var any_errors = false;
351 var line_index: u32 = 0;
352 var line_it = std.mem.splitScalar(u8, contents, '\n');
353 while (line_it.next()) |line| : (line_index += 1) {
354 const last_line = line_it.index == line_it.buffer.len;
355
356 const old_len = aw.written().len;
357 expand_variables_autoconf_at(bw, line, values, used) catch |err| switch (err) {
358 error.MissingValue => {
359 const name = aw.written()[old_len..];
360 defer aw.shrinkRetainingCapacity(old_len);
361 try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{
362 src_path, line_index + 1, name,
363 });
364 any_errors = true;
365 continue;
366 },
367 else => {
368 try step.addError("{s}:{d}: unable to substitute variable: error: {s}", .{
369 src_path, line_index + 1, @errorName(err),
370 });
371 any_errors = true;
372 continue;
373 },
374 };
375 if (!last_line) try bw.writeByte('\n');
376 }
377
378 for (values.entries.slice().items(.key), used) |name, u| {
379 if (!u) {
380 try step.addError("{s}: error: config header value unused: '{s}'", .{ src_path, name });
381 any_errors = true;
382 }
383 }
384
385 if (any_errors) return error.MakeFailed;
386}
387
388fn render_cmake(
389 step: *Step,
390 contents: []const u8,
391 bw: *Writer,
392 values: std.array_hash_map.String(Value),
393 src_path: []const u8,
394) !void {
395 const build = step.owner;
396 const allocator = build.allocator;
397
398 var values_copy = try values.clone(allocator);
399 defer values_copy.deinit(allocator);
400
401 var any_errors = false;
402 var line_index: u32 = 0;
403 var line_it = std.mem.splitScalar(u8, contents, '\n');
404 while (line_it.next()) |raw_line| : (line_index += 1) {
405 const last_line = line_it.index == line_it.buffer.len;
406
407 const line = expand_variables_cmake(allocator, raw_line, values) catch |err| switch (err) {
408 error.InvalidCharacter => {
409 try step.addError("{s}:{d}: error: invalid character in a variable name", .{
410 src_path, line_index + 1,
411 });
412 any_errors = true;
413 continue;
414 },
415 else => {
416 try step.addError("{s}:{d}: unable to substitute variable: error: {s}", .{
417 src_path, line_index + 1, @errorName(err),
418 });
419 any_errors = true;
420 continue;
421 },
422 };
423 defer allocator.free(line);
424
425 const line_start = std.mem.findNone(u8, line, " \t\r") orelse {
426 try bw.writeAll(line);
427 if (!last_line) try bw.writeByte('\n');
428 continue;
429 };
430 const whitespace_prefix = line[0..line_start];
431 const trimmed_line = line[line_start..];
432
433 if (!std.mem.startsWith(u8, trimmed_line, "#")) {
434 try bw.writeAll(line);
435 if (!last_line) try bw.writeByte('\n');
436 continue;
437 }
438
439 var it = std.mem.tokenizeAny(u8, trimmed_line[1..], " \t\r");
440 const cmakedefine = it.next().?;
441 if (!std.mem.eql(u8, cmakedefine, "cmakedefine") and
442 !std.mem.eql(u8, cmakedefine, "cmakedefine01"))
443 {
444 try bw.writeAll(line);
445 if (!last_line) try bw.writeByte('\n');
446 continue;
447 }
448
449 const booldefine = std.mem.eql(u8, cmakedefine, "cmakedefine01");
450
451 const name = it.next() orelse {
452 try step.addError("{s}:{d}: error: missing define name", .{
453 src_path, line_index + 1,
454 });
455 any_errors = true;
456 continue;
457 };
458 var value = values_copy.get(name) orelse blk: {
459 if (booldefine) {
460 break :blk Value{ .int = 0 };
461 }
462 break :blk Value.undef;
463 };
464
465 value = blk: {
466 switch (value) {
467 .boolean => |b| {
468 if (!b) {
469 break :blk Value.undef;
470 }
471 },
472 .int => |i| {
473 if (i == 0) {
474 break :blk Value.undef;
475 }
476 },
477 .string => |string| {
478 if (string.len == 0) {
479 break :blk Value.undef;
480 }
481 },
482
483 else => {},
484 }
485 break :blk value;
486 };
487
488 if (booldefine) {
489 value = blk: {
490 switch (value) {
491 .undef => {
492 break :blk Value{ .boolean = false };
493 },
494 .defined => {
495 break :blk Value{ .boolean = false };
496 },
497 .boolean => |b| {
498 break :blk Value{ .boolean = b };
499 },
500 .int => |i| {
501 break :blk Value{ .boolean = i != 0 };
502 },
503 .string => |string| {
504 break :blk Value{ .boolean = string.len != 0 };
505 },
506
507 else => {
508 break :blk Value{ .boolean = false };
509 },
510 }
511 };
512 } else if (value != Value.undef) {
513 value = Value{ .ident = it.rest() };
514 }
515
516 try bw.writeAll(whitespace_prefix);
517 try renderValueC(bw, name, value);
518 }
519
520 if (any_errors) {
521 return error.HeaderConfigFailed;
522 }
523}
524
525fn render_blank(
526 gpa: std.mem.Allocator,
527 bw: *Writer,
528 defines: std.array_hash_map.String(Value),
529 include_path: []const u8,
530 include_guard_override: ?[]const u8,
531) !void {
532 const include_guard_name = include_guard_override orelse blk: {
533 const name = try gpa.dupe(u8, include_path);
534 for (name) |*byte| {
535 switch (byte.*) {
536 'a'...'z' => byte.* = byte.* - 'a' + 'A',
537 'A'...'Z', '0'...'9' => continue,
538 else => byte.* = '_',
539 }
540 }
541 break :blk name;
542 };
543 defer if (include_guard_override == null) gpa.free(include_guard_name);
544
545 try bw.print(
546 \\#ifndef {[0]s}
547 \\#define {[0]s}
548 \\
549 , .{include_guard_name});
550
551 const values = defines.values();
552 for (defines.keys(), 0..) |name, i| try renderValueC(bw, name, values[i]);
553
554 try bw.print(
555 \\#endif /* {s} */
556 \\
557 , .{include_guard_name});
558}
559
560fn render_nasm(bw: *Writer, defines: std.array_hash_map.String(Value)) !void {
561 for (defines.keys(), defines.values()) |name, value| try renderValueNasm(bw, name, value);
562}
563
564fn renderValueC(bw: *Writer, name: []const u8, value: Value) !void {
565 switch (value) {
566 .undef => try bw.print("/* #undef {s} */\n", .{name}),
567 .defined => try bw.print("#define {s}\n", .{name}),
568 .boolean => |b| try bw.print("#define {s} {c}\n", .{ name, @as(u8, '0') + @intFromBool(b) }),
569 .int => |i| try bw.print("#define {s} {d}\n", .{ name, i }),
570 .ident => |ident| try bw.print("#define {s} {s}\n", .{ name, ident }),
571 // TODO: use C-specific escaping instead of zig string literals
572 .string => |string| try bw.print("#define {s} \"{f}\"\n", .{ name, std.zig.fmtString(string) }),
573 }
574}
575
576fn renderValueNasm(bw: *Writer, name: []const u8, value: Value) !void {
577 switch (value) {
578 .undef => try bw.print("; %undef {s}\n", .{name}),
579 .defined => try bw.print("%define {s}\n", .{name}),
580 .boolean => |b| try bw.print("%define {s} {c}\n", .{ name, @as(u8, '0') + @intFromBool(b) }),
581 .int => |i| try bw.print("%define {s} {d}\n", .{ name, i }),
582 .ident => |ident| try bw.print("%define {s} {s}\n", .{ name, ident }),
583 // TODO: use nasm-specific escaping instead of zig string literals
584 .string => |string| try bw.print("%define {s} \"{f}\"\n", .{ name, std.zig.fmtString(string) }),
585 }
586}
587
588fn expand_variables_autoconf_at(
589 bw: *Writer,
590 contents: []const u8,
591 values: *const std.array_hash_map.String(Value),
592 used: []bool,
593) !void {
594 const valid_varname_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_";
595
596 var curr: usize = 0;
597 var source_offset: usize = 0;
598 while (curr < contents.len) : (curr += 1) {
599 if (contents[curr] != '@') continue;
600 if (std.mem.findScalarPos(u8, contents, curr + 1, '@')) |close_pos| {
601 if (close_pos == curr + 1) {
602 // closed immediately, preserve as a literal
603 continue;
604 }
605 const valid_varname_end = std.mem.findNonePos(u8, contents, curr + 1, valid_varname_chars) orelse 0;
606 if (valid_varname_end != close_pos) {
607 // contains invalid characters, preserve as a literal
608 continue;
609 }
610
611 const key = contents[curr + 1 .. close_pos];
612 const index = values.getIndex(key) orelse {
613 // Report the missing key to the caller.
614 try bw.writeAll(key);
615 return error.MissingValue;
616 };
617 const value = values.entries.slice().items(.value)[index];
618 used[index] = true;
619 try bw.writeAll(contents[source_offset..curr]);
620 switch (value) {
621 .undef, .defined => {},
622 .boolean => |b| try bw.writeByte(@as(u8, '0') + @intFromBool(b)),
623 .int => |i| try bw.print("{d}", .{i}),
624 .ident, .string => |s| try bw.writeAll(s),
625 }
626
627 curr = close_pos;
628 source_offset = close_pos + 1;
629 }
630 }
631
632 try bw.writeAll(contents[source_offset..]);
633}
634
635fn expand_variables_cmake(
636 allocator: Allocator,
637 contents: []const u8,
638 values: std.array_hash_map.String(Value),
639) ![]const u8 {
640 var result: std.array_list.Managed(u8) = .init(allocator);
641 errdefer result.deinit();
642
643 const valid_varname_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/_.+-";
644 const open_var = "${";
645
646 var curr: usize = 0;
647 var source_offset: usize = 0;
648 const Position = struct {
649 source: usize,
650 target: usize,
651 };
652 var var_stack: std.array_list.Managed(Position) = .init(allocator);
653 defer var_stack.deinit();
654 loop: while (curr < contents.len) : (curr += 1) {
655 switch (contents[curr]) {
656 '@' => blk: {
657 if (std.mem.findScalarPos(u8, contents, curr + 1, '@')) |close_pos| {
658 if (close_pos == curr + 1) {
659 // closed immediately, preserve as a literal
660 break :blk;
661 }
662 const valid_varname_end = std.mem.findNonePos(u8, contents, curr + 1, valid_varname_chars) orelse 0;
663 if (valid_varname_end != close_pos) {
664 // contains invalid characters, preserve as a literal
665 break :blk;
666 }
667
668 const key = contents[curr + 1 .. close_pos];
669 const value = values.get(key) orelse return error.MissingValue;
670 const missing = contents[source_offset..curr];
671 try result.appendSlice(missing);
672 switch (value) {
673 .undef, .defined => {},
674 .boolean => |b| {
675 try result.append(if (b) '1' else '0');
676 },
677 .int => |i| {
678 try result.print("{d}", .{i});
679 },
680 .ident, .string => |s| {
681 try result.appendSlice(s);
682 },
683 }
684
685 curr = close_pos;
686 source_offset = close_pos + 1;
687
688 continue :loop;
689 }
690 },
691 '$' => blk: {
692 const next = curr + 1;
693 if (next == contents.len or contents[next] != '{') {
694 // no open bracket detected, preserve as a literal
695 break :blk;
696 }
697 const missing = contents[source_offset..curr];
698 try result.appendSlice(missing);
699 try result.appendSlice(open_var);
700
701 source_offset = curr + open_var.len;
702 curr = next;
703 try var_stack.append(Position{
704 .source = curr,
705 .target = result.items.len - open_var.len,
706 });
707
708 continue :loop;
709 },
710 '}' => blk: {
711 if (var_stack.items.len == 0) {
712 // no open bracket, preserve as a literal
713 break :blk;
714 }
715 const open_pos = var_stack.pop().?;
716 if (source_offset == open_pos.source) {
717 source_offset += open_var.len;
718 }
719 const missing = contents[source_offset..curr];
720 try result.appendSlice(missing);
721
722 const key_start = open_pos.target + open_var.len;
723 const key = result.items[key_start..];
724 if (key.len == 0) {
725 return error.MissingKey;
726 }
727 const value = values.get(key) orelse return error.MissingValue;
728 result.shrinkRetainingCapacity(result.items.len - key.len - open_var.len);
729 switch (value) {
730 .undef, .defined => {},
731 .boolean => |b| {
732 try result.append(if (b) '1' else '0');
733 },
734 .int => |i| {
735 try result.print("{d}", .{i});
736 },
737 .ident, .string => |s| {
738 try result.appendSlice(s);
739 },
740 }
741
742 source_offset = curr + 1;
743
744 continue :loop;
745 },
746 '\\' => {
747 // backslash is not considered a special character
748 continue :loop;
749 },
750 else => {},
751 }
752
753 if (var_stack.items.len > 0 and std.mem.findScalar(u8, valid_varname_chars, contents[curr]) == null) {
754 return error.InvalidCharacter;
755 }
756 }
757
758 if (source_offset != contents.len) {
759 const missing = contents[source_offset..];
760 try result.appendSlice(missing);
175pub fn addValues(config_header: *ConfigHeader, values: anytype) void {
176 inline for (@typeInfo(@TypeOf(values)).@"struct".fields) |field| {
177 addValue(config_header, field.name, field.type, @field(values, field.name));
761178 }
762
763 return result.toOwnedSlice();
764}
765
766fn testReplaceVariablesAutoconfAt(
767 allocator: Allocator,
768 contents: []const u8,
769 expected: []const u8,
770 values: std.array_hash_map.String(Value),
771) !void {
772 var aw: Writer.Allocating = .init(allocator);
773 defer aw.deinit();
774
775 const used = try allocator.alloc(bool, values.count());
776 for (used) |*u| u.* = false;
777 defer allocator.free(used);
778
779 try expand_variables_autoconf_at(&aw.writer, contents, values, used);
780
781 for (used) |u| if (!u) return error.UnusedValue;
782 try std.testing.expectEqualStrings(expected, aw.written());
783}
784
785fn testReplaceVariablesCMake(
786 allocator: Allocator,
787 contents: []const u8,
788 expected: []const u8,
789 values: std.array_hash_map.String(Value),
790) !void {
791 const actual = try expand_variables_cmake(allocator, contents, values);
792 defer allocator.free(actual);
793
794 try std.testing.expectEqualStrings(expected, actual);
795}
796
797test "expand_variables_autoconf_at simple cases" {
798 const allocator = std.testing.allocator;
799 var values: std.array_hash_map.String(Value) = .init(allocator);
800 defer values.deinit();
801
802 // empty strings are preserved
803 try testReplaceVariablesAutoconfAt(allocator, "", "", values);
804
805 // line with misc content is preserved
806 try testReplaceVariablesAutoconfAt(allocator, "no substitution", "no substitution", values);
807
808 // empty @ sigils are preserved
809 try testReplaceVariablesAutoconfAt(allocator, "@", "@", values);
810 try testReplaceVariablesAutoconfAt(allocator, "@@", "@@", values);
811 try testReplaceVariablesAutoconfAt(allocator, "@@@", "@@@", values);
812 try testReplaceVariablesAutoconfAt(allocator, "@@@@", "@@@@", values);
813
814 // simple substitution
815 try values.putNoClobber("undef", .undef);
816 try testReplaceVariablesAutoconfAt(allocator, "@undef@", "", values);
817 values.clearRetainingCapacity();
818
819 try values.putNoClobber("defined", .defined);
820 try testReplaceVariablesAutoconfAt(allocator, "@defined@", "", values);
821 values.clearRetainingCapacity();
822
823 try values.putNoClobber("true", Value{ .boolean = true });
824 try testReplaceVariablesAutoconfAt(allocator, "@true@", "1", values);
825 values.clearRetainingCapacity();
826
827 try values.putNoClobber("false", Value{ .boolean = false });
828 try testReplaceVariablesAutoconfAt(allocator, "@false@", "0", values);
829 values.clearRetainingCapacity();
830
831 try values.putNoClobber("int", Value{ .int = 42 });
832 try testReplaceVariablesAutoconfAt(allocator, "@int@", "42", values);
833 values.clearRetainingCapacity();
834
835 try values.putNoClobber("ident", Value{ .string = "value" });
836 try testReplaceVariablesAutoconfAt(allocator, "@ident@", "value", values);
837 values.clearRetainingCapacity();
838
839 try values.putNoClobber("string", Value{ .string = "text" });
840 try testReplaceVariablesAutoconfAt(allocator, "@string@", "text", values);
841 values.clearRetainingCapacity();
842
843 // double packed substitution
844 try values.putNoClobber("string", Value{ .string = "text" });
845 try testReplaceVariablesAutoconfAt(allocator, "@string@@string@", "texttext", values);
846 values.clearRetainingCapacity();
847
848 // triple packed substitution
849 try values.putNoClobber("int", Value{ .int = 42 });
850 try values.putNoClobber("string", Value{ .string = "text" });
851 try testReplaceVariablesAutoconfAt(allocator, "@string@@int@@string@", "text42text", values);
852 values.clearRetainingCapacity();
853
854 // double separated substitution
855 try values.putNoClobber("int", Value{ .int = 42 });
856 try testReplaceVariablesAutoconfAt(allocator, "@int@.@int@", "42.42", values);
857 values.clearRetainingCapacity();
858
859 // triple separated substitution
860 try values.putNoClobber("true", Value{ .boolean = true });
861 try values.putNoClobber("int", Value{ .int = 42 });
862 try testReplaceVariablesAutoconfAt(allocator, "@int@.@true@.@int@", "42.1.42", values);
863 values.clearRetainingCapacity();
864
865 // misc prefix is preserved
866 try values.putNoClobber("false", Value{ .boolean = false });
867 try testReplaceVariablesAutoconfAt(allocator, "false is @false@", "false is 0", values);
868 values.clearRetainingCapacity();
869
870 // misc suffix is preserved
871 try values.putNoClobber("true", Value{ .boolean = true });
872 try testReplaceVariablesAutoconfAt(allocator, "@true@ is true", "1 is true", values);
873 values.clearRetainingCapacity();
874
875 // surrounding content is preserved
876 try values.putNoClobber("int", Value{ .int = 42 });
877 try testReplaceVariablesAutoconfAt(allocator, "what is 6*7? @int@!", "what is 6*7? 42!", values);
878 values.clearRetainingCapacity();
879
880 // incomplete key is preserved
881 try testReplaceVariablesAutoconfAt(allocator, "@undef", "@undef", values);
882
883 // unknown key leads to an error
884 try std.testing.expectError(error.MissingValue, testReplaceVariablesAutoconfAt(allocator, "@bad@", "", values));
885
886 // unused key leads to an error
887 try values.putNoClobber("int", Value{ .int = 42 });
888 try values.putNoClobber("false", Value{ .boolean = false });
889 try std.testing.expectError(error.UnusedValue, testReplaceVariablesAutoconfAt(allocator, "@int", "", values));
890 values.clearRetainingCapacity();
891}
892
893test "expand_variables_autoconf_at edge cases" {
894 const allocator = std.testing.allocator;
895 var values: std.array_hash_map.String(Value) = .init(allocator);
896 defer values.deinit();
897
898 // @-vars resolved only when they wrap valid characters, otherwise considered literals
899 try values.putNoClobber("string", Value{ .string = "text" });
900 try testReplaceVariablesAutoconfAt(allocator, "@@string@@", "@text@", values);
901 values.clearRetainingCapacity();
902
903 // expanded variables are considered strings after expansion
904 try values.putNoClobber("string_at", Value{ .string = "@string@" });
905 try testReplaceVariablesAutoconfAt(allocator, "@string_at@", "@string@", values);
906 values.clearRetainingCapacity();
907}
908
909test "expand_variables_cmake simple cases" {
910 const allocator = std.testing.allocator;
911 var values: std.array_hash_map.String(Value) = .init(allocator);
912 defer values.deinit();
913
914 try values.putNoClobber("undef", .undef);
915 try values.putNoClobber("defined", .defined);
916 try values.putNoClobber("true", Value{ .boolean = true });
917 try values.putNoClobber("false", Value{ .boolean = false });
918 try values.putNoClobber("int", Value{ .int = 42 });
919 try values.putNoClobber("ident", Value{ .string = "value" });
920 try values.putNoClobber("string", Value{ .string = "text" });
921
922 // empty strings are preserved
923 try testReplaceVariablesCMake(allocator, "", "", values);
924
925 // line with misc content is preserved
926 try testReplaceVariablesCMake(allocator, "no substitution", "no substitution", values);
927
928 // empty ${} wrapper leads to an error
929 try std.testing.expectError(error.MissingKey, testReplaceVariablesCMake(allocator, "${}", "", values));
930
931 // empty @ sigils are preserved
932 try testReplaceVariablesCMake(allocator, "@", "@", values);
933 try testReplaceVariablesCMake(allocator, "@@", "@@", values);
934 try testReplaceVariablesCMake(allocator, "@@@", "@@@", values);
935 try testReplaceVariablesCMake(allocator, "@@@@", "@@@@", values);
936
937 // simple substitution
938 try testReplaceVariablesCMake(allocator, "@undef@", "", values);
939 try testReplaceVariablesCMake(allocator, "${undef}", "", values);
940 try testReplaceVariablesCMake(allocator, "@defined@", "", values);
941 try testReplaceVariablesCMake(allocator, "${defined}", "", values);
942 try testReplaceVariablesCMake(allocator, "@true@", "1", values);
943 try testReplaceVariablesCMake(allocator, "${true}", "1", values);
944 try testReplaceVariablesCMake(allocator, "@false@", "0", values);
945 try testReplaceVariablesCMake(allocator, "${false}", "0", values);
946 try testReplaceVariablesCMake(allocator, "@int@", "42", values);
947 try testReplaceVariablesCMake(allocator, "${int}", "42", values);
948 try testReplaceVariablesCMake(allocator, "@ident@", "value", values);
949 try testReplaceVariablesCMake(allocator, "${ident}", "value", values);
950 try testReplaceVariablesCMake(allocator, "@string@", "text", values);
951 try testReplaceVariablesCMake(allocator, "${string}", "text", values);
952
953 // double packed substitution
954 try testReplaceVariablesCMake(allocator, "@string@@string@", "texttext", values);
955 try testReplaceVariablesCMake(allocator, "${string}${string}", "texttext", values);
956
957 // triple packed substitution
958 try testReplaceVariablesCMake(allocator, "@string@@int@@string@", "text42text", values);
959 try testReplaceVariablesCMake(allocator, "@string@${int}@string@", "text42text", values);
960 try testReplaceVariablesCMake(allocator, "${string}@int@${string}", "text42text", values);
961 try testReplaceVariablesCMake(allocator, "${string}${int}${string}", "text42text", values);
962
963 // double separated substitution
964 try testReplaceVariablesCMake(allocator, "@int@.@int@", "42.42", values);
965 try testReplaceVariablesCMake(allocator, "${int}.${int}", "42.42", values);
966
967 // triple separated substitution
968 try testReplaceVariablesCMake(allocator, "@int@.@true@.@int@", "42.1.42", values);
969 try testReplaceVariablesCMake(allocator, "@int@.${true}.@int@", "42.1.42", values);
970 try testReplaceVariablesCMake(allocator, "${int}.@true@.${int}", "42.1.42", values);
971 try testReplaceVariablesCMake(allocator, "${int}.${true}.${int}", "42.1.42", values);
972
973 // misc prefix is preserved
974 try testReplaceVariablesCMake(allocator, "false is @false@", "false is 0", values);
975 try testReplaceVariablesCMake(allocator, "false is ${false}", "false is 0", values);
976
977 // misc suffix is preserved
978 try testReplaceVariablesCMake(allocator, "@true@ is true", "1 is true", values);
979 try testReplaceVariablesCMake(allocator, "${true} is true", "1 is true", values);
980
981 // surrounding content is preserved
982 try testReplaceVariablesCMake(allocator, "what is 6*7? @int@!", "what is 6*7? 42!", values);
983 try testReplaceVariablesCMake(allocator, "what is 6*7? ${int}!", "what is 6*7? 42!", values);
984
985 // incomplete key is preserved
986 try testReplaceVariablesCMake(allocator, "@undef", "@undef", values);
987 try testReplaceVariablesCMake(allocator, "${undef", "${undef", values);
988 try testReplaceVariablesCMake(allocator, "{undef}", "{undef}", values);
989 try testReplaceVariablesCMake(allocator, "undef@", "undef@", values);
990 try testReplaceVariablesCMake(allocator, "undef}", "undef}", values);
991
992 // unknown key leads to an error
993 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "@bad@", "", values));
994 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${bad}", "", values));
995179}
996180
997test "expand_variables_cmake edge cases" {
998 const allocator = std.testing.allocator;
999 var values: std.array_hash_map.String(Value) = .init(allocator);
1000 defer values.deinit();
1001
1002 // special symbols
1003 try values.putNoClobber("at", Value{ .string = "@" });
1004 try values.putNoClobber("dollar", Value{ .string = "$" });
1005 try values.putNoClobber("underscore", Value{ .string = "_" });
1006
1007 // basic value
1008 try values.putNoClobber("string", Value{ .string = "text" });
1009
1010 // proxy case values
1011 try values.putNoClobber("string_proxy", Value{ .string = "string" });
1012 try values.putNoClobber("string_at", Value{ .string = "@string@" });
1013 try values.putNoClobber("string_curly", Value{ .string = "{string}" });
1014 try values.putNoClobber("string_var", Value{ .string = "${string}" });
1015
1016 // stack case values
1017 try values.putNoClobber("nest_underscore_proxy", Value{ .string = "underscore" });
1018 try values.putNoClobber("nest_proxy", Value{ .string = "nest_underscore_proxy" });
1019
1020 // @-vars resolved only when they wrap valid characters, otherwise considered literals
1021 try testReplaceVariablesCMake(allocator, "@@string@@", "@text@", values);
1022 try testReplaceVariablesCMake(allocator, "@${string}@", "@text@", values);
1023
1024 // @-vars are resolved inside ${}-vars
1025 try testReplaceVariablesCMake(allocator, "${@string_proxy@}", "text", values);
1026
1027 // expanded variables are considered strings after expansion
1028 try testReplaceVariablesCMake(allocator, "@string_at@", "@string@", values);
1029 try testReplaceVariablesCMake(allocator, "${string_at}", "@string@", values);
1030 try testReplaceVariablesCMake(allocator, "$@string_curly@", "${string}", values);
1031 try testReplaceVariablesCMake(allocator, "$${string_curly}", "${string}", values);
1032 try testReplaceVariablesCMake(allocator, "${string_var}", "${string}", values);
1033 try testReplaceVariablesCMake(allocator, "@string_var@", "${string}", values);
1034 try testReplaceVariablesCMake(allocator, "${dollar}{${string}}", "${text}", values);
1035 try testReplaceVariablesCMake(allocator, "@dollar@{${string}}", "${text}", values);
1036 try testReplaceVariablesCMake(allocator, "@dollar@{@string@}", "${text}", values);
1037
1038 // when expanded variables contain invalid characters, they prevent further expansion
1039 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${${string_var}}", "", values));
1040 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${@string_var@}", "", values));
1041
1042 // nested expanded variables are expanded from the inside out
1043 try testReplaceVariablesCMake(allocator, "${string${underscore}proxy}", "string", values);
1044 try testReplaceVariablesCMake(allocator, "${string@underscore@proxy}", "string", values);
1045
1046 // nested vars are only expanded when ${} is closed
1047 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "@nest@underscore@proxy@", "", values));
1048 try testReplaceVariablesCMake(allocator, "${nest${underscore}proxy}", "nest_underscore_proxy", values);
1049 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "@nest@@nest_underscore@underscore@proxy@@proxy@", "", values));
1050 try testReplaceVariablesCMake(allocator, "${nest${${nest_underscore${underscore}proxy}}proxy}", "nest_underscore_proxy", values);
1051
1052 // invalid characters lead to an error
1053 try std.testing.expectError(error.InvalidCharacter, testReplaceVariablesCMake(allocator, "${str*ing}", "", values));
1054 try std.testing.expectError(error.InvalidCharacter, testReplaceVariablesCMake(allocator, "${str$ing}", "", values));
1055 try std.testing.expectError(error.InvalidCharacter, testReplaceVariablesCMake(allocator, "${str@ing}", "", values));
181pub fn getOutputDir(ch: *ConfigHeader) std.Build.LazyPath {
182 return .{ .generated = .{ .index = ch.generated_dir } };
1056183}
1057184
1058test "expand_variables_cmake escaped characters" {
1059 const allocator = std.testing.allocator;
1060 var values: std.array_hash_map.String(Value) = .init(allocator);
1061 defer values.deinit();
1062
1063 try values.putNoClobber("string", Value{ .string = "text" });
1064
1065 // backslash is an invalid character for @ lookup
1066 try testReplaceVariablesCMake(allocator, "\\@string\\@", "\\@string\\@", values);
1067
1068 // backslash is preserved, but doesn't affect ${} variable expansion
1069 try testReplaceVariablesCMake(allocator, "\\${string}", "\\text", values);
1070
1071 // backslash breaks ${} opening bracket identification
1072 try testReplaceVariablesCMake(allocator, "$\\{string}", "$\\{string}", values);
1073
1074 // backslash is skipped when checking for invalid characters, yet it mangles the key
1075 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${string\\}", "", values));
185pub fn getOutputFile(ch: *ConfigHeader) std.Build.LazyPath {
186 return ch.getOutputDir().path(ch.step.owner, ch.include_path);
1076187}
lib/std/Build/Step/Fail.zig+10-20
......@@ -1,35 +1,25 @@
11//! Fail the build with a given message.
2const Fail = @This();
3
24const std = @import("std");
35const Step = std.Build.Step;
4const Fail = @This();
6const Configuration = std.Build.Configuration;
57
68step: Step,
7error_msg: []const u8,
9error_msg: Configuration.String,
810
9pub const base_id: Step.Id = .fail;
11pub const base_tag: Step.Tag = .fail;
1012
1113pub fn create(owner: *std.Build, error_msg: []const u8) *Fail {
12 const fail = owner.allocator.create(Fail) catch @panic("OOM");
13
14 const graph = owner.graph;
15 const fail = graph.create(Fail);
1416 fail.* = .{
15 .step = Step.init(.{
16 .id = base_id,
17 .step = .init(.{
18 .tag = base_tag,
1719 .name = "fail",
1820 .owner = owner,
19 .makeFn = make,
2021 }),
21 .error_msg = owner.dupe(error_msg),
22 .error_msg = graph.addString(error_msg),
2223 };
23
2424 return fail;
2525}
26
27fn make(step: *Step, options: Step.MakeOptions) !void {
28 _ = options; // No progress to report.
29
30 const fail: *Fail = @fieldParentPtr("step", step);
31
32 try step.result_error_msgs.append(step.owner.allocator, fail.error_msg);
33
34 return error.MakeFailed;
35}
lib/std/Build/Step/FindProgram.zig created+31
......@@ -0,0 +1,31 @@
1const FindProgram = @This();
2
3const std = @import("std");
4const Step = std.Build.Step;
5const Configuration = std.Build.Configuration;
6
7step: Step,
8found_path: Configuration.GeneratedFileIndex,
9names: Configuration.StringList,
10
11pub const base_tag: Step.Tag = .find_program;
12
13pub const Options = struct {
14 names: []const []const u8,
15};
16
17pub fn create(owner: *std.Build, options: Options) *FindProgram {
18 const graph = owner.graph;
19 const wc = &graph.wip_configuration;
20 const fp = graph.create(FindProgram);
21 fp.* = .{
22 .step = .init(.{
23 .tag = base_tag,
24 .name = owner.fmt("find program {s} ({d} candidates)", .{ options.names[0], options.names.len }),
25 .owner = owner,
26 }),
27 .found_path = graph.addGeneratedFile(&fp.step),
28 .names = wc.addStringList(options.names) catch @panic("OOM"),
29 };
30 return fp;
31}
lib/std/Build/Step/Fmt.zig+22-57
......@@ -1,81 +1,46 @@
11//! This step has two modes:
22//! * Modify mode: directly modify source files, formatting them in place.
33//! * Check mode: fail the step if a non-conforming file is found.
4const Fmt = @This();
5
46const std = @import("std");
57const Step = std.Build.Step;
6const Fmt = @This();
8const LazyPath = std.Build.LazyPath;
9const Configuration = std.Build.Configuration;
710
811step: Step,
9paths: []const []const u8,
10exclude_paths: []const []const u8,
12/// Intended to be read-only after the `Fmt` step is created.
13paths: []const LazyPath,
14/// Intended to be read-only after the `Fmt` step is created.
15exclude_paths: []const LazyPath,
1116check: bool,
1217
13pub const base_id: Step.Id = .fmt;
18pub const base_tag: Step.Tag = .fmt;
1419
1520pub const Options = struct {
16 paths: []const []const u8 = &.{},
17 exclude_paths: []const []const u8 = &.{},
21 paths: []const LazyPath = &.{},
22 exclude_paths: []const LazyPath = &.{},
1823 /// If true, fails the build step when any non-conforming files are encountered.
1924 check: bool = false,
2025};
2126
2227pub fn create(owner: *std.Build, options: Options) *Fmt {
23 const fmt = owner.allocator.create(Fmt) catch @panic("OOM");
24 const name = if (options.check) "zig fmt --check" else "zig fmt";
28 const graph = owner.graph;
29 const fmt = graph.create(Fmt);
30
2531 fmt.* = .{
26 .step = Step.init(.{
27 .id = base_id,
28 .name = name,
32 .step = .init(.{
33 .tag = base_tag,
34 .name = if (options.check) "zig fmt --check" else "zig fmt",
2935 .owner = owner,
30 .makeFn = make,
3136 }),
32 .paths = owner.dupeStrings(options.paths),
33 .exclude_paths = owner.dupeStrings(options.exclude_paths),
37 .paths = options.paths,
38 .exclude_paths = options.exclude_paths,
3439 .check = options.check,
3540 };
36 return fmt;
37}
38
39fn make(step: *Step, options: Step.MakeOptions) !void {
40 const prog_node = options.progress_node;
41
42 // TODO: if check=false, this means we are modifying source files in place, which
43 // is an operation that could race against other operations also modifying source files
44 // in place. In this case, this step should obtain a write lock while making those
45 // modifications.
4641
47 const b = step.owner;
48 const arena = b.allocator;
49 const fmt: *Fmt = @fieldParentPtr("step", step);
42 for (options.paths) |lp| lp.addStepDependencies(&fmt.step);
43 for (options.exclude_paths) |lp| lp.addStepDependencies(&fmt.step);
5044
51 var argv: std.ArrayList([]const u8) = .empty;
52 try argv.ensureUnusedCapacity(arena, 2 + 1 + fmt.paths.len + 2 * fmt.exclude_paths.len);
53
54 argv.appendAssumeCapacity(b.graph.zig_exe);
55 argv.appendAssumeCapacity("fmt");
56
57 if (fmt.check) {
58 argv.appendAssumeCapacity("--check");
59 }
60
61 for (fmt.paths) |p| {
62 argv.appendAssumeCapacity(b.pathFromRoot(p));
63 }
64
65 for (fmt.exclude_paths) |p| {
66 argv.appendAssumeCapacity("--exclude");
67 argv.appendAssumeCapacity(b.pathFromRoot(p));
68 }
69
70 const run_result = try step.captureChildProcess(options.gpa, prog_node, argv.items);
71 if (fmt.check) switch (run_result.term) {
72 .exited => |code| if (code != 0 and run_result.stdout.len != 0) {
73 var it = std.mem.tokenizeScalar(u8, run_result.stdout, '\n');
74 while (it.next()) |bad_file_name| {
75 try step.addError("{s}: non-conforming formatting", .{bad_file_name});
76 }
77 },
78 else => {},
79 };
80 try step.handleChildProcessTerm(run_result.term);
45 return fmt;
8146}
lib/std/Build/Step/InstallArtifact.zig+26-141
......@@ -1,14 +1,14 @@
1const InstallArtifact = @This();
2
13const std = @import("std");
24const Step = std.Build.Step;
35const InstallDir = std.Build.InstallDir;
4const InstallArtifact = @This();
5const fs = std.fs;
66const LazyPath = std.Build.LazyPath;
77
88step: Step,
99
1010dest_dir: ?InstallDir,
11dest_sub_path: []const u8,
11dest_sub_path: ?[]const u8,
1212emitted_bin: ?LazyPath,
1313
1414implib_dir: ?InstallDir,
......@@ -17,14 +17,10 @@ emitted_implib: ?LazyPath,
1717pdb_dir: ?InstallDir,
1818emitted_pdb: ?LazyPath,
1919
20// hack for stage2_x86_64 + coff
21compiler_rt_dyn_lib_dir: ?InstallDir,
22emitted_compiler_rt_dyn_lib: ?LazyPath,
23
2420h_dir: ?InstallDir,
2521emitted_h: ?LazyPath,
2622
27dylib_symlinks: ?DylibSymlinkInfo,
23dylib_symlinks: bool,
2824
2925artifact: *Step.Compile,
3026
......@@ -33,7 +29,7 @@ const DylibSymlinkInfo = struct {
3329 name_only_filename: []const u8,
3430};
3531
36pub const base_id: Step.Id = .install_artifact;
32pub const base_tag: Step.Tag = .install_artifact;
3733
3834pub const Options = struct {
3935 /// Which installation directory to put the main output file into.
......@@ -67,158 +63,47 @@ pub fn create(owner: *std.Build, artifact: *Step.Compile, options: Options) *Ins
6763 },
6864 .override => |o| o,
6965 };
66 const pdb_dir: ?InstallDir = switch (options.pdb_dir) {
67 .disabled => null,
68 .default => if (artifact.producesPdbFile()) dest_dir else null,
69 .override => |o| o,
70 };
71 const implib_dir: ?InstallDir = switch (options.implib_dir) {
72 .disabled => null,
73 .default => if (artifact.producesImplib()) .lib else null,
74 .override => |o| o,
75 };
7076 install_artifact.* = .{
7177 .step = Step.init(.{
72 .id = base_id,
78 .tag = base_tag,
7379 .name = owner.fmt("install {s}", .{artifact.name}),
7480 .owner = owner,
75 .makeFn = make,
7681 }),
7782 .dest_dir = dest_dir,
78 .pdb_dir = switch (options.pdb_dir) {
79 .disabled => null,
80 .default => if (artifact.producesPdbFile()) dest_dir else null,
81 .override => |o| o,
82 },
83 .compiler_rt_dyn_lib_dir = switch (options.compiler_rt_dyn_lib_dir) {
84 .disabled => null,
85 .default => if (artifact.producesCompilerRtDynLib()) dest_dir else null,
86 .override => |o| o,
87 },
83 .pdb_dir = pdb_dir,
8884 .h_dir = switch (options.h_dir) {
8985 .disabled => null,
9086 .default => if (artifact.kind == .lib) .header else null,
9187 .override => |o| o,
9288 },
93 .implib_dir = switch (options.implib_dir) {
94 .disabled => null,
95 .default => if (artifact.producesImplib()) .lib else null,
96 .override => |o| o,
97 },
89 .implib_dir = implib_dir,
9890
99 .dylib_symlinks = if (options.dylib_symlinks orelse (dest_dir != null and
100 artifact.isDynamicLibrary() and
101 artifact.version != null and
102 std.Build.wantSharedLibSymLinks(artifact.rootModuleTarget()))) .{
103 .major_only_filename = artifact.major_only_filename.?,
104 .name_only_filename = artifact.name_only_filename.?,
105 } else null,
91 .dylib_symlinks = options.dylib_symlinks orelse (dest_dir != null and
92 artifact.isDynamicLibrary() and artifact.version != null and
93 std.Build.wantSharedLibSymLinks(artifact.rootModuleTarget())),
10694
107 .dest_sub_path = options.dest_sub_path orelse artifact.out_filename,
95 .dest_sub_path = options.dest_sub_path,
10896
109 .emitted_bin = null,
110 .emitted_pdb = null,
111 .emitted_compiler_rt_dyn_lib = null,
97 .emitted_bin = if (dest_dir != null) artifact.getEmittedBin() else null,
98 .emitted_pdb = if (pdb_dir != null) artifact.getEmittedPdb() else null,
99 // https://github.com/ziglang/zig/issues/9698
112100 .emitted_h = null,
113 .emitted_implib = null,
101 .emitted_implib = if (implib_dir != null) artifact.getEmittedImplib() else null,
114102
115103 .artifact = artifact,
116104 };
117105
118106 install_artifact.step.dependOn(&artifact.step);
119107
120 if (install_artifact.dest_dir != null) install_artifact.emitted_bin = artifact.getEmittedBin();
121 if (install_artifact.compiler_rt_dyn_lib_dir != null) install_artifact.emitted_compiler_rt_dyn_lib = artifact.getEmittedCompilerRtDynLib();
122 if (install_artifact.pdb_dir != null) install_artifact.emitted_pdb = artifact.getEmittedPdb();
123 // https://github.com/ziglang/zig/issues/9698
124 //if (install_artifact.h_dir != null) install_artifact.emitted_h = artifact.getEmittedH();
125 if (install_artifact.implib_dir != null) install_artifact.emitted_implib = artifact.getEmittedImplib();
126
127108 return install_artifact;
128109}
129
130fn make(step: *Step, options: Step.MakeOptions) !void {
131 _ = options;
132 const install_artifact: *InstallArtifact = @fieldParentPtr("step", step);
133 const b = step.owner;
134 const io = b.graph.io;
135
136 var all_cached = true;
137
138 if (install_artifact.dest_dir) |dest_dir| {
139 const full_dest_path = b.getInstallPath(dest_dir, install_artifact.dest_sub_path);
140 const p = try step.installFile(install_artifact.emitted_bin.?, full_dest_path);
141 all_cached = all_cached and p == .fresh;
142
143 if (install_artifact.dylib_symlinks) |dls| {
144 try Step.Compile.doAtomicSymLinks(step, full_dest_path, dls.major_only_filename, dls.name_only_filename);
145 }
146
147 install_artifact.artifact.installed_path = full_dest_path;
148 }
149
150 if (install_artifact.compiler_rt_dyn_lib_dir) |compiler_rt_dir| {
151 const full_compiler_rt_path = b.getInstallPath(compiler_rt_dir, install_artifact.emitted_compiler_rt_dyn_lib.?.basename(b, step));
152 const p = try step.installFile(install_artifact.emitted_compiler_rt_dyn_lib.?, full_compiler_rt_path);
153 all_cached = all_cached and p == .fresh;
154 }
155
156 if (install_artifact.implib_dir) |implib_dir| {
157 const full_implib_path = b.getInstallPath(implib_dir, install_artifact.emitted_implib.?.basename(b, step));
158 const p = try step.installFile(install_artifact.emitted_implib.?, full_implib_path);
159 all_cached = all_cached and p == .fresh;
160 }
161
162 if (install_artifact.pdb_dir) |pdb_dir| {
163 const full_pdb_path = b.getInstallPath(pdb_dir, install_artifact.emitted_pdb.?.basename(b, step));
164 const p = try step.installFile(install_artifact.emitted_pdb.?, full_pdb_path);
165 all_cached = all_cached and p == .fresh;
166 }
167
168 if (install_artifact.h_dir) |h_dir| {
169 if (install_artifact.emitted_h) |emitted_h| {
170 const full_h_path = b.getInstallPath(h_dir, emitted_h.basename(b, step));
171 const p = try step.installFile(emitted_h, full_h_path);
172 all_cached = all_cached and p == .fresh;
173 }
174
175 for (install_artifact.artifact.installed_headers.items) |installation| switch (installation) {
176 .file => |file| {
177 const full_h_path = b.getInstallPath(h_dir, file.dest_rel_path);
178 const p = try step.installFile(file.source, full_h_path);
179 all_cached = all_cached and p == .fresh;
180 },
181 .directory => |dir| {
182 const src_dir_path = dir.source.getPath3(b, step);
183 const full_h_prefix = b.getInstallPath(h_dir, dir.dest_rel_path);
184
185 var src_dir = src_dir_path.root_dir.handle.openDir(io, src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| {
186 return step.fail("unable to open source directory '{f}': {s}", .{
187 src_dir_path, @errorName(err),
188 });
189 };
190 defer src_dir.close(io);
191
192 var it = try src_dir.walk(b.allocator);
193 next_entry: while (try it.next(io)) |entry| {
194 for (dir.options.exclude_extensions) |ext| {
195 if (std.mem.endsWith(u8, entry.path, ext)) continue :next_entry;
196 }
197 if (dir.options.include_extensions) |incs| {
198 for (incs) |inc| {
199 if (std.mem.endsWith(u8, entry.path, inc)) break;
200 } else {
201 continue :next_entry;
202 }
203 }
204
205 const full_dest_path = b.pathJoin(&.{ full_h_prefix, entry.path });
206 switch (entry.kind) {
207 .directory => {
208 try Step.handleVerbose(b, .inherit, &.{ "install", "-d", full_dest_path });
209 const p = try step.installDir(full_dest_path);
210 all_cached = all_cached and p == .existed;
211 },
212 .file => {
213 const p = try step.installFile(try dir.source.join(b.allocator, entry.path), full_dest_path);
214 all_cached = all_cached and p == .fresh;
215 },
216 else => continue,
217 }
218 }
219 },
220 };
221 }
222
223 step.result_cached = all_cached;
224}
lib/std/Build/Step/InstallDir.zig+14-67
......@@ -1,14 +1,15 @@
1const InstallDir = @This();
2
13const std = @import("std");
24const mem = std.mem;
35const fs = std.fs;
46const Step = std.Build.Step;
57const LazyPath = std.Build.LazyPath;
6const InstallDir = @This();
78
89step: Step,
910options: Options,
1011
11pub const base_id: Step.Id = .install_dir;
12pub const base_tag: Step.Tag = .install_dir;
1213
1314pub const Options = struct {
1415 source_dir: LazyPath,
......@@ -28,83 +29,29 @@ pub const Options = struct {
2829 /// `@import("test.zig")` would be a compile error.
2930 blank_extensions: []const []const u8 = &.{},
3031
31 fn dupe(opts: Options, b: *std.Build) Options {
32 fn dupe(opts: Options, graph: *const std.Build.Graph) Options {
3233 return .{
33 .source_dir = opts.source_dir.dupe(b),
34 .install_dir = opts.install_dir.dupe(b),
35 .install_subdir = b.dupe(opts.install_subdir),
36 .exclude_extensions = b.dupeStrings(opts.exclude_extensions),
37 .include_extensions = if (opts.include_extensions) |incs| b.dupeStrings(incs) else null,
38 .blank_extensions = b.dupeStrings(opts.blank_extensions),
34 .source_dir = opts.source_dir.dupe(graph),
35 .install_dir = opts.install_dir.dupe(graph),
36 .install_subdir = graph.dupeString(opts.install_subdir),
37 .exclude_extensions = graph.dupeStrings(opts.exclude_extensions),
38 .include_extensions = if (opts.include_extensions) |incs| graph.dupeStrings(incs) else null,
39 .blank_extensions = graph.dupeStrings(opts.blank_extensions),
3940 };
4041 }
4142};
4243
4344pub fn create(owner: *std.Build, options: Options) *InstallDir {
4445 const install_dir = owner.allocator.create(InstallDir) catch @panic("OOM");
46 const graph = owner.graph;
4547 install_dir.* = .{
4648 .step = Step.init(.{
47 .id = base_id,
48 .name = owner.fmt("install {s}/", .{options.source_dir.getDisplayName()}),
49 .tag = base_tag,
50 .name = owner.fmt("install {f}/", .{options.source_dir}),
4951 .owner = owner,
50 .makeFn = make,
5152 }),
52 .options = options.dupe(owner),
53 .options = options.dupe(graph),
5354 };
5455 options.source_dir.addStepDependencies(&install_dir.step);
5556 return install_dir;
5657}
57
58fn make(step: *Step, options: Step.MakeOptions) !void {
59 _ = options;
60 const b = step.owner;
61 const io = b.graph.io;
62 const install_dir: *InstallDir = @fieldParentPtr("step", step);
63 step.clearWatchInputs();
64 const arena = b.allocator;
65 const dest_prefix = b.getInstallPath(install_dir.options.install_dir, install_dir.options.install_subdir);
66 const src_dir_path = install_dir.options.source_dir.getPath3(b, step);
67 const need_derived_inputs = try step.addDirectoryWatchInput(install_dir.options.source_dir);
68 var src_dir = src_dir_path.root_dir.handle.openDir(io, src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| {
69 return step.fail("unable to open source directory '{f}': {t}", .{ src_dir_path, err });
70 };
71 defer src_dir.close(io);
72 var it = try src_dir.walk(arena);
73 var all_cached = true;
74 next_entry: while (try it.next(io)) |entry| {
75 for (install_dir.options.exclude_extensions) |ext| {
76 if (mem.endsWith(u8, entry.path, ext)) continue :next_entry;
77 }
78 if (install_dir.options.include_extensions) |incs| {
79 for (incs) |inc| {
80 if (mem.endsWith(u8, entry.path, inc)) break;
81 } else {
82 continue :next_entry;
83 }
84 }
85
86 const src_path = try install_dir.options.source_dir.join(b.allocator, entry.path);
87 const dest_path = b.pathJoin(&.{ dest_prefix, entry.path });
88 switch (entry.kind) {
89 .directory => {
90 if (need_derived_inputs) _ = try step.addDirectoryWatchInput(src_path);
91 const p = try step.installDir(dest_path);
92 all_cached = all_cached and p == .existed;
93 },
94 .file => {
95 for (install_dir.options.blank_extensions) |ext| {
96 if (mem.endsWith(u8, entry.path, ext)) {
97 try b.truncateFile(dest_path);
98 continue :next_entry;
99 }
100 }
101
102 const p = try step.installFile(src_path, dest_path);
103 all_cached = all_cached and p == .fresh;
104 },
105 else => continue,
106 }
107 }
108
109 step.result_cached = all_cached;
110}
lib/std/Build/Step/InstallFile.zig+12-21
......@@ -1,17 +1,18 @@
1const InstallFile = @This();
2
13const std = @import("std");
24const Step = std.Build.Step;
35const LazyPath = std.Build.LazyPath;
46const InstallDir = std.Build.InstallDir;
5const InstallFile = @This();
67const assert = std.debug.assert;
78
8pub const base_id: Step.Id = .install_file;
9
109step: Step,
1110source: LazyPath,
1211dir: InstallDir,
1312dest_rel_path: []const u8,
1413
14pub const base_tag: Step.Tag = .install_file;
15
1516pub fn create(
1617 owner: *std.Build,
1718 source: LazyPath,
......@@ -19,29 +20,19 @@ pub fn create(
1920 dest_rel_path: []const u8,
2021) *InstallFile {
2122 assert(dest_rel_path.len != 0);
22 const install_file = owner.allocator.create(InstallFile) catch @panic("OOM");
23 const graph = owner.graph;
24 const arena = graph.arena;
25 const install_file = arena.create(InstallFile) catch @panic("OOM");
2326 install_file.* = .{
2427 .step = Step.init(.{
25 .id = base_id,
26 .name = owner.fmt("install {s} to {s}", .{ source.getDisplayName(), dest_rel_path }),
28 .tag = base_tag,
29 .name = owner.fmt("install {f} to {s}", .{ source, dest_rel_path }),
2730 .owner = owner,
28 .makeFn = make,
2931 }),
30 .source = source.dupe(owner),
31 .dir = dir.dupe(owner),
32 .dest_rel_path = owner.dupePath(dest_rel_path),
32 .source = source.dupe(graph),
33 .dir = dir.dupe(graph),
34 .dest_rel_path = graph.dupePath(dest_rel_path),
3335 };
3436 source.addStepDependencies(&install_file.step);
3537 return install_file;
3638}
37
38fn make(step: *Step, options: Step.MakeOptions) !void {
39 _ = options;
40 const b = step.owner;
41 const install_file: *InstallFile = @fieldParentPtr("step", step);
42 try step.singleUnchangingWatchInput(install_file.source);
43
44 const full_dest_path = b.getInstallPath(install_file.dir, install_file.dest_rel_path);
45 const p = try step.installFile(install_file.source, full_dest_path);
46 step.result_cached = p == .fresh;
47}
lib/std/Build/Step/ObjCopy.zig+82-199
......@@ -1,92 +1,43 @@
1const std = @import("std");
21const ObjCopy = @This();
32
4const Allocator = std.mem.Allocator;
5const ArenaAllocator = std.heap.ArenaAllocator;
6const File = std.Io.File;
7const InstallDir = std.Build.InstallDir;
3const std = @import("std");
84const Step = std.Build.Step;
9const elf = std.elf;
10const fs = std.fs;
11const sort = std.sort;
12
13pub const base_id: Step.Id = .objcopy;
14
15pub const RawFormat = enum {
16 bin,
17 hex,
18 elf,
19};
20
21pub const Strip = enum {
22 none,
23 debug,
24 debug_and_symbols,
25};
26
27pub const SectionFlags = packed struct {
28 /// add SHF_ALLOC
29 alloc: bool = false,
30
31 /// if section is SHT_NOBITS, set SHT_PROGBITS, otherwise do nothing
32 contents: bool = false,
33
34 /// if section is SHT_NOBITS, set SHT_PROGBITS, otherwise do nothing (same as contents)
35 load: bool = false,
5const Configuration = std.Build.Configuration;
366
37 /// readonly: clear default SHF_WRITE flag
38 readonly: bool = false,
39
40 /// add SHF_EXECINSTR
41 code: bool = false,
7step: Step,
8input_file: std.Build.LazyPath,
9basename: Configuration.OptionalString,
10output_file: Configuration.GeneratedFileIndex,
11debug_file: ?DebugFile,
4212
43 /// add SHF_EXCLUDE
44 exclude: bool = false,
13format: ?Format,
14only_section: Configuration.OptionalString,
15pad_to: ?u64,
16strip: Strip,
17compress_debug: bool,
4518
46 /// add SHF_X86_64_LARGE. Fatal error if target is not x86_64
47 large: bool = false,
19add_sections: std.ArrayList(AddSection) = .empty,
20update_sections: std.ArrayList(Configuration.Step.ObjCopy.UpdateSection) = .empty,
4821
49 /// add SHF_MERGE
50 merge: bool = false,
22pub const base_tag: Step.Tag = .obj_copy;
5123
52 /// add SHF_STRINGS
53 strings: bool = false,
54};
24pub const Format = enum { binary, hex, elf };
25pub const Strip = Configuration.Step.ObjCopy.Strip;
26pub const SectionFlags = Configuration.Step.ObjCopy.SectionFlags;
5527
5628pub const AddSection = struct {
57 section_name: []const u8,
29 section_name: Configuration.String,
5830 file_path: std.Build.LazyPath,
5931};
6032
61pub const SetSectionAlignment = struct {
62 section_name: []const u8,
63 alignment: u32,
64};
65
66pub const SetSectionFlags = struct {
67 section_name: []const u8,
68 flags: SectionFlags,
33pub const DebugFile = struct {
34 basename: Configuration.OptionalString,
35 output_file: Configuration.GeneratedFileIndex,
6936};
7037
71step: Step,
72input_file: std.Build.LazyPath,
73basename: []const u8,
74output_file: std.Build.GeneratedFile,
75output_file_debug: ?std.Build.GeneratedFile,
76
77format: ?RawFormat,
78only_section: ?[]const u8,
79pad_to: ?u64,
80strip: Strip,
81compress_debug: bool,
82
83add_section: ?AddSection,
84set_section_alignment: ?SetSectionAlignment,
85set_section_flags: ?SetSectionFlags,
86
8738pub const Options = struct {
8839 basename: ?[]const u8 = null,
89 format: ?RawFormat = null,
40 format: ?Format = null,
9041 only_section: ?[]const u8 = null,
9142 pad_to: ?u64 = null,
9243
......@@ -96,148 +47,80 @@ pub const Options = struct {
9647 /// Put the stripped out debug sections in a separate file.
9748 /// note: the `basename` is baked into the elf file to specify the link to the separate debug file.
9849 /// see https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
99 extract_to_separate_file: bool = false,
50 ///
51 /// Makes `getOutputSeparatedDebug` return non-null.
52 separate_debug_file: ?SeparateDebugFile = null,
10053
101 add_section: ?AddSection = null,
102 set_section_alignment: ?SetSectionAlignment = null,
103 set_section_flags: ?SetSectionFlags = null,
54 pub const SeparateDebugFile = struct {
55 basename: ?[]const u8,
56 };
10457};
10558
106pub fn create(
107 owner: *std.Build,
108 input_file: std.Build.LazyPath,
109 options: Options,
110) *ObjCopy {
111 const objcopy = owner.allocator.create(ObjCopy) catch @panic("OOM");
112 objcopy.* = ObjCopy{
113 .step = Step.init(.{
114 .id = base_id,
115 .name = owner.fmt("objcopy {s}", .{input_file.getDisplayName()}),
59pub fn create(owner: *std.Build, input_file: std.Build.LazyPath, options: Options) *ObjCopy {
60 const graph = owner.graph;
61 const wc = &graph.wip_configuration;
62 const oc = graph.create(ObjCopy);
63 oc.* = .{
64 .step = .init(.{
65 .tag = base_tag,
66 .name = owner.fmt("objcopy {f}", .{input_file}),
11667 .owner = owner,
117 .makeFn = make,
11868 }),
11969 .input_file = input_file,
120 .basename = options.basename orelse input_file.getDisplayName(),
121 .output_file = std.Build.GeneratedFile{ .step = &objcopy.step },
122 .output_file_debug = if (options.strip != .none and options.extract_to_separate_file) std.Build.GeneratedFile{ .step = &objcopy.step } else null,
70 .basename = if (options.basename) |s| .init(wc.addString(s) catch @panic("OOM")) else .none,
71 .output_file = graph.addGeneratedFile(&oc.step),
72 .debug_file = if (options.separate_debug_file) |df| .{
73 .basename = if (df.basename) |s| .init(wc.addString(s) catch @panic("OOM")) else .none,
74 .output_file = graph.addGeneratedFile(&oc.step),
75 } else null,
12376 .format = options.format,
124 .only_section = options.only_section,
77 .only_section = if (options.only_section) |s| .init(wc.addString(s) catch @panic("OOM")) else .none,
12578 .pad_to = options.pad_to,
12679 .strip = options.strip,
12780 .compress_debug = options.compress_debug,
128 .add_section = options.add_section,
129 .set_section_alignment = options.set_section_alignment,
130 .set_section_flags = options.set_section_flags,
13181 };
132 input_file.addStepDependencies(&objcopy.step);
133 return objcopy;
82 input_file.addStepDependencies(&oc.step);
83 return oc;
13484}
13585
136pub fn getOutput(objcopy: *const ObjCopy) std.Build.LazyPath {
137 return .{ .generated = .{ .file = &objcopy.output_file } };
86pub const UpdateSectionOptions = struct {
87 alignment: ?std.mem.Alignment = null,
88 flags: SectionFlags = .default,
89};
90
91pub fn updateSection(oc: *ObjCopy, section_name: []const u8, options: UpdateSectionOptions) void {
92 const graph = oc.owner.graph;
93 const arena = graph.arena;
94 const wc = &graph.wip_configuration;
95 oc.update_sections.append(arena, .{
96 .flags = .{
97 .section_flags = options.flags,
98 .alignment = .init(options.alignment),
99 },
100 .section_name = wc.addString(section_name) catch @panic("OOM"),
101 }) catch @panic("OOM");
138102}
139pub fn getOutputSeparatedDebug(objcopy: *const ObjCopy) ?std.Build.LazyPath {
140 return if (objcopy.output_file_debug) |*file| .{ .generated = .{ .file = file } } else null;
103
104pub const AddSectionOptions = struct {
105 file_path: std.Build.LazyPath,
106};
107
108pub fn addSection(oc: *ObjCopy, section_name: []const u8, options: AddSectionOptions) void {
109 const graph = oc.owner.graph;
110 const arena = graph.arena;
111 const wc = &graph.wip_configuration;
112 oc.add_sections.append(arena, .{
113 .section_name = wc.addString(section_name) catch @panic("OOM"),
114 .file_path = options.file_path,
115 }) catch @panic("OOM");
116 options.file_path.addStepDependencies(&oc.step);
141117}
142118
143fn make(step: *Step, options: Step.MakeOptions) !void {
144 const prog_node = options.progress_node;
145 const b = step.owner;
146 const io = b.graph.io;
147 const objcopy: *ObjCopy = @fieldParentPtr("step", step);
148 try step.singleUnchangingWatchInput(objcopy.input_file);
149
150 var man = b.graph.cache.obtain();
151 defer man.deinit();
152
153 const full_src_path = objcopy.input_file.getPath2(b, step);
154 _ = try man.addFile(full_src_path, null);
155 man.hash.addOptionalBytes(objcopy.only_section);
156 man.hash.addOptional(objcopy.pad_to);
157 man.hash.addOptional(objcopy.format);
158 man.hash.add(objcopy.compress_debug);
159 man.hash.add(objcopy.strip);
160 man.hash.add(objcopy.output_file_debug != null);
161
162 if (try step.cacheHit(&man)) {
163 // Cache hit, skip subprocess execution.
164 const digest = man.final();
165 objcopy.output_file.path = try b.cache_root.join(b.allocator, &.{
166 "o", &digest, objcopy.basename,
167 });
168 if (objcopy.output_file_debug) |*file| {
169 file.path = try b.cache_root.join(b.allocator, &.{
170 "o", &digest, b.fmt("{s}.debug", .{objcopy.basename}),
171 });
172 }
173 return;
174 }
175
176 const digest = man.final();
177 const cache_path = "o" ++ fs.path.sep_str ++ digest;
178 const full_dest_path = try b.cache_root.join(b.allocator, &.{ cache_path, objcopy.basename });
179 const full_dest_path_debug = try b.cache_root.join(b.allocator, &.{ cache_path, b.fmt("{s}.debug", .{objcopy.basename}) });
180 b.cache_root.handle.createDirPath(io, cache_path) catch |err| {
181 return step.fail("unable to make path {s}: {s}", .{ cache_path, @errorName(err) });
182 };
119pub fn getOutput(oc: *const ObjCopy) std.Build.LazyPath {
120 return .{ .generated = .{ .index = oc.output_file } };
121}
183122
184 var argv = std.array_list.Managed([]const u8).init(b.allocator);
185 try argv.appendSlice(&.{ b.graph.zig_exe, "objcopy" });
186
187 if (objcopy.only_section) |only_section| {
188 try argv.appendSlice(&.{ "-j", only_section });
189 }
190 switch (objcopy.strip) {
191 .none => {},
192 .debug => try argv.appendSlice(&.{"--strip-debug"}),
193 .debug_and_symbols => try argv.appendSlice(&.{"--strip-all"}),
194 }
195 if (objcopy.pad_to) |pad_to| {
196 try argv.appendSlice(&.{ "--pad-to", b.fmt("{d}", .{pad_to}) });
197 }
198 if (objcopy.format) |format| switch (format) {
199 .bin => try argv.appendSlice(&.{ "-O", "binary" }),
200 .hex => try argv.appendSlice(&.{ "-O", "hex" }),
201 .elf => try argv.appendSlice(&.{ "-O", "elf" }),
202 };
203 if (objcopy.compress_debug) {
204 try argv.appendSlice(&.{"--compress-debug-sections"});
205 }
206 if (objcopy.output_file_debug != null) {
207 try argv.appendSlice(&.{b.fmt("--extract-to={s}", .{full_dest_path_debug})});
208 }
209 if (objcopy.add_section) |section| {
210 try argv.append("--add-section");
211 try argv.appendSlice(&.{b.fmt("{s}={s}", .{ section.section_name, section.file_path.getPath2(b, step) })});
212 }
213 if (objcopy.set_section_alignment) |set_align| {
214 try argv.append("--set-section-alignment");
215 try argv.appendSlice(&.{b.fmt("{s}={d}", .{ set_align.section_name, set_align.alignment })});
216 }
217 if (objcopy.set_section_flags) |set_flags| {
218 const f = set_flags.flags;
219 // trailing comma is allowed
220 try argv.append("--set-section-flags");
221 try argv.appendSlice(&.{b.fmt("{s}={s}{s}{s}{s}{s}{s}{s}{s}{s}", .{
222 set_flags.section_name,
223 if (f.alloc) "alloc," else "",
224 if (f.contents) "contents," else "",
225 if (f.load) "load," else "",
226 if (f.readonly) "readonly," else "",
227 if (f.code) "code," else "",
228 if (f.exclude) "exclude," else "",
229 if (f.large) "large," else "",
230 if (f.merge) "merge," else "",
231 if (f.strings) "strings," else "",
232 })});
233 }
234
235 try argv.appendSlice(&.{ full_src_path, full_dest_path });
236
237 try argv.append("--listen=-");
238 _ = try step.evalZigProcess(argv.items, prog_node, false, options.web_server, options.gpa);
239
240 objcopy.output_file.path = full_dest_path;
241 if (objcopy.output_file_debug) |*file| file.path = full_dest_path_debug;
242 try man.writeManifest();
123pub fn getOutputSeparatedDebug(oc: *const ObjCopy) ?std.Build.LazyPath {
124 const df = oc.debug_file orelse return null;
125 return .{ .generated = .{ .index = df.output_file } };
243126}
lib/std/Build/Step/Options.zig+26-261
......@@ -1,37 +1,41 @@
11const Options = @This();
2
23const builtin = @import("builtin");
34
45const std = @import("std");
56const Io = std.Io;
67const fs = std.fs;
78const Step = std.Build.Step;
8const GeneratedFile = std.Build.GeneratedFile;
99const LazyPath = std.Build.LazyPath;
10
11pub const base_id: Step.Id = .options;
10const Configuration = std.Build.Configuration;
1211
1312step: Step,
14generated_file: GeneratedFile,
15
16contents: std.ArrayList(u8),
17args: std.ArrayList(Arg),
13generated_file: Configuration.GeneratedFileIndex,
14contents: std.ArrayList(u8) = .empty,
15args: std.ArrayList(Arg) = .empty,
1816encountered_types: std.StringHashMapUnmanaged(void),
1917
18pub const base_tag: Step.Tag = .options;
19
20pub const Arg = struct {
21 name: Configuration.String,
22 path: LazyPath,
23};
24
2025pub fn create(owner: *std.Build) *Options {
21 const options = owner.allocator.create(Options) catch @panic("OOM");
26 const graph = owner.graph;
27 const arena = graph.arena;
28
29 const options = arena.create(Options) catch @panic("OOM");
2230 options.* = .{
2331 .step = .init(.{
24 .id = base_id,
32 .tag = base_tag,
2533 .name = "options",
2634 .owner = owner,
27 .makeFn = make,
2835 }),
29 .generated_file = undefined,
30 .contents = .empty,
31 .args = .empty,
36 .generated_file = graph.addGeneratedFile(&options.step),
3237 .encountered_types = .empty,
3338 };
34 options.generated_file = .{ .step = &options.step };
3539
3640 return options;
3741}
......@@ -410,16 +414,14 @@ fn printStructValue(
410414 }
411415}
412416
413/// The value is the path in the cache dir.
414/// Adds a dependency automatically.
415pub fn addOptionPath(
416 options: *Options,
417 name: []const u8,
418 path: LazyPath,
419) void {
420 const arena = options.step.owner.allocator;
417/// The added option has type `[]const u8` and value of the provided path.
418pub fn addOptionPath(options: *Options, name: []const u8, path: LazyPath) void {
419 const graph = options.step.owner.graph;
420 const arena = graph.arena;
421 const wc = &graph.wip_configuration;
422
421423 options.args.append(arena, .{
422 .name = options.step.owner.dupe(name),
424 .name = try wc.addString(name),
423425 .path = path.dupe(options.step.owner),
424426 }) catch @panic("OOM");
425427 path.addStepDependencies(&options.step);
......@@ -434,242 +436,5 @@ pub fn createModule(options: *Options) *std.Build.Module {
434436/// Returns the main artifact of this Build Step which is a Zig source file
435437/// generated from the key-value pairs of the Options.
436438pub fn getOutput(options: *Options) LazyPath {
437 return .{ .generated = .{ .file = &options.generated_file } };
438}
439
440fn make(step: *Step, make_options: Step.MakeOptions) !void {
441 // This step completes so quickly that no progress reporting is necessary.
442 _ = make_options;
443
444 const b = step.owner;
445 const io = b.graph.io;
446 const options: *Options = @fieldParentPtr("step", step);
447
448 for (options.args.items) |item| {
449 options.addOption(
450 []const u8,
451 item.name,
452 item.path.getPath2(b, step),
453 );
454 }
455 if (!step.inputs.populated()) for (options.args.items) |item| {
456 try step.addWatchInput(item.path);
457 };
458
459 const basename = "options.zig";
460
461 // Hash contents to file name.
462 var hash = b.graph.cache.hash;
463 // Random bytes to make unique. Refresh this with new random bytes when
464 // implementation is modified in a non-backwards-compatible way.
465 hash.add(@as(u32, 0xad95e922));
466 hash.addBytes(options.contents.items);
467 const sub_path = "c" ++ fs.path.sep_str ++ hash.final() ++ fs.path.sep_str ++ basename;
468
469 options.generated_file.path = try b.cache_root.join(b.allocator, &.{sub_path});
470
471 // Optimize for the hot path. Stat the file, and if it already exists,
472 // cache hit.
473 if (b.cache_root.handle.access(io, sub_path, .{})) |_| {
474 // This is the hot path, success.
475 step.result_cached = true;
476 return;
477 } else |outer_err| switch (outer_err) {
478 error.FileNotFound => {
479 var atomic_file = b.cache_root.handle.createFileAtomic(io, sub_path, .{
480 .replace = false,
481 .make_path = true,
482 }) catch |err| return step.fail("failed to create temporary path for '{f}{s}': {t}", .{
483 b.cache_root, sub_path, err,
484 });
485 defer atomic_file.deinit(io);
486
487 atomic_file.file.writeStreamingAll(io, options.contents.items) catch |err| {
488 return step.fail("failed to write options to temporary path for '{f}{s}': {t}", .{
489 b.cache_root, sub_path, err,
490 });
491 };
492
493 atomic_file.link(io) catch |err| switch (err) {
494 error.PathAlreadyExists => {
495 step.result_cached = true;
496 return;
497 },
498 else => return step.fail("failed to link temporary file into '{f}{s}': {t}", .{
499 b.cache_root, sub_path, err,
500 }),
501 };
502 },
503 else => |e| return step.fail("unable to access options file '{f}{s}': {t}", .{
504 b.cache_root, sub_path, e,
505 }),
506 }
507}
508
509const Arg = struct {
510 name: []const u8,
511 path: LazyPath,
512};
513
514test Options {
515 if (builtin.os.tag == .wasi) return error.SkipZigTest;
516
517 const io = std.testing.io;
518
519 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
520 defer arena.deinit();
521
522 const cwd = try std.process.currentPathAlloc(io, std.testing.allocator);
523 defer std.testing.allocator.free(cwd);
524
525 var graph: std.Build.Graph = .{
526 .io = io,
527 .arena = arena.allocator(),
528 .cache = .{
529 .io = io,
530 .gpa = arena.allocator(),
531 .manifest_dir = Io.Dir.cwd(),
532 .cwd = cwd,
533 },
534 .zig_exe = "test",
535 .environ_map = std.process.Environ.Map.init(arena.allocator()),
536 .global_cache_root = .{ .path = "test", .handle = Io.Dir.cwd() },
537 .host = .{
538 .query = .{},
539 .result = try std.zig.system.resolveTargetQuery(io, .{}),
540 },
541 .zig_lib_directory = std.Build.Cache.Directory.cwd(),
542 .time_report = false,
543 };
544
545 var builder = try std.Build.create(
546 &graph,
547 .{ .path = "test", .handle = Io.Dir.cwd() },
548 .{ .path = "test", .handle = Io.Dir.cwd() },
549 &.{},
550 );
551
552 const options = builder.addOptions();
553
554 const KeywordEnum = enum {
555 @"0.8.1",
556 };
557
558 const NormalEnum = enum {
559 foo,
560 bar,
561 };
562
563 const nested_array = [2][2]u16{
564 [2]u16{ 300, 200 },
565 [2]u16{ 300, 200 },
566 };
567 const nested_slice: []const []const u16 = &[_][]const u16{ &nested_array[0], &nested_array[1] };
568
569 const NormalStruct = struct {
570 hello: ?[]const u8,
571 world: bool = true,
572 };
573
574 const NestedStruct = struct {
575 normal_struct: NormalStruct,
576 normal_enum: NormalEnum = .foo,
577 };
578
579 options.addOption(usize, "option1", 1);
580 options.addOption(?usize, "option2", null);
581 options.addOption(?usize, "option3", 3);
582 options.addOption(comptime_int, "option4", 4);
583 options.addOption(comptime_float, "option5", 5.01);
584 options.addOption([]const u8, "string", "zigisthebest");
585 options.addOption(?[]const u8, "optional_string", null);
586 options.addOption([2][2]u16, "nested_array", nested_array);
587 options.addOption([]const []const u16, "nested_slice", nested_slice);
588 options.addOption(KeywordEnum, "keyword_enum", .@"0.8.1");
589 options.addOption(std.SemanticVersion, "semantic_version", try std.SemanticVersion.parse("0.1.2-foo+bar"));
590 options.addOption(NormalEnum, "normal1_enum", NormalEnum.foo);
591 options.addOption(NormalEnum, "normal2_enum", NormalEnum.bar);
592 options.addOption(NormalStruct, "normal1_struct", NormalStruct{
593 .hello = "foo",
594 });
595 options.addOption(NormalStruct, "normal2_struct", NormalStruct{
596 .hello = null,
597 .world = false,
598 });
599 options.addOption(NestedStruct, "nested_struct", NestedStruct{
600 .normal_struct = .{ .hello = "bar" },
601 });
602
603 try std.testing.expectEqualStrings(
604 \\pub const option1: usize = 1;
605 \\pub const option2: ?usize = null;
606 \\pub const option3: ?usize = 3;
607 \\pub const option4: comptime_int = 4;
608 \\pub const option5: comptime_float = 5.01;
609 \\pub const string: []const u8 = "zigisthebest";
610 \\pub const optional_string: ?[]const u8 = null;
611 \\pub const nested_array: [2][2]u16 = [2][2]u16 {
612 \\ [2]u16 {
613 \\ 300,
614 \\ 200,
615 \\ },
616 \\ [2]u16 {
617 \\ 300,
618 \\ 200,
619 \\ },
620 \\};
621 \\pub const nested_slice: []const []const u16 = &[_][]const u16 {
622 \\ &[_]u16 {
623 \\ 300,
624 \\ 200,
625 \\ },
626 \\ &[_]u16 {
627 \\ 300,
628 \\ 200,
629 \\ },
630 \\};
631 \\pub const @"Build.Step.Options.decltest.Options.KeywordEnum" = enum (u0) {
632 \\ @"0.8.1" = 0,
633 \\};
634 \\pub const keyword_enum: @"Build.Step.Options.decltest.Options.KeywordEnum" = .@"0.8.1";
635 \\pub const semantic_version: @import("std").SemanticVersion = .{
636 \\ .major = 0,
637 \\ .minor = 1,
638 \\ .patch = 2,
639 \\ .pre = "foo",
640 \\ .build = "bar",
641 \\};
642 \\pub const @"Build.Step.Options.decltest.Options.NormalEnum" = enum (u1) {
643 \\ foo = 0,
644 \\ bar = 1,
645 \\};
646 \\pub const normal1_enum: @"Build.Step.Options.decltest.Options.NormalEnum" = .foo;
647 \\pub const normal2_enum: @"Build.Step.Options.decltest.Options.NormalEnum" = .bar;
648 \\pub const @"Build.Step.Options.decltest.Options.NormalStruct" = struct {
649 \\ hello: ?[]const u8,
650 \\ world: bool = true,
651 \\};
652 \\pub const normal1_struct: @"Build.Step.Options.decltest.Options.NormalStruct" = .{
653 \\ .hello = "foo",
654 \\ .world = true,
655 \\};
656 \\pub const normal2_struct: @"Build.Step.Options.decltest.Options.NormalStruct" = .{
657 \\ .hello = null,
658 \\ .world = false,
659 \\};
660 \\pub const @"Build.Step.Options.decltest.Options.NestedStruct" = struct {
661 \\ normal_struct: @"Build.Step.Options.decltest.Options.NormalStruct",
662 \\ normal_enum: @"Build.Step.Options.decltest.Options.NormalEnum" = .foo,
663 \\};
664 \\pub const nested_struct: @"Build.Step.Options.decltest.Options.NestedStruct" = .{
665 \\ .normal_struct = .{
666 \\ .hello = "bar",
667 \\ .world = true,
668 \\ },
669 \\ .normal_enum = .foo,
670 \\};
671 \\
672 , options.contents.items);
673
674 _ = try std.zig.Ast.parse(arena.allocator(), try options.contents.toOwnedSliceSentinel(arena.allocator(), 0), .zig);
439 return .{ .generated = .{ .index = options.generated_file } };
675440}
lib/std/Build/Step/Run.zig+139-2221
......@@ -11,8 +11,9 @@ const process = std.process;
1111const EnvMap = std.process.Environ.Map;
1212const assert = std.debug.assert;
1313const Path = std.Build.Cache.Path;
14const Configuration = std.Build.Configuration;
1415
15pub const base_id: Step.Id = .run;
16pub const base_tag: Step.Tag = .run;
1617
1718step: Step,
1819
......@@ -84,36 +85,13 @@ stdio_limit: std.Io.Limit,
8485captured_stdout: ?*CapturedStdIo,
8586captured_stderr: ?*CapturedStdIo,
8687
87dep_output_file: ?*Output,
88
8988has_side_effects: bool,
90
91/// If this is a Zig unit test binary, this tracks the names of the unit
92/// tests that are also fuzz tests. Indexes cannot be used as they may
93/// change between reruns.
94fuzz_tests: std.ArrayList([]const u8),
95cached_test_metadata: ?CachedTestMetadata = null,
96
97/// Populated during the fuzz phase if this run step corresponds to a unit test
98/// executable that contains fuzz tests.
99rebuilt_executable: ?Path,
89test_runner_mode: bool = false,
10090
10191/// If this Run step was produced by a Compile step, it is tracked here.
10292producer: ?*Step.Compile,
10393
104pub const Color = enum {
105 /// `CLICOLOR_FORCE` is set, and `NO_COLOR` is unset.
106 enable,
107 /// `NO_COLOR` is set, and `CLICOLOR_FORCE` is unset.
108 disable,
109 /// If the build runner is using color, equivalent to `.enable`. Otherwise, equivalent to `.disable`.
110 inherit,
111 /// If stderr is captured or checked, equivalent to `.disable`. Otherwise, equivalent to `.inherit`.
112 auto,
113 /// The build runner does not modify the `CLICOLOR_FORCE` or `NO_COLOR` environment variables.
114 /// They are treated like normal variables, so can be controlled through `setEnvironmentVariable`.
115 manual,
116};
94pub const Color = std.Build.Configuration.Step.Run.Color;
11795
11896pub const StdIn = union(enum) {
11997 none,
......@@ -159,9 +137,12 @@ pub const Arg = union(enum) {
159137 lazy_path: PrefixedLazyPath,
160138 decorated_directory: DecoratedLazyPath,
161139 file_content: PrefixedLazyPath,
162 bytes: []u8,
140 bytes: []const u8,
163141 output_file: *Output,
142 output_file_dep: *Output,
164143 output_directory: *Output,
144 /// The arguments passed after "--" on the "zig build" CLI.
145 passthru,
165146};
166147
167148pub const PrefixedArtifact = struct {
......@@ -181,7 +162,7 @@ pub const DecoratedLazyPath = struct {
181162};
182163
183164pub const Output = struct {
184 generated_file: std.Build.GeneratedFile,
165 generated_file: Configuration.GeneratedFileIndex,
185166 prefix: []const u8,
186167 basename: []const u8,
187168};
......@@ -197,22 +178,16 @@ pub const CapturedStdIo = struct {
197178 trim_whitespace: TrimWhitespace = .none,
198179 };
199180
200 pub const TrimWhitespace = enum {
201 none,
202 all,
203 leading,
204 trailing,
205 };
181 pub const TrimWhitespace = std.Build.Configuration.Step.Run.TrimWhitespace;
206182};
207183
208184pub fn create(owner: *std.Build, name: []const u8) *Run {
209185 const run = owner.allocator.create(Run) catch @panic("OOM");
210186 run.* = .{
211187 .step = .init(.{
212 .id = base_id,
188 .tag = base_tag,
213189 .name = name,
214190 .owner = owner,
215 .makeFn = make,
216191 }),
217192 .argv = .empty,
218193 .cwd = null,
......@@ -227,10 +202,7 @@ pub fn create(owner: *std.Build, name: []const u8) *Run {
227202 .stdio_limit = .unlimited,
228203 .captured_stdout = null,
229204 .captured_stderr = null,
230 .dep_output_file = null,
231205 .has_side_effects = false,
232 .fuzz_tests = .empty,
233 .rebuilt_executable = null,
234206 .producer = null,
235207 };
236208 return run;
......@@ -242,13 +214,9 @@ pub fn setName(run: *Run, name: []const u8) void {
242214}
243215
244216pub fn enableTestRunnerMode(run: *Run) void {
245 const b = run.step.owner;
217 if (run.test_runner_mode) return;
246218 run.stdio = .zig_test;
247 run.addPrefixedDirectoryArg("--cache-dir=", .{ .cwd_relative = b.cache_root.path orelse "." });
248 run.addArgs(&.{
249 b.fmt("--seed=0x{x}", .{b.graph.random_seed}),
250 "--listen=-",
251 });
219 run.test_runner_mode = true;
252220}
253221
254222pub fn addArtifactArg(run: *Run, artifact: *Step.Compile) void {
......@@ -256,13 +224,14 @@ pub fn addArtifactArg(run: *Run, artifact: *Step.Compile) void {
256224}
257225
258226pub fn addPrefixedArtifactArg(run: *Run, prefix: []const u8, artifact: *Step.Compile) void {
259 const b = run.step.owner;
227 const graph = run.step.owner.graph;
228 const arena = graph.arena;
260229
261230 const prefixed_artifact: PrefixedArtifact = .{
262 .prefix = b.dupe(prefix),
231 .prefix = graph.dupeString(prefix),
263232 .artifact = artifact,
264233 };
265 run.argv.append(b.allocator, .{ .artifact = prefixed_artifact }) catch @panic("OOM");
234 run.argv.append(arena, .{ .artifact = prefixed_artifact }) catch @panic("OOM");
266235
267236 const bin_file = artifact.getEmittedBin();
268237 bin_file.addStepDependencies(&run.step);
......@@ -273,21 +242,23 @@ pub fn addPrefixedArtifactArg(run: *Run, prefix: []const u8, artifact: *Step.Com
273242/// Returns a `std.Build.LazyPath` which can be used as inputs to other APIs
274243/// throughout the build system.
275244///
245/// `sub_path` is the name of the generated output file which may have zero or
246/// more path components.
247///
276248/// Related:
277249/// * `addPrefixedOutputFileArg` - same thing but prepends a string to the argument
278250/// * `addFileArg` - for input files given to the child process
279pub fn addOutputFileArg(run: *Run, basename: []const u8) std.Build.LazyPath {
280 return run.addPrefixedOutputFileArg("", basename);
251pub fn addOutputFileArg(run: *Run, sub_path: []const u8) std.Build.LazyPath {
252 return run.addPrefixedOutputFileArg("", sub_path);
281253}
282254
283255/// Provides a file path as a command line argument to the command being run.
284/// Asserts `basename` is not empty.
285256///
286/// For example, a prefix of "-o" and basename of "output.txt" will result in
257/// For example, a prefix of "-o" and `sub_path` of "output.txt" will result in
287258/// the child process seeing something like this: "-ozig-cache/.../output.txt"
288259///
289260/// The child process will see a single argument, regardless of whether the
290/// prefix or basename have spaces.
261/// prefix or `sub_path` have spaces.
291262///
292263/// The returned `std.Build.LazyPath` can be used as inputs to other APIs
293264/// throughout the build system.
......@@ -298,24 +269,30 @@ pub fn addOutputFileArg(run: *Run, basename: []const u8) std.Build.LazyPath {
298269pub fn addPrefixedOutputFileArg(
299270 run: *Run,
300271 prefix: []const u8,
301 basename: []const u8,
272 /// The name of the generated output file which may have zero or more path
273 /// components.
274 ///
275 /// Asserted to be non-empty.
276 sub_path: []const u8,
302277) std.Build.LazyPath {
303278 const b = run.step.owner;
304 if (basename.len == 0) @panic("basename must not be empty");
279 const graph = b.graph;
280 const arena = graph.arena;
281 assert(sub_path.len != 0);
305282
306 const output = b.allocator.create(Output) catch @panic("OOM");
283 const output = graph.create(Output);
307284 output.* = .{
308 .prefix = b.dupe(prefix),
309 .basename = b.dupe(basename),
310 .generated_file = .{ .step = &run.step },
285 .prefix = graph.dupeString(prefix),
286 .basename = graph.dupeString(sub_path),
287 .generated_file = graph.addGeneratedFile(&run.step),
311288 };
312 run.argv.append(b.allocator, .{ .output_file = output }) catch @panic("OOM");
289 run.argv.append(arena, .{ .output_file = output }) catch @panic("OOM");
313290
314291 if (run.rename_step_with_output_arg) {
315 run.setName(b.fmt("{s} ({s})", .{ run.step.name, basename }));
292 run.setName(b.fmt("{s} ({s})", .{ run.step.name, sub_path }));
316293 }
317294
318 return .{ .generated = .{ .file = &output.generated_file } };
295 return .{ .generated = .{ .index = output.generated_file } };
319296}
320297
321298/// Appends an input file to the command line arguments.
......@@ -344,13 +321,14 @@ pub fn addFileArg(run: *Run, lp: std.Build.LazyPath) void {
344321/// * `addFileArg` - same thing but without the prefix
345322/// * `addOutputFileArg` - for files generated by the child process
346323pub fn addPrefixedFileArg(run: *Run, prefix: []const u8, lp: std.Build.LazyPath) void {
347 const b = run.step.owner;
324 const graph = run.step.owner.graph;
325 const arena = graph.arena;
348326
349327 const prefixed_file_source: PrefixedLazyPath = .{
350 .prefix = b.dupe(prefix),
351 .lazy_path = lp.dupe(b),
328 .prefix = graph.dupeString(prefix),
329 .lazy_path = lp.dupe(graph),
352330 };
353 run.argv.append(b.allocator, .{ .lazy_path = prefixed_file_source }) catch @panic("OOM");
331 run.argv.append(arena, .{ .lazy_path = prefixed_file_source }) catch @panic("OOM");
354332 lp.addStepDependencies(&run.step);
355333}
356334
......@@ -391,7 +369,8 @@ pub fn addFileContentArg(run: *Run, lp: std.Build.LazyPath) void {
391369/// Related:
392370/// * `addFileContentArg` - same thing but without the prefix
393371pub fn addPrefixedFileContentArg(run: *Run, prefix: []const u8, lp: std.Build.LazyPath) void {
394 const b = run.step.owner;
372 const graph = run.step.owner.graph;
373 const arena = graph.arena;
395374
396375 // Some parts of this step's configure phase API rely on the first argument being somewhat
397376 // transparent/readable, but the content of the file specified by `lp` remains completely
......@@ -401,10 +380,10 @@ pub fn addPrefixedFileContentArg(run: *Run, prefix: []const u8, lp: std.Build.La
401380 }
402381
403382 const prefixed_file_source: PrefixedLazyPath = .{
404 .prefix = b.dupe(prefix),
405 .lazy_path = lp.dupe(b),
383 .prefix = graph.dupeString(prefix),
384 .lazy_path = lp.dupe(graph),
406385 };
407 run.argv.append(b.allocator, .{ .file_content = prefixed_file_source }) catch @panic("OOM");
386 run.argv.append(arena, .{ .file_content = prefixed_file_source }) catch @panic("OOM");
408387 lp.addStepDependencies(&run.step);
409388}
410389
......@@ -441,21 +420,22 @@ pub fn addPrefixedOutputDirectoryArg(
441420 basename: []const u8,
442421) std.Build.LazyPath {
443422 if (basename.len == 0) @panic("basename must not be empty");
444 const b = run.step.owner;
423 const graph = run.step.owner.graph;
424 const arena = graph.arena;
445425
446 const output = b.allocator.create(Output) catch @panic("OOM");
426 const output = arena.create(Output) catch @panic("OOM");
447427 output.* = .{
448 .prefix = b.dupe(prefix),
449 .basename = b.dupe(basename),
450 .generated_file = .{ .step = &run.step },
428 .prefix = graph.dupeString(prefix),
429 .basename = graph.dupeString(basename),
430 .generated_file = graph.addGeneratedFile(&run.step),
451431 };
452 run.argv.append(b.allocator, .{ .output_directory = output }) catch @panic("OOM");
432 run.argv.append(arena, .{ .output_directory = output }) catch @panic("OOM");
453433
454434 if (run.rename_step_with_output_arg) {
455 run.setName(b.fmt("{s} ({s})", .{ run.step.name, basename }));
435 run.setName(std.fmt.allocPrint(arena, "{s} ({s})", .{ run.step.name, basename }) catch @panic("OOM"));
456436 }
457437
458 return .{ .generated = .{ .file = &output.generated_file } };
438 return .{ .generated = .{ .index = output.generated_file } };
459439}
460440
461441pub fn addDirectoryArg(run: *Run, lazy_directory: std.Build.LazyPath) void {
......@@ -463,10 +443,11 @@ pub fn addDirectoryArg(run: *Run, lazy_directory: std.Build.LazyPath) void {
463443}
464444
465445pub fn addPrefixedDirectoryArg(run: *Run, prefix: []const u8, lazy_directory: std.Build.LazyPath) void {
466 const b = run.step.owner;
467 run.argv.append(b.allocator, .{ .decorated_directory = .{
468 .prefix = b.dupe(prefix),
469 .lazy_path = lazy_directory.dupe(b),
446 const graph = run.step.owner.graph;
447 const arena = graph.arena;
448 run.argv.append(arena, .{ .decorated_directory = .{
449 .prefix = graph.dupeString(prefix),
450 .lazy_path = lazy_directory.dupe(graph),
470451 .suffix = "",
471452 } }) catch @panic("OOM");
472453 lazy_directory.addStepDependencies(&run.step);
......@@ -478,11 +459,12 @@ pub fn addDecoratedDirectoryArg(
478459 lazy_directory: std.Build.LazyPath,
479460 suffix: []const u8,
480461) void {
481 const b = run.step.owner;
482 run.argv.append(b.allocator, .{ .decorated_directory = .{
483 .prefix = b.dupe(prefix),
484 .lazy_path = lazy_directory.dupe(b),
485 .suffix = b.dupe(suffix),
462 const graph = run.step.owner.graph;
463 const arena = graph.arena;
464 run.argv.append(arena, .{ .decorated_directory = .{
465 .prefix = graph.dupeString(prefix),
466 .lazy_path = lazy_directory.dupe(graph),
467 .suffix = graph.dupeString(suffix),
486468 } }) catch @panic("OOM");
487469 lazy_directory.addStepDependencies(&run.step);
488470}
......@@ -496,34 +478,63 @@ pub fn addDepFileOutputArg(run: *Run, basename: []const u8) std.Build.LazyPath {
496478
497479/// Add a prefixed path argument to a dep file (.d) for the child process to
498480/// write its discovered additional dependencies.
499/// Only one dep file argument is allowed by instance.
500481pub fn addPrefixedDepFileOutputArg(run: *Run, prefix: []const u8, basename: []const u8) std.Build.LazyPath {
501482 const b = run.step.owner;
502 assert(run.dep_output_file == null);
483 const graph = b.graph;
484 const arena = graph.arena;
503485
504 const dep_file = b.allocator.create(Output) catch @panic("OOM");
486 const dep_file = arena.create(Output) catch @panic("OOM");
505487 dep_file.* = .{
506 .prefix = b.dupe(prefix),
507 .basename = b.dupe(basename),
508 .generated_file = .{ .step = &run.step },
488 .prefix = graph.dupeString(prefix),
489 .basename = graph.dupeString(basename),
490 .generated_file = graph.addGeneratedFile(&run.step),
509491 };
510492
511 run.dep_output_file = dep_file;
493 run.argv.append(arena, .{ .output_file_dep = dep_file }) catch @panic("OOM");
512494
513 run.argv.append(b.allocator, .{ .output_file = dep_file }) catch @panic("OOM");
514
515 return .{ .generated = .{ .file = &dep_file.generated_file } };
495 return .{ .generated = .{ .index = dep_file.generated_file } };
516496}
517497
498/// Appends the contents of `arg`, verbatim, to the command line that will be
499/// passed to the process being run.
500///
501/// If `arg` is an input file, `addFileInput` (or related function) must be
502/// used instead to ensure correct cache behavior.
503///
504/// If `arg` is an output file, `addOutputFileArg` (or related function) must
505/// be used instead to ensure correct cache behavior.
518506pub fn addArg(run: *Run, arg: []const u8) void {
519 const b = run.step.owner;
520 run.argv.append(b.allocator, .{ .bytes = b.dupe(arg) }) catch @panic("OOM");
507 const graph = run.step.owner.graph;
508 const arena = graph.arena;
509 run.argv.append(arena, .{ .bytes = graph.dupeString(arg) }) catch @panic("OOM");
521510}
522511
512/// Appends each of `args`, verbatim, to the command line that will be passed
513/// to the process being run.
514///
515/// If any element of `args` is an input file, `addFileInput` must be used
516/// instead to ensure correct cache behavior.
517///
518/// If any element of `args` is an output file, `addOutputFileArg` (or related
519/// function) must be used instead to ensure correct cache behavior.
523520pub fn addArgs(run: *Run, args: []const []const u8) void {
524521 for (args) |arg| run.addArg(arg);
525522}
526523
524/// Appends the extra arguments provided to `zig build` to the command line
525/// that will be passed to the process being run.
526///
527/// This causes the step to be considered to have side effects, disabling
528/// caching.
529///
530/// In the example command `zig build run -- arg1 arg2`, "arg1" and "arg2" will
531/// be passed to the process being run.
532pub fn addPassthruArgs(run: *Run) void {
533 const graph = run.step.owner.graph;
534 const arena = graph.arena;
535 run.argv.append(arena, .passthru) catch @panic("OOM");
536}
537
527538pub fn setStdIn(run: *Run, stdin: StdIn) void {
528539 switch (stdin) {
529540 .lazy_path => |lazy_path| lazy_path.addStepDependencies(&run.step),
......@@ -533,8 +544,9 @@ pub fn setStdIn(run: *Run, stdin: StdIn) void {
533544}
534545
535546pub fn setCwd(run: *Run, cwd: Build.LazyPath) void {
547 const graph = run.step.owner.graph;
536548 cwd.addStepDependencies(&run.step);
537 run.cwd = cwd.dupe(run.step.owner);
549 run.cwd = cwd.dupe(graph);
538550}
539551
540552pub fn clearEnvironment(run: *Run) void {
......@@ -560,7 +572,7 @@ pub fn addPathDir(run: *Run, search_path: []const u8) void {
560572 .decorated_directory => false,
561573 .file_content => unreachable, // not allowed as first arg
562574 .bytes => |bytes| std.mem.endsWith(u8, bytes, ".exe"),
563 .output_file, .output_directory => false,
575 .output_file, .output_file_dep, .output_directory => false,
564576 };
565577 const key = if (use_wine) "WINEPATH" else "PATH";
566578 const prev_path = environ_map.get(key);
......@@ -604,24 +616,28 @@ pub fn removeEnvironmentVariable(run: *Run, key: []const u8) void {
604616
605617/// Adds a check for exact stderr match. Does not add any other checks.
606618pub fn expectStdErrEqual(run: *Run, bytes: []const u8) void {
607 run.addCheck(.{ .expect_stderr_exact = run.step.owner.dupe(bytes) });
619 const graph = run.step.owner.graph;
620 run.addCheck(.{ .expect_stderr_exact = graph.dupeString(bytes) });
608621}
609622
610623pub fn expectStdErrMatch(run: *Run, bytes: []const u8) void {
611 run.addCheck(.{ .expect_stderr_match = run.step.owner.dupe(bytes) });
624 const graph = run.step.owner.graph;
625 run.addCheck(.{ .expect_stderr_match = graph.dupeString(bytes) });
612626}
613627
614628/// Adds a check for exact stdout match as well as a check for exit code 0, if
615629/// there is not already an expected termination check.
616630pub fn expectStdOutEqual(run: *Run, bytes: []const u8) void {
617 run.addCheck(.{ .expect_stdout_exact = run.step.owner.dupe(bytes) });
631 const graph = run.step.owner.graph;
632 run.addCheck(.{ .expect_stdout_exact = graph.dupeString(bytes) });
618633 if (!run.hasTermCheck()) run.expectExitCode(0);
619634}
620635
621636/// Adds a check for stdout match as well as a check for exit code 0, if there
622637/// is not already an expected termination check.
623638pub fn expectStdOutMatch(run: *Run, bytes: []const u8) void {
624 run.addCheck(.{ .expect_stdout_match = run.step.owner.dupe(bytes) });
639 const graph = run.step.owner.graph;
640 run.addCheck(.{ .expect_stdout_match = graph.dupeString(bytes) });
625641 if (!run.hasTermCheck()) run.expectExitCode(0);
626642}
627643
......@@ -656,20 +672,22 @@ pub fn captureStdErr(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPa
656672 assert(run.stdio != .zig_test);
657673
658674 const b = run.step.owner;
675 const graph = b.graph;
676 const arena = graph.arena;
659677
660 if (run.captured_stderr) |captured| return .{ .generated = .{ .file = &captured.output.generated_file } };
678 if (run.captured_stderr) |captured| return .{ .generated = .{ .index = captured.output.generated_file } };
661679
662 const captured = b.allocator.create(CapturedStdIo) catch @panic("OOM");
680 const captured = arena.create(CapturedStdIo) catch @panic("OOM");
663681 captured.* = .{
664682 .output = .{
665683 .prefix = "",
666 .basename = if (options.basename) |basename| b.dupe(basename) else "stderr",
667 .generated_file = .{ .step = &run.step },
684 .basename = if (options.basename) |basename| graph.dupeString(basename) else "stderr",
685 .generated_file = graph.addGeneratedFile(&run.step),
668686 },
669687 .trim_whitespace = options.trim_whitespace,
670688 };
671689 run.captured_stderr = captured;
672 return .{ .generated = .{ .file = &captured.output.generated_file } };
690 return .{ .generated = .{ .index = captured.output.generated_file } };
673691}
674692
675693pub fn captureStdOut(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPath {
......@@ -677,20 +695,22 @@ pub fn captureStdOut(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPa
677695 assert(run.stdio != .zig_test);
678696
679697 const b = run.step.owner;
698 const graph = b.graph;
699 const arena = graph.arena;
680700
681 if (run.captured_stdout) |captured| return .{ .generated = .{ .file = &captured.output.generated_file } };
701 if (run.captured_stdout) |captured| return .{ .generated = .{ .index = captured.output.generated_file } };
682702
683 const captured = b.allocator.create(CapturedStdIo) catch @panic("OOM");
703 const captured = arena.create(CapturedStdIo) catch @panic("OOM");
684704 captured.* = .{
685705 .output = .{
686706 .prefix = "",
687 .basename = if (options.basename) |basename| b.dupe(basename) else "stdout",
688 .generated_file = .{ .step = &run.step },
707 .basename = if (options.basename) |basename| graph.dupeString(basename) else "stdout",
708 .generated_file = graph.addGeneratedFile(&run.step),
689709 },
690710 .trim_whitespace = options.trim_whitespace,
691711 };
692712 run.captured_stdout = captured;
693 return .{ .generated = .{ .file = &captured.output.generated_file } };
713 return .{ .generated = .{ .index = captured.output.generated_file } };
694714}
695715
696716/// Adds an additional input files that, when modified, indicates that this Run
......@@ -698,2111 +718,9 @@ pub fn captureStdOut(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPa
698718/// If the Run step is determined to have side-effects, the Run step is always
699719/// executed when it appears in the build graph, regardless of whether this
700720/// file has been modified.
701pub fn addFileInput(self: *Run, file_input: std.Build.LazyPath) void {
702 file_input.addStepDependencies(&self.step);
703 self.file_inputs.append(self.step.owner.allocator, file_input.dupe(self.step.owner)) catch @panic("OOM");
704}
705
706/// Returns whether the Run step has side effects *other than* updating the output arguments.
707fn hasSideEffects(run: Run) bool {
708 if (run.has_side_effects) return true;
709 return switch (run.stdio) {
710 .infer_from_args => !run.hasAnyOutputArgs(),
711 .inherit => true,
712 .check => false,
713 .zig_test => false,
714 };
715}
716
717fn hasAnyOutputArgs(run: Run) bool {
718 if (run.captured_stdout != null) return true;
719 if (run.captured_stderr != null) return true;
720 for (run.argv.items) |arg| switch (arg) {
721 .output_file, .output_directory => return true,
722 else => continue,
723 };
724 return false;
725}
726
727fn checksContainStdout(checks: []const StdIo.Check) bool {
728 for (checks) |check| switch (check) {
729 .expect_stderr_exact,
730 .expect_stderr_match,
731 .expect_term,
732 => continue,
733
734 .expect_stdout_exact,
735 .expect_stdout_match,
736 => return true,
737 };
738 return false;
739}
740
741fn checksContainStderr(checks: []const StdIo.Check) bool {
742 for (checks) |check| switch (check) {
743 .expect_stdout_exact,
744 .expect_stdout_match,
745 .expect_term,
746 => continue,
747
748 .expect_stderr_exact,
749 .expect_stderr_match,
750 => return true,
751 };
752 return false;
753}
754
755/// If `path` is cwd-relative, make it relative to the cwd of the child instead.
756///
757/// Whenever a path is included in the argv of a child, it should be put through this function first
758/// to make sure the child doesn't see paths relative to a cwd other than its own.
759fn convertPathArg(run: *Run, path: Build.Cache.Path) []const u8 {
760 const b = run.step.owner;
761 const graph = b.graph;
721pub fn addFileInput(run: *Run, file_input: std.Build.LazyPath) void {
722 const graph = run.step.owner.graph;
762723 const arena = graph.arena;
763
764 const path_str = path.toString(arena) catch @panic("OOM");
765 if (Dir.path.isAbsolute(path_str)) {
766 // Absolute paths don't need changing.
767 return path_str;
768 }
769 const child_cwd_rel: []const u8 = rel: {
770 const child_lazy_cwd = run.cwd orelse break :rel path_str;
771 const child_cwd = child_lazy_cwd.getPath3(b, &run.step).toString(arena) catch @panic("OOM");
772 // Convert it from relative to *our* cwd, to relative to the *child's* cwd.
773 break :rel Dir.path.relative(arena, graph.cache.cwd, &graph.environ_map, child_cwd, path_str) catch @panic("OOM");
774 };
775 // Not every path can be made relative, e.g. if the path and the child cwd are on different
776 // disk designators on Windows. In that case, `relative` will return an absolute path which we can
777 // just return.
778 if (Dir.path.isAbsolute(child_cwd_rel)) return child_cwd_rel;
779
780 // We're not done yet. In some cases this path must be prefixed with './':
781 // * On POSIX, the executable name cannot be a single component like 'foo'
782 // * Some executables might treat a leading '-' like a flag, which we must avoid
783 // There's no harm in it, so just *always* apply this prefix.
784 return Dir.path.join(arena, &.{ ".", child_cwd_rel }) catch @panic("OOM");
785}
786
787const IndexedOutput = struct {
788 index: usize,
789 tag: @typeInfo(Arg).@"union".tag_type.?,
790 output: *Output,
791};
792fn make(step: *Step, options: Step.MakeOptions) !void {
793 const b = step.owner;
794 const io = b.graph.io;
795 const arena = b.allocator;
796 const run: *Run = @fieldParentPtr("step", step);
797 const has_side_effects = run.hasSideEffects();
798
799 var argv_list = std.array_list.Managed([]const u8).init(arena);
800 var output_placeholders = std.array_list.Managed(IndexedOutput).init(arena);
801
802 var man = b.graph.cache.obtain();
803 defer man.deinit();
804
805 if (run.environ_map) |environ_map| {
806 for (environ_map.keys(), environ_map.values()) |key, value| {
807 man.hash.addBytes(key);
808 man.hash.addBytes(value);
809 }
810 }
811
812 man.hash.add(run.color);
813 man.hash.add(run.disable_zig_progress);
814
815 for (run.argv.items) |arg| {
816 switch (arg) {
817 .bytes => |bytes| {
818 try argv_list.append(bytes);
819 man.hash.addBytes(bytes);
820 },
821 .lazy_path => |file| {
822 const file_path = file.lazy_path.getPath3(b, step);
823 try argv_list.append(b.fmt("{s}{s}", .{ file.prefix, run.convertPathArg(file_path) }));
824 man.hash.addBytes(file.prefix);
825 _ = try man.addFilePath(file_path, null);
826 },
827 .decorated_directory => |dd| {
828 const file_path = dd.lazy_path.getPath3(b, step);
829 const resolved_arg = b.fmt("{s}{s}{s}", .{ dd.prefix, run.convertPathArg(file_path), dd.suffix });
830 try argv_list.append(resolved_arg);
831 man.hash.addBytes(resolved_arg);
832 },
833 .file_content => |file_plp| {
834 const file_path = file_plp.lazy_path.getPath3(b, step);
835
836 var result: std.Io.Writer.Allocating = .init(arena);
837 errdefer result.deinit();
838 result.writer.writeAll(file_plp.prefix) catch return error.OutOfMemory;
839
840 const file = file_path.root_dir.handle.openFile(io, file_path.subPathOrDot(), .{}) catch |err| {
841 return step.fail(
842 "unable to open input file '{f}': {t}",
843 .{ file_path, err },
844 );
845 };
846 defer file.close(io);
847
848 var buf: [1024]u8 = undefined;
849 var file_reader = file.reader(io, &buf);
850 _ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) {
851 error.ReadFailed => return step.fail(
852 "failed to read from '{f}': {t}",
853 .{ file_path, file_reader.err.? },
854 ),
855 error.WriteFailed => return error.OutOfMemory,
856 };
857
858 try argv_list.append(result.written());
859 man.hash.addBytes(file_plp.prefix);
860 _ = try man.addFilePath(file_path, null);
861 },
862 .artifact => |pa| {
863 const artifact = pa.artifact;
864
865 if (artifact.rootModuleTarget().os.tag == .windows) {
866 // On Windows we don't have rpaths so we have to add .dll search paths to PATH
867 run.addPathForDynLibs(artifact);
868 }
869 const file_path = artifact.installed_path orelse artifact.generated_bin.?.path.?;
870
871 try argv_list.append(b.fmt("{s}{s}", .{
872 pa.prefix,
873 run.convertPathArg(.{ .root_dir = .cwd(), .sub_path = file_path }),
874 }));
875
876 _ = try man.addFile(file_path, null);
877 },
878 .output_file, .output_directory => |output| {
879 man.hash.addBytes(output.prefix);
880 man.hash.addBytes(output.basename);
881 // Add a placeholder into the argument list because we need the
882 // manifest hash to be updated with all arguments before the
883 // object directory is computed.
884 try output_placeholders.append(.{
885 .index = argv_list.items.len,
886 .tag = arg,
887 .output = output,
888 });
889 _ = try argv_list.addOne();
890 },
891 }
892 }
893
894 switch (run.stdin) {
895 .bytes => |bytes| {
896 man.hash.addBytes(bytes);
897 },
898 .lazy_path => |lazy_path| {
899 const file_path = lazy_path.getPath2(b, step);
900 _ = try man.addFile(file_path, null);
901 },
902 .none => {},
903 }
904
905 if (run.captured_stdout) |captured| {
906 man.hash.addBytes(captured.output.basename);
907 man.hash.add(captured.trim_whitespace);
908 }
909
910 if (run.captured_stderr) |captured| {
911 man.hash.addBytes(captured.output.basename);
912 man.hash.add(captured.trim_whitespace);
913 }
914
915 hashStdIo(&man.hash, run.stdio);
916
917 for (run.file_inputs.items) |lazy_path| {
918 _ = try man.addFile(lazy_path.getPath2(b, step), null);
919 }
920
921 if (run.cwd) |cwd| {
922 const cwd_path = cwd.getPath3(b, step);
923 _ = man.hash.addBytes(try cwd_path.toString(arena));
924 }
925
926 if (!has_side_effects and try step.cacheHitAndWatch(&man)) {
927 // cache hit, skip running command
928 const digest = man.final();
929
930 try populateGeneratedPaths(
931 arena,
932 output_placeholders.items,
933 run.captured_stdout,
934 run.captured_stderr,
935 b.cache_root,
936 &digest,
937 );
938
939 step.result_cached = true;
940 return;
941 }
942
943 const dep_output_file = run.dep_output_file orelse {
944 // We already know the final output paths, use them directly.
945 const digest = if (has_side_effects)
946 man.hash.final()
947 else
948 man.final();
949
950 try populateGeneratedPaths(
951 arena,
952 output_placeholders.items,
953 run.captured_stdout,
954 run.captured_stderr,
955 b.cache_root,
956 &digest,
957 );
958
959 const output_dir_path = "o" ++ Dir.path.sep_str ++ &digest;
960 for (output_placeholders.items) |placeholder| {
961 const output_sub_path = b.pathJoin(&.{ output_dir_path, placeholder.output.basename });
962 const output_sub_dir_path = switch (placeholder.tag) {
963 .output_file => Dir.path.dirname(output_sub_path).?,
964 .output_directory => output_sub_path,
965 else => unreachable,
966 };
967 b.cache_root.handle.createDirPath(io, output_sub_dir_path) catch |err| {
968 return step.fail("unable to make path '{f}{s}': {s}", .{
969 b.cache_root, output_sub_dir_path, @errorName(err),
970 });
971 };
972 const arg_output_path = run.convertPathArg(.{
973 .root_dir = .cwd(),
974 .sub_path = placeholder.output.generated_file.getPath(),
975 });
976 argv_list.items[placeholder.index] = if (placeholder.output.prefix.len == 0)
977 arg_output_path
978 else
979 b.fmt("{s}{s}", .{ placeholder.output.prefix, arg_output_path });
980 }
981
982 try runCommand(run, argv_list.items, has_side_effects, output_dir_path, options, null);
983 if (!has_side_effects) try step.writeManifestAndWatch(&man);
984 return;
985 };
986
987 // We do not know the final output paths yet, use temp paths to run the command.
988 var rand_int: u64 = undefined;
989 io.random(@ptrCast(&rand_int));
990 const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);
991
992 for (output_placeholders.items) |placeholder| {
993 const output_components = .{ tmp_dir_path, placeholder.output.basename };
994 const output_sub_path = b.pathJoin(&output_components);
995 const output_sub_dir_path = switch (placeholder.tag) {
996 .output_file => Dir.path.dirname(output_sub_path).?,
997 .output_directory => output_sub_path,
998 else => unreachable,
999 };
1000 b.cache_root.handle.createDirPath(io, output_sub_dir_path) catch |err| {
1001 return step.fail("unable to make path '{f}{s}': {s}", .{
1002 b.cache_root, output_sub_dir_path, @errorName(err),
1003 });
1004 };
1005 const raw_output_path: Build.Cache.Path = .{
1006 .root_dir = b.cache_root,
1007 .sub_path = b.pathJoin(&output_components),
1008 };
1009 placeholder.output.generated_file.path = raw_output_path.toString(b.graph.arena) catch @panic("OOM");
1010 argv_list.items[placeholder.index] = b.fmt("{s}{s}", .{
1011 placeholder.output.prefix,
1012 run.convertPathArg(raw_output_path),
1013 });
1014 }
1015
1016 try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, options, null);
1017
1018 const dep_file_dir = Dir.cwd();
1019 const dep_file_basename = dep_output_file.generated_file.getPath2(b, step);
1020 if (has_side_effects)
1021 try man.addDepFile(dep_file_dir, dep_file_basename)
1022 else
1023 try man.addDepFilePost(dep_file_dir, dep_file_basename);
1024
1025 const digest = if (has_side_effects)
1026 man.hash.final()
1027 else
1028 man.final();
1029
1030 const any_output = output_placeholders.items.len > 0 or
1031 run.captured_stdout != null or run.captured_stderr != null;
1032
1033 // Rename into place
1034 if (any_output) {
1035 const o_sub_path = "o" ++ Dir.path.sep_str ++ &digest;
1036
1037 b.cache_root.handle.rename(tmp_dir_path, b.cache_root.handle, o_sub_path, io) catch |err| switch (err) {
1038 Dir.RenameError.DirNotEmpty => {
1039 b.cache_root.handle.deleteTree(io, o_sub_path) catch |del_err| {
1040 return step.fail("unable to remove dir '{f}'{s}: {t}", .{
1041 b.cache_root, tmp_dir_path, del_err,
1042 });
1043 };
1044 b.cache_root.handle.rename(tmp_dir_path, b.cache_root.handle, o_sub_path, io) catch |retry_err| {
1045 return step.fail("unable to rename dir '{f}{s}' to '{f}{s}': {t}", .{
1046 b.cache_root, tmp_dir_path, b.cache_root, o_sub_path, retry_err,
1047 });
1048 };
1049 },
1050 else => return step.fail("unable to rename dir '{f}{s}' to '{f}{s}': {t}", .{
1051 b.cache_root, tmp_dir_path, b.cache_root, o_sub_path, err,
1052 }),
1053 };
1054 }
1055
1056 if (!has_side_effects) try step.writeManifestAndWatch(&man);
1057
1058 try populateGeneratedPaths(
1059 arena,
1060 output_placeholders.items,
1061 run.captured_stdout,
1062 run.captured_stderr,
1063 b.cache_root,
1064 &digest,
1065 );
1066}
1067
1068pub fn rerunInFuzzMode(
1069 run: *Run,
1070 fuzz: *std.Build.Fuzz,
1071 prog_node: std.Progress.Node,
1072) !void {
1073 const step = &run.step;
1074 const b = step.owner;
1075 const io = b.graph.io;
1076 const arena = b.allocator;
1077 var argv_list: std.ArrayList([]const u8) = .empty;
1078 for (run.argv.items) |arg| {
1079 switch (arg) {
1080 .bytes => |bytes| {
1081 try argv_list.append(arena, bytes);
1082 },
1083 .lazy_path => |file| {
1084 const file_path = file.lazy_path.getPath3(b, step);
1085 try argv_list.append(arena, b.fmt("{s}{s}", .{ file.prefix, run.convertPathArg(file_path) }));
1086 },
1087 .decorated_directory => |dd| {
1088 const file_path = dd.lazy_path.getPath3(b, step);
1089 try argv_list.append(arena, b.fmt("{s}{s}{s}", .{ dd.prefix, run.convertPathArg(file_path), dd.suffix }));
1090 },
1091 .file_content => |file_plp| {
1092 const file_path = file_plp.lazy_path.getPath3(b, step);
1093
1094 var result: std.Io.Writer.Allocating = .init(arena);
1095 errdefer result.deinit();
1096 result.writer.writeAll(file_plp.prefix) catch return error.OutOfMemory;
1097
1098 const file = try file_path.root_dir.handle.openFile(io, file_path.subPathOrDot(), .{});
1099 defer file.close(io);
1100
1101 var buf: [1024]u8 = undefined;
1102 var file_reader = file.reader(io, &buf);
1103 _ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) {
1104 error.ReadFailed => return file_reader.err.?,
1105 error.WriteFailed => return error.OutOfMemory,
1106 };
1107
1108 try argv_list.append(arena, result.written());
1109 },
1110 .artifact => |pa| {
1111 const artifact = pa.artifact;
1112 const file_path: []const u8 = p: {
1113 if (artifact == run.producer.?) break :p b.fmt("{f}", .{run.rebuilt_executable.?});
1114 break :p artifact.installed_path orelse artifact.generated_bin.?.path.?;
1115 };
1116 try argv_list.append(arena, b.fmt("{s}{s}", .{
1117 pa.prefix,
1118 run.convertPathArg(.{ .root_dir = .cwd(), .sub_path = file_path }),
1119 }));
1120 },
1121 .output_file, .output_directory => unreachable,
1122 }
1123 }
1124
1125 if (run.step.result_failed_command) |cmd| {
1126 fuzz.gpa.free(cmd);
1127 run.step.result_failed_command = null;
1128 }
1129
1130 const has_side_effects = false;
1131 var rand_int: u64 = undefined;
1132 io.random(@ptrCast(&rand_int));
1133 const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);
1134 try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, .{
1135 .progress_node = prog_node,
1136 .watch = undefined, // not used by `runCommand`
1137 .web_server = null, // only needed for time reports
1138 .unit_test_timeout_ns = null, // don't time out fuzz tests for now
1139 .gpa = fuzz.gpa,
1140 }, .{
1141 .fuzz = fuzz,
1142 });
1143}
1144
1145fn populateGeneratedPaths(
1146 arena: std.mem.Allocator,
1147 output_placeholders: []const IndexedOutput,
1148 captured_stdout: ?*CapturedStdIo,
1149 captured_stderr: ?*CapturedStdIo,
1150 cache_root: Build.Cache.Directory,
1151 digest: *const Build.Cache.HexDigest,
1152) !void {
1153 for (output_placeholders) |placeholder| {
1154 placeholder.output.generated_file.path = try cache_root.join(arena, &.{
1155 "o", digest, placeholder.output.basename,
1156 });
1157 }
1158
1159 if (captured_stdout) |captured| {
1160 captured.output.generated_file.path = try cache_root.join(arena, &.{
1161 "o", digest, captured.output.basename,
1162 });
1163 }
1164
1165 if (captured_stderr) |captured| {
1166 captured.output.generated_file.path = try cache_root.join(arena, &.{
1167 "o", digest, captured.output.basename,
1168 });
1169 }
1170}
1171
1172fn formatTerm(term: ?process.Child.Term, w: *std.Io.Writer) std.Io.Writer.Error!void {
1173 if (term) |t| switch (t) {
1174 .exited => |code| try w.print("exited with code {d}", .{code}),
1175 .signal => |sig| try w.print("terminated with signal {t}", .{sig}),
1176 .stopped => |sig| try w.print("stopped with signal {t}", .{sig}),
1177 .unknown => |code| try w.print("terminated for unknown reason with code {d}", .{code}),
1178 } else {
1179 try w.writeAll("exited with any code");
1180 }
1181}
1182fn fmtTerm(term: ?process.Child.Term) std.fmt.Alt(?process.Child.Term, formatTerm) {
1183 return .{ .data = term };
1184}
1185
1186fn termMatches(expected: ?process.Child.Term, actual: process.Child.Term) bool {
1187 return if (expected) |e| switch (e) {
1188 .exited => |expected_code| switch (actual) {
1189 .exited => |actual_code| expected_code == actual_code,
1190 else => false,
1191 },
1192 .signal => |expected_sig| switch (actual) {
1193 .signal => |actual_sig| expected_sig == actual_sig,
1194 else => false,
1195 },
1196 .stopped => |expected_sig| switch (actual) {
1197 .stopped => |actual_sig| expected_sig == actual_sig,
1198 else => false,
1199 },
1200 .unknown => |expected_code| switch (actual) {
1201 .unknown => |actual_code| expected_code == actual_code,
1202 else => false,
1203 },
1204 } else switch (actual) {
1205 .exited => true,
1206 else => false,
1207 };
1208}
1209
1210const FuzzContext = struct {
1211 fuzz: *std.Build.Fuzz,
1212};
1213
1214fn runCommand(
1215 run: *Run,
1216 argv: []const []const u8,
1217 has_side_effects: bool,
1218 output_dir_path: []const u8,
1219 options: Step.MakeOptions,
1220 fuzz_context: ?FuzzContext,
1221) !void {
1222 const step = &run.step;
1223 const b = step.owner;
1224 const arena = b.allocator;
1225 const gpa = options.gpa;
1226 const io = b.graph.io;
1227
1228 const cwd: process.Child.Cwd = if (run.cwd) |lazy_cwd| .{ .path = lazy_cwd.getPath2(b, step) } else .inherit;
1229
1230 try step.handleChildProcUnsupported();
1231 try Step.handleVerbose2(step.owner, cwd, run.environ_map, argv);
1232
1233 const allow_skip = switch (run.stdio) {
1234 .check, .zig_test => run.skip_foreign_checks,
1235 else => false,
1236 };
1237
1238 var interp_argv = std.array_list.Managed([]const u8).init(b.allocator);
1239 defer interp_argv.deinit();
1240
1241 var environ_map: EnvMap = env: {
1242 const orig = run.environ_map orelse &b.graph.environ_map;
1243 break :env try orig.clone(gpa);
1244 };
1245 defer environ_map.deinit();
1246
1247 const opt_generic_result = spawnChildAndCollect(run, argv, &environ_map, has_side_effects, options, fuzz_context) catch |err| term: {
1248 // InvalidExe: cpu arch mismatch
1249 // FileNotFound: can happen with a wrong dynamic linker path
1250 if (err == error.InvalidExe or err == error.FileNotFound) interpret: {
1251 // TODO: learn the target from the binary directly rather than from
1252 // relying on it being a Compile step. This will make this logic
1253 // work even for the edge case that the binary was produced by a
1254 // third party.
1255 const exe = switch (run.argv.items[0]) {
1256 .artifact => |exe| exe.artifact,
1257 else => break :interpret,
1258 };
1259 switch (exe.kind) {
1260 .exe, .@"test" => {},
1261 else => break :interpret,
1262 }
1263
1264 const root_target = exe.rootModuleTarget();
1265 const need_cross_libc = exe.is_linking_libc and
1266 (root_target.isGnuLibC() or (root_target.isMuslLibC() and exe.linkage == .dynamic));
1267 const other_target = exe.root_module.resolved_target.?.result;
1268 switch (std.zig.system.getExternalExecutor(io, &b.graph.host.result, &other_target, .{
1269 .qemu_fixes_dl = need_cross_libc and b.libc_runtimes_dir != null,
1270 .link_libc = exe.is_linking_libc,
1271 })) {
1272 .native, .rosetta => {
1273 if (allow_skip) return error.MakeSkipped;
1274 break :interpret;
1275 },
1276 .wine => |bin_name| {
1277 if (b.enable_wine) {
1278 try interp_argv.append(bin_name);
1279 try interp_argv.appendSlice(argv);
1280
1281 // Wine's excessive stderr logging is only situationally helpful. Disable it by default, but
1282 // allow the user to override it (e.g. with `WINEDEBUG=err+all`) if desired.
1283 if (environ_map.get("WINEDEBUG") == null) {
1284 try environ_map.put("WINEDEBUG", "-all");
1285 }
1286 } else {
1287 return failForeign(run, "-fwine", argv[0], exe);
1288 }
1289 },
1290 .qemu => |bin_name| {
1291 if (b.enable_qemu) {
1292 try interp_argv.append(bin_name);
1293
1294 if (need_cross_libc) {
1295 if (b.libc_runtimes_dir) |dir| {
1296 try interp_argv.append("-L");
1297 try interp_argv.append(b.pathJoin(&.{
1298 dir,
1299 try if (root_target.isGnuLibC()) std.zig.target.glibcRuntimeTriple(
1300 b.allocator,
1301 root_target.cpu.arch,
1302 root_target.os.tag,
1303 root_target.abi,
1304 ) else if (root_target.isMuslLibC()) std.zig.target.muslRuntimeTriple(
1305 b.allocator,
1306 root_target.cpu.arch,
1307 root_target.abi,
1308 ) else unreachable,
1309 }));
1310 } else return failForeign(run, "--libc-runtimes", argv[0], exe);
1311 }
1312
1313 try interp_argv.appendSlice(argv);
1314 } else return failForeign(run, "-fqemu", argv[0], exe);
1315 },
1316 .darling => |bin_name| {
1317 if (b.enable_darling) {
1318 try interp_argv.append(bin_name);
1319 try interp_argv.appendSlice(argv);
1320 } else {
1321 return failForeign(run, "-fdarling", argv[0], exe);
1322 }
1323 },
1324 .wasmtime => |bin_name| {
1325 if (b.enable_wasmtime) {
1326 try interp_argv.append(bin_name);
1327 try interp_argv.append("--dir=.");
1328 // Wasmtime doeesn't inherit environment variables from the parent process
1329 // by default. '-S inherit-env' was added in Wasmtime version 20.
1330 try interp_argv.append("-Sinherit-env");
1331 try interp_argv.append(argv[0]);
1332 try interp_argv.appendSlice(argv[1..]);
1333 } else {
1334 return failForeign(run, "-fwasmtime", argv[0], exe);
1335 }
1336 },
1337 .bad_dl => |foreign_dl| {
1338 if (allow_skip) return error.MakeSkipped;
1339
1340 const host_dl = b.graph.host.result.dynamic_linker.get() orelse "(none)";
1341
1342 return step.fail(
1343 \\the host system is unable to execute binaries from the target
1344 \\ because the host dynamic linker is '{s}',
1345 \\ while the target dynamic linker is '{s}'.
1346 \\ consider setting the dynamic linker or enabling skip_foreign_checks in the Run step
1347 , .{ host_dl, foreign_dl });
1348 },
1349 .bad_os_or_cpu => {
1350 if (allow_skip) return error.MakeSkipped;
1351
1352 const host_name = try b.graph.host.result.zigTriple(b.allocator);
1353 const foreign_name = try root_target.zigTriple(b.allocator);
1354
1355 return step.fail("the host system ({s}) is unable to execute binaries from the target ({s})", .{
1356 host_name, foreign_name,
1357 });
1358 },
1359 }
1360
1361 if (root_target.os.tag == .windows) {
1362 // On Windows we don't have rpaths so we have to add .dll search paths to PATH
1363 run.addPathForDynLibs(exe);
1364 }
1365
1366 gpa.free(step.result_failed_command.?);
1367 step.result_failed_command = null;
1368 try Step.handleVerbose2(step.owner, cwd, run.environ_map, interp_argv.items);
1369
1370 break :term spawnChildAndCollect(run, interp_argv.items, &environ_map, has_side_effects, options, fuzz_context) catch |e| {
1371 if (!run.failing_to_execute_foreign_is_an_error) return error.MakeSkipped;
1372 if (e == error.MakeFailed) return error.MakeFailed; // error already reported
1373 return step.fail("unable to spawn interpreter {s}: {t}", .{ interp_argv.items[0], e });
1374 };
1375 }
1376 if (err == error.MakeFailed) return error.MakeFailed; // error already reported
1377
1378 return step.fail("failed to spawn and capture stdio from {s}: {t}", .{ argv[0], err });
1379 };
1380
1381 const generic_result = opt_generic_result orelse {
1382 assert(run.stdio == .zig_test);
1383 // Specific errors have already been reported, and test results are populated. All we need
1384 // to do is report step failure if any test failed.
1385 if (!step.test_results.isSuccess()) return error.MakeFailed;
1386 return;
1387 };
1388
1389 assert(fuzz_context == null);
1390 assert(run.stdio != .zig_test);
1391
1392 // Capture stdout and stderr to GeneratedFile objects.
1393 const Stream = struct {
1394 captured: ?*CapturedStdIo,
1395 bytes: ?[]const u8,
1396 };
1397 for ([_]Stream{
1398 .{
1399 .captured = run.captured_stdout,
1400 .bytes = generic_result.stdout,
1401 },
1402 .{
1403 .captured = run.captured_stderr,
1404 .bytes = generic_result.stderr,
1405 },
1406 }) |stream| {
1407 if (stream.captured) |captured| {
1408 const output_components = .{ output_dir_path, captured.output.basename };
1409 const output_path = try b.cache_root.join(arena, &output_components);
1410 captured.output.generated_file.path = output_path;
1411
1412 const sub_path = b.pathJoin(&output_components);
1413 const sub_path_dirname = Dir.path.dirname(sub_path).?;
1414 b.cache_root.handle.createDirPath(io, sub_path_dirname) catch |err| {
1415 return step.fail("unable to make path '{f}{s}': {s}", .{
1416 b.cache_root, sub_path_dirname, @errorName(err),
1417 });
1418 };
1419 const data = switch (captured.trim_whitespace) {
1420 .none => stream.bytes.?,
1421 .all => mem.trim(u8, stream.bytes.?, &std.ascii.whitespace),
1422 .leading => mem.trimStart(u8, stream.bytes.?, &std.ascii.whitespace),
1423 .trailing => mem.trimEnd(u8, stream.bytes.?, &std.ascii.whitespace),
1424 };
1425 b.cache_root.handle.writeFile(io, .{ .sub_path = sub_path, .data = data }) catch |err| {
1426 return step.fail("unable to write file '{f}{s}': {s}", .{
1427 b.cache_root, sub_path, @errorName(err),
1428 });
1429 };
1430 }
1431 }
1432
1433 switch (run.stdio) {
1434 .zig_test => unreachable,
1435 .check => |checks| for (checks.items) |check| switch (check) {
1436 .expect_stderr_exact => |expected_bytes| {
1437 if (!mem.eql(u8, expected_bytes, generic_result.stderr.?)) {
1438 return step.fail(
1439 \\========= expected this stderr: =========
1440 \\{s}
1441 \\========= but found: ====================
1442 \\{s}
1443 , .{
1444 expected_bytes,
1445 generic_result.stderr.?,
1446 });
1447 }
1448 },
1449 .expect_stderr_match => |match| {
1450 if (mem.find(u8, generic_result.stderr.?, match) == null) {
1451 return step.fail(
1452 \\========= expected to find in stderr: =========
1453 \\{s}
1454 \\========= but stderr does not contain it: =====
1455 \\{s}
1456 , .{
1457 match,
1458 generic_result.stderr.?,
1459 });
1460 }
1461 },
1462 .expect_stdout_exact => |expected_bytes| {
1463 if (!mem.eql(u8, expected_bytes, generic_result.stdout.?)) {
1464 return step.fail(
1465 \\========= expected this stdout: =========
1466 \\{s}
1467 \\========= but found: ====================
1468 \\{s}
1469 , .{
1470 expected_bytes,
1471 generic_result.stdout.?,
1472 });
1473 }
1474 },
1475 .expect_stdout_match => |match| {
1476 if (mem.find(u8, generic_result.stdout.?, match) == null) {
1477 return step.fail(
1478 \\========= expected to find in stdout: =========
1479 \\{s}
1480 \\========= but stdout does not contain it: =====
1481 \\{s}
1482 , .{
1483 match,
1484 generic_result.stdout.?,
1485 });
1486 }
1487 },
1488 .expect_term => |expected_term| {
1489 if (!termMatches(expected_term, generic_result.term)) {
1490 return step.fail("process {f} (expected {f})", .{
1491 fmtTerm(generic_result.term),
1492 fmtTerm(expected_term),
1493 });
1494 }
1495 },
1496 },
1497 else => {
1498 // On failure, report captured stderr like normal standard error output.
1499 const bad_exit = switch (generic_result.term) {
1500 .exited => |code| code != 0,
1501 .signal, .stopped, .unknown => true,
1502 };
1503 if (bad_exit) {
1504 if (generic_result.stderr) |bytes| {
1505 run.step.result_stderr = bytes;
1506 }
1507 }
1508
1509 try step.handleChildProcessTerm(generic_result.term);
1510 },
1511 }
1512}
1513
1514const EvalGenericResult = struct {
1515 term: process.Child.Term,
1516 stdout: ?[]const u8,
1517 stderr: ?[]const u8,
1518};
1519
1520fn spawnChildAndCollect(
1521 run: *Run,
1522 argv: []const []const u8,
1523 environ_map: *EnvMap,
1524 has_side_effects: bool,
1525 options: Step.MakeOptions,
1526 fuzz_context: ?FuzzContext,
1527) !?EvalGenericResult {
1528 const b = run.step.owner;
1529 const graph = b.graph;
1530 const io = graph.io;
1531
1532 if (fuzz_context != null) {
1533 assert(!has_side_effects);
1534 assert(run.stdio == .zig_test);
1535 }
1536
1537 const child_cwd: process.Child.Cwd = if (run.cwd) |lazy_cwd| .{ .path = lazy_cwd.getPath2(b, &run.step) } else .inherit;
1538
1539 // If an error occurs, it's caused by this command:
1540 assert(run.step.result_failed_command == null);
1541 run.step.result_failed_command = try Step.allocPrintCmd(options.gpa, child_cwd, .{
1542 .child = environ_map,
1543 .parent = &graph.environ_map,
1544 }, argv);
1545
1546 var spawn_options: process.SpawnOptions = .{
1547 .argv = argv,
1548 .cwd = child_cwd,
1549 .environ_map = environ_map,
1550 .request_resource_usage_statistics = true,
1551 .stdin = if (run.stdin != .none) s: {
1552 assert(run.stdio != .inherit);
1553 break :s .pipe;
1554 } else switch (run.stdio) {
1555 .infer_from_args => if (has_side_effects) .inherit else .ignore,
1556 .inherit => .inherit,
1557 .check => .ignore,
1558 .zig_test => .pipe,
1559 },
1560 .stdout = if (run.captured_stdout != null) .pipe else switch (run.stdio) {
1561 .infer_from_args => if (has_side_effects) .inherit else .ignore,
1562 .inherit => .inherit,
1563 .check => |checks| if (checksContainStdout(checks.items)) .pipe else .ignore,
1564 .zig_test => .pipe,
1565 },
1566 .stderr = if (run.captured_stderr != null) .pipe else switch (run.stdio) {
1567 .infer_from_args => if (has_side_effects) .inherit else .pipe,
1568 .inherit => .inherit,
1569 .check => .pipe,
1570 .zig_test => .pipe,
1571 },
1572 };
1573
1574 if (run.stdio == .zig_test) {
1575 const started: Io.Clock.Timestamp = .now(io, .awake);
1576 const result = evalZigTest(run, spawn_options, options, fuzz_context) catch |err| switch (err) {
1577 error.Canceled => |e| return e,
1578 else => |e| e,
1579 };
1580 run.step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds);
1581 try result;
1582 return null;
1583 } else {
1584 const inherit = spawn_options.stdout == .inherit or spawn_options.stderr == .inherit;
1585 if (!run.disable_zig_progress and !inherit) {
1586 spawn_options.progress_node = options.progress_node;
1587 }
1588 const terminal_mode: Io.Terminal.Mode = if (inherit) m: {
1589 const stderr = try io.lockStderr(&.{}, graph.stderr_mode);
1590 break :m stderr.terminal_mode;
1591 } else .no_color;
1592 defer if (inherit) io.unlockStderr();
1593 try setColorEnvironmentVariables(run, environ_map, terminal_mode);
1594
1595 const started: Io.Clock.Timestamp = .now(io, .awake);
1596 const result = evalGeneric(run, spawn_options) catch |err| switch (err) {
1597 error.Canceled => |e| return e,
1598 else => |e| e,
1599 };
1600 run.step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds);
1601 return try result;
1602 }
1603}
1604
1605fn setColorEnvironmentVariables(run: *Run, environ_map: *EnvMap, terminal_mode: Io.Terminal.Mode) !void {
1606 color: switch (run.color) {
1607 .manual => {},
1608 .enable => {
1609 try environ_map.put("CLICOLOR_FORCE", "1");
1610 _ = environ_map.swapRemove("NO_COLOR");
1611 },
1612 .disable => {
1613 try environ_map.put("NO_COLOR", "1");
1614 _ = environ_map.swapRemove("CLICOLOR_FORCE");
1615 },
1616 .inherit => switch (terminal_mode) {
1617 .no_color, .windows_api => continue :color .disable,
1618 .escape_codes => continue :color .enable,
1619 },
1620 .auto => {
1621 const capture_stderr = run.captured_stderr != null or switch (run.stdio) {
1622 .check => |checks| checksContainStderr(checks.items),
1623 .infer_from_args, .inherit, .zig_test => false,
1624 };
1625 if (capture_stderr) {
1626 continue :color .disable;
1627 } else {
1628 continue :color .inherit;
1629 }
1630 },
1631 }
1632}
1633
1634const StdioPollEnum = enum { stdout, stderr };
1635
1636fn evalZigTest(
1637 run: *Run,
1638 spawn_options: process.SpawnOptions,
1639 options: Step.MakeOptions,
1640 fuzz_context: ?FuzzContext,
1641) !void {
1642 if (fuzz_context != null) {
1643 try evalFuzzTest(run, spawn_options, options, fuzz_context.?);
1644 return;
1645 }
1646
1647 const step_owner = run.step.owner;
1648 const gpa = step_owner.allocator;
1649 const arena = step_owner.allocator;
1650 const io = step_owner.graph.io;
1651
1652 // We will update this every time a child runs.
1653 run.step.result_peak_rss = 0;
1654
1655 var test_results: Step.TestResults = .{
1656 .test_count = 0,
1657 .skip_count = 0,
1658 .fail_count = 0,
1659 .crash_count = 0,
1660 .timeout_count = 0,
1661 .leak_count = 0,
1662 .log_err_count = 0,
1663 };
1664 var test_metadata: ?TestMetadata = null;
1665
1666 while (true) {
1667 var child = try process.spawn(io, spawn_options);
1668 var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined;
1669 var multi_reader: Io.File.MultiReader = undefined;
1670 multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ child.stdout.?, child.stderr.? });
1671 var child_killed = false;
1672 defer if (!child_killed) {
1673 child.kill(io);
1674 multi_reader.deinit();
1675 run.step.result_peak_rss = @max(
1676 run.step.result_peak_rss,
1677 child.resource_usage_statistics.getMaxRss() orelse 0,
1678 );
1679 };
1680
1681 switch (try waitZigTest(
1682 run,
1683 &child,
1684 options,
1685 &multi_reader,
1686 &test_metadata,
1687 &test_results,
1688 )) {
1689 .write_failed => |err| {
1690 // The runner unexpectedly closed a stdio pipe, which means a crash. Make sure we've captured
1691 // all available stderr to make our error output as useful as possible.
1692 const stderr_fr = multi_reader.fileReader(1);
1693 while (stderr_fr.interface.fillMore()) |_| {} else |e| switch (e) {
1694 error.ReadFailed => return stderr_fr.err.?,
1695 error.EndOfStream => {},
1696 }
1697 run.step.result_stderr = try arena.dupe(u8, stderr_fr.interface.buffered());
1698
1699 // Clean up everything and wait for the child to exit.
1700 child.stdin.?.close(io);
1701 child.stdin = null;
1702 multi_reader.deinit();
1703 child_killed = true;
1704 const term = try child.wait(io);
1705 run.step.result_peak_rss = @max(
1706 run.step.result_peak_rss,
1707 child.resource_usage_statistics.getMaxRss() orelse 0,
1708 );
1709
1710 // The individual unit test results are irrelevant: the test runner itself broke!
1711 // Fail immediately without populating `s.test_results`.
1712 return run.step.fail("unable to write stdin ({t}); test process unexpectedly {f}", .{ err, fmtTerm(term) });
1713 },
1714 .no_poll => |no_poll| {
1715 // This might be a success (we requested exit and the child dutifully closed stdout) or
1716 // a crash of some kind. Either way, the child will terminate by itself -- wait for it.
1717 const stderr_reader = multi_reader.reader(1);
1718 const stderr_owned = try arena.dupe(u8, stderr_reader.buffered());
1719
1720 // Clean up everything and wait for the child to exit.
1721 child.stdin.?.close(io);
1722 child.stdin = null;
1723 multi_reader.deinit();
1724 child_killed = true;
1725 const term = try child.wait(io);
1726 run.step.result_peak_rss = @max(
1727 run.step.result_peak_rss,
1728 child.resource_usage_statistics.getMaxRss() orelse 0,
1729 );
1730
1731 if (no_poll.active_test_index) |test_index| {
1732 // A test was running, so this is definitely a crash. Report it against that
1733 // test, and continue to the next test.
1734 test_metadata.?.ns_per_test[test_index] = no_poll.ns_elapsed;
1735 test_results.crash_count += 1;
1736 try run.step.addError("'{s}' {f}{s}{s}", .{
1737 test_metadata.?.testName(test_index),
1738 fmtTerm(term),
1739 if (stderr_owned.len != 0) " with stderr:\n" else "",
1740 std.mem.trim(u8, stderr_owned, "\n"),
1741 });
1742 continue;
1743 }
1744
1745 // Report an error if the child terminated uncleanly or if we were still trying to run more tests.
1746 run.step.result_stderr = stderr_owned;
1747 const tests_done = test_metadata != null and test_metadata.?.next_index == std.math.maxInt(u32);
1748 if (!tests_done or !termMatches(.{ .exited = 0 }, term)) {
1749 // The individual unit test results are irrelevant: the test runner itself broke!
1750 // Fail immediately without populating `s.test_results`.
1751 return run.step.fail("test process unexpectedly {f}", .{fmtTerm(term)});
1752 }
1753
1754 // We're done with all of the tests! Commit the test results and return.
1755 run.step.test_results = test_results;
1756 if (test_metadata) |tm| {
1757 run.cached_test_metadata = tm.toCachedTestMetadata();
1758 if (options.web_server) |ws| {
1759 if (run.step.owner.graph.time_report) {
1760 ws.updateTimeReportRunTest(
1761 run,
1762 &run.cached_test_metadata.?,
1763 tm.ns_per_test,
1764 );
1765 }
1766 }
1767 }
1768 return;
1769 },
1770 .timeout => |timeout| {
1771 const stderr_reader = multi_reader.reader(1);
1772 const stderr = stderr_reader.buffered();
1773 stderr_reader.tossBuffered();
1774 if (timeout.active_test_index) |test_index| {
1775 // A test was running. Report the timeout against that test, and continue on to
1776 // the next test.
1777 test_metadata.?.ns_per_test[test_index] = timeout.ns_elapsed;
1778 test_results.timeout_count += 1;
1779 try run.step.addError("'{s}' timed out after {f}{s}{s}", .{
1780 test_metadata.?.testName(test_index),
1781 Io.Duration{ .nanoseconds = timeout.ns_elapsed },
1782 if (stderr.len != 0) " with stderr:\n" else "",
1783 std.mem.trim(u8, stderr, "\n"),
1784 });
1785 continue;
1786 }
1787 // Just log an error and let the child be killed.
1788 run.step.result_stderr = try arena.dupe(u8, stderr);
1789 // The individual unit test results in `results` are irrelevant: the test runner
1790 // is broken! Fail immediately without populating `s.test_results`.
1791 return run.step.fail("test runner failed to respond for {f}", .{Io.Duration{ .nanoseconds = timeout.ns_elapsed }});
1792 },
1793 }
1794 comptime unreachable;
1795 }
1796}
1797
1798/// Reads stdout of a Zig test process until a termination condition is reached:
1799/// * A write fails, indicating the child unexpectedly closed stdin
1800/// * A test (or a response from the test runner) times out
1801/// * The wait fails, indicating the child closed stdout and stderr
1802fn waitZigTest(
1803 run: *Run,
1804 child: *process.Child,
1805 options: Step.MakeOptions,
1806 multi_reader: *Io.File.MultiReader,
1807 opt_metadata: *?TestMetadata,
1808 results: *Step.TestResults,
1809) !union(enum) {
1810 write_failed: anyerror,
1811 no_poll: struct {
1812 active_test_index: ?u32,
1813 ns_elapsed: u64,
1814 },
1815 timeout: struct {
1816 active_test_index: ?u32,
1817 ns_elapsed: u64,
1818 },
1819} {
1820 const gpa = run.step.owner.allocator;
1821 const arena = run.step.owner.allocator;
1822 const io = run.step.owner.graph.io;
1823
1824 var sub_prog_node: ?std.Progress.Node = null;
1825 defer if (sub_prog_node) |n| n.end();
1826
1827 if (opt_metadata.*) |*md| {
1828 // Previous unit test process died or was killed; we're continuing where it left off
1829 requestNextTest(io, child.stdin.?, md, &sub_prog_node) catch |err| return .{ .write_failed = err };
1830 } else {
1831 // Running unit tests normally
1832 run.fuzz_tests.clearRetainingCapacity();
1833 sendMessage(io, child.stdin.?, .query_test_metadata) catch |err| return .{ .write_failed = err };
1834 }
1835
1836 var active_test_index: ?u32 = null;
1837
1838 var last_update: Io.Clock.Timestamp = .now(io, .awake);
1839
1840 // This timeout is used when we're waiting on the test runner itself rather than a user-specified
1841 // test. For instance, if the test runner leaves this much time between us requesting a test to
1842 // start and it acknowledging the test starting, we terminate the child and raise an error. This
1843 // *should* never happen, but could in theory be caused by some very unlucky IB in a test.
1844 const response_timeout: Io.Clock.Duration = t: {
1845 const ns = @max(options.unit_test_timeout_ns orelse 0, 60 * std.time.ns_per_s);
1846 break :t .{ .clock = .awake, .raw = .fromNanoseconds(ns) };
1847 };
1848 const test_timeout: ?Io.Clock.Duration = if (options.unit_test_timeout_ns) |ns| .{
1849 .clock = .awake,
1850 .raw = .fromNanoseconds(ns),
1851 } else null;
1852
1853 const stdout = multi_reader.reader(0);
1854 const stderr = multi_reader.reader(1);
1855 const Header = std.zig.Server.Message.Header;
1856
1857 while (true) {
1858 const timeout: Io.Timeout = t: {
1859 const opt_duration = if (active_test_index == null) response_timeout else test_timeout;
1860 const duration = opt_duration orelse break :t .none;
1861 break :t .{ .deadline = last_update.addDuration(duration) };
1862 };
1863
1864 // This block is exited when `stdout` contains enough bytes for a `Header`.
1865 header_ready: {
1866 if (stdout.buffered().len >= @sizeOf(Header)) {
1867 // We already have one, no need to poll!
1868 break :header_ready;
1869 }
1870
1871 multi_reader.fill(64, timeout) catch |err| switch (err) {
1872 error.Timeout => return .{ .timeout = .{
1873 .active_test_index = active_test_index,
1874 .ns_elapsed = @intCast(last_update.untilNow(io).raw.nanoseconds),
1875 } },
1876 error.EndOfStream => return .{ .no_poll = .{
1877 .active_test_index = active_test_index,
1878 .ns_elapsed = @intCast(last_update.untilNow(io).raw.nanoseconds),
1879 } },
1880 else => |e| return e,
1881 };
1882
1883 continue;
1884 }
1885 // There is definitely a header available now -- read it.
1886 const header = stdout.takeStruct(Header, .little) catch unreachable;
1887
1888 while (stdout.buffered().len < header.bytes_len) {
1889 multi_reader.fill(64, timeout) catch |err| switch (err) {
1890 error.Timeout => return .{ .timeout = .{
1891 .active_test_index = active_test_index,
1892 .ns_elapsed = @intCast(last_update.untilNow(io).raw.nanoseconds),
1893 } },
1894 error.EndOfStream => return .{ .no_poll = .{
1895 .active_test_index = active_test_index,
1896 .ns_elapsed = @intCast(last_update.untilNow(io).raw.nanoseconds),
1897 } },
1898 else => |e| return e,
1899 };
1900 }
1901
1902 const body = stdout.take(header.bytes_len) catch unreachable;
1903 var body_r: std.Io.Reader = .fixed(body);
1904 switch (header.tag) {
1905 .zig_version => {
1906 if (!std.mem.eql(u8, builtin.zig_version_string, body)) return run.step.fail(
1907 "zig version mismatch build runner vs compiler: '{s}' vs '{s}'",
1908 .{ builtin.zig_version_string, body },
1909 );
1910 },
1911 .test_metadata => {
1912 // `metadata` would only be populated if we'd already seen a `test_metadata`, but we
1913 // only request it once (and importantly, we don't re-request it if we kill and
1914 // restart the test runner).
1915 assert(opt_metadata.* == null);
1916
1917 const tm_hdr = body_r.takeStruct(std.zig.Server.Message.TestMetadata, .little) catch unreachable;
1918 results.test_count = tm_hdr.tests_len;
1919
1920 const names = try arena.alloc(u32, results.test_count);
1921 for (names) |*dest| dest.* = body_r.takeInt(u32, .little) catch unreachable;
1922
1923 const expected_panic_msgs = try arena.alloc(u32, results.test_count);
1924 for (expected_panic_msgs) |*dest| dest.* = body_r.takeInt(u32, .little) catch unreachable;
1925
1926 const string_bytes = body_r.take(tm_hdr.string_bytes_len) catch unreachable;
1927
1928 options.progress_node.setEstimatedTotalItems(names.len);
1929 opt_metadata.* = .{
1930 .string_bytes = try arena.dupe(u8, string_bytes),
1931 .ns_per_test = try arena.alloc(u64, results.test_count),
1932 .names = names,
1933 .expected_panic_msgs = expected_panic_msgs,
1934 .next_index = 0,
1935 .prog_node = options.progress_node,
1936 };
1937 @memset(opt_metadata.*.?.ns_per_test, std.math.maxInt(u64));
1938
1939 active_test_index = null;
1940 last_update = .now(io, .awake);
1941
1942 requestNextTest(io, child.stdin.?, &opt_metadata.*.?, &sub_prog_node) catch |err| return .{ .write_failed = err };
1943 },
1944 .test_started => {
1945 active_test_index = opt_metadata.*.?.next_index - 1;
1946 last_update = .now(io, .awake);
1947 },
1948 .test_results => {
1949 const md = &opt_metadata.*.?;
1950
1951 const tr_hdr = body_r.takeStruct(std.zig.Server.Message.TestResults, .little) catch unreachable;
1952 assert(tr_hdr.index == active_test_index);
1953
1954 switch (tr_hdr.flags.status) {
1955 .pass => {},
1956 .skip => results.skip_count +|= 1,
1957 .fail => results.fail_count +|= 1,
1958 }
1959 const leak_count = tr_hdr.flags.leak_count;
1960 const log_err_count = tr_hdr.flags.log_err_count;
1961 results.leak_count +|= leak_count;
1962 results.log_err_count +|= log_err_count;
1963
1964 if (tr_hdr.flags.fuzz) try run.fuzz_tests.append(gpa, md.testName(tr_hdr.index));
1965
1966 if (tr_hdr.flags.status == .fail) {
1967 const name = md.testName(tr_hdr.index);
1968 const stderr_bytes = std.mem.trim(u8, stderr.buffered(), "\n");
1969 stderr.tossBuffered();
1970 if (stderr_bytes.len == 0) {
1971 try run.step.addError("'{s}' failed without output", .{name});
1972 } else {
1973 try run.step.addError("'{s}' failed:\n{s}", .{ name, stderr_bytes });
1974 }
1975 } else if (leak_count > 0) {
1976 const name = md.testName(tr_hdr.index);
1977 const stderr_bytes = std.mem.trim(u8, stderr.buffered(), "\n");
1978 stderr.tossBuffered();
1979 try run.step.addError("'{s}' leaked {d} allocations:\n{s}", .{ name, leak_count, stderr_bytes });
1980 } else if (log_err_count > 0) {
1981 const name = md.testName(tr_hdr.index);
1982 const stderr_bytes = std.mem.trim(u8, stderr.buffered(), "\n");
1983 stderr.tossBuffered();
1984 try run.step.addError("'{s}' logged {d} errors:\n{s}", .{ name, log_err_count, stderr_bytes });
1985 }
1986
1987 active_test_index = null;
1988
1989 const now: Io.Clock.Timestamp = .now(io, .awake);
1990 md.ns_per_test[tr_hdr.index] = @intCast(last_update.durationTo(now).raw.nanoseconds);
1991 last_update = now;
1992
1993 requestNextTest(io, child.stdin.?, md, &sub_prog_node) catch |err| return .{ .write_failed = err };
1994 },
1995 else => {}, // ignore other messages
1996 }
1997 }
1998}
1999
2000const FuzzTestRunner = struct {
2001 run: *Run,
2002 ctx: FuzzContext,
2003 coverage_id: ?u64,
2004
2005 instances: []Instance,
2006 /// The indexes of this are layed out such that it is effectively an array
2007 /// of `[instances.len][3]Io.Operation.Storage` of stdin, stdout, stderr.
2008 batch: Io.Batch,
2009 /// LIFO. Stream of message bodies trailed by PendingBroadcastFooter.
2010 pending_broadcasts: std.ArrayList(u8),
2011 broadcast: std.ArrayList(u8),
2012 broadcast_undelivered: u32,
2013
2014 const Instance = struct {
2015 child: process.Child,
2016 message: std.ArrayListAligned(u8, .@"4"),
2017 broadcast_written: usize,
2018 stderr: std.ArrayList(u8),
2019 stdin_vec: [1][]u8,
2020 stdout_vec: [1][]u8,
2021 stderr_vec: [1][]u8,
2022 progress_node: std.Progress.Node,
2023
2024 fn messageHeader(instance: *Instance) InHeader {
2025 assert(instance.message.items.len >= @sizeOf(InHeader));
2026 const header_ptr: *InHeader = @ptrCast(instance.message.items);
2027 var header = header_ptr.*;
2028 if (std.builtin.Endian.native != .little) {
2029 std.mem.byteSwapAllFields(InHeader, &header);
2030 }
2031 return header;
2032 }
2033 };
2034
2035 const PendingBroadcastFooter = struct {
2036 from_id: u32,
2037 body_len: u32,
2038 };
2039
2040 const InHeader = std.zig.Server.Message.Header;
2041 const OutHeader = std.zig.Client.Message.Header;
2042
2043 const stdin_i = 0;
2044 const stdout_i = 1;
2045 const stderr_i = 2;
2046
2047 fn init(
2048 run: *Run,
2049 ctx: FuzzContext,
2050 progress_node: std.Progress.Node,
2051 spawn_options: process.SpawnOptions,
2052 ) !FuzzTestRunner {
2053 const step_owner = run.step.owner;
2054 const gpa = step_owner.allocator;
2055 const io = step_owner.graph.io;
2056
2057 const n_instances = switch (ctx.fuzz.mode) {
2058 .forever => step_owner.graph.max_jobs orelse @min(
2059 std.Thread.getCpuCount() catch 1,
2060 (std.math.maxInt(u32) - 2) / 3,
2061 ),
2062 .limit => 1,
2063 };
2064 const instances = try gpa.alloc(Instance, n_instances);
2065 errdefer gpa.free(instances);
2066 const batch_storage = try gpa.alloc(Io.Operation.Storage, instances.len * 3);
2067 errdefer gpa.free(batch_storage);
2068
2069 @memset(instances, .{
2070 .child = undefined,
2071 .message = .empty,
2072 .broadcast_written = undefined,
2073 .stderr = .empty,
2074 .stdin_vec = undefined,
2075 .stdout_vec = undefined,
2076 .stderr_vec = undefined,
2077 .progress_node = undefined,
2078 });
2079 for (0.., instances) |id, *instance| {
2080 errdefer for (instances[0..id]) |*spawned| {
2081 spawned.child.kill(io);
2082 spawned.progress_node.end();
2083 };
2084 instance.child = try process.spawn(io, spawn_options);
2085 instance.progress_node = progress_node.start("starting fuzzer", 0);
2086 }
2087
2088 return .{
2089 .run = run,
2090 .ctx = ctx,
2091 .coverage_id = null,
2092
2093 .instances = instances,
2094 .batch = .init(batch_storage),
2095 .pending_broadcasts = .empty,
2096 .broadcast = .empty,
2097 .broadcast_undelivered = 0,
2098 };
2099 }
2100
2101 fn deinit(f: *FuzzTestRunner) void {
2102 const step_owner = f.run.step.owner;
2103 const gpa = step_owner.allocator;
2104 const io = step_owner.graph.io;
2105
2106 f.batch.cancel(io);
2107 gpa.free(f.batch.storage);
2108 var total_rss: usize = 0;
2109 for (f.instances) |*instance| {
2110 instance.child.kill(io);
2111 instance.message.deinit(gpa);
2112 instance.stderr.deinit(gpa);
2113 instance.progress_node.end();
2114 total_rss += instance.child.resource_usage_statistics.getMaxRss() orelse 0;
2115 }
2116 f.run.step.result_peak_rss = @max(f.run.step.result_peak_rss, total_rss);
2117 gpa.free(f.instances);
2118 }
2119
2120 fn startInstances(f: *FuzzTestRunner) !void {
2121 const step_owner = f.run.step.owner;
2122 const io = step_owner.graph.io;
2123
2124 for (0.., f.instances) |id, *instance| {
2125 const id32: u32 = @intCast(id);
2126 (switch (f.ctx.fuzz.mode) {
2127 .forever => sendRunFuzzTestMessage(
2128 io,
2129 instance.child.stdin.?,
2130 f.run.fuzz_tests.items,
2131 .forever,
2132 id32,
2133 ),
2134 .limit => |limit| sendRunFuzzTestMessage(
2135 io,
2136 instance.child.stdin.?,
2137 f.run.fuzz_tests.items,
2138 .iterations,
2139 limit.amount,
2140 ),
2141 }) catch |write_err| {
2142 // The runner unexpectedly closed stdin, which means it crashed during initialization.
2143 // Clean up everything and wait for the child to exit.
2144 instance.child.stdin.?.close(io);
2145 instance.child.stdin = null;
2146 const term = try instance.child.wait(io);
2147 return f.run.step.fail(
2148 "unable to write stdin ({t}); test process unexpectedly {f}",
2149 .{ write_err, fmtTerm(term) },
2150 );
2151 };
2152
2153 try f.addStdoutRead(id32, @sizeOf(InHeader));
2154 try f.addStderrRead(id32);
2155 }
2156 }
2157
2158 fn listen(f: *FuzzTestRunner) !void {
2159 const step_owner = f.run.step.owner;
2160 const io = step_owner.graph.io;
2161
2162 while (true) {
2163 try f.batch.awaitConcurrent(io, .none);
2164 while (f.batch.next()) |completion| {
2165 const id = completion.index / 3;
2166 const result = completion.result;
2167 switch (completion.index % 3) {
2168 0 => try f.completeStdinWrite(id, result.file_write_streaming catch |e| switch (e) {
2169 // Avoid calling `instanceEos` until EndOfStream is seen with stderr so
2170 // that all stderr is collected.
2171 error.BrokenPipe => continue,
2172 else => |write_e| return write_e,
2173 }),
2174 1 => try f.completeStdoutRead(id, result.file_read_streaming catch |e| switch (e) {
2175 // Avoid calling `instanceEos` until EndOfStream is seen with stderr so
2176 // that all stderr is collected.
2177 error.EndOfStream => continue,
2178 else => |read_e| return read_e,
2179 }),
2180 2 => try f.completeStderrRead(id, result.file_read_streaming catch |e| switch (e) {
2181 error.EndOfStream => return f.instanceEos(id),
2182 else => |read_e| return read_e,
2183 }),
2184 else => unreachable,
2185 }
2186 }
2187 }
2188 }
2189
2190 fn completeStdoutRead(f: *FuzzTestRunner, id: u32, n: usize) !void {
2191 const step_owner = f.run.step.owner;
2192 const gpa = step_owner.allocator;
2193 const io = step_owner.graph.io;
2194 const instance = &f.instances[id];
2195
2196 instance.message.items.len += n;
2197 const total_read = instance.message.items.len;
2198 if (total_read < @sizeOf(InHeader)) {
2199 try f.addStdoutRead(id, @sizeOf(InHeader));
2200 return;
2201 }
2202
2203 const header = instance.messageHeader();
2204 const body = instance.message.items[@sizeOf(InHeader)..];
2205 if (body.len != header.bytes_len) {
2206 try f.addStdoutRead(id, @sizeOf(InHeader) + header.bytes_len);
2207 return;
2208 }
2209
2210 switch (header.tag) {
2211 .zig_version => {
2212 if (!std.mem.eql(u8, builtin.zig_version_string, body)) return f.run.step.fail(
2213 "zig version mismatch build runner vs compiler: '{s}' vs '{s}'",
2214 .{ builtin.zig_version_string, body },
2215 );
2216 },
2217 .coverage_id => {
2218 var body_r: Io.Reader = .fixed(body);
2219 f.coverage_id = body_r.takeInt(u64, .little) catch unreachable;
2220 const cumulative_runs = body_r.takeInt(u64, .little) catch unreachable;
2221 const cumulative_unique = body_r.takeInt(u64, .little) catch unreachable;
2222 const cumulative_coverage = body_r.takeInt(u64, .little) catch unreachable;
2223
2224 const fuzz = f.ctx.fuzz;
2225 fuzz.queue_mutex.lockUncancelable(io);
2226 defer fuzz.queue_mutex.unlock(io);
2227 try fuzz.msg_queue.append(fuzz.gpa, .{ .coverage = .{
2228 .id = f.coverage_id.?,
2229 .cumulative = .{
2230 .runs = cumulative_runs,
2231 .unique = cumulative_unique,
2232 .coverage = cumulative_coverage,
2233 },
2234 .run = f.run,
2235 } });
2236 fuzz.queue_cond.signal(io);
2237 },
2238 .fuzz_start_addr => {
2239 var body_r: Io.Reader = .fixed(body);
2240 const fuzz = f.ctx.fuzz;
2241 const addr = body_r.takeInt(u64, .little) catch unreachable;
2242
2243 fuzz.queue_mutex.lockUncancelable(io);
2244 defer fuzz.queue_mutex.unlock(io);
2245 try fuzz.msg_queue.append(fuzz.gpa, .{ .entry_point = .{
2246 .addr = addr,
2247 .coverage_id = f.coverage_id.?,
2248 } });
2249 fuzz.queue_cond.signal(io);
2250 },
2251 .fuzz_test_change => {
2252 const test_i = std.mem.readInt(u32, body[0..4], .little);
2253 instance.progress_node.setName(f.run.fuzz_tests.items[test_i]);
2254 },
2255 .broadcast_fuzz_input => {
2256 if (f.instances.len == 1) {
2257 // No other processes to broadcast to.
2258 } else if (f.broadcast_undelivered == 0) {
2259 try f.instanceBroadcast(id, body);
2260 } else {
2261 const footer: PendingBroadcastFooter = .{
2262 .from_id = id,
2263 .body_len = @intCast(body.len),
2264 };
2265 // There is another broadcast in progress so add this one to the queue.
2266 const size = @sizeOf(PendingBroadcastFooter) + body.len;
2267 try f.pending_broadcasts.ensureUnusedCapacity(gpa, size);
2268 f.pending_broadcasts.appendSliceAssumeCapacity(body);
2269 f.pending_broadcasts.appendSliceAssumeCapacity(@ptrCast(&footer));
2270 }
2271 },
2272 else => {}, // ignore other messages
2273 }
2274
2275 instance.message.clearRetainingCapacity();
2276 try f.addStdoutRead(id, @sizeOf(InHeader));
2277 }
2278
2279 fn completeStderrRead(f: *FuzzTestRunner, id: u32, n: usize) !void {
2280 const instance = &f.instances[id];
2281 instance.stderr.items.len += n;
2282 try f.addStderrRead(id);
2283 }
2284
2285 fn completeStdinWrite(f: *FuzzTestRunner, id: u32, n: usize) !void {
2286 const instance = &f.instances[id];
2287
2288 instance.broadcast_written += n;
2289 if (instance.broadcast_written == f.broadcast.items.len) {
2290 f.broadcast_undelivered -= 1;
2291 if (f.broadcast_undelivered == 0) {
2292 try f.broadcastComplete();
2293 }
2294 } else {
2295 f.addStdinWrite(id);
2296 }
2297 }
2298
2299 fn addStdoutRead(f: *FuzzTestRunner, id: u32, end: usize) !void {
2300 const step_owner = f.run.step.owner;
2301 const gpa = step_owner.allocator;
2302 const instance = &f.instances[id];
2303
2304 try instance.message.ensureTotalCapacity(gpa, end);
2305 const start = instance.message.items.len;
2306 instance.stdout_vec = .{instance.message.allocatedSlice()[start..end]};
2307 f.batch.addAt(id * 3 + stdout_i, .{ .file_read_streaming = .{
2308 .file = instance.child.stdout.?,
2309 .data = &instance.stdout_vec,
2310 } });
2311 }
2312
2313 fn addStderrRead(f: *FuzzTestRunner, id: u32) !void {
2314 const step_owner = f.run.step.owner;
2315 const gpa = step_owner.allocator;
2316 const instance = &f.instances[id];
2317
2318 try instance.stderr.ensureUnusedCapacity(gpa, 1);
2319 instance.stderr_vec = .{instance.stderr.unusedCapacitySlice()};
2320 f.batch.addAt(id * 3 + stderr_i, .{ .file_read_streaming = .{
2321 .file = instance.child.stderr.?,
2322 .data = &instance.stderr_vec,
2323 } });
2324 }
2325
2326 fn addStdinWrite(f: *FuzzTestRunner, id: u32) void {
2327 const instance = &f.instances[id];
2328
2329 assert(f.broadcast.items.len != instance.broadcast_written);
2330 instance.stdin_vec = .{f.broadcast.items[instance.broadcast_written..]};
2331 f.batch.addAt(id * 3 + stdin_i, .{ .file_write_streaming = .{
2332 .file = instance.child.stdin.?,
2333 .data = &instance.stdin_vec,
2334 } });
2335 }
2336
2337 fn instanceEos(f: *FuzzTestRunner, id: u32) !void {
2338 const step_owner = f.run.step.owner;
2339 const io = step_owner.graph.io;
2340 const instance = &f.instances[id];
2341
2342 instance.child.stdin.?.close(io);
2343 instance.child.stdin = null;
2344 const term = try instance.child.wait(io);
2345 if (!termMatches(.{ .exited = 0 }, term)) {
2346 f.run.step.result_stderr = try f.mergedStderr();
2347 try f.saveCrash(id, term);
2348 return f.run.step.fail("test process unexpectedly {f}", .{fmtTerm(term)});
2349 }
2350 }
2351
2352 fn saveCrash(f: *FuzzTestRunner, id: u32, term: process.Child.Term) !void {
2353 const step = &f.run.step;
2354 const b = step.owner;
2355 const io = b.graph.io;
2356
2357 if (f.coverage_id == null) return;
2358
2359 // Search for the input file corresponding to the instance
2360 const InputHeader = Build.abi.fuzz.MmapInputHeader;
2361 var in_r_buf: [@sizeOf(InputHeader)]u8 = undefined;
2362 var in_r: Io.File.Reader = undefined;
2363 var in_f: Io.File = undefined;
2364 var in_name_buf: [12]u8 = undefined;
2365 var in_name: []const u8 = undefined;
2366 var i: u32 = 0;
2367 const header: InputHeader = while (true) : ({
2368 if (i == std.math.maxInt(u32)) return;
2369 i += 1;
2370 }) {
2371 const name_prefix = "f" ++ Io.Dir.path.sep_str ++ "in";
2372 in_name = std.fmt.bufPrint(&in_name_buf, name_prefix ++ "{x}", .{i}) catch unreachable;
2373 in_f = b.cache_root.handle.openFile(io, in_name, .{
2374 .lock = .exclusive,
2375 .lock_nonblocking = true,
2376 }) catch |e| switch (e) {
2377 error.FileNotFound => return,
2378 error.WouldBlock => continue, // Can not be from
2379 // the crashed instance since it is still locked.
2380 else => return step.fail("failed to open file '{f}{s}': {t}", .{
2381 b.cache_root, in_name, e,
2382 }),
2383 };
2384
2385 in_r = in_f.readerStreaming(io, &in_r_buf);
2386 const header = in_r.interface.takeStruct(InputHeader, .little) catch |e| {
2387 in_f.close(io);
2388 switch (e) {
2389 error.ReadFailed => return step.fail("failed to read file '{f}{s}': {t}", .{
2390 b.cache_root, in_name, in_r.err.?,
2391 }),
2392 error.EndOfStream => continue,
2393 }
2394 };
2395
2396 if (header.pc_digest == f.coverage_id.? and
2397 header.instance_id == id and
2398 header.test_i < f.run.fuzz_tests.items.len)
2399 {
2400 break header;
2401 }
2402
2403 in_f.close(io);
2404 };
2405 defer in_f.close(io);
2406
2407 // Save it to a seperate file
2408 const crash_name = "f" ++ Io.Dir.path.sep_str ++ "crash";
2409 const out = b.cache_root.handle.createFile(io, crash_name, .{
2410 .lock = .exclusive, // Multiple run steps could have found a crash at the same time
2411 }) catch |e| return step.fail("failed to create file '{f}{s}': {t}", .{
2412 b.cache_root, crash_name, e,
2413 });
2414 defer out.close(io);
2415
2416 var out_w_buf: [512]u8 = undefined;
2417 var out_w = out.writerStreaming(io, &out_w_buf);
2418 _ = out_w.interface.sendFileAll(&in_r, .limited(header.len)) catch |e| switch (e) {
2419 error.ReadFailed => return step.fail("failed to read file '{f}{s}': {t}", .{
2420 b.cache_root, in_name, in_r.err.?,
2421 }),
2422 error.WriteFailed => return step.fail("failed to write file '{f}{s}': {t}", .{
2423 b.cache_root, crash_name, out_w.err.?,
2424 }),
2425 };
2426
2427 return f.run.step.fail("test '{s}' {f}; input saved to '{f}{s}'", .{
2428 f.run.fuzz_tests.items[header.test_i],
2429 fmtTerm(term),
2430 b.cache_root,
2431 crash_name,
2432 });
2433 }
2434
2435 fn instanceBroadcast(f: *FuzzTestRunner, from_id: u32, bytes: []const u8) !void {
2436 assert(f.instances.len > 1);
2437 assert(f.broadcast_undelivered == 0); // no other broadcast is progress
2438 assert(f.broadcast.items.len == 0);
2439 assert(from_id < f.instances.len);
2440
2441 const step_owner = f.run.step.owner;
2442 const gpa = step_owner.allocator;
2443
2444 var out_header: OutHeader = .{
2445 .tag = .new_fuzz_input,
2446 .bytes_len = @intCast(bytes.len),
2447 };
2448 if (std.builtin.Endian.native != .little) {
2449 std.mem.byteSwapAllFields(OutHeader, &out_header);
2450 }
2451 try f.broadcast.ensureTotalCapacity(gpa, @sizeOf(OutHeader) + bytes.len);
2452 f.broadcast.appendSliceAssumeCapacity(@ptrCast(&out_header));
2453 f.broadcast.appendSliceAssumeCapacity(bytes);
2454
2455 f.broadcast_undelivered = @intCast(f.instances.len - 1);
2456 for (0.., f.instances) |to_id, *instance| {
2457 if (to_id == from_id) continue;
2458 instance.broadcast_written = 0;
2459 f.addStdinWrite(@intCast(to_id));
2460 }
2461 }
2462
2463 fn broadcastComplete(f: *FuzzTestRunner) !void {
2464 assert(f.instances.len > 1);
2465 assert(f.broadcast_undelivered == 0);
2466 f.broadcast.clearRetainingCapacity();
2467
2468 const pending = &f.pending_broadcasts;
2469 if (pending.items.len != 0) {
2470 // Another broadcast is pending; copy it over to `broadcast`
2471
2472 const footer_len = @sizeOf(PendingBroadcastFooter);
2473 const footer_bytes = pending.items[pending.items.len - footer_len ..];
2474 const footer: *align(1) PendingBroadcastFooter = @ptrCast(footer_bytes);
2475 pending.items.len -= footer_len;
2476
2477 const body = pending.items[pending.items.len - footer.body_len ..];
2478 try f.instanceBroadcast(footer.from_id, body);
2479 pending.items.len -= body.len;
2480 }
2481 }
2482
2483 fn mergedStderr(f: *FuzzTestRunner) std.mem.Allocator.Error![]const u8 {
2484 const step_owner = f.run.step.owner;
2485 const arena = step_owner.allocator;
2486
2487 // Collect any available stderr
2488 while (f.batch.next()) |completion| {
2489 if (completion.index % 3 != 2) continue;
2490 const len = completion.result.file_read_streaming catch continue;
2491 f.instances[completion.index / 3].stderr.items.len += len;
2492 }
2493
2494 var stderr_len: usize = 0;
2495 for (f.instances) |*instance| stderr_len += instance.stderr.items.len;
2496 const stderr = try arena.alloc(u8, stderr_len);
2497
2498 stderr_len = 0;
2499 for (f.instances) |*instance| {
2500 @memcpy(stderr[stderr_len..][0..instance.stderr.items.len], instance.stderr.items);
2501 stderr_len += instance.stderr.items.len;
2502 }
2503 return stderr;
2504 }
2505};
2506
2507fn evalFuzzTest(
2508 run: *Run,
2509 spawn_options: process.SpawnOptions,
2510 options: Step.MakeOptions,
2511 fuzz_context: FuzzContext,
2512) !void {
2513 var f: FuzzTestRunner = try .init(run, fuzz_context, options.progress_node, spawn_options);
2514 defer f.deinit();
2515 try f.startInstances();
2516 try f.listen();
2517}
2518
2519const TestMetadata = struct {
2520 names: []const u32,
2521 ns_per_test: []u64,
2522 expected_panic_msgs: []const u32,
2523 string_bytes: []const u8,
2524 next_index: u32,
2525 prog_node: std.Progress.Node,
2526
2527 fn toCachedTestMetadata(tm: TestMetadata) CachedTestMetadata {
2528 return .{
2529 .names = tm.names,
2530 .string_bytes = tm.string_bytes,
2531 };
2532 }
2533
2534 fn testName(tm: TestMetadata, index: u32) []const u8 {
2535 return tm.toCachedTestMetadata().testName(index);
2536 }
2537};
2538
2539pub const CachedTestMetadata = struct {
2540 names: []const u32,
2541 string_bytes: []const u8,
2542
2543 pub fn testName(tm: CachedTestMetadata, index: u32) []const u8 {
2544 return std.mem.sliceTo(tm.string_bytes[tm.names[index]..], 0);
2545 }
2546};
2547
2548fn requestNextTest(io: Io, in: Io.File, metadata: *TestMetadata, sub_prog_node: *?std.Progress.Node) !void {
2549 while (metadata.next_index < metadata.names.len) {
2550 const i = metadata.next_index;
2551 metadata.next_index += 1;
2552
2553 if (metadata.expected_panic_msgs[i] != 0) continue;
2554
2555 const name = metadata.testName(i);
2556 if (sub_prog_node.*) |n| n.end();
2557 sub_prog_node.* = metadata.prog_node.start(name, 0);
2558
2559 try sendRunTestMessage(io, in, .run_test, i);
2560 return;
2561 } else {
2562 metadata.next_index = std.math.maxInt(u32); // indicate that all tests are done
2563 try sendMessage(io, in, .exit);
2564 }
2565}
2566
2567fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void {
2568 const header: std.zig.Client.Message.Header = .{
2569 .tag = tag,
2570 .bytes_len = 0,
2571 };
2572 var w = file.writerStreaming(io, &.{});
2573 w.interface.writeStruct(header, .little) catch |err| switch (err) {
2574 error.WriteFailed => return w.err.?,
2575 };
2576}
2577
2578fn sendRunTestMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag, index: u32) !void {
2579 const header: std.zig.Client.Message.Header = .{
2580 .tag = tag,
2581 .bytes_len = 4,
2582 };
2583 var w = file.writerStreaming(io, &.{});
2584 w.interface.writeStruct(header, .little) catch |err| switch (err) {
2585 error.WriteFailed => return w.err.?,
2586 };
2587 w.interface.writeInt(u32, index, .little) catch |err| switch (err) {
2588 error.WriteFailed => return w.err.?,
2589 };
2590}
2591
2592fn sendRunFuzzTestMessage(
2593 io: Io,
2594 file: Io.File,
2595 test_names: []const []const u8,
2596 kind: std.Build.abi.fuzz.LimitKind,
2597 amount_or_instance: u64,
2598) !void {
2599 const header: std.zig.Client.Message.Header = .{
2600 .tag = .start_fuzzing,
2601 .bytes_len = 1 + 8 + 4 + count: {
2602 var c: u32 = @intCast(test_names.len * 4);
2603 for (test_names) |name| {
2604 c += @intCast(name.len);
2605 }
2606 break :count c;
2607 },
2608 };
2609 var w = file.writerStreaming(io, &.{});
2610 w.interface.writeStruct(header, .little) catch |err| switch (err) {
2611 error.WriteFailed => return w.err.?,
2612 };
2613 w.interface.writeByte(@intFromEnum(kind)) catch |err| switch (err) {
2614 error.WriteFailed => return w.err.?,
2615 };
2616 w.interface.writeInt(u64, amount_or_instance, .little) catch |err| switch (err) {
2617 error.WriteFailed => return w.err.?,
2618 };
2619 w.interface.writeInt(u32, @intCast(test_names.len), .little) catch |err| switch (err) {
2620 error.WriteFailed => return w.err.?,
2621 };
2622 for (test_names) |test_name| {
2623 w.interface.writeInt(u32, @intCast(test_name.len), .little) catch |err| switch (err) {
2624 error.WriteFailed => return w.err.?,
2625 };
2626 w.interface.writeAll(test_name) catch |err| switch (err) {
2627 error.WriteFailed => return w.err.?,
2628 };
2629 }
2630}
2631
2632fn evalGeneric(run: *Run, spawn_options: process.SpawnOptions) !EvalGenericResult {
2633 const b = run.step.owner;
2634 const io = b.graph.io;
2635 const arena = b.allocator;
2636 const gpa = b.allocator;
2637
2638 var child = try process.spawn(io, spawn_options);
2639 defer child.kill(io);
2640
2641 switch (run.stdin) {
2642 .bytes => |bytes| {
2643 child.stdin.?.writeStreamingAll(io, bytes) catch |err| {
2644 return run.step.fail("unable to write stdin: {t}", .{err});
2645 };
2646 child.stdin.?.close(io);
2647 child.stdin = null;
2648 },
2649 .lazy_path => |lazy_path| {
2650 const path = lazy_path.getPath3(b, &run.step);
2651 const file = path.root_dir.handle.openFile(io, path.subPathOrDot(), .{}) catch |err| {
2652 return run.step.fail("unable to open stdin file: {t}", .{err});
2653 };
2654 defer file.close(io);
2655 // TODO https://github.com/ziglang/zig/issues/23955
2656 var read_buffer: [1024]u8 = undefined;
2657 var file_reader = file.reader(io, &read_buffer);
2658 var write_buffer: [1024]u8 = undefined;
2659 var stdin_writer = child.stdin.?.writerStreaming(io, &write_buffer);
2660 _ = stdin_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) {
2661 error.ReadFailed => return run.step.fail("failed to read from {f}: {t}", .{
2662 path, file_reader.err.?,
2663 }),
2664 error.WriteFailed => return run.step.fail("failed to write to stdin: {t}", .{
2665 stdin_writer.err.?,
2666 }),
2667 };
2668 stdin_writer.interface.flush() catch |err| switch (err) {
2669 error.WriteFailed => return run.step.fail("failed to write to stdin: {t}", .{
2670 stdin_writer.err.?,
2671 }),
2672 };
2673 child.stdin.?.close(io);
2674 child.stdin = null;
2675 },
2676 .none => {},
2677 }
2678
2679 var stdout_bytes: ?[]const u8 = null;
2680 var stderr_bytes: ?[]const u8 = null;
2681
2682 if (child.stdout) |stdout| {
2683 if (child.stderr) |stderr| {
2684 var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined;
2685 var multi_reader: Io.File.MultiReader = undefined;
2686 multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ stdout, stderr });
2687 defer multi_reader.deinit();
2688
2689 const stdout_reader = multi_reader.reader(0);
2690 const stderr_reader = multi_reader.reader(1);
2691
2692 while (multi_reader.fill(64, .none)) |_| {
2693 if (run.stdio_limit.toInt()) |limit| {
2694 if (stdout_reader.buffered().len > limit)
2695 return error.StdoutStreamTooLong;
2696 if (stderr_reader.buffered().len > limit)
2697 return error.StderrStreamTooLong;
2698 }
2699 } else |err| switch (err) {
2700 error.Timeout => unreachable,
2701 error.EndOfStream => {},
2702 else => |e| return e,
2703 }
2704
2705 try multi_reader.checkAnyError();
2706
2707 // TODO: this string can leak since alloc below can return error.
2708 stdout_bytes = try multi_reader.toOwnedSlice(0);
2709 // TODO: this string can leak since its allocated using gpa and `try child.wait(io)` below can fail.
2710 stderr_bytes = try multi_reader.toOwnedSlice(1);
2711 } else {
2712 var stdout_reader = stdout.readerStreaming(io, &.{});
2713 stdout_bytes = stdout_reader.interface.allocRemaining(arena, run.stdio_limit) catch |err| switch (err) {
2714 error.OutOfMemory => |e| return e,
2715 error.ReadFailed => return stdout_reader.err.?,
2716 error.StreamTooLong => return error.StdoutStreamTooLong,
2717 };
2718 }
2719 } else if (child.stderr) |stderr| {
2720 var stderr_reader = stderr.readerStreaming(io, &.{});
2721 stderr_bytes = stderr_reader.interface.allocRemaining(arena, run.stdio_limit) catch |err| switch (err) {
2722 error.OutOfMemory => |e| return e,
2723 error.ReadFailed => return stderr_reader.err.?,
2724 error.StreamTooLong => return error.StderrStreamTooLong,
2725 };
2726 }
2727
2728 if (stderr_bytes) |bytes| if (bytes.len > 0) {
2729 // Treat stderr as an error message.
2730 const stderr_is_diagnostic = run.captured_stderr == null and switch (run.stdio) {
2731 .check => |checks| !checksContainStderr(checks.items),
2732 else => true,
2733 };
2734 if (stderr_is_diagnostic) {
2735 run.step.result_stderr = bytes;
2736 }
2737 };
2738
2739 run.step.result_peak_rss = child.resource_usage_statistics.getMaxRss() orelse 0;
2740
2741 return .{
2742 .term = try child.wait(io),
2743 .stdout = stdout_bytes,
2744 .stderr = stderr_bytes,
2745 };
2746}
2747
2748fn addPathForDynLibs(run: *Run, artifact: *Step.Compile) void {
2749 const b = run.step.owner;
2750 const compiles = artifact.getCompileDependencies(true);
2751 for (compiles) |compile| {
2752 if (compile.root_module.resolved_target.?.result.os.tag == .windows and
2753 compile.isDynamicLibrary())
2754 {
2755 addPathDir(run, Dir.path.dirname(compile.getEmittedBin().getPath2(b, &run.step)).?);
2756 }
2757 }
2758}
2759
2760fn failForeign(
2761 run: *Run,
2762 suggested_flag: []const u8,
2763 argv0: []const u8,
2764 exe: *Step.Compile,
2765) error{ MakeFailed, MakeSkipped, OutOfMemory } {
2766 switch (run.stdio) {
2767 .check, .zig_test => {
2768 if (run.skip_foreign_checks)
2769 return error.MakeSkipped;
2770
2771 const b = run.step.owner;
2772 const host_name = try b.graph.host.result.zigTriple(b.allocator);
2773 const foreign_name = try exe.rootModuleTarget().zigTriple(b.allocator);
2774
2775 return run.step.fail(
2776 \\unable to spawn foreign binary '{s}' ({s}) on host system ({s})
2777 \\ consider using {s} or enabling skip_foreign_checks in the Run step
2778 , .{ argv0, foreign_name, host_name, suggested_flag });
2779 },
2780 else => {
2781 return run.step.fail("unable to spawn foreign binary '{s}'", .{argv0});
2782 },
2783 }
2784}
2785
2786fn hashStdIo(hh: *std.Build.Cache.HashHelper, stdio: StdIo) void {
2787 switch (stdio) {
2788 .infer_from_args, .inherit, .zig_test => {},
2789 .check => |checks| for (checks.items) |check| {
2790 hh.add(@as(std.meta.Tag(StdIo.Check), check));
2791 switch (check) {
2792 .expect_stderr_exact,
2793 .expect_stderr_match,
2794 .expect_stdout_exact,
2795 .expect_stdout_match,
2796 => |s| hh.addBytes(s),
2797
2798 .expect_term => |term| {
2799 hh.add(@as(std.meta.Tag(process.Child.Term), term));
2800 switch (term) {
2801 inline .exited, .signal, .stopped => |x| hh.add(x),
2802 .unknown => |x| hh.add(x),
2803 }
2804 },
2805 }
2806 },
2807 }
724 file_input.addStepDependencies(&run.step);
725 run.file_inputs.append(arena, file_input.dupe(graph)) catch @panic("OOM");
2808726}
lib/std/Build/Step/TranslateC.zig+55-164
......@@ -1,24 +1,25 @@
1const TranslateC = @This();
2
13const std = @import("std");
2const Step = std.Build.Step;
3const LazyPath = std.Build.LazyPath;
44const fs = std.fs;
55const mem = std.mem;
6
7const TranslateC = @This();
8
9pub const base_id: Step.Id = .translate_c;
6const allocPrint = std.fmt.allocPrint;
7const Step = std.Build.Step;
8const LazyPath = std.Build.LazyPath;
9const Configuration = std.Build.Configuration;
1010
1111step: Step,
1212source: std.Build.LazyPath,
13include_dirs: std.array_list.Managed(std.Build.Module.IncludeDir),
14system_libs: std.ArrayList(std.Build.Module.SystemLib),
15c_macros: std.array_list.Managed([]const u8),
16out_basename: []const u8,
13include_dirs: std.ArrayList(std.Build.Module.IncludeDir) = .empty,
14system_libs: std.ArrayList(std.Build.Module.SystemLib) = .empty,
15c_macros: std.ArrayList(Configuration.String) = .empty,
1716target: std.Build.ResolvedTarget,
1817optimize: std.builtin.OptimizeMode,
19output_file: std.Build.GeneratedFile,
18output_file: Configuration.GeneratedFileIndex,
2019link_libc: bool,
2120
21pub const base_tag: Step.Tag = .translate_c;
22
2223pub const Options = struct {
2324 root_source_file: std.Build.LazyPath,
2425 target: std.Build.ResolvedTarget,
......@@ -27,24 +28,20 @@ pub const Options = struct {
2728};
2829
2930pub fn create(owner: *std.Build, options: Options) *TranslateC {
30 const translate_c = owner.allocator.create(TranslateC) catch @panic("OOM");
31 const source = options.root_source_file.dupe(owner);
31 const graph = owner.graph;
32 const translate_c = graph.create(TranslateC);
33 const source = options.root_source_file.dupe(graph);
3234 translate_c.* = .{
33 .step = Step.init(.{
34 .id = base_id,
35 .step = .init(.{
36 .tag = base_tag,
3537 .name = "translate-c",
3638 .owner = owner,
37 .makeFn = make,
3839 }),
3940 .source = source,
40 .include_dirs = std.array_list.Managed(std.Build.Module.IncludeDir).init(owner.allocator),
41 .c_macros = std.array_list.Managed([]const u8).init(owner.allocator),
42 .out_basename = undefined,
4341 .target = options.target,
4442 .optimize = options.optimize,
45 .output_file = .{ .step = &translate_c.step },
43 .output_file = graph.addGeneratedFile(&translate_c.step),
4644 .link_libc = options.link_libc,
47 .system_libs = .empty,
4845 };
4946 source.addStepDependencies(&translate_c.step);
5047 return translate_c;
......@@ -59,7 +56,7 @@ pub const AddExecutableOptions = struct {
5956};
6057
6158pub fn getOutput(translate_c: *TranslateC) std.Build.LazyPath {
62 return .{ .generated = .{ .file = &translate_c.output_file } };
59 return .{ .generated = .{ .index = translate_c.output_file } };
6360}
6461
6562/// Creates a module from the translated source and adds it to the package's
......@@ -87,8 +84,8 @@ pub fn createModule(translate_c: *TranslateC) *std.Build.Module {
8784}
8885
8986fn setUpModule(translate_c: *TranslateC, module: *std.Build.Module) *std.Build.Module {
90 const b = translate_c.step.owner;
91 const arena = b.graph.arena;
87 const graph = translate_c.step.owner.graph;
88 const arena = graph.arena;
9289
9390 if (translate_c.link_libc) module.link_libc = true;
9491
......@@ -100,42 +97,49 @@ fn setUpModule(translate_c: *TranslateC, module: *std.Build.Module) *std.Build.M
10097}
10198
10299pub fn addAfterIncludePath(translate_c: *TranslateC, lazy_path: LazyPath) void {
103 const b = translate_c.step.owner;
104 translate_c.include_dirs.append(.{ .path_after = lazy_path.dupe(b) }) catch
100 const graph = translate_c.step.owner.graph;
101 const arena = graph.arena;
102 translate_c.include_dirs.append(arena, .{ .path_after = lazy_path.dupe(graph) }) catch
105103 @panic("OOM");
106104 lazy_path.addStepDependencies(&translate_c.step);
107105}
108106
109107pub fn addSystemIncludePath(translate_c: *TranslateC, lazy_path: LazyPath) void {
110 const b = translate_c.step.owner;
111 translate_c.include_dirs.append(.{ .path_system = lazy_path.dupe(b) }) catch
108 const graph = translate_c.step.owner.graph;
109 const arena = graph.arena;
110 translate_c.include_dirs.append(arena, .{ .path_system = lazy_path.dupe(graph) }) catch
112111 @panic("OOM");
113112 lazy_path.addStepDependencies(&translate_c.step);
114113}
115114
116115pub fn addIncludePath(translate_c: *TranslateC, lazy_path: LazyPath) void {
117 const b = translate_c.step.owner;
118 translate_c.include_dirs.append(.{ .path = lazy_path.dupe(b) }) catch
116 const graph = translate_c.step.owner.graph;
117 const arena = graph.arena;
118 translate_c.include_dirs.append(arena, .{ .path = lazy_path.dupe(graph) }) catch
119119 @panic("OOM");
120120 lazy_path.addStepDependencies(&translate_c.step);
121121}
122122
123123pub fn addConfigHeader(translate_c: *TranslateC, config_header: *Step.ConfigHeader) void {
124 translate_c.include_dirs.append(.{ .config_header_step = config_header }) catch
124 const graph = translate_c.step.owner.graph;
125 const arena = graph.arena;
126 translate_c.include_dirs.append(arena, .{ .config_header_step = config_header }) catch
125127 @panic("OOM");
126128 translate_c.step.dependOn(&config_header.step);
127129}
128130
129131pub fn addSystemFrameworkPath(translate_c: *TranslateC, directory_path: LazyPath) void {
130 const b = translate_c.step.owner;
131 translate_c.include_dirs.append(.{ .framework_path_system = directory_path.dupe(b) }) catch
132 const graph = translate_c.step.owner.graph;
133 const arena = graph.arena;
134 translate_c.include_dirs.append(arena, .{ .framework_path_system = directory_path.dupe(graph) }) catch
132135 @panic("OOM");
133136 directory_path.addStepDependencies(&translate_c.step);
134137}
135138
136139pub fn addFrameworkPath(translate_c: *TranslateC, directory_path: LazyPath) void {
137 const b = translate_c.step.owner;
138 translate_c.include_dirs.append(.{ .framework_path = directory_path.dupe(b) }) catch
140 const graph = translate_c.step.owner.graph;
141 const arena = graph.arena;
142 translate_c.include_dirs.append(arena, .{ .framework_path = directory_path.dupe(graph) }) catch
139143 @panic("OOM");
140144 directory_path.addStepDependencies(&translate_c.step);
141145}
......@@ -151,135 +155,21 @@ pub fn addCheckFile(translate_c: *TranslateC, expected_matches: []const []const
151155/// If the value is omitted, it is set to 1.
152156/// `name` and `value` need not live longer than the function call.
153157pub fn defineCMacro(translate_c: *TranslateC, name: []const u8, value: ?[]const u8) void {
154 const macro = translate_c.step.owner.fmt("{s}={s}", .{ name, value orelse "1" });
155 translate_c.c_macros.append(macro) catch @panic("OOM");
158 const graph = translate_c.step.owner.graph;
159 const arena = graph.arena;
160 const wc = &graph.wip_configuration;
161 const macro = allocPrint(arena, "{s}={s}", .{ name, value orelse "1" }) catch @panic("OOM");
162 const macro_string = wc.addString(macro) catch @panic("OOM");
163 translate_c.c_macros.append(arena, macro_string) catch @panic("OOM");
156164}
157165
158/// name_and_value looks like [name]=[value]. If the value is omitted, it is set to 1.
166/// name_and_value looks like [name]=[value].
159167pub fn defineCMacroRaw(translate_c: *TranslateC, name_and_value: []const u8) void {
160 translate_c.c_macros.append(translate_c.step.owner.dupe(name_and_value)) catch @panic("OOM");
161}
162
163fn make(step: *Step, options: Step.MakeOptions) !void {
164 const prog_node = options.progress_node;
165 const b = step.owner;
166 const translate_c: *TranslateC = @fieldParentPtr("step", step);
167 const arena = b.graph.arena;
168
169 var argv_list = std.array_list.Managed([]const u8).init(b.allocator);
170 try argv_list.append(b.graph.zig_exe);
171 try argv_list.append("translate-c");
172 if (translate_c.link_libc) {
173 try argv_list.append("-lc");
174 }
175
176 try argv_list.append("--cache-dir");
177 try argv_list.append(b.cache_root.path orelse ".");
178
179 try argv_list.append("--global-cache-dir");
180 try argv_list.append(b.graph.global_cache_root.path orelse ".");
181
182 if (!translate_c.target.query.isNative()) {
183 try argv_list.append("-target");
184 try argv_list.append(try translate_c.target.query.zigTriple(b.allocator));
185 }
186
187 switch (translate_c.optimize) {
188 .Debug => {}, // Skip since it's the default.
189 else => try argv_list.append(b.fmt("-O{s}", .{@tagName(translate_c.optimize)})),
190 }
191
192 for (translate_c.include_dirs.items) |include_dir| {
193 try include_dir.appendZigProcessFlags(b, &argv_list, step);
194 }
195
196 for (translate_c.c_macros.items) |c_macro| {
197 try argv_list.append("-D");
198 try argv_list.append(c_macro);
199 }
200
201 var prev_search_strategy: std.Build.Module.SystemLib.SearchStrategy = .paths_first;
202 var prev_preferred_link_mode: std.builtin.LinkMode = .dynamic;
203
204 for (translate_c.system_libs.items) |*system_lib| {
205 var seen_system_libs: std.StringHashMapUnmanaged([]const []const u8) = .empty;
206 const system_lib_gop = try seen_system_libs.getOrPut(arena, system_lib.name);
207 if (system_lib_gop.found_existing) {
208 try argv_list.appendSlice(system_lib_gop.value_ptr.*);
209 continue;
210 } else {
211 system_lib_gop.value_ptr.* = &.{};
212 }
213
214 if (system_lib.search_strategy != prev_search_strategy or
215 system_lib.preferred_link_mode != prev_preferred_link_mode)
216 {
217 switch (system_lib.search_strategy) {
218 .no_fallback => switch (system_lib.preferred_link_mode) {
219 .dynamic => try argv_list.append("-search_dylibs_only"),
220 .static => try argv_list.append("-search_static_only"),
221 },
222 .paths_first => switch (system_lib.preferred_link_mode) {
223 .dynamic => try argv_list.append("-search_paths_first"),
224 .static => try argv_list.append("-search_paths_first_static"),
225 },
226 .mode_first => switch (system_lib.preferred_link_mode) {
227 .dynamic => try argv_list.append("-search_dylibs_first"),
228 .static => try argv_list.append("-search_static_first"),
229 },
230 }
231 prev_search_strategy = system_lib.search_strategy;
232 prev_preferred_link_mode = system_lib.preferred_link_mode;
233 }
234
235 const prefix: []const u8 = prefix: {
236 if (system_lib.needed) break :prefix "-needed-l";
237 if (system_lib.weak) break :prefix "-weak-l";
238 break :prefix "-l";
239 };
240 switch (system_lib.use_pkg_config) {
241 .no => try argv_list.append(b.fmt("{s}{s}", .{ prefix, system_lib.name })),
242 .yes, .force => {
243 if (Step.Compile.runPkgConfig(&translate_c.step, system_lib.name)) |result| {
244 try argv_list.appendSlice(result.cflags);
245 try argv_list.appendSlice(result.libs);
246 try seen_system_libs.put(arena, system_lib.name, result.cflags);
247 } else |err| switch (err) {
248 error.PkgConfigInvalidOutput,
249 error.PkgConfigCrashed,
250 error.PkgConfigFailed,
251 error.PkgConfigNotInstalled,
252 error.PackageNotFound,
253 => switch (system_lib.use_pkg_config) {
254 .yes => {
255 // pkg-config failed, so fall back to linking the library
256 // by name directly.
257 try argv_list.append(b.fmt("{s}{s}", .{
258 prefix,
259 system_lib.name,
260 }));
261 },
262 .force => {
263 std.debug.panic("pkg-config failed for library {s}", .{system_lib.name});
264 },
265 .no => unreachable,
266 },
267
268 else => |e| return e,
269 }
270 },
271 }
272 }
273
274 const c_source_path = translate_c.source.getPath2(b, step);
275 try argv_list.append(c_source_path);
276
277 try argv_list.append("--listen=-");
278 const output_dir = try step.evalZigProcess(argv_list.items, prog_node, false, options.web_server, options.gpa);
279
280 const basename = std.fs.path.stem(std.fs.path.basename(c_source_path));
281 translate_c.out_basename = b.fmt("{s}.zig", .{basename});
282 translate_c.output_file.path = output_dir.?.joinString(b.allocator, translate_c.out_basename) catch @panic("OOM");
168 const graph = translate_c.step.owner.graph;
169 const arena = graph.arena;
170 const wc = &graph.wip_configuration;
171 const macro_string = wc.addString(name_and_value) catch @panic("OOM");
172 translate_c.c_macros.append(arena, macro_string) catch @panic("OOM");
283173}
284174
285175pub fn linkSystemLibrary(
......@@ -287,9 +177,10 @@ pub fn linkSystemLibrary(
287177 name: []const u8,
288178 options: std.Build.Module.LinkSystemLibraryOptions,
289179) void {
290 const b = translate_c.step.owner;
291 translate_c.system_libs.append(b.allocator, .{
292 .name = b.dupe(name),
180 const graph = translate_c.step.owner.graph;
181 const arena = graph.arena;
182 translate_c.system_libs.append(arena, .{
183 .name = graph.dupeString(name),
293184 .needed = options.needed,
294185 .weak = options.weak,
295186 .use_pkg_config = options.use_pkg_config,
lib/std/Build/Step/UpdateSourceFiles.zig+38-91
......@@ -1,116 +1,63 @@
1//! Writes data to paths relative to the package root, effectively mutating the
2//! package's source files. Be careful with the latter functionality; it should
3//! not be used during the normal build process, but as a utility run by a
4//! developer with intention to update source files, which will then be
5//! committed to version control.
61const UpdateSourceFiles = @This();
72
83const std = @import("std");
9const Io = std.Io;
104const Step = std.Build.Step;
11const fs = std.fs;
12const ArrayList = std.ArrayList;
5const Configuration = std.Build.Configuration;
136
147step: Step,
15output_source_files: std.ArrayList(OutputSourceFile),
8embeds: std.ArrayList(Embed) = .empty,
9copies: std.ArrayList(Copy) = .empty,
1610
17pub const base_id: Step.Id = .update_source_files;
11pub const base_tag: Step.Tag = .update_source_files;
1812
19pub const OutputSourceFile = struct {
20 contents: Contents,
21 sub_path: []const u8,
22};
23
24pub const Contents = union(enum) {
25 bytes: []const u8,
26 copy: std.Build.LazyPath,
27};
13pub const Embed = Step.WriteFile.Embed;
14pub const Copy = Step.WriteFile.Copy;
2815
2916pub fn create(owner: *std.Build) *UpdateSourceFiles {
30 const usf = owner.allocator.create(UpdateSourceFiles) catch @panic("OOM");
17 const graph = owner.graph;
18 const usf = graph.create(UpdateSourceFiles);
3119 usf.* = .{
32 .step = Step.init(.{
33 .id = base_id,
20 .step = .init(.{
21 .tag = base_tag,
3422 .name = "UpdateSourceFiles",
3523 .owner = owner,
36 .makeFn = make,
3724 }),
38 .output_source_files = .empty,
3925 };
4026 return usf;
4127}
4228
43/// A path relative to the package root.
29/// Overwrites a path relative to the build root with the contents of another file.
4430///
45/// Be careful with this because it updates source files. This should not be
46/// used as part of the normal build process, but as a utility occasionally
47/// run by a developer with intent to modify source files and then commit
48/// those changes to version control.
49pub fn addCopyFileToSource(usf: *UpdateSourceFiles, source: std.Build.LazyPath, sub_path: []const u8) void {
50 const b = usf.step.owner;
51 usf.output_source_files.append(b.allocator, .{
52 .contents = .{ .copy = source },
53 .sub_path = sub_path,
31/// Because it updates source files, this should not be used as part of the
32/// normal build process, but as a utility occasionally run by a developer with
33/// intent to modify source files and then commit those changes to version
34/// control.
35pub fn addCopyFileToSource(usf: *UpdateSourceFiles, src_file: std.Build.LazyPath, sub_path: []const u8) void {
36 const graph = usf.step.owner.graph;
37 const wc = &graph.wip_configuration;
38 const arena = graph.arena;
39
40 usf.copies.append(arena, .{
41 .sub_path = wc.addString(sub_path) catch @panic("OOM"),
42 .src_file = src_file.dupe(graph),
5443 }) catch @panic("OOM");
55 source.addStepDependencies(&usf.step);
44
45 src_file.addStepDependencies(&usf.step);
5646}
5747
58/// A path relative to the package root.
48/// Overwrites a path relative to the package root with the provided bytes.
5949///
60/// Be careful with this because it updates source files. This should not be
61/// used as part of the normal build process, but as a utility occasionally
62/// run by a developer with intent to modify source files and then commit
63/// those changes to version control.
64pub fn addBytesToSource(usf: *UpdateSourceFiles, bytes: []const u8, sub_path: []const u8) void {
65 const b = usf.step.owner;
66 usf.output_source_files.append(b.allocator, .{
67 .contents = .{ .bytes = bytes },
68 .sub_path = sub_path,
50/// Because it updates source files, this should not be used as part of the
51/// normal build process, but as a utility occasionally run by a developer with
52/// intent to modify source files and then commit those changes to version
53/// control.
54pub fn addBytesToSource(usf: *UpdateSourceFiles, contents: []const u8, sub_path: []const u8) void {
55 const graph = usf.step.owner.graph;
56 const wc = &graph.wip_configuration;
57 const arena = graph.arena;
58
59 usf.embeds.append(arena, .{
60 .sub_path = wc.addString(sub_path) catch @panic("OOM"),
61 .contents = wc.addBytes(contents) catch @panic("OOM"),
6962 }) catch @panic("OOM");
7063}
71
72fn make(step: *Step, options: Step.MakeOptions) !void {
73 _ = options;
74 const b = step.owner;
75 const io = b.graph.io;
76 const usf: *UpdateSourceFiles = @fieldParentPtr("step", step);
77
78 var any_miss = false;
79 for (usf.output_source_files.items) |output_source_file| {
80 if (fs.path.dirname(output_source_file.sub_path)) |dirname| {
81 b.build_root.handle.createDirPath(io, dirname) catch |err| {
82 return step.fail("unable to make path '{f}{s}': {t}", .{ b.build_root, dirname, err });
83 };
84 }
85 switch (output_source_file.contents) {
86 .bytes => |bytes| {
87 b.build_root.handle.writeFile(io, .{ .sub_path = output_source_file.sub_path, .data = bytes }) catch |err| {
88 return step.fail("unable to write file '{f}{s}': {t}", .{
89 b.build_root, output_source_file.sub_path, err,
90 });
91 };
92 any_miss = true;
93 },
94 .copy => |file_source| {
95 if (!step.inputs.populated()) try step.addWatchInput(file_source);
96
97 const source_path = file_source.getPath2(b, step);
98 const prev_status = Io.Dir.updateFile(
99 .cwd(),
100 io,
101 source_path,
102 b.build_root.handle,
103 output_source_file.sub_path,
104 .{},
105 ) catch |err| {
106 return step.fail("unable to update file from '{s}' to '{f}{s}': {t}", .{
107 source_path, b.build_root, output_source_file.sub_path, err,
108 });
109 };
110 any_miss = any_miss or prev_status == .stale;
111 },
112 }
113 }
114
115 step.result_cached = !any_miss;
116}
lib/std/Build/Step/WriteFile.zig+113-328
......@@ -4,21 +4,17 @@
44const WriteFile = @This();
55
66const std = @import("std");
7const Io = std.Io;
8const Dir = std.Io.Dir;
97const Step = std.Build.Step;
10const ArrayList = std.ArrayList;
11const assert = std.debug.assert;
8const Configuration = std.Build.Configuration;
129
1310step: Step,
14
15/// The elements here are pointers because we need stable pointers for the GeneratedFile field.
16files: std.ArrayList(File),
17directories: std.ArrayList(Directory),
18generated_directory: std.Build.GeneratedFile,
11embeds: std.ArrayList(Embed) = .empty,
12copies: std.ArrayList(Copy) = .empty,
13directories: std.ArrayList(Directory) = .empty,
14generated_directory: Configuration.GeneratedFileIndex,
1915mode: Mode = .whole_cached,
2016
21pub const base_id: Step.Id = .write_file;
17pub const base_tag: Step.Tag = .write_file;
2218
2319pub const Mode = union(enum) {
2420 /// Default mode. Integrates with the cache system. The directory should be
......@@ -37,363 +33,152 @@ pub const Mode = union(enum) {
3733 mutate: std.Build.LazyPath,
3834};
3935
40pub const File = struct {
41 sub_path: []const u8,
42 contents: Contents,
43};
44
45pub const Directory = struct {
46 source: std.Build.LazyPath,
47 sub_path: []const u8,
48 options: Options,
49
50 pub const Options = struct {
51 /// File paths that end in any of these suffixes will be excluded from copying.
52 exclude_extensions: []const []const u8 = &.{},
53 /// Only file paths that end in any of these suffixes will be included in copying.
54 /// `null` means that all suffixes will be included.
55 /// `exclude_extensions` takes precedence over `include_extensions`.
56 include_extensions: ?[]const []const u8 = null,
57
58 pub fn dupe(opts: Options, b: *std.Build) Options {
59 return .{
60 .exclude_extensions = b.dupeStrings(opts.exclude_extensions),
61 .include_extensions = if (opts.include_extensions) |incs| b.dupeStrings(incs) else null,
62 };
63 }
36pub const Embed = Configuration.Step.WriteFile.Embed;
6437
65 pub fn pathIncluded(opts: Options, path: []const u8) bool {
66 for (opts.exclude_extensions) |ext| {
67 if (std.mem.endsWith(u8, path, ext))
68 return false;
69 }
70 if (opts.include_extensions) |incs| {
71 for (incs) |inc| {
72 if (std.mem.endsWith(u8, path, inc))
73 return true;
74 } else {
75 return false;
76 }
77 }
78 return true;
79 }
80 };
38pub const Copy = struct {
39 sub_path: Configuration.String,
40 src_file: std.Build.LazyPath,
8141};
8242
83pub const Contents = union(enum) {
84 bytes: []const u8,
85 copy: std.Build.LazyPath,
43pub const Directory = struct {
44 sub_path: Configuration.String,
45 src_path: std.Build.LazyPath,
46 exclude_extensions: Configuration.OptionalStringList,
47 include_extensions: Configuration.OptionalStringList,
8648};
8749
8850pub fn create(owner: *std.Build) *WriteFile {
89 const write_file = owner.allocator.create(WriteFile) catch @panic("OOM");
90 write_file.* = .{
91 .step = Step.init(.{
92 .id = base_id,
51 const graph = owner.graph;
52 const wf = graph.create(WriteFile);
53 wf.* = .{
54 .step = .init(.{
55 .tag = base_tag,
9356 .name = "WriteFile",
9457 .owner = owner,
95 .makeFn = make,
9658 }),
97 .files = .empty,
98 .directories = .empty,
99 .generated_directory = .{ .step = &write_file.step },
59 .generated_directory = graph.addGeneratedFile(&wf.step),
10060 };
101 return write_file;
61 return wf;
10262}
10363
104pub fn add(write_file: *WriteFile, sub_path: []const u8, bytes: []const u8) std.Build.LazyPath {
105 const b = write_file.step.owner;
106 const gpa = b.allocator;
107 const file = File{
108 .sub_path = b.dupePath(sub_path),
109 .contents = .{ .bytes = b.dupe(bytes) },
110 };
111 write_file.files.append(gpa, file) catch @panic("OOM");
112 write_file.maybeUpdateName();
64/// Writes `contents` to a file at `sub_path` relative to the output
65/// directory.
66///
67/// `sub_path` may be a basename, or it may include subdirectories, which are
68/// created as needed.
69pub fn add(wf: *WriteFile, sub_path: []const u8, contents: []const u8) std.Build.LazyPath {
70 const graph = wf.step.owner.graph;
71 const wc = &graph.wip_configuration;
72 const arena = graph.arena;
73
74 wf.embeds.append(arena, .{
75 .sub_path = wc.addString(sub_path) catch @panic("OOM"),
76 .contents = wc.addBytes(contents) catch @panic("OOM"),
77 }) catch @panic("OOM");
78
79 wf.maybeUpdateName();
80
11381 return .{
11482 .generated = .{
115 .file = &write_file.generated_directory,
116 .sub_path = file.sub_path,
83 .index = wf.generated_directory,
84 .sub_path = graph.dupeString(sub_path),
11785 },
11886 };
11987}
12088
121/// Place the file into the generated directory within the local cache,
122/// along with all the rest of the files added to this step. The parameter
123/// here is the destination path relative to the local cache directory
124/// associated with this WriteFile. It may be a basename, or it may
125/// include sub-directories, in which case this step will ensure the
126/// required sub-path exists.
127/// This is the option expected to be used most commonly with `addCopyFile`.
128pub fn addCopyFile(write_file: *WriteFile, source: std.Build.LazyPath, sub_path: []const u8) std.Build.LazyPath {
129 const b = write_file.step.owner;
130 const gpa = b.allocator;
131 const file = File{
132 .sub_path = b.dupePath(sub_path),
133 .contents = .{ .copy = source },
134 };
135 write_file.files.append(gpa, file) catch @panic("OOM");
89/// Copies the provided file to `sub_path` relative to the output directory.
90///
91/// `sub_path` may be a basename, or it may include subdirectories, which are
92/// created as needed.
93pub fn addCopyFile(wf: *WriteFile, src_file: std.Build.LazyPath, sub_path: []const u8) std.Build.LazyPath {
94 const graph = wf.step.owner.graph;
95 const wc = &graph.wip_configuration;
96 const arena = graph.arena;
13697
137 write_file.maybeUpdateName();
138 source.addStepDependencies(&write_file.step);
139 return .{
140 .generated = .{
141 .file = &write_file.generated_directory,
142 .sub_path = file.sub_path,
143 },
144 };
98 wf.copies.append(arena, .{
99 .sub_path = wc.addString(sub_path) catch @panic("OOM"),
100 .src_file = src_file.dupe(graph),
101 }) catch @panic("OOM");
102
103 wf.maybeUpdateName();
104
105 src_file.addStepDependencies(&wf.step);
106
107 return .{ .generated = .{
108 .index = wf.generated_directory,
109 .sub_path = graph.dupePath(sub_path),
110 } };
145111}
146112
147/// Copy files matching the specified exclude/include patterns to the specified subdirectory
148/// relative to this step's generated directory.
113pub const CopyDirectoryOptions = struct {
114 /// File paths that end in any of these suffixes will be excluded from copying.
115 exclude_extensions: []const []const u8 = &.{},
116 /// Only file paths that end in any of these suffixes will be included in copying.
117 /// `null` means that all suffixes will be included.
118 /// `exclude_extensions` takes precedence over `include_extensions`.
119 include_extensions: ?[]const []const u8 = null,
120};
121
122/// Copy files matching the specified exclude/include patterns to the specified
123/// subdirectory relative to this step's generated directory.
124///
149125/// The returned value is a lazy path to the generated subdirectory.
150126pub fn addCopyDirectory(
151 write_file: *WriteFile,
152 source: std.Build.LazyPath,
127 wf: *WriteFile,
128 src_path: std.Build.LazyPath,
153129 sub_path: []const u8,
154 options: Directory.Options,
130 options: CopyDirectoryOptions,
155131) std.Build.LazyPath {
156 const b = write_file.step.owner;
157 const gpa = b.allocator;
158 const dir = Directory{
159 .source = source.dupe(b),
160 .sub_path = b.dupePath(sub_path),
161 .options = options.dupe(b),
162 };
163 write_file.directories.append(gpa, dir) catch @panic("OOM");
132 const graph = wf.step.owner.graph;
133 const wc = &graph.wip_configuration;
134 const arena = graph.arena;
135
136 wf.directories.append(arena, .{
137 .sub_path = wc.addString(sub_path) catch @panic("OOM"),
138 .src_path = src_path.dupe(graph),
139 .exclude_extensions = if (options.exclude_extensions.len != 0)
140 .init(wc.addStringList(options.exclude_extensions) catch @panic("OOM"))
141 else
142 .none,
143 .include_extensions = if (options.include_extensions) |list|
144 .init(wc.addStringList(list) catch @panic("OOM"))
145 else
146 .none,
147 }) catch @panic("OOM");
148
149 wf.maybeUpdateName();
150
151 src_path.addStepDependencies(&wf.step);
164152
165 write_file.maybeUpdateName();
166 source.addStepDependencies(&write_file.step);
167153 return .{
168154 .generated = .{
169 .file = &write_file.generated_directory,
170 .sub_path = dir.sub_path,
155 .index = wf.generated_directory,
156 .sub_path = graph.dupePath(sub_path),
171157 },
172158 };
173159}
174160
175161/// Returns a `LazyPath` representing the base directory that contains all the
176162/// files from this `WriteFile`.
177pub fn getDirectory(write_file: *WriteFile) std.Build.LazyPath {
178 return .{ .generated = .{ .file = &write_file.generated_directory } };
163pub fn getDirectory(wf: *WriteFile) std.Build.LazyPath {
164 return .{ .generated = .{ .index = wf.generated_directory } };
179165}
180166
181fn maybeUpdateName(write_file: *WriteFile) void {
182 if (write_file.files.items.len == 1 and write_file.directories.items.len == 0) {
167fn maybeUpdateName(wf: *WriteFile) void {
168 const graph = wf.step.owner.graph;
169 const wc = &graph.wip_configuration;
170 const files_count = wf.embeds.items.len + wf.copies.items.len;
171 if (files_count == 1 and wf.directories.items.len == 0) {
183172 // First time adding a file; update name.
184 if (std.mem.eql(u8, write_file.step.name, "WriteFile")) {
185 write_file.step.name = write_file.step.owner.fmt("WriteFile {s}", .{write_file.files.items[0].sub_path});
173 const sub_path = if (wf.embeds.items.len == 1) wf.embeds.items[0].sub_path else wf.copies.items[0].sub_path;
174 if (std.mem.eql(u8, wf.step.name, "WriteFile")) {
175 wf.step.name = wf.step.owner.fmt("WriteFile {s}", .{wc.stringSlice(sub_path)});
186176 }
187 } else if (write_file.directories.items.len == 1 and write_file.files.items.len == 0) {
177 } else if (wf.directories.items.len == 1 and files_count == 0) {
188178 // First time adding a directory; update name.
189 if (std.mem.eql(u8, write_file.step.name, "WriteFile")) {
190 write_file.step.name = write_file.step.owner.fmt("WriteFile {s}", .{write_file.directories.items[0].sub_path});
191 }
192 }
193}
194
195fn make(step: *Step, options: Step.MakeOptions) !void {
196 _ = options;
197 const b = step.owner;
198 const graph = b.graph;
199 const io = graph.io;
200 const arena = b.allocator;
201 const gpa = graph.cache.gpa;
202 const write_file: *WriteFile = @fieldParentPtr("step", step);
203
204 const open_dir_cache = try arena.alloc(Io.Dir, write_file.directories.items.len);
205 var open_dirs_count: usize = 0;
206 defer Io.Dir.closeMany(io, open_dir_cache[0..open_dirs_count]);
207
208 switch (write_file.mode) {
209 .whole_cached => {
210 step.clearWatchInputs();
211
212 // The cache is used here not really as a way to speed things up - because writing
213 // the data to a file would probably be very fast - but as a way to find a canonical
214 // location to put build artifacts.
215
216 // If, for example, a hard-coded path was used as the location to put WriteFile
217 // files, then two WriteFiles executing in parallel might clobber each other.
218
219 var man = b.graph.cache.obtain();
220 defer man.deinit();
221
222 for (write_file.files.items) |file| {
223 man.hash.addBytes(file.sub_path);
224
225 switch (file.contents) {
226 .bytes => |bytes| {
227 man.hash.addBytes(bytes);
228 },
229 .copy => |lazy_path| {
230 const path = lazy_path.getPath3(b, step);
231 _ = try man.addFilePath(path, null);
232 try step.addWatchInput(lazy_path);
233 },
234 }
235 }
236
237 for (write_file.directories.items, open_dir_cache) |dir, *open_dir_cache_elem| {
238 man.hash.addBytes(dir.sub_path);
239 for (dir.options.exclude_extensions) |ext| man.hash.addBytes(ext);
240 if (dir.options.include_extensions) |incs| for (incs) |inc| man.hash.addBytes(inc);
241
242 const need_derived_inputs = try step.addDirectoryWatchInput(dir.source);
243 const src_dir_path = dir.source.getPath3(b, step);
244
245 var src_dir = src_dir_path.root_dir.handle.openDir(io, src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| {
246 return step.fail("unable to open source directory '{f}': {s}", .{
247 src_dir_path, @errorName(err),
248 });
249 };
250 open_dir_cache_elem.* = src_dir;
251 open_dirs_count += 1;
252
253 var it = try src_dir.walk(gpa);
254 defer it.deinit();
255 while (try it.next(io)) |entry| {
256 if (!dir.options.pathIncluded(entry.path)) continue;
257
258 switch (entry.kind) {
259 .directory => {
260 if (need_derived_inputs) {
261 const entry_path = try src_dir_path.join(arena, entry.path);
262 try step.addDirectoryWatchInputFromPath(entry_path);
263 }
264 },
265 .file => {
266 const entry_path = try src_dir_path.join(arena, entry.path);
267 _ = try man.addFilePath(entry_path, null);
268 },
269 else => continue,
270 }
271 }
272 }
273
274 if (try step.cacheHit(&man)) {
275 const digest = man.final();
276 write_file.generated_directory.path = try b.cache_root.join(arena, &.{ "o", &digest });
277 assert(step.result_cached);
278 return;
279 }
280
281 const digest = man.final();
282 const cache_path = "o" ++ Dir.path.sep_str ++ digest;
283
284 write_file.generated_directory.path = try b.cache_root.join(arena, &.{cache_path});
285
286 try operate(write_file, open_dir_cache, .{
287 .root_dir = b.cache_root,
288 .sub_path = cache_path,
289 });
290
291 try step.writeManifest(&man);
292 },
293 .tmp => {
294 step.result_cached = false;
295
296 var rand_int: u64 = undefined;
297 io.random(@ptrCast(&rand_int));
298 const tmp_dir_sub_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);
299
300 write_file.generated_directory.path = try b.cache_root.join(arena, &.{tmp_dir_sub_path});
301
302 try operate(write_file, open_dir_cache, .{
303 .root_dir = b.cache_root,
304 .sub_path = tmp_dir_sub_path,
305 });
306 },
307 .mutate => |lp| {
308 step.result_cached = false;
309 const root_path = try lp.getPath4(b, step);
310 write_file.generated_directory.path = try root_path.toString(arena);
311 try operate(write_file, open_dir_cache, root_path);
312 },
313 }
314}
315
316fn operate(write_file: *WriteFile, open_dir_cache: []const Io.Dir, root_path: std.Build.Cache.Path) !void {
317 const step = &write_file.step;
318 const b = step.owner;
319 const io = b.graph.io;
320 const gpa = b.graph.cache.gpa;
321 const arena = b.allocator;
322
323 var cache_dir = root_path.root_dir.handle.createDirPathOpen(io, root_path.sub_path, .{}) catch |err|
324 return step.fail("unable to make path {f}: {t}", .{ root_path, err });
325 defer cache_dir.close(io);
326
327 for (write_file.files.items) |file| {
328 if (Dir.path.dirname(file.sub_path)) |dirname| {
329 cache_dir.createDirPath(io, dirname) catch |err| {
330 return step.fail("unable to make path '{f}{c}{s}': {t}", .{
331 root_path, Dir.path.sep, dirname, err,
332 });
333 };
334 }
335 switch (file.contents) {
336 .bytes => |bytes| {
337 cache_dir.writeFile(io, .{ .sub_path = file.sub_path, .data = bytes }) catch |err| {
338 return step.fail("unable to write file '{f}{c}{s}': {t}", .{
339 root_path, Dir.path.sep, file.sub_path, err,
340 });
341 };
342 },
343 .copy => |file_source| {
344 const source_path = file_source.getPath2(b, step);
345 const prev_status = Io.Dir.updateFile(.cwd(), io, source_path, cache_dir, file.sub_path, .{}) catch |err| {
346 return step.fail("unable to update file from '{s}' to '{f}{c}{s}': {t}", .{
347 source_path, root_path, Dir.path.sep, file.sub_path, err,
348 });
349 };
350 // At this point we already will mark the step as a cache miss.
351 // But this is kind of a partial cache hit since individual
352 // file copies may be avoided. Oh well, this information is
353 // discarded.
354 _ = prev_status;
355 },
356 }
357 }
358
359 for (write_file.directories.items, open_dir_cache) |dir, already_open_dir| {
360 const src_dir_path = dir.source.getPath3(b, step);
361 const dest_dirname = dir.sub_path;
362
363 if (dest_dirname.len != 0) {
364 cache_dir.createDirPath(io, dest_dirname) catch |err| {
365 return step.fail("unable to make path '{f}{c}{s}': {t}", .{
366 root_path, Dir.path.sep, dest_dirname, err,
367 });
368 };
369 }
370
371 var it = try already_open_dir.walk(gpa);
372 defer it.deinit();
373 while (try it.next(io)) |entry| {
374 if (!dir.options.pathIncluded(entry.path)) continue;
375
376 const src_entry_path = try src_dir_path.join(arena, entry.path);
377 const dest_path = b.pathJoin(&.{ dest_dirname, entry.path });
378 switch (entry.kind) {
379 .directory => try cache_dir.createDirPath(io, dest_path),
380 .file => {
381 const prev_status = Io.Dir.updateFile(
382 src_entry_path.root_dir.handle,
383 io,
384 src_entry_path.sub_path,
385 cache_dir,
386 dest_path,
387 .{},
388 ) catch |err| {
389 return step.fail("unable to update file from '{f}' to '{f}{c}{s}': {t}", .{
390 src_entry_path, root_path, Dir.path.sep, dest_path, err,
391 });
392 };
393 _ = prev_status;
394 },
395 else => continue,
396 }
179 const dir_name = wc.stringSlice(wf.directories.items[0].sub_path);
180 if (std.mem.eql(u8, wf.step.name, "WriteFile")) {
181 wf.step.name = wf.step.owner.fmt("WriteFile {s}", .{dir_name});
397182 }
398183 }
399184}
lib/std/Build/Watch.zig deleted-968
......@@ -1,968 +0,0 @@
1const builtin = @import("builtin");
2
3const std = @import("../std.zig");
4const Io = std.Io;
5const Step = std.Build.Step;
6const Allocator = std.mem.Allocator;
7const assert = std.debug.assert;
8const fatal = std.process.fatal;
9const Watch = @This();
10const FsEvents = @import("Watch/FsEvents.zig");
11
12os: Os,
13/// The number to show as the number of directories being watched.
14dir_count: usize,
15// These fields are common to most implementations so are kept here for simplicity.
16// They are `undefined` on implementations which do not utilize then.
17dir_table: DirTable,
18generation: Generation,
19
20pub const have_impl = Os != void;
21
22/// Key is the directory to watch which contains one or more files we are
23/// interested in noticing changes to.
24///
25/// Value is generation.
26const DirTable = std.ArrayHashMapUnmanaged(Cache.Path, void, Cache.Path.TableAdapter, false);
27
28/// Special key of "." means any changes in this directory trigger the steps.
29const ReactionSet = std.StringArrayHashMapUnmanaged(StepSet);
30const StepSet = std.AutoArrayHashMapUnmanaged(*Step, Generation);
31
32const Generation = u8;
33
34const Hash = std.hash.Wyhash;
35const Cache = std.Build.Cache;
36
37const Os = switch (builtin.os.tag) {
38 .linux => struct {
39 const posix = std.posix;
40
41 /// Keyed differently but indexes correspond 1:1 with `dir_table`.
42 handle_table: HandleTable,
43 /// fanotify file descriptors are keyed by mount id since marks
44 /// are limited to a single filesystem.
45 poll_fds: std.AutoArrayHashMapUnmanaged(MountId, posix.pollfd),
46
47 const MountId = i32;
48 const HandleTable = std.ArrayHashMapUnmanaged(FileHandle, struct { mount_id: MountId, reaction_set: ReactionSet }, FileHandle.Adapter, false);
49
50 const fan_mask: std.os.linux.fanotify.MarkMask = .{
51 .CLOSE_WRITE = true,
52 .CREATE = true,
53 .DELETE = true,
54 .DELETE_SELF = true,
55 .EVENT_ON_CHILD = true,
56 .MOVED_FROM = true,
57 .MOVED_TO = true,
58 .MOVE_SELF = true,
59 .ONDIR = true,
60 };
61
62 const FileHandle = struct {
63 handle: *align(1) std.os.linux.file_handle,
64
65 fn clone(lfh: FileHandle, gpa: Allocator) Allocator.Error!FileHandle {
66 const bytes = lfh.slice();
67 const new_ptr = try gpa.alignedAlloc(
68 u8,
69 .of(std.os.linux.file_handle),
70 @sizeOf(std.os.linux.file_handle) + bytes.len,
71 );
72 const new_header: *std.os.linux.file_handle = @ptrCast(new_ptr);
73 new_header.* = lfh.handle.*;
74 const new: FileHandle = .{ .handle = new_header };
75 @memcpy(new.slice(), lfh.slice());
76 return new;
77 }
78
79 fn destroy(lfh: FileHandle, gpa: Allocator) void {
80 const ptr: [*]u8 = @ptrCast(lfh.handle);
81 const allocated_slice = ptr[0 .. @sizeOf(std.os.linux.file_handle) + lfh.handle.handle_bytes];
82 return gpa.free(allocated_slice);
83 }
84
85 fn slice(lfh: FileHandle) []u8 {
86 const ptr: [*]u8 = &lfh.handle.f_handle;
87 return ptr[0..lfh.handle.handle_bytes];
88 }
89
90 const Adapter = struct {
91 pub fn hash(self: Adapter, a: FileHandle) u32 {
92 _ = self;
93 const unsigned_type: u32 = @bitCast(a.handle.handle_type);
94 return @truncate(Hash.hash(unsigned_type, a.slice()));
95 }
96 pub fn eql(self: Adapter, a: FileHandle, b: FileHandle, b_index: usize) bool {
97 _ = self;
98 _ = b_index;
99 return a.handle.handle_type == b.handle.handle_type and std.mem.eql(u8, a.slice(), b.slice());
100 }
101 };
102 };
103
104 fn init(cwd_path: []const u8) !Watch {
105 _ = cwd_path;
106 return .{
107 .dir_table = .{},
108 .dir_count = 0,
109 .os = switch (builtin.os.tag) {
110 .linux => .{
111 .handle_table = .{},
112 .poll_fds = .{},
113 },
114 else => {},
115 },
116 .generation = 0,
117 };
118 }
119
120 fn getDirHandle(gpa: Allocator, path: std.Build.Cache.Path, mount_id: *MountId) !FileHandle {
121 var file_handle_buffer: [@sizeOf(std.os.linux.file_handle) + 128]u8 align(@alignOf(std.os.linux.file_handle)) = undefined;
122 var buf: [std.fs.max_path_bytes]u8 = undefined;
123 const adjusted_path = if (path.sub_path.len == 0) "./" else std.fmt.bufPrint(&buf, "{s}/", .{
124 path.sub_path,
125 }) catch return error.NameTooLong;
126 const stack_ptr: *std.os.linux.file_handle = @ptrCast(&file_handle_buffer);
127 stack_ptr.handle_bytes = file_handle_buffer.len - @sizeOf(std.os.linux.file_handle);
128 try posix.name_to_handle_at(path.root_dir.handle.handle, adjusted_path, stack_ptr, mount_id, std.os.linux.AT.HANDLE_FID);
129 const stack_lfh: FileHandle = .{ .handle = stack_ptr };
130 return stack_lfh.clone(gpa);
131 }
132
133 fn markDirtySteps(w: *Watch, gpa: Allocator, fan_fd: posix.fd_t) !bool {
134 const fanotify = std.os.linux.fanotify;
135 const M = fanotify.event_metadata;
136 var events_buf: [256 + 4096]u8 = undefined;
137 var any_dirty = false;
138 while (true) {
139 var len = posix.read(fan_fd, &events_buf) catch |err| switch (err) {
140 error.WouldBlock => return any_dirty,
141 else => |e| return e,
142 };
143 var meta: [*]align(1) M = @ptrCast(&events_buf);
144 while (len >= @sizeOf(M) and meta[0].event_len >= @sizeOf(M) and meta[0].event_len <= len) : ({
145 len -= meta[0].event_len;
146 meta = @ptrCast(@as([*]u8, @ptrCast(meta)) + meta[0].event_len);
147 }) {
148 assert(meta[0].vers == M.VERSION);
149 if (meta[0].mask.Q_OVERFLOW) {
150 any_dirty = true;
151 std.log.warn("file system watch queue overflowed; falling back to fstat", .{});
152 markAllFilesDirty(w, gpa);
153 return true;
154 }
155 const fid: *align(1) fanotify.event_info_fid = @ptrCast(meta + 1);
156 switch (fid.hdr.info_type) {
157 .DFID_NAME => {
158 const file_handle: *align(1) std.os.linux.file_handle = @ptrCast(&fid.handle);
159 const file_name_z: [*:0]u8 = @ptrCast((&file_handle.f_handle).ptr + file_handle.handle_bytes);
160 const file_name = std.mem.span(file_name_z);
161 const lfh: FileHandle = .{ .handle = file_handle };
162 if (w.os.handle_table.getPtr(lfh)) |value| {
163 if (value.reaction_set.getPtr(".")) |glob_set|
164 any_dirty = markStepSetDirty(gpa, glob_set, any_dirty);
165 if (value.reaction_set.getPtr(file_name)) |step_set|
166 any_dirty = markStepSetDirty(gpa, step_set, any_dirty);
167 }
168 },
169 else => |t| std.log.warn("unexpected fanotify event '{s}'", .{@tagName(t)}),
170 }
171 }
172 }
173 }
174
175 fn update(w: *Watch, gpa: Allocator, steps: []const *Step) !void {
176 // Add missing marks and note persisted ones.
177 for (steps) |step| {
178 for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| {
179 const reaction_set = rs: {
180 const gop = try w.dir_table.getOrPut(gpa, path);
181 if (!gop.found_existing) {
182 var mount_id: MountId = undefined;
183 const dir_handle = getDirHandle(gpa, path, &mount_id) catch |err| switch (err) {
184 error.FileNotFound => {
185 std.debug.assert(w.dir_table.swapRemove(path));
186 continue;
187 },
188 else => return err,
189 };
190 const fan_fd = blk: {
191 const fd_gop = try w.os.poll_fds.getOrPut(gpa, mount_id);
192 if (!fd_gop.found_existing) {
193 const fan_fd = std.posix.fanotify_init(.{
194 .CLASS = .NOTIF,
195 .CLOEXEC = true,
196 .NONBLOCK = true,
197 .REPORT_NAME = true,
198 .REPORT_DIR_FID = true,
199 .REPORT_FID = true,
200 .REPORT_TARGET_FID = true,
201 }, 0) catch |err| switch (err) {
202 error.UnsupportedFlags => fatal("fanotify_init failed due to old kernel; requires 5.17+", .{}),
203 else => |e| return e,
204 };
205 fd_gop.value_ptr.* = .{
206 .fd = fan_fd,
207 .events = std.posix.POLL.IN,
208 .revents = undefined,
209 };
210 }
211 break :blk fd_gop.value_ptr.*.fd;
212 };
213 // `dir_handle` may already be present in the table in
214 // the case that we have multiple Cache.Path instances
215 // that compare inequal but ultimately point to the same
216 // directory on the file system.
217 // In such case, we must revert adding this directory, but keep
218 // the additions to the step set.
219 const dh_gop = try w.os.handle_table.getOrPut(gpa, dir_handle);
220 if (dh_gop.found_existing) {
221 _ = w.dir_table.pop();
222 } else {
223 assert(dh_gop.index == gop.index);
224 dh_gop.value_ptr.* = .{ .mount_id = mount_id, .reaction_set = .{} };
225 posix.fanotify_mark(fan_fd, .{
226 .ADD = true,
227 .ONLYDIR = true,
228 }, fan_mask, path.root_dir.handle.handle, path.subPathOrDot()) catch |err| {
229 fatal("unable to watch {f}: {s}", .{ path, @errorName(err) });
230 };
231 }
232 break :rs &dh_gop.value_ptr.reaction_set;
233 }
234 break :rs &w.os.handle_table.values()[gop.index].reaction_set;
235 };
236 for (files.items) |basename| {
237 const gop = try reaction_set.getOrPut(gpa, basename);
238 if (!gop.found_existing) gop.value_ptr.* = .{};
239 try gop.value_ptr.put(gpa, step, w.generation);
240 }
241 }
242 }
243
244 {
245 // Remove marks for files that are no longer inputs.
246 var i: usize = 0;
247 while (i < w.os.handle_table.entries.len) {
248 {
249 const reaction_set = &w.os.handle_table.values()[i].reaction_set;
250 var step_set_i: usize = 0;
251 while (step_set_i < reaction_set.entries.len) {
252 const step_set = &reaction_set.values()[step_set_i];
253 var dirent_i: usize = 0;
254 while (dirent_i < step_set.entries.len) {
255 const generations = step_set.values();
256 if (generations[dirent_i] == w.generation) {
257 dirent_i += 1;
258 continue;
259 }
260 step_set.swapRemoveAt(dirent_i);
261 }
262 if (step_set.entries.len > 0) {
263 step_set_i += 1;
264 continue;
265 }
266 reaction_set.swapRemoveAt(step_set_i);
267 }
268 if (reaction_set.entries.len > 0) {
269 i += 1;
270 continue;
271 }
272 }
273
274 const path = w.dir_table.keys()[i];
275
276 const mount_id = w.os.handle_table.values()[i].mount_id;
277 const fan_fd = w.os.poll_fds.getEntry(mount_id).?.value_ptr.fd;
278 posix.fanotify_mark(fan_fd, .{
279 .REMOVE = true,
280 .ONLYDIR = true,
281 }, fan_mask, path.root_dir.handle.handle, path.subPathOrDot()) catch |err| switch (err) {
282 error.FileNotFound => {}, // Expected, harmless.
283 else => |e| std.log.warn("unable to unwatch '{f}': {s}", .{ path, @errorName(e) }),
284 };
285
286 w.dir_table.swapRemoveAt(i);
287 w.os.handle_table.swapRemoveAt(i);
288 }
289 w.generation +%= 1;
290 }
291 w.dir_count = w.dir_table.count();
292 }
293
294 fn wait(w: *Watch, gpa: Allocator, io: Io, timeout: Timeout) !WaitResult {
295 _ = io;
296 const events_len = try std.posix.poll(w.os.poll_fds.values(), timeout.to_i32_ms());
297 if (events_len == 0)
298 return .timeout;
299 for (w.os.poll_fds.values()) |poll_fd| {
300 if (poll_fd.revents & std.posix.POLL.IN == std.posix.POLL.IN and try markDirtySteps(w, gpa, poll_fd.fd))
301 return .dirty;
302 }
303 return .clean;
304 }
305 },
306 .windows => struct {
307 const windows = std.os.windows;
308
309 /// Keyed differently but indexes correspond 1:1 with `dir_table`.
310 handle_table: std.ArrayHashMapUnmanaged(*Directory, void, Directory.TableAdapter, false),
311 ready_dirs: std.DoublyLinkedList,
312
313 const FileId = struct {
314 volumeSerialNumber: windows.ULONG,
315 indexNumber: windows.LARGE_INTEGER,
316 };
317
318 const Directory = struct {
319 reaction_set: ReactionSet,
320 id: FileId,
321 file: Io.File,
322 state: enum { idle, listening, ready },
323 iosb: windows.IO_STATUS_BLOCK,
324 // 64 KB is the packet size limit when monitoring over a network.
325 // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-readdirectorychangesw#remarks
326 buffer: [64 * 1024]u8 align(@alignOf(windows.FILE.NOTIFY.INFORMATION)),
327 ready_node: std.DoublyLinkedList.Node,
328
329 /// Start listening for events, buffer field will be overwritten eventually.
330 fn startListening(dir: *Directory, w: *Watch) !void {
331 assert(dir.file.flags.nonblocking);
332 assert(dir.state == .idle);
333 switch (windows.ntdll.NtNotifyChangeDirectoryFileEx(
334 dir.file.handle,
335 null,
336 &notifyApc,
337 w,
338 &dir.iosb,
339 &dir.buffer,
340 dir.buffer.len,
341 .{
342 .FILE_NAME = true,
343 .DIR_NAME = true,
344 .SIZE = true,
345 .LAST_WRITE = true,
346 .CREATION = true,
347 },
348 .FALSE,
349 .Notify,
350 )) {
351 .SUCCESS, .PENDING => dir.state = .listening,
352 .ILLEGAL_FUNCTION => return error.ReadDirectoryChangesUnsupported,
353 else => |status| return windows.unexpectedStatus(status),
354 }
355 }
356
357 fn notifyApc(apc_context: ?*anyopaque, iosb: *windows.IO_STATUS_BLOCK, _: windows.ULONG) align(std.Io.Threaded.apc_align) callconv(.winapi) void {
358 const w: *Watch = @ptrCast(@alignCast(apc_context));
359 const dir: *Directory = @fieldParentPtr("iosb", iosb);
360 assert(iosb.u.Status != .PENDING);
361 assert(dir.state == .listening);
362 w.os.ready_dirs.append(&dir.ready_node);
363 dir.state = .ready;
364 }
365
366 fn init(gpa: Allocator, path: Cache.Path) !*Directory {
367 // The following code is a drawn out NtCreateFile call. (mostly adapted from Io.Dir.makeOpenDirAccessMaskW)
368 // It's necessary in order to get the specific flags that are required when calling ReadDirectoryChangesW.
369 var dir_handle: windows.HANDLE = undefined;
370 const root_fd = path.root_dir.handle.handle;
371 const sub_path = path.subPathOrDot();
372 const sub_path_w = try Io.Threaded.sliceToPrefixedFileW(root_fd, sub_path, .{}); // TODO eliminate this call
373 var iosb: windows.IO_STATUS_BLOCK = undefined;
374 switch (windows.ntdll.NtCreateFile(
375 &dir_handle,
376 .{
377 .SPECIFIC = .{ .FILE_DIRECTORY = .{
378 .LIST = true,
379 } },
380 .STANDARD = .{ .SYNCHRONIZE = true },
381 .GENERIC = .{ .READ = true },
382 },
383 &.{
384 .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w.span())) null else root_fd,
385 .ObjectName = @constCast(&sub_path_w.string()),
386 },
387 &iosb,
388 null,
389 .{},
390 .VALID_FLAGS,
391 .OPEN,
392 .{
393 .DIRECTORY_FILE = true,
394 .IO = .ASYNCHRONOUS,
395 .OPEN_FOR_BACKUP_INTENT = true,
396 },
397 null,
398 0,
399 )) {
400 .SUCCESS => {},
401 .OBJECT_NAME_INVALID => return error.BadPathName,
402 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
403 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
404 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
405 .NOT_A_DIRECTORY => return error.NotDir,
406 // This can happen if the directory has 'List folder contents' permission set to 'Deny'
407 .ACCESS_DENIED => return error.AccessDenied,
408 .INVALID_PARAMETER => unreachable,
409 else => |rc| return windows.unexpectedStatus(rc),
410 }
411 assert(dir_handle != windows.INVALID_HANDLE_VALUE);
412 errdefer windows.CloseHandle(dir_handle);
413
414 const dir_id = try getFileId(dir_handle);
415
416 const dir = try gpa.create(Directory);
417 dir.* = .{
418 .reaction_set = .empty,
419 .id = dir_id,
420 .file = .{ .handle = dir_handle, .flags = .{ .nonblocking = true } },
421 .state = .idle,
422 .iosb = undefined,
423 .buffer = undefined,
424 .ready_node = undefined,
425 };
426 return dir;
427 }
428
429 fn deinit(dir: *Directory, gpa: Allocator, w: *Watch) void {
430 state: switch (dir.state) {
431 .idle => {},
432 .listening => {
433 var cancel_iosb: windows.IO_STATUS_BLOCK = undefined;
434 _ = windows.ntdll.NtCancelIoFileEx(dir.file.handle, &dir.iosb, &cancel_iosb);
435 while (switch (dir.state) {
436 .idle => unreachable,
437 .listening => true,
438 .ready => false,
439 }) Io.Threaded.waitForApcOrAlert();
440 continue :state .ready;
441 },
442 .ready => w.os.ready_dirs.remove(&dir.ready_node),
443 }
444 windows.CloseHandle(dir.file.handle);
445 gpa.destroy(dir);
446 }
447
448 /// Useful to make `*Directory` a key in `std.ArrayHashMap`.
449 const TableAdapter = struct {
450 pub fn hash(_: TableAdapter, lhs_dir: *Directory) u32 {
451 return @truncate(Hash.hash(lhs_dir.id.volumeSerialNumber, @ptrCast(&lhs_dir.id.indexNumber)));
452 }
453 pub fn eql(_: TableAdapter, lhs_dir: *Directory, rhs_dir: *Directory, rhs_index: usize) bool {
454 _ = rhs_index;
455 return lhs_dir.id.volumeSerialNumber == rhs_dir.id.volumeSerialNumber and
456 lhs_dir.id.indexNumber == rhs_dir.id.indexNumber;
457 }
458 };
459 };
460
461 fn init(cwd_path: []const u8) !Watch {
462 _ = cwd_path;
463 return .{
464 .dir_table = .{},
465 .dir_count = 0,
466 .os = switch (builtin.os.tag) {
467 .windows => .{
468 .handle_table = .empty,
469 .ready_dirs = .{},
470 },
471 else => {},
472 },
473 .generation = 0,
474 };
475 }
476
477 fn getFileId(handle: windows.HANDLE) !FileId {
478 var file_id: FileId = undefined;
479 var io_status: windows.IO_STATUS_BLOCK = undefined;
480 var volume_info: windows.FILE.FS_VOLUME_INFORMATION = undefined;
481 switch (windows.ntdll.NtQueryVolumeInformationFile(
482 handle,
483 &io_status,
484 &volume_info,
485 @sizeOf(windows.FILE.FS_VOLUME_INFORMATION),
486 .Volume,
487 )) {
488 .SUCCESS => {},
489 // Buffer overflow here indicates that there is more information available than was able to be stored in the buffer
490 // size provided. This is treated as success because the type of variable-length information that this would be relevant for
491 // (name, volume name, etc) we don't care about.
492 .BUFFER_OVERFLOW => {},
493 else => |rc| return windows.unexpectedStatus(rc),
494 }
495 file_id.volumeSerialNumber = volume_info.VolumeSerialNumber;
496 var internal_info: windows.FILE.INTERNAL_INFORMATION = undefined;
497 switch (windows.ntdll.NtQueryInformationFile(
498 handle,
499 &io_status,
500 &internal_info,
501 @sizeOf(windows.FILE.INTERNAL_INFORMATION),
502 .Internal,
503 )) {
504 .SUCCESS => {},
505 else => |rc| return windows.unexpectedStatus(rc),
506 }
507 file_id.indexNumber = internal_info.IndexNumber;
508 return file_id;
509 }
510
511 fn markDirtySteps(w: *Watch, gpa: Allocator, dir: *Directory) !bool {
512 var any_dirty = false;
513 const bytes_returned = dir.iosb.Information;
514 if (bytes_returned == 0) {
515 std.log.warn("file system watch queue overflowed; falling back to fstat", .{});
516 markAllFilesDirty(w, gpa);
517 try dir.startListening(w);
518 return true;
519 }
520 var file_name_buf: [std.fs.max_path_bytes]u8 = undefined;
521 var offset: usize = 0;
522 while (true) {
523 const notify: *windows.FILE.NOTIFY.INFORMATION = @ptrCast(@alignCast(&dir.buffer[offset]));
524 const file_name = file_name_buf[0..std.unicode.wtf16LeToWtf8(&file_name_buf, notify.fileName())];
525 if (dir.reaction_set.getPtr(".")) |glob_set|
526 any_dirty = markStepSetDirty(gpa, glob_set, any_dirty);
527 if (dir.reaction_set.getPtr(file_name)) |step_set|
528 any_dirty = markStepSetDirty(gpa, step_set, any_dirty);
529 if (notify.NextEntryOffset == 0)
530 break;
531
532 offset += notify.NextEntryOffset;
533 }
534
535 // We call this now since at this point we have finished reading dir.buffer.
536 try dir.startListening(w);
537 return any_dirty;
538 }
539
540 fn update(w: *Watch, gpa: Allocator, steps: []const *Step) !void {
541 // Add missing marks and note persisted ones.
542 for (steps) |step| {
543 for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| {
544 const dir = dir: {
545 const gop = try w.dir_table.getOrPut(gpa, path);
546 if (!gop.found_existing) {
547 const dir: *Directory = try .init(gpa, path);
548 errdefer dir.deinit(gpa, w);
549 // `dir.id` may already be present in the table in
550 // the case that we have multiple Cache.Path instances
551 // that compare inequal but ultimately point to the same
552 // directory on the file system.
553 // In such case, we must revert adding this directory, but keep
554 // the additions to the step set.
555 const dh_gop = try w.os.handle_table.getOrPut(gpa, dir);
556 if (dh_gop.found_existing) {
557 dir.deinit(gpa, w);
558 _ = w.dir_table.pop();
559 break :dir w.os.handle_table.keys()[dh_gop.index];
560 } else {
561 assert(dh_gop.index == gop.index);
562 try dir.startListening(w);
563 break :dir dir;
564 }
565 }
566 break :dir w.os.handle_table.keys()[gop.index];
567 };
568 for (files.items) |basename| {
569 const gop = try dir.reaction_set.getOrPut(gpa, basename);
570 if (!gop.found_existing) gop.value_ptr.* = .{};
571 try gop.value_ptr.put(gpa, step, w.generation);
572 }
573 }
574 }
575
576 {
577 // Remove marks for files that are no longer inputs.
578 var i: usize = 0;
579 while (i < w.os.handle_table.entries.len) {
580 const dir = w.os.handle_table.keys()[i];
581 {
582 var step_set_i: usize = 0;
583 while (step_set_i < dir.reaction_set.entries.len) {
584 const step_set = &dir.reaction_set.values()[step_set_i];
585 var dirent_i: usize = 0;
586 while (dirent_i < step_set.entries.len) {
587 const generations = step_set.values();
588 if (generations[dirent_i] == w.generation) {
589 dirent_i += 1;
590 continue;
591 }
592 step_set.swapRemoveAt(dirent_i);
593 }
594 if (step_set.entries.len > 0) {
595 step_set_i += 1;
596 continue;
597 }
598 dir.reaction_set.swapRemoveAt(step_set_i);
599 }
600 if (dir.reaction_set.entries.len > 0) {
601 i += 1;
602 continue;
603 }
604 }
605
606 w.dir_table.swapRemoveAt(i);
607 w.os.handle_table.swapRemoveAt(i);
608 dir.deinit(gpa, w);
609 }
610 w.generation +%= 1;
611 }
612 w.dir_count = w.dir_table.count();
613 }
614
615 fn wait(w: *Watch, gpa: Allocator, io: Io, timeout: Timeout) !WaitResult {
616 for (0..2) |attempt| {
617 while (w.os.ready_dirs.popFirst()) |ready_node| {
618 const dir: *Directory = @fieldParentPtr("ready_node", ready_node);
619 assert(dir.state == .ready);
620 dir.state = .idle;
621 switch (dir.iosb.u.Status) {
622 .SUCCESS => return if (try markDirtySteps(w, gpa, dir)) .dirty else .clean,
623 .PENDING => unreachable,
624 .CANCELLED => {},
625 else => |status| return windows.unexpectedStatus(status),
626 }
627 try dir.startListening(w);
628 }
629 try io.checkCancel();
630 if (attempt == 1) return .timeout;
631 const delay_interval: windows.LARGE_INTEGER = switch (timeout) {
632 .none => std.math.minInt(windows.LARGE_INTEGER),
633 .ms => |ms| -@as(windows.LARGE_INTEGER, ms) * (std.time.ns_per_ms / 100),
634 };
635 _ = windows.ntdll.NtDelayExecution(.TRUE, &delay_interval);
636 } else unreachable;
637 }
638 },
639 .dragonfly, .freebsd, .netbsd, .openbsd, .ios, .tvos, .visionos, .watchos => struct {
640 const posix = std.posix;
641
642 kq_fd: i32,
643 /// Indexes correspond 1:1 with `dir_table`.
644 handles: std.MultiArrayList(struct {
645 rs: ReactionSet,
646 /// If the corresponding dir_table Path has sub_path == "", then it
647 /// suffices as the open directory handle, and this value will be
648 /// -1. Otherwise, it needs to be opened in update(), and will be
649 /// stored here.
650 dir_fd: i32,
651 }),
652
653 const dir_open_flags: posix.O = f: {
654 var f: posix.O = .{
655 .ACCMODE = .RDONLY,
656 .NOFOLLOW = false,
657 .DIRECTORY = true,
658 .CLOEXEC = true,
659 };
660 if (@hasField(posix.O, "EVTONLY")) f.EVTONLY = true;
661 if (@hasField(posix.O, "PATH")) f.PATH = true;
662 break :f f;
663 };
664
665 const EV = std.c.EV;
666 const NOTE = std.c.NOTE;
667
668 fn init(cwd_path: []const u8) !Watch {
669 _ = cwd_path;
670 return .{
671 .dir_table = .{},
672 .dir_count = 0,
673 .os = .{
674 .kq_fd = try Io.Kqueue.createFileDescriptor(),
675 .handles = .empty,
676 },
677 .generation = 0,
678 };
679 }
680
681 fn update(w: *Watch, gpa: Allocator, steps: []const *Step) !void {
682 const handles = &w.os.handles;
683 for (steps) |step| {
684 for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| {
685 const reaction_set = rs: {
686 const gop = try w.dir_table.getOrPut(gpa, path);
687 if (!gop.found_existing) {
688 const skip_open_dir = path.sub_path.len == 0;
689 const dir_fd = if (skip_open_dir)
690 path.root_dir.handle.handle
691 else
692 posix.openat(path.root_dir.handle.handle, path.sub_path, dir_open_flags, 0) catch |err| {
693 fatal("failed to open directory {f}: {t}", .{ path, err });
694 };
695 // Empirically the dir has to stay open or else no events are triggered.
696 errdefer if (!skip_open_dir) std.Io.Threaded.closeFd(dir_fd);
697 const changes = [1]posix.Kevent{.{
698 .ident = @bitCast(@as(isize, dir_fd)),
699 .filter = std.c.EVFILT.VNODE,
700 .flags = EV.ADD | EV.ENABLE | EV.CLEAR,
701 .fflags = NOTE.DELETE | NOTE.WRITE | NOTE.RENAME | NOTE.REVOKE,
702 .data = 0,
703 .udata = gop.index,
704 }};
705 _ = try Io.Kqueue.kevent(w.os.kq_fd, &changes, &.{}, null);
706 assert(handles.len == gop.index);
707 try handles.append(gpa, .{
708 .rs = .{},
709 .dir_fd = if (skip_open_dir) -1 else dir_fd,
710 });
711 }
712
713 break :rs &handles.items(.rs)[gop.index];
714 };
715 for (files.items) |basename| {
716 const gop = try reaction_set.getOrPut(gpa, basename);
717 if (!gop.found_existing) gop.value_ptr.* = .{};
718 try gop.value_ptr.put(gpa, step, w.generation);
719 }
720 }
721 }
722
723 {
724 // Remove marks for files that are no longer inputs.
725 var i: usize = 0;
726 while (i < handles.len) {
727 {
728 const reaction_set = &handles.items(.rs)[i];
729 var step_set_i: usize = 0;
730 while (step_set_i < reaction_set.entries.len) {
731 const step_set = &reaction_set.values()[step_set_i];
732 var dirent_i: usize = 0;
733 while (dirent_i < step_set.entries.len) {
734 const generations = step_set.values();
735 if (generations[dirent_i] == w.generation) {
736 dirent_i += 1;
737 continue;
738 }
739 step_set.swapRemoveAt(dirent_i);
740 }
741 if (step_set.entries.len > 0) {
742 step_set_i += 1;
743 continue;
744 }
745 reaction_set.swapRemoveAt(step_set_i);
746 }
747 if (reaction_set.entries.len > 0) {
748 i += 1;
749 continue;
750 }
751 }
752
753 // If the sub_path == "" then this patch has already the
754 // dir fd that we need to use as the ident to remove the
755 // event. If it was opened above with openat() then we need
756 // to access that data via the dir_fd field.
757 const path = w.dir_table.keys()[i];
758 const dir_fd = if (path.sub_path.len == 0)
759 path.root_dir.handle.handle
760 else
761 handles.items(.dir_fd)[i];
762 assert(dir_fd != -1);
763
764 // The changelist also needs to update the udata field of the last
765 // event, since we are doing a swap remove, and we store the dir_table
766 // index in the udata field.
767 const last_dir_fd = fd: {
768 const last_path = w.dir_table.keys()[handles.len - 1];
769 const last_dir_fd = if (last_path.sub_path.len == 0)
770 last_path.root_dir.handle.handle
771 else
772 handles.items(.dir_fd)[handles.len - 1];
773 assert(last_dir_fd != -1);
774 break :fd last_dir_fd;
775 };
776 const changes = [_]posix.Kevent{
777 .{
778 .ident = @bitCast(@as(isize, dir_fd)),
779 .filter = std.c.EVFILT.VNODE,
780 .flags = EV.DELETE,
781 .fflags = 0,
782 .data = 0,
783 .udata = i,
784 },
785 .{
786 .ident = @bitCast(@as(isize, last_dir_fd)),
787 .filter = std.c.EVFILT.VNODE,
788 .flags = EV.ADD,
789 .fflags = NOTE.DELETE | NOTE.WRITE | NOTE.RENAME | NOTE.REVOKE,
790 .data = 0,
791 .udata = i,
792 },
793 };
794 const filtered_changes = if (i == handles.len - 1) changes[0..1] else &changes;
795 _ = try Io.Kqueue.kevent(w.os.kq_fd, filtered_changes, &.{}, null);
796 if (path.sub_path.len != 0) std.Io.Threaded.closeFd(dir_fd);
797
798 w.dir_table.swapRemoveAt(i);
799 handles.swapRemove(i);
800 }
801 w.generation +%= 1;
802 }
803 w.dir_count = w.dir_table.count();
804 }
805
806 fn wait(w: *Watch, gpa: Allocator, io: Io, timeout: Timeout) !WaitResult {
807 _ = io;
808 var timespec_buffer: posix.timespec = undefined;
809 var event_buffer: [100]posix.Kevent = undefined;
810 var n = try Io.Kqueue.kevent(w.os.kq_fd, &.{}, &event_buffer, timeout.toTimespec(&timespec_buffer));
811 if (n == 0) return .timeout;
812 const reaction_sets = w.os.handles.items(.rs);
813 var any_dirty = markDirtySteps(gpa, reaction_sets, event_buffer[0..n], false);
814 timespec_buffer = .{ .sec = 0, .nsec = 0 };
815 while (n == event_buffer.len) {
816 n = try Io.Kqueue.kevent(w.os.kq_fd, &.{}, &event_buffer, &timespec_buffer);
817 if (n == 0) break;
818 any_dirty = markDirtySteps(gpa, reaction_sets, event_buffer[0..n], any_dirty);
819 }
820 return if (any_dirty) .dirty else .clean;
821 }
822
823 fn markDirtySteps(
824 gpa: Allocator,
825 reaction_sets: []ReactionSet,
826 events: []const std.c.Kevent,
827 start_any_dirty: bool,
828 ) bool {
829 var any_dirty = start_any_dirty;
830 for (events) |event| {
831 const index: usize = @intCast(event.udata);
832 const reaction_set = &reaction_sets[index];
833 // If we knew the basename of the changed file, here we would
834 // mark only the step set dirty, and possibly the glob set:
835 //if (reaction_set.getPtr(".")) |glob_set|
836 // any_dirty = markStepSetDirty(gpa, glob_set, any_dirty);
837 //if (reaction_set.getPtr(file_name)) |step_set|
838 // any_dirty = markStepSetDirty(gpa, step_set, any_dirty);
839 // However we don't know the file name so just mark all the
840 // sets dirty for this directory.
841 for (reaction_set.values()) |*step_set| {
842 any_dirty = markStepSetDirty(gpa, step_set, any_dirty);
843 }
844 }
845 return any_dirty;
846 }
847 },
848 .macos => struct {
849 fse: FsEvents,
850
851 fn init(cwd_path: []const u8) !Watch {
852 return .{
853 .os = .{ .fse = try .init(cwd_path) },
854 .dir_count = 0,
855 .dir_table = undefined,
856 .generation = undefined,
857 };
858 }
859 fn update(w: *Watch, gpa: Allocator, steps: []const *Step) !void {
860 try w.os.fse.setPaths(gpa, steps);
861 w.dir_count = w.os.fse.watch_roots.len;
862 }
863 fn wait(w: *Watch, gpa: Allocator, io: Io, timeout: Timeout) !WaitResult {
864 _ = io;
865 return w.os.fse.wait(gpa, switch (timeout) {
866 .none => null,
867 .ms => |ms| @as(u64, ms) * std.time.ns_per_ms,
868 });
869 }
870 },
871 else => void,
872};
873
874pub fn init(cwd_path: []const u8) !Watch {
875 return Os.init(cwd_path);
876}
877
878pub const Match = struct {
879 /// Relative to the watched directory, the file path that triggers this
880 /// match.
881 basename: []const u8,
882 /// The step to re-run when file corresponding to `basename` is changed.
883 step: *Step,
884
885 pub const Context = struct {
886 pub fn hash(self: Context, a: Match) u32 {
887 _ = self;
888 var hasher = Hash.init(0);
889 std.hash.autoHash(&hasher, a.step);
890 hasher.update(a.basename);
891 return @truncate(hasher.final());
892 }
893 pub fn eql(self: Context, a: Match, b: Match, b_index: usize) bool {
894 _ = self;
895 _ = b_index;
896 return a.step == b.step and std.mem.eql(u8, a.basename, b.basename);
897 }
898 };
899};
900
901fn markAllFilesDirty(w: *Watch, gpa: Allocator) void {
902 for (switch (builtin.os.tag) {
903 .windows => w.os.handle_table.keys(),
904 else => w.os.handle_table.values(),
905 }) |item| {
906 const reaction_set = switch (builtin.os.tag) {
907 .linux, .windows => item.reaction_set,
908 else => item,
909 };
910 for (reaction_set.values()) |step_set| {
911 for (step_set.keys()) |step| {
912 _ = step.invalidateResult(gpa);
913 }
914 }
915 }
916}
917
918fn markStepSetDirty(gpa: Allocator, step_set: *StepSet, any_dirty: bool) bool {
919 var this_any_dirty = false;
920 for (step_set.keys()) |step| {
921 if (step.invalidateResult(gpa)) this_any_dirty = true;
922 }
923 return any_dirty or this_any_dirty;
924}
925
926pub fn update(w: *Watch, gpa: Allocator, steps: []const *Step) !void {
927 return Os.update(w, gpa, steps);
928}
929
930pub const Timeout = union(enum) {
931 none,
932 ms: u16,
933
934 pub fn to_i32_ms(t: Timeout) i32 {
935 return switch (t) {
936 .none => -1,
937 .ms => |ms| ms,
938 };
939 }
940
941 pub fn toTimespec(t: Timeout, buf: *std.posix.timespec) ?*std.posix.timespec {
942 return switch (t) {
943 .none => null,
944 .ms => |ms_u16| {
945 const ms: isize = ms_u16;
946 buf.* = .{
947 .sec = @divTrunc(ms, std.time.ms_per_s),
948 .nsec = @rem(ms, std.time.ms_per_s) * std.time.ns_per_ms,
949 };
950 return buf;
951 },
952 };
953 }
954};
955
956pub const WaitResult = enum {
957 timeout,
958 /// File system watching triggered on files that were marked as inputs to at least one Step.
959 /// Relevant steps have been marked dirty.
960 dirty,
961 /// File system watching triggered but none of the events were relevant to
962 /// what we are listening to. There is nothing to do.
963 clean,
964};
965
966pub fn wait(w: *Watch, gpa: Allocator, io: Io, timeout: Timeout) !WaitResult {
967 return Os.wait(w, gpa, io, timeout);
968}
lib/std/Build/Watch/FsEvents.zig deleted-479
......@@ -1,479 +0,0 @@
1//! An implementation of file-system watching based on the `FSEventStream` API in macOS.
2//! While macOS supports kqueue, it does not allow detecting changes to files without
3//! placing watches on each individual file, meaning FD limits are reached incredibly
4//! quickly. The File System Events API works differently: it implements *recursive*
5//! directory watches, managed by a system service. Rather than being in libc, the API is
6//! exposed by the CoreServices framework. To avoid a compile dependency on the framework
7//! bundle, we dynamically load CoreServices with `std.DynLib`.
8//!
9//! While the logic in this file *is* specialized to `std.Build.Watch`, efforts have been
10//! made to keep that specialization to a minimum. Other use cases could be served with
11//! relatively minimal modifications to the `watch_paths` field and its usages (in
12//! particular the `setPaths` function). We avoid using the global GCD dispatch queue in
13//! favour of creating our own and synchronizing with an explicit semaphore, meaning this
14//! logic is thread-safe and does not affect process-global state.
15//!
16//! In theory, this API is quite good at avoiding filesystem race conditions. In practice,
17//! the logic that would avoid them is currently disabled, because the build system kind
18//! of relies on them at the time of writing to avoid redundant work -- see the comment at
19//! the top of `wait` for details.
20
21const enable_debug_logs = false;
22
23core_services: std.DynLib,
24resolved_symbols: ResolvedSymbols,
25
26paths_arena: std.heap.ArenaAllocator.State,
27/// The roots of the recursive watches. FSEvents has relatively small limits on the number
28/// of watched paths, so this slice must not be too long. The paths themselves are allocated
29/// into `paths_arena`, but this slice is allocated into the GPA.
30watch_roots: [][:0]const u8,
31/// All of the paths being watched. Value is the set of steps which depend on the file/directory.
32/// Keys and values are in `paths_arena`, but this map is allocated into the GPA.
33watch_paths: std.StringArrayHashMapUnmanaged([]const *std.Build.Step),
34
35/// The semaphore we use to block the thread calling `wait` until the callback determines a relevant
36/// event has occurred. This is retained across `wait` calls for simplicity and efficiency.
37waiting_semaphore: dispatch.semaphore_t,
38/// This dispatch queue is created by us and executes serially. It exists exclusively to trigger the
39/// callbacks of the FSEventStream we create. This is not in use outside of `wait`, but is retained
40/// across `wait` calls for simplicity and efficiency.
41dispatch_queue: dispatch.queue_t,
42/// In theory, this field avoids race conditions. In practice, it is essentially unused at the time
43/// of writing. See the comment at the start of `wait` for details.
44since_event: FSEventStreamEventId,
45
46cwd_path: []const u8,
47
48/// All of the symbols we pull from the `dlopen`ed CoreServices framework. If any of these symbols
49/// is not present, `init` will close the framework and return an error.
50const ResolvedSymbols = struct {
51 FSEventStreamCreate: *const fn (
52 allocator: CFAllocatorRef,
53 callback: FSEventStreamCallback,
54 ctx: ?*const FSEventStreamContext,
55 paths_to_watch: CFArrayRef,
56 since_when: FSEventStreamEventId,
57 latency: CFTimeInterval,
58 flags: FSEventStreamCreateFlags,
59 ) callconv(.c) FSEventStreamRef,
60 FSEventStreamSetDispatchQueue: *const fn (stream: FSEventStreamRef, queue: dispatch.queue_t) callconv(.c) void,
61 FSEventStreamStart: *const fn (stream: FSEventStreamRef) callconv(.c) bool,
62 FSEventStreamStop: *const fn (stream: FSEventStreamRef) callconv(.c) void,
63 FSEventStreamInvalidate: *const fn (stream: FSEventStreamRef) callconv(.c) void,
64 FSEventStreamRelease: *const fn (stream: FSEventStreamRef) callconv(.c) void,
65 FSEventStreamGetLatestEventId: *const fn (stream: ConstFSEventStreamRef) callconv(.c) FSEventStreamEventId,
66 FSEventsGetCurrentEventId: *const fn () callconv(.c) FSEventStreamEventId,
67 CFRelease: *const fn (cf: *const anyopaque) callconv(.c) void,
68 CFArrayCreate: *const fn (
69 allocator: CFAllocatorRef,
70 values: [*]const usize,
71 num_values: CFIndex,
72 call_backs: ?*const CFArrayCallBacks,
73 ) callconv(.c) CFArrayRef,
74 CFStringCreateWithCString: *const fn (
75 alloc: CFAllocatorRef,
76 c_str: [*:0]const u8,
77 encoding: CFStringEncoding,
78 ) callconv(.c) CFStringRef,
79 CFAllocatorCreate: *const fn (allocator: CFAllocatorRef, context: *const CFAllocatorContext) callconv(.c) CFAllocatorRef,
80 kCFAllocatorUseContext: *const CFAllocatorRef,
81};
82
83pub fn init(cwd_path: []const u8) error{ OpenFrameworkFailed, MissingCoreServicesSymbol, SystemResources }!FsEvents {
84 var core_services = std.DynLib.open("/System/Library/Frameworks/CoreServices.framework/CoreServices") catch
85 return error.OpenFrameworkFailed;
86 errdefer core_services.close();
87
88 var resolved_symbols: ResolvedSymbols = undefined;
89 inline for (@typeInfo(ResolvedSymbols).@"struct".fields) |f| {
90 @field(resolved_symbols, f.name) = core_services.lookup(f.type, f.name) orelse return error.MissingCoreServicesSymbol;
91 }
92
93 return .{
94 .core_services = core_services,
95 .resolved_symbols = resolved_symbols,
96 .paths_arena = .{},
97 .watch_roots = &.{},
98 .watch_paths = .empty,
99 .waiting_semaphore = dispatch.semaphore_create(0) orelse return error.SystemResources,
100 .dispatch_queue = dispatch.queue_create("zig-watch", .SERIAL()) orelse return error.SystemResources,
101 // Not `.since_now`, because this means we can init `FsEvents` *before* we do work in order
102 // to notice any changes which happened during said work.
103 .since_event = resolved_symbols.FSEventsGetCurrentEventId(),
104 .cwd_path = cwd_path,
105 };
106}
107
108pub fn deinit(fse: *FsEvents, gpa: Allocator, io: Io) void {
109 fse.waiting_semaphore.as_object().release();
110 fse.dispatch_queue.as_object().release();
111 fse.core_services.close(io);
112
113 gpa.free(fse.watch_roots);
114 fse.watch_paths.deinit(gpa);
115 {
116 var paths_arena = fse.paths_arena.promote(gpa);
117 paths_arena.deinit();
118 }
119}
120
121pub fn setPaths(fse: *FsEvents, gpa: Allocator, steps: []const *std.Build.Step) !void {
122 var paths_arena_instance = fse.paths_arena.promote(gpa);
123 defer fse.paths_arena = paths_arena_instance.state;
124 const paths_arena = paths_arena_instance.allocator();
125
126 var need_dirs: std.StringArrayHashMapUnmanaged(void) = .empty;
127 defer need_dirs.deinit(gpa);
128
129 fse.watch_paths.clearRetainingCapacity();
130
131 // We take `step` by pointer for a slight memory optimization in a moment.
132 for (steps) |*step| {
133 for (step.*.inputs.table.keys(), step.*.inputs.table.values()) |path, *files| {
134 const resolved_dir = try std.fs.path.resolvePosix(paths_arena, &.{
135 fse.cwd_path, path.root_dir.path orelse ".", path.sub_path,
136 });
137 try need_dirs.put(gpa, resolved_dir, {});
138 for (files.items) |file_name| {
139 const watch_path = if (std.mem.eql(u8, file_name, "."))
140 resolved_dir
141 else
142 try std.fs.path.join(paths_arena, &.{ resolved_dir, file_name });
143 const gop = try fse.watch_paths.getOrPut(gpa, watch_path);
144 if (gop.found_existing) {
145 const old_steps = gop.value_ptr.*;
146 const new_steps = try paths_arena.alloc(*std.Build.Step, old_steps.len + 1);
147 @memcpy(new_steps[0..old_steps.len], old_steps);
148 new_steps[old_steps.len] = step.*;
149 gop.value_ptr.* = new_steps;
150 } else {
151 // This is why we captured `step` by pointer! We can avoid allocating a slice of one
152 // step in the arena in the common case where a file is referenced by only one step.
153 gop.value_ptr.* = step[0..1];
154 }
155 }
156 }
157 }
158
159 {
160 // There's no point looking at directories inside other ones (e.g. "/foo" and "/foo/bar").
161 // To eliminate these, we'll re-add directories in order of path length with a redundancy check.
162 const old_dirs = try gpa.dupe([]const u8, need_dirs.keys());
163 defer gpa.free(old_dirs);
164 std.mem.sort([]const u8, old_dirs, {}, struct {
165 fn lessThan(ctx: void, a: []const u8, b: []const u8) bool {
166 ctx;
167 return std.mem.lessThan(u8, a, b);
168 }
169 }.lessThan);
170 need_dirs.clearRetainingCapacity();
171 for (old_dirs) |dir_path| {
172 var it: std.fs.path.ComponentIterator(.posix, u8) = .init(dir_path);
173 while (it.next()) |component| {
174 if (need_dirs.contains(component.path)) {
175 // this path is '/foo/bar/qux', but '/foo' or '/foo/bar' was already added
176 break;
177 }
178 } else {
179 need_dirs.putAssumeCapacityNoClobber(dir_path, {});
180 }
181 }
182 }
183
184 // `need_dirs` is now a set of directories to watch with no redundancy. In practice, this is very
185 // likely to have reduced it to a quite small set (e.g. it'll typically coalesce a full `src/`
186 // directory into one entry). However, the FSEventStream API has a fairly low undocumented limit
187 // on total watches (supposedly 4096), so we should handle the case where we exceed it. To be
188 // safe, because this API can be a little unpredictable, we'll cap ourselves a little *below*
189 // that known limit.
190 if (need_dirs.count() > 2048) {
191 // Fallback: watch the whole filesystem. This is excessive, but... it *works* :P
192 if (enable_debug_logs) watch_log.debug("too many dirs; recursively watching root", .{});
193 fse.watch_roots = try gpa.realloc(fse.watch_roots, 1);
194 fse.watch_roots[0] = "/";
195 } else {
196 fse.watch_roots = try gpa.realloc(fse.watch_roots, need_dirs.count());
197 for (fse.watch_roots, need_dirs.keys()) |*out, in| {
198 out.* = try paths_arena.dupeSentinel(u8, in, 0);
199 }
200 }
201 if (enable_debug_logs) {
202 watch_log.debug("watching {d} paths using {d} recursive watches:", .{ fse.watch_paths.count(), fse.watch_roots.len });
203 for (fse.watch_roots) |dir_path| {
204 watch_log.debug("- '{s}'", .{dir_path});
205 }
206 }
207}
208
209pub fn wait(fse: *FsEvents, gpa: Allocator, timeout_ns: ?u64) error{ OutOfMemory, StartFailed }!std.Build.Watch.WaitResult {
210 if (fse.watch_roots.len == 0) @panic("nothing to watch");
211
212 const rs = fse.resolved_symbols;
213
214 // At the time of writing, using `since_event` in the obvious way causes redundant rebuilds
215 // to occur, because one step modifies a file which is an input to another step. The solution
216 // to this problem will probably be either:
217 //
218 // a) Don't include the output of one step as a watch input of another; only mark external
219 // files as watch inputs. Or...
220 //
221 // b) Note the current event ID when a step begins, and disregard events preceding that ID
222 // when considering whether to dirty that step in `eventCallback`.
223 //
224 // For now, to avoid the redundant rebuilds, we bypass this `since_event` mechanism. This does
225 // introduce race conditions, but the other `std.Build.Watch` implementations suffer from those
226 // too at the time of writing, so this is kind of expected.
227 fse.since_event = .since_now;
228
229 const cf_allocator = rs.CFAllocatorCreate(rs.kCFAllocatorUseContext.*, &.{
230 .version = 0,
231 .info = @constCast(&gpa),
232 .retain = null,
233 .release = null,
234 .copy_description = null,
235 .allocate = &cf_alloc_callbacks.allocate,
236 .reallocate = &cf_alloc_callbacks.reallocate,
237 .deallocate = &cf_alloc_callbacks.deallocate,
238 .preferred_size = null,
239 }) orelse return error.OutOfMemory;
240 defer rs.CFRelease(cf_allocator);
241
242 const cf_paths = try gpa.alloc(?CFStringRef, fse.watch_roots.len);
243 @memset(cf_paths, null);
244 defer {
245 for (cf_paths) |o| if (o) |p| rs.CFRelease(p);
246 gpa.free(cf_paths);
247 }
248 for (fse.watch_roots, cf_paths) |raw_path, *cf_path| {
249 cf_path.* = rs.CFStringCreateWithCString(cf_allocator, raw_path, .utf8);
250 }
251 const cf_paths_array = rs.CFArrayCreate(cf_allocator, @ptrCast(cf_paths), @intCast(cf_paths.len), null);
252 defer rs.CFRelease(cf_paths_array);
253
254 const callback_ctx: EventCallbackCtx = .{
255 .fse = fse,
256 .gpa = gpa,
257 };
258 const event_stream = rs.FSEventStreamCreate(
259 null,
260 &eventCallback,
261 &.{
262 .version = 0,
263 .info = @constCast(&callback_ctx),
264 .retain = null,
265 .release = null,
266 .copy_description = null,
267 },
268 cf_paths_array,
269 fse.since_event,
270 0.05, // 0.05s latency; higher values increase efficiency by coalescing more events
271 .{ .watch_root = true, .file_events = true },
272 );
273 defer rs.FSEventStreamRelease(event_stream);
274 rs.FSEventStreamSetDispatchQueue(event_stream, fse.dispatch_queue);
275 defer rs.FSEventStreamInvalidate(event_stream);
276 if (!rs.FSEventStreamStart(event_stream)) return error.StartFailed;
277 defer rs.FSEventStreamStop(event_stream);
278 const result = fse.waiting_semaphore.wait(timeout: {
279 const ns = timeout_ns orelse break :timeout .FOREVER;
280 break :timeout .time(.NOW, @intCast(ns));
281 });
282 return switch (result) {
283 0 => .dirty,
284 else => .timeout,
285 };
286}
287
288const cf_alloc_callbacks = struct {
289 const log = std.log.scoped(.cf_alloc);
290 fn allocate(size: CFIndex, hint: CFOptionFlags, info: ?*const anyopaque) callconv(.c) ?*const anyopaque {
291 if (enable_debug_logs) log.debug("allocate {d}", .{size});
292 _ = hint;
293 const gpa: *const Allocator = @ptrCast(@alignCast(info));
294 const mem = gpa.alignedAlloc(u8, .of(usize), @intCast(size + @sizeOf(usize))) catch return null;
295 const metadata: *usize = @ptrCast(mem);
296 metadata.* = @intCast(size);
297 return mem[@sizeOf(usize)..].ptr;
298 }
299 fn reallocate(ptr: ?*anyopaque, new_size: CFIndex, hint: CFOptionFlags, info: ?*const anyopaque) callconv(.c) ?*const anyopaque {
300 if (enable_debug_logs) log.debug("reallocate @{*} {d}", .{ ptr, new_size });
301 _ = hint;
302 if (ptr == null or new_size == 0) return null; // not a bug: documentation explicitly states that realloc on NULL should return NULL
303 const gpa: *const Allocator = @ptrCast(@alignCast(info));
304 const old_base: [*]align(@alignOf(usize)) u8 = @alignCast(@as([*]u8, @ptrCast(ptr)) - @sizeOf(usize));
305 const old_size = @as(*const usize, @ptrCast(old_base)).*;
306 const old_mem = old_base[0 .. old_size + @sizeOf(usize)];
307 const new_mem = gpa.realloc(old_mem, @intCast(new_size + @sizeOf(usize))) catch return null;
308 const metadata: *usize = @ptrCast(new_mem);
309 metadata.* = @intCast(new_size);
310 return new_mem[@sizeOf(usize)..].ptr;
311 }
312 fn deallocate(ptr: *anyopaque, info: ?*const anyopaque) callconv(.c) void {
313 if (enable_debug_logs) log.debug("deallocate @{*}", .{ptr});
314 const gpa: *const Allocator = @ptrCast(@alignCast(info));
315 const old_base: [*]align(@alignOf(usize)) u8 = @alignCast(@as([*]u8, @ptrCast(ptr)) - @sizeOf(usize));
316 const old_size = @as(*const usize, @ptrCast(old_base)).*;
317 const old_mem = old_base[0 .. old_size + @sizeOf(usize)];
318 gpa.free(old_mem);
319 }
320};
321
322const EventCallbackCtx = struct {
323 fse: *FsEvents,
324 gpa: Allocator,
325};
326
327fn eventCallback(
328 stream: ConstFSEventStreamRef,
329 client_callback_info: ?*anyopaque,
330 num_events: usize,
331 events_paths_ptr: *anyopaque,
332 events_flags_ptr: [*]const FSEventStreamEventFlags,
333 events_ids_ptr: [*]const FSEventStreamEventId,
334) callconv(.c) void {
335 const ctx: *const EventCallbackCtx = @ptrCast(@alignCast(client_callback_info));
336 const fse = ctx.fse;
337 const gpa = ctx.gpa;
338 const rs = fse.resolved_symbols;
339 const events_paths_ptr_casted: [*]const [*:0]const u8 = @ptrCast(@alignCast(events_paths_ptr));
340 const events_paths = events_paths_ptr_casted[0..num_events];
341 const events_ids = events_ids_ptr[0..num_events];
342 const events_flags = events_flags_ptr[0..num_events];
343 var any_dirty = false;
344 for (events_paths, events_ids, events_flags) |event_path_nts, event_id, event_flags| {
345 _ = event_id;
346 if (event_flags.history_done) continue; // sentinel
347 const event_path = std.mem.span(event_path_nts);
348 switch (event_flags.must_scan_sub_dirs) {
349 false => {
350 if (fse.watch_paths.get(event_path)) |steps| {
351 assert(steps.len > 0);
352 for (steps) |s| {
353 if (s.invalidateResult(gpa)) any_dirty = true;
354 }
355 }
356 if (std.fs.path.dirname(event_path)) |event_dirname| {
357 // Modifying '/foo/bar' triggers the watch on '/foo'.
358 if (fse.watch_paths.get(event_dirname)) |steps| {
359 assert(steps.len > 0);
360 for (steps) |s| {
361 if (s.invalidateResult(gpa)) any_dirty = true;
362 }
363 }
364 }
365 },
366 true => {
367 // This is unlikely, but can occasionally happen when bottlenecked: events have been
368 // coalesced into one. We want to see if any of these events are actually relevant
369 // to us. The only way we can reasonably do that in this rare edge case is iterate
370 // the watch paths and see if any is under this directory. That's acceptable because
371 // we would otherwise kick off a rebuild which would be clearing those paths anyway.
372 const changed_path = std.fs.path.dirname(event_path) orelse event_path;
373 for (fse.watch_paths.keys(), fse.watch_paths.values()) |watching_path, steps| {
374 if (dirStartsWith(watching_path, changed_path)) {
375 for (steps) |s| {
376 if (s.invalidateResult(gpa)) any_dirty = true;
377 }
378 }
379 }
380 },
381 }
382 }
383 if (any_dirty) {
384 fse.since_event = rs.FSEventStreamGetLatestEventId(stream);
385 _ = fse.waiting_semaphore.signal();
386 }
387}
388fn dirStartsWith(path: []const u8, prefix: []const u8) bool {
389 if (std.mem.eql(u8, path, prefix)) return true;
390 if (!std.mem.startsWith(u8, path, prefix)) return false;
391 if (path[prefix.len] != '/') return false; // `path` is `/foo/barx`, `prefix` is `/foo/bar`
392 return true; // `path` is `/foo/bar/...`, `prefix` is `/foo/bar`
393}
394
395const CFAllocatorRef = ?*const opaque {};
396const CFArrayRef = *const opaque {};
397const CFStringRef = *const opaque {};
398const CFTimeInterval = f64;
399const CFIndex = i32;
400const CFOptionFlags = enum(u32) { _ };
401const CFAllocatorRetainCallBack = *const fn (info: ?*const anyopaque) callconv(.c) *const anyopaque;
402const CFAllocatorReleaseCallBack = *const fn (info: ?*const anyopaque) callconv(.c) void;
403const CFAllocatorCopyDescriptionCallBack = *const fn (info: ?*const anyopaque) callconv(.c) CFStringRef;
404const CFAllocatorAllocateCallBack = *const fn (alloc_size: CFIndex, hint: CFOptionFlags, info: ?*const anyopaque) callconv(.c) ?*const anyopaque;
405const CFAllocatorReallocateCallBack = *const fn (ptr: ?*anyopaque, new_size: CFIndex, hint: CFOptionFlags, info: ?*const anyopaque) callconv(.c) ?*const anyopaque;
406const CFAllocatorDeallocateCallBack = *const fn (ptr: *anyopaque, info: ?*const anyopaque) callconv(.c) void;
407const CFAllocatorPreferredSizeCallBack = *const fn (size: CFIndex, hint: CFOptionFlags, info: ?*const anyopaque) callconv(.c) CFIndex;
408const CFAllocatorContext = extern struct {
409 version: CFIndex,
410 info: ?*anyopaque,
411 retain: ?CFAllocatorRetainCallBack,
412 release: ?CFAllocatorReleaseCallBack,
413 copy_description: ?CFAllocatorCopyDescriptionCallBack,
414 allocate: CFAllocatorAllocateCallBack,
415 reallocate: ?CFAllocatorReallocateCallBack,
416 deallocate: ?CFAllocatorDeallocateCallBack,
417 preferred_size: ?CFAllocatorPreferredSizeCallBack,
418};
419const CFArrayCallBacks = opaque {};
420const CFStringEncoding = enum(u32) {
421 invalid_id = std.math.maxInt(u32),
422 mac_roman = 0,
423 windows_latin_1 = 0x500,
424 iso_latin_1 = 0x201,
425 next_step_latin = 0xB01,
426 ascii = 0x600,
427 unicode = 0x100,
428 utf8 = 0x8000100,
429 non_lossy_ascii = 0xBFF,
430};
431
432const FSEventStreamRef = *opaque {};
433const ConstFSEventStreamRef = *const @typeInfo(FSEventStreamRef).pointer.child;
434const FSEventStreamCallback = *const fn (
435 stream: ConstFSEventStreamRef,
436 client_callback_info: ?*anyopaque,
437 num_events: usize,
438 event_paths: *anyopaque,
439 event_flags: [*]const FSEventStreamEventFlags,
440 event_ids: [*]const FSEventStreamEventId,
441) callconv(.c) void;
442const FSEventStreamContext = extern struct {
443 version: CFIndex,
444 info: ?*anyopaque,
445 retain: ?CFAllocatorRetainCallBack,
446 release: ?CFAllocatorReleaseCallBack,
447 copy_description: ?CFAllocatorCopyDescriptionCallBack,
448};
449const FSEventStreamEventId = enum(u64) {
450 since_now = std.math.maxInt(u64),
451 _,
452};
453const FSEventStreamCreateFlags = packed struct(u32) {
454 use_cf_types: bool = false,
455 no_defer: bool = false,
456 watch_root: bool = false,
457 ignore_self: bool = false,
458 file_events: bool = false,
459 _: u27 = 0,
460};
461const FSEventStreamEventFlags = packed struct(u32) {
462 must_scan_sub_dirs: bool,
463 user_dropped: bool,
464 kernel_dropped: bool,
465 event_ids_wrapped: bool,
466 history_done: bool,
467 root_changed: bool,
468 mount: bool,
469 unmount: bool,
470 _: u24 = 0,
471};
472
473const dispatch = std.c.dispatch;
474const std = @import("std");
475const Io = std.Io;
476const assert = std.debug.assert;
477const Allocator = std.mem.Allocator;
478const watch_log = std.log.scoped(.watch);
479const FsEvents = @This();
lib/std/Build/WebServer.zig deleted-926
......@@ -1,926 +0,0 @@
1gpa: Allocator,
2graph: *const Build.Graph,
3all_steps: []const *Build.Step,
4listen_address: net.IpAddress,
5root_prog_node: std.Progress.Node,
6watch: bool,
7
8tcp_server: ?net.Server,
9serve_task: ?Io.Future(Io.Cancelable!void),
10
11/// Uses `Io.Clock.awake`.
12base_timestamp: Io.Timestamp,
13/// The "step name" data which trails `abi.Hello`, for the steps in `all_steps`.
14step_names_trailing: []u8,
15
16/// The bit-packed "step status" data. Values are `abi.StepUpdate.Status`. LSBs are earlier steps.
17/// Accessed atomically.
18step_status_bits: []u8,
19
20fuzz: ?Fuzz,
21time_report_mutex: Io.Mutex,
22time_report_msgs: [][]u8,
23time_report_update_times: []i64,
24
25build_status: std.atomic.Value(abi.BuildStatus),
26/// When an event occurs which means WebSocket clients should be sent updates, call `notifyUpdate`
27/// to increment this value. Each client thread waits for this increment with `Io.futexWaitTimeout`, so
28/// `notifyUpdate` will wake those threads. Updates are sent on a short interval regardless, so it
29/// is recommended to only use `notifyUpdate` for changes which the user should see immediately. For
30/// instance, we do not call `notifyUpdate` when the number of "unique runs" in the fuzzer changes,
31/// because this value changes quickly so this would result in constantly spamming all clients with
32/// an unreasonable number of packets.
33update_id: std.atomic.Value(u32),
34
35runner_request_mutex: Io.Mutex,
36runner_request_ready_cond: Io.Condition,
37runner_request_empty_cond: Io.Condition,
38runner_request: ?RunnerRequest,
39
40/// If a client is not explicitly notified of changes with `notifyUpdate`, it will be sent updates
41/// on a fixed interval of this many milliseconds.
42const default_update_interval_ms = 500;
43
44pub const base_clock: Io.Clock = .awake;
45
46/// Thread-safe. Triggers updates to be sent to connected WebSocket clients; see `update_id`.
47pub fn notifyUpdate(ws: *WebServer) void {
48 _ = ws.update_id.rmw(.Add, 1, .release);
49 ws.graph.io.futexWake(u32, &ws.update_id.raw, 16);
50}
51
52pub const Options = struct {
53 gpa: Allocator,
54 graph: *const std.Build.Graph,
55 all_steps: []const *Build.Step,
56 root_prog_node: std.Progress.Node,
57 watch: bool,
58 listen_address: net.IpAddress,
59 base_timestamp: Io.Clock.Timestamp,
60};
61pub fn init(opts: Options) WebServer {
62 // The upcoming `Io` interface should allow us to use `Io.async` and `Io.concurrent`
63 // instead of threads, so that the web server can function in single-threaded builds.
64 comptime assert(!builtin.single_threaded);
65 assert(opts.base_timestamp.clock == base_clock);
66
67 const all_steps = opts.all_steps;
68
69 const step_names_trailing = opts.gpa.alloc(u8, len: {
70 var name_bytes: usize = 0;
71 for (all_steps) |step| name_bytes += step.name.len;
72 break :len name_bytes + all_steps.len * 4;
73 }) catch @panic("out of memory");
74 {
75 const step_name_lens: []align(1) u32 = @ptrCast(step_names_trailing[0 .. all_steps.len * 4]);
76 var idx: usize = all_steps.len * 4;
77 for (all_steps, step_name_lens) |step, *name_len| {
78 name_len.* = @intCast(step.name.len);
79 @memcpy(step_names_trailing[idx..][0..step.name.len], step.name);
80 idx += step.name.len;
81 }
82 assert(idx == step_names_trailing.len);
83 }
84
85 const step_status_bits = opts.gpa.alloc(
86 u8,
87 std.math.divCeil(usize, all_steps.len, 4) catch unreachable,
88 ) catch @panic("out of memory");
89 @memset(step_status_bits, 0);
90
91 const time_reports_len: usize = if (opts.graph.time_report) all_steps.len else 0;
92 const time_report_msgs = opts.gpa.alloc([]u8, time_reports_len) catch @panic("out of memory");
93 const time_report_update_times = opts.gpa.alloc(i64, time_reports_len) catch @panic("out of memory");
94 @memset(time_report_msgs, &.{});
95 @memset(time_report_update_times, std.math.minInt(i64));
96
97 return .{
98 .gpa = opts.gpa,
99 .graph = opts.graph,
100 .all_steps = all_steps,
101 .listen_address = opts.listen_address,
102 .root_prog_node = opts.root_prog_node,
103 .watch = opts.watch,
104
105 .tcp_server = null,
106 .serve_task = null,
107
108 .base_timestamp = opts.base_timestamp.raw,
109 .step_names_trailing = step_names_trailing,
110
111 .step_status_bits = step_status_bits,
112
113 .fuzz = null,
114 .time_report_mutex = .init,
115 .time_report_msgs = time_report_msgs,
116 .time_report_update_times = time_report_update_times,
117
118 .build_status = .init(.idle),
119 .update_id = .init(0),
120
121 .runner_request_mutex = .init,
122 .runner_request_ready_cond = .init,
123 .runner_request_empty_cond = .init,
124 .runner_request = null,
125 };
126}
127pub fn deinit(ws: *WebServer) void {
128 const gpa = ws.gpa;
129 const io = ws.graph.io;
130
131 gpa.free(ws.step_names_trailing);
132 gpa.free(ws.step_status_bits);
133
134 if (ws.fuzz) |*f| f.deinit();
135 for (ws.time_report_msgs) |msg| gpa.free(msg);
136 gpa.free(ws.time_report_msgs);
137 gpa.free(ws.time_report_update_times);
138
139 if (ws.serve_task) |t| {
140 if (ws.tcp_server) |*s| s.stream.close(io);
141 t.await();
142 }
143 if (ws.tcp_server) |*s| s.deinit();
144
145 gpa.free(ws.step_names_trailing);
146}
147pub fn start(ws: *WebServer) error{AlreadyReported}!void {
148 assert(ws.tcp_server == null);
149 assert(ws.serve_task == null);
150 const io = ws.graph.io;
151
152 ws.tcp_server = ws.listen_address.listen(io, .{ .reuse_address = true }) catch |err| {
153 log.err("failed to listen to port {d}: {t}", .{ ws.listen_address.getPort(), err });
154 return error.AlreadyReported;
155 };
156 ws.serve_task = io.concurrent(serve, .{ws}) catch |err| {
157 log.err("unable to spawn web server thread: {t}", .{err});
158 ws.tcp_server.?.deinit(io);
159 ws.tcp_server = null;
160 return error.AlreadyReported;
161 };
162
163 log.info("web interface listening at http://{f}/", .{ws.tcp_server.?.socket.address});
164 if (ws.listen_address.getPort() == 0) {
165 log.info("hint: pass '--webui={f}' to use the same port next time", .{ws.tcp_server.?.socket.address});
166 }
167}
168fn serve(ws: *WebServer) Io.Cancelable!void {
169 const io = ws.graph.io;
170 var group: Io.Group = .init;
171 defer group.cancel(io);
172 while (true) {
173 var stream = ws.tcp_server.?.accept(io) catch |err| switch (err) {
174 error.Canceled => |e| return e,
175 else => |e| {
176 log.err("failed to accept connection: {t}", .{e});
177 return;
178 },
179 };
180 group.concurrent(io, accept, .{ ws, stream }) catch |err| {
181 log.err("unable to spawn connection thread: {t}", .{err});
182 stream.close(io);
183 continue;
184 };
185 }
186}
187
188pub fn startBuild(ws: *WebServer) void {
189 if (ws.fuzz) |*fuzz| {
190 fuzz.deinit();
191 ws.fuzz = null;
192 }
193 for (ws.step_status_bits) |*bits| @atomicStore(u8, bits, 0, .monotonic);
194 ws.build_status.store(.running, .monotonic);
195 ws.notifyUpdate();
196}
197
198pub fn updateStepStatus(ws: *WebServer, step: *Build.Step, new_status: abi.StepUpdate.Status) void {
199 const step_idx: u32 = for (ws.all_steps, 0..) |s, i| {
200 if (s == step) break @intCast(i);
201 } else unreachable;
202 const ptr = &ws.step_status_bits[step_idx / 4];
203 const bit_offset: u3 = @intCast((step_idx % 4) * 2);
204 const old_bits: u2 = @truncate(@atomicLoad(u8, ptr, .monotonic) >> bit_offset);
205 const mask = @as(u8, @intFromEnum(new_status) ^ old_bits) << bit_offset;
206 _ = @atomicRmw(u8, ptr, .Xor, mask, .monotonic);
207 ws.notifyUpdate();
208}
209
210pub fn finishBuild(ws: *WebServer, opts: struct {
211 fuzz: bool,
212}) void {
213 if (opts.fuzz) {
214 switch (builtin.os.tag) {
215 // Current implementation depends on two things that need to be ported to Windows:
216 // * Memory-mapping to share data between the fuzzer and build runner.
217 // * COFF/PE support added to `std.debug.Info` (it needs a batching API for resolving
218 // many addresses to source locations).
219 .windows => std.process.fatal("--fuzz not yet implemented for {s}", .{@tagName(builtin.os.tag)}),
220 else => {},
221 }
222 if (@bitSizeOf(usize) != 64) {
223 // Current implementation depends on posix.mmap()'s second
224 // parameter, `length: usize`, being compatible with file system's
225 // u64 return value. This is not the case on 32-bit platforms.
226 // Affects or affected by issues #5185, #22523, and #22464.
227 std.process.fatal("--fuzz not yet implemented on {d}-bit platforms", .{@bitSizeOf(usize)});
228 }
229
230 assert(ws.fuzz == null);
231
232 ws.build_status.store(.fuzz_init, .monotonic);
233 ws.notifyUpdate();
234
235 ws.fuzz = Fuzz.init(
236 ws.gpa,
237 ws.graph.io,
238 ws.all_steps,
239 ws.root_prog_node,
240 .{ .forever = .{ .ws = ws } },
241 ) catch |err| std.process.fatal("failed to start fuzzer: {s}", .{@errorName(err)});
242 ws.fuzz.?.start();
243 }
244
245 ws.build_status.store(if (ws.watch) .watching else .idle, .monotonic);
246 ws.notifyUpdate();
247}
248
249pub fn now(s: *const WebServer) i64 {
250 const io = s.graph.io;
251 const ts = base_clock.now(io);
252 return @intCast(s.base_timestamp.durationTo(ts).toNanoseconds());
253}
254
255fn accept(ws: *WebServer, stream: net.Stream) void {
256 const io = ws.graph.io;
257 defer {
258 // `net.Stream.close` wants to helpfully overwrite `stream` with
259 // `undefined`, but it cannot do so since it is an immutable parameter.
260 var copy = stream;
261 copy.close(io);
262 }
263 var send_buffer: [4096]u8 = undefined;
264 var recv_buffer: [4096]u8 = undefined;
265 var connection_reader = stream.reader(io, &recv_buffer);
266 var connection_writer = stream.writer(io, &send_buffer);
267 var server: http.Server = .init(&connection_reader.interface, &connection_writer.interface);
268
269 while (true) {
270 var request = server.receiveHead() catch |err| switch (err) {
271 error.HttpConnectionClosing => return,
272 else => return log.err("failed to receive http request: {t}", .{err}),
273 };
274 switch (request.upgradeRequested()) {
275 .websocket => |opt_key| {
276 const key = opt_key orelse return log.err("missing websocket key", .{});
277 var web_socket = request.respondWebSocket(.{ .key = key }) catch {
278 return log.err("failed to respond web socket: {t}", .{connection_writer.err.?});
279 };
280 ws.serveWebSocket(&web_socket) catch |err| {
281 log.err("failed to serve websocket: {t}", .{err});
282 return;
283 };
284 comptime unreachable;
285 },
286 .other => |name| return log.err("unknown upgrade request: {s}", .{name}),
287 .none => {
288 ws.serveRequest(&request) catch |err| switch (err) {
289 error.AlreadyReported => return,
290 else => {
291 log.err("failed to serve '{s}': {t}", .{ request.head.target, err });
292 return;
293 },
294 };
295 },
296 }
297 }
298}
299
300fn serveWebSocket(ws: *WebServer, sock: *http.Server.WebSocket) !noreturn {
301 const io = ws.graph.io;
302
303 var prev_build_status = ws.build_status.load(.monotonic);
304
305 const prev_step_status_bits = try ws.gpa.alloc(u8, ws.step_status_bits.len);
306 defer ws.gpa.free(prev_step_status_bits);
307 for (prev_step_status_bits, ws.step_status_bits) |*copy, *shared| {
308 copy.* = @atomicLoad(u8, shared, .monotonic);
309 }
310
311 var recv_thread = try io.concurrent(recvWebSocketMessages, .{ ws, sock });
312 defer recv_thread.cancel(io);
313
314 {
315 const hello_header: abi.Hello = .{
316 .status = prev_build_status,
317 .flags = .{
318 .time_report = ws.graph.time_report,
319 },
320 .timestamp = ws.now(),
321 .steps_len = @intCast(ws.all_steps.len),
322 };
323 var bufs: [3][]const u8 = .{ @ptrCast(&hello_header), ws.step_names_trailing, prev_step_status_bits };
324 try sock.writeMessageVec(&bufs, .binary);
325 }
326
327 var prev_fuzz: Fuzz.Previous = .init;
328 var prev_time: i64 = std.math.minInt(i64);
329 while (true) {
330 const start_time = ws.now();
331 const start_update_id = ws.update_id.load(.acquire);
332
333 if (ws.fuzz) |*fuzz| {
334 try fuzz.sendUpdate(sock, &prev_fuzz);
335 }
336
337 {
338 try ws.time_report_mutex.lock(io);
339 defer ws.time_report_mutex.unlock(io);
340 for (ws.time_report_msgs, ws.time_report_update_times) |msg, update_time| {
341 if (update_time <= prev_time) continue;
342 // We want to send `msg`, but shouldn't block `ws.time_report_mutex` while we do, so
343 // that we don't hold up the build system on the client accepting this packet.
344 const owned_msg = try ws.gpa.dupe(u8, msg);
345 defer ws.gpa.free(owned_msg);
346 // Temporarily unlock, then re-lock after the message is sent.
347 ws.time_report_mutex.unlock(io);
348 defer ws.time_report_mutex.lockUncancelable(io);
349 try sock.writeMessage(owned_msg, .binary);
350 }
351 }
352
353 {
354 const build_status = ws.build_status.load(.monotonic);
355 if (build_status != prev_build_status) {
356 prev_build_status = build_status;
357 const msg: abi.StatusUpdate = .{ .new = build_status };
358 try sock.writeMessage(@ptrCast(&msg), .binary);
359 }
360 }
361
362 for (prev_step_status_bits, ws.step_status_bits, 0..) |*prev_byte, *shared, byte_idx| {
363 const cur_byte = @atomicLoad(u8, shared, .monotonic);
364 if (prev_byte.* == cur_byte) continue;
365 const cur: [4]abi.StepUpdate.Status = .{
366 @enumFromInt(@as(u2, @truncate(cur_byte >> 0))),
367 @enumFromInt(@as(u2, @truncate(cur_byte >> 2))),
368 @enumFromInt(@as(u2, @truncate(cur_byte >> 4))),
369 @enumFromInt(@as(u2, @truncate(cur_byte >> 6))),
370 };
371 const prev: [4]abi.StepUpdate.Status = .{
372 @enumFromInt(@as(u2, @truncate(prev_byte.* >> 0))),
373 @enumFromInt(@as(u2, @truncate(prev_byte.* >> 2))),
374 @enumFromInt(@as(u2, @truncate(prev_byte.* >> 4))),
375 @enumFromInt(@as(u2, @truncate(prev_byte.* >> 6))),
376 };
377 for (cur, prev, byte_idx * 4..) |cur_status, prev_status, step_idx| {
378 const msg: abi.StepUpdate = .{ .step_idx = @intCast(step_idx), .bits = .{ .status = cur_status } };
379 if (cur_status != prev_status) try sock.writeMessage(@ptrCast(&msg), .binary);
380 }
381 prev_byte.* = cur_byte;
382 }
383
384 prev_time = start_time;
385
386 const old_cp = io.swapCancelProtection(.blocked);
387 defer _ = io.swapCancelProtection(old_cp);
388 io.futexWaitTimeout(
389 u32,
390 &ws.update_id.raw,
391 start_update_id,
392 .{ .duration = .{
393 .clock = .awake,
394 .raw = .fromMilliseconds(default_update_interval_ms),
395 } },
396 ) catch |err| switch (err) {
397 error.Canceled => unreachable,
398 };
399 }
400}
401fn recvWebSocketMessages(ws: *WebServer, sock: *http.Server.WebSocket) void {
402 const io = ws.graph.io;
403
404 while (true) {
405 const msg = sock.readSmallMessage() catch return;
406 if (msg.opcode != .binary) continue;
407 if (msg.data.len == 0) continue;
408 const tag: abi.ToServerTag = @enumFromInt(msg.data[0]);
409 switch (tag) {
410 _ => continue,
411 .rebuild => while (true) {
412 ws.runner_request_mutex.lock(io) catch |err| switch (err) {
413 error.Canceled => return,
414 };
415 defer ws.runner_request_mutex.unlock(io);
416 if (ws.runner_request == null) {
417 ws.runner_request = .rebuild;
418 ws.runner_request_ready_cond.signal(io);
419 break;
420 }
421 ws.runner_request_empty_cond.wait(io, &ws.runner_request_mutex) catch return;
422 },
423 }
424 }
425}
426
427fn serveRequest(ws: *WebServer, req: *http.Server.Request) !void {
428 // Strip an optional leading '/debug' component from the request.
429 const target: []const u8, const debug: bool = target: {
430 if (mem.eql(u8, req.head.target, "/debug")) break :target .{ "/", true };
431 if (mem.eql(u8, req.head.target, "/debug/")) break :target .{ "/", true };
432 if (mem.startsWith(u8, req.head.target, "/debug/")) break :target .{ req.head.target["/debug".len..], true };
433 break :target .{ req.head.target, false };
434 };
435
436 if (mem.eql(u8, target, "/")) return serveLibFile(ws, req, "build-web/index.html", "text/html");
437 if (mem.eql(u8, target, "/main.js")) return serveLibFile(ws, req, "build-web/main.js", "application/javascript");
438 if (mem.eql(u8, target, "/style.css")) return serveLibFile(ws, req, "build-web/style.css", "text/css");
439 if (mem.eql(u8, target, "/time_report.css")) return serveLibFile(ws, req, "build-web/time_report.css", "text/css");
440 if (mem.eql(u8, target, "/main.wasm")) return serveClientWasm(ws, req, if (debug) .Debug else .ReleaseFast);
441
442 if (ws.fuzz) |*fuzz| {
443 if (mem.eql(u8, target, "/sources.tar")) return fuzz.serveSourcesTar(req);
444 }
445
446 try req.respond("not found", .{
447 .status = .not_found,
448 .extra_headers = &.{
449 .{ .name = "Content-Type", .value = "text/plain" },
450 },
451 });
452}
453
454fn serveLibFile(
455 ws: *WebServer,
456 request: *http.Server.Request,
457 sub_path: []const u8,
458 content_type: []const u8,
459) !void {
460 return serveFile(ws, request, .{
461 .root_dir = ws.graph.zig_lib_directory,
462 .sub_path = sub_path,
463 }, content_type);
464}
465fn serveClientWasm(
466 ws: *WebServer,
467 req: *http.Server.Request,
468 optimize_mode: std.builtin.OptimizeMode,
469) !void {
470 var arena_state: std.heap.ArenaAllocator = .init(ws.gpa);
471 defer arena_state.deinit();
472 const arena = arena_state.allocator();
473
474 // We always rebuild the wasm on-the-fly, so that if it is edited the user can just refresh the page.
475 const bin_path = try buildClientWasm(ws, arena, optimize_mode);
476 return serveFile(ws, req, bin_path, "application/wasm");
477}
478
479pub fn serveFile(
480 ws: *WebServer,
481 request: *http.Server.Request,
482 path: Cache.Path,
483 content_type: []const u8,
484) !void {
485 const gpa = ws.gpa;
486 const io = ws.graph.io;
487 // The desired API is actually sendfile, which will require enhancing http.Server.
488 // We load the file with every request so that the user can make changes to the file
489 // and refresh the HTML page without restarting this server.
490 const file_contents = path.root_dir.handle.readFileAlloc(io, path.sub_path, gpa, .limited(10 * 1024 * 1024)) catch |err| {
491 log.err("failed to read '{f}': {t}", .{ path, err });
492 return error.AlreadyReported;
493 };
494 defer gpa.free(file_contents);
495 try request.respond(file_contents, .{
496 .extra_headers = &.{
497 .{ .name = "Content-Type", .value = content_type },
498 cache_control_header,
499 },
500 });
501}
502pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []const Cache.Path) !void {
503 const graph = ws.graph;
504 const io = graph.io;
505
506 var send_buffer: [0x4000]u8 = undefined;
507 var response = try request.respondStreaming(&send_buffer, .{
508 .respond_options = .{
509 .extra_headers = &.{
510 .{ .name = "Content-Type", .value = "application/x-tar" },
511 cache_control_header,
512 },
513 },
514 });
515
516 var archiver: std.tar.Writer = .{ .underlying_writer = &response.writer };
517
518 for (paths) |path| {
519 var file = path.root_dir.handle.openFile(io, path.sub_path, .{}) catch |err| {
520 log.err("failed to open '{f}': {s}", .{ path, @errorName(err) });
521 continue;
522 };
523 defer file.close(io);
524 const stat = try file.stat(io);
525 var read_buffer: [1024]u8 = undefined;
526 var file_reader: Io.File.Reader = .initSize(file, io, &read_buffer, stat.size);
527
528 // TODO: this logic is completely bogus -- obviously so, because `path.root_dir.path` can
529 // be cwd-relative. This is also related to why linkification doesn't work in the fuzzer UI:
530 // it turns out the WASM treats the first path component as the module name, typically
531 // resulting in modules named "" and "src". The compiler needs to tell the build system
532 // about the module graph so that the build system can correctly encode this information in
533 // the tar file.
534 //
535 // Additionally, this needs to ensure that all path separators for both prefix and
536 // sub_path are using the POSIX-style `/` on platforms that don't use it as their native
537 // path separator.
538 archiver.prefix = path.root_dir.path orelse graph.cache.cwd;
539 try archiver.writeFile(path.sub_path, &file_reader, @intCast(stat.mtime.toSeconds()));
540 }
541
542 // intentionally not calling `archiver.finishPedantically`
543 try response.end();
544}
545
546fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.OptimizeMode) !Cache.Path {
547 const root_name = "build-web";
548 const arch_os_abi = "wasm32-freestanding";
549 const cpu_features = "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext";
550
551 const gpa = ws.gpa;
552 const graph = ws.graph;
553 const io = graph.io;
554
555 const main_src_path: Cache.Path = .{
556 .root_dir = graph.zig_lib_directory,
557 .sub_path = "build-web/main.zig",
558 };
559 const walk_src_path: Cache.Path = .{
560 .root_dir = graph.zig_lib_directory,
561 .sub_path = "docs/wasm/Walk.zig",
562 };
563 const html_render_src_path: Cache.Path = .{
564 .root_dir = graph.zig_lib_directory,
565 .sub_path = "docs/wasm/html_render.zig",
566 };
567
568 var argv: std.ArrayList([]const u8) = .empty;
569
570 try argv.appendSlice(arena, &.{
571 graph.zig_exe, "build-exe", //
572 "-fno-entry", //
573 "-O", @tagName(optimize), //
574 "-target", arch_os_abi, //
575 "-mcpu", cpu_features, //
576 "--cache-dir", graph.global_cache_root.path orelse ".", //
577 "--global-cache-dir", graph.global_cache_root.path orelse ".", //
578 "--zig-lib-dir", graph.zig_lib_directory.path orelse ".", //
579 "--name", root_name, //
580 "-rdynamic", //
581 "-fsingle-threaded", //
582 "--dep", "Walk", //
583 "--dep", "html_render", //
584 try std.fmt.allocPrint(arena, "-Mroot={f}", .{main_src_path}), //
585 try std.fmt.allocPrint(arena, "-MWalk={f}", .{walk_src_path}), //
586 "--dep", "Walk", //
587 try std.fmt.allocPrint(arena, "-Mhtml_render={f}", .{html_render_src_path}), //
588 "--listen=-",
589 });
590
591 var child = try std.process.spawn(io, .{
592 .argv = argv.items,
593 .environ_map = &graph.environ_map,
594 .stdin = .pipe,
595 .stdout = .pipe,
596 .stderr = .pipe,
597 });
598 defer child.kill(io);
599
600 var stderr_task = try io.concurrent(readStreamAlloc, .{ gpa, io, child.stderr.?, .unlimited });
601 defer if (stderr_task.cancel(io)) |slice| gpa.free(slice) else |_| {};
602
603 var stdout_buffer: [512]u8 = undefined;
604 var stdout_reader: Io.File.Reader = .initStreaming(child.stdout.?, io, &stdout_buffer);
605 const stdout = &stdout_reader.interface;
606
607 {
608 var w = child.stdin.?.writer(io, &.{});
609 w.interface.writeStruct(std.zig.Client.Message.Header{ .tag = .update, .bytes_len = 0 }, .little) catch |err| switch (err) {
610 error.WriteFailed => return w.err.?,
611 };
612 w.interface.writeStruct(std.zig.Client.Message.Header{ .tag = .exit, .bytes_len = 0 }, .little) catch |err| switch (err) {
613 error.WriteFailed => return w.err.?,
614 };
615 }
616
617 const Header = std.zig.Server.Message.Header;
618
619 var result: ?Cache.Path = null;
620 var result_error_bundle = std.zig.ErrorBundle.empty;
621 var body_buffer: std.ArrayList(u8) = .empty;
622 defer body_buffer.deinit(gpa);
623
624 while (true) {
625 const header = stdout.takeStruct(Header, .little) catch |err| switch (err) {
626 error.ReadFailed => |e| return e,
627 error.EndOfStream => break,
628 };
629 body_buffer.clearRetainingCapacity();
630 try stdout.appendExact(gpa, &body_buffer, header.bytes_len);
631 const body = body_buffer.items;
632
633 switch (header.tag) {
634 .zig_version => {
635 if (!std.mem.eql(u8, builtin.zig_version_string, body)) {
636 return error.ZigProtocolVersionMismatch;
637 }
638 },
639 .error_bundle => {
640 result_error_bundle = try std.zig.Server.allocErrorBundle(arena, body);
641 },
642 .emit_digest => {
643 const EmitDigest = std.zig.Server.Message.EmitDigest;
644 const ebp_hdr: *align(1) const EmitDigest = @ptrCast(body);
645 if (!ebp_hdr.flags.cache_hit) {
646 log.info("source changes detected; rebuilt wasm component", .{});
647 }
648 const digest = body[@sizeOf(EmitDigest)..][0..Cache.bin_digest_len];
649 result = .{
650 .root_dir = graph.global_cache_root,
651 .sub_path = try arena.dupe(u8, "o" ++ std.fs.path.sep_str ++ Cache.binToHex(digest.*)),
652 };
653 },
654 else => {}, // ignore other messages
655 }
656 }
657
658 const stderr_contents = try stderr_task.await(io);
659 if (stderr_contents.len > 0) {
660 std.debug.print("{s}", .{stderr_contents});
661 }
662
663 // Send EOF to stdin.
664 child.stdin.?.close(io);
665 child.stdin = null;
666
667 switch (try child.wait(io)) {
668 .exited => |code| {
669 if (code != 0) {
670 log.err(
671 "the following command exited with error code {d}:\n{s}",
672 .{ code, try Build.Step.allocPrintCmd(arena, .inherit, null, argv.items) },
673 );
674 return error.WasmCompilationFailed;
675 }
676 },
677 .signal => |sig| {
678 log.err(
679 "the following command terminated with signal {t}:\n{s}",
680 .{ sig, try Build.Step.allocPrintCmd(arena, .inherit, null, argv.items) },
681 );
682 return error.WasmCompilationFailed;
683 },
684 .stopped => |sig| {
685 log.err(
686 "the following command stopped unexpectedly with signal {t}:\n{s}",
687 .{ sig, try Build.Step.allocPrintCmd(arena, .inherit, null, argv.items) },
688 );
689 return error.WasmCompilationFailed;
690 },
691 .unknown => {
692 log.err(
693 "the following command terminated unexpectedly:\n{s}",
694 .{try Build.Step.allocPrintCmd(arena, .inherit, null, argv.items)},
695 );
696 return error.WasmCompilationFailed;
697 },
698 }
699
700 if (result_error_bundle.errorMessageCount() > 0) {
701 try result_error_bundle.renderToStderr(io, .{}, .auto);
702 log.err("the following command failed with {d} compilation errors:\n{s}", .{
703 result_error_bundle.errorMessageCount(),
704 try Build.Step.allocPrintCmd(arena, .inherit, null, argv.items),
705 });
706 return error.WasmCompilationFailed;
707 }
708
709 const base_path = result orelse {
710 log.err("child process failed to report result\n{s}", .{
711 try Build.Step.allocPrintCmd(arena, .inherit, null, argv.items),
712 });
713 return error.WasmCompilationFailed;
714 };
715 const bin_name = try std.zig.binNameAlloc(arena, .{
716 .root_name = root_name,
717 .target = &(std.zig.system.resolveTargetQuery(io, std.Build.parseTargetQuery(.{
718 .arch_os_abi = arch_os_abi,
719 .cpu_features = cpu_features,
720 }) catch unreachable) catch unreachable),
721 .output_mode = .Exe,
722 });
723 return base_path.join(arena, bin_name);
724}
725
726fn readStreamAlloc(gpa: Allocator, io: Io, file: Io.File, limit: Io.Limit) ![]u8 {
727 var file_reader: Io.File.Reader = .initStreaming(file, io, &.{});
728 return file_reader.interface.allocRemaining(gpa, limit) catch |err| switch (err) {
729 error.ReadFailed => return file_reader.err.?,
730 else => |e| return e,
731 };
732}
733
734pub fn updateTimeReportCompile(ws: *WebServer, opts: struct {
735 compile: *Build.Step.Compile,
736
737 use_llvm: bool,
738 stats: abi.time_report.CompileResult.Stats,
739 ns_total: u64,
740
741 llvm_pass_timings_len: u32,
742 files_len: u32,
743 decls_len: u32,
744
745 /// The trailing data of `abi.time_report.CompileResult`, except the step name.
746 trailing: []const u8,
747}) void {
748 const gpa = ws.gpa;
749 const io = ws.graph.io;
750
751 const step_idx: u32 = for (ws.all_steps, 0..) |s, i| {
752 if (s == &opts.compile.step) break @intCast(i);
753 } else unreachable;
754
755 const old_buf = old: {
756 ws.time_report_mutex.lock(io) catch return;
757 defer ws.time_report_mutex.unlock(io);
758 const old = ws.time_report_msgs[step_idx];
759 ws.time_report_msgs[step_idx] = &.{};
760 break :old old;
761 };
762 const buf = gpa.realloc(old_buf, @sizeOf(abi.time_report.CompileResult) + opts.trailing.len) catch @panic("out of memory");
763
764 const out_header: *align(1) abi.time_report.CompileResult = @ptrCast(buf[0..@sizeOf(abi.time_report.CompileResult)]);
765 out_header.* = .{
766 .step_idx = step_idx,
767 .flags = .{
768 .use_llvm = opts.use_llvm,
769 },
770 .stats = opts.stats,
771 .ns_total = opts.ns_total,
772 .llvm_pass_timings_len = opts.llvm_pass_timings_len,
773 .files_len = opts.files_len,
774 .decls_len = opts.decls_len,
775 };
776 @memcpy(buf[@sizeOf(abi.time_report.CompileResult)..], opts.trailing);
777
778 {
779 ws.time_report_mutex.lock(io) catch return;
780 defer ws.time_report_mutex.unlock(io);
781 assert(ws.time_report_msgs[step_idx].len == 0);
782 ws.time_report_msgs[step_idx] = buf;
783 ws.time_report_update_times[step_idx] = ws.now();
784 }
785 ws.notifyUpdate();
786}
787
788pub fn updateTimeReportGeneric(ws: *WebServer, step: *Build.Step, duration: Io.Duration) void {
789 const gpa = ws.gpa;
790 const io = ws.graph.io;
791
792 const step_idx: u32 = for (ws.all_steps, 0..) |s, i| {
793 if (s == step) break @intCast(i);
794 } else unreachable;
795
796 const old_buf = old: {
797 ws.time_report_mutex.lock(io) catch return;
798 defer ws.time_report_mutex.unlock(io);
799 const old = ws.time_report_msgs[step_idx];
800 ws.time_report_msgs[step_idx] = &.{};
801 break :old old;
802 };
803 const buf = gpa.realloc(old_buf, @sizeOf(abi.time_report.GenericResult)) catch @panic("out of memory");
804 const out: *align(1) abi.time_report.GenericResult = @ptrCast(buf);
805 out.* = .{
806 .step_idx = step_idx,
807 .ns_total = @intCast(duration.toNanoseconds()),
808 };
809 {
810 ws.time_report_mutex.lock(io) catch return;
811 defer ws.time_report_mutex.unlock(io);
812 assert(ws.time_report_msgs[step_idx].len == 0);
813 ws.time_report_msgs[step_idx] = buf;
814 ws.time_report_update_times[step_idx] = ws.now();
815 }
816 ws.notifyUpdate();
817}
818
819pub fn updateTimeReportRunTest(
820 ws: *WebServer,
821 run: *Build.Step.Run,
822 tests: *const Build.Step.Run.CachedTestMetadata,
823 ns_per_test: []const u64,
824) void {
825 const gpa = ws.gpa;
826 const io = ws.graph.io;
827
828 const step_idx: u32 = for (ws.all_steps, 0..) |s, i| {
829 if (s == &run.step) break @intCast(i);
830 } else unreachable;
831
832 assert(tests.names.len == ns_per_test.len);
833 const tests_len: u32 = @intCast(tests.names.len);
834
835 const new_len: u64 = len: {
836 var names_len: u64 = 0;
837 for (0..tests_len) |i| {
838 names_len += tests.testName(@intCast(i)).len + 1;
839 }
840 break :len @sizeOf(abi.time_report.RunTestResult) + names_len + 8 * tests_len;
841 };
842 const old_buf = old: {
843 ws.time_report_mutex.lock(io) catch return;
844 defer ws.time_report_mutex.unlock(io);
845 const old = ws.time_report_msgs[step_idx];
846 ws.time_report_msgs[step_idx] = &.{};
847 break :old old;
848 };
849 const buf = gpa.realloc(old_buf, new_len) catch @panic("out of memory");
850
851 const out_header: *align(1) abi.time_report.RunTestResult = @ptrCast(buf[0..@sizeOf(abi.time_report.RunTestResult)]);
852 out_header.* = .{
853 .step_idx = step_idx,
854 .tests_len = tests_len,
855 };
856 var offset: usize = @sizeOf(abi.time_report.RunTestResult);
857 const ns_per_test_out: []align(1) u64 = @ptrCast(buf[offset..][0 .. tests_len * 8]);
858 @memcpy(ns_per_test_out, ns_per_test);
859 offset += tests_len * 8;
860 for (0..tests_len) |i| {
861 const name = tests.testName(@intCast(i));
862 @memcpy(buf[offset..][0..name.len], name);
863 buf[offset..][name.len] = 0;
864 offset += name.len + 1;
865 }
866 assert(offset == buf.len);
867
868 {
869 ws.time_report_mutex.lock(io) catch return;
870 defer ws.time_report_mutex.unlock(io);
871 assert(ws.time_report_msgs[step_idx].len == 0);
872 ws.time_report_msgs[step_idx] = buf;
873 ws.time_report_update_times[step_idx] = ws.now();
874 }
875 ws.notifyUpdate();
876}
877
878const RunnerRequest = union(enum) {
879 rebuild,
880};
881pub fn getRunnerRequest(ws: *WebServer) ?RunnerRequest {
882 const io = ws.graph.io;
883 ws.runner_request_mutex.lock(io) catch return;
884 defer ws.runner_request_mutex.unlock(io);
885 if (ws.runner_request) |req| {
886 ws.runner_request = null;
887 ws.runner_request_empty_cond.signal();
888 return req;
889 }
890 return null;
891}
892pub fn wait(ws: *WebServer) Io.Cancelable!RunnerRequest {
893 const io = ws.graph.io;
894 try ws.runner_request_mutex.lock(io);
895 defer ws.runner_request_mutex.unlock(io);
896 while (true) {
897 if (ws.runner_request) |req| {
898 ws.runner_request = null;
899 ws.runner_request_empty_cond.signal(io);
900 return req;
901 }
902 try ws.runner_request_ready_cond.wait(io, &ws.runner_request_mutex);
903 }
904}
905
906const cache_control_header: http.Header = .{
907 .name = "Cache-Control",
908 .value = "max-age=0, must-revalidate",
909};
910
911const builtin = @import("builtin");
912
913const std = @import("std");
914const Io = std.Io;
915const net = std.Io.net;
916const assert = std.debug.assert;
917const mem = std.mem;
918const log = std.log.scoped(.web_server);
919const Allocator = std.mem.Allocator;
920const Build = std.Build;
921const Cache = Build.Cache;
922const Fuzz = Build.Fuzz;
923const abi = Build.abi;
924const http = std.http;
925
926const WebServer = @This();
lib/std/Io/Dir.zig+3
......@@ -409,7 +409,10 @@ pub const PathNameError = error{
409409};
410410
411411pub const AccessError = error{
412 /// The requested `AccessOptions` would be denied to the file, or search
413 /// permission is denied for one of the directories in the path prefix.
412414 AccessDenied,
415 /// Write permission was requested but the file is immutable.
413416 PermissionDenied,
414417 FileNotFound,
415418 InputOutput,
lib/std/Io/Writer.zig+21
......@@ -1172,6 +1172,14 @@ pub fn printValue(
11721172 },
11731173 else => invalidFmtError(fmt, value),
11741174 },
1175 'q' => switch (@typeInfo(T)) {
1176 .pointer => |info| switch (info.size) {
1177 .one, .slice => return printStringEscaped(w, value),
1178 .many, .c => return printStringEscaped(w, std.mem.span(value)),
1179 },
1180 .array => return printStringEscaped(w, &value),
1181 else => invalidFmtError(fmt, value),
1182 },
11751183 'B' => switch (@typeInfo(T)) {
11761184 .int, .comptime_int => return w.printByteSize(value, .decimal, options),
11771185 .@"struct" => return value.formatByteSize(w, .decimal),
......@@ -1448,6 +1456,14 @@ fn printEnumNonexhaustive(w: *Writer, value: anytype) Error!void {
14481456 try w.writeByte(')');
14491457}
14501458
1459/// Prints a double quote, then escapes a string according to Zig string
1460/// literal rules, then a double quote.
1461pub fn printStringEscaped(w: *Writer, bytes: []const u8) Error!void {
1462 try w.writeByte('"');
1463 try std.zig.stringEscape(bytes, w);
1464 try w.writeByte('"');
1465}
1466
14511467pub fn printVector(
14521468 w: *Writer,
14531469 comptime fmt: []const u8,
......@@ -2102,6 +2118,11 @@ test "printFloat with comptime_float" {
21022118 try testing.expectFmt("1", "{}", .{1.0});
21032119}
21042120
2121test "{q} format string" {
2122 const data: []const u8 = "i\tlike\"cheese\x00\x05cheese";
2123 try testing.expectFmt("hello \"i\\tlike\\\"cheese\\x00\\x05cheese\" world", "hello {q} world", .{data});
2124}
2125
21052126fn testPrintIntCase(expected: []const u8, value: anytype, base: u8, case: std.fmt.Case, options: std.fmt.Options) !void {
21062127 var buffer: [100]u8 = undefined;
21072128 var w: Writer = .fixed(&buffer);
lib/std/Target.zig+2-2
......@@ -1668,13 +1668,13 @@ pub const Cpu = struct {
16681668 };
16691669 }
16701670
1671 pub fn parseCpuModel(arch: Arch, cpu_name: []const u8) !*const Cpu.Model {
1671 pub fn parseCpuModel(arch: Arch, cpu_name: []const u8) ?*const Cpu.Model {
16721672 for (arch.allCpuModels()) |cpu| {
16731673 if (std.mem.eql(u8, cpu_name, cpu.name)) {
16741674 return cpu;
16751675 }
16761676 }
1677 return error.UnknownCpuModel;
1677 return null;
16781678 }
16791679
16801680 pub fn endian(arch: Arch) std.builtin.Endian {
lib/std/Target/Query.zig+1-1
......@@ -282,7 +282,7 @@ pub fn parse(args: ParseOptions) !Query {
282282 } else if (mem.eql(u8, cpu_name, "baseline")) {
283283 result.cpu_model = .baseline;
284284 } else {
285 result.cpu_model = .{ .explicit = try arch.parseCpuModel(cpu_name) };
285 result.cpu_model = .{ .explicit = arch.parseCpuModel(cpu_name) orelse return error.UnknownCpuModel };
286286 }
287287
288288 while (index < cpu_features.len) {
lib/std/array_list.zig+14-8
......@@ -1391,12 +1391,19 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type {
13911391 return self.allocatedSlice()[self.items.len..];
13921392 }
13931393
1394 /// Returns the last element from the list, or `null` if the list is empty.
1394 /// Deprecated in favor of `last`.
13951395 pub fn getLast(self: Self) ?T {
13961396 if (self.items.len == 0) return null;
13971397 return self.items[self.items.len - 1];
13981398 }
13991399
1400 /// Returns a pointer to the last element from the list, or `null` if
1401 /// the list is empty.
1402 pub fn last(self: Self) ?*T {
1403 if (self.items.len == 0) return null;
1404 return &self.items[self.items.len - 1];
1405 }
1406
14001407 /// Called when memory growth is necessary. Returns a capacity larger than
14011408 /// minimum that grows super-linearly.
14021409 pub fn growCapacity(minimum: usize) usize {
......@@ -2378,17 +2385,16 @@ test "Managed(?u32).pop()" {
23782385 try testing.expect(list.pop() == null);
23792386}
23802387
2381test "Managed(u32).getLast()" {
2388test "last" {
23822389 const a = testing.allocator;
23832390
2384 var list = Managed(u32).init(a);
2385 defer list.deinit();
2391 var list: ArrayList(u32) = .empty;
2392 defer list.deinit(a);
23862393
2387 try testing.expectEqual(list.getLast(), null);
2394 try testing.expectEqual(list.last(), null);
23882395
2389 try list.append(2);
2390 const const_list = list;
2391 try testing.expectEqual(const_list.getLast().?, 2);
2396 try list.append(a, 2);
2397 try testing.expectEqual(list.last().?.*, 2);
23922398}
23932399
23942400test "return OutOfMemory when capacity would exceed maximum usize integer value" {
lib/std/lang.zig+2-2
......@@ -93,7 +93,7 @@ pub const AtomicRmwOp = enum {
9393///
9494/// This data structure is used by the Zig language code generation and
9595/// therefore must be kept in sync with the compiler implementation.
96pub const CodeModel = enum {
96pub const CodeModel = enum(u4) {
9797 default,
9898 extreme,
9999 kernel,
......@@ -873,7 +873,7 @@ pub const OutputMode = enum {
873873
874874/// This data structure is used by the Zig language code generation and
875875/// therefore must be kept in sync with the compiler implementation.
876pub const LinkMode = enum {
876pub const LinkMode = enum(u1) {
877877 static,
878878 dynamic,
879879};
lib/std/mem/Allocator.zig+3-3
......@@ -169,7 +169,7 @@ pub fn create(a: Allocator, comptime T: type) Error!*T {
169169 const ptr = comptime std.mem.alignBackward(usize, math.maxInt(usize), @alignOf(T));
170170 return @ptrFromInt(ptr);
171171 }
172 const ptr: *T = @ptrCast(try a.allocBytesWithAlignment(.of(T), @sizeOf(T), @returnAddress()));
172 const ptr: *T = @ptrCast(try a.allocBytesAligned(.of(T), @sizeOf(T), @returnAddress()));
173173 return ptr;
174174}
175175
......@@ -285,10 +285,10 @@ fn allocWithSizeAndAlignment(
285285 return_address: usize,
286286) Error![*]align(alignment.toByteUnits()) u8 {
287287 const byte_count = math.mul(usize, size, n) catch return error.OutOfMemory;
288 return self.allocBytesWithAlignment(alignment, byte_count, return_address);
288 return self.allocBytesAligned(alignment, byte_count, return_address);
289289}
290290
291fn allocBytesWithAlignment(
291pub fn allocBytesAligned(
292292 self: Allocator,
293293 comptime alignment: Alignment,
294294 byte_count: usize,
lib/std/process.zig+7
......@@ -1113,3 +1113,10 @@ test protectMemory {
11131113 protectMemory(&test_page, .{}) catch return error.SkipZigTest;
11141114 protectMemory(&test_page, .{ .read = true, .write = true }) catch return error.SkipZigTest;
11151115}
1116
1117test {
1118 _ = Child;
1119 _ = Args;
1120 _ = Environ;
1121 _ = Preopens;
1122}
lib/std/process/Child.zig+20
......@@ -96,6 +96,22 @@ pub const Term = union(enum) {
9696 signal: std.posix.SIG,
9797 stopped: std.posix.SIG,
9898 unknown: u32,
99
100 pub fn success(t: Term) bool {
101 return switch (t) {
102 .exited => |code| code == 0,
103 else => false,
104 };
105 }
106
107 pub fn format(t: Term, w: *Io.Writer) Io.Writer.Error!void {
108 switch (t) {
109 .exited => |code| return w.print("exited with code {d}", .{code}),
110 .signal => |sig| return w.print("terminated with signal {t}", .{sig}),
111 .stopped => |sig| return w.print("stopped with signal {t}", .{sig}),
112 .unknown => return w.writeAll("terminated unexpectedly"),
113 }
114 }
99115};
100116
101117pub const Cwd = union(enum) {
......@@ -135,3 +151,7 @@ pub fn wait(child: *Child, io: Io) WaitError!Term {
135151 assert(child.id != null);
136152 return io.vtable.childWait(io.userdata, child);
137153}
154
155test {
156 _ = Term;
157}
lib/std/process/Environ.zig+27-3
......@@ -96,6 +96,7 @@ pub const WindowsBlock = struct {
9696 }
9797};
9898
99/// Each key and each value are allocated independently and owned by this data structure.
99100pub const Map = struct {
100101 array_hash_map: ArrayHashMap,
101102 allocator: Allocator,
......@@ -340,9 +341,6 @@ pub const Map = struct {
340341 /// Returns a full copy of `em` allocated with `gpa`, which is not necessarily
341342 /// the same allocator used to allocate `em`.
342343 pub fn clone(m: *const Map, gpa: Allocator) Allocator.Error!Map {
343 // Since we need to dupe the keys and values, the only way for error handling to not be a
344 // nightmare is to add keys to an empty map one-by-one. This could be avoided if this
345 // abstraction were a bit less... OOP-esque.
346344 var new: Map = .init(gpa);
347345 errdefer new.deinit();
348346 try new.array_hash_map.ensureUnusedCapacity(gpa, m.array_hash_map.count());
......@@ -352,6 +350,32 @@ pub const Map = struct {
352350 return new;
353351 }
354352
353 /// Adds all the key-value pairs from `other` into this `m`.
354 pub fn putAll(m: *Map, other: *const Map) Allocator.Error!void {
355 const gpa = m.allocator;
356 try m.array_hash_map.ensureUnusedCapacity(gpa, other.array_hash_map.count());
357 const start = m.count();
358 errdefer while (m.array_hash_map.count() > start) {
359 const kv = m.array_hash_map.pop().?;
360 gpa.free(kv.key);
361 gpa.free(kv.value);
362 };
363 for (other.array_hash_map.keys(), other.array_hash_map.values()) |key, value| {
364 try m.put(key, value);
365 }
366 }
367
368 /// Set the length to zero, freeing all key and value memory, not freeing
369 /// the allocation for the entries.
370 pub fn clearRetainingCapacity(m: *Map) void {
371 const gpa = m.allocator;
372 for (m.array_hash_map.keys(), m.array_hash_map.values()) |k, v| {
373 gpa.free(k);
374 gpa.free(v);
375 }
376 m.array_hash_map.clearRetainingCapacity();
377 }
378
355379 /// Creates a null-delimited environment variable block in the format
356380 /// expected by POSIX, from a hash map plus options.
357381 pub fn createPosixBlock(
lib/std/zig.zig+95-18
......@@ -33,6 +33,7 @@ pub const AstRlAnnotate = @import("zig/AstRlAnnotate.zig");
3333pub const LibCInstallation = @import("zig/LibCInstallation.zig");
3434pub const WindowsSdk = @import("zig/WindowsSdk.zig");
3535pub const LibCDirs = @import("zig/LibCDirs.zig");
36pub const PkgConfig = @import("zig/PkgConfig.zig");
3637pub const target = @import("zig/target.zig");
3738pub const llvm = @import("zig/llvm.zig");
3839
......@@ -146,7 +147,10 @@ pub fn lineDelta(source: []const u8, start: usize, end: usize) isize {
146147
147148pub const BinNameOptions = struct {
148149 root_name: []const u8,
149 target: *const std.Target,
150 cpu_arch: std.Target.Cpu.Arch,
151 os_tag: std.Target.Os.Tag,
152 ofmt: std.Target.ObjectFormat,
153 abi: std.Target.Abi,
150154 output_mode: std.builtin.OutputMode,
151155 link_mode: ?std.builtin.LinkMode = null,
152156 version: ?std.SemanticVersion = null,
......@@ -155,10 +159,12 @@ pub const BinNameOptions = struct {
155159/// Returns the standard file system basename of a binary generated by the Zig compiler.
156160pub fn binNameAlloc(allocator: Allocator, options: BinNameOptions) error{OutOfMemory}![]u8 {
157161 const root_name = options.root_name;
158 const t = options.target;
159 switch (t.ofmt) {
162 switch (options.ofmt) {
160163 .coff => switch (options.output_mode) {
161 .Exe => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, t.exeFileExt() }),
164 .Exe => return std.fmt.allocPrint(allocator, "{s}{s}", .{
165 root_name,
166 options.os_tag.exeFileExt(options.cpu_arch),
167 }),
162168 .Lib => {
163169 const suffix = switch (options.link_mode orelse .static) {
164170 .static => ".lib",
......@@ -173,16 +179,16 @@ pub fn binNameAlloc(allocator: Allocator, options: BinNameOptions) error{OutOfMe
173179 .Lib => {
174180 switch (options.link_mode orelse .static) {
175181 .static => return std.fmt.allocPrint(allocator, "{s}{s}.a", .{
176 t.libPrefix(), root_name,
182 options.os_tag.libPrefix(options.abi), root_name,
177183 }),
178184 .dynamic => {
179185 if (options.version) |ver| {
180186 return std.fmt.allocPrint(allocator, "{s}{s}.so.{d}.{d}.{d}", .{
181 t.libPrefix(), root_name, ver.major, ver.minor, ver.patch,
187 options.os_tag.libPrefix(options.abi), root_name, ver.major, ver.minor, ver.patch,
182188 });
183189 } else {
184190 return std.fmt.allocPrint(allocator, "{s}{s}.so", .{
185 t.libPrefix(), root_name,
191 options.os_tag.libPrefix(options.abi), root_name,
186192 });
187193 }
188194 },
......@@ -195,16 +201,16 @@ pub fn binNameAlloc(allocator: Allocator, options: BinNameOptions) error{OutOfMe
195201 .Lib => {
196202 switch (options.link_mode orelse .static) {
197203 .static => return std.fmt.allocPrint(allocator, "{s}{s}.a", .{
198 t.libPrefix(), root_name,
204 options.os_tag.libPrefix(options.abi), root_name,
199205 }),
200206 .dynamic => {
201207 if (options.version) |ver| {
202208 return std.fmt.allocPrint(allocator, "{s}{s}.{d}.{d}.{d}.dylib", .{
203 t.libPrefix(), root_name, ver.major, ver.minor, ver.patch,
209 options.os_tag.libPrefix(options.abi), root_name, ver.major, ver.minor, ver.patch,
204210 });
205211 } else {
206212 return std.fmt.allocPrint(allocator, "{s}{s}.dylib", .{
207 t.libPrefix(), root_name,
213 options.os_tag.libPrefix(options.abi), root_name,
208214 });
209215 }
210216 },
......@@ -213,11 +219,14 @@ pub fn binNameAlloc(allocator: Allocator, options: BinNameOptions) error{OutOfMe
213219 .Obj => return std.fmt.allocPrint(allocator, "{s}.o", .{root_name}),
214220 },
215221 .wasm => switch (options.output_mode) {
216 .Exe => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, t.exeFileExt() }),
222 .Exe => return std.fmt.allocPrint(allocator, "{s}{s}", .{
223 root_name,
224 options.os_tag.exeFileExt(options.cpu_arch),
225 }),
217226 .Lib => {
218227 switch (options.link_mode orelse .static) {
219228 .static => return std.fmt.allocPrint(allocator, "{s}{s}.a", .{
220 t.libPrefix(), root_name,
229 options.os_tag.libPrefix(options.abi), root_name,
221230 }),
222231 .dynamic => return std.fmt.allocPrint(allocator, "{s}.wasm", .{root_name}),
223232 }
......@@ -231,10 +240,10 @@ pub fn binNameAlloc(allocator: Allocator, options: BinNameOptions) error{OutOfMe
231240 .plan9 => switch (options.output_mode) {
232241 .Exe => return allocator.dupe(u8, root_name),
233242 .Obj => return std.fmt.allocPrint(allocator, "{s}{s}", .{
234 root_name, t.ofmt.fileExt(t.cpu.arch),
243 root_name, options.ofmt.fileExt(options.cpu_arch),
235244 }),
236245 .Lib => return std.fmt.allocPrint(allocator, "{s}{s}.a", .{
237 t.libPrefix(), root_name,
246 options.os_tag.libPrefix(options.abi), root_name,
238247 }),
239248 },
240249 }
......@@ -374,9 +383,9 @@ pub const Subsystem = enum {
374383 pub const EfiRuntimeDriver: Subsystem = .efi_runtime_driver;
375384};
376385
377pub const CompressDebugSections = enum { none, zlib, zstd };
386pub const CompressDebugSections = enum(u2) { none, zlib, zstd };
378387
379pub const RcIncludes = enum {
388pub const RcIncludes = enum(u2) {
380389 /// Use MSVC if available, fall back to MinGW.
381390 any,
382391 /// Use MSVC include paths (MSVC install + Windows SDK, must be present on the system).
......@@ -672,7 +681,7 @@ pub fn putAstErrorsIntoBundle(
672681
673682pub fn resolveTargetQueryOrFatal(io: Io, target_query: std.Target.Query) std.Target {
674683 return std.zig.system.resolveTargetQuery(io, target_query) catch |err|
675 std.process.fatal("unable to resolve target: {s}", .{@errorName(err)});
684 std.process.fatal("unable to resolve target: {t}", .{err});
676685}
677686
678687pub fn parseTargetQueryOrReportFatalError(
......@@ -747,7 +756,6 @@ pub const EnvVar = enum {
747756 ZIG_LOCAL_PKG_DIR,
748757 ZIG_LIB_DIR,
749758 ZIG_LIBC,
750 ZIG_BUILD_RUNNER,
751759 ZIG_BUILD_ERROR_STYLE,
752760 ZIG_BUILD_MULTILINE_ERRORS,
753761 ZIG_VERBOSE_LINK,
......@@ -764,6 +772,7 @@ pub const EnvVar = enum {
764772 CPLUS_INCLUDE_PATH,
765773 LIBRARY_PATH,
766774 CC,
775 PKG_CONFIG,
767776
768777 // Terminal integration
769778 NO_COLOR,
......@@ -1157,6 +1166,74 @@ pub const ClangCliParam = struct {
11571166 }
11581167};
11591168
1169pub const AllocPrintCmdOptions = struct {
1170 cwd: ?[]const u8 = null,
1171 parent_env: ?*const std.process.Environ.Map = null,
1172 child_env: ?*const std.process.Environ.Map = null,
1173};
1174
1175pub fn allocPrintCmd(gpa: Allocator, argv: []const []const u8, options: AllocPrintCmdOptions) Allocator.Error![]u8 {
1176 const shell = struct {
1177 fn escape(writer: *Io.Writer, string: []const u8, is_argv0: bool) !void {
1178 for (string) |c| {
1179 if (switch (c) {
1180 else => true,
1181 '%', '+'...':', '@'...'Z', '_', 'a'...'z' => false,
1182 '=' => is_argv0,
1183 }) break;
1184 } else return writer.writeAll(string);
1185
1186 try writer.writeByte('"');
1187 for (string) |c| {
1188 if (switch (c) {
1189 std.ascii.control_code.nul => break,
1190 '!', '"', '$', '\\', '`' => true,
1191 else => !std.ascii.isPrint(c),
1192 }) try writer.writeByte('\\');
1193 switch (c) {
1194 std.ascii.control_code.nul => unreachable,
1195 std.ascii.control_code.bel => try writer.writeByte('a'),
1196 std.ascii.control_code.bs => try writer.writeByte('b'),
1197 std.ascii.control_code.ht => try writer.writeByte('t'),
1198 std.ascii.control_code.lf => try writer.writeByte('n'),
1199 std.ascii.control_code.vt => try writer.writeByte('v'),
1200 std.ascii.control_code.ff => try writer.writeByte('f'),
1201 std.ascii.control_code.cr => try writer.writeByte('r'),
1202 std.ascii.control_code.esc => try writer.writeByte('E'),
1203 ' '...'~' => try writer.writeByte(c),
1204 else => try writer.print("{o:0>3}", .{c}),
1205 }
1206 }
1207 try writer.writeByte('"');
1208 }
1209 };
1210
1211 var aw: Io.Writer.Allocating = .init(gpa);
1212 defer aw.deinit();
1213 const writer = &aw.writer;
1214 if (options.cwd) |path| {
1215 writer.print("cd {s} && ", .{path}) catch return error.OutOfMemory;
1216 }
1217 if (options.child_env) |child_env| {
1218 for (child_env.keys(), child_env.values()) |key, value| {
1219 if (options.parent_env) |parent_env| {
1220 if (parent_env.get(key)) |process_value| {
1221 if (std.mem.eql(u8, value, process_value)) continue;
1222 }
1223 }
1224 writer.print("{s}=", .{key}) catch return error.OutOfMemory;
1225 shell.escape(writer, value, false) catch return error.OutOfMemory;
1226 writer.writeByte(' ') catch return error.OutOfMemory;
1227 }
1228 }
1229 shell.escape(writer, argv[0], true) catch return error.OutOfMemory;
1230 for (argv[1..]) |arg| {
1231 writer.writeByte(' ') catch return error.OutOfMemory;
1232 shell.escape(writer, arg, false) catch return error.OutOfMemory;
1233 }
1234 return aw.toOwnedSlice();
1235}
1236
11601237test {
11611238 _ = Ast;
11621239 _ = AstRlAnnotate;
lib/std/zig/LibCInstallation.zig+16-2
......@@ -13,6 +13,7 @@ const Target = std.Target;
1313const fs = std.fs;
1414const Allocator = std.mem.Allocator;
1515const Path = std.Build.Cache.Path;
16const Cache = std.Build.Cache;
1617const log = std.log.scoped(.libc_installation);
1718const Environ = std.process.Environ;
1819
......@@ -990,7 +991,7 @@ pub fn resolveCrtPaths(
990991 target: *const std.Target,
991992) error{ OutOfMemory, LibCInstallationMissingCrtDir }!CrtPaths {
992993 const crt_dir_path: Path = .{
993 .root_dir = std.Build.Cache.Directory.cwd(),
994 .root_dir = Cache.Directory.cwd(),
994995 .sub_path = lci.crt_dir orelse return error.LibCInstallationMissingCrtDir,
995996 };
996997 switch (target.os.tag) {
......@@ -1016,7 +1017,7 @@ pub fn resolveCrtPaths(
10161017 },
10171018 .haiku, .serenity => {
10181019 const gcc_dir_path: Path = .{
1019 .root_dir = std.Build.Cache.Directory.cwd(),
1020 .root_dir = Cache.Directory.cwd(),
10201021 .sub_path = lci.gcc_dir orelse return error.LibCInstallationMissingCrtDir,
10211022 };
10221023 return .{
......@@ -1038,3 +1039,16 @@ pub fn resolveCrtPaths(
10381039 },
10391040 }
10401041}
1042
1043pub fn addToHash(opt_lci: ?*const LibCInstallation, hh: *Cache.HashHelper, abi: std.Target.Abi) void {
1044 const lci = opt_lci orelse return hh.add(false);
1045 hh.add(true);
1046 hh.addOptionalBytes(lci.crt_dir);
1047 switch (abi) {
1048 .msvc, .itanium => {
1049 hh.addOptionalBytes(lci.msvc_lib_dir);
1050 hh.addOptionalBytes(lci.kernel32_lib_dir);
1051 },
1052 else => {},
1053 }
1054}
lib/std/zig/PkgConfig.zig created+146
......@@ -0,0 +1,146 @@
1//! The more reusable pieces of the build system's pkg-config integration logic.
2const PkgConfig = @This();
3
4const std = @import("../std.zig");
5const mem = std.mem;
6const Allocator = std.mem.Allocator;
7const assert = std.debug.assert;
8
9all: []const Pkg,
10
11pub const Pkg = struct {
12 name: []const u8,
13 desc: []const u8,
14};
15
16pub const InitError = Allocator.Error || error{InvalidPkgConfigOutput};
17
18pub const Diagnostic = struct {
19 invalid_line_index: usize,
20 invalid_line: []const u8,
21};
22
23/// Parses the output of `pkg-config --list-all`.
24pub fn init(arena: Allocator, stdout: []const u8, diagnostic: ?*Diagnostic) InitError!PkgConfig {
25 var list: std.ArrayList(Pkg) = .empty;
26 var line_it = mem.tokenizeAny(u8, stdout, "\r\n");
27 var line_index: usize = 0;
28 while (line_it.next()) |line| : (line_index += 1) {
29 if (mem.trim(u8, line, " \t").len == 0) continue;
30 var tok_it = mem.tokenizeAny(u8, line, " \t");
31 try list.append(arena, .{
32 .name = tok_it.next() orelse {
33 if (diagnostic) |d| d.* = .{
34 .invalid_line_index = line_index,
35 .invalid_line = line,
36 };
37 return error.InvalidPkgConfigOutput;
38 },
39 .desc = tok_it.rest(),
40 });
41 }
42 try list.shrinkToLen(arena);
43 return .{ .all = list.toOwnedSliceAssert() };
44}
45
46// Maps the library name to pkg config name. Unfortunately, there are several
47// examples where this is not straightforward:
48// * -lSDL2 -> pkg-config sdl2
49// * -lgdk-3 -> pkg-config gdk-3.0
50// * -latk-1.0 -> pkg-config atk
51// * -lpulse -> pkg-config libpulse
52pub fn find(pc: *const PkgConfig, lib_name: []const u8) ?usize {
53 const all = pc.all;
54
55 // Exact match means instant winner.
56 for (all, 0..) |pkg, i| {
57 if (mem.eql(u8, pkg.name, lib_name))
58 return i;
59 }
60
61 // Next we'll try ignoring case.
62 for (all, 0..) |pkg, i| {
63 if (std.ascii.eqlIgnoreCase(pkg.name, lib_name))
64 return i;
65 }
66
67 // Prefixed "lib" or suffixed ".0".
68 for (all, 0..) |pkg, i| {
69 if (std.ascii.findIgnoreCase(pkg.name, lib_name)) |pos| {
70 const prefix = pkg.name[0..pos];
71 const suffix = pkg.name[pos + lib_name.len ..];
72 if (prefix.len > 0 and !mem.eql(u8, prefix, "lib")) continue;
73 if (suffix.len > 0 and !mem.eql(u8, suffix, ".0")) continue;
74 return i;
75 }
76 }
77
78 // Trimming "-1.0".
79 if (mem.cutSuffix(u8, lib_name, "-1.0")) |trimmed| {
80 for (all, 0..) |pkg, i| {
81 if (std.ascii.eqlIgnoreCase(pkg.name, trimmed)) {
82 return i;
83 }
84 }
85 }
86
87 return null;
88}
89
90pub fn exe(environ_map: *const std.process.Environ.Map) []const u8 {
91 return std.zig.EnvVar.PKG_CONFIG.get(environ_map) orelse "pkg-config";
92}
93
94pub const Parsed = struct {
95 cflags: []const []const u8,
96 libs: []const []const u8,
97 unknown_flags: []const []const u8,
98};
99
100pub const ParseError = Allocator.Error || error{InvalidPkgConfigOutput};
101
102/// Parses the output of `pkg-config [name] --cflags --libs`.
103pub fn parse(arena: Allocator, stdout: []const u8) ParseError!Parsed {
104 var zig_cflags: std.ArrayList([]const u8) = .empty;
105 var zig_libs: std.ArrayList([]const u8) = .empty;
106 var unknown_flags: std.ArrayList([]const u8) = .empty;
107 var arg_it = mem.tokenizeAny(u8, stdout, " \r\n\t");
108
109 while (arg_it.next()) |arg| {
110 if (mem.eql(u8, arg, "-I")) {
111 const dir = arg_it.next() orelse return error.InvalidPkgConfigOutput;
112 try zig_cflags.appendSlice(arena, &.{ "-I", dir });
113 } else if (mem.startsWith(u8, arg, "-I")) {
114 try zig_cflags.append(arena, arg);
115 } else if (mem.eql(u8, arg, "-L")) {
116 const dir = arg_it.next() orelse return error.InvalidPkgConfigOutput;
117 try zig_libs.appendSlice(arena, &.{ "-L", dir });
118 } else if (mem.startsWith(u8, arg, "-L")) {
119 try zig_libs.append(arena, arg);
120 } else if (mem.eql(u8, arg, "-l")) {
121 const lib = arg_it.next() orelse return error.InvalidPkgConfigOutput;
122 try zig_libs.appendSlice(arena, &.{ "-l", lib });
123 } else if (mem.startsWith(u8, arg, "-l")) {
124 try zig_libs.append(arena, arg);
125 } else if (mem.eql(u8, arg, "-D")) {
126 const macro = arg_it.next() orelse return error.InvalidPkgConfigOutput;
127 try zig_cflags.appendSlice(arena, &.{ "-D", macro });
128 } else if (mem.startsWith(u8, arg, "-D")) {
129 try zig_cflags.append(arena, arg);
130 } else if (mem.cutPrefix(u8, arg, "-Wl,-rpath,")) |rest| {
131 try zig_cflags.appendSlice(arena, &.{ "-rpath", rest });
132 } else {
133 try unknown_flags.append(arena, arg);
134 }
135 }
136
137 try zig_cflags.shrinkToLen(arena);
138 try zig_libs.shrinkToLen(arena);
139 try unknown_flags.shrinkToLen(arena);
140
141 return .{
142 .cflags = zig_cflags.toOwnedSliceAssert(),
143 .libs = zig_libs.toOwnedSliceAssert(),
144 .unknown_flags = unknown_flags.toOwnedSliceAssert(),
145 };
146}
lib/std/zig/system.zig+16-17
......@@ -28,6 +28,8 @@ pub const Executor = union(enum) {
2828};
2929
3030pub const GetExternalExecutorOptions = struct {
31 host_cpu_arch: std.Target.Cpu.Arch,
32 host_os_tag: std.Target.Os.Tag,
3133 allow_darling: bool = true,
3234 allow_qemu: bool = true,
3335 allow_rosetta: bool = true,
......@@ -39,24 +41,21 @@ pub const GetExternalExecutorOptions = struct {
3941
4042/// Return whether or not the given host is capable of running executables of
4143/// the other target.
42pub fn getExternalExecutor(
43 io: Io,
44 host: *const std.Target,
45 candidate: *const std.Target,
46 options: GetExternalExecutorOptions,
47) Executor {
48 const os_match = host.os.tag == candidate.os.tag;
44pub fn getExternalExecutor(io: Io, candidate: *const std.Target, options: GetExternalExecutorOptions) Executor {
45 const host_os_tag = options.host_os_tag;
46 const host_cpu_arch = options.host_cpu_arch;
47 const os_match = host_os_tag == candidate.os.tag;
4948 const cpu_ok = cpu_ok: {
50 if (host.cpu.arch == candidate.cpu.arch)
49 if (host_cpu_arch == candidate.cpu.arch)
5150 break :cpu_ok true;
5251
53 if (host.cpu.arch == .x86_64 and candidate.cpu.arch == .x86)
52 if (host_cpu_arch == .x86_64 and candidate.cpu.arch == .x86)
5453 break :cpu_ok true;
5554
56 if (host.cpu.arch == .aarch64 and candidate.cpu.arch == .arm)
55 if (host_cpu_arch == .aarch64 and candidate.cpu.arch == .arm)
5756 break :cpu_ok true;
5857
59 if (host.cpu.arch == .aarch64_be and candidate.cpu.arch == .armeb)
58 if (host_cpu_arch == .aarch64_be and candidate.cpu.arch == .armeb)
6059 break :cpu_ok true;
6160
6261 // TODO additionally detect incompatible CPU features.
......@@ -83,7 +82,7 @@ pub fn getExternalExecutor(
8382 // If the OS match and OS is macOS and CPU is arm64, we can use Rosetta 2
8483 // to emulate the foreign architecture.
8584 if (options.allow_rosetta and os_match and
86 (host.os.tag == .maccatalyst or host.os.tag == .macos) and host.cpu.arch == .aarch64)
85 (host_os_tag == .maccatalyst or host_os_tag == .macos) and host_cpu_arch == .aarch64)
8786 {
8887 switch (candidate.cpu.arch) {
8988 .x86_64 => return .rosetta,
......@@ -173,13 +172,13 @@ pub fn getExternalExecutor(
173172 .windows => {
174173 if (options.allow_wine) {
175174 const wine_supported = switch (candidate.cpu.arch) {
176 .thumb => switch (host.cpu.arch) {
175 .thumb => switch (host_cpu_arch) {
177176 .arm, .thumb, .aarch64 => true,
178177 else => false,
179178 },
180 .aarch64 => host.cpu.arch == .aarch64,
181 .x86 => host.cpu.arch.isX86(),
182 .x86_64 => host.cpu.arch == .x86_64,
179 .aarch64 => host_cpu_arch == .aarch64,
180 .x86 => host_cpu_arch.isX86(),
181 .x86_64 => host_cpu_arch == .x86_64,
183182 else => false,
184183 };
185184 return if (wine_supported) .{ .wine = "wine" } else bad_result;
......@@ -191,7 +190,7 @@ pub fn getExternalExecutor(
191190 // This check can be loosened once darling adds a QEMU-based emulation
192191 // layer for non-host architectures:
193192 // https://github.com/darlinghq/darling/issues/863
194 if (candidate.cpu.arch != host.cpu.arch) {
193 if (candidate.cpu.arch != host_cpu_arch) {
195194 return bad_result;
196195 }
197196 return .{ .darling = "darling" };
lib/std/zon/Serializer.zig+6-2
......@@ -122,7 +122,7 @@ pub fn valueMaxDepth(self: *Serializer, val: anytype, options: ValueOptions, dep
122122
123123/// Serialize a value, similar to `serializeArbitraryDepth`.
124124pub fn valueArbitraryDepth(self: *Serializer, val: anytype, options: ValueOptions) Error!void {
125 comptime assert(canSerializeType(@TypeOf(val)));
125 comptime assertCanSerializeType(@TypeOf(val));
126126 switch (@typeInfo(@TypeOf(val))) {
127127 .int, .comptime_int => if (options.emit_codepoint_literals.emitAsCodepoint(val)) |c| {
128128 self.codePoint(c) catch |err| switch (err) {
......@@ -321,7 +321,7 @@ pub fn tupleArbitraryDepth(
321321}
322322
323323fn tupleImpl(self: *Serializer, val: anytype, options: ValueOptions) Error!void {
324 comptime assert(canSerializeType(@TypeOf(val)));
324 comptime assertCanSerializeType(@TypeOf(val));
325325 switch (@typeInfo(@TypeOf(val))) {
326326 .@"struct" => {
327327 var container = try self.beginTuple(.{ .whitespace_style = .{ .fields = val.len } });
......@@ -814,6 +814,10 @@ test checkValueDepth {
814814 try expectValueDepthEquals(3, @as([]const []const u8, &.{&.{ 1, 2, 3 }}));
815815}
816816
817inline fn assertCanSerializeType(T: type) void {
818 if (!canSerializeType(T)) @compileError("cannot serialize: " ++ @typeName(T));
819}
820
817821inline fn canSerializeType(T: type) bool {
818822 comptime return canSerializeTypeInner(T, &.{}, false);
819823}
src/Compilation.zig+14-17
......@@ -752,13 +752,10 @@ pub const Directories = struct {
752752 else => []const u8,
753753 },
754754 environ_map: *const std.process.Environ.Map,
755 cwd: []const u8,
755756 ) Directories {
756757 const wasi = builtin.target.os.tag == .wasi;
757758
758 const cwd = introspect.getResolvedCwd(io, arena) catch |err| {
759 fatal("unable to get cwd: {t}", .{err});
760 };
761
762759 const zig_lib: Cache.Directory = d: {
763760 if (override_zig_lib) |path| break :d openUnresolved(arena, io, cwd, path, .@"zig lib");
764761 if (wasi) break :d getPreopen(preopens, "/lib");
......@@ -1750,9 +1747,13 @@ pub const CreateOptions = struct {
17501747 .no => return null,
17511748 .yes_cache => {
17521749 assert(opts.cache_mode != .none);
1750 const target = &opts.root_mod.resolved_target.result;
17531751 return try ea.cacheName(arena, .{
17541752 .root_name = opts.root_name,
1755 .target = &opts.root_mod.resolved_target.result,
1753 .cpu_arch = target.cpu.arch,
1754 .os_tag = target.os.tag,
1755 .ofmt = target.ofmt,
1756 .abi = target.abi,
17561757 .output_mode = opts.config.output_mode,
17571758 .link_mode = opts.config.link_mode,
17581759 .version = opts.version,
......@@ -3236,9 +3237,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
32363237 }
32373238
32383239 // Failure here only means an unnecessary cache miss.
3239 man.writeManifest() catch |err| {
3240 log.warn("failed to write cache manifest: {s}", .{@errorName(err)});
3241 };
3240 man.writeManifest() catch |err| log.warn("failed to write cache manifest: {t}", .{err});
32423241
32433242 assert(whole.lock == null);
32443243 whole.lock = man.toOwnedLock();
......@@ -3528,14 +3527,7 @@ fn addNonIncrementalStuffToCacheManifest(
35283527 man.hash.addListOfBytes(opts.rpath_list);
35293528 man.hash.addListOfBytes(opts.symbol_wrap_set.keys());
35303529 if (comp.config.link_libc) {
3531 man.hash.add(comp.libc_installation != null);
3532 if (comp.libc_installation) |libc_installation| {
3533 man.hash.addOptionalBytes(libc_installation.crt_dir);
3534 if (target.abi == .msvc or target.abi == .itanium) {
3535 man.hash.addOptionalBytes(libc_installation.msvc_lib_dir);
3536 man.hash.addOptionalBytes(libc_installation.kernel32_lib_dir);
3537 }
3538 }
3530 LibCInstallation.addToHash(comp.libc_installation, &man.hash, target.abi);
35393531 man.hash.addOptionalBytes(target.dynamic_linker.get());
35403532 }
35413533 man.hash.add(opts.repro);
......@@ -7473,9 +7465,14 @@ pub fn build_crt_file(
74737465 defer arena_allocator.deinit();
74747466 const arena = arena_allocator.allocator();
74757467
7468 const target = &comp.root_mod.resolved_target.result;
7469
74767470 const basename = try std.zig.binNameAlloc(gpa, .{
74777471 .root_name = root_name,
7478 .target = &comp.root_mod.resolved_target.result,
7472 .cpu_arch = target.cpu.arch,
7473 .os_tag = target.os.tag,
7474 .ofmt = target.ofmt,
7475 .abi = target.abi,
74797476 .output_mode = output_mode,
74807477 });
74817478
src/Package/Fetch.zig+2-2
......@@ -782,7 +782,7 @@ fn runResource(
782782 f.package_root = try ls.pkg_root.join(arena, computed_package_hash.toSlice());
783783 renameTmpIntoCache(io, package_sub_path, f.package_root) catch |err| {
784784 try eb.addRootErrorMessage(.{ .msg = try eb.printString(
785 "unable to rename temporary directory {f} into package cache directory {f}: {t}",
785 "failed renaming temporary directory {f} into package cache directory {f}: {t}",
786786 .{ package_sub_path, f.package_root, err },
787787 ) });
788788 return error.FetchFailed;
......@@ -802,7 +802,7 @@ fn runResource(
802802 if (!package_sub_path.eql(tmp_directory_path)) {
803803 tmp_directory_path.root_dir.handle.deleteDir(io, tmp_directory_path.sub_path) catch |err| switch (err) {
804804 error.Canceled => |e| return e,
805 else => |e| log.warn("failed to delete temporary directory {f}: {t}", .{ tmp_directory_path, e }),
805 else => |e| log.warn("failed deleting temporary directory {f}: {t}", .{ tmp_directory_path, e }),
806806 };
807807 }
808808
src/libs/libtsan.zig+4-1
......@@ -41,7 +41,10 @@ pub fn buildTsan(comp: *Compilation, prog_node: std.Progress.Node) BuildError!vo
4141 const output_mode = .Lib;
4242 const basename = try std.zig.binNameAlloc(arena, .{
4343 .root_name = root_name,
44 .target = target,
44 .cpu_arch = target.cpu.arch,
45 .os_tag = target.os.tag,
46 .ofmt = target.ofmt,
47 .abi = target.abi,
4548 .output_mode = output_mode,
4649 .link_mode = link_mode,
4750 });
src/main.zig+784-514
......@@ -3166,6 +3166,8 @@ fn buildOutputType(
31663166 else => process.executablePathAlloc(io, arena) catch |err| fatal("unable to find zig self exe path: {t}", .{err}),
31673167 };
31683168
3169 const cwd_path = try introspect.getResolvedCwd(io, arena);
3170
31693171 // This `init` calls `fatal` on error.
31703172 var dirs: Compilation.Directories = .init(
31713173 arena,
......@@ -3182,6 +3184,7 @@ fn buildOutputType(
31823184 preopens,
31833185 self_exe_path,
31843186 environ_map,
3187 cwd_path,
31853188 );
31863189 defer dirs.deinit(io);
31873190
......@@ -3377,7 +3380,10 @@ fn buildOutputType(
33773380 .pch => try std.fmt.allocPrint(arena, "{s}.pch", .{root_name}),
33783381 else => try std.zig.binNameAlloc(arena, .{
33793382 .root_name = root_name,
3380 .target = target,
3383 .cpu_arch = target.cpu.arch,
3384 .os_tag = target.os.tag,
3385 .ofmt = target.ofmt,
3386 .abi = target.abi,
33813387 .output_mode = create_module.resolved_options.output_mode,
33823388 .link_mode = create_module.resolved_options.link_mode,
33833389 .version = optional_version,
......@@ -3675,26 +3681,16 @@ fn buildOutputType(
36753681 if (t.arch == target.cpu.arch and t.os == target.os.tag) {
36763682 // If there's a `glibc_min`, there's also an `os_ver`.
36773683 if (t.glibc_min) |glibc_min| {
3678 std.log.info("zig can provide libc for related target {s}-{s}.{f}-{s}.{d}.{d}", .{
3679 @tagName(t.arch),
3680 @tagName(t.os),
3681 t.os_ver.?,
3682 @tagName(t.abi),
3683 glibc_min.major,
3684 glibc_min.minor,
3684 std.log.info("zig can provide libc for related target {t}-{t}.{f}-{t}.{d}.{d}", .{
3685 t.arch, t.os, t.os_ver.?, t.abi, glibc_min.major, glibc_min.minor,
36853686 });
36863687 } else if (t.os_ver) |os_ver| {
3687 std.log.info("zig can provide libc for related target {s}-{s}.{f}-{s}", .{
3688 @tagName(t.arch),
3689 @tagName(t.os),
3690 os_ver,
3691 @tagName(t.abi),
3688 std.log.info("zig can provide libc for related target {t}-{t}.{f}-{t}", .{
3689 t.arch, t.os, os_ver, t.abi,
36923690 });
36933691 } else {
3694 std.log.info("zig can provide libc for related target {s}-{s}-{s}", .{
3695 @tagName(t.arch),
3696 @tagName(t.os),
3697 @tagName(t.abi),
3692 std.log.info("zig can provide libc for related target {t}-{t}-{t}", .{
3693 t.arch, t.os, t.abi,
36983694 });
36993695 }
37003696 }
......@@ -3703,7 +3699,7 @@ fn buildOutputType(
37033699 },
37043700 else => fatal("{f}", .{create_diag}),
37053701 },
3706 else => fatal("failed to create compilation: {s}", .{@errorName(err)}),
3702 else => fatal("failed to create compilation: {t}", .{err}),
37073703 };
37083704 var comp_destroyed = false;
37093705 defer if (!comp_destroyed) comp.destroy();
......@@ -4936,16 +4932,25 @@ test sanitizeExampleName {
49364932 try std.testing.expectEqualStrings("test_project", try sanitizeExampleName(arena, "test project"));
49374933}
49384934
4939fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, environ_map: *process.Environ.Map) !void {
4940 dev.check(.build_command);
4941
4935fn cmdBuild(
4936 gpa: Allocator,
4937 arena: Allocator,
4938 io: Io,
4939 args: []const []const u8,
4940 environ_map: *process.Environ.Map,
4941) !void {
49424942 var build_file: ?[]const u8 = null;
49434943 var override_lib_dir: ?[]const u8 = EnvVar.ZIG_LIB_DIR.get(environ_map);
49444944 var override_global_cache_dir: ?[]const u8 = EnvVar.ZIG_GLOBAL_CACHE_DIR.get(environ_map);
49454945 var override_local_cache_dir: ?[]const u8 = EnvVar.ZIG_LOCAL_CACHE_DIR.get(environ_map);
49464946 var override_pkg_dir: ?[]const u8 = EnvVar.ZIG_LOCAL_PKG_DIR.get(environ_map);
4947 var override_build_runner: ?[]const u8 = EnvVar.ZIG_BUILD_RUNNER.get(environ_map);
4948 var child_argv: std.ArrayList([]const u8) = .empty;
4947 var maker_optimize_mode: std.builtin.OptimizeMode = if (EnvVar.ZIG_DEBUG_CMD.isSet(environ_map))
4948 .Debug
4949 else
4950 .ReleaseSafe;
4951 var configure_argv: std.ArrayList([]const u8) = .empty;
4952 var make_argv: std.ArrayList([]const u8) = .empty;
4953 var cached_passthru_configure: std.ArrayList(u32) = .empty;
49494954 var forks: std.ArrayList(Fork) = .empty;
49504955 var reference_trace: ?u32 = null;
49514956 var debug_compile_errors = false;
......@@ -4964,47 +4969,41 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8,
49644969 var system_pkg_dir_path: ?[]const u8 = null;
49654970 var debug_target: ?[]const u8 = null;
49664971 var debug_libc_paths_file: ?[]const u8 = null;
4967
4968 const argv_index_exe = child_argv.items.len;
4969 _ = try child_argv.addOne(arena);
4972 var cache_poison: std.Build.Graph.CachePoison = .pure;
49704973
49714974 const self_exe_path = try process.executablePathAlloc(io, arena);
4972 try child_argv.append(arena, self_exe_path);
4975 const default_seed = try std.fmt.allocPrint(arena, "0x{x}", .{randInt(io, u32)});
49734976
4974 const argv_index_zig_lib_dir = child_argv.items.len;
4975 _ = try child_argv.addOne(arena);
4977 try configure_argv.ensureUnusedCapacity(arena, 16);
4978 try make_argv.ensureUnusedCapacity(arena, 16);
4979 try cached_passthru_configure.ensureUnusedCapacity(arena, 16);
49764980
4977 const argv_index_build_file = child_argv.items.len;
4978 _ = try child_argv.addOne(arena);
4981 _ = configure_argv.addOneAssumeCapacity(); // configurer executable
4982 _ = make_argv.addOneAssumeCapacity(); // maker executable
49794983
4980 const argv_index_cache_dir = child_argv.items.len;
4981 _ = try child_argv.addOne(arena);
4984 make_argv.addManyAsArrayAssumeCapacity(2).* = .{ "--zig", self_exe_path };
4985 configure_argv.addManyAsArrayAssumeCapacity(2).* = .{ "--zig", self_exe_path };
49824986
4983 const argv_index_global_cache_dir = child_argv.items.len;
4984 _ = try child_argv.addOne(arena);
4987 make_argv.addManyAsArrayAssumeCapacity(2).* = .{ "--zig-lib-dir", undefined };
4988 const make_argv_index_zig_lib_dir = make_argv.items.len - 1;
49854989
4986 try child_argv.appendSlice(arena, &.{
4987 "--seed",
4988 try std.fmt.allocPrint(arena, "0x{x}", .{randInt(io, u32)}),
4989 });
4990 const argv_index_seed = child_argv.items.len - 1;
4991
4992 // This parent process needs a way to obtain results from the configuration
4993 // phase of the child process. In the future, the make phase will be
4994 // executed in a separate process than the configure phase, and we can then
4995 // use stdout from the configuration phase for this purpose.
4996 //
4997 // However, currently, both phases are in the same process, and Run Step
4998 // provides API for making the runned subprocesses inherit stdout and stderr
4999 // which means these streams are not available for passing metadata back
5000 // to the parent.
5001 //
5002 // Until make and configure phases are separated into different processes,
5003 // the strategy is to choose a temporary file name ahead of time, and then
5004 // read this file in the parent to obtain the results, in the case the child
5005 // exits with code 3.
5006 const results_tmp_file_nonce = std.fmt.hex(randInt(io, u64));
5007 try child_argv.append(arena, "-Z" ++ results_tmp_file_nonce);
4990 make_argv.addManyAsArrayAssumeCapacity(2).* = .{ "--build-root", undefined };
4991 const make_argv_index_build_root = make_argv.items.len - 1;
4992
4993 make_argv.addManyAsArrayAssumeCapacity(2).* = .{ "--local-cache", undefined };
4994 const make_argv_index_cache_dir = make_argv.items.len - 1;
4995
4996 make_argv.addManyAsArrayAssumeCapacity(2).* = .{ "--global-cache", undefined };
4997 const make_argv_index_global_cache_dir = make_argv.items.len - 1;
4998
4999 make_argv.addManyAsArrayAssumeCapacity(2).* = .{ "--configuration", undefined };
5000 const argv_index_configuration_file = make_argv.items.len - 1;
5001
5002 make_argv.addManyAsArrayAssumeCapacity(2).* = .{ "--seed", default_seed };
5003 const argv_index_seed = make_argv.items.len - 1;
5004
5005 configure_argv.addManyAsArrayAssumeCapacity(2).* = .{ "--build-root", undefined };
5006 const conf_argv_index_build_root = configure_argv.items.len - 1;
50085007
50095008 var color: Color = .auto;
50105009 var n_jobs: ?u32 = null;
......@@ -5014,20 +5013,65 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8,
50145013 while (i < args.len) : (i += 1) {
50155014 const arg = args[i];
50165015 if (mem.startsWith(u8, arg, "-")) {
5017 if (mem.eql(u8, arg, "--build-file")) {
5016 try configure_argv.ensureUnusedCapacity(arena, 2);
5017
5018 if (mem.startsWith(u8, arg, "-D") or
5019 mem.startsWith(u8, arg, "-fsys=") or
5020 mem.startsWith(u8, arg, "-fno-sys=") or
5021 mem.startsWith(u8, arg, "--release=") or
5022 mem.eql(u8, arg, "--release"))
5023 {
5024 try cached_passthru_configure.append(arena, @intCast(configure_argv.items.len));
5025 configure_argv.appendAssumeCapacity(arg);
5026 continue;
5027 } else if (mem.eql(u8, arg, "--system")) {
50185028 if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg});
50195029 i += 1;
5020 build_file = args[i];
5030 system_pkg_dir_path = args[i];
5031
5032 try cached_passthru_configure.append(arena, @intCast(configure_argv.items.len));
5033 configure_argv.appendAssumeCapacity(arg); // Intentionally "--system" only; not the path.
50215034 continue;
5022 } else if (mem.eql(u8, arg, "--zig-lib-dir")) {
5035 } else if (mem.cutPrefix(u8, arg, "--color=")) |rest| {
5036 color = std.meta.stringToEnum(Color, rest) orelse
5037 fatal("expected --color=[auto|on|off]; found: {s}", .{arg});
5038
5039 try cached_passthru_configure.append(arena, @intCast(configure_argv.items.len));
5040 configure_argv.appendAssumeCapacity(arg);
5041 continue;
5042 } else if (mem.eql(u8, arg, "--cache-poison")) {
5043 cache_poison = .poisoned;
5044 configure_argv.appendAssumeCapacity("--cache-poison=poisoned");
5045 continue;
5046 } else if (mem.cutPrefix(u8, arg, "--cache-poison=")) |rest| {
5047 // Allow the configurer process to report parse failure.
5048 if (std.meta.stringToEnum(std.Build.Graph.CachePoison, rest)) |poison| {
5049 cache_poison = poison;
5050 }
5051 configure_argv.appendAssumeCapacity(arg);
5052 continue;
5053 } else if (mem.eql(u8, arg, "--verbose")) {
5054 // Intentionally is added both to make and configure but
5055 // does not go into the cache hash.
5056 configure_argv.appendAssumeCapacity(arg);
5057 } else if (mem.eql(u8, arg, "--search-prefix")) {
50235058 if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg});
50245059 i += 1;
5025 override_lib_dir = args[i];
5060 // This argument is cache poisonous: it does not go into
5061 // the cache and configurer must set the poison bit when
5062 // choosing to observe it.
5063 configure_argv.addManyAsArrayAssumeCapacity(2).* = .{ arg, args[i] };
5064 (try make_argv.addManyAsArray(arena, 2)).* = .{ arg, args[i] };
5065 continue;
5066 } else if (mem.eql(u8, arg, "--build-file")) {
5067 if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg});
5068 i += 1;
5069 build_file = args[i];
50265070 continue;
5027 } else if (mem.eql(u8, arg, "--build-runner")) {
5071 } else if (mem.eql(u8, arg, "--zig-lib-dir")) {
50285072 if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg});
50295073 i += 1;
5030 override_build_runner = args[i];
5074 override_lib_dir = args[i];
50315075 continue;
50325076 } else if (mem.eql(u8, arg, "--cache-dir")) {
50335077 if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg});
......@@ -5051,37 +5095,27 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8,
50515095 } else if (mem.cutPrefix(u8, arg, "--fetch=")) |sub_arg| {
50525096 fetch_only = true;
50535097 fetch_mode = std.meta.stringToEnum(Package.Fetch.JobQueue.Mode, sub_arg) orelse
5054 fatal("expected [needed|all] after '--fetch=', found '{s}'", .{
5055 sub_arg,
5056 });
5098 fatal("expected [needed|all] after '--fetch=', found '{s}'", .{sub_arg});
50575099 } else if (mem.cutPrefix(u8, arg, "--fork=")) |sub_arg| {
5058 try forks.append(arena, .{
5059 .manifest_ast = undefined,
5060 .manifest = undefined,
5061 .error_bundle = undefined,
5062 .arena_allocator = undefined,
5063 .path = .{
5064 .root_dir = .cwd(),
5065 .sub_path = sub_arg,
5066 },
5067 .failed = false,
5068 });
5100 try forks.append(arena, .init(sub_arg));
50695101 continue;
5070 } else if (mem.eql(u8, arg, "--system")) {
5071 if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg});
5102 } else if (mem.eql(u8, arg, "--fork")) {
5103 if (i + 1 >= args.len) fatal("expected argument after: {s}", .{arg});
50725104 i += 1;
5073 system_pkg_dir_path = args[i];
5074 try child_argv.append(arena, "--system");
5105 try forks.append(arena, .init(args[i]));
50755106 continue;
50765107 } else if (mem.cutPrefix(u8, arg, "-freference-trace=")) |num| {
50775108 reference_trace = std.fmt.parseUnsigned(u32, num, 10) catch |err| {
5078 fatal("unable to parse reference_trace count '{s}': {s}", .{ num, @errorName(err) });
5109 fatal("unable to parse reference_trace count '{s}': {t}", .{ num, err });
50795110 };
50805111 } else if (mem.eql(u8, arg, "-fno-reference-trace")) {
50815112 reference_trace = null;
5113 } else if (mem.cutPrefix(u8, arg, "--maker-opt=")) |rest| {
5114 maker_optimize_mode = parseOptimizeMode(rest);
5115 continue;
50825116 } else if (mem.eql(u8, arg, "--debug-log")) {
50835117 if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg});
5084 try child_argv.appendSlice(arena, args[i .. i + 2]);
5118 try make_argv.appendSlice(arena, args[i .. i + 2]);
50855119 i += 1;
50865120 try addDebugLog(arena, args[i]);
50875121 continue;
......@@ -5125,505 +5159,720 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8,
51255159 verbose_llvm_bc = rest;
51265160 } else if (mem.eql(u8, arg, "--verbose-llvm-cpu-features")) {
51275161 verbose_llvm_cpu_features = true;
5128 } else if (mem.eql(u8, arg, "--color")) {
5129 if (i + 1 >= args.len) fatal("expected [auto|on|off] after {s}", .{arg});
5130 i += 1;
5131 color = std.meta.stringToEnum(Color, args[i]) orelse {
5132 fatal("expected [auto|on|off] after {s}, found '{s}'", .{ arg, args[i] });
5133 };
5134 try child_argv.appendSlice(arena, &.{ arg, args[i] });
5135 continue;
51365162 } else if (mem.cutPrefix(u8, arg, "-j")) |str| {
5137 const num = std.fmt.parseUnsigned(u32, str, 10) catch |err| {
5138 fatal("unable to parse jobs count '{s}': {s}", .{
5139 str, @errorName(err),
5140 });
5141 };
5163 const num = std.fmt.parseUnsigned(u32, str, 10) catch |err|
5164 fatal("unable to parse jobs count {s}: {t}", .{ str, err });
51425165 if (num < 1) {
5143 fatal("number of jobs must be at least 1\n", .{});
5166 fatal("number of jobs must be at least 1", .{});
51445167 }
51455168 n_jobs = num;
51465169 } else if (mem.eql(u8, arg, "--seed")) {
51475170 if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg});
51485171 i += 1;
5149 child_argv.items[argv_index_seed] = args[i];
5172 make_argv.items[argv_index_seed] = args[i];
51505173 continue;
51515174 } else if (mem.eql(u8, arg, "--")) {
5152 // The rest of the args are supposed to get passed onto
5153 // build runner's `build.args`
5154 try child_argv.appendSlice(arena, args[i..]);
5175 try make_argv.appendSlice(arena, args[i..]);
51555176 break;
51565177 }
51575178 }
5158 try child_argv.append(arena, arg);
5179 try make_argv.append(arena, arg);
51595180 }
51605181 }
51615182
51625183 const root_prog_node = std.Progress.start(io, .{
51635184 .disable_printing = (color == .off),
5164 .root_name = "Compile Build Script",
5185 .root_name = "",
51655186 });
51665187 defer root_prog_node.end();
51675188
5168 // Normally the build runner is compiled for the host target but here is
5169 // some code to help when debugging edits to the build runner so that you
5170 // can make sure it compiles successfully on other targets.
5171 const resolved_target: Package.Module.ResolvedTarget = t: {
5172 if (build_options.enable_debug_extensions) {
5173 if (debug_target) |triple| {
5174 const target_query = try std.Target.Query.parse(.{
5175 .arch_os_abi = triple,
5176 });
5177 break :t .{
5178 .result = std.zig.resolveTargetQueryOrFatal(io, target_query),
5179 .is_native_os = false,
5180 .is_native_abi = false,
5181 .is_explicit_dynamic_linker = false,
5182 };
5183 }
5184 }
5185 break :t .{
5186 .result = std.zig.resolveTargetQueryOrFatal(io, .{}),
5187 .is_native_os = true,
5188 .is_native_abi = true,
5189 .is_explicit_dynamic_linker = false,
5190 };
5191 };
5192 // Likewise, `--debug-libc` allows overriding the libc installation.
5193 const libc_installation: ?*const LibCInstallation = lci: {
5194 const paths_file = debug_libc_paths_file orelse break :lci null;
5195 if (!build_options.enable_debug_extensions) unreachable;
5196 const lci = try arena.create(LibCInstallation);
5197 lci.* = try .parse(arena, io, paths_file, &resolved_target.result);
5198 break :lci lci;
5199 };
5200
52015189 process.raiseFileDescriptorLimit();
52025190
5203 const cwd_path = try introspect.getResolvedCwd(io, arena);
5191 const cwd_path = introspect.getResolvedCwd(io, arena) catch |err|
5192 fatal("failed to get current directory path: {t}", .{err});
5193
52045194 const build_root = try findBuildRoot(arena, io, .{
52055195 .cwd_path = cwd_path,
52065196 .build_file = build_file,
52075197 });
52085198
5209 // This `init` calls `fatal` on error.
5210 var dirs: Compilation.Directories = .init(
5211 arena,
5212 io,
5213 override_lib_dir,
5214 override_global_cache_dir,
5215 .{ .override = path: {
5216 if (override_local_cache_dir) |d| break :path d;
5217 break :path try build_root.directory.join(arena, &.{introspect.default_local_zig_cache_basename});
5218 } },
5219 .empty,
5220 self_exe_path,
5221 environ_map,
5222 );
5223 defer dirs.deinit(io);
5199 {
5200 // This `init` calls `fatal` on error.
5201 var dirs: Compilation.Directories = .init(
5202 arena,
5203 io,
5204 override_lib_dir,
5205 override_global_cache_dir,
5206 .{ .override = path: {
5207 if (override_local_cache_dir) |d| break :path d;
5208 break :path try build_root.directory.join(arena, &.{introspect.default_local_zig_cache_basename});
5209 } },
5210 .empty,
5211 self_exe_path,
5212 environ_map,
5213 cwd_path,
5214 );
5215 defer dirs.deinit(io);
52245216
5225 child_argv.items[argv_index_zig_lib_dir] = dirs.zig_lib.path orelse cwd_path;
5226 child_argv.items[argv_index_build_file] = build_root.directory.path orelse cwd_path;
5227 child_argv.items[argv_index_global_cache_dir] = dirs.global_cache.path orelse cwd_path;
5228 child_argv.items[argv_index_cache_dir] = dirs.local_cache.path orelse cwd_path;
5217 const thread_limit = @min(
5218 @max(n_jobs orelse std.Thread.getCpuCount() catch 1, 1),
5219 std.math.maxInt(Zcu.PerThread.IdBacking),
5220 );
5221 try setThreadLimit(arena, thread_limit);
5222
5223 // Cache lookup for configure options. If we get a match, we can skip
5224 // execution of the configure script. If not, we get the file path to pass
5225 // to the configure process.
5226 var local_cache: Cache = .{
5227 .gpa = gpa,
5228 .io = io,
5229 .manifest_dir = try dirs.local_cache.handle.createDirPathOpen(io, "h", .{}),
5230 .cwd = cwd_path,
5231 };
5232 local_cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() });
5233 local_cache.addPrefix(dirs.zig_lib);
5234 local_cache.addPrefix(dirs.local_cache);
5235 local_cache.addPrefix(dirs.global_cache);
5236 defer local_cache.manifest_dir.close(io);
5237
5238 var config_man = local_cache.obtain();
5239 defer config_man.deinit();
5240 config_man.hash.addBytes(build_options.version);
5241
5242 for (cached_passthru_configure.items) |i|
5243 config_man.hash.addBytes(configure_argv.items[i]);
5244
5245 // Prevents a `zig build` from getting a false positive cache hit following
5246 // a `zig build --cache-poison=ignored`.
5247 config_man.hash.add(cache_poison == .ignored);
5248
5249 // Normally the build runner is compiled for the host target but here is
5250 // some code to help when debugging edits to the build runner so that you
5251 // can make sure it compiles successfully on other targets.
5252 const resolved_target: Package.Module.ResolvedTarget = t: {
5253 if (build_options.enable_debug_extensions) {
5254 if (debug_target) |triple| {
5255 const target_query = try std.Target.Query.parse(.{
5256 .arch_os_abi = triple,
5257 });
5258 config_man.hash.addBytes(triple);
5259 break :t .{
5260 .result = std.zig.resolveTargetQueryOrFatal(io, target_query),
5261 .is_native_os = false,
5262 .is_native_abi = false,
5263 .is_explicit_dynamic_linker = false,
5264 };
5265 }
5266 }
5267 break :t .{
5268 .result = std.zig.resolveTargetQueryOrFatal(io, .{}),
5269 .is_native_os = true,
5270 .is_native_abi = true,
5271 .is_explicit_dynamic_linker = false,
5272 };
5273 };
52295274
5230 const thread_limit = @min(
5231 @max(n_jobs orelse std.Thread.getCpuCount() catch 1, 1),
5232 std.math.maxInt(Zcu.PerThread.IdBacking),
5233 );
5234 try setThreadLimit(arena, thread_limit);
5275 // Likewise, `--debug-libc` allows overriding the libc installation.
5276 const libc_installation: ?*const LibCInstallation = lci: {
5277 const paths_file = debug_libc_paths_file orelse break :lci null;
5278 if (!build_options.enable_debug_extensions) unreachable;
5279 const lci = try arena.create(LibCInstallation);
5280 lci.* = try .parse(arena, io, paths_file, &resolved_target.result);
5281 LibCInstallation.addToHash(lci, &config_man.hash, resolved_target.result.abi);
5282 break :lci lci;
5283 };
52355284
5236 // Dummy http client that is not actually used when fetch_command is unsupported.
5237 // Prevents bootstrap from depending on a bunch of unnecessary stuff.
5238 var http_client: if (dev.env.supports(.fetch_command)) std.http.Client else struct {
5239 allocator: Allocator,
5240 io: Io,
5241 fn deinit(_: @This()) void {}
5242 } = .{ .allocator = gpa, .io = io };
5243 defer http_client.deinit();
5285 // Kick off an optimized compilation of the make runner.
5286 var make_runner_task = io.async(compileMakeRunner, .{ gpa, arena, io, .{
5287 .dirs = .{
5288 .cwd = dirs.cwd,
5289 .zig_lib = dirs.zig_lib,
5290 .global_cache = dirs.global_cache,
5291 .local_cache = dirs.global_cache,
5292 },
5293 .environ_map = environ_map,
5294 .parent_prog_node = root_prog_node,
5295 .resolved_target = resolved_target,
5296 .libc_installation = libc_installation,
5297 .thread_limit = thread_limit,
5298 .self_exe_path = self_exe_path,
5299 .color = color,
5300 .reference_trace = reference_trace,
5301 .optimize_mode = maker_optimize_mode,
5302 } });
5303 defer _ = make_runner_task.cancel(io) catch {};
5304
5305 const pkg_root: Path = if (override_pkg_dir) |p|
5306 .initCwd(p)
5307 else if (system_pkg_dir_path) |p|
5308 .initCwd(p)
5309 else
5310 .{
5311 .root_dir = build_root.directory,
5312 .sub_path = "zig-pkg",
5313 };
52445314
5245 var unlazy_set: Package.Fetch.JobQueue.UnlazySet = .{};
5246 var fork_set: Package.Fetch.JobQueue.ForkSet = .{};
5315 make_argv.items[make_argv_index_zig_lib_dir] = dirs.zig_lib.path orelse cwd_path;
5316 make_argv.items[make_argv_index_build_root] = build_root.directory.path orelse cwd_path;
5317 make_argv.items[make_argv_index_global_cache_dir] = dirs.global_cache.path orelse cwd_path;
5318 make_argv.items[make_argv_index_cache_dir] = dirs.local_cache.path orelse cwd_path;
52475319
5248 {
5249 // Populate fork_set.
5250 var group: Io.Group = .init;
5251 defer group.cancel(io);
5252
5253 for (forks.items) |*fork|
5254 group.async(io, Fork.load, .{ io, gpa, fork, color });
5255
5256 try group.await(io);
5257
5258 for (forks.items) |*fork| {
5259 if (fork.failed) process.exit(1);
5260 try fork_set.put(arena, .{
5261 .path = fork.path,
5262 .manifest_ast = fork.manifest_ast,
5263 .manifest = fork.manifest,
5264 .uses = 0,
5265 }, {});
5266 }
5267 }
5268 defer Fork.deinitList(forks.items);
5320 configure_argv.items[conf_argv_index_build_root] = build_root.directory.path orelse cwd_path;
5321
5322 // Dummy http client that is not actually used when fetch_command is unsupported.
5323 // Prevents bootstrap from depending on a bunch of unnecessary stuff.
5324 var http_client: if (dev.env.supports(.fetch_command)) std.http.Client else struct {
5325 allocator: Allocator,
5326 io: Io,
5327 fn deinit(_: @This()) void {}
5328 } = .{ .allocator = gpa, .io = io };
5329 defer http_client.deinit();
5330
5331 var unlazy_set: Package.Fetch.JobQueue.UnlazySet = .{};
5332 var fork_set: Package.Fetch.JobQueue.ForkSet = .{};
52695333
5270 // This loop is re-evaluated when the build script exits with an indication that it
5271 // could not continue due to missing lazy dependencies.
5272 while (true) {
5273 // We want to release all the locks before executing the child process, so we make a nice
5274 // big block here to ensure the cleanup gets run when we extract out our argv.
52755334 {
5276 const main_mod_paths: Package.Module.CreateOptions.Paths = if (override_build_runner) |runner| .{
5277 .root = try .fromUnresolved(arena, dirs, &.{fs.path.dirname(runner) orelse "."}),
5278 .root_src_path = fs.path.basename(runner),
5279 } else .{
5280 .root = try .fromRoot(arena, dirs, .zig_lib, "compiler"),
5281 .root_src_path = "build_runner.zig",
5282 };
5335 // Populate fork_set.
5336 var group: Io.Group = .init;
5337 defer group.cancel(io);
5338
5339 for (forks.items) |*fork|
5340 group.async(io, Fork.load, .{ io, gpa, fork, color });
5341
5342 try group.await(io);
5343
5344 for (forks.items) |*fork| {
5345 if (fork.failed) process.exit(1);
5346 try fork_set.put(arena, .{
5347 .path = fork.path,
5348 .manifest_ast = fork.manifest_ast,
5349 .manifest = fork.manifest,
5350 .uses = 0,
5351 }, {});
5352 }
5353 }
5354 defer Fork.deinitList(forks.items);
52835355
5284 const config = try Compilation.Config.resolve(.{
5285 .output_mode = .Exe,
5286 .resolved_target = resolved_target,
5287 .have_zcu = true,
5288 .emit_bin = true,
5289 .is_test = false,
5290 });
5356 // This loop is re-evaluated when the build script exits with an indication that it
5357 // could not continue due to missing lazy dependencies.
5358 const configuration_path: Path, const poisoned: bool = cp: while (true) {
5359 // We want to release all the locks before executing the child process, so we make a nice
5360 // big block here to ensure the cleanup gets run when we extract out our argv.
5361 {
5362 const main_mod_paths: Package.Module.CreateOptions.Paths = .{
5363 .root = try .fromRoot(arena, dirs, .zig_lib, "compiler"),
5364 .root_src_path = "configurer.zig",
5365 };
52915366
5292 const root_mod = try Package.Module.create(arena, .{
5293 .paths = main_mod_paths,
5294 .fully_qualified_name = "root",
5295 .cc_argv = &.{},
5296 .inherited = .{
5367 const config = try Compilation.Config.resolve(.{
5368 .output_mode = .Exe,
52975369 .resolved_target = resolved_target,
5298 },
5299 .global = config,
5300 .parent = null,
5301 });
5370 .have_zcu = true,
5371 .emit_bin = true,
5372 .is_test = false,
5373 });
53025374
5303 const build_mod = try Package.Module.create(arena, .{
5304 .paths = .{
5305 .root = try .fromUnresolved(arena, dirs, &.{build_root.directory.path orelse "."}),
5306 .root_src_path = build_root.build_zig_basename,
5307 },
5308 .fully_qualified_name = "root.@build",
5309 .cc_argv = &.{},
5310 .inherited = .{},
5311 .global = config,
5312 .parent = root_mod,
5313 });
5375 const root_mod = try Package.Module.create(arena, .{
5376 .paths = main_mod_paths,
5377 .fully_qualified_name = "root",
5378 .cc_argv = &.{},
5379 .inherited = .{
5380 .resolved_target = resolved_target,
5381 .single_threaded = true,
5382 },
5383 .global = config,
5384 .parent = null,
5385 });
53145386
5315 if (dev.env.supports(.fetch_command)) {
5316 const fetch_prog_node = root_prog_node.start("Fetch Packages", 0);
5317 defer fetch_prog_node.end();
5318
5319 // Reset fork match counts.
5320 for (fork_set.keys()) |*fork| fork.uses = 0;
5321
5322 var job_queue: Package.Fetch.JobQueue = .{
5323 .io = io,
5324 .http_client = &http_client,
5325 .global_cache = dirs.global_cache,
5326 .local_storage = &.{
5327 .cache_root = .{ .root_dir = dirs.local_cache, .sub_path = "" },
5328 .pkg_root = if (override_pkg_dir) |p|
5329 .initCwd(p)
5330 else if (system_pkg_dir_path) |p|
5331 .initCwd(p)
5332 else
5333 .{
5334 .root_dir = build_root.directory,
5335 .sub_path = "zig-pkg",
5336 },
5387 const build_mod = try Package.Module.create(arena, .{
5388 .paths = .{
5389 .root = try .fromUnresolved(arena, dirs, &.{build_root.directory.path orelse "."}),
5390 .root_src_path = build_root.build_zig_basename,
53375391 },
5338 .recursive = true,
5339 .debug_hash = false,
5340 .unlazy_set = unlazy_set,
5341 .fork_set = fork_set,
5342 .mode = fetch_mode,
5343 .prog_node = fetch_prog_node,
5344 .read_only = system_pkg_dir_path != null,
5345 };
5346 defer job_queue.deinit();
5392 .fully_qualified_name = "root.@build",
5393 .cc_argv = &.{},
5394 .inherited = .{},
5395 .global = config,
5396 .parent = root_mod,
5397 });
53475398
5348 if (system_pkg_dir_path == null) {
5349 try http_client.initDefaultProxies(arena, environ_map);
5350 }
5399 if (dev.env.supports(.fetch_command)) {
5400 const fetch_prog_node = root_prog_node.start("Fetch Packages", 0);
5401 defer fetch_prog_node.end();
53515402
5352 try job_queue.all_fetches.ensureUnusedCapacity(gpa, 1);
5353 try job_queue.table.ensureUnusedCapacity(gpa, 1);
5354
5355 const phantom_package_root: Cache.Path = .{ .root_dir = build_root.directory };
5356
5357 var fetch: Package.Fetch = .{
5358 .arena = std.heap.ArenaAllocator.init(gpa),
5359 .location = .{ .relative_path = phantom_package_root },
5360 .location_tok = 0,
5361 .hash_tok = .none,
5362 .name_tok = 0,
5363 .lazy_status = .eager,
5364 .remote_package_root = phantom_package_root,
5365 .parent_package_root = phantom_package_root,
5366 .parent_manifest_ast = null,
5367 .prog_node = fetch_prog_node,
5368 .job_queue = &job_queue,
5369 .omit_missing_hash_error = true,
5370 .allow_missing_paths_field = false,
5371 .use_latest_commit = false,
5372
5373 .package_root = undefined,
5374 .error_bundle = undefined,
5375 .manifest = undefined,
5376 .manifest_ast = undefined,
5377 .have_manifest = false,
5378 .computed_hash = undefined,
5379 .has_build_zig = true,
5380 .oom_flag = false,
5381 .latest_commit = null,
5382
5383 .module = build_mod,
5384 };
5403 // Reset fork match counts.
5404 for (fork_set.keys()) |*fork| fork.uses = 0;
5405
5406 var job_queue: Package.Fetch.JobQueue = .{
5407 .io = io,
5408 .http_client = &http_client,
5409 .global_cache = dirs.global_cache,
5410 .local_storage = &.{
5411 .cache_root = .{ .root_dir = dirs.local_cache, .sub_path = "" },
5412 .pkg_root = pkg_root,
5413 },
5414 .recursive = true,
5415 .debug_hash = false,
5416 .unlazy_set = unlazy_set,
5417 .fork_set = fork_set,
5418 .mode = fetch_mode,
5419 .prog_node = fetch_prog_node,
5420 .read_only = system_pkg_dir_path != null,
5421 };
5422 defer job_queue.deinit();
53855423
5386 job_queue.all_fetches.appendAssumeCapacity(&fetch);
5424 if (system_pkg_dir_path == null) {
5425 try http_client.initDefaultProxies(arena, environ_map);
5426 }
53875427
5388 job_queue.table.putAssumeCapacityNoClobber(
5389 Package.Fetch.relativePathDigest(phantom_package_root, dirs.global_cache),
5390 &fetch,
5391 );
5428 try job_queue.all_fetches.ensureUnusedCapacity(gpa, 1);
5429 try job_queue.table.ensureUnusedCapacity(gpa, 1);
5430
5431 const phantom_package_root: Cache.Path = .{ .root_dir = build_root.directory };
5432
5433 var fetch: Package.Fetch = .{
5434 .arena = std.heap.ArenaAllocator.init(gpa),
5435 .location = .{ .relative_path = phantom_package_root },
5436 .location_tok = 0,
5437 .hash_tok = .none,
5438 .name_tok = 0,
5439 .lazy_status = .eager,
5440 .remote_package_root = phantom_package_root,
5441 .parent_package_root = phantom_package_root,
5442 .parent_manifest_ast = null,
5443 .prog_node = fetch_prog_node,
5444 .job_queue = &job_queue,
5445 .omit_missing_hash_error = true,
5446 .allow_missing_paths_field = false,
5447 .use_latest_commit = false,
5448
5449 .package_root = undefined,
5450 .error_bundle = undefined,
5451 .manifest = undefined,
5452 .manifest_ast = undefined,
5453 .have_manifest = false,
5454 .computed_hash = undefined,
5455 .has_build_zig = true,
5456 .oom_flag = false,
5457 .latest_commit = null,
53925458
5393 job_queue.group.async(io, Package.Fetch.workerRun, .{ &fetch, "root" });
5394 try job_queue.group.await(io);
5459 .module = build_mod,
5460 };
53955461
5396 {
5397 // Ensure that forks were actually used. This is done
5398 // before printing manifest errors because using a fork can
5399 // prevent them.
5400 var any_unused = false;
5401 for (fork_set.keys()) |*fork| {
5402 if (fork.uses == 0) {
5403 std.log.err("fork {f} matched no {s} packages", .{
5404 fork.path, fork.manifest.name,
5405 });
5406 any_unused = true;
5407 } else {
5408 std.log.info("fork {f} matched {d} {s} packages", .{
5409 fork.path, fork.uses, fork.manifest.name,
5410 });
5462 job_queue.all_fetches.appendAssumeCapacity(&fetch);
5463
5464 job_queue.table.putAssumeCapacityNoClobber(
5465 Package.Fetch.relativePathDigest(phantom_package_root, dirs.global_cache),
5466 &fetch,
5467 );
5468
5469 job_queue.group.async(io, Package.Fetch.workerRun, .{ &fetch, "root" });
5470 try job_queue.group.await(io);
5471
5472 {
5473 // Ensure that forks were actually used. This is done
5474 // before printing manifest errors because using a fork can
5475 // prevent them.
5476 var any_unused = false;
5477 for (fork_set.keys()) |*fork| {
5478 if (fork.uses == 0) {
5479 std.log.err("fork {f} matched no {s} packages", .{
5480 fork.path, fork.manifest.name,
5481 });
5482 any_unused = true;
5483 } else {
5484 std.log.info("fork {f} matched {d} {s} packages", .{
5485 fork.path, fork.uses, fork.manifest.name,
5486 });
5487 }
54115488 }
5489 if (any_unused) process.exit(1);
54125490 }
5413 if (any_unused) process.exit(1);
5414 }
54155491
5416 try job_queue.consolidateErrors();
5492 try job_queue.consolidateErrors();
54175493
5418 if (fetch.error_bundle.root_list.items.len > 0) {
5419 var errors = try fetch.error_bundle.toOwnedBundle("");
5420 errors.renderToStderr(io, .{}, color) catch {};
5421 process.exit(1);
5422 }
5494 if (fetch.error_bundle.root_list.items.len > 0) {
5495 var errors = try fetch.error_bundle.toOwnedBundle("");
5496 errors.renderToStderr(io, .{}, color) catch {};
5497 process.exit(1);
5498 }
5499
5500 if (fetch_only) return cleanExit(io);
5501
5502 var source_buf = std.array_list.Managed(u8).init(gpa);
5503 defer source_buf.deinit();
5504 try job_queue.createDependenciesSource(&source_buf);
5505 const deps_mod = try createDependenciesModule(
5506 arena,
5507 io,
5508 source_buf.items,
5509 root_mod,
5510 dirs,
5511 config,
5512 );
54235513
5424 if (fetch_only) return cleanExit(io);
5514 {
5515 // We need a Module for each package's build.zig.
5516 const hashes = job_queue.table.keys();
5517 const fetches = job_queue.table.values();
5518 try deps_mod.deps.ensureUnusedCapacity(arena, @intCast(hashes.len));
5519 for (hashes, fetches) |*hash, f| {
5520 if (f == &fetch) {
5521 // The first one is a dummy package for the current project.
5522 continue;
5523 }
5524 if (!f.has_build_zig)
5525 continue;
5526 const hash_slice = hash.toSlice();
5527 const mod_root_path = try f.package_root.toString(arena);
5528 const m = try Package.Module.create(arena, .{
5529 .paths = .{
5530 .root = try .fromUnresolved(arena, dirs, &.{mod_root_path}),
5531 .root_src_path = Package.build_zig_basename,
5532 },
5533 .fully_qualified_name = try std.fmt.allocPrint(
5534 arena,
5535 "root.@dependencies.{s}",
5536 .{hash_slice},
5537 ),
5538 .cc_argv = &.{},
5539 .inherited = .{},
5540 .global = config,
5541 .parent = root_mod,
5542 });
5543 const hash_cloned = try arena.dupe(u8, hash_slice);
5544 deps_mod.deps.putAssumeCapacityNoClobber(hash_cloned, m);
5545 f.module = m;
5546 }
54255547
5426 var source_buf = std.array_list.Managed(u8).init(gpa);
5427 defer source_buf.deinit();
5428 try job_queue.createDependenciesSource(&source_buf);
5429 const deps_mod = try createDependenciesModule(
5548 // Each build.zig module needs access to each of its
5549 // dependencies' build.zig modules by name.
5550 for (fetches) |f| {
5551 const mod = f.module orelse continue;
5552 if (!f.have_manifest) continue;
5553 const man = &f.manifest;
5554 const dep_names = man.dependencies.keys();
5555 try mod.deps.ensureUnusedCapacity(arena, @intCast(dep_names.len));
5556 for (dep_names, man.dependencies.values()) |name, dep| {
5557 const dep_digest = Package.Fetch.depDigest(
5558 f.package_root,
5559 dirs.global_cache,
5560 dep,
5561 ) orelse continue;
5562 const dep_mod = job_queue.table.get(dep_digest).?.module orelse continue;
5563 const name_cloned = try arena.dupe(u8, name);
5564 mod.deps.putAssumeCapacityNoClobber(name_cloned, dep_mod);
5565 }
5566 }
5567 }
5568 } else try createEmptyDependenciesModule(
54305569 arena,
54315570 io,
5432 source_buf.items,
54335571 root_mod,
54345572 dirs,
54355573 config,
54365574 );
54375575
5438 {
5439 // We need a Module for each package's build.zig.
5440 const hashes = job_queue.table.keys();
5441 const fetches = job_queue.table.values();
5442 try deps_mod.deps.ensureUnusedCapacity(arena, @intCast(hashes.len));
5443 for (hashes, fetches) |*hash, f| {
5444 if (f == &fetch) {
5445 // The first one is a dummy package for the current project.
5446 continue;
5447 }
5448 if (!f.has_build_zig)
5449 continue;
5450 const hash_slice = hash.toSlice();
5451 const mod_root_path = try f.package_root.toString(arena);
5452 const m = try Package.Module.create(arena, .{
5453 .paths = .{
5454 .root = try .fromUnresolved(arena, dirs, &.{mod_root_path}),
5455 .root_src_path = Package.build_zig_basename,
5456 },
5457 .fully_qualified_name = try std.fmt.allocPrint(
5458 arena,
5459 "root.@dependencies.{s}",
5460 .{hash_slice},
5461 ),
5462 .cc_argv = &.{},
5463 .inherited = .{},
5464 .global = config,
5465 .parent = root_mod,
5466 });
5467 const hash_cloned = try arena.dupe(u8, hash_slice);
5468 deps_mod.deps.putAssumeCapacityNoClobber(hash_cloned, m);
5469 f.module = m;
5470 }
5576 const compile_prog_node = root_prog_node.start("Compile Configure Script", 0);
5577 defer compile_prog_node.end();
5578
5579 try root_mod.deps.put(arena, "@build", build_mod);
5580
5581 var create_diag: Compilation.CreateDiagnostic = undefined;
5582 const comp = Compilation.create(gpa, arena, io, &create_diag, .{
5583 .libc_installation = libc_installation,
5584 .dirs = dirs,
5585 .root_name = "configure",
5586 .config = config,
5587 .root_mod = root_mod,
5588 .main_mod = build_mod,
5589 .emit_bin = .yes_cache,
5590 .self_exe_path = self_exe_path,
5591 .thread_limit = thread_limit,
5592 .verbose_cc = verbose_cc,
5593 .verbose_link = verbose_link,
5594 .verbose_air = verbose_air,
5595 .verbose_intern_pool = verbose_intern_pool,
5596 .verbose_generic_instances = verbose_generic_instances,
5597 .verbose_llvm_ir = verbose_llvm_ir,
5598 .verbose_llvm_bc = verbose_llvm_bc,
5599 .verbose_llvm_cpu_features = verbose_llvm_cpu_features,
5600 .cache_mode = .whole,
5601 .reference_trace = reference_trace,
5602 .debug_compile_errors = debug_compile_errors,
5603 .environ_map = environ_map,
5604 }) catch |err| switch (err) {
5605 error.CreateFail => fatal("failed to create compilation: {f}", .{create_diag}),
5606 else => |e| fatal("failed to create compilation: {t}", .{e}),
5607 };
5608 defer comp.destroy();
54715609
5472 // Each build.zig module needs access to each of its
5473 // dependencies' build.zig modules by name.
5474 for (fetches) |f| {
5475 const mod = f.module orelse continue;
5476 if (!f.have_manifest) continue;
5477 const man = &f.manifest;
5478 const dep_names = man.dependencies.keys();
5479 try mod.deps.ensureUnusedCapacity(arena, @intCast(dep_names.len));
5480 for (dep_names, man.dependencies.values()) |name, dep| {
5481 const dep_digest = Package.Fetch.depDigest(
5482 f.package_root,
5483 dirs.global_cache,
5484 dep,
5485 ) orelse continue;
5486 const dep_mod = job_queue.table.get(dep_digest).?.module orelse continue;
5487 const name_cloned = try arena.dupe(u8, name);
5488 mod.deps.putAssumeCapacityNoClobber(name_cloned, dep_mod);
5489 }
5490 }
5491 }
5492 } else try createEmptyDependenciesModule(
5493 arena,
5494 io,
5495 root_mod,
5496 dirs,
5497 config,
5498 );
5610 updateModule(comp, color, compile_prog_node) catch |err| switch (err) {
5611 error.CompileErrorsReported => process.exit(2),
5612 else => |e| return e,
5613 };
54995614
5500 try root_mod.deps.put(arena, "@build", build_mod);
5501
5502 var create_diag: Compilation.CreateDiagnostic = undefined;
5503 const comp = Compilation.create(gpa, arena, io, &create_diag, .{
5504 .libc_installation = libc_installation,
5505 .dirs = dirs,
5506 .root_name = "build",
5507 .config = config,
5508 .root_mod = root_mod,
5509 .main_mod = build_mod,
5510 .emit_bin = .yes_cache,
5511 .self_exe_path = self_exe_path,
5512 .thread_limit = thread_limit,
5513 .verbose_cc = verbose_cc,
5514 .verbose_link = verbose_link,
5515 .verbose_air = verbose_air,
5516 .verbose_intern_pool = verbose_intern_pool,
5517 .verbose_generic_instances = verbose_generic_instances,
5518 .verbose_llvm_ir = verbose_llvm_ir,
5519 .verbose_llvm_bc = verbose_llvm_bc,
5520 .verbose_llvm_cpu_features = verbose_llvm_cpu_features,
5521 .cache_mode = .whole,
5522 .reference_trace = reference_trace,
5523 .debug_compile_errors = debug_compile_errors,
5524 .environ_map = environ_map,
5525 }) catch |err| switch (err) {
5526 error.CreateFail => fatal("failed to create compilation: {f}", .{create_diag}),
5527 else => fatal("failed to create compilation: {t}", .{err}),
5528 };
5529 defer comp.destroy();
5615 // Since incremental compilation isn't done yet, we use cache_mode = whole
5616 // above, and thus the output file is already closed.
5617 //try comp.makeBinFileExecutable();
5618 const hex_digest: []const u8 = &Cache.binToHex(comp.digest.?);
5619 const exe_path: Path = .{
5620 .root_dir = dirs.local_cache,
5621 .sub_path = try std.fmt.allocPrint(arena, "o/{s}/{s}", .{ hex_digest, comp.emit_bin.? }),
5622 };
5623 _ = try config_man.addFilePath(exe_path, null);
5624 configure_argv.items[0] = try exe_path.toString(arena);
55305625
5531 updateModule(comp, color, root_prog_node) catch |err| switch (err) {
5532 error.CompileErrorsReported => process.exit(2),
5533 else => |e| return e,
5534 };
5626 switch (cache_poison) {
5627 .pure, .disallowed, .ignored => if (try config_man.hit()) {
5628 const digest = config_man.final();
5629 break :cp .{
5630 .{
5631 .root_dir = dirs.local_cache,
5632 .sub_path = try std.fmt.allocPrint(arena, "c/{s}", .{&digest}),
5633 },
5634 false,
5635 };
5636 },
5637 .poisoned => {}, // Don't bother checking for cache hit.
5638 }
5639 }
55355640
5536 // Since incremental compilation isn't done yet, we use cache_mode = whole
5537 // above, and thus the output file is already closed.
5538 //try comp.makeBinFileExecutable();
5539 child_argv.items[argv_index_exe] = try dirs.local_cache.join(arena, &.{
5540 "o",
5541 &Cache.binToHex(comp.digest.?),
5542 comp.emit_bin.?,
5543 });
5544 }
5641 if (!process.can_spawn) {
5642 const cmd = try std.mem.join(arena, " ", configure_argv.items);
5643 fatal("the following command cannot be executed ({t} does not support spawning a child process):\n{s}", .{ native_os, cmd });
5644 }
55455645
5546 if (!process.can_spawn) {
5547 const cmd = try std.mem.join(arena, " ", child_argv.items);
5548 fatal("the following command cannot be executed ({t} does not support spawning a child process):\n{s}", .{ native_os, cmd });
5549 }
5550 switch (term: {
5551 _ = try io.lockStderr(&.{}, .no_color);
5552 defer io.unlockStderr();
5553 var child = std.process.spawn(io, .{
5554 .argv = child_argv.items,
5555 }) catch |err| fatal("failed to spawn build runner {s}: {t}", .{ child_argv.items[0], err });
5556 defer child.kill(io);
5557 break :term child.wait(io) catch |err|
5558 fatal("failed to wait build runner {s}: {t}", .{ child_argv.items[0], err });
5559 }) {
5560 .exited => |code| {
5561 if (code == 0) return cleanExit(io);
5562 // Indicates that the build runner has reported compile errors
5563 // and this parent process does not need to report any further
5564 // diagnostics.
5565 if (code == 2) process.exit(2);
5566
5567 if (code == 3) {
5568 if (!dev.env.supports(.fetch_command)) process.exit(3);
5569 // Indicates the configure phase failed due to missing lazy
5570 // dependencies and stdout contains the hashes of the ones
5571 // that are missing.
5572 const s = fs.path.sep_str;
5573 const tmp_sub_path = "tmp" ++ s ++ results_tmp_file_nonce;
5574 const stdout = dirs.local_cache.handle.readFileAlloc(io, tmp_sub_path, arena, .limited(50 * 1024 * 1024)) catch |err| {
5575 fatal("unable to read results of configure phase from '{f}{s}': {t}", .{
5576 dirs.local_cache, tmp_sub_path, err,
5577 });
5578 };
5579 dirs.local_cache.handle.deleteFile(io, tmp_sub_path) catch {};
5580
5581 var it = mem.splitScalar(u8, stdout, '\n');
5582 var any_errors = false;
5583 while (it.next()) |hash| {
5584 if (hash.len == 0) continue;
5585 if (hash.len > Package.Hash.max_len) {
5586 std.log.err("invalid digest (length {d} exceeds maximum): '{s}'", .{
5587 hash.len, hash,
5588 });
5589 any_errors = true;
5590 continue;
5591 }
5592 try unlazy_set.put(arena, .fromSlice(hash), {});
5646 const rand_int = randInt(io, u64);
5647 const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int);
5648 const config_tmp_path: Path = .{
5649 .root_dir = dirs.local_cache,
5650 .sub_path = tmp_dir_sub_path,
5651 };
5652 const config_tmp_file: Io.File = try config_tmp_path.root_dir.handle.createFile(
5653 io,
5654 config_tmp_path.sub_path,
5655 .{ .read = true, .exclusive = true },
5656 );
5657 defer config_tmp_file.close(io);
5658
5659 const term = term: {
5660 const child_node = root_prog_node.start("Run Configure Script", 0);
5661 defer child_node.end();
5662 var child = std.process.spawn(io, .{
5663 .argv = configure_argv.items,
5664 .stdout = .{ .file = config_tmp_file },
5665 .progress_node = child_node,
5666 }) catch |err| fatal("failed to spawn configure script {s}: {t}", .{ configure_argv.items[0], err });
5667 defer child.kill(io);
5668 break :term child.wait(io) catch |err|
5669 fatal("failed to wait configure script {s}: {t}", .{ configure_argv.items[0], err });
5670 };
5671 if (!term.success()) {
5672 // Failure to produce the configuration file.
5673 const cmd = try std.mem.join(arena, " ", configure_argv.items);
5674 fatal("the following configure command {f}:\n{s}", .{ term, cmd });
5675 }
5676 // Even though the file is designed to be sent directly to make
5677 // runner, we must load it now because:
5678 // * If it contains additional file dependencies, we need to
5679 // add them to `config_man` before obtaining the final digest.
5680 // * If it contains a set of lazy packages that need to be
5681 // fetched, we need to fetch those now and re-run configure.
5682 var configuration = std.Build.Configuration.loadFile(arena, io, config_tmp_file) catch |err|
5683 fatal("failed to load configuration file {f}: {t}", .{ config_tmp_path, err });
5684
5685 if (configuration.unlazy_deps.len != 0) {
5686 if (!dev.env.supports(.fetch_command)) process.exit(1);
5687 var any_errors = false;
5688 for (configuration.unlazy_deps) |hash_string| {
5689 const hash = hash_string.slice(&configuration);
5690 assert(hash.len != 0);
5691 if (hash.len > Package.Hash.max_len) {
5692 std.log.err("invalid digest (length {d} exceeds maximum): '{s}'", .{ hash.len, hash });
5693 any_errors = true;
5694 continue;
55935695 }
5594 if (any_errors) process.exit(3);
5595 if (system_pkg_dir_path) |p| {
5596 // In this mode, the system needs to provide these packages; they
5597 // cannot be fetched by Zig.
5598 for (unlazy_set.keys()) |*hash| {
5599 std.log.err("lazy dependency package not found: {s}" ++ s ++ "{s}", .{
5600 p, hash.toSlice(),
5601 });
5602 }
5603 std.log.info("remote package fetching disabled due to --system mode", .{});
5604 std.log.info("dependencies might be avoidable depending on build configuration", .{});
5605 process.exit(3);
5696 try unlazy_set.put(arena, .fromSlice(hash), {});
5697 }
5698 if (any_errors) process.exit(1);
5699 if (system_pkg_dir_path) |p| {
5700 // In this mode, the system needs to provide these packages; they
5701 // cannot be fetched by Zig.
5702 const s = fs.path.sep_str;
5703 for (unlazy_set.keys()) |*hash| {
5704 std.log.err("lazy dependency package not found: {s}" ++ s ++ "{s}", .{ p, hash.toSlice() });
56065705 }
5607 continue;
5706 std.log.info("remote package fetching disabled due to --system mode", .{});
5707 std.log.info("dependencies might be avoidable depending on build configuration", .{});
5708 process.exit(1);
56085709 }
5710 continue :cp;
5711 }
56095712
5610 const cmd = try std.mem.join(arena, " ", child_argv.items);
5611 fatal("the following build command failed with exit code {d}:\n{s}", .{ code, cmd });
5612 },
5613 .signal => |sig| {
5614 const cmd = try std.mem.join(arena, " ", child_argv.items);
5615 fatal("the following build command terminated with signal {t}:\n{s}", .{ sig, cmd });
5616 },
5617 .stopped => |sig| {
5618 const cmd = try std.mem.join(arena, " ", child_argv.items);
5619 fatal("the following build command stopped with signal {t}:\n{s}", .{ sig, cmd });
5620 },
5621 .unknown => {
5622 const cmd = try std.mem.join(arena, " ", child_argv.items);
5623 fatal("the following build command crashed:\n{s}", .{cmd});
5624 },
5713 for (configuration.path_deps_base, configuration.path_deps_sub) |base, sub| {
5714 const conf_path: std.Build.Configuration.Path = .{ .base = base, .sub = sub };
5715 try config_man.addPathPost(conf_path.toCachePath(&configuration, arena));
5716 }
5717
5718 // If it is poisoned, there is no point in moving it to cached
5719 // location. Just leave it in the tmp directory.
5720 if (configuration.poisoned) {
5721 break :cp .{ config_tmp_path, true };
5722 } else {
5723 const digest = config_man.final();
5724 const final_path: Path = .{
5725 .root_dir = dirs.local_cache,
5726 .sub_path = try std.fmt.allocPrint(arena, "c/{s}", .{&digest}),
5727 };
5728 Io.Dir.rename(
5729 config_tmp_path.root_dir.handle,
5730 config_tmp_path.sub_path,
5731 final_path.root_dir.handle,
5732 final_path.sub_path,
5733 io,
5734 ) catch |err| retry: {
5735 const e = switch (err) {
5736 error.FileNotFound => e: {
5737 const dir_path = final_path.dirname().?;
5738 dir_path.root_dir.handle.createDirPath(io, dir_path.sub_path) catch |e|
5739 fatal("failed to create directory {f}: {t}", .{ dir_path, e });
5740 if (Io.Dir.rename(
5741 config_tmp_path.root_dir.handle,
5742 config_tmp_path.sub_path,
5743 final_path.root_dir.handle,
5744 final_path.sub_path,
5745 io,
5746 )) |_| break :retry else |e| break :e e;
5747 },
5748 else => |e| e,
5749 };
5750 fatal("failed to rename configuration file from {f} into {f}: {t}", .{
5751 config_tmp_path, final_path, e,
5752 });
5753 };
5754 config_man.writeManifest() catch |err| warn("failed to write cache manifest: {t}", .{err});
5755 break :cp .{ final_path, false };
5756 }
5757 };
5758
5759 {
5760 // Release all file system locks just before running the maker process.
5761 var configuration_lock = if (!poisoned) config_man.toOwnedLock() else null;
5762 defer if (configuration_lock) |*l| l.release(io);
5763
5764 const make_runner = make_runner_task.await(io) catch |err| fatal("failed compiling maker: {t}", .{err});
5765
5766 make_argv.items[0] = try make_runner.exe_path.toString(arena);
5767 make_argv.items[argv_index_configuration_file] = try configuration_path.toString(arena);
56255768 }
56265769 }
5770
5771 if (!process.can_spawn) {
5772 const cmd = try std.mem.join(arena, " ", make_argv.items);
5773 fatal("the following command cannot be executed ({t} does not support spawning a child process):\n{s}", .{
5774 native_os, cmd,
5775 });
5776 }
5777
5778 const term = term: {
5779 _ = try io.lockStderr(&.{}, .no_color);
5780 defer io.unlockStderr();
5781 var child = std.process.spawn(io, .{
5782 .argv = make_argv.items,
5783 }) catch |err| fatal("failed spawning maker {s}: {t}", .{ make_argv.items[0], err });
5784 defer child.kill(io);
5785 break :term child.wait(io) catch |err|
5786 fatal("failed waiting on maker {s}: {t}", .{ make_argv.items[0], err });
5787 };
5788 if (term.success()) return cleanExit(io);
5789 const cmd = try std.mem.join(arena, " ", make_argv.items);
5790 fatal("the following maker command {f}:\n{s}", .{ term, cmd });
5791}
5792
5793const MakeRunner = struct {
5794 exe_path: Path,
5795
5796 const Options = struct {
5797 environ_map: *const process.Environ.Map,
5798 dirs: Compilation.Directories,
5799 parent_prog_node: std.Progress.Node,
5800 resolved_target: Package.Module.ResolvedTarget,
5801 libc_installation: ?*const LibCInstallation,
5802 self_exe_path: []const u8,
5803 thread_limit: usize,
5804 color: Color,
5805 reference_trace: ?u32,
5806 optimize_mode: std.builtin.OptimizeMode,
5807 };
5808};
5809
5810fn compileMakeRunner(gpa: Allocator, arena: Allocator, io: Io, options: MakeRunner.Options) !MakeRunner {
5811 const compile_prog_node = options.parent_prog_node.start("Compile Maker", 0);
5812 defer compile_prog_node.end();
5813
5814 const strip = options.optimize_mode != .Debug;
5815
5816 const main_mod_paths: Package.Module.CreateOptions.Paths = .{
5817 .root = try .fromRoot(arena, options.dirs, .zig_lib, "compiler"),
5818 .root_src_path = "Maker.zig",
5819 };
5820
5821 const config = try Compilation.Config.resolve(.{
5822 .output_mode = .Exe,
5823 .root_strip = strip,
5824 .root_optimize_mode = options.optimize_mode,
5825 .resolved_target = options.resolved_target,
5826 .have_zcu = true,
5827 .emit_bin = true,
5828 .is_test = false,
5829 });
5830
5831 const root_mod = try Package.Module.create(arena, .{
5832 .paths = main_mod_paths,
5833 .fully_qualified_name = "root",
5834 .cc_argv = &.{},
5835 .inherited = .{
5836 .resolved_target = options.resolved_target,
5837 .optimize_mode = options.optimize_mode,
5838 .strip = strip,
5839 },
5840 .global = config,
5841 .parent = null,
5842 });
5843
5844 var create_diag: Compilation.CreateDiagnostic = undefined;
5845 const comp = Compilation.create(gpa, arena, io, &create_diag, .{
5846 .dirs = options.dirs,
5847 .root_name = "maker",
5848 .config = config,
5849 .root_mod = root_mod,
5850 .main_mod = root_mod,
5851 .emit_bin = .yes_cache,
5852 .self_exe_path = options.self_exe_path,
5853 .thread_limit = options.thread_limit,
5854 .cache_mode = .whole,
5855 .environ_map = options.environ_map,
5856 .reference_trace = options.reference_trace,
5857 }) catch |err| switch (err) {
5858 error.CreateFail => fatal("failed to create compilation: {f}", .{create_diag}),
5859 error.Canceled => |e| return e,
5860 else => |e| fatal("failed to create compilation: {t}", .{e}),
5861 };
5862 defer comp.destroy();
5863
5864 try updateModule(comp, options.color, compile_prog_node);
5865
5866 const exe_path: Path = .{
5867 .root_dir = options.dirs.global_cache,
5868 .sub_path = try std.fmt.allocPrint(arena, "o/{s}/{s}", .{
5869 &Cache.binToHex(comp.digest.?), comp.emit_bin.?,
5870 }),
5871 };
5872
5873 return .{
5874 .exe_path = exe_path,
5875 };
56275876}
56285877
56295878const Fork = struct {
......@@ -5634,6 +5883,20 @@ const Fork = struct {
56345883 failed: bool,
56355884 arena_allocator: std.heap.ArenaAllocator,
56365885
5886 fn init(cwd_relative_path: []const u8) Fork {
5887 return .{
5888 .manifest_ast = undefined,
5889 .manifest = undefined,
5890 .error_bundle = undefined,
5891 .arena_allocator = undefined,
5892 .path = .{
5893 .root_dir = .cwd(),
5894 .sub_path = cwd_relative_path,
5895 },
5896 .failed = false,
5897 };
5898 }
5899
56375900 fn load(io: Io, gpa: Allocator, fork: *Fork, color: Color) Io.Cancelable!void {
56385901 loadFallible(io, gpa, fork, color) catch |err| switch (err) {
56395902 error.Canceled => |e| return e,
......@@ -5749,6 +6012,8 @@ fn jitCmdInner(
57496012 const override_lib_dir: ?[]const u8 = EnvVar.ZIG_LIB_DIR.get(environ_map);
57506013 const override_global_cache_dir: ?[]const u8 = EnvVar.ZIG_GLOBAL_CACHE_DIR.get(environ_map);
57516014
6015 const cwd_path = try introspect.getResolvedCwd(io, arena);
6016
57526017 // This `init` calls `fatal` on error.
57536018 var dirs: Compilation.Directories = .init(
57546019 arena,
......@@ -5759,6 +6024,7 @@ fn jitCmdInner(
57596024 preopens,
57606025 self_exe_path,
57616026 environ_map,
6027 cwd_path,
57626028 );
57636029 defer dirs.deinit(io);
57646030
......@@ -5829,7 +6095,7 @@ fn jitCmdInner(
58296095 .environ_map = environ_map,
58306096 }) catch |err| switch (err) {
58316097 error.CreateFail => fatal("failed to create compilation: {f}", .{create_diag}),
5832 else => fatal("failed to create compilation: {s}", .{@errorName(err)}),
6098 else => fatal("failed to create compilation: {t}", .{err}),
58336099 };
58346100 defer comp.destroy();
58356101
......@@ -6582,7 +6848,11 @@ fn warnAboutForeignBinaries(
65826848 const host_query: std.Target.Query = .{};
65836849 const host_target = std.zig.resolveTargetQueryOrFatal(io, host_query);
65846850
6585 switch (std.zig.system.getExternalExecutor(io, &host_target, target, .{ .link_libc = link_libc })) {
6851 switch (std.zig.system.getExternalExecutor(io, target, .{
6852 .host_cpu_arch = host_target.cpu.arch,
6853 .host_os_tag = host_target.os.tag,
6854 .link_libc = link_libc,
6855 })) {
65866856 .native => return,
65876857 .rosetta => {
65886858 const host_name = try host_target.zigTriple(arena);
src/print_env.zig+4
......@@ -8,6 +8,7 @@ const fatal = std.process.fatal;
88
99const build_options = @import("build_options");
1010const Compilation = @import("Compilation.zig");
11const introspect = @import("introspect.zig");
1112
1213pub fn cmdEnv(
1314 arena: Allocator,
......@@ -28,6 +29,8 @@ pub fn cmdEnv(
2829 },
2930 };
3031
32 const cwd_path = try introspect.getResolvedCwd(io, arena);
33
3134 var dirs: Compilation.Directories = .init(
3235 arena,
3336 io,
......@@ -37,6 +40,7 @@ pub fn cmdEnv(
3740 preopens,
3841 if (builtin.target.os.tag != .wasi) self_exe_path,
3942 environ_map,
43 cwd_path,
4044 );
4145 defer dirs.deinit(io);
4246
test/src/Cases.zig+18-10
......@@ -470,8 +470,9 @@ pub fn lowerToBuildSteps(
470470 options: CaseTestOptions,
471471) void {
472472 const io = self.io;
473 const graph = b.graph;
474 const arena = graph.arena;
473475 const host = b.resolveTargetQuery(.{});
474 const cases_dir_path = b.build_root.join(b.allocator, &.{ "test", "cases" }) catch @panic("OOM");
475476
476477 for (self.cases.items) |case| {
477478 for (options.test_filters) |test_filter| {
......@@ -504,7 +505,7 @@ pub fn lowerToBuildSteps(
504505 );
505506 if (options.skip_llvm and would_use_llvm) continue;
506507
507 const triple_txt = case.target.query.zigTriple(b.allocator) catch @panic("OOM");
508 const triple_txt = case.target.query.zigTriple(arena) catch @panic("OOM");
508509
509510 if (options.test_target_filters.len > 0) {
510511 for (options.test_target_filters) |filter| {
......@@ -516,7 +517,7 @@ pub fn lowerToBuildSteps(
516517 continue;
517518
518519 const writefiles = b.addWriteFiles();
519 var file_sources = std.StringHashMap(std.Build.LazyPath).init(b.allocator);
520 var file_sources = std.StringHashMap(std.Build.LazyPath).init(arena);
520521 defer file_sources.deinit();
521522 const first_file = case.files.items[0];
522523 const root_source_file = writefiles.add(first_file.path, first_file.src);
......@@ -526,12 +527,15 @@ pub fn lowerToBuildSteps(
526527 }
527528
528529 for (case.imports) |import_rel| {
529 const import_abs = std.fs.path.join(b.allocator, &.{
530 cases_dir_path,
531 case.import_path orelse @panic("import_path not set"),
532 import_rel,
533 }) catch @panic("OOM");
534 _ = writefiles.addCopyFile(.{ .cwd_relative = import_abs }, import_rel);
530 _ = writefiles.addCopyFile(.{ .src_path = .{
531 .owner = b,
532 .sub_path = b.pathJoin(&.{
533 "test",
534 "cases",
535 case.import_path orelse @panic("import_path not set"),
536 import_rel,
537 }),
538 } }, import_rel);
535539 }
536540
537541 const mod = b.createModule(.{
......@@ -605,7 +609,11 @@ pub fn lowerToBuildSteps(
605609 },
606610 .Execution => |expected_stdout| no_exec: {
607611 const run = if (case.target.result.ofmt == .c) run_step: {
608 if (getExternalExecutor(io, &host.result, &case.target.result, .{ .link_libc = true }) != .native) {
612 if (getExternalExecutor(io, &case.target.result, .{
613 .host_cpu_arch = host.result.cpu.arch,
614 .host_os_tag = host.result.os.tag,
615 .link_libc = true,
616 }) != .native) {
609617 // We wouldn't be able to run the compiled C code.
610618 break :no_exec;
611619 }
test/src/Libc.zig+5-2
......@@ -31,9 +31,11 @@ pub fn addLibcTestCase(
3131 supports_wasi_libc: bool,
3232 options: LibcTestCaseOption,
3333) void {
34 const name = libc.b.dupe(path[0 .. path.len - std.fs.path.extension(path).len]);
34 const graph = libc.b.graph;
35 const arena = graph.arena;
36 const name = arena.dupe(u8, path[0 .. path.len - std.fs.path.extension(path).len]) catch @panic("OOM");
3537 std.mem.replaceScalar(u8, name, '/', '.');
36 libc.test_cases.append(libc.b.allocator, .{
38 libc.test_cases.append(arena, .{
3739 .name = name,
3840 .src_file = libc.libc_test_src_path.path(libc.b, path),
3941 .additional_src_file = if (options.additional_src_file) |additional_src_file| libc.libc_test_src_path.path(libc.b, additional_src_file) else null,
......@@ -112,6 +114,7 @@ pub fn addTarget(libc: *const Libc, target: std.Build.ResolvedTarget) void {
112114 const run = libc.b.addRunArtifact(exe);
113115 run.setName(annotated_case_name);
114116 run.skip_foreign_checks = true;
117 run.disable_zig_progress = true; // can interfere with fd count assumptions
115118 run.expectStdErrEqual("");
116119 run.expectStdOutEqual("");
117120 run.expectExitCode(0);
test/standalone/build.zig.zon-6
......@@ -153,9 +153,6 @@
153153 .compiler_rt_panic = .{
154154 .path = "compiler_rt_panic",
155155 },
156 .ios = .{
157 .path = "ios",
158 },
159156 .depend_on_main_mod = .{
160157 .path = "depend_on_main_mod",
161158 },
......@@ -171,9 +168,6 @@
171168 .run_output_paths = .{
172169 .path = "run_output_paths",
173170 },
174 .run_output_caching = .{
175 .path = "run_output_caching",
176 },
177171 .empty_global_error_set = .{
178172 .path = "empty_global_error_set",
179173 },
test/standalone/cmakedefine/build.zig-1
......@@ -48,7 +48,6 @@ pub fn build(b: *std.Build) void {
4848 .include_path = "stack.h",
4949 },
5050 .{
51 .AT = "@",
5251 .UNDERSCORE = "_",
5352 .NEST_UNDERSCORE_PROXY = "UNDERSCORE",
5453 .NEST_PROXY = "NEST_UNDERSCORE_PROXY",
test/standalone/dependency_options/build.zig+1-1
......@@ -10,7 +10,7 @@ pub fn build(b: *std.Build) !void {
1010
1111 const none_specified_mod = none_specified.module("dummy");
1212 if (!none_specified_mod.resolved_target.?.query.eql(b.graph.host.query)) return error.TestFailed;
13 const expected_optimize: std.builtin.OptimizeMode = switch (b.release_mode) {
13 const expected_optimize: std.builtin.OptimizeMode = switch (b.graph.release_mode) {
1414 .off => .Debug,
1515 .any => unreachable,
1616 .fast => .ReleaseFast,
test/standalone/dirname/build.zig+6-29
......@@ -27,41 +27,16 @@ pub fn build(b: *std.Build) void {
2727 }),
2828 });
2929
30 const has_basename = b.addExecutable(.{
31 .name = "has_basename",
32 .root_module = b.createModule(.{
33 .root_source_file = b.path("has_basename.zig"),
34 .optimize = .Debug,
35 .target = target,
36 }),
37 });
38
39 // Known path:
40 addTestRun(test_step, exists_in, touch_src.dirname(), &.{"touch.zig"});
41
42 // Generated file:
43 addTestRun(test_step, exists_in, generated.dirname(), &.{"generated.txt"});
44
45 // Generated file multiple levels:
46 addTestRun(test_step, exists_in, generated.dirname().dirname(), &.{
30 addTestRun(test_step, exists_in, "run exists_in (known path)", touch_src.dirname(), &.{"touch.zig"});
31 addTestRun(test_step, exists_in, "run exists_in (generated file)", generated.dirname(), &.{"generated.txt"});
32 addTestRun(test_step, exists_in, "run exists_in (generated file multi level)", generated.dirname().dirname(), &.{
4733 "subdir" ++ std.fs.path.sep_str ++ "generated.txt",
4834 });
4935
50 // Cache root:
51 const cache_dir = b.cache_root.path orelse
52 (b.cache_root.join(b.allocator, &.{"."}) catch @panic("OOM"));
53 addTestRun(
54 test_step,
55 has_basename,
56 generated.dirname().dirname().dirname().dirname(),
57 &.{std.fs.path.basename(cache_dir)},
58 );
59
60 // Absolute path:
6136 const write_files = b.addWriteFiles();
6237 _ = write_files.add("foo.txt", "");
6338 const abs_path = write_files.getDirectory();
64 addTestRun(test_step, exists_in, abs_path, &.{"foo.txt"});
39 addTestRun(test_step, exists_in, "run exists_in (absolute path)", abs_path, &.{"foo.txt"});
6540}
6641
6742// Runs exe with the parameters [dirname, args...].
......@@ -69,10 +44,12 @@ pub fn build(b: *std.Build) void {
6944fn addTestRun(
7045 test_step: *std.Build.Step,
7146 exe: *std.Build.Step.Compile,
47 step_name: []const u8,
7248 dirname: std.Build.LazyPath,
7349 args: []const []const u8,
7450) void {
7551 const run = test_step.owner.addRunArtifact(exe);
52 run.setName(step_name);
7653 run.addDirectoryArg(dirname);
7754 run.addArgs(args);
7855 run.expectExitCode(0);
test/standalone/dirname/touch.zig+9-10
......@@ -7,27 +7,26 @@
77//! Path must be absolute.
88
99const std = @import("std");
10const Io = std.Io;
1011
1112pub fn main(init: std.process.Init) !void {
13 const io = init.io;
14
1215 var args = try init.minimal.args.iterateAllocator(init.gpa);
1316 defer args.deinit();
14 _ = args.next() orelse unreachable; // skip binary name
17 _ = args.next().?; // skip binary name
1518
1619 const path = args.next() orelse {
1720 std.log.err("missing <path> argument", .{});
1821 return error.BadUsage;
1922 };
2023
21 const dir_path = std.Io.Dir.path.dirname(path) orelse unreachable;
22 const basename = std.Io.Dir.path.basename(path);
23
24 const io = std.Io.Threaded.global_single_threaded.io();
24 const dir_path = Io.Dir.path.dirname(path).?;
25 const basename = Io.Dir.path.basename(path);
2526
26 var dir = try std.Io.Dir.cwd().openDir(io, dir_path, .{});
27 var dir = try Io.Dir.cwd().openDir(io, dir_path, .{});
2728 defer dir.close(io);
2829
29 _ = dir.statFile(io, basename, .{}) catch {
30 var file = try dir.createFile(io, basename, .{});
31 file.close(io);
32 };
30 var file = try dir.createFile(io, basename, .{ .truncate = false });
31 file.close(io);
3332}
test/standalone/install_headers/build.zig+1-1
......@@ -106,7 +106,7 @@ pub fn build(b: *std.Build) void {
106106 "custom/include/foo/config.h",
107107 "custom/include/bar.h",
108108 });
109 run_check_exists.setCwd(.{ .cwd_relative = b.getInstallPath(.prefix, "") });
109 run_check_exists.setCwd(.{ .relative = .{ .base = .install_prefix } });
110110 run_check_exists.expectExitCode(0);
111111 run_check_exists.step.dependOn(&install_libfoo.step);
112112 test_step.dependOn(&run_check_exists.step);
test/standalone/ios/build.zig deleted-40
......@@ -1,40 +0,0 @@
1const std = @import("std");
2
3pub const requires_symlinks = true;
4pub const requires_ios_sdk = true;
5
6pub fn build(b: *std.Build) void {
7 const test_step = b.step("test", "Test it");
8 b.default_step = test_step;
9
10 const optimize: std.builtin.OptimizeMode = .Debug;
11 const target = b.resolveTargetQuery(.{
12 .cpu_arch = .aarch64,
13 .os_tag = .ios,
14 });
15
16 const exe = b.addExecutable(.{
17 .name = "main",
18 .root_module = b.createModule(.{
19 .root_source_file = null,
20 .optimize = optimize,
21 .target = target,
22 .link_libc = true,
23 }),
24 });
25
26 const io = b.graph.io;
27
28 if (std.zig.system.darwin.getSdk(b.allocator, io, &target.result)) |sdk| {
29 b.sysroot = sdk;
30 exe.root_module.addSystemIncludePath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/usr/include" }) });
31 exe.root_module.addSystemFrameworkPath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/System/Library/Frameworks" }) });
32 exe.root_module.addLibraryPath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/usr/lib" }) });
33 } else {
34 exe.step.dependOn(&b.addFail("no iOS SDK found").step);
35 }
36
37 exe.root_module.addCSourceFile(.{ .file = b.path("main.m"), .flags = &.{} });
38 exe.root_module.linkFramework("Foundation", .{});
39 exe.root_module.linkFramework("UIKit", .{});
40}
test/standalone/ios/main.m deleted-34
......@@ -1,34 +0,0 @@
1#import <UIKit/UIKit.h>
2
3@interface AppDelegate : UIResponder <UIApplicationDelegate>
4@property (strong, nonatomic) UIWindow *window;
5@end
6
7int main() {
8 @autoreleasepool {
9 return UIApplicationMain(0, nil, nil, NSStringFromClass([AppDelegate class]));
10 }
11}
12
13@implementation AppDelegate
14
15- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(id)options {
16 CGRect mainScreenBounds = [[UIScreen mainScreen] bounds];
17 self.window = [[UIWindow alloc] initWithFrame:mainScreenBounds];
18 UIViewController *viewController = [[UIViewController alloc] init];
19 viewController.view.frame = mainScreenBounds;
20
21 NSString* msg = @"Hello world";
22
23 UILabel *label = [[UILabel alloc] initWithFrame:mainScreenBounds];
24 [label setText:msg];
25 [viewController.view addSubview: label];
26
27 self.window.rootViewController = viewController;
28
29 [self.window makeKeyAndVisible];
30
31 return YES;
32}
33
34@end
test/standalone/libfuzzer/build.zig+1-1
......@@ -24,6 +24,6 @@ pub fn build(b: *std.Build) void {
2424 b.default_step = run_step;
2525
2626 const run_artifact = b.addRunArtifact(exe);
27 run_artifact.addArg(b.cache_root.path orelse "");
27 run_artifact.addFileArg(.cache_root);
2828 run_step.dependOn(&run_artifact.step);
2929}
test/standalone/run_output_caching/build.zig deleted-140
......@@ -1,140 +0,0 @@
1const builtin = @import("builtin");
2const std = @import("std");
3
4pub fn build(b: *std.Build) void {
5 const test_step = b.step("test", "Test it");
6 b.default_step = test_step;
7
8 if (builtin.os.tag == .windows) return; // https://codeberg.org/ziglang/zig/issues/31564
9
10 const target = b.standardTargetOptions(.{});
11 const optimize = b.standardOptimizeOption(.{});
12
13 const exe = b.addExecutable(.{
14 .name = "create-file",
15 .root_module = b.createModule(.{
16 .root_source_file = b.path("main.zig"),
17 .target = target,
18 .optimize = optimize,
19 }),
20 });
21
22 {
23 const run_random_with_sideeffects_first = b.addRunArtifact(exe);
24 run_random_with_sideeffects_first.setName("run with side-effects (first)");
25 run_random_with_sideeffects_first.has_side_effects = true;
26
27 const run_random_with_sideeffects_second = b.addRunArtifact(exe);
28 run_random_with_sideeffects_second.setName("run with side-effects (second)");
29 run_random_with_sideeffects_second.has_side_effects = true;
30
31 // ensure that "second" runs after "first"
32 run_random_with_sideeffects_second.step.dependOn(&run_random_with_sideeffects_first.step);
33
34 const first_output = run_random_with_sideeffects_first.addOutputFileArg("a.txt");
35 const second_output = run_random_with_sideeffects_second.addOutputFileArg("a.txt");
36
37 const expect_uncached_dependencies = CheckOutputCaching.init(b, false, &.{ first_output, second_output });
38 test_step.dependOn(&expect_uncached_dependencies.step);
39
40 const expect_unequal_output = CheckPathEquality.init(b, true, &.{ first_output, second_output });
41 test_step.dependOn(&expect_unequal_output.step);
42
43 const check_first_output = b.addCheckFile(first_output, .{ .expected_matches = &.{"a.txt"} });
44 test_step.dependOn(&check_first_output.step);
45 const check_second_output = b.addCheckFile(second_output, .{ .expected_matches = &.{"a.txt"} });
46 test_step.dependOn(&check_second_output.step);
47 }
48
49 {
50 const run_random_without_sideeffects_1 = b.addRunArtifact(exe);
51 run_random_without_sideeffects_1.setName("run without side-effects (A)");
52
53 const run_random_without_sideeffects_2 = b.addRunArtifact(exe);
54 run_random_without_sideeffects_2.setName("run without side-effects (B)");
55
56 run_random_without_sideeffects_2.step.dependOn(&run_random_without_sideeffects_1.step);
57
58 const first_output = run_random_without_sideeffects_1.addOutputFileArg("a.txt");
59 const second_output = run_random_without_sideeffects_2.addOutputFileArg("a.txt");
60
61 const expect_cached_dependencies = CheckOutputCaching.init(b, true, &.{second_output});
62 test_step.dependOn(&expect_cached_dependencies.step);
63
64 const expect_equal_output = CheckPathEquality.init(b, true, &.{ first_output, second_output });
65 test_step.dependOn(&expect_equal_output.step);
66
67 const check_first_output = b.addCheckFile(first_output, .{ .expected_matches = &.{"a.txt"} });
68 test_step.dependOn(&check_first_output.step);
69 const check_second_output = b.addCheckFile(second_output, .{ .expected_matches = &.{"a.txt"} });
70 test_step.dependOn(&check_second_output.step);
71 }
72}
73
74const CheckOutputCaching = struct {
75 step: std.Build.Step,
76 expect_caching: bool,
77
78 pub fn init(owner: *std.Build, expect_caching: bool, output_paths: []const std.Build.LazyPath) *CheckOutputCaching {
79 const check = owner.allocator.create(CheckOutputCaching) catch @panic("OOM");
80 check.* = .{
81 .step = std.Build.Step.init(.{
82 .id = .custom,
83 .name = "check output caching",
84 .owner = owner,
85 .makeFn = make,
86 }),
87 .expect_caching = expect_caching,
88 };
89 for (output_paths) |output_path| {
90 output_path.addStepDependencies(&check.step);
91 }
92 return check;
93 }
94
95 fn make(step: *std.Build.Step, _: std.Build.Step.MakeOptions) !void {
96 const check: *CheckOutputCaching = @fieldParentPtr("step", step);
97
98 for (step.dependencies.items) |dependency| {
99 if (check.expect_caching) {
100 if (dependency.result_cached) continue;
101 return step.fail("expected '{s}' step to be cached, but it was not", .{dependency.name});
102 } else {
103 if (!dependency.result_cached) continue;
104 return step.fail("expected '{s}' step to not be cached, but it was", .{dependency.name});
105 }
106 }
107 }
108};
109
110const CheckPathEquality = struct {
111 step: std.Build.Step,
112 expected_equality: bool,
113 output_paths: []const std.Build.LazyPath,
114
115 pub fn init(owner: *std.Build, expected_equality: bool, output_paths: []const std.Build.LazyPath) *CheckPathEquality {
116 const check = owner.allocator.create(CheckPathEquality) catch @panic("OOM");
117 check.* = .{
118 .step = std.Build.Step.init(.{
119 .id = .custom,
120 .name = "check output path equality",
121 .owner = owner,
122 .makeFn = make,
123 }),
124 .expected_equality = expected_equality,
125 .output_paths = owner.allocator.dupe(std.Build.LazyPath, output_paths) catch @panic("OOM"),
126 };
127 for (output_paths) |output_path| {
128 output_path.addStepDependencies(&check.step);
129 }
130 return check;
131 }
132
133 fn make(step: *std.Build.Step, _: std.Build.Step.MakeOptions) !void {
134 const check: *CheckPathEquality = @fieldParentPtr("step", step);
135 std.debug.assert(check.output_paths.len != 0);
136 for (check.output_paths[0 .. check.output_paths.len - 1], check.output_paths[1..]) |a, b| {
137 try std.testing.expectEqual(check.expected_equality, std.mem.eql(u8, a.getPath(step.owner), b.getPath(step.owner)));
138 }
139 }
140};
test/standalone/run_output_caching/main.zig deleted-11
......@@ -1,11 +0,0 @@
1const std = @import("std");
2
3pub fn main(init: std.process.Init) !void {
4 const io = init.io;
5 var args = try init.minimal.args.iterateAllocator(init.arena.allocator());
6 _ = args.skip();
7 const filename = args.next().?;
8 const file = try std.Io.Dir.cwd().createFile(io, filename, .{});
9 defer file.close(io);
10 try file.writeStreamingAll(io, filename);
11}
test/standalone/windows_resources/build.zig+1-1
......@@ -38,7 +38,7 @@ fn add(
3838 .file = b.path("res/zig.rc"),
3939 .flags = &.{"/c65001"}, // UTF-8 code page
4040 .include_paths = &.{
41 .{ .generated = .{ .file = &generated_h_step.generated_directory } },
41 .{ .generated = .{ .index = generated_h_step.generated_directory } },
4242 },
4343 });
4444 exe.rc_includes = switch (rc_includes) {
test/tests.zig+10-7
......@@ -2433,8 +2433,10 @@ pub fn addCliTests(b: *std.Build) *Step {
24332433 });
24342434 run_test.addArg("--build-file");
24352435 run_test.addFileArg(b.path("test/cli/options/build.zig"));
2436
24362437 run_test.addArg("--cache-dir");
2437 run_test.addFileArg(.{ .cwd_relative = b.cache_root.join(b.allocator, &.{}) catch @panic("OOM") });
2438 run_test.addFileArg(.cache_root);
2439
24382440 run_test.setName("test build options");
24392441
24402442 step.dependOn(&run_test.step);
......@@ -2890,7 +2892,7 @@ pub fn addCases(
28902892
28912893 var cases = @import("src/Cases.zig").init(gpa, arena, io);
28922894
2893 var dir = try b.build_root.handle.openDir(io, "test/cases", .{ .iterate = true });
2895 var dir = try b.root.openDir(io, "test/cases", .{ .iterate = true });
28942896 defer dir.close(io);
28952897
28962898 cases.addFromDir(dir, b);
......@@ -2948,7 +2950,7 @@ pub fn addIncrementalTests(b: *std.Build, test_step: *Step, test_filters: []cons
29482950 }),
29492951 });
29502952
2951 var dir = try b.build_root.handle.openDir(io, "test/incremental", .{ .iterate = true });
2953 var dir = try b.root.openDir(io, "test/incremental", .{ .iterate = true });
29522954 defer dir.close(io);
29532955
29542956 var it = try dir.walk(b.graph.arena);
......@@ -2966,10 +2968,11 @@ pub fn addIncrementalTests(b: *std.Build, test_step: *Step, test_filters: []cons
29662968
29672969 run.addArg(b.graph.zig_exe);
29682970 run.addFileArg(b.path("test/incremental/").path(b, entry.path));
2969 run.addArgs(&.{
2970 "--zig-lib-dir", b.graph.zig_lib_directory.path orelse ".",
2971 "--target", target_str,
2972 });
2971
2972 run.addArg("--zig-lib-dir");
2973 run.addDirectoryArg(.zig_lib);
2974
2975 run.addArgs(&.{ "--target", target_str });
29732976
29742977 run.addArg("--quiet"); // don't fill stderr telling us about skipped tests etc
29752978
tools/docgen.zig+16-5
......@@ -3,6 +3,7 @@ const builtin = @import("builtin");
33const std = @import("std");
44const Io = std.Io;
55const Dir = std.Io.Dir;
6const Path = std.Build.Cache.Path;
67const process = std.process;
78const Progress = std.Progress;
89const print = std.debug.print;
......@@ -73,8 +74,13 @@ pub fn main(init: std.process.Init) !void {
7374 var out_file_buffer: [4096]u8 = undefined;
7475 var out_file_writer = out_file.writer(io, &out_file_buffer);
7576
76 var code_dir = try Dir.cwd().openDir(io, code_dir_path, .{});
77 defer code_dir.close(io);
77 var code_dir: Path = .{
78 .root_dir = .{
79 .handle = try Dir.cwd().openDir(io, code_dir_path, .{}),
80 .path = code_dir_path,
81 },
82 };
83 defer code_dir.root_dir.handle.close(io);
7884
7985 var in_file_reader = in_file.reader(io, &.{});
8086 const input_file_bytes = try in_file_reader.interface.allocRemaining(arena, .limited(max_doc_file_size));
......@@ -988,7 +994,7 @@ fn genHtml(
988994 io: Io,
989995 tokenizer: *Tokenizer,
990996 toc: *Toc,
991 code_dir: Dir,
997 code_dir: Path,
992998 out: *Writer,
993999) !void {
9941000 for (toc.nodes) |node| {
......@@ -1044,8 +1050,13 @@ fn genHtml(
10441050 });
10451051 defer allocator.free(out_basename);
10461052
1047 const contents = code_dir.readFileAlloc(io, out_basename, allocator, .limited(std.math.maxInt(u32))) catch |err| {
1048 return parseError(tokenizer, code.token, "unable to open '{s}': {t}", .{ out_basename, err });
1053 const out_path: Path = .{
1054 .root_dir = code_dir.root_dir,
1055 .sub_path = out_basename,
1056 };
1057
1058 const contents = out_path.root_dir.handle.readFileAlloc(io, out_path.sub_path, allocator, .unlimited) catch |err| {
1059 return parseError(tokenizer, code.token, "failed opening {f}: {t}", .{ out_path, err });
10491060 };
10501061 defer allocator.free(contents);
10511062
tools/doctest.zig+7-2
......@@ -311,7 +311,9 @@ fn printOutput(
311311 .arch_os_abi = triple,
312312 });
313313 const target = try std.zig.system.resolveTargetQuery(io, target_query);
314 switch (getExternalExecutor(io, &host, &target, .{
314 switch (getExternalExecutor(io, &target, .{
315 .host_cpu_arch = host.cpu.arch,
316 .host_os_tag = host.os.tag,
315317 .link_libc = code.link_libc,
316318 })) {
317319 .native => {},
......@@ -526,7 +528,10 @@ fn printOutput(
526528 .lib => {
527529 const bin_basename = try std.zig.binNameAlloc(arena, .{
528530 .root_name = code_name,
529 .target = &builtin.target,
531 .cpu_arch = builtin.target.cpu.arch,
532 .os_tag = builtin.target.os.tag,
533 .ofmt = builtin.target.ofmt,
534 .abi = builtin.target.abi,
530535 .output_mode = .Lib,
531536 });
532537
tools/incr-check.zig+9-3
......@@ -360,7 +360,10 @@ const Eval = struct {
360360
361361 const bin_name = try std.zig.EmitArtifact.bin.cacheName(arena, .{
362362 .root_name = "root", // corresponds to the module name "root"
363 .target = &eval.target,
363 .cpu_arch = eval.target.cpu.arch,
364 .os_tag = eval.target.os.tag,
365 .ofmt = eval.target.ofmt,
366 .abi = eval.target.abi,
364367 .output_mode = .Exe,
365368 });
366369 const bin_path = try Dir.path.join(arena, &.{ result_dir, bin_name });
......@@ -487,9 +490,12 @@ const Eval = struct {
487490 var argv_buf: [2][]const u8 = undefined;
488491 const argv: []const []const u8, const is_foreign: bool = sw: switch (std.zig.system.getExternalExecutor(
489492 io,
490 &eval.host,
491493 &eval.target,
492 .{ .link_libc = eval.backend == .cbe },
494 .{
495 .link_libc = eval.backend == .cbe,
496 .host_cpu_arch = eval.host.cpu.arch,
497 .host_os_tag = eval.host.os.tag,
498 },
493499 )) {
494500 .bad_dl, .bad_os_or_cpu => {
495501 // This binary cannot be executed on this host.