master
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.fs.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;
17const tty = std.Io.tty;
18
19pub const root = @import("@build");
20pub const dependencies = @import("@dependencies");
21
22pub const std_options: std.Options = .{
23 .side_channels_mitigations = .none,
24 .http_disable_tls = true,
25 .crypto_fork_safety = false,
26};
27
28pub fn main() !void {
29 // The build runner is often short-lived, but thanks to `--watch` and `--webui`, that's not
30 // always the case. So, we do need a true gpa for some things.
31 var debug_gpa_state: std.heap.DebugAllocator(.{}) = .init;
32 defer _ = debug_gpa_state.deinit();
33 const gpa = debug_gpa_state.allocator();
34
35 // ...but we'll back our arena by `std.heap.page_allocator` for efficiency.
36 var single_threaded_arena: std.heap.ArenaAllocator = .init(std.heap.page_allocator);
37 defer single_threaded_arena.deinit();
38 var thread_safe_arena: std.heap.ThreadSafeAllocator = .{ .child_allocator = single_threaded_arena.allocator() };
39 const arena = thread_safe_arena.allocator();
40
41 const args = try process.argsAlloc(arena);
42
43 var threaded: std.Io.Threaded = .init(gpa);
44 defer threaded.deinit();
45 const io = threaded.io();
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 zig_lib_directory: std.Build.Cache.Directory = .{
57 .path = zig_lib_dir,
58 .handle = try std.fs.cwd().openDir(zig_lib_dir, .{}),
59 };
60
61 const build_root_directory: std.Build.Cache.Directory = .{
62 .path = build_root,
63 .handle = try std.fs.cwd().openDir(build_root, .{}),
64 };
65
66 const local_cache_directory: std.Build.Cache.Directory = .{
67 .path = cache_root,
68 .handle = try std.fs.cwd().makeOpenPath(cache_root, .{}),
69 };
70
71 const global_cache_directory: std.Build.Cache.Directory = .{
72 .path = global_cache_root,
73 .handle = try std.fs.cwd().makeOpenPath(global_cache_root, .{}),
74 };
75
76 var graph: std.Build.Graph = .{
77 .io = io,
78 .arena = arena,
79 .cache = .{
80 .io = io,
81 .gpa = arena,
82 .manifest_dir = try local_cache_directory.handle.makeOpenPath("h", .{}),
83 },
84 .zig_exe = zig_exe,
85 .env_map = try process.getEnvMap(arena),
86 .global_cache_root = global_cache_directory,
87 .zig_lib_directory = zig_lib_directory,
88 .host = .{
89 .query = .{},
90 .result = try std.zig.system.resolveTargetQuery(io, .{}),
91 },
92 .time_report = false,
93 };
94
95 graph.cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() });
96 graph.cache.addPrefix(build_root_directory);
97 graph.cache.addPrefix(local_cache_directory);
98 graph.cache.addPrefix(global_cache_directory);
99 graph.cache.hash.addBytes(builtin.zig_version_string);
100
101 const builder = try std.Build.create(
102 &graph,
103 build_root_directory,
104 local_cache_directory,
105 dependencies.root_deps,
106 );
107
108 var targets = std.array_list.Managed([]const u8).init(arena);
109 var debug_log_scopes = std.array_list.Managed([]const u8).init(arena);
110
111 var install_prefix: ?[]const u8 = null;
112 var dir_list = std.Build.DirList{};
113 var error_style: ErrorStyle = .verbose;
114 var multiline_errors: MultilineErrors = .indent;
115 var summary: ?Summary = null;
116 var max_rss: u64 = 0;
117 var skip_oom_steps = false;
118 var test_timeout_ns: ?u64 = null;
119 var color: Color = .auto;
120 var help_menu = false;
121 var steps_menu = false;
122 var output_tmp_nonce: ?[16]u8 = null;
123 var watch = false;
124 var fuzz: ?std.Build.Fuzz.Mode = null;
125 var debounce_interval_ms: u16 = 50;
126 var webui_listen: ?Io.net.IpAddress = null;
127
128 if (try std.zig.EnvVar.ZIG_BUILD_ERROR_STYLE.get(arena)) |str| {
129 if (std.meta.stringToEnum(ErrorStyle, str)) |style| {
130 error_style = style;
131 }
132 }
133
134 if (try std.zig.EnvVar.ZIG_BUILD_MULTILINE_ERRORS.get(arena)) |str| {
135 if (std.meta.stringToEnum(MultilineErrors, str)) |style| {
136 multiline_errors = style;
137 }
138 }
139
140 while (nextArg(args, &arg_idx)) |arg| {
141 if (mem.startsWith(u8, arg, "-Z")) {
142 if (arg.len != 18) fatalWithHint("bad argument: '{s}'", .{arg});
143 output_tmp_nonce = arg[2..18].*;
144 } else if (mem.startsWith(u8, arg, "-D")) {
145 const option_contents = arg[2..];
146 if (option_contents.len == 0)
147 fatalWithHint("expected option name after '-D'", .{});
148 if (mem.indexOfScalar(u8, option_contents, '=')) |name_end| {
149 const option_name = option_contents[0..name_end];
150 const option_value = option_contents[name_end + 1 ..];
151 if (try builder.addUserInputOption(option_name, option_value))
152 fatal(" access the help menu with 'zig build -h'", .{});
153 } else {
154 if (try builder.addUserInputFlag(option_contents))
155 fatal(" access the help menu with 'zig build -h'", .{});
156 }
157 } else if (mem.startsWith(u8, arg, "-")) {
158 if (mem.eql(u8, arg, "--verbose")) {
159 builder.verbose = true;
160 } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
161 help_menu = true;
162 } else if (mem.eql(u8, arg, "-p") or mem.eql(u8, arg, "--prefix")) {
163 install_prefix = nextArgOrFatal(args, &arg_idx);
164 } else if (mem.eql(u8, arg, "-l") or mem.eql(u8, arg, "--list-steps")) {
165 steps_menu = true;
166 } else if (mem.startsWith(u8, arg, "-fsys=")) {
167 const name = arg["-fsys=".len..];
168 graph.system_library_options.put(arena, name, .user_enabled) catch @panic("OOM");
169 } else if (mem.startsWith(u8, arg, "-fno-sys=")) {
170 const name = arg["-fno-sys=".len..];
171 graph.system_library_options.put(arena, name, .user_disabled) catch @panic("OOM");
172 } else if (mem.eql(u8, arg, "--release")) {
173 builder.release_mode = .any;
174 } else if (mem.startsWith(u8, arg, "--release=")) {
175 const text = arg["--release=".len..];
176 builder.release_mode = std.meta.stringToEnum(std.Build.ReleaseMode, text) orelse {
177 fatalWithHint("expected [off|any|fast|safe|small] in '{s}', found '{s}'", .{
178 arg, text,
179 });
180 };
181 } else if (mem.eql(u8, arg, "--prefix-lib-dir")) {
182 dir_list.lib_dir = nextArgOrFatal(args, &arg_idx);
183 } else if (mem.eql(u8, arg, "--prefix-exe-dir")) {
184 dir_list.exe_dir = nextArgOrFatal(args, &arg_idx);
185 } else if (mem.eql(u8, arg, "--prefix-include-dir")) {
186 dir_list.include_dir = nextArgOrFatal(args, &arg_idx);
187 } else if (mem.eql(u8, arg, "--sysroot")) {
188 builder.sysroot = nextArgOrFatal(args, &arg_idx);
189 } else if (mem.eql(u8, arg, "--maxrss")) {
190 const max_rss_text = nextArgOrFatal(args, &arg_idx);
191 max_rss = std.fmt.parseIntSizeSuffix(max_rss_text, 10) catch |err| {
192 std.debug.print("invalid byte size: '{s}': {s}\n", .{
193 max_rss_text, @errorName(err),
194 });
195 process.exit(1);
196 };
197 } else if (mem.eql(u8, arg, "--skip-oom-steps")) {
198 skip_oom_steps = true;
199 } else if (mem.eql(u8, arg, "--test-timeout")) {
200 const units: []const struct { []const u8, u64 } = &.{
201 .{ "ns", 1 },
202 .{ "nanosecond", 1 },
203 .{ "us", std.time.ns_per_us },
204 .{ "microsecond", std.time.ns_per_us },
205 .{ "ms", std.time.ns_per_ms },
206 .{ "millisecond", std.time.ns_per_ms },
207 .{ "s", std.time.ns_per_s },
208 .{ "second", std.time.ns_per_s },
209 .{ "m", std.time.ns_per_min },
210 .{ "minute", std.time.ns_per_min },
211 .{ "h", std.time.ns_per_hour },
212 .{ "hour", std.time.ns_per_hour },
213 };
214 const timeout_str = nextArgOrFatal(args, &arg_idx);
215 const num_end_idx = std.mem.findLastNone(u8, timeout_str, "abcdefghijklmnopqrstuvwxyz") orelse fatal(
216 "invalid timeout '{s}': expected unit (ns, us, ms, s, m, h)",
217 .{timeout_str},
218 );
219 const num_str = timeout_str[0 .. num_end_idx + 1];
220 const unit_str = timeout_str[num_end_idx + 1 ..];
221 const unit_factor: f64 = for (units) |unit_and_factor| {
222 if (std.mem.eql(u8, unit_str, unit_and_factor[0])) {
223 break @floatFromInt(unit_and_factor[1]);
224 }
225 } else fatal(
226 "invalid timeout '{s}': invalid unit '{s}' (expected ns, us, ms, s, m, h)",
227 .{ timeout_str, unit_str },
228 );
229 const num_parsed = std.fmt.parseFloat(f64, num_str) catch |err| fatal(
230 "invalid timeout '{s}': invalid number '{s}' ({t})",
231 .{ timeout_str, num_str, err },
232 );
233 test_timeout_ns = std.math.lossyCast(u64, unit_factor * num_parsed);
234 } else if (mem.eql(u8, arg, "--search-prefix")) {
235 const search_prefix = nextArgOrFatal(args, &arg_idx);
236 builder.addSearchPrefix(search_prefix);
237 } else if (mem.eql(u8, arg, "--libc")) {
238 builder.libc_file = nextArgOrFatal(args, &arg_idx);
239 } else if (mem.eql(u8, arg, "--color")) {
240 const next_arg = nextArg(args, &arg_idx) orelse
241 fatalWithHint("expected [auto|on|off] after '{s}'", .{arg});
242 color = std.meta.stringToEnum(Color, next_arg) orelse {
243 fatalWithHint("expected [auto|on|off] after '{s}', found '{s}'", .{
244 arg, next_arg,
245 });
246 };
247 } else if (mem.eql(u8, arg, "--error-style")) {
248 const next_arg = nextArg(args, &arg_idx) orelse
249 fatalWithHint("expected style after '{s}'", .{arg});
250 error_style = std.meta.stringToEnum(ErrorStyle, next_arg) orelse {
251 fatalWithHint("expected style after '{s}', found '{s}'", .{ arg, next_arg });
252 };
253 } else if (mem.eql(u8, arg, "--multiline-errors")) {
254 const next_arg = nextArg(args, &arg_idx) orelse
255 fatalWithHint("expected style after '{s}'", .{arg});
256 multiline_errors = std.meta.stringToEnum(MultilineErrors, next_arg) orelse {
257 fatalWithHint("expected style after '{s}', found '{s}'", .{ arg, next_arg });
258 };
259 } else if (mem.eql(u8, arg, "--summary")) {
260 const next_arg = nextArg(args, &arg_idx) orelse
261 fatalWithHint("expected [all|new|failures|line|none] after '{s}'", .{arg});
262 summary = std.meta.stringToEnum(Summary, next_arg) orelse {
263 fatalWithHint("expected [all|new|failures|line|none] after '{s}', found '{s}'", .{
264 arg, next_arg,
265 });
266 };
267 } else if (mem.eql(u8, arg, "--seed")) {
268 const next_arg = nextArg(args, &arg_idx) orelse
269 fatalWithHint("expected u32 after '{s}'", .{arg});
270 graph.random_seed = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err| {
271 fatal("unable to parse seed '{s}' as unsigned 32-bit integer: {s}\n", .{
272 next_arg, @errorName(err),
273 });
274 };
275 } else if (mem.eql(u8, arg, "--build-id")) {
276 builder.build_id = .fast;
277 } else if (mem.startsWith(u8, arg, "--build-id=")) {
278 const style = arg["--build-id=".len..];
279 builder.build_id = std.zig.BuildId.parse(style) catch |err| {
280 fatal("unable to parse --build-id style '{s}': {s}", .{
281 style, @errorName(err),
282 });
283 };
284 } else if (mem.eql(u8, arg, "--debounce")) {
285 const next_arg = nextArg(args, &arg_idx) orelse
286 fatalWithHint("expected u16 after '{s}'", .{arg});
287 debounce_interval_ms = std.fmt.parseUnsigned(u16, next_arg, 0) catch |err| {
288 fatal("unable to parse debounce interval '{s}' as unsigned 16-bit integer: {s}\n", .{
289 next_arg, @errorName(err),
290 });
291 };
292 } else if (mem.eql(u8, arg, "--webui")) {
293 if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) };
294 } else if (mem.startsWith(u8, arg, "--webui=")) {
295 const addr_str = arg["--webui=".len..];
296 if (std.mem.eql(u8, addr_str, "-")) fatal("web interface cannot listen on stdio", .{});
297 webui_listen = Io.net.IpAddress.parseLiteral(addr_str) catch |err| {
298 fatal("invalid web UI address '{s}': {s}", .{ addr_str, @errorName(err) });
299 };
300 } else if (mem.eql(u8, arg, "--debug-log")) {
301 const next_arg = nextArgOrFatal(args, &arg_idx);
302 try debug_log_scopes.append(next_arg);
303 } else if (mem.eql(u8, arg, "--debug-pkg-config")) {
304 builder.debug_pkg_config = true;
305 } else if (mem.eql(u8, arg, "--debug-rt")) {
306 graph.debug_compiler_runtime_libs = true;
307 } else if (mem.eql(u8, arg, "--debug-compile-errors")) {
308 builder.debug_compile_errors = true;
309 } else if (mem.eql(u8, arg, "--debug-incremental")) {
310 builder.debug_incremental = true;
311 } else if (mem.eql(u8, arg, "--system")) {
312 // The usage text shows another argument after this parameter
313 // but it is handled by the parent process. The build runner
314 // only sees this flag.
315 graph.system_package_mode = true;
316 } else if (mem.eql(u8, arg, "--libc-runtimes") or mem.eql(u8, arg, "--glibc-runtimes")) {
317 // --glibc-runtimes was the old name of the flag; kept for compatibility for now.
318 builder.libc_runtimes_dir = nextArgOrFatal(args, &arg_idx);
319 } else if (mem.eql(u8, arg, "--verbose-link")) {
320 builder.verbose_link = true;
321 } else if (mem.eql(u8, arg, "--verbose-air")) {
322 builder.verbose_air = true;
323 } else if (mem.eql(u8, arg, "--verbose-llvm-ir")) {
324 builder.verbose_llvm_ir = "-";
325 } else if (mem.startsWith(u8, arg, "--verbose-llvm-ir=")) {
326 builder.verbose_llvm_ir = arg["--verbose-llvm-ir=".len..];
327 } else if (mem.startsWith(u8, arg, "--verbose-llvm-bc=")) {
328 builder.verbose_llvm_bc = arg["--verbose-llvm-bc=".len..];
329 } else if (mem.eql(u8, arg, "--verbose-cimport")) {
330 builder.verbose_cimport = true;
331 } else if (mem.eql(u8, arg, "--verbose-cc")) {
332 builder.verbose_cc = true;
333 } else if (mem.eql(u8, arg, "--verbose-llvm-cpu-features")) {
334 builder.verbose_llvm_cpu_features = true;
335 } else if (mem.eql(u8, arg, "--watch")) {
336 watch = true;
337 } else if (mem.eql(u8, arg, "--time-report")) {
338 graph.time_report = true;
339 if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) };
340 } else if (mem.eql(u8, arg, "--fuzz")) {
341 fuzz = .{ .forever = undefined };
342 if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) };
343 } else if (mem.startsWith(u8, arg, "--fuzz=")) {
344 const value = arg["--fuzz=".len..];
345 if (value.len == 0) fatal("missing argument to --fuzz", .{});
346
347 const unit: u8 = value[value.len - 1];
348 const digits = switch (unit) {
349 '0'...'9' => value,
350 'K', 'M', 'G' => value[0 .. value.len - 1],
351 else => fatal(
352 "invalid argument to --fuzz, expected a positive number optionally suffixed by one of: [KMG]",
353 .{},
354 ),
355 };
356
357 const amount = std.fmt.parseInt(u64, digits, 10) catch {
358 fatal(
359 "invalid argument to --fuzz, expected a positive number optionally suffixed by one of: [KMG]",
360 .{},
361 );
362 };
363
364 const normalized_amount = std.math.mul(u64, amount, switch (unit) {
365 else => unreachable,
366 '0'...'9' => 1,
367 'K' => 1000,
368 'M' => 1_000_000,
369 'G' => 1_000_000_000,
370 }) catch fatal("fuzzing limit amount overflows u64", .{});
371
372 fuzz = .{
373 .limit = .{
374 .amount = normalized_amount,
375 },
376 };
377 } else if (mem.eql(u8, arg, "-fincremental")) {
378 graph.incremental = true;
379 } else if (mem.eql(u8, arg, "-fno-incremental")) {
380 graph.incremental = false;
381 } else if (mem.eql(u8, arg, "-fwine")) {
382 builder.enable_wine = true;
383 } else if (mem.eql(u8, arg, "-fno-wine")) {
384 builder.enable_wine = false;
385 } else if (mem.eql(u8, arg, "-fqemu")) {
386 builder.enable_qemu = true;
387 } else if (mem.eql(u8, arg, "-fno-qemu")) {
388 builder.enable_qemu = false;
389 } else if (mem.eql(u8, arg, "-fwasmtime")) {
390 builder.enable_wasmtime = true;
391 } else if (mem.eql(u8, arg, "-fno-wasmtime")) {
392 builder.enable_wasmtime = false;
393 } else if (mem.eql(u8, arg, "-frosetta")) {
394 builder.enable_rosetta = true;
395 } else if (mem.eql(u8, arg, "-fno-rosetta")) {
396 builder.enable_rosetta = false;
397 } else if (mem.eql(u8, arg, "-fdarling")) {
398 builder.enable_darling = true;
399 } else if (mem.eql(u8, arg, "-fno-darling")) {
400 builder.enable_darling = false;
401 } else if (mem.eql(u8, arg, "-fallow-so-scripts")) {
402 graph.allow_so_scripts = true;
403 } else if (mem.eql(u8, arg, "-fno-allow-so-scripts")) {
404 graph.allow_so_scripts = false;
405 } else if (mem.eql(u8, arg, "-freference-trace")) {
406 builder.reference_trace = 256;
407 } else if (mem.startsWith(u8, arg, "-freference-trace=")) {
408 const num = arg["-freference-trace=".len..];
409 builder.reference_trace = std.fmt.parseUnsigned(u32, num, 10) catch |err| {
410 std.debug.print("unable to parse reference_trace count '{s}': {s}", .{ num, @errorName(err) });
411 process.exit(1);
412 };
413 } else if (mem.eql(u8, arg, "-fno-reference-trace")) {
414 builder.reference_trace = null;
415 } else if (mem.cutPrefix(u8, arg, "-j")) |text| {
416 const n = std.fmt.parseUnsigned(u32, text, 10) catch |err|
417 fatal("unable to parse jobs count '{s}': {t}", .{ text, err });
418 if (n < 1) fatal("number of jobs must be at least 1", .{});
419 threaded.setAsyncLimit(.limited(n));
420 } else if (mem.eql(u8, arg, "--")) {
421 builder.args = argsRest(args, arg_idx);
422 break;
423 } else {
424 fatalWithHint("unrecognized argument: '{s}'", .{arg});
425 }
426 } else {
427 try targets.append(arg);
428 }
429 }
430
431 if (webui_listen != null) {
432 if (watch) fatal("using '--webui' and '--watch' together is not yet supported; consider omitting '--watch' in favour of the web UI \"Rebuild\" button", .{});
433 if (builtin.single_threaded) fatal("'--webui' is not yet supported on single-threaded hosts", .{});
434 }
435
436 const ttyconf = color.detectTtyConf();
437
438 const main_progress_node = std.Progress.start(.{
439 .disable_printing = (color == .off),
440 });
441 defer main_progress_node.end();
442
443 builder.debug_log_scopes = debug_log_scopes.items;
444 builder.resolveInstallPrefix(install_prefix, dir_list);
445 {
446 var prog_node = main_progress_node.start("Configure", 0);
447 defer prog_node.end();
448 try builder.runBuild(root);
449 createModuleDependencies(builder) catch @panic("OOM");
450 }
451
452 if (graph.needed_lazy_dependencies.entries.len != 0) {
453 var buffer: std.ArrayList(u8) = .empty;
454 for (graph.needed_lazy_dependencies.keys()) |k| {
455 try buffer.appendSlice(arena, k);
456 try buffer.append(arena, '\n');
457 }
458 const s = std.fs.path.sep_str;
459 const tmp_sub_path = "tmp" ++ s ++ (output_tmp_nonce orelse fatal("missing -Z arg", .{}));
460 local_cache_directory.handle.writeFile(.{
461 .sub_path = tmp_sub_path,
462 .data = buffer.items,
463 .flags = .{ .exclusive = true },
464 }) catch |err| {
465 fatal("unable to write configuration results to '{f}{s}': {s}", .{
466 local_cache_directory, tmp_sub_path, @errorName(err),
467 });
468 };
469 process.exit(3); // Indicate configure phase failed with meaningful stdout.
470 }
471
472 if (builder.validateUserInputDidItFail()) {
473 fatal(" access the help menu with 'zig build -h'", .{});
474 }
475
476 validateSystemLibraryOptions(builder);
477
478 if (help_menu) {
479 var w = initStdoutWriter();
480 printUsage(builder, w) catch return stdout_writer_allocation.err.?;
481 w.flush() catch return stdout_writer_allocation.err.?;
482 return;
483 }
484
485 if (steps_menu) {
486 var w = initStdoutWriter();
487 printSteps(builder, w) catch return stdout_writer_allocation.err.?;
488 w.flush() catch return stdout_writer_allocation.err.?;
489 return;
490 }
491
492 var run: Run = .{
493 .gpa = gpa,
494
495 .max_rss = max_rss,
496 .max_rss_is_default = false,
497 .max_rss_mutex = .init,
498 .skip_oom_steps = skip_oom_steps,
499 .unit_test_timeout_ns = test_timeout_ns,
500
501 .watch = watch,
502 .web_server = undefined, // set after `prepare`
503 .memory_blocked_steps = .empty,
504 .step_stack = .empty,
505
506 .claimed_rss = 0,
507 .error_style = error_style,
508 .multiline_errors = multiline_errors,
509 .summary = summary orelse if (watch or webui_listen != null) .line else .failures,
510
511 .ttyconf = ttyconf,
512 };
513 defer {
514 run.memory_blocked_steps.deinit(gpa);
515 run.step_stack.deinit(gpa);
516 }
517
518 if (run.max_rss == 0) {
519 run.max_rss = process.totalSystemMemory() catch std.math.maxInt(u64);
520 run.max_rss_is_default = true;
521 }
522
523 prepare(arena, builder, targets.items, &run, graph.random_seed) catch |err| switch (err) {
524 error.DependencyLoopDetected => {
525 // Perhaps in the future there could be an Advanced Options flag such as
526 // --debug-build-runner-leaks which would make this code return instead of
527 // calling exit.
528 std.debug.lockStdErr();
529 process.exit(1);
530 },
531 else => |e| return e,
532 };
533
534 var w: Watch = w: {
535 if (!watch) break :w undefined;
536 if (!Watch.have_impl) fatal("--watch not yet implemented for {t}", .{builtin.os.tag});
537 break :w try .init();
538 };
539
540 const now = Io.Clock.Timestamp.now(io, .awake) catch |err| fatal("failed to collect timestamp: {t}", .{err});
541
542 run.web_server = if (webui_listen) |listen_address| ws: {
543 if (builtin.single_threaded) unreachable; // `fatal` above
544 break :ws .init(.{
545 .gpa = gpa,
546 .ttyconf = ttyconf,
547 .graph = &graph,
548 .all_steps = run.step_stack.keys(),
549 .root_prog_node = main_progress_node,
550 .watch = watch,
551 .listen_address = listen_address,
552 .base_timestamp = now,
553 });
554 } else null;
555
556 if (run.web_server) |*ws| {
557 ws.start() catch |err| fatal("failed to start web server: {t}", .{err});
558 }
559
560 rebuild: while (true) : (if (run.error_style.clearOnUpdate()) {
561 const bw, _ = std.debug.lockStderrWriter(&stdio_buffer_allocation);
562 defer std.debug.unlockStderrWriter();
563 try bw.writeAll("\x1B[2J\x1B[3J\x1B[H");
564 }) {
565 if (run.web_server) |*ws| ws.startBuild();
566
567 try runStepNames(
568 builder,
569 targets.items,
570 main_progress_node,
571 &run,
572 fuzz,
573 );
574
575 if (run.web_server) |*web_server| {
576 if (fuzz) |mode| if (mode != .forever) fatal(
577 "error: limited fuzzing is not implemented yet for --webui",
578 .{},
579 );
580
581 web_server.finishBuild(.{ .fuzz = fuzz != null });
582 }
583
584 if (run.web_server) |*ws| {
585 assert(!watch); // fatal error after CLI parsing
586 while (true) switch (try ws.wait()) {
587 .rebuild => {
588 for (run.step_stack.keys()) |step| {
589 step.state = .precheck_done;
590 step.reset(gpa);
591 }
592 continue :rebuild;
593 },
594 };
595 }
596
597 // Comptime-known guard to prevent including the logic below when `!Watch.have_impl`.
598 if (!Watch.have_impl) unreachable;
599
600 try w.update(gpa, run.step_stack.keys());
601
602 // Wait until a file system notification arrives. Read all such events
603 // until the buffer is empty. Then wait for a debounce interval, resetting
604 // if any more events come in. After the debounce interval has passed,
605 // trigger a rebuild on all steps with modified inputs, as well as their
606 // recursive dependants.
607 var caption_buf: [std.Progress.Node.max_name_len]u8 = undefined;
608 const caption = std.fmt.bufPrint(&caption_buf, "watching {d} directories, {d} processes", .{
609 w.dir_count, countSubProcesses(run.step_stack.keys()),
610 }) catch &caption_buf;
611 var debouncing_node = main_progress_node.start(caption, 0);
612 var in_debounce = false;
613 while (true) switch (try w.wait(gpa, if (in_debounce) .{ .ms = debounce_interval_ms } else .none)) {
614 .timeout => {
615 assert(in_debounce);
616 debouncing_node.end();
617 markFailedStepsDirty(gpa, run.step_stack.keys());
618 continue :rebuild;
619 },
620 .dirty => if (!in_debounce) {
621 in_debounce = true;
622 debouncing_node.end();
623 debouncing_node = main_progress_node.start("Debouncing (Change Detected)", 0);
624 },
625 .clean => {},
626 };
627 }
628}
629
630fn markFailedStepsDirty(gpa: Allocator, all_steps: []const *Step) void {
631 for (all_steps) |step| switch (step.state) {
632 .dependency_failure, .failure, .skipped => step.recursiveReset(gpa),
633 else => continue,
634 };
635 // Now that all dirty steps have been found, the remaining steps that
636 // succeeded from last run shall be marked "cached".
637 for (all_steps) |step| switch (step.state) {
638 .success => step.result_cached = true,
639 else => continue,
640 };
641}
642
643fn countSubProcesses(all_steps: []const *Step) usize {
644 var count: usize = 0;
645 for (all_steps) |s| {
646 count += @intFromBool(s.getZigProcess() != null);
647 }
648 return count;
649}
650
651const Run = struct {
652 gpa: Allocator,
653 max_rss: u64,
654 max_rss_is_default: bool,
655 max_rss_mutex: Io.Mutex,
656 skip_oom_steps: bool,
657 unit_test_timeout_ns: ?u64,
658 watch: bool,
659 web_server: if (!builtin.single_threaded) ?WebServer else ?noreturn,
660 /// Allocated into `gpa`.
661 memory_blocked_steps: std.ArrayList(*Step),
662 /// Allocated into `gpa`.
663 step_stack: std.AutoArrayHashMapUnmanaged(*Step, void),
664 /// Similar to the `tty.Config` returned by `std.debug.lockStderrWriter`,
665 /// but also respects the '--color' flag.
666 ttyconf: tty.Config,
667
668 claimed_rss: usize,
669 error_style: ErrorStyle,
670 multiline_errors: MultilineErrors,
671 summary: Summary,
672};
673
674fn prepare(
675 arena: Allocator,
676 b: *std.Build,
677 step_names: []const []const u8,
678 run: *Run,
679 seed: u32,
680) !void {
681 const gpa = run.gpa;
682 const step_stack = &run.step_stack;
683
684 if (step_names.len == 0) {
685 try step_stack.put(gpa, b.default_step, {});
686 } else {
687 try step_stack.ensureUnusedCapacity(gpa, step_names.len);
688 for (0..step_names.len) |i| {
689 const step_name = step_names[step_names.len - i - 1];
690 const s = b.top_level_steps.get(step_name) orelse {
691 std.debug.print("no step named '{s}'\n access the help menu with 'zig build -h'\n", .{step_name});
692 process.exit(1);
693 };
694 step_stack.putAssumeCapacity(&s.step, {});
695 }
696 }
697
698 const starting_steps = try arena.dupe(*Step, step_stack.keys());
699
700 var rng = std.Random.DefaultPrng.init(seed);
701 const rand = rng.random();
702 rand.shuffle(*Step, starting_steps);
703
704 for (starting_steps) |s| {
705 try constructGraphAndCheckForDependencyLoop(gpa, b, s, &run.step_stack, rand);
706 }
707
708 {
709 // Check that we have enough memory to complete the build.
710 var any_problems = false;
711 for (step_stack.keys()) |s| {
712 if (s.max_rss == 0) continue;
713 if (s.max_rss > run.max_rss) {
714 if (run.skip_oom_steps) {
715 s.state = .skipped_oom;
716 } else {
717 std.debug.print("{s}{s}: this step declares an upper bound of {d} bytes of memory, exceeding the available {d} bytes of memory\n", .{
718 s.owner.dep_prefix, s.name, s.max_rss, run.max_rss,
719 });
720 any_problems = true;
721 }
722 }
723 }
724 if (any_problems) {
725 if (run.max_rss_is_default) {
726 std.debug.print("note: use --maxrss to override the default", .{});
727 }
728 }
729 }
730}
731
732fn runStepNames(
733 b: *std.Build,
734 step_names: []const []const u8,
735 parent_prog_node: std.Progress.Node,
736 run: *Run,
737 fuzz: ?std.Build.Fuzz.Mode,
738) !void {
739 const gpa = run.gpa;
740 const io = b.graph.io;
741 const step_stack = &run.step_stack;
742
743 {
744 const step_prog = parent_prog_node.start("steps", step_stack.count());
745 defer step_prog.end();
746
747 var group: Io.Group = .init;
748 defer group.wait(io);
749
750 // Here we spawn the initial set of tasks with a nice heuristic -
751 // dependency order. Each worker when it finishes a step will then
752 // check whether it should run any dependants.
753 const steps_slice = step_stack.keys();
754 for (0..steps_slice.len) |i| {
755 const step = steps_slice[steps_slice.len - i - 1];
756 if (step.state == .skipped_oom) continue;
757
758 group.async(io, workerMakeOneStep, .{ &group, b, step, step_prog, run });
759 }
760 }
761
762 assert(run.memory_blocked_steps.items.len == 0);
763
764 var test_pass_count: usize = 0;
765 var test_skip_count: usize = 0;
766 var test_fail_count: usize = 0;
767 var test_crash_count: usize = 0;
768 var test_timeout_count: usize = 0;
769
770 var test_count: usize = 0;
771
772 var success_count: usize = 0;
773 var skipped_count: usize = 0;
774 var failure_count: usize = 0;
775 var pending_count: usize = 0;
776 var total_compile_errors: usize = 0;
777
778 for (step_stack.keys()) |s| {
779 test_pass_count += s.test_results.passCount();
780 test_skip_count += s.test_results.skip_count;
781 test_fail_count += s.test_results.fail_count;
782 test_crash_count += s.test_results.crash_count;
783 test_timeout_count += s.test_results.timeout_count;
784
785 test_count += s.test_results.test_count;
786
787 switch (s.state) {
788 .precheck_unstarted => unreachable,
789 .precheck_started => unreachable,
790 .running => unreachable,
791 .precheck_done => {
792 // precheck_done is equivalent to dependency_failure in the case of
793 // transitive dependencies. For example:
794 // A -> B -> C (failure)
795 // B will be marked as dependency_failure, while A may never be queued, and thus
796 // remain in the initial state of precheck_done.
797 s.state = .dependency_failure;
798 if (run.web_server) |*ws| ws.updateStepStatus(s, .failure);
799 pending_count += 1;
800 },
801 .dependency_failure => pending_count += 1,
802 .success => success_count += 1,
803 .skipped, .skipped_oom => skipped_count += 1,
804 .failure => {
805 failure_count += 1;
806 const compile_errors_len = s.result_error_bundle.errorMessageCount();
807 if (compile_errors_len > 0) {
808 total_compile_errors += compile_errors_len;
809 }
810 },
811 }
812 }
813
814 if (fuzz) |mode| blk: {
815 switch (builtin.os.tag) {
816 // Current implementation depends on two things that need to be ported to Windows:
817 // * Memory-mapping to share data between the fuzzer and build runner.
818 // * COFF/PE support added to `std.debug.Info` (it needs a batching API for resolving
819 // many addresses to source locations).
820 .windows => fatal("--fuzz not yet implemented for {s}", .{@tagName(builtin.os.tag)}),
821 else => {},
822 }
823 if (@bitSizeOf(usize) != 64) {
824 // Current implementation depends on posix.mmap()'s second parameter, `length: usize`,
825 // being compatible with `std.fs.getEndPos() u64`'s return value. This is not the case
826 // on 32-bit platforms.
827 // Affects or affected by issues #5185, #22523, and #22464.
828 fatal("--fuzz not yet implemented on {d}-bit platforms", .{@bitSizeOf(usize)});
829 }
830
831 switch (mode) {
832 .forever => break :blk,
833 .limit => {},
834 }
835
836 assert(mode == .limit);
837 var f = std.Build.Fuzz.init(
838 gpa,
839 io,
840 run.ttyconf,
841 step_stack.keys(),
842 parent_prog_node,
843 mode,
844 ) catch |err| fatal("failed to start fuzzer: {s}", .{@errorName(err)});
845 defer f.deinit();
846
847 f.start();
848 f.waitAndPrintReport();
849 }
850
851 // Every test has a state
852 assert(test_pass_count + test_skip_count + test_fail_count + test_crash_count + test_timeout_count == test_count);
853
854 if (failure_count == 0) {
855 std.Progress.setStatus(.success);
856 } else {
857 std.Progress.setStatus(.failure);
858 }
859
860 summary: {
861 switch (run.summary) {
862 .all, .new, .line => {},
863 .failures => if (failure_count == 0) break :summary,
864 .none => break :summary,
865 }
866
867 const w, _ = std.debug.lockStderrWriter(&stdio_buffer_allocation);
868 defer std.debug.unlockStderrWriter();
869 const ttyconf = run.ttyconf;
870
871 const total_count = success_count + failure_count + pending_count + skipped_count;
872 ttyconf.setColor(w, .cyan) catch {};
873 ttyconf.setColor(w, .bold) catch {};
874 w.writeAll("Build Summary: ") catch {};
875 ttyconf.setColor(w, .reset) catch {};
876 w.print("{d}/{d} steps succeeded", .{ success_count, total_count }) catch {};
877 {
878 ttyconf.setColor(w, .dim) catch {};
879 var first = true;
880 if (skipped_count > 0) {
881 w.print("{s}{d} skipped", .{ if (first) " (" else ", ", skipped_count }) catch {};
882 first = false;
883 }
884 if (failure_count > 0) {
885 w.print("{s}{d} failed", .{ if (first) " (" else ", ", failure_count }) catch {};
886 first = false;
887 }
888 if (!first) w.writeByte(')') catch {};
889 ttyconf.setColor(w, .reset) catch {};
890 }
891
892 if (test_count > 0) {
893 w.print("; {d}/{d} tests passed", .{ test_pass_count, test_count }) catch {};
894 ttyconf.setColor(w, .dim) catch {};
895 var first = true;
896 if (test_skip_count > 0) {
897 w.print("{s}{d} skipped", .{ if (first) " (" else ", ", test_skip_count }) catch {};
898 first = false;
899 }
900 if (test_fail_count > 0) {
901 w.print("{s}{d} failed", .{ if (first) " (" else ", ", test_fail_count }) catch {};
902 first = false;
903 }
904 if (test_crash_count > 0) {
905 w.print("{s}{d} crashed", .{ if (first) " (" else ", ", test_crash_count }) catch {};
906 first = false;
907 }
908 if (test_timeout_count > 0) {
909 w.print("{s}{d} timed out", .{ if (first) " (" else ", ", test_timeout_count }) catch {};
910 first = false;
911 }
912 if (!first) w.writeByte(')') catch {};
913 ttyconf.setColor(w, .reset) catch {};
914 }
915
916 w.writeAll("\n") catch {};
917
918 if (run.summary == .line) break :summary;
919
920 // Print a fancy tree with build results.
921 var step_stack_copy = try step_stack.clone(gpa);
922 defer step_stack_copy.deinit(gpa);
923
924 var print_node: PrintNode = .{ .parent = null };
925 if (step_names.len == 0) {
926 print_node.last = true;
927 printTreeStep(b, b.default_step, run, w, ttyconf, &print_node, &step_stack_copy) catch {};
928 } else {
929 const last_index = if (run.summary == .all) b.top_level_steps.count() else blk: {
930 var i: usize = step_names.len;
931 while (i > 0) {
932 i -= 1;
933 const step = b.top_level_steps.get(step_names[i]).?.step;
934 const found = switch (run.summary) {
935 .all, .line, .none => unreachable,
936 .failures => step.state != .success,
937 .new => !step.result_cached,
938 };
939 if (found) break :blk i;
940 }
941 break :blk b.top_level_steps.count();
942 };
943 for (step_names, 0..) |step_name, i| {
944 const tls = b.top_level_steps.get(step_name).?;
945 print_node.last = i + 1 == last_index;
946 printTreeStep(b, &tls.step, run, w, ttyconf, &print_node, &step_stack_copy) catch {};
947 }
948 }
949 w.writeByte('\n') catch {};
950 }
951
952 if (run.watch or run.web_server != null) return;
953
954 // Perhaps in the future there could be an Advanced Options flag such as
955 // --debug-build-runner-leaks which would make this code return instead of
956 // calling exit.
957
958 const code: u8 = code: {
959 if (failure_count == 0) break :code 0; // success
960 if (run.error_style.verboseContext()) break :code 1; // failure; print build command
961 break :code 2; // failure; do not print build command
962 };
963 std.debug.lockStdErr();
964 process.exit(code);
965}
966
967const PrintNode = struct {
968 parent: ?*PrintNode,
969 last: bool = false,
970};
971
972fn printPrefix(node: *PrintNode, stderr: *Writer, ttyconf: tty.Config) !void {
973 const parent = node.parent orelse return;
974 if (parent.parent == null) return;
975 try printPrefix(parent, stderr, ttyconf);
976 if (parent.last) {
977 try stderr.writeAll(" ");
978 } else {
979 try stderr.writeAll(switch (ttyconf) {
980 .no_color, .windows_api => "| ",
981 .escape_codes => "\x1B\x28\x30\x78\x1B\x28\x42 ", // │
982 });
983 }
984}
985
986fn printChildNodePrefix(stderr: *Writer, ttyconf: tty.Config) !void {
987 try stderr.writeAll(switch (ttyconf) {
988 .no_color, .windows_api => "+- ",
989 .escape_codes => "\x1B\x28\x30\x6d\x71\x1B\x28\x42 ", // └─
990 });
991}
992
993fn printStepStatus(
994 s: *Step,
995 stderr: *Writer,
996 ttyconf: tty.Config,
997 run: *const Run,
998) !void {
999 switch (s.state) {
1000 .precheck_unstarted => unreachable,
1001 .precheck_started => unreachable,
1002 .precheck_done => unreachable,
1003 .running => unreachable,
1004
1005 .dependency_failure => {
1006 try ttyconf.setColor(stderr, .dim);
1007 try stderr.writeAll(" transitive failure\n");
1008 try ttyconf.setColor(stderr, .reset);
1009 },
1010
1011 .success => {
1012 try ttyconf.setColor(stderr, .green);
1013 if (s.result_cached) {
1014 try stderr.writeAll(" cached");
1015 } else if (s.test_results.test_count > 0) {
1016 const pass_count = s.test_results.passCount();
1017 assert(s.test_results.test_count == pass_count + s.test_results.skip_count);
1018 try stderr.print(" {d} pass", .{pass_count});
1019 if (s.test_results.skip_count > 0) {
1020 try ttyconf.setColor(stderr, .reset);
1021 try stderr.writeAll(", ");
1022 try ttyconf.setColor(stderr, .yellow);
1023 try stderr.print("{d} skip", .{s.test_results.skip_count});
1024 }
1025 try ttyconf.setColor(stderr, .reset);
1026 try stderr.print(" ({d} total)", .{s.test_results.test_count});
1027 } else {
1028 try stderr.writeAll(" success");
1029 }
1030 try ttyconf.setColor(stderr, .reset);
1031 if (s.result_duration_ns) |ns| {
1032 try ttyconf.setColor(stderr, .dim);
1033 if (ns >= std.time.ns_per_min) {
1034 try stderr.print(" {d}m", .{ns / std.time.ns_per_min});
1035 } else if (ns >= std.time.ns_per_s) {
1036 try stderr.print(" {d}s", .{ns / std.time.ns_per_s});
1037 } else if (ns >= std.time.ns_per_ms) {
1038 try stderr.print(" {d}ms", .{ns / std.time.ns_per_ms});
1039 } else if (ns >= std.time.ns_per_us) {
1040 try stderr.print(" {d}us", .{ns / std.time.ns_per_us});
1041 } else {
1042 try stderr.print(" {d}ns", .{ns});
1043 }
1044 try ttyconf.setColor(stderr, .reset);
1045 }
1046 if (s.result_peak_rss != 0) {
1047 const rss = s.result_peak_rss;
1048 try ttyconf.setColor(stderr, .dim);
1049 if (rss >= 1000_000_000) {
1050 try stderr.print(" MaxRSS:{d}G", .{rss / 1000_000_000});
1051 } else if (rss >= 1000_000) {
1052 try stderr.print(" MaxRSS:{d}M", .{rss / 1000_000});
1053 } else if (rss >= 1000) {
1054 try stderr.print(" MaxRSS:{d}K", .{rss / 1000});
1055 } else {
1056 try stderr.print(" MaxRSS:{d}B", .{rss});
1057 }
1058 try ttyconf.setColor(stderr, .reset);
1059 }
1060 try stderr.writeAll("\n");
1061 },
1062 .skipped, .skipped_oom => |skip| {
1063 try ttyconf.setColor(stderr, .yellow);
1064 try stderr.writeAll(" skipped");
1065 if (skip == .skipped_oom) {
1066 try stderr.writeAll(" (not enough memory)");
1067 try ttyconf.setColor(stderr, .dim);
1068 try stderr.print(" upper bound of {d} exceeded runner limit ({d})", .{ s.max_rss, run.max_rss });
1069 try ttyconf.setColor(stderr, .yellow);
1070 }
1071 try stderr.writeAll("\n");
1072 try ttyconf.setColor(stderr, .reset);
1073 },
1074 .failure => {
1075 try printStepFailure(s, stderr, ttyconf, false);
1076 try ttyconf.setColor(stderr, .reset);
1077 },
1078 }
1079}
1080
1081fn printStepFailure(
1082 s: *Step,
1083 stderr: *Writer,
1084 ttyconf: tty.Config,
1085 dim: bool,
1086) !void {
1087 if (s.result_error_bundle.errorMessageCount() > 0) {
1088 try ttyconf.setColor(stderr, .red);
1089 try stderr.print(" {d} errors\n", .{
1090 s.result_error_bundle.errorMessageCount(),
1091 });
1092 } else if (!s.test_results.isSuccess()) {
1093 // These first values include all of the test "statuses". Every test is either passsed,
1094 // skipped, failed, crashed, or timed out.
1095 try ttyconf.setColor(stderr, .green);
1096 try stderr.print(" {d} pass", .{s.test_results.passCount()});
1097 try ttyconf.setColor(stderr, .reset);
1098 if (dim) try ttyconf.setColor(stderr, .dim);
1099 if (s.test_results.skip_count > 0) {
1100 try stderr.writeAll(", ");
1101 try ttyconf.setColor(stderr, .yellow);
1102 try stderr.print("{d} skip", .{s.test_results.skip_count});
1103 try ttyconf.setColor(stderr, .reset);
1104 if (dim) try ttyconf.setColor(stderr, .dim);
1105 }
1106 if (s.test_results.fail_count > 0) {
1107 try stderr.writeAll(", ");
1108 try ttyconf.setColor(stderr, .red);
1109 try stderr.print("{d} fail", .{s.test_results.fail_count});
1110 try ttyconf.setColor(stderr, .reset);
1111 if (dim) try ttyconf.setColor(stderr, .dim);
1112 }
1113 if (s.test_results.crash_count > 0) {
1114 try stderr.writeAll(", ");
1115 try ttyconf.setColor(stderr, .red);
1116 try stderr.print("{d} crash", .{s.test_results.crash_count});
1117 try ttyconf.setColor(stderr, .reset);
1118 if (dim) try ttyconf.setColor(stderr, .dim);
1119 }
1120 if (s.test_results.timeout_count > 0) {
1121 try stderr.writeAll(", ");
1122 try ttyconf.setColor(stderr, .red);
1123 try stderr.print("{d} timeout", .{s.test_results.timeout_count});
1124 try ttyconf.setColor(stderr, .reset);
1125 if (dim) try ttyconf.setColor(stderr, .dim);
1126 }
1127 try stderr.print(" ({d} total)", .{s.test_results.test_count});
1128
1129 // Memory leaks are intentionally written after the total, because is isn't a test *status*,
1130 // but just a flag that any tests -- even passed ones -- can have. We also use a different
1131 // separator, so it looks like:
1132 // 2 pass, 1 skip, 2 fail (5 total); 2 leaks
1133 if (s.test_results.leak_count > 0) {
1134 try stderr.writeAll("; ");
1135 try ttyconf.setColor(stderr, .red);
1136 try stderr.print("{d} leaks", .{s.test_results.leak_count});
1137 try ttyconf.setColor(stderr, .reset);
1138 if (dim) try ttyconf.setColor(stderr, .dim);
1139 }
1140
1141 // It's usually not helpful to know how many error logs there were because they tend to
1142 // just come with other errors (e.g. crashes and leaks print stack traces, and clean
1143 // failures print error traces). So only mention them if they're the only thing causing
1144 // the failure.
1145 const show_err_logs: bool = show: {
1146 var alt_results = s.test_results;
1147 alt_results.log_err_count = 0;
1148 break :show alt_results.isSuccess();
1149 };
1150 if (show_err_logs) {
1151 try stderr.writeAll("; ");
1152 try ttyconf.setColor(stderr, .red);
1153 try stderr.print("{d} error logs", .{s.test_results.log_err_count});
1154 try ttyconf.setColor(stderr, .reset);
1155 if (dim) try ttyconf.setColor(stderr, .dim);
1156 }
1157
1158 try stderr.writeAll("\n");
1159 } else if (s.result_error_msgs.items.len > 0) {
1160 try ttyconf.setColor(stderr, .red);
1161 try stderr.writeAll(" failure\n");
1162 } else {
1163 assert(s.result_stderr.len > 0);
1164 try ttyconf.setColor(stderr, .red);
1165 try stderr.writeAll(" stderr\n");
1166 }
1167}
1168
1169fn printTreeStep(
1170 b: *std.Build,
1171 s: *Step,
1172 run: *const Run,
1173 stderr: *Writer,
1174 ttyconf: tty.Config,
1175 parent_node: *PrintNode,
1176 step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),
1177) !void {
1178 const first = step_stack.swapRemove(s);
1179 const summary = run.summary;
1180 const skip = switch (summary) {
1181 .none, .line => unreachable,
1182 .all => false,
1183 .new => s.result_cached,
1184 .failures => s.state == .success,
1185 };
1186 if (skip) return;
1187 try printPrefix(parent_node, stderr, ttyconf);
1188
1189 if (!first) try ttyconf.setColor(stderr, .dim);
1190 if (parent_node.parent != null) {
1191 if (parent_node.last) {
1192 try printChildNodePrefix(stderr, ttyconf);
1193 } else {
1194 try stderr.writeAll(switch (ttyconf) {
1195 .no_color, .windows_api => "+- ",
1196 .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─
1197 });
1198 }
1199 }
1200
1201 // dep_prefix omitted here because it is redundant with the tree.
1202 try stderr.writeAll(s.name);
1203
1204 if (first) {
1205 try printStepStatus(s, stderr, ttyconf, run);
1206
1207 const last_index = if (summary == .all) s.dependencies.items.len -| 1 else blk: {
1208 var i: usize = s.dependencies.items.len;
1209 while (i > 0) {
1210 i -= 1;
1211
1212 const step = s.dependencies.items[i];
1213 const found = switch (summary) {
1214 .all, .line, .none => unreachable,
1215 .failures => step.state != .success,
1216 .new => !step.result_cached,
1217 };
1218 if (found) break :blk i;
1219 }
1220 break :blk s.dependencies.items.len -| 1;
1221 };
1222 for (s.dependencies.items, 0..) |dep, i| {
1223 var print_node: PrintNode = .{
1224 .parent = parent_node,
1225 .last = i == last_index,
1226 };
1227 try printTreeStep(b, dep, run, stderr, ttyconf, &print_node, step_stack);
1228 }
1229 } else {
1230 if (s.dependencies.items.len == 0) {
1231 try stderr.writeAll(" (reused)\n");
1232 } else {
1233 try stderr.print(" (+{d} more reused dependencies)\n", .{
1234 s.dependencies.items.len,
1235 });
1236 }
1237 try ttyconf.setColor(stderr, .reset);
1238 }
1239}
1240
1241/// Traverse the dependency graph depth-first and make it undirected by having
1242/// steps know their dependants (they only know dependencies at start).
1243/// Along the way, check that there is no dependency loop, and record the steps
1244/// in traversal order in `step_stack`.
1245/// Each step has its dependencies traversed in random order, this accomplishes
1246/// two things:
1247/// - `step_stack` will be in randomized-depth-first order, so the build runner
1248/// spawns steps in a random (but optimized) order
1249/// - each step's `dependants` list is also filled in a random order, so that
1250/// when it finishes executing in `workerMakeOneStep`, it spawns next steps
1251/// to run in random order
1252fn constructGraphAndCheckForDependencyLoop(
1253 gpa: Allocator,
1254 b: *std.Build,
1255 s: *Step,
1256 step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),
1257 rand: std.Random,
1258) !void {
1259 switch (s.state) {
1260 .precheck_started => {
1261 std.debug.print("dependency loop detected:\n {s}\n", .{s.name});
1262 return error.DependencyLoopDetected;
1263 },
1264 .precheck_unstarted => {
1265 s.state = .precheck_started;
1266
1267 try step_stack.ensureUnusedCapacity(gpa, s.dependencies.items.len);
1268
1269 // We dupe to avoid shuffling the steps in the summary, it depends
1270 // on s.dependencies' order.
1271 const deps = gpa.dupe(*Step, s.dependencies.items) catch @panic("OOM");
1272 defer gpa.free(deps);
1273
1274 rand.shuffle(*Step, deps);
1275
1276 for (deps) |dep| {
1277 try step_stack.put(gpa, dep, {});
1278 try dep.dependants.append(b.allocator, s);
1279 constructGraphAndCheckForDependencyLoop(gpa, b, dep, step_stack, rand) catch |err| {
1280 if (err == error.DependencyLoopDetected) {
1281 std.debug.print(" {s}\n", .{s.name});
1282 }
1283 return err;
1284 };
1285 }
1286
1287 s.state = .precheck_done;
1288 },
1289 .precheck_done => {},
1290
1291 // These don't happen until we actually run the step graph.
1292 .dependency_failure => unreachable,
1293 .running => unreachable,
1294 .success => unreachable,
1295 .failure => unreachable,
1296 .skipped => unreachable,
1297 .skipped_oom => unreachable,
1298 }
1299}
1300
1301fn workerMakeOneStep(
1302 group: *Io.Group,
1303 b: *std.Build,
1304 s: *Step,
1305 prog_node: std.Progress.Node,
1306 run: *Run,
1307) void {
1308 const io = b.graph.io;
1309 const gpa = run.gpa;
1310
1311 // First, check the conditions for running this step. If they are not met,
1312 // then we return without doing the step, relying on another worker to
1313 // queue this step up again when dependencies are met.
1314 for (s.dependencies.items) |dep| {
1315 switch (@atomicLoad(Step.State, &dep.state, .seq_cst)) {
1316 .success, .skipped => continue,
1317 .failure, .dependency_failure, .skipped_oom => {
1318 @atomicStore(Step.State, &s.state, .dependency_failure, .seq_cst);
1319 if (run.web_server) |*ws| ws.updateStepStatus(s, .failure);
1320 return;
1321 },
1322 .precheck_done, .running => {
1323 // dependency is not finished yet.
1324 return;
1325 },
1326 .precheck_unstarted => unreachable,
1327 .precheck_started => unreachable,
1328 }
1329 }
1330
1331 if (s.max_rss != 0) {
1332 run.max_rss_mutex.lockUncancelable(io);
1333 defer run.max_rss_mutex.unlock(io);
1334
1335 // Avoid running steps twice.
1336 if (s.state != .precheck_done) {
1337 // Another worker got the job.
1338 return;
1339 }
1340
1341 const new_claimed_rss = run.claimed_rss + s.max_rss;
1342 if (new_claimed_rss > run.max_rss) {
1343 // Running this step right now could possibly exceed the allotted RSS.
1344 // Add this step to the queue of memory-blocked steps.
1345 run.memory_blocked_steps.append(gpa, s) catch @panic("OOM");
1346 return;
1347 }
1348
1349 run.claimed_rss = new_claimed_rss;
1350 s.state = .running;
1351 } else {
1352 // Avoid running steps twice.
1353 if (@cmpxchgStrong(Step.State, &s.state, .precheck_done, .running, .seq_cst, .seq_cst) != null) {
1354 // Another worker got the job.
1355 return;
1356 }
1357 }
1358
1359 const sub_prog_node = prog_node.start(s.name, 0);
1360 defer sub_prog_node.end();
1361
1362 if (run.web_server) |*ws| ws.updateStepStatus(s, .wip);
1363
1364 const make_result = s.make(.{
1365 .progress_node = sub_prog_node,
1366 .watch = run.watch,
1367 .web_server = if (run.web_server) |*ws| ws else null,
1368 .ttyconf = run.ttyconf,
1369 .unit_test_timeout_ns = run.unit_test_timeout_ns,
1370 .gpa = gpa,
1371 });
1372
1373 // No matter the result, we want to display error/warning messages.
1374 const show_compile_errors = s.result_error_bundle.errorMessageCount() > 0;
1375 const show_error_msgs = s.result_error_msgs.items.len > 0;
1376 const show_stderr = s.result_stderr.len > 0;
1377 if (show_error_msgs or show_compile_errors or show_stderr) {
1378 const bw, _ = std.debug.lockStderrWriter(&stdio_buffer_allocation);
1379 defer std.debug.unlockStderrWriter();
1380 const ttyconf = run.ttyconf;
1381 printErrorMessages(gpa, s, .{}, bw, ttyconf, run.error_style, run.multiline_errors) catch {};
1382 }
1383
1384 handle_result: {
1385 if (make_result) |_| {
1386 @atomicStore(Step.State, &s.state, .success, .seq_cst);
1387 if (run.web_server) |*ws| ws.updateStepStatus(s, .success);
1388 } else |err| switch (err) {
1389 error.MakeFailed => {
1390 @atomicStore(Step.State, &s.state, .failure, .seq_cst);
1391 if (run.web_server) |*ws| ws.updateStepStatus(s, .failure);
1392 std.Progress.setStatus(.failure_working);
1393 break :handle_result;
1394 },
1395 error.MakeSkipped => {
1396 @atomicStore(Step.State, &s.state, .skipped, .seq_cst);
1397 if (run.web_server) |*ws| ws.updateStepStatus(s, .success);
1398 },
1399 }
1400
1401 // Successful completion of a step, so we queue up its dependants as well.
1402 for (s.dependants.items) |dep| {
1403 group.async(io, workerMakeOneStep, .{ group, b, dep, prog_node, run });
1404 }
1405 }
1406
1407 // If this is a step that claims resources, we must now queue up other
1408 // steps that are waiting for resources.
1409 if (s.max_rss != 0) {
1410 var dispatch_deps: std.ArrayList(*Step) = .empty;
1411 defer dispatch_deps.deinit(gpa);
1412 dispatch_deps.ensureUnusedCapacity(gpa, run.memory_blocked_steps.items.len) catch @panic("OOM");
1413
1414 {
1415 run.max_rss_mutex.lockUncancelable(io);
1416 defer run.max_rss_mutex.unlock(io);
1417
1418 // Give the memory back to the scheduler.
1419 run.claimed_rss -= s.max_rss;
1420 // Avoid kicking off too many tasks that we already know will not have
1421 // enough resources.
1422 var remaining = run.max_rss - run.claimed_rss;
1423 var i: usize = 0;
1424 for (run.memory_blocked_steps.items) |dep| {
1425 assert(dep.max_rss != 0);
1426 if (dep.max_rss <= remaining) {
1427 remaining -= dep.max_rss;
1428 dispatch_deps.appendAssumeCapacity(dep);
1429 } else {
1430 run.memory_blocked_steps.items[i] = dep;
1431 i += 1;
1432 }
1433 }
1434 run.memory_blocked_steps.shrinkRetainingCapacity(i);
1435 }
1436 for (dispatch_deps.items) |dep| {
1437 // Must be called without max_rss_mutex held in case it executes recursively.
1438 group.async(io, workerMakeOneStep, .{ group, b, dep, prog_node, run });
1439 }
1440 }
1441}
1442
1443pub fn printErrorMessages(
1444 gpa: Allocator,
1445 failing_step: *Step,
1446 options: std.zig.ErrorBundle.RenderOptions,
1447 stderr: *Writer,
1448 ttyconf: tty.Config,
1449 error_style: ErrorStyle,
1450 multiline_errors: MultilineErrors,
1451) !void {
1452 if (error_style.verboseContext()) {
1453 // Provide context for where these error messages are coming from by
1454 // printing the corresponding Step subtree.
1455 var step_stack: std.ArrayList(*Step) = .empty;
1456 defer step_stack.deinit(gpa);
1457 try step_stack.append(gpa, failing_step);
1458 while (step_stack.items[step_stack.items.len - 1].dependants.items.len != 0) {
1459 try step_stack.append(gpa, step_stack.items[step_stack.items.len - 1].dependants.items[0]);
1460 }
1461
1462 // Now, `step_stack` has the subtree that we want to print, in reverse order.
1463 try ttyconf.setColor(stderr, .dim);
1464 var indent: usize = 0;
1465 while (step_stack.pop()) |s| : (indent += 1) {
1466 if (indent > 0) {
1467 try stderr.splatByteAll(' ', (indent - 1) * 3);
1468 try printChildNodePrefix(stderr, ttyconf);
1469 }
1470
1471 try stderr.writeAll(s.name);
1472
1473 if (s == failing_step) {
1474 try printStepFailure(s, stderr, ttyconf, true);
1475 } else {
1476 try stderr.writeAll("\n");
1477 }
1478 }
1479 try ttyconf.setColor(stderr, .reset);
1480 } else {
1481 // Just print the failing step itself.
1482 try ttyconf.setColor(stderr, .dim);
1483 try stderr.writeAll(failing_step.name);
1484 try printStepFailure(failing_step, stderr, ttyconf, true);
1485 try ttyconf.setColor(stderr, .reset);
1486 }
1487
1488 if (failing_step.result_stderr.len > 0) {
1489 try stderr.writeAll(failing_step.result_stderr);
1490 if (!mem.endsWith(u8, failing_step.result_stderr, "\n")) {
1491 try stderr.writeAll("\n");
1492 }
1493 }
1494
1495 try failing_step.result_error_bundle.renderToWriter(options, stderr, ttyconf);
1496
1497 for (failing_step.result_error_msgs.items) |msg| {
1498 try ttyconf.setColor(stderr, .red);
1499 try stderr.writeAll("error:");
1500 try ttyconf.setColor(stderr, .reset);
1501 if (std.mem.indexOfScalar(u8, msg, '\n') == null) {
1502 try stderr.print(" {s}\n", .{msg});
1503 } else switch (multiline_errors) {
1504 .indent => {
1505 var it = std.mem.splitScalar(u8, msg, '\n');
1506 try stderr.print(" {s}\n", .{it.first()});
1507 while (it.next()) |line| {
1508 try stderr.print(" {s}\n", .{line});
1509 }
1510 },
1511 .newline => try stderr.print("\n{s}\n", .{msg}),
1512 .none => try stderr.print(" {s}\n", .{msg}),
1513 }
1514 }
1515
1516 if (error_style.verboseContext()) {
1517 if (failing_step.result_failed_command) |cmd_str| {
1518 try ttyconf.setColor(stderr, .red);
1519 try stderr.writeAll("failed command: ");
1520 try ttyconf.setColor(stderr, .reset);
1521 try stderr.writeAll(cmd_str);
1522 try stderr.writeByte('\n');
1523 }
1524 }
1525
1526 try stderr.writeByte('\n');
1527}
1528
1529fn printSteps(builder: *std.Build, w: *Writer) !void {
1530 const arena = builder.graph.arena;
1531 for (builder.top_level_steps.values()) |top_level_step| {
1532 const name = if (&top_level_step.step == builder.default_step)
1533 try fmt.allocPrint(arena, "{s} (default)", .{top_level_step.step.name})
1534 else
1535 top_level_step.step.name;
1536 try w.print(" {s:<28} {s}\n", .{ name, top_level_step.description });
1537 }
1538}
1539
1540fn printUsage(b: *std.Build, w: *Writer) !void {
1541 try w.print(
1542 \\Usage: {s} build [steps] [options]
1543 \\
1544 \\Steps:
1545 \\
1546 , .{b.graph.zig_exe});
1547 try printSteps(b, w);
1548
1549 try w.writeAll(
1550 \\
1551 \\General Options:
1552 \\ -p, --prefix [path] Where to install files (default: zig-out)
1553 \\ --prefix-lib-dir [path] Where to install libraries
1554 \\ --prefix-exe-dir [path] Where to install executables
1555 \\ --prefix-include-dir [path] Where to install C header files
1556 \\
1557 \\ --release[=mode] Request release mode, optionally specifying a
1558 \\ preferred optimization mode: fast, safe, small
1559 \\
1560 \\ -fdarling, -fno-darling Integration with system-installed Darling to
1561 \\ execute macOS programs on Linux hosts
1562 \\ (default: no)
1563 \\ -fqemu, -fno-qemu Integration with system-installed QEMU to execute
1564 \\ foreign-architecture programs on Linux hosts
1565 \\ (default: no)
1566 \\ --libc-runtimes [path] Enhances QEMU integration by providing dynamic libc
1567 \\ (e.g. glibc or musl) built for multiple foreign
1568 \\ architectures, allowing execution of non-native
1569 \\ programs that link with libc.
1570 \\ -frosetta, -fno-rosetta Rely on Rosetta to execute x86_64 programs on
1571 \\ ARM64 macOS hosts. (default: no)
1572 \\ -fwasmtime, -fno-wasmtime Integration with system-installed wasmtime to
1573 \\ execute WASI binaries. (default: no)
1574 \\ -fwine, -fno-wine Integration with system-installed Wine to execute
1575 \\ Windows programs on Linux hosts. (default: no)
1576 \\
1577 \\ -h, --help Print this help and exit
1578 \\ -l, --list-steps Print available steps
1579 \\ --verbose Print commands before executing them
1580 \\ --color [auto|off|on] Enable or disable colored error messages
1581 \\ --error-style [style] Control how build errors are printed
1582 \\ verbose (Default) Report errors with full context
1583 \\ minimal Report errors after summary, excluding context like command lines
1584 \\ verbose_clear Like 'verbose', but clear the terminal at the start of each update
1585 \\ minimal_clear Like 'minimal', but clear the terminal at the start of each update
1586 \\ --multiline-errors [style] Control how multi-line error messages are printed
1587 \\ indent (Default) Indent non-initial lines to align with initial line
1588 \\ newline Include a leading newline so that the error message is on its own lines
1589 \\ none Print as usual so the first line is misaligned
1590 \\ --summary [mode] Control the printing of the build summary
1591 \\ all Print the build summary in its entirety
1592 \\ new Omit cached steps
1593 \\ failures (Default if short-lived) Only print failed steps
1594 \\ line (Default if long-lived) Only print the single-line summary
1595 \\ none Do not print the build summary
1596 \\ -j<N> Limit concurrent jobs (default is to use all CPU cores)
1597 \\ --maxrss <bytes> Limit memory usage (default is to use available memory)
1598 \\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss
1599 \\ --test-timeout <timeout> Limit execution time of unit tests, terminating if exceeded.
1600 \\ The timeout must include a unit: ns, us, ms, s, m, h
1601 \\ --fetch[=mode] Fetch dependency tree (optionally choose laziness) and exit
1602 \\ needed (Default) Lazy dependencies are fetched as needed
1603 \\ all Lazy dependencies are always fetched
1604 \\ --watch Continuously rebuild when source files are modified
1605 \\ --debounce <ms> Delay before rebuilding after changed file detected
1606 \\ --webui[=ip] Enable the web interface on the given IP address
1607 \\ --fuzz[=limit] Continuously search for unit test failures with an optional
1608 \\ limit to the max number of iterations. The argument supports
1609 \\ an optional 'K', 'M', or 'G' suffix (e.g. '10K'). Implies
1610 \\ '--webui' when no limit is specified.
1611 \\ --time-report Force full rebuild and provide detailed information on
1612 \\ compilation time of Zig source code (implies '--webui')
1613 \\ -fincremental Enable incremental compilation
1614 \\ -fno-incremental Disable incremental compilation
1615 \\
1616 \\Project-Specific Options:
1617 \\
1618 );
1619
1620 const arena = b.graph.arena;
1621 if (b.available_options_list.items.len == 0) {
1622 try w.print(" (none)\n", .{});
1623 } else {
1624 for (b.available_options_list.items) |option| {
1625 const name = try fmt.allocPrint(arena, " -D{s}=[{s}]", .{
1626 option.name,
1627 @tagName(option.type_id),
1628 });
1629 try w.print("{s:<30} {s}\n", .{ name, option.description });
1630 if (option.enum_options) |enum_options| {
1631 const padding = " " ** 33;
1632 try w.writeAll(padding ++ "Supported Values:\n");
1633 for (enum_options) |enum_option| {
1634 try w.print(padding ++ " {s}\n", .{enum_option});
1635 }
1636 }
1637 }
1638 }
1639
1640 try w.writeAll(
1641 \\
1642 \\System Integration Options:
1643 \\ --search-prefix [path] Add a path to look for binaries, libraries, headers
1644 \\ --sysroot [path] Set the system root directory (usually /)
1645 \\ --libc [file] Provide a file which specifies libc paths
1646 \\
1647 \\ --system [pkgdir] Disable package fetching; enable all integrations
1648 \\ -fsys=[name] Enable a system integration
1649 \\ -fno-sys=[name] Disable a system integration
1650 \\
1651 \\ Available System Integrations: Enabled:
1652 \\
1653 );
1654 if (b.graph.system_library_options.entries.len == 0) {
1655 try w.writeAll(" (none) -\n");
1656 } else {
1657 for (b.graph.system_library_options.keys(), b.graph.system_library_options.values()) |k, v| {
1658 const status = switch (v) {
1659 .declared_enabled => "yes",
1660 .declared_disabled => "no",
1661 .user_enabled, .user_disabled => unreachable, // already emitted error
1662 };
1663 try w.print(" {s:<43} {s}\n", .{ k, status });
1664 }
1665 }
1666
1667 try w.writeAll(
1668 \\
1669 \\Advanced Options:
1670 \\ -freference-trace[=num] How many lines of reference trace should be shown per compile error
1671 \\ -fno-reference-trace Disable reference trace
1672 \\ -fallow-so-scripts Allows .so files to be GNU ld scripts
1673 \\ -fno-allow-so-scripts (default) .so files must be ELF files
1674 \\ --build-file [file] Override path to build.zig
1675 \\ --cache-dir [path] Override path to local Zig cache directory
1676 \\ --global-cache-dir [path] Override path to global Zig cache directory
1677 \\ --zig-lib-dir [arg] Override path to Zig lib directory
1678 \\ --build-runner [file] Override path to build runner
1679 \\ --seed [integer] For shuffling dependency traversal order (default: random)
1680 \\ --build-id[=style] At a minor link-time expense, embeds a build ID in binaries
1681 \\ fast 8-byte non-cryptographic hash (COFF, ELF, WASM)
1682 \\ sha1, tree 20-byte cryptographic hash (ELF, WASM)
1683 \\ md5 16-byte cryptographic hash (ELF)
1684 \\ uuid 16-byte random UUID (ELF, WASM)
1685 \\ 0x[hexstring] Constant ID, maximum 32 bytes (ELF, WASM)
1686 \\ none (default) No build ID
1687 \\ --debug-log [scope] Enable debugging the compiler
1688 \\ --debug-pkg-config Fail if unknown pkg-config flags encountered
1689 \\ --debug-rt Debug compiler runtime libraries
1690 \\ --verbose-link Enable compiler debug output for linking
1691 \\ --verbose-air Enable compiler debug output for Zig AIR
1692 \\ --verbose-llvm-ir[=file] Enable compiler debug output for LLVM IR
1693 \\ --verbose-llvm-bc=[file] Enable compiler debug output for LLVM BC
1694 \\ --verbose-cimport Enable compiler debug output for C imports
1695 \\ --verbose-cc Enable compiler debug output for C compilation
1696 \\ --verbose-llvm-cpu-features Enable compiler debug output for LLVM CPU features
1697 \\
1698 );
1699}
1700
1701fn nextArg(args: []const [:0]const u8, idx: *usize) ?[:0]const u8 {
1702 if (idx.* >= args.len) return null;
1703 defer idx.* += 1;
1704 return args[idx.*];
1705}
1706
1707fn nextArgOrFatal(args: []const [:0]const u8, idx: *usize) [:0]const u8 {
1708 return nextArg(args, idx) orelse {
1709 std.debug.print("expected argument after '{s}'\n access the help menu with 'zig build -h'\n", .{args[idx.* - 1]});
1710 process.exit(1);
1711 };
1712}
1713
1714fn argsRest(args: []const [:0]const u8, idx: usize) ?[]const [:0]const u8 {
1715 if (idx >= args.len) return null;
1716 return args[idx..];
1717}
1718
1719const Color = std.zig.Color;
1720const ErrorStyle = enum {
1721 verbose,
1722 minimal,
1723 verbose_clear,
1724 minimal_clear,
1725 fn verboseContext(s: ErrorStyle) bool {
1726 return switch (s) {
1727 .verbose, .verbose_clear => true,
1728 .minimal, .minimal_clear => false,
1729 };
1730 }
1731 fn clearOnUpdate(s: ErrorStyle) bool {
1732 return switch (s) {
1733 .verbose, .minimal => false,
1734 .verbose_clear, .minimal_clear => true,
1735 };
1736 }
1737};
1738const MultilineErrors = enum { indent, newline, none };
1739const Summary = enum { all, new, failures, line, none };
1740
1741fn fatalWithHint(comptime f: []const u8, args: anytype) noreturn {
1742 std.debug.print(f ++ "\n access the help menu with 'zig build -h'\n", args);
1743 process.exit(1);
1744}
1745
1746fn validateSystemLibraryOptions(b: *std.Build) void {
1747 var bad = false;
1748 for (b.graph.system_library_options.keys(), b.graph.system_library_options.values()) |k, v| {
1749 switch (v) {
1750 .user_disabled, .user_enabled => {
1751 // The user tried to enable or disable a system library integration, but
1752 // the build script did not recognize that option.
1753 std.debug.print("system library name not recognized by build script: '{s}'\n", .{k});
1754 bad = true;
1755 },
1756 .declared_disabled, .declared_enabled => {},
1757 }
1758 }
1759 if (bad) {
1760 std.debug.print(" access the help menu with 'zig build -h'\n", .{});
1761 process.exit(1);
1762 }
1763}
1764
1765/// Starting from all top-level steps in `b`, traverses the entire step graph
1766/// and adds all step dependencies implied by module graphs.
1767fn createModuleDependencies(b: *std.Build) Allocator.Error!void {
1768 const arena = b.graph.arena;
1769
1770 var all_steps: std.AutoArrayHashMapUnmanaged(*Step, void) = .empty;
1771 var next_step_idx: usize = 0;
1772
1773 try all_steps.ensureUnusedCapacity(arena, b.top_level_steps.count());
1774 for (b.top_level_steps.values()) |tls| {
1775 all_steps.putAssumeCapacityNoClobber(&tls.step, {});
1776 }
1777
1778 while (next_step_idx < all_steps.count()) {
1779 const step = all_steps.keys()[next_step_idx];
1780 next_step_idx += 1;
1781
1782 // Set up any implied dependencies for this step. It's important that we do this first, so
1783 // that the loop below discovers steps implied by the module graph.
1784 try createModuleDependenciesForStep(step);
1785
1786 try all_steps.ensureUnusedCapacity(arena, step.dependencies.items.len);
1787 for (step.dependencies.items) |other_step| {
1788 all_steps.putAssumeCapacity(other_step, {});
1789 }
1790 }
1791}
1792
1793/// If the given `Step` is a `Step.Compile`, adds any dependencies for that step which
1794/// are implied by the module graph rooted at `step.cast(Step.Compile).?.root_module`.
1795fn createModuleDependenciesForStep(step: *Step) Allocator.Error!void {
1796 const root_module = if (step.cast(Step.Compile)) |cs| root: {
1797 break :root cs.root_module;
1798 } else return; // not a compile step so no module dependencies
1799
1800 // Starting from `root_module`, discover all modules in this graph.
1801 const modules = root_module.getGraph().modules;
1802
1803 // For each of those modules, set up the implied step dependencies.
1804 for (modules) |mod| {
1805 if (mod.root_source_file) |lp| lp.addStepDependencies(step);
1806 for (mod.include_dirs.items) |include_dir| switch (include_dir) {
1807 .path,
1808 .path_system,
1809 .path_after,
1810 .framework_path,
1811 .framework_path_system,
1812 .embed_path,
1813 => |lp| lp.addStepDependencies(step),
1814
1815 .other_step => |other| {
1816 other.getEmittedIncludeTree().addStepDependencies(step);
1817 step.dependOn(&other.step);
1818 },
1819
1820 .config_header_step => |other| step.dependOn(&other.step),
1821 };
1822 for (mod.lib_paths.items) |lp| lp.addStepDependencies(step);
1823 for (mod.rpaths.items) |rpath| switch (rpath) {
1824 .lazy_path => |lp| lp.addStepDependencies(step),
1825 .special => {},
1826 };
1827 for (mod.link_objects.items) |link_object| switch (link_object) {
1828 .static_path,
1829 .assembly_file,
1830 => |lp| lp.addStepDependencies(step),
1831 .other_step => |other| step.dependOn(&other.step),
1832 .system_lib => {},
1833 .c_source_file => |source| source.file.addStepDependencies(step),
1834 .c_source_files => |source_files| source_files.root.addStepDependencies(step),
1835 .win32_resource_file => |rc_source| {
1836 rc_source.file.addStepDependencies(step);
1837 for (rc_source.include_paths) |lp| lp.addStepDependencies(step);
1838 },
1839 };
1840 }
1841}
1842
1843var stdio_buffer_allocation: [256]u8 = undefined;
1844var stdout_writer_allocation: std.fs.File.Writer = undefined;
1845
1846fn initStdoutWriter() *Writer {
1847 stdout_writer_allocation = std.fs.File.stdout().writerStreaming(&stdio_buffer_allocation);
1848 return &stdout_writer_allocation.interface;
1849}