authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-02-26 23:18:11-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-02-26 23:44:01-07:00
log1a01151a4e1e83826d6911c929210aabcaed36e9
treef823367851d6a2ee03d667d23f24b4c4a89ef9ce
parentdfe430e9f488536c6ce4be23473f60aa5e89ab5a

back out the build_runner.zig moving change

I'd like to move this file but to do so requires a zig1.wasm update, so I'll choose a more opportune moment to make this change.

3 files changed, 1273 insertions(+), 1274 deletions(-)

lib/build_runner.zig created+1273
...@@ -0,0 +1,1273 @@
1const root = @import("@build");
2const std = @import("std");
3const builtin = @import("builtin");
4const assert = std.debug.assert;
5const io = std.io;
6const fmt = std.fmt;
7const mem = std.mem;
8const process = std.process;
9const ArrayList = std.ArrayList;
10const File = std.fs.File;
11const Step = std.Build.Step;
12
13pub const dependencies = @import("@dependencies");
14
15pub fn main() !void {
16 // Here we use an ArenaAllocator backed by a page allocator because a build is a short-lived,
17 // one shot program. We don't need to waste time freeing memory and finding places to squish
18 // bytes into. So we free everything all at once at the very end.
19 var single_threaded_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
20 defer single_threaded_arena.deinit();
21
22 var thread_safe_arena: std.heap.ThreadSafeAllocator = .{
23 .child_allocator = single_threaded_arena.allocator(),
24 };
25 const arena = thread_safe_arena.allocator();
26
27 const args = try process.argsAlloc(arena);
28
29 // skip my own exe name
30 var arg_idx: usize = 1;
31
32 const zig_exe = nextArg(args, &arg_idx) orelse {
33 std.debug.print("Expected path to zig compiler\n", .{});
34 return error.InvalidArgs;
35 };
36 const build_root = nextArg(args, &arg_idx) orelse {
37 std.debug.print("Expected build root directory path\n", .{});
38 return error.InvalidArgs;
39 };
40 const cache_root = nextArg(args, &arg_idx) orelse {
41 std.debug.print("Expected cache root directory path\n", .{});
42 return error.InvalidArgs;
43 };
44 const global_cache_root = nextArg(args, &arg_idx) orelse {
45 std.debug.print("Expected global cache root directory path\n", .{});
46 return error.InvalidArgs;
47 };
48
49 const build_root_directory: std.Build.Cache.Directory = .{
50 .path = build_root,
51 .handle = try std.fs.cwd().openDir(build_root, .{}),
52 };
53
54 const local_cache_directory: std.Build.Cache.Directory = .{
55 .path = cache_root,
56 .handle = try std.fs.cwd().makeOpenPath(cache_root, .{}),
57 };
58
59 const global_cache_directory: std.Build.Cache.Directory = .{
60 .path = global_cache_root,
61 .handle = try std.fs.cwd().makeOpenPath(global_cache_root, .{}),
62 };
63
64 var graph: std.Build.Graph = .{
65 .arena = arena,
66 .cache = .{
67 .gpa = arena,
68 .manifest_dir = try local_cache_directory.handle.makeOpenPath("h", .{}),
69 },
70 .zig_exe = zig_exe,
71 .env_map = try process.getEnvMap(arena),
72 .global_cache_root = global_cache_directory,
73 };
74
75 graph.cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() });
76 graph.cache.addPrefix(build_root_directory);
77 graph.cache.addPrefix(local_cache_directory);
78 graph.cache.addPrefix(global_cache_directory);
79 graph.cache.hash.addBytes(builtin.zig_version_string);
80
81 const builder = try std.Build.create(
82 &graph,
83 build_root_directory,
84 local_cache_directory,
85 dependencies.root_deps,
86 );
87
88 var targets = ArrayList([]const u8).init(arena);
89 var debug_log_scopes = ArrayList([]const u8).init(arena);
90 var thread_pool_options: std.Thread.Pool.Options = .{ .allocator = arena };
91
92 var install_prefix: ?[]const u8 = null;
93 var dir_list = std.Build.DirList{};
94 var summary: ?Summary = null;
95 var max_rss: u64 = 0;
96 var skip_oom_steps: bool = false;
97 var color: Color = .auto;
98 var seed: u32 = 0;
99 var prominent_compile_errors: bool = false;
100 var help_menu: bool = false;
101 var steps_menu: bool = false;
102 var output_tmp_nonce: ?[16]u8 = null;
103
104 while (nextArg(args, &arg_idx)) |arg| {
105 if (mem.startsWith(u8, arg, "-Z")) {
106 if (arg.len != 18) fatalWithHint("bad argument: '{s}'", .{arg});
107 output_tmp_nonce = arg[2..18].*;
108 } else if (mem.startsWith(u8, arg, "-D")) {
109 const option_contents = arg[2..];
110 if (option_contents.len == 0)
111 fatalWithHint("expected option name after '-D'", .{});
112 if (mem.indexOfScalar(u8, option_contents, '=')) |name_end| {
113 const option_name = option_contents[0..name_end];
114 const option_value = option_contents[name_end + 1 ..];
115 if (try builder.addUserInputOption(option_name, option_value))
116 fatal(" access the help menu with 'zig build -h'", .{});
117 } else {
118 if (try builder.addUserInputFlag(option_contents))
119 fatal(" access the help menu with 'zig build -h'", .{});
120 }
121 } else if (mem.startsWith(u8, arg, "-")) {
122 if (mem.eql(u8, arg, "--verbose")) {
123 builder.verbose = true;
124 } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
125 help_menu = true;
126 } else if (mem.eql(u8, arg, "-p") or mem.eql(u8, arg, "--prefix")) {
127 install_prefix = nextArgOrFatal(args, &arg_idx);
128 } else if (mem.eql(u8, arg, "-l") or mem.eql(u8, arg, "--list-steps")) {
129 steps_menu = true;
130 } else if (mem.startsWith(u8, arg, "-fsys=")) {
131 const name = arg["-fsys=".len..];
132 graph.system_library_options.put(arena, name, .user_enabled) catch @panic("OOM");
133 } else if (mem.startsWith(u8, arg, "-fno-sys=")) {
134 const name = arg["-fno-sys=".len..];
135 graph.system_library_options.put(arena, name, .user_disabled) catch @panic("OOM");
136 } else if (mem.eql(u8, arg, "--release")) {
137 builder.release_mode = .any;
138 } else if (mem.startsWith(u8, arg, "--release=")) {
139 const text = arg["--release=".len..];
140 builder.release_mode = std.meta.stringToEnum(std.Build.ReleaseMode, text) orelse {
141 fatalWithHint("expected [off|any|fast|safe|small] in '{s}', found '{s}'", .{
142 arg, text,
143 });
144 };
145 } else if (mem.eql(u8, arg, "--host-target")) {
146 graph.host_query_options.arch_os_abi = nextArgOrFatal(args, &arg_idx);
147 } else if (mem.eql(u8, arg, "--host-cpu")) {
148 graph.host_query_options.cpu_features = nextArgOrFatal(args, &arg_idx);
149 } else if (mem.eql(u8, arg, "--host-dynamic-linker")) {
150 graph.host_query_options.dynamic_linker = nextArgOrFatal(args, &arg_idx);
151 } else if (mem.eql(u8, arg, "--prefix-lib-dir")) {
152 dir_list.lib_dir = nextArgOrFatal(args, &arg_idx);
153 } else if (mem.eql(u8, arg, "--prefix-exe-dir")) {
154 dir_list.exe_dir = nextArgOrFatal(args, &arg_idx);
155 } else if (mem.eql(u8, arg, "--prefix-include-dir")) {
156 dir_list.include_dir = nextArgOrFatal(args, &arg_idx);
157 } else if (mem.eql(u8, arg, "--sysroot")) {
158 builder.sysroot = nextArgOrFatal(args, &arg_idx);
159 } else if (mem.eql(u8, arg, "--maxrss")) {
160 const max_rss_text = nextArgOrFatal(args, &arg_idx);
161 max_rss = std.fmt.parseIntSizeSuffix(max_rss_text, 10) catch |err| {
162 std.debug.print("invalid byte size: '{s}': {s}\n", .{
163 max_rss_text, @errorName(err),
164 });
165 process.exit(1);
166 };
167 } else if (mem.eql(u8, arg, "--skip-oom-steps")) {
168 skip_oom_steps = true;
169 } else if (mem.eql(u8, arg, "--search-prefix")) {
170 const search_prefix = nextArgOrFatal(args, &arg_idx);
171 builder.addSearchPrefix(search_prefix);
172 } else if (mem.eql(u8, arg, "--libc")) {
173 builder.libc_file = nextArgOrFatal(args, &arg_idx);
174 } else if (mem.eql(u8, arg, "--color")) {
175 const next_arg = nextArg(args, &arg_idx) orelse
176 fatalWithHint("expected [auto|on|off] after '{s}'", .{arg});
177 color = std.meta.stringToEnum(Color, next_arg) orelse {
178 fatalWithHint("expected [auto|on|off] after '{s}', found '{s}'", .{
179 arg, next_arg,
180 });
181 };
182 } else if (mem.eql(u8, arg, "--summary")) {
183 const next_arg = nextArg(args, &arg_idx) orelse
184 fatalWithHint("expected [all|failures|none] after '{s}'", .{arg});
185 summary = std.meta.stringToEnum(Summary, next_arg) orelse {
186 fatalWithHint("expected [all|failures|none] after '{s}', found '{s}'", .{
187 arg, next_arg,
188 });
189 };
190 } else if (mem.eql(u8, arg, "--zig-lib-dir")) {
191 builder.zig_lib_dir = .{ .cwd_relative = nextArgOrFatal(args, &arg_idx) };
192 } else if (mem.eql(u8, arg, "--seed")) {
193 const next_arg = nextArg(args, &arg_idx) orelse
194 fatalWithHint("expected u32 after '{s}'", .{arg});
195 seed = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err| {
196 fatal("unable to parse seed '{s}' as 32-bit integer: {s}\n", .{
197 next_arg, @errorName(err),
198 });
199 };
200 } else if (mem.eql(u8, arg, "--debug-log")) {
201 const next_arg = nextArgOrFatal(args, &arg_idx);
202 try debug_log_scopes.append(next_arg);
203 } else if (mem.eql(u8, arg, "--debug-pkg-config")) {
204 builder.debug_pkg_config = true;
205 } else if (mem.eql(u8, arg, "--debug-compile-errors")) {
206 builder.debug_compile_errors = true;
207 } else if (mem.eql(u8, arg, "--system")) {
208 // The usage text shows another argument after this parameter
209 // but it is handled by the parent process. The build runner
210 // only sees this flag.
211 graph.system_package_mode = true;
212 } else if (mem.eql(u8, arg, "--glibc-runtimes")) {
213 builder.glibc_runtimes_dir = nextArgOrFatal(args, &arg_idx);
214 } else if (mem.eql(u8, arg, "--verbose-link")) {
215 builder.verbose_link = true;
216 } else if (mem.eql(u8, arg, "--verbose-air")) {
217 builder.verbose_air = true;
218 } else if (mem.eql(u8, arg, "--verbose-llvm-ir")) {
219 builder.verbose_llvm_ir = "-";
220 } else if (mem.startsWith(u8, arg, "--verbose-llvm-ir=")) {
221 builder.verbose_llvm_ir = arg["--verbose-llvm-ir=".len..];
222 } else if (mem.eql(u8, arg, "--verbose-llvm-bc=")) {
223 builder.verbose_llvm_bc = arg["--verbose-llvm-bc=".len..];
224 } else if (mem.eql(u8, arg, "--verbose-cimport")) {
225 builder.verbose_cimport = true;
226 } else if (mem.eql(u8, arg, "--verbose-cc")) {
227 builder.verbose_cc = true;
228 } else if (mem.eql(u8, arg, "--verbose-llvm-cpu-features")) {
229 builder.verbose_llvm_cpu_features = true;
230 } else if (mem.eql(u8, arg, "--prominent-compile-errors")) {
231 prominent_compile_errors = true;
232 } else if (mem.eql(u8, arg, "-fwine")) {
233 builder.enable_wine = true;
234 } else if (mem.eql(u8, arg, "-fno-wine")) {
235 builder.enable_wine = false;
236 } else if (mem.eql(u8, arg, "-fqemu")) {
237 builder.enable_qemu = true;
238 } else if (mem.eql(u8, arg, "-fno-qemu")) {
239 builder.enable_qemu = false;
240 } else if (mem.eql(u8, arg, "-fwasmtime")) {
241 builder.enable_wasmtime = true;
242 } else if (mem.eql(u8, arg, "-fno-wasmtime")) {
243 builder.enable_wasmtime = false;
244 } else if (mem.eql(u8, arg, "-frosetta")) {
245 builder.enable_rosetta = true;
246 } else if (mem.eql(u8, arg, "-fno-rosetta")) {
247 builder.enable_rosetta = false;
248 } else if (mem.eql(u8, arg, "-fdarling")) {
249 builder.enable_darling = true;
250 } else if (mem.eql(u8, arg, "-fno-darling")) {
251 builder.enable_darling = false;
252 } else if (mem.eql(u8, arg, "-freference-trace")) {
253 builder.reference_trace = 256;
254 } else if (mem.startsWith(u8, arg, "-freference-trace=")) {
255 const num = arg["-freference-trace=".len..];
256 builder.reference_trace = std.fmt.parseUnsigned(u32, num, 10) catch |err| {
257 std.debug.print("unable to parse reference_trace count '{s}': {s}", .{ num, @errorName(err) });
258 process.exit(1);
259 };
260 } else if (mem.eql(u8, arg, "-fno-reference-trace")) {
261 builder.reference_trace = null;
262 } else if (mem.startsWith(u8, arg, "-j")) {
263 const num = arg["-j".len..];
264 const n_jobs = std.fmt.parseUnsigned(u32, num, 10) catch |err| {
265 std.debug.print("unable to parse jobs count '{s}': {s}", .{
266 num, @errorName(err),
267 });
268 process.exit(1);
269 };
270 if (n_jobs < 1) {
271 std.debug.print("number of jobs must be at least 1\n", .{});
272 process.exit(1);
273 }
274 thread_pool_options.n_jobs = n_jobs;
275 } else if (mem.eql(u8, arg, "--")) {
276 builder.args = argsRest(args, arg_idx);
277 break;
278 } else {
279 fatalWithHint("unrecognized argument: '{s}'", .{arg});
280 }
281 } else {
282 try targets.append(arg);
283 }
284 }
285
286 const host_query = std.Build.parseTargetQuery(graph.host_query_options) catch |err| switch (err) {
287 error.ParseFailed => process.exit(1),
288 };
289 builder.host = .{
290 .query = .{},
291 .result = try std.zig.system.resolveTargetQuery(host_query),
292 };
293
294 const stderr = std.io.getStdErr();
295 const ttyconf = get_tty_conf(color, stderr);
296 switch (ttyconf) {
297 .no_color => try graph.env_map.put("NO_COLOR", "1"),
298 .escape_codes => try graph.env_map.put("YES_COLOR", "1"),
299 .windows_api => {},
300 }
301
302 var progress: std.Progress = .{ .dont_print_on_dumb = true };
303 const main_progress_node = progress.start("", 0);
304
305 builder.debug_log_scopes = debug_log_scopes.items;
306 builder.resolveInstallPrefix(install_prefix, dir_list);
307 {
308 var prog_node = main_progress_node.start("user build.zig logic", 0);
309 defer prog_node.end();
310 try builder.runBuild(root);
311 }
312
313 if (graph.needed_lazy_dependencies.entries.len != 0) {
314 var buffer: std.ArrayListUnmanaged(u8) = .{};
315 for (graph.needed_lazy_dependencies.keys()) |k| {
316 try buffer.appendSlice(arena, k);
317 try buffer.append(arena, '\n');
318 }
319 const s = std.fs.path.sep_str;
320 const tmp_sub_path = "tmp" ++ s ++ (output_tmp_nonce orelse fatal("missing -Z arg", .{}));
321 local_cache_directory.handle.writeFile2(.{
322 .sub_path = tmp_sub_path,
323 .data = buffer.items,
324 .flags = .{ .exclusive = true },
325 }) catch |err| {
326 fatal("unable to write configuration results to '{}{s}': {s}", .{
327 local_cache_directory, tmp_sub_path, @errorName(err),
328 });
329 };
330 process.exit(3); // Indicate configure phase failed with meaningful stdout.
331 }
332
333 if (builder.validateUserInputDidItFail()) {
334 fatal(" access the help menu with 'zig build -h'", .{});
335 }
336
337 validateSystemLibraryOptions(builder);
338
339 const stdout_writer = io.getStdOut().writer();
340
341 if (help_menu)
342 return usage(builder, stdout_writer);
343
344 if (steps_menu)
345 return steps(builder, stdout_writer);
346
347 var run: Run = .{
348 .max_rss = max_rss,
349 .max_rss_is_default = false,
350 .max_rss_mutex = .{},
351 .skip_oom_steps = skip_oom_steps,
352 .memory_blocked_steps = std.ArrayList(*Step).init(arena),
353 .prominent_compile_errors = prominent_compile_errors,
354
355 .claimed_rss = 0,
356 .summary = summary,
357 .ttyconf = ttyconf,
358 .stderr = stderr,
359 };
360
361 if (run.max_rss == 0) {
362 run.max_rss = process.totalSystemMemory() catch std.math.maxInt(u64);
363 run.max_rss_is_default = true;
364 }
365
366 runStepNames(
367 arena,
368 builder,
369 targets.items,
370 main_progress_node,
371 thread_pool_options,
372 &run,
373 seed,
374 ) catch |err| switch (err) {
375 error.UncleanExit => process.exit(1),
376 else => return err,
377 };
378}
379
380const Run = struct {
381 max_rss: u64,
382 max_rss_is_default: bool,
383 max_rss_mutex: std.Thread.Mutex,
384 skip_oom_steps: bool,
385 memory_blocked_steps: std.ArrayList(*Step),
386 prominent_compile_errors: bool,
387
388 claimed_rss: usize,
389 summary: ?Summary,
390 ttyconf: std.io.tty.Config,
391 stderr: File,
392};
393
394fn runStepNames(
395 arena: std.mem.Allocator,
396 b: *std.Build,
397 step_names: []const []const u8,
398 parent_prog_node: *std.Progress.Node,
399 thread_pool_options: std.Thread.Pool.Options,
400 run: *Run,
401 seed: u32,
402) !void {
403 const gpa = b.allocator;
404 var step_stack: std.AutoArrayHashMapUnmanaged(*Step, void) = .{};
405 defer step_stack.deinit(gpa);
406
407 if (step_names.len == 0) {
408 try step_stack.put(gpa, b.default_step, {});
409 } else {
410 try step_stack.ensureUnusedCapacity(gpa, step_names.len);
411 for (0..step_names.len) |i| {
412 const step_name = step_names[step_names.len - i - 1];
413 const s = b.top_level_steps.get(step_name) orelse {
414 std.debug.print("no step named '{s}'\n access the help menu with 'zig build -h'\n", .{step_name});
415 process.exit(1);
416 };
417 step_stack.putAssumeCapacity(&s.step, {});
418 }
419 }
420
421 const starting_steps = try arena.dupe(*Step, step_stack.keys());
422
423 var rng = std.Random.DefaultPrng.init(seed);
424 const rand = rng.random();
425 rand.shuffle(*Step, starting_steps);
426
427 for (starting_steps) |s| {
428 constructGraphAndCheckForDependencyLoop(b, s, &step_stack, rand) catch |err| switch (err) {
429 error.DependencyLoopDetected => return error.UncleanExit,
430 else => |e| return e,
431 };
432 }
433
434 {
435 // Check that we have enough memory to complete the build.
436 var any_problems = false;
437 for (step_stack.keys()) |s| {
438 if (s.max_rss == 0) continue;
439 if (s.max_rss > run.max_rss) {
440 if (run.skip_oom_steps) {
441 s.state = .skipped_oom;
442 } else {
443 std.debug.print("{s}{s}: this step declares an upper bound of {d} bytes of memory, exceeding the available {d} bytes of memory\n", .{
444 s.owner.dep_prefix, s.name, s.max_rss, run.max_rss,
445 });
446 any_problems = true;
447 }
448 }
449 }
450 if (any_problems) {
451 if (run.max_rss_is_default) {
452 std.debug.print("note: use --maxrss to override the default", .{});
453 }
454 return error.UncleanExit;
455 }
456 }
457
458 var thread_pool: std.Thread.Pool = undefined;
459 try thread_pool.init(thread_pool_options);
460 defer thread_pool.deinit();
461
462 {
463 defer parent_prog_node.end();
464
465 var step_prog = parent_prog_node.start("steps", step_stack.count());
466 defer step_prog.end();
467
468 var wait_group: std.Thread.WaitGroup = .{};
469 defer wait_group.wait();
470
471 // Here we spawn the initial set of tasks with a nice heuristic -
472 // dependency order. Each worker when it finishes a step will then
473 // check whether it should run any dependants.
474 const steps_slice = step_stack.keys();
475 for (0..steps_slice.len) |i| {
476 const step = steps_slice[steps_slice.len - i - 1];
477 if (step.state == .skipped_oom) continue;
478
479 wait_group.start();
480 thread_pool.spawn(workerMakeOneStep, .{
481 &wait_group, &thread_pool, b, step, &step_prog, run,
482 }) catch @panic("OOM");
483 }
484 }
485 assert(run.memory_blocked_steps.items.len == 0);
486
487 var test_skip_count: usize = 0;
488 var test_fail_count: usize = 0;
489 var test_pass_count: usize = 0;
490 var test_leak_count: usize = 0;
491 var test_count: usize = 0;
492
493 var success_count: usize = 0;
494 var skipped_count: usize = 0;
495 var failure_count: usize = 0;
496 var pending_count: usize = 0;
497 var total_compile_errors: usize = 0;
498 var compile_error_steps: std.ArrayListUnmanaged(*Step) = .{};
499 defer compile_error_steps.deinit(gpa);
500
501 for (step_stack.keys()) |s| {
502 test_fail_count += s.test_results.fail_count;
503 test_skip_count += s.test_results.skip_count;
504 test_leak_count += s.test_results.leak_count;
505 test_pass_count += s.test_results.passCount();
506 test_count += s.test_results.test_count;
507
508 switch (s.state) {
509 .precheck_unstarted => unreachable,
510 .precheck_started => unreachable,
511 .running => unreachable,
512 .precheck_done => {
513 // precheck_done is equivalent to dependency_failure in the case of
514 // transitive dependencies. For example:
515 // A -> B -> C (failure)
516 // B will be marked as dependency_failure, while A may never be queued, and thus
517 // remain in the initial state of precheck_done.
518 s.state = .dependency_failure;
519 pending_count += 1;
520 },
521 .dependency_failure => pending_count += 1,
522 .success => success_count += 1,
523 .skipped, .skipped_oom => skipped_count += 1,
524 .failure => {
525 failure_count += 1;
526 const compile_errors_len = s.result_error_bundle.errorMessageCount();
527 if (compile_errors_len > 0) {
528 total_compile_errors += compile_errors_len;
529 try compile_error_steps.append(gpa, s);
530 }
531 },
532 }
533 }
534
535 // A proper command line application defaults to silently succeeding.
536 // The user may request verbose mode if they have a different preference.
537 if (failure_count == 0 and run.summary != Summary.all) return cleanExit();
538
539 const ttyconf = run.ttyconf;
540 const stderr = run.stderr;
541
542 if (run.summary != Summary.none) {
543 const total_count = success_count + failure_count + pending_count + skipped_count;
544 ttyconf.setColor(stderr, .cyan) catch {};
545 stderr.writeAll("Build Summary:") catch {};
546 ttyconf.setColor(stderr, .reset) catch {};
547 stderr.writer().print(" {d}/{d} steps succeeded", .{ success_count, total_count }) catch {};
548 if (skipped_count > 0) stderr.writer().print("; {d} skipped", .{skipped_count}) catch {};
549 if (failure_count > 0) stderr.writer().print("; {d} failed", .{failure_count}) catch {};
550
551 if (test_count > 0) stderr.writer().print("; {d}/{d} tests passed", .{ test_pass_count, test_count }) catch {};
552 if (test_skip_count > 0) stderr.writer().print("; {d} skipped", .{test_skip_count}) catch {};
553 if (test_fail_count > 0) stderr.writer().print("; {d} failed", .{test_fail_count}) catch {};
554 if (test_leak_count > 0) stderr.writer().print("; {d} leaked", .{test_leak_count}) catch {};
555
556 if (run.summary == null) {
557 ttyconf.setColor(stderr, .dim) catch {};
558 stderr.writeAll(" (disable with --summary none)") catch {};
559 ttyconf.setColor(stderr, .reset) catch {};
560 }
561 stderr.writeAll("\n") catch {};
562 const failures_only = run.summary != Summary.all;
563
564 // Print a fancy tree with build results.
565 var print_node: PrintNode = .{ .parent = null };
566 if (step_names.len == 0) {
567 print_node.last = true;
568 printTreeStep(b, b.default_step, run, stderr, ttyconf, &print_node, &step_stack, failures_only) catch {};
569 } else {
570 const last_index = if (!failures_only) b.top_level_steps.count() else blk: {
571 var i: usize = step_names.len;
572 while (i > 0) {
573 i -= 1;
574 if (b.top_level_steps.get(step_names[i]).?.step.state != .success) break :blk i;
575 }
576 break :blk b.top_level_steps.count();
577 };
578 for (step_names, 0..) |step_name, i| {
579 const tls = b.top_level_steps.get(step_name).?;
580 print_node.last = i + 1 == last_index;
581 printTreeStep(b, &tls.step, run, stderr, ttyconf, &print_node, &step_stack, failures_only) catch {};
582 }
583 }
584 }
585
586 if (failure_count == 0) return cleanExit();
587
588 // Finally, render compile errors at the bottom of the terminal.
589 // We use a separate compile_error_steps array list because step_stack is destructively
590 // mutated in printTreeStep above.
591 if (run.prominent_compile_errors and total_compile_errors > 0) {
592 for (compile_error_steps.items) |s| {
593 if (s.result_error_bundle.errorMessageCount() > 0) {
594 s.result_error_bundle.renderToStdErr(renderOptions(ttyconf));
595 }
596 }
597
598 // Signal to parent process that we have printed compile errors. The
599 // parent process may choose to omit the "following command failed"
600 // line in this case.
601 process.exit(2);
602 }
603
604 process.exit(1);
605}
606
607const PrintNode = struct {
608 parent: ?*PrintNode,
609 last: bool = false,
610};
611
612fn printPrefix(node: *PrintNode, stderr: File, ttyconf: std.io.tty.Config) !void {
613 const parent = node.parent orelse return;
614 if (parent.parent == null) return;
615 try printPrefix(parent, stderr, ttyconf);
616 if (parent.last) {
617 try stderr.writeAll(" ");
618 } else {
619 try stderr.writeAll(switch (ttyconf) {
620 .no_color, .windows_api => "| ",
621 .escape_codes => "\x1B\x28\x30\x78\x1B\x28\x42 ", // │
622 });
623 }
624}
625
626fn printChildNodePrefix(stderr: File, ttyconf: std.io.tty.Config) !void {
627 try stderr.writeAll(switch (ttyconf) {
628 .no_color, .windows_api => "+- ",
629 .escape_codes => "\x1B\x28\x30\x6d\x71\x1B\x28\x42 ", // └─
630 });
631}
632
633fn printStepStatus(
634 s: *Step,
635 stderr: File,
636 ttyconf: std.io.tty.Config,
637 run: *const Run,
638) !void {
639 switch (s.state) {
640 .precheck_unstarted => unreachable,
641 .precheck_started => unreachable,
642 .precheck_done => unreachable,
643 .running => unreachable,
644
645 .dependency_failure => {
646 try ttyconf.setColor(stderr, .dim);
647 try stderr.writeAll(" transitive failure\n");
648 try ttyconf.setColor(stderr, .reset);
649 },
650
651 .success => {
652 try ttyconf.setColor(stderr, .green);
653 if (s.result_cached) {
654 try stderr.writeAll(" cached");
655 } else if (s.test_results.test_count > 0) {
656 const pass_count = s.test_results.passCount();
657 try stderr.writer().print(" {d} passed", .{pass_count});
658 if (s.test_results.skip_count > 0) {
659 try ttyconf.setColor(stderr, .yellow);
660 try stderr.writer().print(" {d} skipped", .{s.test_results.skip_count});
661 }
662 } else {
663 try stderr.writeAll(" success");
664 }
665 try ttyconf.setColor(stderr, .reset);
666 if (s.result_duration_ns) |ns| {
667 try ttyconf.setColor(stderr, .dim);
668 if (ns >= std.time.ns_per_min) {
669 try stderr.writer().print(" {d}m", .{ns / std.time.ns_per_min});
670 } else if (ns >= std.time.ns_per_s) {
671 try stderr.writer().print(" {d}s", .{ns / std.time.ns_per_s});
672 } else if (ns >= std.time.ns_per_ms) {
673 try stderr.writer().print(" {d}ms", .{ns / std.time.ns_per_ms});
674 } else if (ns >= std.time.ns_per_us) {
675 try stderr.writer().print(" {d}us", .{ns / std.time.ns_per_us});
676 } else {
677 try stderr.writer().print(" {d}ns", .{ns});
678 }
679 try ttyconf.setColor(stderr, .reset);
680 }
681 if (s.result_peak_rss != 0) {
682 const rss = s.result_peak_rss;
683 try ttyconf.setColor(stderr, .dim);
684 if (rss >= 1000_000_000) {
685 try stderr.writer().print(" MaxRSS:{d}G", .{rss / 1000_000_000});
686 } else if (rss >= 1000_000) {
687 try stderr.writer().print(" MaxRSS:{d}M", .{rss / 1000_000});
688 } else if (rss >= 1000) {
689 try stderr.writer().print(" MaxRSS:{d}K", .{rss / 1000});
690 } else {
691 try stderr.writer().print(" MaxRSS:{d}B", .{rss});
692 }
693 try ttyconf.setColor(stderr, .reset);
694 }
695 try stderr.writeAll("\n");
696 },
697 .skipped, .skipped_oom => |skip| {
698 try ttyconf.setColor(stderr, .yellow);
699 try stderr.writeAll(" skipped");
700 if (skip == .skipped_oom) {
701 try stderr.writeAll(" (not enough memory)");
702 try ttyconf.setColor(stderr, .dim);
703 try stderr.writer().print(" upper bound of {d} exceeded runner limit ({d})", .{ s.max_rss, run.max_rss });
704 try ttyconf.setColor(stderr, .yellow);
705 }
706 try stderr.writeAll("\n");
707 try ttyconf.setColor(stderr, .reset);
708 },
709 .failure => try printStepFailure(s, stderr, ttyconf),
710 }
711}
712
713fn printStepFailure(
714 s: *Step,
715 stderr: File,
716 ttyconf: std.io.tty.Config,
717) !void {
718 if (s.result_error_bundle.errorMessageCount() > 0) {
719 try ttyconf.setColor(stderr, .red);
720 try stderr.writer().print(" {d} errors\n", .{
721 s.result_error_bundle.errorMessageCount(),
722 });
723 try ttyconf.setColor(stderr, .reset);
724 } else if (!s.test_results.isSuccess()) {
725 try stderr.writer().print(" {d}/{d} passed", .{
726 s.test_results.passCount(), s.test_results.test_count,
727 });
728 if (s.test_results.fail_count > 0) {
729 try stderr.writeAll(", ");
730 try ttyconf.setColor(stderr, .red);
731 try stderr.writer().print("{d} failed", .{
732 s.test_results.fail_count,
733 });
734 try ttyconf.setColor(stderr, .reset);
735 }
736 if (s.test_results.skip_count > 0) {
737 try stderr.writeAll(", ");
738 try ttyconf.setColor(stderr, .yellow);
739 try stderr.writer().print("{d} skipped", .{
740 s.test_results.skip_count,
741 });
742 try ttyconf.setColor(stderr, .reset);
743 }
744 if (s.test_results.leak_count > 0) {
745 try stderr.writeAll(", ");
746 try ttyconf.setColor(stderr, .red);
747 try stderr.writer().print("{d} leaked", .{
748 s.test_results.leak_count,
749 });
750 try ttyconf.setColor(stderr, .reset);
751 }
752 try stderr.writeAll("\n");
753 } else if (s.result_error_msgs.items.len > 0) {
754 try ttyconf.setColor(stderr, .red);
755 try stderr.writeAll(" failure\n");
756 try ttyconf.setColor(stderr, .reset);
757 } else {
758 assert(s.result_stderr.len > 0);
759 try ttyconf.setColor(stderr, .red);
760 try stderr.writeAll(" stderr\n");
761 try ttyconf.setColor(stderr, .reset);
762 }
763}
764
765fn printTreeStep(
766 b: *std.Build,
767 s: *Step,
768 run: *const Run,
769 stderr: File,
770 ttyconf: std.io.tty.Config,
771 parent_node: *PrintNode,
772 step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),
773 failures_only: bool,
774) !void {
775 const first = step_stack.swapRemove(s);
776 if (failures_only and s.state == .success) return;
777 try printPrefix(parent_node, stderr, ttyconf);
778
779 if (!first) try ttyconf.setColor(stderr, .dim);
780 if (parent_node.parent != null) {
781 if (parent_node.last) {
782 try printChildNodePrefix(stderr, ttyconf);
783 } else {
784 try stderr.writeAll(switch (ttyconf) {
785 .no_color, .windows_api => "+- ",
786 .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─
787 });
788 }
789 }
790
791 // dep_prefix omitted here because it is redundant with the tree.
792 try stderr.writeAll(s.name);
793
794 if (first) {
795 try printStepStatus(s, stderr, ttyconf, run);
796
797 const last_index = if (!failures_only) s.dependencies.items.len -| 1 else blk: {
798 var i: usize = s.dependencies.items.len;
799 while (i > 0) {
800 i -= 1;
801 if (s.dependencies.items[i].state != .success) break :blk i;
802 }
803 break :blk s.dependencies.items.len -| 1;
804 };
805 for (s.dependencies.items, 0..) |dep, i| {
806 var print_node: PrintNode = .{
807 .parent = parent_node,
808 .last = i == last_index,
809 };
810 try printTreeStep(b, dep, run, stderr, ttyconf, &print_node, step_stack, failures_only);
811 }
812 } else {
813 if (s.dependencies.items.len == 0) {
814 try stderr.writeAll(" (reused)\n");
815 } else {
816 try stderr.writer().print(" (+{d} more reused dependencies)\n", .{
817 s.dependencies.items.len,
818 });
819 }
820 try ttyconf.setColor(stderr, .reset);
821 }
822}
823
824/// Traverse the dependency graph depth-first and make it undirected by having
825/// steps know their dependants (they only know dependencies at start).
826/// Along the way, check that there is no dependency loop, and record the steps
827/// in traversal order in `step_stack`.
828/// Each step has its dependencies traversed in random order, this accomplishes
829/// two things:
830/// - `step_stack` will be in randomized-depth-first order, so the build runner
831/// spawns steps in a random (but optimized) order
832/// - each step's `dependants` list is also filled in a random order, so that
833/// when it finishes executing in `workerMakeOneStep`, it spawns next steps
834/// to run in random order
835fn constructGraphAndCheckForDependencyLoop(
836 b: *std.Build,
837 s: *Step,
838 step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),
839 rand: std.Random,
840) !void {
841 switch (s.state) {
842 .precheck_started => {
843 std.debug.print("dependency loop detected:\n {s}\n", .{s.name});
844 return error.DependencyLoopDetected;
845 },
846 .precheck_unstarted => {
847 s.state = .precheck_started;
848
849 try step_stack.ensureUnusedCapacity(b.allocator, s.dependencies.items.len);
850
851 // We dupe to avoid shuffling the steps in the summary, it depends
852 // on s.dependencies' order.
853 const deps = b.allocator.dupe(*Step, s.dependencies.items) catch @panic("OOM");
854 rand.shuffle(*Step, deps);
855
856 for (deps) |dep| {
857 try step_stack.put(b.allocator, dep, {});
858 try dep.dependants.append(b.allocator, s);
859 constructGraphAndCheckForDependencyLoop(b, dep, step_stack, rand) catch |err| {
860 if (err == error.DependencyLoopDetected) {
861 std.debug.print(" {s}\n", .{s.name});
862 }
863 return err;
864 };
865 }
866
867 s.state = .precheck_done;
868 },
869 .precheck_done => {},
870
871 // These don't happen until we actually run the step graph.
872 .dependency_failure => unreachable,
873 .running => unreachable,
874 .success => unreachable,
875 .failure => unreachable,
876 .skipped => unreachable,
877 .skipped_oom => unreachable,
878 }
879}
880
881fn workerMakeOneStep(
882 wg: *std.Thread.WaitGroup,
883 thread_pool: *std.Thread.Pool,
884 b: *std.Build,
885 s: *Step,
886 prog_node: *std.Progress.Node,
887 run: *Run,
888) void {
889 defer wg.finish();
890
891 // First, check the conditions for running this step. If they are not met,
892 // then we return without doing the step, relying on another worker to
893 // queue this step up again when dependencies are met.
894 for (s.dependencies.items) |dep| {
895 switch (@atomicLoad(Step.State, &dep.state, .SeqCst)) {
896 .success, .skipped => continue,
897 .failure, .dependency_failure, .skipped_oom => {
898 @atomicStore(Step.State, &s.state, .dependency_failure, .SeqCst);
899 return;
900 },
901 .precheck_done, .running => {
902 // dependency is not finished yet.
903 return;
904 },
905 .precheck_unstarted => unreachable,
906 .precheck_started => unreachable,
907 }
908 }
909
910 if (s.max_rss != 0) {
911 run.max_rss_mutex.lock();
912 defer run.max_rss_mutex.unlock();
913
914 // Avoid running steps twice.
915 if (s.state != .precheck_done) {
916 // Another worker got the job.
917 return;
918 }
919
920 const new_claimed_rss = run.claimed_rss + s.max_rss;
921 if (new_claimed_rss > run.max_rss) {
922 // Running this step right now could possibly exceed the allotted RSS.
923 // Add this step to the queue of memory-blocked steps.
924 run.memory_blocked_steps.append(s) catch @panic("OOM");
925 return;
926 }
927
928 run.claimed_rss = new_claimed_rss;
929 s.state = .running;
930 } else {
931 // Avoid running steps twice.
932 if (@cmpxchgStrong(Step.State, &s.state, .precheck_done, .running, .SeqCst, .SeqCst) != null) {
933 // Another worker got the job.
934 return;
935 }
936 }
937
938 var sub_prog_node = prog_node.start(s.name, 0);
939 sub_prog_node.activate();
940 defer sub_prog_node.end();
941
942 const make_result = s.make(&sub_prog_node);
943
944 // No matter the result, we want to display error/warning messages.
945 const show_compile_errors = !run.prominent_compile_errors and
946 s.result_error_bundle.errorMessageCount() > 0;
947 const show_error_msgs = s.result_error_msgs.items.len > 0;
948 const show_stderr = s.result_stderr.len > 0;
949
950 if (show_error_msgs or show_compile_errors or show_stderr) {
951 sub_prog_node.context.lock_stderr();
952 defer sub_prog_node.context.unlock_stderr();
953
954 printErrorMessages(b, s, run) catch {};
955 }
956
957 handle_result: {
958 if (make_result) |_| {
959 @atomicStore(Step.State, &s.state, .success, .SeqCst);
960 } else |err| switch (err) {
961 error.MakeFailed => {
962 @atomicStore(Step.State, &s.state, .failure, .SeqCst);
963 break :handle_result;
964 },
965 error.MakeSkipped => @atomicStore(Step.State, &s.state, .skipped, .SeqCst),
966 }
967
968 // Successful completion of a step, so we queue up its dependants as well.
969 for (s.dependants.items) |dep| {
970 wg.start();
971 thread_pool.spawn(workerMakeOneStep, .{
972 wg, thread_pool, b, dep, prog_node, run,
973 }) catch @panic("OOM");
974 }
975 }
976
977 // If this is a step that claims resources, we must now queue up other
978 // steps that are waiting for resources.
979 if (s.max_rss != 0) {
980 run.max_rss_mutex.lock();
981 defer run.max_rss_mutex.unlock();
982
983 // Give the memory back to the scheduler.
984 run.claimed_rss -= s.max_rss;
985 // Avoid kicking off too many tasks that we already know will not have
986 // enough resources.
987 var remaining = run.max_rss - run.claimed_rss;
988 var i: usize = 0;
989 var j: usize = 0;
990 while (j < run.memory_blocked_steps.items.len) : (j += 1) {
991 const dep = run.memory_blocked_steps.items[j];
992 assert(dep.max_rss != 0);
993 if (dep.max_rss <= remaining) {
994 remaining -= dep.max_rss;
995
996 wg.start();
997 thread_pool.spawn(workerMakeOneStep, .{
998 wg, thread_pool, b, dep, prog_node, run,
999 }) catch @panic("OOM");
1000 } else {
1001 run.memory_blocked_steps.items[i] = dep;
1002 i += 1;
1003 }
1004 }
1005 run.memory_blocked_steps.shrinkRetainingCapacity(i);
1006 }
1007}
1008
1009fn printErrorMessages(b: *std.Build, failing_step: *Step, run: *const Run) !void {
1010 const gpa = b.allocator;
1011 const stderr = run.stderr;
1012 const ttyconf = run.ttyconf;
1013
1014 // Provide context for where these error messages are coming from by
1015 // printing the corresponding Step subtree.
1016
1017 var step_stack: std.ArrayListUnmanaged(*Step) = .{};
1018 defer step_stack.deinit(gpa);
1019 try step_stack.append(gpa, failing_step);
1020 while (step_stack.items[step_stack.items.len - 1].dependants.items.len != 0) {
1021 try step_stack.append(gpa, step_stack.items[step_stack.items.len - 1].dependants.items[0]);
1022 }
1023
1024 // Now, `step_stack` has the subtree that we want to print, in reverse order.
1025 try ttyconf.setColor(stderr, .dim);
1026 var indent: usize = 0;
1027 while (step_stack.popOrNull()) |s| : (indent += 1) {
1028 if (indent > 0) {
1029 try stderr.writer().writeByteNTimes(' ', (indent - 1) * 3);
1030 try printChildNodePrefix(stderr, ttyconf);
1031 }
1032
1033 try stderr.writeAll(s.name);
1034
1035 if (s == failing_step) {
1036 try printStepFailure(s, stderr, ttyconf);
1037 } else {
1038 try stderr.writeAll("\n");
1039 }
1040 }
1041 try ttyconf.setColor(stderr, .reset);
1042
1043 if (failing_step.result_stderr.len > 0) {
1044 try stderr.writeAll(failing_step.result_stderr);
1045 if (!mem.endsWith(u8, failing_step.result_stderr, "\n")) {
1046 try stderr.writeAll("\n");
1047 }
1048 }
1049
1050 if (!run.prominent_compile_errors and failing_step.result_error_bundle.errorMessageCount() > 0)
1051 try failing_step.result_error_bundle.renderToWriter(renderOptions(ttyconf), stderr.writer());
1052
1053 for (failing_step.result_error_msgs.items) |msg| {
1054 try ttyconf.setColor(stderr, .red);
1055 try stderr.writeAll("error: ");
1056 try ttyconf.setColor(stderr, .reset);
1057 try stderr.writeAll(msg);
1058 try stderr.writeAll("\n");
1059 }
1060}
1061
1062fn steps(builder: *std.Build, out_stream: anytype) !void {
1063 const allocator = builder.allocator;
1064 for (builder.top_level_steps.values()) |top_level_step| {
1065 const name = if (&top_level_step.step == builder.default_step)
1066 try fmt.allocPrint(allocator, "{s} (default)", .{top_level_step.step.name})
1067 else
1068 top_level_step.step.name;
1069 try out_stream.print(" {s:<28} {s}\n", .{ name, top_level_step.description });
1070 }
1071}
1072
1073fn usage(b: *std.Build, out_stream: anytype) !void {
1074 try out_stream.print(
1075 \\Usage: {s} build [steps] [options]
1076 \\
1077 \\Steps:
1078 \\
1079 , .{b.graph.zig_exe});
1080 try steps(b, out_stream);
1081
1082 try out_stream.writeAll(
1083 \\
1084 \\General Options:
1085 \\ -p, --prefix [path] Where to install files (default: zig-out)
1086 \\ --prefix-lib-dir [path] Where to install libraries
1087 \\ --prefix-exe-dir [path] Where to install executables
1088 \\ --prefix-include-dir [path] Where to install C header files
1089 \\
1090 \\ --release[=mode] Request release mode, optionally specifying a
1091 \\ preferred optimization mode: fast, safe, small
1092 \\
1093 \\ -fdarling, -fno-darling Integration with system-installed Darling to
1094 \\ execute macOS programs on Linux hosts
1095 \\ (default: no)
1096 \\ -fqemu, -fno-qemu Integration with system-installed QEMU to execute
1097 \\ foreign-architecture programs on Linux hosts
1098 \\ (default: no)
1099 \\ --glibc-runtimes [path] Enhances QEMU integration by providing glibc built
1100 \\ for multiple foreign architectures, allowing
1101 \\ execution of non-native programs that link with glibc.
1102 \\ -frosetta, -fno-rosetta Rely on Rosetta to execute x86_64 programs on
1103 \\ ARM64 macOS hosts. (default: no)
1104 \\ -fwasmtime, -fno-wasmtime Integration with system-installed wasmtime to
1105 \\ execute WASI binaries. (default: no)
1106 \\ -fwine, -fno-wine Integration with system-installed Wine to execute
1107 \\ Windows programs on Linux hosts. (default: no)
1108 \\
1109 \\ -h, --help Print this help and exit
1110 \\ -l, --list-steps Print available steps
1111 \\ --verbose Print commands before executing them
1112 \\ --color [auto|off|on] Enable or disable colored error messages
1113 \\ --prominent-compile-errors Buffer compile errors and display at end
1114 \\ --summary [mode] Control the printing of the build summary
1115 \\ all Print the build summary in its entirety
1116 \\ failures (Default) Only print failed steps
1117 \\ none Do not print the build summary
1118 \\ -j<N> Limit concurrent jobs (default is to use all CPU cores)
1119 \\ --maxrss <bytes> Limit memory usage (default is to use available memory)
1120 \\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss
1121 \\ --fetch Exit after fetching dependency tree
1122 \\
1123 \\Project-Specific Options:
1124 \\
1125 );
1126
1127 const arena = b.allocator;
1128 if (b.available_options_list.items.len == 0) {
1129 try out_stream.print(" (none)\n", .{});
1130 } else {
1131 for (b.available_options_list.items) |option| {
1132 const name = try fmt.allocPrint(arena, " -D{s}=[{s}]", .{
1133 option.name,
1134 @tagName(option.type_id),
1135 });
1136 try out_stream.print("{s:<30} {s}\n", .{ name, option.description });
1137 if (option.enum_options) |enum_options| {
1138 const padding = " " ** 33;
1139 try out_stream.writeAll(padding ++ "Supported Values:\n");
1140 for (enum_options) |enum_option| {
1141 try out_stream.print(padding ++ " {s}\n", .{enum_option});
1142 }
1143 }
1144 }
1145 }
1146
1147 try out_stream.writeAll(
1148 \\
1149 \\System Integration Options:
1150 \\ --search-prefix [path] Add a path to look for binaries, libraries, headers
1151 \\ --sysroot [path] Set the system root directory (usually /)
1152 \\ --libc [file] Provide a file which specifies libc paths
1153 \\
1154 \\ --host-target [triple] Use the provided target as the host
1155 \\ --host-cpu [cpu] Use the provided CPU as the host
1156 \\ --host-dynamic-linker [path] Use the provided dynamic linker as the host
1157 \\
1158 \\ --system [pkgdir] Disable package fetching; enable all integrations
1159 \\ -fsys=[name] Enable a system integration
1160 \\ -fno-sys=[name] Disable a system integration
1161 \\
1162 \\ Available System Integrations: Enabled:
1163 \\
1164 );
1165 if (b.graph.system_library_options.entries.len == 0) {
1166 try out_stream.writeAll(" (none) -\n");
1167 } else {
1168 for (b.graph.system_library_options.keys(), b.graph.system_library_options.values()) |k, v| {
1169 const status = switch (v) {
1170 .declared_enabled => "yes",
1171 .declared_disabled => "no",
1172 .user_enabled, .user_disabled => unreachable, // already emitted error
1173 };
1174 try out_stream.print(" {s:<43} {s}\n", .{ k, status });
1175 }
1176 }
1177
1178 try out_stream.writeAll(
1179 \\
1180 \\Advanced Options:
1181 \\ -freference-trace[=num] How many lines of reference trace should be shown per compile error
1182 \\ -fno-reference-trace Disable reference trace
1183 \\ --build-file [file] Override path to build.zig
1184 \\ --cache-dir [path] Override path to local Zig cache directory
1185 \\ --global-cache-dir [path] Override path to global Zig cache directory
1186 \\ --zig-lib-dir [arg] Override path to Zig lib directory
1187 \\ --build-runner [file] Override path to build runner
1188 \\ --seed [integer] For shuffling dependency traversal order (default: random)
1189 \\ --debug-log [scope] Enable debugging the compiler
1190 \\ --debug-pkg-config Fail if unknown pkg-config flags encountered
1191 \\ --verbose-link Enable compiler debug output for linking
1192 \\ --verbose-air Enable compiler debug output for Zig AIR
1193 \\ --verbose-llvm-ir[=file] Enable compiler debug output for LLVM IR
1194 \\ --verbose-llvm-bc=[file] Enable compiler debug output for LLVM BC
1195 \\ --verbose-cimport Enable compiler debug output for C imports
1196 \\ --verbose-cc Enable compiler debug output for C compilation
1197 \\ --verbose-llvm-cpu-features Enable compiler debug output for LLVM CPU features
1198 \\
1199 );
1200}
1201
1202fn nextArg(args: [][:0]const u8, idx: *usize) ?[:0]const u8 {
1203 if (idx.* >= args.len) return null;
1204 defer idx.* += 1;
1205 return args[idx.*];
1206}
1207
1208fn nextArgOrFatal(args: [][:0]const u8, idx: *usize) [:0]const u8 {
1209 return nextArg(args, idx) orelse {
1210 std.debug.print("expected argument after '{s}'\n access the help menu with 'zig build -h'\n", .{args[idx.*]});
1211 process.exit(1);
1212 };
1213}
1214
1215fn argsRest(args: [][:0]const u8, idx: usize) ?[][:0]const u8 {
1216 if (idx >= args.len) return null;
1217 return args[idx..];
1218}
1219
1220fn cleanExit() void {
1221 // Perhaps in the future there could be an Advanced Options flag such as
1222 // --debug-build-runner-leaks which would make this function return instead
1223 // of calling exit.
1224 process.exit(0);
1225}
1226
1227const Color = enum { auto, off, on };
1228const Summary = enum { all, failures, none };
1229
1230fn get_tty_conf(color: Color, stderr: File) std.io.tty.Config {
1231 return switch (color) {
1232 .auto => std.io.tty.detectConfig(stderr),
1233 .on => .escape_codes,
1234 .off => .no_color,
1235 };
1236}
1237
1238fn renderOptions(ttyconf: std.io.tty.Config) std.zig.ErrorBundle.RenderOptions {
1239 return .{
1240 .ttyconf = ttyconf,
1241 .include_source_line = ttyconf != .no_color,
1242 .include_reference_trace = ttyconf != .no_color,
1243 };
1244}
1245
1246fn fatalWithHint(comptime f: []const u8, args: anytype) noreturn {
1247 std.debug.print(f ++ "\n access the help menu with 'zig build -h'\n", args);
1248 process.exit(1);
1249}
1250
1251fn fatal(comptime f: []const u8, args: anytype) noreturn {
1252 std.debug.print(f ++ "\n", args);
1253 process.exit(1);
1254}
1255
1256fn validateSystemLibraryOptions(b: *std.Build) void {
1257 var bad = false;
1258 for (b.graph.system_library_options.keys(), b.graph.system_library_options.values()) |k, v| {
1259 switch (v) {
1260 .user_disabled, .user_enabled => {
1261 // The user tried to enable or disable a system library integration, but
1262 // the build script did not recognize that option.
1263 std.debug.print("system library name not recognized by build script: '{s}'\n", .{k});
1264 bad = true;
1265 },
1266 .declared_disabled, .declared_enabled => {},
1267 }
1268 }
1269 if (bad) {
1270 std.debug.print(" access the help menu with 'zig build -h'\n", .{});
1271 process.exit(1);
1272 }
1273}
lib/compiler/build_runner.zig deleted-1273
...@@ -1,1273 +0,0 @@
1const root = @import("@build");
2const std = @import("std");
3const builtin = @import("builtin");
4const assert = std.debug.assert;
5const io = std.io;
6const fmt = std.fmt;
7const mem = std.mem;
8const process = std.process;
9const ArrayList = std.ArrayList;
10const File = std.fs.File;
11const Step = std.Build.Step;
12
13pub const dependencies = @import("@dependencies");
14
15pub fn main() !void {
16 // Here we use an ArenaAllocator backed by a page allocator because a build is a short-lived,
17 // one shot program. We don't need to waste time freeing memory and finding places to squish
18 // bytes into. So we free everything all at once at the very end.
19 var single_threaded_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
20 defer single_threaded_arena.deinit();
21
22 var thread_safe_arena: std.heap.ThreadSafeAllocator = .{
23 .child_allocator = single_threaded_arena.allocator(),
24 };
25 const arena = thread_safe_arena.allocator();
26
27 const args = try process.argsAlloc(arena);
28
29 // skip my own exe name
30 var arg_idx: usize = 1;
31
32 const zig_exe = nextArg(args, &arg_idx) orelse {
33 std.debug.print("Expected path to zig compiler\n", .{});
34 return error.InvalidArgs;
35 };
36 const build_root = nextArg(args, &arg_idx) orelse {
37 std.debug.print("Expected build root directory path\n", .{});
38 return error.InvalidArgs;
39 };
40 const cache_root = nextArg(args, &arg_idx) orelse {
41 std.debug.print("Expected cache root directory path\n", .{});
42 return error.InvalidArgs;
43 };
44 const global_cache_root = nextArg(args, &arg_idx) orelse {
45 std.debug.print("Expected global cache root directory path\n", .{});
46 return error.InvalidArgs;
47 };
48
49 const build_root_directory: std.Build.Cache.Directory = .{
50 .path = build_root,
51 .handle = try std.fs.cwd().openDir(build_root, .{}),
52 };
53
54 const local_cache_directory: std.Build.Cache.Directory = .{
55 .path = cache_root,
56 .handle = try std.fs.cwd().makeOpenPath(cache_root, .{}),
57 };
58
59 const global_cache_directory: std.Build.Cache.Directory = .{
60 .path = global_cache_root,
61 .handle = try std.fs.cwd().makeOpenPath(global_cache_root, .{}),
62 };
63
64 var graph: std.Build.Graph = .{
65 .arena = arena,
66 .cache = .{
67 .gpa = arena,
68 .manifest_dir = try local_cache_directory.handle.makeOpenPath("h", .{}),
69 },
70 .zig_exe = zig_exe,
71 .env_map = try process.getEnvMap(arena),
72 .global_cache_root = global_cache_directory,
73 };
74
75 graph.cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() });
76 graph.cache.addPrefix(build_root_directory);
77 graph.cache.addPrefix(local_cache_directory);
78 graph.cache.addPrefix(global_cache_directory);
79 graph.cache.hash.addBytes(builtin.zig_version_string);
80
81 const builder = try std.Build.create(
82 &graph,
83 build_root_directory,
84 local_cache_directory,
85 dependencies.root_deps,
86 );
87
88 var targets = ArrayList([]const u8).init(arena);
89 var debug_log_scopes = ArrayList([]const u8).init(arena);
90 var thread_pool_options: std.Thread.Pool.Options = .{ .allocator = arena };
91
92 var install_prefix: ?[]const u8 = null;
93 var dir_list = std.Build.DirList{};
94 var summary: ?Summary = null;
95 var max_rss: u64 = 0;
96 var skip_oom_steps: bool = false;
97 var color: Color = .auto;
98 var seed: u32 = 0;
99 var prominent_compile_errors: bool = false;
100 var help_menu: bool = false;
101 var steps_menu: bool = false;
102 var output_tmp_nonce: ?[16]u8 = null;
103
104 while (nextArg(args, &arg_idx)) |arg| {
105 if (mem.startsWith(u8, arg, "-Z")) {
106 if (arg.len != 18) fatalWithHint("bad argument: '{s}'", .{arg});
107 output_tmp_nonce = arg[2..18].*;
108 } else if (mem.startsWith(u8, arg, "-D")) {
109 const option_contents = arg[2..];
110 if (option_contents.len == 0)
111 fatalWithHint("expected option name after '-D'", .{});
112 if (mem.indexOfScalar(u8, option_contents, '=')) |name_end| {
113 const option_name = option_contents[0..name_end];
114 const option_value = option_contents[name_end + 1 ..];
115 if (try builder.addUserInputOption(option_name, option_value))
116 fatal(" access the help menu with 'zig build -h'", .{});
117 } else {
118 if (try builder.addUserInputFlag(option_contents))
119 fatal(" access the help menu with 'zig build -h'", .{});
120 }
121 } else if (mem.startsWith(u8, arg, "-")) {
122 if (mem.eql(u8, arg, "--verbose")) {
123 builder.verbose = true;
124 } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
125 help_menu = true;
126 } else if (mem.eql(u8, arg, "-p") or mem.eql(u8, arg, "--prefix")) {
127 install_prefix = nextArgOrFatal(args, &arg_idx);
128 } else if (mem.eql(u8, arg, "-l") or mem.eql(u8, arg, "--list-steps")) {
129 steps_menu = true;
130 } else if (mem.startsWith(u8, arg, "-fsys=")) {
131 const name = arg["-fsys=".len..];
132 graph.system_library_options.put(arena, name, .user_enabled) catch @panic("OOM");
133 } else if (mem.startsWith(u8, arg, "-fno-sys=")) {
134 const name = arg["-fno-sys=".len..];
135 graph.system_library_options.put(arena, name, .user_disabled) catch @panic("OOM");
136 } else if (mem.eql(u8, arg, "--release")) {
137 builder.release_mode = .any;
138 } else if (mem.startsWith(u8, arg, "--release=")) {
139 const text = arg["--release=".len..];
140 builder.release_mode = std.meta.stringToEnum(std.Build.ReleaseMode, text) orelse {
141 fatalWithHint("expected [off|any|fast|safe|small] in '{s}', found '{s}'", .{
142 arg, text,
143 });
144 };
145 } else if (mem.eql(u8, arg, "--host-target")) {
146 graph.host_query_options.arch_os_abi = nextArgOrFatal(args, &arg_idx);
147 } else if (mem.eql(u8, arg, "--host-cpu")) {
148 graph.host_query_options.cpu_features = nextArgOrFatal(args, &arg_idx);
149 } else if (mem.eql(u8, arg, "--host-dynamic-linker")) {
150 graph.host_query_options.dynamic_linker = nextArgOrFatal(args, &arg_idx);
151 } else if (mem.eql(u8, arg, "--prefix-lib-dir")) {
152 dir_list.lib_dir = nextArgOrFatal(args, &arg_idx);
153 } else if (mem.eql(u8, arg, "--prefix-exe-dir")) {
154 dir_list.exe_dir = nextArgOrFatal(args, &arg_idx);
155 } else if (mem.eql(u8, arg, "--prefix-include-dir")) {
156 dir_list.include_dir = nextArgOrFatal(args, &arg_idx);
157 } else if (mem.eql(u8, arg, "--sysroot")) {
158 builder.sysroot = nextArgOrFatal(args, &arg_idx);
159 } else if (mem.eql(u8, arg, "--maxrss")) {
160 const max_rss_text = nextArgOrFatal(args, &arg_idx);
161 max_rss = std.fmt.parseIntSizeSuffix(max_rss_text, 10) catch |err| {
162 std.debug.print("invalid byte size: '{s}': {s}\n", .{
163 max_rss_text, @errorName(err),
164 });
165 process.exit(1);
166 };
167 } else if (mem.eql(u8, arg, "--skip-oom-steps")) {
168 skip_oom_steps = true;
169 } else if (mem.eql(u8, arg, "--search-prefix")) {
170 const search_prefix = nextArgOrFatal(args, &arg_idx);
171 builder.addSearchPrefix(search_prefix);
172 } else if (mem.eql(u8, arg, "--libc")) {
173 builder.libc_file = nextArgOrFatal(args, &arg_idx);
174 } else if (mem.eql(u8, arg, "--color")) {
175 const next_arg = nextArg(args, &arg_idx) orelse
176 fatalWithHint("expected [auto|on|off] after '{s}'", .{arg});
177 color = std.meta.stringToEnum(Color, next_arg) orelse {
178 fatalWithHint("expected [auto|on|off] after '{s}', found '{s}'", .{
179 arg, next_arg,
180 });
181 };
182 } else if (mem.eql(u8, arg, "--summary")) {
183 const next_arg = nextArg(args, &arg_idx) orelse
184 fatalWithHint("expected [all|failures|none] after '{s}'", .{arg});
185 summary = std.meta.stringToEnum(Summary, next_arg) orelse {
186 fatalWithHint("expected [all|failures|none] after '{s}', found '{s}'", .{
187 arg, next_arg,
188 });
189 };
190 } else if (mem.eql(u8, arg, "--zig-lib-dir")) {
191 builder.zig_lib_dir = .{ .cwd_relative = nextArgOrFatal(args, &arg_idx) };
192 } else if (mem.eql(u8, arg, "--seed")) {
193 const next_arg = nextArg(args, &arg_idx) orelse
194 fatalWithHint("expected u32 after '{s}'", .{arg});
195 seed = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err| {
196 fatal("unable to parse seed '{s}' as 32-bit integer: {s}\n", .{
197 next_arg, @errorName(err),
198 });
199 };
200 } else if (mem.eql(u8, arg, "--debug-log")) {
201 const next_arg = nextArgOrFatal(args, &arg_idx);
202 try debug_log_scopes.append(next_arg);
203 } else if (mem.eql(u8, arg, "--debug-pkg-config")) {
204 builder.debug_pkg_config = true;
205 } else if (mem.eql(u8, arg, "--debug-compile-errors")) {
206 builder.debug_compile_errors = true;
207 } else if (mem.eql(u8, arg, "--system")) {
208 // The usage text shows another argument after this parameter
209 // but it is handled by the parent process. The build runner
210 // only sees this flag.
211 graph.system_package_mode = true;
212 } else if (mem.eql(u8, arg, "--glibc-runtimes")) {
213 builder.glibc_runtimes_dir = nextArgOrFatal(args, &arg_idx);
214 } else if (mem.eql(u8, arg, "--verbose-link")) {
215 builder.verbose_link = true;
216 } else if (mem.eql(u8, arg, "--verbose-air")) {
217 builder.verbose_air = true;
218 } else if (mem.eql(u8, arg, "--verbose-llvm-ir")) {
219 builder.verbose_llvm_ir = "-";
220 } else if (mem.startsWith(u8, arg, "--verbose-llvm-ir=")) {
221 builder.verbose_llvm_ir = arg["--verbose-llvm-ir=".len..];
222 } else if (mem.eql(u8, arg, "--verbose-llvm-bc=")) {
223 builder.verbose_llvm_bc = arg["--verbose-llvm-bc=".len..];
224 } else if (mem.eql(u8, arg, "--verbose-cimport")) {
225 builder.verbose_cimport = true;
226 } else if (mem.eql(u8, arg, "--verbose-cc")) {
227 builder.verbose_cc = true;
228 } else if (mem.eql(u8, arg, "--verbose-llvm-cpu-features")) {
229 builder.verbose_llvm_cpu_features = true;
230 } else if (mem.eql(u8, arg, "--prominent-compile-errors")) {
231 prominent_compile_errors = true;
232 } else if (mem.eql(u8, arg, "-fwine")) {
233 builder.enable_wine = true;
234 } else if (mem.eql(u8, arg, "-fno-wine")) {
235 builder.enable_wine = false;
236 } else if (mem.eql(u8, arg, "-fqemu")) {
237 builder.enable_qemu = true;
238 } else if (mem.eql(u8, arg, "-fno-qemu")) {
239 builder.enable_qemu = false;
240 } else if (mem.eql(u8, arg, "-fwasmtime")) {
241 builder.enable_wasmtime = true;
242 } else if (mem.eql(u8, arg, "-fno-wasmtime")) {
243 builder.enable_wasmtime = false;
244 } else if (mem.eql(u8, arg, "-frosetta")) {
245 builder.enable_rosetta = true;
246 } else if (mem.eql(u8, arg, "-fno-rosetta")) {
247 builder.enable_rosetta = false;
248 } else if (mem.eql(u8, arg, "-fdarling")) {
249 builder.enable_darling = true;
250 } else if (mem.eql(u8, arg, "-fno-darling")) {
251 builder.enable_darling = false;
252 } else if (mem.eql(u8, arg, "-freference-trace")) {
253 builder.reference_trace = 256;
254 } else if (mem.startsWith(u8, arg, "-freference-trace=")) {
255 const num = arg["-freference-trace=".len..];
256 builder.reference_trace = std.fmt.parseUnsigned(u32, num, 10) catch |err| {
257 std.debug.print("unable to parse reference_trace count '{s}': {s}", .{ num, @errorName(err) });
258 process.exit(1);
259 };
260 } else if (mem.eql(u8, arg, "-fno-reference-trace")) {
261 builder.reference_trace = null;
262 } else if (mem.startsWith(u8, arg, "-j")) {
263 const num = arg["-j".len..];
264 const n_jobs = std.fmt.parseUnsigned(u32, num, 10) catch |err| {
265 std.debug.print("unable to parse jobs count '{s}': {s}", .{
266 num, @errorName(err),
267 });
268 process.exit(1);
269 };
270 if (n_jobs < 1) {
271 std.debug.print("number of jobs must be at least 1\n", .{});
272 process.exit(1);
273 }
274 thread_pool_options.n_jobs = n_jobs;
275 } else if (mem.eql(u8, arg, "--")) {
276 builder.args = argsRest(args, arg_idx);
277 break;
278 } else {
279 fatalWithHint("unrecognized argument: '{s}'", .{arg});
280 }
281 } else {
282 try targets.append(arg);
283 }
284 }
285
286 const host_query = std.Build.parseTargetQuery(graph.host_query_options) catch |err| switch (err) {
287 error.ParseFailed => process.exit(1),
288 };
289 builder.host = .{
290 .query = .{},
291 .result = try std.zig.system.resolveTargetQuery(host_query),
292 };
293
294 const stderr = std.io.getStdErr();
295 const ttyconf = get_tty_conf(color, stderr);
296 switch (ttyconf) {
297 .no_color => try graph.env_map.put("NO_COLOR", "1"),
298 .escape_codes => try graph.env_map.put("YES_COLOR", "1"),
299 .windows_api => {},
300 }
301
302 var progress: std.Progress = .{ .dont_print_on_dumb = true };
303 const main_progress_node = progress.start("", 0);
304
305 builder.debug_log_scopes = debug_log_scopes.items;
306 builder.resolveInstallPrefix(install_prefix, dir_list);
307 {
308 var prog_node = main_progress_node.start("user build.zig logic", 0);
309 defer prog_node.end();
310 try builder.runBuild(root);
311 }
312
313 if (graph.needed_lazy_dependencies.entries.len != 0) {
314 var buffer: std.ArrayListUnmanaged(u8) = .{};
315 for (graph.needed_lazy_dependencies.keys()) |k| {
316 try buffer.appendSlice(arena, k);
317 try buffer.append(arena, '\n');
318 }
319 const s = std.fs.path.sep_str;
320 const tmp_sub_path = "tmp" ++ s ++ (output_tmp_nonce orelse fatal("missing -Z arg", .{}));
321 local_cache_directory.handle.writeFile2(.{
322 .sub_path = tmp_sub_path,
323 .data = buffer.items,
324 .flags = .{ .exclusive = true },
325 }) catch |err| {
326 fatal("unable to write configuration results to '{}{s}': {s}", .{
327 local_cache_directory, tmp_sub_path, @errorName(err),
328 });
329 };
330 process.exit(3); // Indicate configure phase failed with meaningful stdout.
331 }
332
333 if (builder.validateUserInputDidItFail()) {
334 fatal(" access the help menu with 'zig build -h'", .{});
335 }
336
337 validateSystemLibraryOptions(builder);
338
339 const stdout_writer = io.getStdOut().writer();
340
341 if (help_menu)
342 return usage(builder, stdout_writer);
343
344 if (steps_menu)
345 return steps(builder, stdout_writer);
346
347 var run: Run = .{
348 .max_rss = max_rss,
349 .max_rss_is_default = false,
350 .max_rss_mutex = .{},
351 .skip_oom_steps = skip_oom_steps,
352 .memory_blocked_steps = std.ArrayList(*Step).init(arena),
353 .prominent_compile_errors = prominent_compile_errors,
354
355 .claimed_rss = 0,
356 .summary = summary,
357 .ttyconf = ttyconf,
358 .stderr = stderr,
359 };
360
361 if (run.max_rss == 0) {
362 run.max_rss = process.totalSystemMemory() catch std.math.maxInt(u64);
363 run.max_rss_is_default = true;
364 }
365
366 runStepNames(
367 arena,
368 builder,
369 targets.items,
370 main_progress_node,
371 thread_pool_options,
372 &run,
373 seed,
374 ) catch |err| switch (err) {
375 error.UncleanExit => process.exit(1),
376 else => return err,
377 };
378}
379
380const Run = struct {
381 max_rss: u64,
382 max_rss_is_default: bool,
383 max_rss_mutex: std.Thread.Mutex,
384 skip_oom_steps: bool,
385 memory_blocked_steps: std.ArrayList(*Step),
386 prominent_compile_errors: bool,
387
388 claimed_rss: usize,
389 summary: ?Summary,
390 ttyconf: std.io.tty.Config,
391 stderr: File,
392};
393
394fn runStepNames(
395 arena: std.mem.Allocator,
396 b: *std.Build,
397 step_names: []const []const u8,
398 parent_prog_node: *std.Progress.Node,
399 thread_pool_options: std.Thread.Pool.Options,
400 run: *Run,
401 seed: u32,
402) !void {
403 const gpa = b.allocator;
404 var step_stack: std.AutoArrayHashMapUnmanaged(*Step, void) = .{};
405 defer step_stack.deinit(gpa);
406
407 if (step_names.len == 0) {
408 try step_stack.put(gpa, b.default_step, {});
409 } else {
410 try step_stack.ensureUnusedCapacity(gpa, step_names.len);
411 for (0..step_names.len) |i| {
412 const step_name = step_names[step_names.len - i - 1];
413 const s = b.top_level_steps.get(step_name) orelse {
414 std.debug.print("no step named '{s}'\n access the help menu with 'zig build -h'\n", .{step_name});
415 process.exit(1);
416 };
417 step_stack.putAssumeCapacity(&s.step, {});
418 }
419 }
420
421 const starting_steps = try arena.dupe(*Step, step_stack.keys());
422
423 var rng = std.Random.DefaultPrng.init(seed);
424 const rand = rng.random();
425 rand.shuffle(*Step, starting_steps);
426
427 for (starting_steps) |s| {
428 constructGraphAndCheckForDependencyLoop(b, s, &step_stack, rand) catch |err| switch (err) {
429 error.DependencyLoopDetected => return error.UncleanExit,
430 else => |e| return e,
431 };
432 }
433
434 {
435 // Check that we have enough memory to complete the build.
436 var any_problems = false;
437 for (step_stack.keys()) |s| {
438 if (s.max_rss == 0) continue;
439 if (s.max_rss > run.max_rss) {
440 if (run.skip_oom_steps) {
441 s.state = .skipped_oom;
442 } else {
443 std.debug.print("{s}{s}: this step declares an upper bound of {d} bytes of memory, exceeding the available {d} bytes of memory\n", .{
444 s.owner.dep_prefix, s.name, s.max_rss, run.max_rss,
445 });
446 any_problems = true;
447 }
448 }
449 }
450 if (any_problems) {
451 if (run.max_rss_is_default) {
452 std.debug.print("note: use --maxrss to override the default", .{});
453 }
454 return error.UncleanExit;
455 }
456 }
457
458 var thread_pool: std.Thread.Pool = undefined;
459 try thread_pool.init(thread_pool_options);
460 defer thread_pool.deinit();
461
462 {
463 defer parent_prog_node.end();
464
465 var step_prog = parent_prog_node.start("steps", step_stack.count());
466 defer step_prog.end();
467
468 var wait_group: std.Thread.WaitGroup = .{};
469 defer wait_group.wait();
470
471 // Here we spawn the initial set of tasks with a nice heuristic -
472 // dependency order. Each worker when it finishes a step will then
473 // check whether it should run any dependants.
474 const steps_slice = step_stack.keys();
475 for (0..steps_slice.len) |i| {
476 const step = steps_slice[steps_slice.len - i - 1];
477 if (step.state == .skipped_oom) continue;
478
479 wait_group.start();
480 thread_pool.spawn(workerMakeOneStep, .{
481 &wait_group, &thread_pool, b, step, &step_prog, run,
482 }) catch @panic("OOM");
483 }
484 }
485 assert(run.memory_blocked_steps.items.len == 0);
486
487 var test_skip_count: usize = 0;
488 var test_fail_count: usize = 0;
489 var test_pass_count: usize = 0;
490 var test_leak_count: usize = 0;
491 var test_count: usize = 0;
492
493 var success_count: usize = 0;
494 var skipped_count: usize = 0;
495 var failure_count: usize = 0;
496 var pending_count: usize = 0;
497 var total_compile_errors: usize = 0;
498 var compile_error_steps: std.ArrayListUnmanaged(*Step) = .{};
499 defer compile_error_steps.deinit(gpa);
500
501 for (step_stack.keys()) |s| {
502 test_fail_count += s.test_results.fail_count;
503 test_skip_count += s.test_results.skip_count;
504 test_leak_count += s.test_results.leak_count;
505 test_pass_count += s.test_results.passCount();
506 test_count += s.test_results.test_count;
507
508 switch (s.state) {
509 .precheck_unstarted => unreachable,
510 .precheck_started => unreachable,
511 .running => unreachable,
512 .precheck_done => {
513 // precheck_done is equivalent to dependency_failure in the case of
514 // transitive dependencies. For example:
515 // A -> B -> C (failure)
516 // B will be marked as dependency_failure, while A may never be queued, and thus
517 // remain in the initial state of precheck_done.
518 s.state = .dependency_failure;
519 pending_count += 1;
520 },
521 .dependency_failure => pending_count += 1,
522 .success => success_count += 1,
523 .skipped, .skipped_oom => skipped_count += 1,
524 .failure => {
525 failure_count += 1;
526 const compile_errors_len = s.result_error_bundle.errorMessageCount();
527 if (compile_errors_len > 0) {
528 total_compile_errors += compile_errors_len;
529 try compile_error_steps.append(gpa, s);
530 }
531 },
532 }
533 }
534
535 // A proper command line application defaults to silently succeeding.
536 // The user may request verbose mode if they have a different preference.
537 if (failure_count == 0 and run.summary != Summary.all) return cleanExit();
538
539 const ttyconf = run.ttyconf;
540 const stderr = run.stderr;
541
542 if (run.summary != Summary.none) {
543 const total_count = success_count + failure_count + pending_count + skipped_count;
544 ttyconf.setColor(stderr, .cyan) catch {};
545 stderr.writeAll("Build Summary:") catch {};
546 ttyconf.setColor(stderr, .reset) catch {};
547 stderr.writer().print(" {d}/{d} steps succeeded", .{ success_count, total_count }) catch {};
548 if (skipped_count > 0) stderr.writer().print("; {d} skipped", .{skipped_count}) catch {};
549 if (failure_count > 0) stderr.writer().print("; {d} failed", .{failure_count}) catch {};
550
551 if (test_count > 0) stderr.writer().print("; {d}/{d} tests passed", .{ test_pass_count, test_count }) catch {};
552 if (test_skip_count > 0) stderr.writer().print("; {d} skipped", .{test_skip_count}) catch {};
553 if (test_fail_count > 0) stderr.writer().print("; {d} failed", .{test_fail_count}) catch {};
554 if (test_leak_count > 0) stderr.writer().print("; {d} leaked", .{test_leak_count}) catch {};
555
556 if (run.summary == null) {
557 ttyconf.setColor(stderr, .dim) catch {};
558 stderr.writeAll(" (disable with --summary none)") catch {};
559 ttyconf.setColor(stderr, .reset) catch {};
560 }
561 stderr.writeAll("\n") catch {};
562 const failures_only = run.summary != Summary.all;
563
564 // Print a fancy tree with build results.
565 var print_node: PrintNode = .{ .parent = null };
566 if (step_names.len == 0) {
567 print_node.last = true;
568 printTreeStep(b, b.default_step, run, stderr, ttyconf, &print_node, &step_stack, failures_only) catch {};
569 } else {
570 const last_index = if (!failures_only) b.top_level_steps.count() else blk: {
571 var i: usize = step_names.len;
572 while (i > 0) {
573 i -= 1;
574 if (b.top_level_steps.get(step_names[i]).?.step.state != .success) break :blk i;
575 }
576 break :blk b.top_level_steps.count();
577 };
578 for (step_names, 0..) |step_name, i| {
579 const tls = b.top_level_steps.get(step_name).?;
580 print_node.last = i + 1 == last_index;
581 printTreeStep(b, &tls.step, run, stderr, ttyconf, &print_node, &step_stack, failures_only) catch {};
582 }
583 }
584 }
585
586 if (failure_count == 0) return cleanExit();
587
588 // Finally, render compile errors at the bottom of the terminal.
589 // We use a separate compile_error_steps array list because step_stack is destructively
590 // mutated in printTreeStep above.
591 if (run.prominent_compile_errors and total_compile_errors > 0) {
592 for (compile_error_steps.items) |s| {
593 if (s.result_error_bundle.errorMessageCount() > 0) {
594 s.result_error_bundle.renderToStdErr(renderOptions(ttyconf));
595 }
596 }
597
598 // Signal to parent process that we have printed compile errors. The
599 // parent process may choose to omit the "following command failed"
600 // line in this case.
601 process.exit(2);
602 }
603
604 process.exit(1);
605}
606
607const PrintNode = struct {
608 parent: ?*PrintNode,
609 last: bool = false,
610};
611
612fn printPrefix(node: *PrintNode, stderr: File, ttyconf: std.io.tty.Config) !void {
613 const parent = node.parent orelse return;
614 if (parent.parent == null) return;
615 try printPrefix(parent, stderr, ttyconf);
616 if (parent.last) {
617 try stderr.writeAll(" ");
618 } else {
619 try stderr.writeAll(switch (ttyconf) {
620 .no_color, .windows_api => "| ",
621 .escape_codes => "\x1B\x28\x30\x78\x1B\x28\x42 ", // │
622 });
623 }
624}
625
626fn printChildNodePrefix(stderr: File, ttyconf: std.io.tty.Config) !void {
627 try stderr.writeAll(switch (ttyconf) {
628 .no_color, .windows_api => "+- ",
629 .escape_codes => "\x1B\x28\x30\x6d\x71\x1B\x28\x42 ", // └─
630 });
631}
632
633fn printStepStatus(
634 s: *Step,
635 stderr: File,
636 ttyconf: std.io.tty.Config,
637 run: *const Run,
638) !void {
639 switch (s.state) {
640 .precheck_unstarted => unreachable,
641 .precheck_started => unreachable,
642 .precheck_done => unreachable,
643 .running => unreachable,
644
645 .dependency_failure => {
646 try ttyconf.setColor(stderr, .dim);
647 try stderr.writeAll(" transitive failure\n");
648 try ttyconf.setColor(stderr, .reset);
649 },
650
651 .success => {
652 try ttyconf.setColor(stderr, .green);
653 if (s.result_cached) {
654 try stderr.writeAll(" cached");
655 } else if (s.test_results.test_count > 0) {
656 const pass_count = s.test_results.passCount();
657 try stderr.writer().print(" {d} passed", .{pass_count});
658 if (s.test_results.skip_count > 0) {
659 try ttyconf.setColor(stderr, .yellow);
660 try stderr.writer().print(" {d} skipped", .{s.test_results.skip_count});
661 }
662 } else {
663 try stderr.writeAll(" success");
664 }
665 try ttyconf.setColor(stderr, .reset);
666 if (s.result_duration_ns) |ns| {
667 try ttyconf.setColor(stderr, .dim);
668 if (ns >= std.time.ns_per_min) {
669 try stderr.writer().print(" {d}m", .{ns / std.time.ns_per_min});
670 } else if (ns >= std.time.ns_per_s) {
671 try stderr.writer().print(" {d}s", .{ns / std.time.ns_per_s});
672 } else if (ns >= std.time.ns_per_ms) {
673 try stderr.writer().print(" {d}ms", .{ns / std.time.ns_per_ms});
674 } else if (ns >= std.time.ns_per_us) {
675 try stderr.writer().print(" {d}us", .{ns / std.time.ns_per_us});
676 } else {
677 try stderr.writer().print(" {d}ns", .{ns});
678 }
679 try ttyconf.setColor(stderr, .reset);
680 }
681 if (s.result_peak_rss != 0) {
682 const rss = s.result_peak_rss;
683 try ttyconf.setColor(stderr, .dim);
684 if (rss >= 1000_000_000) {
685 try stderr.writer().print(" MaxRSS:{d}G", .{rss / 1000_000_000});
686 } else if (rss >= 1000_000) {
687 try stderr.writer().print(" MaxRSS:{d}M", .{rss / 1000_000});
688 } else if (rss >= 1000) {
689 try stderr.writer().print(" MaxRSS:{d}K", .{rss / 1000});
690 } else {
691 try stderr.writer().print(" MaxRSS:{d}B", .{rss});
692 }
693 try ttyconf.setColor(stderr, .reset);
694 }
695 try stderr.writeAll("\n");
696 },
697 .skipped, .skipped_oom => |skip| {
698 try ttyconf.setColor(stderr, .yellow);
699 try stderr.writeAll(" skipped");
700 if (skip == .skipped_oom) {
701 try stderr.writeAll(" (not enough memory)");
702 try ttyconf.setColor(stderr, .dim);
703 try stderr.writer().print(" upper bound of {d} exceeded runner limit ({d})", .{ s.max_rss, run.max_rss });
704 try ttyconf.setColor(stderr, .yellow);
705 }
706 try stderr.writeAll("\n");
707 try ttyconf.setColor(stderr, .reset);
708 },
709 .failure => try printStepFailure(s, stderr, ttyconf),
710 }
711}
712
713fn printStepFailure(
714 s: *Step,
715 stderr: File,
716 ttyconf: std.io.tty.Config,
717) !void {
718 if (s.result_error_bundle.errorMessageCount() > 0) {
719 try ttyconf.setColor(stderr, .red);
720 try stderr.writer().print(" {d} errors\n", .{
721 s.result_error_bundle.errorMessageCount(),
722 });
723 try ttyconf.setColor(stderr, .reset);
724 } else if (!s.test_results.isSuccess()) {
725 try stderr.writer().print(" {d}/{d} passed", .{
726 s.test_results.passCount(), s.test_results.test_count,
727 });
728 if (s.test_results.fail_count > 0) {
729 try stderr.writeAll(", ");
730 try ttyconf.setColor(stderr, .red);
731 try stderr.writer().print("{d} failed", .{
732 s.test_results.fail_count,
733 });
734 try ttyconf.setColor(stderr, .reset);
735 }
736 if (s.test_results.skip_count > 0) {
737 try stderr.writeAll(", ");
738 try ttyconf.setColor(stderr, .yellow);
739 try stderr.writer().print("{d} skipped", .{
740 s.test_results.skip_count,
741 });
742 try ttyconf.setColor(stderr, .reset);
743 }
744 if (s.test_results.leak_count > 0) {
745 try stderr.writeAll(", ");
746 try ttyconf.setColor(stderr, .red);
747 try stderr.writer().print("{d} leaked", .{
748 s.test_results.leak_count,
749 });
750 try ttyconf.setColor(stderr, .reset);
751 }
752 try stderr.writeAll("\n");
753 } else if (s.result_error_msgs.items.len > 0) {
754 try ttyconf.setColor(stderr, .red);
755 try stderr.writeAll(" failure\n");
756 try ttyconf.setColor(stderr, .reset);
757 } else {
758 assert(s.result_stderr.len > 0);
759 try ttyconf.setColor(stderr, .red);
760 try stderr.writeAll(" stderr\n");
761 try ttyconf.setColor(stderr, .reset);
762 }
763}
764
765fn printTreeStep(
766 b: *std.Build,
767 s: *Step,
768 run: *const Run,
769 stderr: File,
770 ttyconf: std.io.tty.Config,
771 parent_node: *PrintNode,
772 step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),
773 failures_only: bool,
774) !void {
775 const first = step_stack.swapRemove(s);
776 if (failures_only and s.state == .success) return;
777 try printPrefix(parent_node, stderr, ttyconf);
778
779 if (!first) try ttyconf.setColor(stderr, .dim);
780 if (parent_node.parent != null) {
781 if (parent_node.last) {
782 try printChildNodePrefix(stderr, ttyconf);
783 } else {
784 try stderr.writeAll(switch (ttyconf) {
785 .no_color, .windows_api => "+- ",
786 .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─
787 });
788 }
789 }
790
791 // dep_prefix omitted here because it is redundant with the tree.
792 try stderr.writeAll(s.name);
793
794 if (first) {
795 try printStepStatus(s, stderr, ttyconf, run);
796
797 const last_index = if (!failures_only) s.dependencies.items.len -| 1 else blk: {
798 var i: usize = s.dependencies.items.len;
799 while (i > 0) {
800 i -= 1;
801 if (s.dependencies.items[i].state != .success) break :blk i;
802 }
803 break :blk s.dependencies.items.len -| 1;
804 };
805 for (s.dependencies.items, 0..) |dep, i| {
806 var print_node: PrintNode = .{
807 .parent = parent_node,
808 .last = i == last_index,
809 };
810 try printTreeStep(b, dep, run, stderr, ttyconf, &print_node, step_stack, failures_only);
811 }
812 } else {
813 if (s.dependencies.items.len == 0) {
814 try stderr.writeAll(" (reused)\n");
815 } else {
816 try stderr.writer().print(" (+{d} more reused dependencies)\n", .{
817 s.dependencies.items.len,
818 });
819 }
820 try ttyconf.setColor(stderr, .reset);
821 }
822}
823
824/// Traverse the dependency graph depth-first and make it undirected by having
825/// steps know their dependants (they only know dependencies at start).
826/// Along the way, check that there is no dependency loop, and record the steps
827/// in traversal order in `step_stack`.
828/// Each step has its dependencies traversed in random order, this accomplishes
829/// two things:
830/// - `step_stack` will be in randomized-depth-first order, so the build runner
831/// spawns steps in a random (but optimized) order
832/// - each step's `dependants` list is also filled in a random order, so that
833/// when it finishes executing in `workerMakeOneStep`, it spawns next steps
834/// to run in random order
835fn constructGraphAndCheckForDependencyLoop(
836 b: *std.Build,
837 s: *Step,
838 step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),
839 rand: std.Random,
840) !void {
841 switch (s.state) {
842 .precheck_started => {
843 std.debug.print("dependency loop detected:\n {s}\n", .{s.name});
844 return error.DependencyLoopDetected;
845 },
846 .precheck_unstarted => {
847 s.state = .precheck_started;
848
849 try step_stack.ensureUnusedCapacity(b.allocator, s.dependencies.items.len);
850
851 // We dupe to avoid shuffling the steps in the summary, it depends
852 // on s.dependencies' order.
853 const deps = b.allocator.dupe(*Step, s.dependencies.items) catch @panic("OOM");
854 rand.shuffle(*Step, deps);
855
856 for (deps) |dep| {
857 try step_stack.put(b.allocator, dep, {});
858 try dep.dependants.append(b.allocator, s);
859 constructGraphAndCheckForDependencyLoop(b, dep, step_stack, rand) catch |err| {
860 if (err == error.DependencyLoopDetected) {
861 std.debug.print(" {s}\n", .{s.name});
862 }
863 return err;
864 };
865 }
866
867 s.state = .precheck_done;
868 },
869 .precheck_done => {},
870
871 // These don't happen until we actually run the step graph.
872 .dependency_failure => unreachable,
873 .running => unreachable,
874 .success => unreachable,
875 .failure => unreachable,
876 .skipped => unreachable,
877 .skipped_oom => unreachable,
878 }
879}
880
881fn workerMakeOneStep(
882 wg: *std.Thread.WaitGroup,
883 thread_pool: *std.Thread.Pool,
884 b: *std.Build,
885 s: *Step,
886 prog_node: *std.Progress.Node,
887 run: *Run,
888) void {
889 defer wg.finish();
890
891 // First, check the conditions for running this step. If they are not met,
892 // then we return without doing the step, relying on another worker to
893 // queue this step up again when dependencies are met.
894 for (s.dependencies.items) |dep| {
895 switch (@atomicLoad(Step.State, &dep.state, .SeqCst)) {
896 .success, .skipped => continue,
897 .failure, .dependency_failure, .skipped_oom => {
898 @atomicStore(Step.State, &s.state, .dependency_failure, .SeqCst);
899 return;
900 },
901 .precheck_done, .running => {
902 // dependency is not finished yet.
903 return;
904 },
905 .precheck_unstarted => unreachable,
906 .precheck_started => unreachable,
907 }
908 }
909
910 if (s.max_rss != 0) {
911 run.max_rss_mutex.lock();
912 defer run.max_rss_mutex.unlock();
913
914 // Avoid running steps twice.
915 if (s.state != .precheck_done) {
916 // Another worker got the job.
917 return;
918 }
919
920 const new_claimed_rss = run.claimed_rss + s.max_rss;
921 if (new_claimed_rss > run.max_rss) {
922 // Running this step right now could possibly exceed the allotted RSS.
923 // Add this step to the queue of memory-blocked steps.
924 run.memory_blocked_steps.append(s) catch @panic("OOM");
925 return;
926 }
927
928 run.claimed_rss = new_claimed_rss;
929 s.state = .running;
930 } else {
931 // Avoid running steps twice.
932 if (@cmpxchgStrong(Step.State, &s.state, .precheck_done, .running, .SeqCst, .SeqCst) != null) {
933 // Another worker got the job.
934 return;
935 }
936 }
937
938 var sub_prog_node = prog_node.start(s.name, 0);
939 sub_prog_node.activate();
940 defer sub_prog_node.end();
941
942 const make_result = s.make(&sub_prog_node);
943
944 // No matter the result, we want to display error/warning messages.
945 const show_compile_errors = !run.prominent_compile_errors and
946 s.result_error_bundle.errorMessageCount() > 0;
947 const show_error_msgs = s.result_error_msgs.items.len > 0;
948 const show_stderr = s.result_stderr.len > 0;
949
950 if (show_error_msgs or show_compile_errors or show_stderr) {
951 sub_prog_node.context.lock_stderr();
952 defer sub_prog_node.context.unlock_stderr();
953
954 printErrorMessages(b, s, run) catch {};
955 }
956
957 handle_result: {
958 if (make_result) |_| {
959 @atomicStore(Step.State, &s.state, .success, .SeqCst);
960 } else |err| switch (err) {
961 error.MakeFailed => {
962 @atomicStore(Step.State, &s.state, .failure, .SeqCst);
963 break :handle_result;
964 },
965 error.MakeSkipped => @atomicStore(Step.State, &s.state, .skipped, .SeqCst),
966 }
967
968 // Successful completion of a step, so we queue up its dependants as well.
969 for (s.dependants.items) |dep| {
970 wg.start();
971 thread_pool.spawn(workerMakeOneStep, .{
972 wg, thread_pool, b, dep, prog_node, run,
973 }) catch @panic("OOM");
974 }
975 }
976
977 // If this is a step that claims resources, we must now queue up other
978 // steps that are waiting for resources.
979 if (s.max_rss != 0) {
980 run.max_rss_mutex.lock();
981 defer run.max_rss_mutex.unlock();
982
983 // Give the memory back to the scheduler.
984 run.claimed_rss -= s.max_rss;
985 // Avoid kicking off too many tasks that we already know will not have
986 // enough resources.
987 var remaining = run.max_rss - run.claimed_rss;
988 var i: usize = 0;
989 var j: usize = 0;
990 while (j < run.memory_blocked_steps.items.len) : (j += 1) {
991 const dep = run.memory_blocked_steps.items[j];
992 assert(dep.max_rss != 0);
993 if (dep.max_rss <= remaining) {
994 remaining -= dep.max_rss;
995
996 wg.start();
997 thread_pool.spawn(workerMakeOneStep, .{
998 wg, thread_pool, b, dep, prog_node, run,
999 }) catch @panic("OOM");
1000 } else {
1001 run.memory_blocked_steps.items[i] = dep;
1002 i += 1;
1003 }
1004 }
1005 run.memory_blocked_steps.shrinkRetainingCapacity(i);
1006 }
1007}
1008
1009fn printErrorMessages(b: *std.Build, failing_step: *Step, run: *const Run) !void {
1010 const gpa = b.allocator;
1011 const stderr = run.stderr;
1012 const ttyconf = run.ttyconf;
1013
1014 // Provide context for where these error messages are coming from by
1015 // printing the corresponding Step subtree.
1016
1017 var step_stack: std.ArrayListUnmanaged(*Step) = .{};
1018 defer step_stack.deinit(gpa);
1019 try step_stack.append(gpa, failing_step);
1020 while (step_stack.items[step_stack.items.len - 1].dependants.items.len != 0) {
1021 try step_stack.append(gpa, step_stack.items[step_stack.items.len - 1].dependants.items[0]);
1022 }
1023
1024 // Now, `step_stack` has the subtree that we want to print, in reverse order.
1025 try ttyconf.setColor(stderr, .dim);
1026 var indent: usize = 0;
1027 while (step_stack.popOrNull()) |s| : (indent += 1) {
1028 if (indent > 0) {
1029 try stderr.writer().writeByteNTimes(' ', (indent - 1) * 3);
1030 try printChildNodePrefix(stderr, ttyconf);
1031 }
1032
1033 try stderr.writeAll(s.name);
1034
1035 if (s == failing_step) {
1036 try printStepFailure(s, stderr, ttyconf);
1037 } else {
1038 try stderr.writeAll("\n");
1039 }
1040 }
1041 try ttyconf.setColor(stderr, .reset);
1042
1043 if (failing_step.result_stderr.len > 0) {
1044 try stderr.writeAll(failing_step.result_stderr);
1045 if (!mem.endsWith(u8, failing_step.result_stderr, "\n")) {
1046 try stderr.writeAll("\n");
1047 }
1048 }
1049
1050 if (!run.prominent_compile_errors and failing_step.result_error_bundle.errorMessageCount() > 0)
1051 try failing_step.result_error_bundle.renderToWriter(renderOptions(ttyconf), stderr.writer());
1052
1053 for (failing_step.result_error_msgs.items) |msg| {
1054 try ttyconf.setColor(stderr, .red);
1055 try stderr.writeAll("error: ");
1056 try ttyconf.setColor(stderr, .reset);
1057 try stderr.writeAll(msg);
1058 try stderr.writeAll("\n");
1059 }
1060}
1061
1062fn steps(builder: *std.Build, out_stream: anytype) !void {
1063 const allocator = builder.allocator;
1064 for (builder.top_level_steps.values()) |top_level_step| {
1065 const name = if (&top_level_step.step == builder.default_step)
1066 try fmt.allocPrint(allocator, "{s} (default)", .{top_level_step.step.name})
1067 else
1068 top_level_step.step.name;
1069 try out_stream.print(" {s:<28} {s}\n", .{ name, top_level_step.description });
1070 }
1071}
1072
1073fn usage(b: *std.Build, out_stream: anytype) !void {
1074 try out_stream.print(
1075 \\Usage: {s} build [steps] [options]
1076 \\
1077 \\Steps:
1078 \\
1079 , .{b.graph.zig_exe});
1080 try steps(b, out_stream);
1081
1082 try out_stream.writeAll(
1083 \\
1084 \\General Options:
1085 \\ -p, --prefix [path] Where to install files (default: zig-out)
1086 \\ --prefix-lib-dir [path] Where to install libraries
1087 \\ --prefix-exe-dir [path] Where to install executables
1088 \\ --prefix-include-dir [path] Where to install C header files
1089 \\
1090 \\ --release[=mode] Request release mode, optionally specifying a
1091 \\ preferred optimization mode: fast, safe, small
1092 \\
1093 \\ -fdarling, -fno-darling Integration with system-installed Darling to
1094 \\ execute macOS programs on Linux hosts
1095 \\ (default: no)
1096 \\ -fqemu, -fno-qemu Integration with system-installed QEMU to execute
1097 \\ foreign-architecture programs on Linux hosts
1098 \\ (default: no)
1099 \\ --glibc-runtimes [path] Enhances QEMU integration by providing glibc built
1100 \\ for multiple foreign architectures, allowing
1101 \\ execution of non-native programs that link with glibc.
1102 \\ -frosetta, -fno-rosetta Rely on Rosetta to execute x86_64 programs on
1103 \\ ARM64 macOS hosts. (default: no)
1104 \\ -fwasmtime, -fno-wasmtime Integration with system-installed wasmtime to
1105 \\ execute WASI binaries. (default: no)
1106 \\ -fwine, -fno-wine Integration with system-installed Wine to execute
1107 \\ Windows programs on Linux hosts. (default: no)
1108 \\
1109 \\ -h, --help Print this help and exit
1110 \\ -l, --list-steps Print available steps
1111 \\ --verbose Print commands before executing them
1112 \\ --color [auto|off|on] Enable or disable colored error messages
1113 \\ --prominent-compile-errors Buffer compile errors and display at end
1114 \\ --summary [mode] Control the printing of the build summary
1115 \\ all Print the build summary in its entirety
1116 \\ failures (Default) Only print failed steps
1117 \\ none Do not print the build summary
1118 \\ -j<N> Limit concurrent jobs (default is to use all CPU cores)
1119 \\ --maxrss <bytes> Limit memory usage (default is to use available memory)
1120 \\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss
1121 \\ --fetch Exit after fetching dependency tree
1122 \\
1123 \\Project-Specific Options:
1124 \\
1125 );
1126
1127 const arena = b.allocator;
1128 if (b.available_options_list.items.len == 0) {
1129 try out_stream.print(" (none)\n", .{});
1130 } else {
1131 for (b.available_options_list.items) |option| {
1132 const name = try fmt.allocPrint(arena, " -D{s}=[{s}]", .{
1133 option.name,
1134 @tagName(option.type_id),
1135 });
1136 try out_stream.print("{s:<30} {s}\n", .{ name, option.description });
1137 if (option.enum_options) |enum_options| {
1138 const padding = " " ** 33;
1139 try out_stream.writeAll(padding ++ "Supported Values:\n");
1140 for (enum_options) |enum_option| {
1141 try out_stream.print(padding ++ " {s}\n", .{enum_option});
1142 }
1143 }
1144 }
1145 }
1146
1147 try out_stream.writeAll(
1148 \\
1149 \\System Integration Options:
1150 \\ --search-prefix [path] Add a path to look for binaries, libraries, headers
1151 \\ --sysroot [path] Set the system root directory (usually /)
1152 \\ --libc [file] Provide a file which specifies libc paths
1153 \\
1154 \\ --host-target [triple] Use the provided target as the host
1155 \\ --host-cpu [cpu] Use the provided CPU as the host
1156 \\ --host-dynamic-linker [path] Use the provided dynamic linker as the host
1157 \\
1158 \\ --system [pkgdir] Disable package fetching; enable all integrations
1159 \\ -fsys=[name] Enable a system integration
1160 \\ -fno-sys=[name] Disable a system integration
1161 \\
1162 \\ Available System Integrations: Enabled:
1163 \\
1164 );
1165 if (b.graph.system_library_options.entries.len == 0) {
1166 try out_stream.writeAll(" (none) -\n");
1167 } else {
1168 for (b.graph.system_library_options.keys(), b.graph.system_library_options.values()) |k, v| {
1169 const status = switch (v) {
1170 .declared_enabled => "yes",
1171 .declared_disabled => "no",
1172 .user_enabled, .user_disabled => unreachable, // already emitted error
1173 };
1174 try out_stream.print(" {s:<43} {s}\n", .{ k, status });
1175 }
1176 }
1177
1178 try out_stream.writeAll(
1179 \\
1180 \\Advanced Options:
1181 \\ -freference-trace[=num] How many lines of reference trace should be shown per compile error
1182 \\ -fno-reference-trace Disable reference trace
1183 \\ --build-file [file] Override path to build.zig
1184 \\ --cache-dir [path] Override path to local Zig cache directory
1185 \\ --global-cache-dir [path] Override path to global Zig cache directory
1186 \\ --zig-lib-dir [arg] Override path to Zig lib directory
1187 \\ --build-runner [file] Override path to build runner
1188 \\ --seed [integer] For shuffling dependency traversal order (default: random)
1189 \\ --debug-log [scope] Enable debugging the compiler
1190 \\ --debug-pkg-config Fail if unknown pkg-config flags encountered
1191 \\ --verbose-link Enable compiler debug output for linking
1192 \\ --verbose-air Enable compiler debug output for Zig AIR
1193 \\ --verbose-llvm-ir[=file] Enable compiler debug output for LLVM IR
1194 \\ --verbose-llvm-bc=[file] Enable compiler debug output for LLVM BC
1195 \\ --verbose-cimport Enable compiler debug output for C imports
1196 \\ --verbose-cc Enable compiler debug output for C compilation
1197 \\ --verbose-llvm-cpu-features Enable compiler debug output for LLVM CPU features
1198 \\
1199 );
1200}
1201
1202fn nextArg(args: [][:0]const u8, idx: *usize) ?[:0]const u8 {
1203 if (idx.* >= args.len) return null;
1204 defer idx.* += 1;
1205 return args[idx.*];
1206}
1207
1208fn nextArgOrFatal(args: [][:0]const u8, idx: *usize) [:0]const u8 {
1209 return nextArg(args, idx) orelse {
1210 std.debug.print("expected argument after '{s}'\n access the help menu with 'zig build -h'\n", .{args[idx.*]});
1211 process.exit(1);
1212 };
1213}
1214
1215fn argsRest(args: [][:0]const u8, idx: usize) ?[][:0]const u8 {
1216 if (idx >= args.len) return null;
1217 return args[idx..];
1218}
1219
1220fn cleanExit() void {
1221 // Perhaps in the future there could be an Advanced Options flag such as
1222 // --debug-build-runner-leaks which would make this function return instead
1223 // of calling exit.
1224 process.exit(0);
1225}
1226
1227const Color = enum { auto, off, on };
1228const Summary = enum { all, failures, none };
1229
1230fn get_tty_conf(color: Color, stderr: File) std.io.tty.Config {
1231 return switch (color) {
1232 .auto => std.io.tty.detectConfig(stderr),
1233 .on => .escape_codes,
1234 .off => .no_color,
1235 };
1236}
1237
1238fn renderOptions(ttyconf: std.io.tty.Config) std.zig.ErrorBundle.RenderOptions {
1239 return .{
1240 .ttyconf = ttyconf,
1241 .include_source_line = ttyconf != .no_color,
1242 .include_reference_trace = ttyconf != .no_color,
1243 };
1244}
1245
1246fn fatalWithHint(comptime f: []const u8, args: anytype) noreturn {
1247 std.debug.print(f ++ "\n access the help menu with 'zig build -h'\n", args);
1248 process.exit(1);
1249}
1250
1251fn fatal(comptime f: []const u8, args: anytype) noreturn {
1252 std.debug.print(f ++ "\n", args);
1253 process.exit(1);
1254}
1255
1256fn validateSystemLibraryOptions(b: *std.Build) void {
1257 var bad = false;
1258 for (b.graph.system_library_options.keys(), b.graph.system_library_options.values()) |k, v| {
1259 switch (v) {
1260 .user_disabled, .user_enabled => {
1261 // The user tried to enable or disable a system library integration, but
1262 // the build script did not recognize that option.
1263 std.debug.print("system library name not recognized by build script: '{s}'\n", .{k});
1264 bad = true;
1265 },
1266 .declared_disabled, .declared_enabled => {},
1267 }
1268 }
1269 if (bad) {
1270 std.debug.print(" access the help menu with 'zig build -h'\n", .{});
1271 process.exit(1);
1272 }
1273}
src/main.zig-1
...@@ -5388,7 +5388,6 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -5388,7 +5388,6 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
5388 } else .{5388 } else .{
5389 .root = .{5389 .root = .{
5390 .root_dir = zig_lib_directory,5390 .root_dir = zig_lib_directory,
5391 .sub_path = "compiler",
5392 },5391 },
5393 .root_src_path = "build_runner.zig",5392 .root_src_path = "build_runner.zig",
5394 };5393 };