authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2024-03-17 03:06:39+01:00
committergravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2024-03-17 03:06:39+01:00
loge646e0116196c2bd9668317366f5380f08c30e6e
treecb9f8e7144849d721e7f28f91df055bbe0cc0e91
parentd10c52c194a093f58df40bc6122f24380f0cc097

Revert "back out the build_runner.zig moving change"

This reverts commit 1a01151a4e1e83826d6911c929210aabcaed36e9 in preparation for a zig1.wasm update.

3 files changed, 1294 insertions(+), 1293 deletions(-)

lib/build_runner.zig deleted-1293
...@@ -1,1293 +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|new|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 const failures_only = run.summary != .all and run.summary != .new;
538 if (failure_count == 0 and failures_only) return cleanExit();
539
540 const ttyconf = run.ttyconf;
541 const stderr = run.stderr;
542
543 if (run.summary != Summary.none) {
544 const total_count = success_count + failure_count + pending_count + skipped_count;
545 ttyconf.setColor(stderr, .cyan) catch {};
546 stderr.writeAll("Build Summary:") catch {};
547 ttyconf.setColor(stderr, .reset) catch {};
548 stderr.writer().print(" {d}/{d} steps succeeded", .{ success_count, total_count }) catch {};
549 if (skipped_count > 0) stderr.writer().print("; {d} skipped", .{skipped_count}) catch {};
550 if (failure_count > 0) stderr.writer().print("; {d} failed", .{failure_count}) catch {};
551
552 if (test_count > 0) stderr.writer().print("; {d}/{d} tests passed", .{ test_pass_count, test_count }) catch {};
553 if (test_skip_count > 0) stderr.writer().print("; {d} skipped", .{test_skip_count}) catch {};
554 if (test_fail_count > 0) stderr.writer().print("; {d} failed", .{test_fail_count}) catch {};
555 if (test_leak_count > 0) stderr.writer().print("; {d} leaked", .{test_leak_count}) catch {};
556
557 if (run.summary == null) {
558 ttyconf.setColor(stderr, .dim) catch {};
559 stderr.writeAll(" (disable with --summary none)") catch {};
560 ttyconf.setColor(stderr, .reset) catch {};
561 }
562 stderr.writeAll("\n") catch {};
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) catch {};
569 } else {
570 const last_index = if (run.summary == .all) b.top_level_steps.count() else blk: {
571 var i: usize = step_names.len;
572 while (i > 0) {
573 i -= 1;
574 const step = b.top_level_steps.get(step_names[i]).?.step;
575 const found = switch (run.summary orelse .failures) {
576 .all, .none => unreachable,
577 .failures => step.state != .success,
578 .new => !step.result_cached,
579 };
580 if (found) break :blk i;
581 }
582 break :blk b.top_level_steps.count();
583 };
584 for (step_names, 0..) |step_name, i| {
585 const tls = b.top_level_steps.get(step_name).?;
586 print_node.last = i + 1 == last_index;
587 printTreeStep(b, &tls.step, run, stderr, ttyconf, &print_node, &step_stack) catch {};
588 }
589 }
590 }
591
592 if (failure_count == 0) return cleanExit();
593
594 // Finally, render compile errors at the bottom of the terminal.
595 // We use a separate compile_error_steps array list because step_stack is destructively
596 // mutated in printTreeStep above.
597 if (run.prominent_compile_errors and total_compile_errors > 0) {
598 for (compile_error_steps.items) |s| {
599 if (s.result_error_bundle.errorMessageCount() > 0) {
600 s.result_error_bundle.renderToStdErr(renderOptions(ttyconf));
601 }
602 }
603
604 // Signal to parent process that we have printed compile errors. The
605 // parent process may choose to omit the "following command failed"
606 // line in this case.
607 process.exit(2);
608 }
609
610 process.exit(1);
611}
612
613const PrintNode = struct {
614 parent: ?*PrintNode,
615 last: bool = false,
616};
617
618fn printPrefix(node: *PrintNode, stderr: File, ttyconf: std.io.tty.Config) !void {
619 const parent = node.parent orelse return;
620 if (parent.parent == null) return;
621 try printPrefix(parent, stderr, ttyconf);
622 if (parent.last) {
623 try stderr.writeAll(" ");
624 } else {
625 try stderr.writeAll(switch (ttyconf) {
626 .no_color, .windows_api => "| ",
627 .escape_codes => "\x1B\x28\x30\x78\x1B\x28\x42 ", // │
628 });
629 }
630}
631
632fn printChildNodePrefix(stderr: File, ttyconf: std.io.tty.Config) !void {
633 try stderr.writeAll(switch (ttyconf) {
634 .no_color, .windows_api => "+- ",
635 .escape_codes => "\x1B\x28\x30\x6d\x71\x1B\x28\x42 ", // └─
636 });
637}
638
639fn printStepStatus(
640 s: *Step,
641 stderr: File,
642 ttyconf: std.io.tty.Config,
643 run: *const Run,
644) !void {
645 switch (s.state) {
646 .precheck_unstarted => unreachable,
647 .precheck_started => unreachable,
648 .precheck_done => unreachable,
649 .running => unreachable,
650
651 .dependency_failure => {
652 try ttyconf.setColor(stderr, .dim);
653 try stderr.writeAll(" transitive failure\n");
654 try ttyconf.setColor(stderr, .reset);
655 },
656
657 .success => {
658 try ttyconf.setColor(stderr, .green);
659 if (s.result_cached) {
660 try stderr.writeAll(" cached");
661 } else if (s.test_results.test_count > 0) {
662 const pass_count = s.test_results.passCount();
663 try stderr.writer().print(" {d} passed", .{pass_count});
664 if (s.test_results.skip_count > 0) {
665 try ttyconf.setColor(stderr, .yellow);
666 try stderr.writer().print(" {d} skipped", .{s.test_results.skip_count});
667 }
668 } else {
669 try stderr.writeAll(" success");
670 }
671 try ttyconf.setColor(stderr, .reset);
672 if (s.result_duration_ns) |ns| {
673 try ttyconf.setColor(stderr, .dim);
674 if (ns >= std.time.ns_per_min) {
675 try stderr.writer().print(" {d}m", .{ns / std.time.ns_per_min});
676 } else if (ns >= std.time.ns_per_s) {
677 try stderr.writer().print(" {d}s", .{ns / std.time.ns_per_s});
678 } else if (ns >= std.time.ns_per_ms) {
679 try stderr.writer().print(" {d}ms", .{ns / std.time.ns_per_ms});
680 } else if (ns >= std.time.ns_per_us) {
681 try stderr.writer().print(" {d}us", .{ns / std.time.ns_per_us});
682 } else {
683 try stderr.writer().print(" {d}ns", .{ns});
684 }
685 try ttyconf.setColor(stderr, .reset);
686 }
687 if (s.result_peak_rss != 0) {
688 const rss = s.result_peak_rss;
689 try ttyconf.setColor(stderr, .dim);
690 if (rss >= 1000_000_000) {
691 try stderr.writer().print(" MaxRSS:{d}G", .{rss / 1000_000_000});
692 } else if (rss >= 1000_000) {
693 try stderr.writer().print(" MaxRSS:{d}M", .{rss / 1000_000});
694 } else if (rss >= 1000) {
695 try stderr.writer().print(" MaxRSS:{d}K", .{rss / 1000});
696 } else {
697 try stderr.writer().print(" MaxRSS:{d}B", .{rss});
698 }
699 try ttyconf.setColor(stderr, .reset);
700 }
701 try stderr.writeAll("\n");
702 },
703 .skipped, .skipped_oom => |skip| {
704 try ttyconf.setColor(stderr, .yellow);
705 try stderr.writeAll(" skipped");
706 if (skip == .skipped_oom) {
707 try stderr.writeAll(" (not enough memory)");
708 try ttyconf.setColor(stderr, .dim);
709 try stderr.writer().print(" upper bound of {d} exceeded runner limit ({d})", .{ s.max_rss, run.max_rss });
710 try ttyconf.setColor(stderr, .yellow);
711 }
712 try stderr.writeAll("\n");
713 try ttyconf.setColor(stderr, .reset);
714 },
715 .failure => try printStepFailure(s, stderr, ttyconf),
716 }
717}
718
719fn printStepFailure(
720 s: *Step,
721 stderr: File,
722 ttyconf: std.io.tty.Config,
723) !void {
724 if (s.result_error_bundle.errorMessageCount() > 0) {
725 try ttyconf.setColor(stderr, .red);
726 try stderr.writer().print(" {d} errors\n", .{
727 s.result_error_bundle.errorMessageCount(),
728 });
729 try ttyconf.setColor(stderr, .reset);
730 } else if (!s.test_results.isSuccess()) {
731 try stderr.writer().print(" {d}/{d} passed", .{
732 s.test_results.passCount(), s.test_results.test_count,
733 });
734 if (s.test_results.fail_count > 0) {
735 try stderr.writeAll(", ");
736 try ttyconf.setColor(stderr, .red);
737 try stderr.writer().print("{d} failed", .{
738 s.test_results.fail_count,
739 });
740 try ttyconf.setColor(stderr, .reset);
741 }
742 if (s.test_results.skip_count > 0) {
743 try stderr.writeAll(", ");
744 try ttyconf.setColor(stderr, .yellow);
745 try stderr.writer().print("{d} skipped", .{
746 s.test_results.skip_count,
747 });
748 try ttyconf.setColor(stderr, .reset);
749 }
750 if (s.test_results.leak_count > 0) {
751 try stderr.writeAll(", ");
752 try ttyconf.setColor(stderr, .red);
753 try stderr.writer().print("{d} leaked", .{
754 s.test_results.leak_count,
755 });
756 try ttyconf.setColor(stderr, .reset);
757 }
758 try stderr.writeAll("\n");
759 } else if (s.result_error_msgs.items.len > 0) {
760 try ttyconf.setColor(stderr, .red);
761 try stderr.writeAll(" failure\n");
762 try ttyconf.setColor(stderr, .reset);
763 } else {
764 assert(s.result_stderr.len > 0);
765 try ttyconf.setColor(stderr, .red);
766 try stderr.writeAll(" stderr\n");
767 try ttyconf.setColor(stderr, .reset);
768 }
769}
770
771fn printTreeStep(
772 b: *std.Build,
773 s: *Step,
774 run: *const Run,
775 stderr: File,
776 ttyconf: std.io.tty.Config,
777 parent_node: *PrintNode,
778 step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),
779) !void {
780 const first = step_stack.swapRemove(s);
781 const summary = run.summary orelse .failures;
782 const skip = switch (summary) {
783 .none => unreachable,
784 .all => false,
785 .new => s.result_cached,
786 .failures => s.state == .success,
787 };
788 if (skip) return;
789 try printPrefix(parent_node, stderr, ttyconf);
790
791 if (!first) try ttyconf.setColor(stderr, .dim);
792 if (parent_node.parent != null) {
793 if (parent_node.last) {
794 try printChildNodePrefix(stderr, ttyconf);
795 } else {
796 try stderr.writeAll(switch (ttyconf) {
797 .no_color, .windows_api => "+- ",
798 .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─
799 });
800 }
801 }
802
803 // dep_prefix omitted here because it is redundant with the tree.
804 try stderr.writeAll(s.name);
805
806 if (first) {
807 try printStepStatus(s, stderr, ttyconf, run);
808
809 const last_index = if (summary == .all) s.dependencies.items.len -| 1 else blk: {
810 var i: usize = s.dependencies.items.len;
811 while (i > 0) {
812 i -= 1;
813
814 const step = s.dependencies.items[i];
815 const found = switch (summary) {
816 .all, .none => unreachable,
817 .failures => step.state != .success,
818 .new => !step.result_cached,
819 };
820 if (found) break :blk i;
821 }
822 break :blk s.dependencies.items.len -| 1;
823 };
824 for (s.dependencies.items, 0..) |dep, i| {
825 var print_node: PrintNode = .{
826 .parent = parent_node,
827 .last = i == last_index,
828 };
829 try printTreeStep(b, dep, run, stderr, ttyconf, &print_node, step_stack);
830 }
831 } else {
832 if (s.dependencies.items.len == 0) {
833 try stderr.writeAll(" (reused)\n");
834 } else {
835 try stderr.writer().print(" (+{d} more reused dependencies)\n", .{
836 s.dependencies.items.len,
837 });
838 }
839 try ttyconf.setColor(stderr, .reset);
840 }
841}
842
843/// Traverse the dependency graph depth-first and make it undirected by having
844/// steps know their dependants (they only know dependencies at start).
845/// Along the way, check that there is no dependency loop, and record the steps
846/// in traversal order in `step_stack`.
847/// Each step has its dependencies traversed in random order, this accomplishes
848/// two things:
849/// - `step_stack` will be in randomized-depth-first order, so the build runner
850/// spawns steps in a random (but optimized) order
851/// - each step's `dependants` list is also filled in a random order, so that
852/// when it finishes executing in `workerMakeOneStep`, it spawns next steps
853/// to run in random order
854fn constructGraphAndCheckForDependencyLoop(
855 b: *std.Build,
856 s: *Step,
857 step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),
858 rand: std.Random,
859) !void {
860 switch (s.state) {
861 .precheck_started => {
862 std.debug.print("dependency loop detected:\n {s}\n", .{s.name});
863 return error.DependencyLoopDetected;
864 },
865 .precheck_unstarted => {
866 s.state = .precheck_started;
867
868 try step_stack.ensureUnusedCapacity(b.allocator, s.dependencies.items.len);
869
870 // We dupe to avoid shuffling the steps in the summary, it depends
871 // on s.dependencies' order.
872 const deps = b.allocator.dupe(*Step, s.dependencies.items) catch @panic("OOM");
873 rand.shuffle(*Step, deps);
874
875 for (deps) |dep| {
876 try step_stack.put(b.allocator, dep, {});
877 try dep.dependants.append(b.allocator, s);
878 constructGraphAndCheckForDependencyLoop(b, dep, step_stack, rand) catch |err| {
879 if (err == error.DependencyLoopDetected) {
880 std.debug.print(" {s}\n", .{s.name});
881 }
882 return err;
883 };
884 }
885
886 s.state = .precheck_done;
887 },
888 .precheck_done => {},
889
890 // These don't happen until we actually run the step graph.
891 .dependency_failure => unreachable,
892 .running => unreachable,
893 .success => unreachable,
894 .failure => unreachable,
895 .skipped => unreachable,
896 .skipped_oom => unreachable,
897 }
898}
899
900fn workerMakeOneStep(
901 wg: *std.Thread.WaitGroup,
902 thread_pool: *std.Thread.Pool,
903 b: *std.Build,
904 s: *Step,
905 prog_node: *std.Progress.Node,
906 run: *Run,
907) void {
908 defer wg.finish();
909
910 // First, check the conditions for running this step. If they are not met,
911 // then we return without doing the step, relying on another worker to
912 // queue this step up again when dependencies are met.
913 for (s.dependencies.items) |dep| {
914 switch (@atomicLoad(Step.State, &dep.state, .seq_cst)) {
915 .success, .skipped => continue,
916 .failure, .dependency_failure, .skipped_oom => {
917 @atomicStore(Step.State, &s.state, .dependency_failure, .seq_cst);
918 return;
919 },
920 .precheck_done, .running => {
921 // dependency is not finished yet.
922 return;
923 },
924 .precheck_unstarted => unreachable,
925 .precheck_started => unreachable,
926 }
927 }
928
929 if (s.max_rss != 0) {
930 run.max_rss_mutex.lock();
931 defer run.max_rss_mutex.unlock();
932
933 // Avoid running steps twice.
934 if (s.state != .precheck_done) {
935 // Another worker got the job.
936 return;
937 }
938
939 const new_claimed_rss = run.claimed_rss + s.max_rss;
940 if (new_claimed_rss > run.max_rss) {
941 // Running this step right now could possibly exceed the allotted RSS.
942 // Add this step to the queue of memory-blocked steps.
943 run.memory_blocked_steps.append(s) catch @panic("OOM");
944 return;
945 }
946
947 run.claimed_rss = new_claimed_rss;
948 s.state = .running;
949 } else {
950 // Avoid running steps twice.
951 if (@cmpxchgStrong(Step.State, &s.state, .precheck_done, .running, .seq_cst, .seq_cst) != null) {
952 // Another worker got the job.
953 return;
954 }
955 }
956
957 var sub_prog_node = prog_node.start(s.name, 0);
958 sub_prog_node.activate();
959 defer sub_prog_node.end();
960
961 const make_result = s.make(&sub_prog_node);
962
963 // No matter the result, we want to display error/warning messages.
964 const show_compile_errors = !run.prominent_compile_errors and
965 s.result_error_bundle.errorMessageCount() > 0;
966 const show_error_msgs = s.result_error_msgs.items.len > 0;
967 const show_stderr = s.result_stderr.len > 0;
968
969 if (show_error_msgs or show_compile_errors or show_stderr) {
970 sub_prog_node.context.lock_stderr();
971 defer sub_prog_node.context.unlock_stderr();
972
973 printErrorMessages(b, s, run) catch {};
974 }
975
976 handle_result: {
977 if (make_result) |_| {
978 @atomicStore(Step.State, &s.state, .success, .seq_cst);
979 } else |err| switch (err) {
980 error.MakeFailed => {
981 @atomicStore(Step.State, &s.state, .failure, .seq_cst);
982 break :handle_result;
983 },
984 error.MakeSkipped => @atomicStore(Step.State, &s.state, .skipped, .seq_cst),
985 }
986
987 // Successful completion of a step, so we queue up its dependants as well.
988 for (s.dependants.items) |dep| {
989 wg.start();
990 thread_pool.spawn(workerMakeOneStep, .{
991 wg, thread_pool, b, dep, prog_node, run,
992 }) catch @panic("OOM");
993 }
994 }
995
996 // If this is a step that claims resources, we must now queue up other
997 // steps that are waiting for resources.
998 if (s.max_rss != 0) {
999 run.max_rss_mutex.lock();
1000 defer run.max_rss_mutex.unlock();
1001
1002 // Give the memory back to the scheduler.
1003 run.claimed_rss -= s.max_rss;
1004 // Avoid kicking off too many tasks that we already know will not have
1005 // enough resources.
1006 var remaining = run.max_rss - run.claimed_rss;
1007 var i: usize = 0;
1008 var j: usize = 0;
1009 while (j < run.memory_blocked_steps.items.len) : (j += 1) {
1010 const dep = run.memory_blocked_steps.items[j];
1011 assert(dep.max_rss != 0);
1012 if (dep.max_rss <= remaining) {
1013 remaining -= dep.max_rss;
1014
1015 wg.start();
1016 thread_pool.spawn(workerMakeOneStep, .{
1017 wg, thread_pool, b, dep, prog_node, run,
1018 }) catch @panic("OOM");
1019 } else {
1020 run.memory_blocked_steps.items[i] = dep;
1021 i += 1;
1022 }
1023 }
1024 run.memory_blocked_steps.shrinkRetainingCapacity(i);
1025 }
1026}
1027
1028fn printErrorMessages(b: *std.Build, failing_step: *Step, run: *const Run) !void {
1029 const gpa = b.allocator;
1030 const stderr = run.stderr;
1031 const ttyconf = run.ttyconf;
1032
1033 // Provide context for where these error messages are coming from by
1034 // printing the corresponding Step subtree.
1035
1036 var step_stack: std.ArrayListUnmanaged(*Step) = .{};
1037 defer step_stack.deinit(gpa);
1038 try step_stack.append(gpa, failing_step);
1039 while (step_stack.items[step_stack.items.len - 1].dependants.items.len != 0) {
1040 try step_stack.append(gpa, step_stack.items[step_stack.items.len - 1].dependants.items[0]);
1041 }
1042
1043 // Now, `step_stack` has the subtree that we want to print, in reverse order.
1044 try ttyconf.setColor(stderr, .dim);
1045 var indent: usize = 0;
1046 while (step_stack.popOrNull()) |s| : (indent += 1) {
1047 if (indent > 0) {
1048 try stderr.writer().writeByteNTimes(' ', (indent - 1) * 3);
1049 try printChildNodePrefix(stderr, ttyconf);
1050 }
1051
1052 try stderr.writeAll(s.name);
1053
1054 if (s == failing_step) {
1055 try printStepFailure(s, stderr, ttyconf);
1056 } else {
1057 try stderr.writeAll("\n");
1058 }
1059 }
1060 try ttyconf.setColor(stderr, .reset);
1061
1062 if (failing_step.result_stderr.len > 0) {
1063 try stderr.writeAll(failing_step.result_stderr);
1064 if (!mem.endsWith(u8, failing_step.result_stderr, "\n")) {
1065 try stderr.writeAll("\n");
1066 }
1067 }
1068
1069 if (!run.prominent_compile_errors and failing_step.result_error_bundle.errorMessageCount() > 0)
1070 try failing_step.result_error_bundle.renderToWriter(renderOptions(ttyconf), stderr.writer());
1071
1072 for (failing_step.result_error_msgs.items) |msg| {
1073 try ttyconf.setColor(stderr, .red);
1074 try stderr.writeAll("error: ");
1075 try ttyconf.setColor(stderr, .reset);
1076 try stderr.writeAll(msg);
1077 try stderr.writeAll("\n");
1078 }
1079}
1080
1081fn steps(builder: *std.Build, out_stream: anytype) !void {
1082 const allocator = builder.allocator;
1083 for (builder.top_level_steps.values()) |top_level_step| {
1084 const name = if (&top_level_step.step == builder.default_step)
1085 try fmt.allocPrint(allocator, "{s} (default)", .{top_level_step.step.name})
1086 else
1087 top_level_step.step.name;
1088 try out_stream.print(" {s:<28} {s}\n", .{ name, top_level_step.description });
1089 }
1090}
1091
1092fn usage(b: *std.Build, out_stream: anytype) !void {
1093 try out_stream.print(
1094 \\Usage: {s} build [steps] [options]
1095 \\
1096 \\Steps:
1097 \\
1098 , .{b.graph.zig_exe});
1099 try steps(b, out_stream);
1100
1101 try out_stream.writeAll(
1102 \\
1103 \\General Options:
1104 \\ -p, --prefix [path] Where to install files (default: zig-out)
1105 \\ --prefix-lib-dir [path] Where to install libraries
1106 \\ --prefix-exe-dir [path] Where to install executables
1107 \\ --prefix-include-dir [path] Where to install C header files
1108 \\
1109 \\ --release[=mode] Request release mode, optionally specifying a
1110 \\ preferred optimization mode: fast, safe, small
1111 \\
1112 \\ -fdarling, -fno-darling Integration with system-installed Darling to
1113 \\ execute macOS programs on Linux hosts
1114 \\ (default: no)
1115 \\ -fqemu, -fno-qemu Integration with system-installed QEMU to execute
1116 \\ foreign-architecture programs on Linux hosts
1117 \\ (default: no)
1118 \\ --glibc-runtimes [path] Enhances QEMU integration by providing glibc built
1119 \\ for multiple foreign architectures, allowing
1120 \\ execution of non-native programs that link with glibc.
1121 \\ -frosetta, -fno-rosetta Rely on Rosetta to execute x86_64 programs on
1122 \\ ARM64 macOS hosts. (default: no)
1123 \\ -fwasmtime, -fno-wasmtime Integration with system-installed wasmtime to
1124 \\ execute WASI binaries. (default: no)
1125 \\ -fwine, -fno-wine Integration with system-installed Wine to execute
1126 \\ Windows programs on Linux hosts. (default: no)
1127 \\
1128 \\ -h, --help Print this help and exit
1129 \\ -l, --list-steps Print available steps
1130 \\ --verbose Print commands before executing them
1131 \\ --color [auto|off|on] Enable or disable colored error messages
1132 \\ --prominent-compile-errors Buffer compile errors and display at end
1133 \\ --summary [mode] Control the printing of the build summary
1134 \\ all Print the build summary in its entirety
1135 \\ new Omit cached steps
1136 \\ failures (Default) Only print failed steps
1137 \\ none Do not print the build summary
1138 \\ -j<N> Limit concurrent jobs (default is to use all CPU cores)
1139 \\ --maxrss <bytes> Limit memory usage (default is to use available memory)
1140 \\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss
1141 \\ --fetch Exit after fetching dependency tree
1142 \\
1143 \\Project-Specific Options:
1144 \\
1145 );
1146
1147 const arena = b.allocator;
1148 if (b.available_options_list.items.len == 0) {
1149 try out_stream.print(" (none)\n", .{});
1150 } else {
1151 for (b.available_options_list.items) |option| {
1152 const name = try fmt.allocPrint(arena, " -D{s}=[{s}]", .{
1153 option.name,
1154 @tagName(option.type_id),
1155 });
1156 try out_stream.print("{s:<30} {s}\n", .{ name, option.description });
1157 if (option.enum_options) |enum_options| {
1158 const padding = " " ** 33;
1159 try out_stream.writeAll(padding ++ "Supported Values:\n");
1160 for (enum_options) |enum_option| {
1161 try out_stream.print(padding ++ " {s}\n", .{enum_option});
1162 }
1163 }
1164 }
1165 }
1166
1167 try out_stream.writeAll(
1168 \\
1169 \\System Integration Options:
1170 \\ --search-prefix [path] Add a path to look for binaries, libraries, headers
1171 \\ --sysroot [path] Set the system root directory (usually /)
1172 \\ --libc [file] Provide a file which specifies libc paths
1173 \\
1174 \\ --host-target [triple] Use the provided target as the host
1175 \\ --host-cpu [cpu] Use the provided CPU as the host
1176 \\ --host-dynamic-linker [path] Use the provided dynamic linker as the host
1177 \\
1178 \\ --system [pkgdir] Disable package fetching; enable all integrations
1179 \\ -fsys=[name] Enable a system integration
1180 \\ -fno-sys=[name] Disable a system integration
1181 \\
1182 \\ Available System Integrations: Enabled:
1183 \\
1184 );
1185 if (b.graph.system_library_options.entries.len == 0) {
1186 try out_stream.writeAll(" (none) -\n");
1187 } else {
1188 for (b.graph.system_library_options.keys(), b.graph.system_library_options.values()) |k, v| {
1189 const status = switch (v) {
1190 .declared_enabled => "yes",
1191 .declared_disabled => "no",
1192 .user_enabled, .user_disabled => unreachable, // already emitted error
1193 };
1194 try out_stream.print(" {s:<43} {s}\n", .{ k, status });
1195 }
1196 }
1197
1198 try out_stream.writeAll(
1199 \\
1200 \\Advanced Options:
1201 \\ -freference-trace[=num] How many lines of reference trace should be shown per compile error
1202 \\ -fno-reference-trace Disable reference trace
1203 \\ --build-file [file] Override path to build.zig
1204 \\ --cache-dir [path] Override path to local Zig cache directory
1205 \\ --global-cache-dir [path] Override path to global Zig cache directory
1206 \\ --zig-lib-dir [arg] Override path to Zig lib directory
1207 \\ --build-runner [file] Override path to build runner
1208 \\ --seed [integer] For shuffling dependency traversal order (default: random)
1209 \\ --debug-log [scope] Enable debugging the compiler
1210 \\ --debug-pkg-config Fail if unknown pkg-config flags encountered
1211 \\ --verbose-link Enable compiler debug output for linking
1212 \\ --verbose-air Enable compiler debug output for Zig AIR
1213 \\ --verbose-llvm-ir[=file] Enable compiler debug output for LLVM IR
1214 \\ --verbose-llvm-bc=[file] Enable compiler debug output for LLVM BC
1215 \\ --verbose-cimport Enable compiler debug output for C imports
1216 \\ --verbose-cc Enable compiler debug output for C compilation
1217 \\ --verbose-llvm-cpu-features Enable compiler debug output for LLVM CPU features
1218 \\
1219 );
1220}
1221
1222fn nextArg(args: [][:0]const u8, idx: *usize) ?[:0]const u8 {
1223 if (idx.* >= args.len) return null;
1224 defer idx.* += 1;
1225 return args[idx.*];
1226}
1227
1228fn nextArgOrFatal(args: [][:0]const u8, idx: *usize) [:0]const u8 {
1229 return nextArg(args, idx) orelse {
1230 std.debug.print("expected argument after '{s}'\n access the help menu with 'zig build -h'\n", .{args[idx.*]});
1231 process.exit(1);
1232 };
1233}
1234
1235fn argsRest(args: [][:0]const u8, idx: usize) ?[][:0]const u8 {
1236 if (idx >= args.len) return null;
1237 return args[idx..];
1238}
1239
1240fn cleanExit() void {
1241 // Perhaps in the future there could be an Advanced Options flag such as
1242 // --debug-build-runner-leaks which would make this function return instead
1243 // of calling exit.
1244 process.exit(0);
1245}
1246
1247const Color = enum { auto, off, on };
1248const Summary = enum { all, new, failures, none };
1249
1250fn get_tty_conf(color: Color, stderr: File) std.io.tty.Config {
1251 return switch (color) {
1252 .auto => std.io.tty.detectConfig(stderr),
1253 .on => .escape_codes,
1254 .off => .no_color,
1255 };
1256}
1257
1258fn renderOptions(ttyconf: std.io.tty.Config) std.zig.ErrorBundle.RenderOptions {
1259 return .{
1260 .ttyconf = ttyconf,
1261 .include_source_line = ttyconf != .no_color,
1262 .include_reference_trace = ttyconf != .no_color,
1263 };
1264}
1265
1266fn fatalWithHint(comptime f: []const u8, args: anytype) noreturn {
1267 std.debug.print(f ++ "\n access the help menu with 'zig build -h'\n", args);
1268 process.exit(1);
1269}
1270
1271fn fatal(comptime f: []const u8, args: anytype) noreturn {
1272 std.debug.print(f ++ "\n", args);
1273 process.exit(1);
1274}
1275
1276fn validateSystemLibraryOptions(b: *std.Build) void {
1277 var bad = false;
1278 for (b.graph.system_library_options.keys(), b.graph.system_library_options.values()) |k, v| {
1279 switch (v) {
1280 .user_disabled, .user_enabled => {
1281 // The user tried to enable or disable a system library integration, but
1282 // the build script did not recognize that option.
1283 std.debug.print("system library name not recognized by build script: '{s}'\n", .{k});
1284 bad = true;
1285 },
1286 .declared_disabled, .declared_enabled => {},
1287 }
1288 }
1289 if (bad) {
1290 std.debug.print(" access the help menu with 'zig build -h'\n", .{});
1291 process.exit(1);
1292 }
1293}
lib/compiler/build_runner.zig created+1293
...@@ -0,0 +1,1293 @@
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|new|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 const failures_only = run.summary != .all and run.summary != .new;
538 if (failure_count == 0 and failures_only) return cleanExit();
539
540 const ttyconf = run.ttyconf;
541 const stderr = run.stderr;
542
543 if (run.summary != Summary.none) {
544 const total_count = success_count + failure_count + pending_count + skipped_count;
545 ttyconf.setColor(stderr, .cyan) catch {};
546 stderr.writeAll("Build Summary:") catch {};
547 ttyconf.setColor(stderr, .reset) catch {};
548 stderr.writer().print(" {d}/{d} steps succeeded", .{ success_count, total_count }) catch {};
549 if (skipped_count > 0) stderr.writer().print("; {d} skipped", .{skipped_count}) catch {};
550 if (failure_count > 0) stderr.writer().print("; {d} failed", .{failure_count}) catch {};
551
552 if (test_count > 0) stderr.writer().print("; {d}/{d} tests passed", .{ test_pass_count, test_count }) catch {};
553 if (test_skip_count > 0) stderr.writer().print("; {d} skipped", .{test_skip_count}) catch {};
554 if (test_fail_count > 0) stderr.writer().print("; {d} failed", .{test_fail_count}) catch {};
555 if (test_leak_count > 0) stderr.writer().print("; {d} leaked", .{test_leak_count}) catch {};
556
557 if (run.summary == null) {
558 ttyconf.setColor(stderr, .dim) catch {};
559 stderr.writeAll(" (disable with --summary none)") catch {};
560 ttyconf.setColor(stderr, .reset) catch {};
561 }
562 stderr.writeAll("\n") catch {};
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) catch {};
569 } else {
570 const last_index = if (run.summary == .all) b.top_level_steps.count() else blk: {
571 var i: usize = step_names.len;
572 while (i > 0) {
573 i -= 1;
574 const step = b.top_level_steps.get(step_names[i]).?.step;
575 const found = switch (run.summary orelse .failures) {
576 .all, .none => unreachable,
577 .failures => step.state != .success,
578 .new => !step.result_cached,
579 };
580 if (found) break :blk i;
581 }
582 break :blk b.top_level_steps.count();
583 };
584 for (step_names, 0..) |step_name, i| {
585 const tls = b.top_level_steps.get(step_name).?;
586 print_node.last = i + 1 == last_index;
587 printTreeStep(b, &tls.step, run, stderr, ttyconf, &print_node, &step_stack) catch {};
588 }
589 }
590 }
591
592 if (failure_count == 0) return cleanExit();
593
594 // Finally, render compile errors at the bottom of the terminal.
595 // We use a separate compile_error_steps array list because step_stack is destructively
596 // mutated in printTreeStep above.
597 if (run.prominent_compile_errors and total_compile_errors > 0) {
598 for (compile_error_steps.items) |s| {
599 if (s.result_error_bundle.errorMessageCount() > 0) {
600 s.result_error_bundle.renderToStdErr(renderOptions(ttyconf));
601 }
602 }
603
604 // Signal to parent process that we have printed compile errors. The
605 // parent process may choose to omit the "following command failed"
606 // line in this case.
607 process.exit(2);
608 }
609
610 process.exit(1);
611}
612
613const PrintNode = struct {
614 parent: ?*PrintNode,
615 last: bool = false,
616};
617
618fn printPrefix(node: *PrintNode, stderr: File, ttyconf: std.io.tty.Config) !void {
619 const parent = node.parent orelse return;
620 if (parent.parent == null) return;
621 try printPrefix(parent, stderr, ttyconf);
622 if (parent.last) {
623 try stderr.writeAll(" ");
624 } else {
625 try stderr.writeAll(switch (ttyconf) {
626 .no_color, .windows_api => "| ",
627 .escape_codes => "\x1B\x28\x30\x78\x1B\x28\x42 ", // │
628 });
629 }
630}
631
632fn printChildNodePrefix(stderr: File, ttyconf: std.io.tty.Config) !void {
633 try stderr.writeAll(switch (ttyconf) {
634 .no_color, .windows_api => "+- ",
635 .escape_codes => "\x1B\x28\x30\x6d\x71\x1B\x28\x42 ", // └─
636 });
637}
638
639fn printStepStatus(
640 s: *Step,
641 stderr: File,
642 ttyconf: std.io.tty.Config,
643 run: *const Run,
644) !void {
645 switch (s.state) {
646 .precheck_unstarted => unreachable,
647 .precheck_started => unreachable,
648 .precheck_done => unreachable,
649 .running => unreachable,
650
651 .dependency_failure => {
652 try ttyconf.setColor(stderr, .dim);
653 try stderr.writeAll(" transitive failure\n");
654 try ttyconf.setColor(stderr, .reset);
655 },
656
657 .success => {
658 try ttyconf.setColor(stderr, .green);
659 if (s.result_cached) {
660 try stderr.writeAll(" cached");
661 } else if (s.test_results.test_count > 0) {
662 const pass_count = s.test_results.passCount();
663 try stderr.writer().print(" {d} passed", .{pass_count});
664 if (s.test_results.skip_count > 0) {
665 try ttyconf.setColor(stderr, .yellow);
666 try stderr.writer().print(" {d} skipped", .{s.test_results.skip_count});
667 }
668 } else {
669 try stderr.writeAll(" success");
670 }
671 try ttyconf.setColor(stderr, .reset);
672 if (s.result_duration_ns) |ns| {
673 try ttyconf.setColor(stderr, .dim);
674 if (ns >= std.time.ns_per_min) {
675 try stderr.writer().print(" {d}m", .{ns / std.time.ns_per_min});
676 } else if (ns >= std.time.ns_per_s) {
677 try stderr.writer().print(" {d}s", .{ns / std.time.ns_per_s});
678 } else if (ns >= std.time.ns_per_ms) {
679 try stderr.writer().print(" {d}ms", .{ns / std.time.ns_per_ms});
680 } else if (ns >= std.time.ns_per_us) {
681 try stderr.writer().print(" {d}us", .{ns / std.time.ns_per_us});
682 } else {
683 try stderr.writer().print(" {d}ns", .{ns});
684 }
685 try ttyconf.setColor(stderr, .reset);
686 }
687 if (s.result_peak_rss != 0) {
688 const rss = s.result_peak_rss;
689 try ttyconf.setColor(stderr, .dim);
690 if (rss >= 1000_000_000) {
691 try stderr.writer().print(" MaxRSS:{d}G", .{rss / 1000_000_000});
692 } else if (rss >= 1000_000) {
693 try stderr.writer().print(" MaxRSS:{d}M", .{rss / 1000_000});
694 } else if (rss >= 1000) {
695 try stderr.writer().print(" MaxRSS:{d}K", .{rss / 1000});
696 } else {
697 try stderr.writer().print(" MaxRSS:{d}B", .{rss});
698 }
699 try ttyconf.setColor(stderr, .reset);
700 }
701 try stderr.writeAll("\n");
702 },
703 .skipped, .skipped_oom => |skip| {
704 try ttyconf.setColor(stderr, .yellow);
705 try stderr.writeAll(" skipped");
706 if (skip == .skipped_oom) {
707 try stderr.writeAll(" (not enough memory)");
708 try ttyconf.setColor(stderr, .dim);
709 try stderr.writer().print(" upper bound of {d} exceeded runner limit ({d})", .{ s.max_rss, run.max_rss });
710 try ttyconf.setColor(stderr, .yellow);
711 }
712 try stderr.writeAll("\n");
713 try ttyconf.setColor(stderr, .reset);
714 },
715 .failure => try printStepFailure(s, stderr, ttyconf),
716 }
717}
718
719fn printStepFailure(
720 s: *Step,
721 stderr: File,
722 ttyconf: std.io.tty.Config,
723) !void {
724 if (s.result_error_bundle.errorMessageCount() > 0) {
725 try ttyconf.setColor(stderr, .red);
726 try stderr.writer().print(" {d} errors\n", .{
727 s.result_error_bundle.errorMessageCount(),
728 });
729 try ttyconf.setColor(stderr, .reset);
730 } else if (!s.test_results.isSuccess()) {
731 try stderr.writer().print(" {d}/{d} passed", .{
732 s.test_results.passCount(), s.test_results.test_count,
733 });
734 if (s.test_results.fail_count > 0) {
735 try stderr.writeAll(", ");
736 try ttyconf.setColor(stderr, .red);
737 try stderr.writer().print("{d} failed", .{
738 s.test_results.fail_count,
739 });
740 try ttyconf.setColor(stderr, .reset);
741 }
742 if (s.test_results.skip_count > 0) {
743 try stderr.writeAll(", ");
744 try ttyconf.setColor(stderr, .yellow);
745 try stderr.writer().print("{d} skipped", .{
746 s.test_results.skip_count,
747 });
748 try ttyconf.setColor(stderr, .reset);
749 }
750 if (s.test_results.leak_count > 0) {
751 try stderr.writeAll(", ");
752 try ttyconf.setColor(stderr, .red);
753 try stderr.writer().print("{d} leaked", .{
754 s.test_results.leak_count,
755 });
756 try ttyconf.setColor(stderr, .reset);
757 }
758 try stderr.writeAll("\n");
759 } else if (s.result_error_msgs.items.len > 0) {
760 try ttyconf.setColor(stderr, .red);
761 try stderr.writeAll(" failure\n");
762 try ttyconf.setColor(stderr, .reset);
763 } else {
764 assert(s.result_stderr.len > 0);
765 try ttyconf.setColor(stderr, .red);
766 try stderr.writeAll(" stderr\n");
767 try ttyconf.setColor(stderr, .reset);
768 }
769}
770
771fn printTreeStep(
772 b: *std.Build,
773 s: *Step,
774 run: *const Run,
775 stderr: File,
776 ttyconf: std.io.tty.Config,
777 parent_node: *PrintNode,
778 step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),
779) !void {
780 const first = step_stack.swapRemove(s);
781 const summary = run.summary orelse .failures;
782 const skip = switch (summary) {
783 .none => unreachable,
784 .all => false,
785 .new => s.result_cached,
786 .failures => s.state == .success,
787 };
788 if (skip) return;
789 try printPrefix(parent_node, stderr, ttyconf);
790
791 if (!first) try ttyconf.setColor(stderr, .dim);
792 if (parent_node.parent != null) {
793 if (parent_node.last) {
794 try printChildNodePrefix(stderr, ttyconf);
795 } else {
796 try stderr.writeAll(switch (ttyconf) {
797 .no_color, .windows_api => "+- ",
798 .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─
799 });
800 }
801 }
802
803 // dep_prefix omitted here because it is redundant with the tree.
804 try stderr.writeAll(s.name);
805
806 if (first) {
807 try printStepStatus(s, stderr, ttyconf, run);
808
809 const last_index = if (summary == .all) s.dependencies.items.len -| 1 else blk: {
810 var i: usize = s.dependencies.items.len;
811 while (i > 0) {
812 i -= 1;
813
814 const step = s.dependencies.items[i];
815 const found = switch (summary) {
816 .all, .none => unreachable,
817 .failures => step.state != .success,
818 .new => !step.result_cached,
819 };
820 if (found) break :blk i;
821 }
822 break :blk s.dependencies.items.len -| 1;
823 };
824 for (s.dependencies.items, 0..) |dep, i| {
825 var print_node: PrintNode = .{
826 .parent = parent_node,
827 .last = i == last_index,
828 };
829 try printTreeStep(b, dep, run, stderr, ttyconf, &print_node, step_stack);
830 }
831 } else {
832 if (s.dependencies.items.len == 0) {
833 try stderr.writeAll(" (reused)\n");
834 } else {
835 try stderr.writer().print(" (+{d} more reused dependencies)\n", .{
836 s.dependencies.items.len,
837 });
838 }
839 try ttyconf.setColor(stderr, .reset);
840 }
841}
842
843/// Traverse the dependency graph depth-first and make it undirected by having
844/// steps know their dependants (they only know dependencies at start).
845/// Along the way, check that there is no dependency loop, and record the steps
846/// in traversal order in `step_stack`.
847/// Each step has its dependencies traversed in random order, this accomplishes
848/// two things:
849/// - `step_stack` will be in randomized-depth-first order, so the build runner
850/// spawns steps in a random (but optimized) order
851/// - each step's `dependants` list is also filled in a random order, so that
852/// when it finishes executing in `workerMakeOneStep`, it spawns next steps
853/// to run in random order
854fn constructGraphAndCheckForDependencyLoop(
855 b: *std.Build,
856 s: *Step,
857 step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),
858 rand: std.Random,
859) !void {
860 switch (s.state) {
861 .precheck_started => {
862 std.debug.print("dependency loop detected:\n {s}\n", .{s.name});
863 return error.DependencyLoopDetected;
864 },
865 .precheck_unstarted => {
866 s.state = .precheck_started;
867
868 try step_stack.ensureUnusedCapacity(b.allocator, s.dependencies.items.len);
869
870 // We dupe to avoid shuffling the steps in the summary, it depends
871 // on s.dependencies' order.
872 const deps = b.allocator.dupe(*Step, s.dependencies.items) catch @panic("OOM");
873 rand.shuffle(*Step, deps);
874
875 for (deps) |dep| {
876 try step_stack.put(b.allocator, dep, {});
877 try dep.dependants.append(b.allocator, s);
878 constructGraphAndCheckForDependencyLoop(b, dep, step_stack, rand) catch |err| {
879 if (err == error.DependencyLoopDetected) {
880 std.debug.print(" {s}\n", .{s.name});
881 }
882 return err;
883 };
884 }
885
886 s.state = .precheck_done;
887 },
888 .precheck_done => {},
889
890 // These don't happen until we actually run the step graph.
891 .dependency_failure => unreachable,
892 .running => unreachable,
893 .success => unreachable,
894 .failure => unreachable,
895 .skipped => unreachable,
896 .skipped_oom => unreachable,
897 }
898}
899
900fn workerMakeOneStep(
901 wg: *std.Thread.WaitGroup,
902 thread_pool: *std.Thread.Pool,
903 b: *std.Build,
904 s: *Step,
905 prog_node: *std.Progress.Node,
906 run: *Run,
907) void {
908 defer wg.finish();
909
910 // First, check the conditions for running this step. If they are not met,
911 // then we return without doing the step, relying on another worker to
912 // queue this step up again when dependencies are met.
913 for (s.dependencies.items) |dep| {
914 switch (@atomicLoad(Step.State, &dep.state, .seq_cst)) {
915 .success, .skipped => continue,
916 .failure, .dependency_failure, .skipped_oom => {
917 @atomicStore(Step.State, &s.state, .dependency_failure, .seq_cst);
918 return;
919 },
920 .precheck_done, .running => {
921 // dependency is not finished yet.
922 return;
923 },
924 .precheck_unstarted => unreachable,
925 .precheck_started => unreachable,
926 }
927 }
928
929 if (s.max_rss != 0) {
930 run.max_rss_mutex.lock();
931 defer run.max_rss_mutex.unlock();
932
933 // Avoid running steps twice.
934 if (s.state != .precheck_done) {
935 // Another worker got the job.
936 return;
937 }
938
939 const new_claimed_rss = run.claimed_rss + s.max_rss;
940 if (new_claimed_rss > run.max_rss) {
941 // Running this step right now could possibly exceed the allotted RSS.
942 // Add this step to the queue of memory-blocked steps.
943 run.memory_blocked_steps.append(s) catch @panic("OOM");
944 return;
945 }
946
947 run.claimed_rss = new_claimed_rss;
948 s.state = .running;
949 } else {
950 // Avoid running steps twice.
951 if (@cmpxchgStrong(Step.State, &s.state, .precheck_done, .running, .seq_cst, .seq_cst) != null) {
952 // Another worker got the job.
953 return;
954 }
955 }
956
957 var sub_prog_node = prog_node.start(s.name, 0);
958 sub_prog_node.activate();
959 defer sub_prog_node.end();
960
961 const make_result = s.make(&sub_prog_node);
962
963 // No matter the result, we want to display error/warning messages.
964 const show_compile_errors = !run.prominent_compile_errors and
965 s.result_error_bundle.errorMessageCount() > 0;
966 const show_error_msgs = s.result_error_msgs.items.len > 0;
967 const show_stderr = s.result_stderr.len > 0;
968
969 if (show_error_msgs or show_compile_errors or show_stderr) {
970 sub_prog_node.context.lock_stderr();
971 defer sub_prog_node.context.unlock_stderr();
972
973 printErrorMessages(b, s, run) catch {};
974 }
975
976 handle_result: {
977 if (make_result) |_| {
978 @atomicStore(Step.State, &s.state, .success, .seq_cst);
979 } else |err| switch (err) {
980 error.MakeFailed => {
981 @atomicStore(Step.State, &s.state, .failure, .seq_cst);
982 break :handle_result;
983 },
984 error.MakeSkipped => @atomicStore(Step.State, &s.state, .skipped, .seq_cst),
985 }
986
987 // Successful completion of a step, so we queue up its dependants as well.
988 for (s.dependants.items) |dep| {
989 wg.start();
990 thread_pool.spawn(workerMakeOneStep, .{
991 wg, thread_pool, b, dep, prog_node, run,
992 }) catch @panic("OOM");
993 }
994 }
995
996 // If this is a step that claims resources, we must now queue up other
997 // steps that are waiting for resources.
998 if (s.max_rss != 0) {
999 run.max_rss_mutex.lock();
1000 defer run.max_rss_mutex.unlock();
1001
1002 // Give the memory back to the scheduler.
1003 run.claimed_rss -= s.max_rss;
1004 // Avoid kicking off too many tasks that we already know will not have
1005 // enough resources.
1006 var remaining = run.max_rss - run.claimed_rss;
1007 var i: usize = 0;
1008 var j: usize = 0;
1009 while (j < run.memory_blocked_steps.items.len) : (j += 1) {
1010 const dep = run.memory_blocked_steps.items[j];
1011 assert(dep.max_rss != 0);
1012 if (dep.max_rss <= remaining) {
1013 remaining -= dep.max_rss;
1014
1015 wg.start();
1016 thread_pool.spawn(workerMakeOneStep, .{
1017 wg, thread_pool, b, dep, prog_node, run,
1018 }) catch @panic("OOM");
1019 } else {
1020 run.memory_blocked_steps.items[i] = dep;
1021 i += 1;
1022 }
1023 }
1024 run.memory_blocked_steps.shrinkRetainingCapacity(i);
1025 }
1026}
1027
1028fn printErrorMessages(b: *std.Build, failing_step: *Step, run: *const Run) !void {
1029 const gpa = b.allocator;
1030 const stderr = run.stderr;
1031 const ttyconf = run.ttyconf;
1032
1033 // Provide context for where these error messages are coming from by
1034 // printing the corresponding Step subtree.
1035
1036 var step_stack: std.ArrayListUnmanaged(*Step) = .{};
1037 defer step_stack.deinit(gpa);
1038 try step_stack.append(gpa, failing_step);
1039 while (step_stack.items[step_stack.items.len - 1].dependants.items.len != 0) {
1040 try step_stack.append(gpa, step_stack.items[step_stack.items.len - 1].dependants.items[0]);
1041 }
1042
1043 // Now, `step_stack` has the subtree that we want to print, in reverse order.
1044 try ttyconf.setColor(stderr, .dim);
1045 var indent: usize = 0;
1046 while (step_stack.popOrNull()) |s| : (indent += 1) {
1047 if (indent > 0) {
1048 try stderr.writer().writeByteNTimes(' ', (indent - 1) * 3);
1049 try printChildNodePrefix(stderr, ttyconf);
1050 }
1051
1052 try stderr.writeAll(s.name);
1053
1054 if (s == failing_step) {
1055 try printStepFailure(s, stderr, ttyconf);
1056 } else {
1057 try stderr.writeAll("\n");
1058 }
1059 }
1060 try ttyconf.setColor(stderr, .reset);
1061
1062 if (failing_step.result_stderr.len > 0) {
1063 try stderr.writeAll(failing_step.result_stderr);
1064 if (!mem.endsWith(u8, failing_step.result_stderr, "\n")) {
1065 try stderr.writeAll("\n");
1066 }
1067 }
1068
1069 if (!run.prominent_compile_errors and failing_step.result_error_bundle.errorMessageCount() > 0)
1070 try failing_step.result_error_bundle.renderToWriter(renderOptions(ttyconf), stderr.writer());
1071
1072 for (failing_step.result_error_msgs.items) |msg| {
1073 try ttyconf.setColor(stderr, .red);
1074 try stderr.writeAll("error: ");
1075 try ttyconf.setColor(stderr, .reset);
1076 try stderr.writeAll(msg);
1077 try stderr.writeAll("\n");
1078 }
1079}
1080
1081fn steps(builder: *std.Build, out_stream: anytype) !void {
1082 const allocator = builder.allocator;
1083 for (builder.top_level_steps.values()) |top_level_step| {
1084 const name = if (&top_level_step.step == builder.default_step)
1085 try fmt.allocPrint(allocator, "{s} (default)", .{top_level_step.step.name})
1086 else
1087 top_level_step.step.name;
1088 try out_stream.print(" {s:<28} {s}\n", .{ name, top_level_step.description });
1089 }
1090}
1091
1092fn usage(b: *std.Build, out_stream: anytype) !void {
1093 try out_stream.print(
1094 \\Usage: {s} build [steps] [options]
1095 \\
1096 \\Steps:
1097 \\
1098 , .{b.graph.zig_exe});
1099 try steps(b, out_stream);
1100
1101 try out_stream.writeAll(
1102 \\
1103 \\General Options:
1104 \\ -p, --prefix [path] Where to install files (default: zig-out)
1105 \\ --prefix-lib-dir [path] Where to install libraries
1106 \\ --prefix-exe-dir [path] Where to install executables
1107 \\ --prefix-include-dir [path] Where to install C header files
1108 \\
1109 \\ --release[=mode] Request release mode, optionally specifying a
1110 \\ preferred optimization mode: fast, safe, small
1111 \\
1112 \\ -fdarling, -fno-darling Integration with system-installed Darling to
1113 \\ execute macOS programs on Linux hosts
1114 \\ (default: no)
1115 \\ -fqemu, -fno-qemu Integration with system-installed QEMU to execute
1116 \\ foreign-architecture programs on Linux hosts
1117 \\ (default: no)
1118 \\ --glibc-runtimes [path] Enhances QEMU integration by providing glibc built
1119 \\ for multiple foreign architectures, allowing
1120 \\ execution of non-native programs that link with glibc.
1121 \\ -frosetta, -fno-rosetta Rely on Rosetta to execute x86_64 programs on
1122 \\ ARM64 macOS hosts. (default: no)
1123 \\ -fwasmtime, -fno-wasmtime Integration with system-installed wasmtime to
1124 \\ execute WASI binaries. (default: no)
1125 \\ -fwine, -fno-wine Integration with system-installed Wine to execute
1126 \\ Windows programs on Linux hosts. (default: no)
1127 \\
1128 \\ -h, --help Print this help and exit
1129 \\ -l, --list-steps Print available steps
1130 \\ --verbose Print commands before executing them
1131 \\ --color [auto|off|on] Enable or disable colored error messages
1132 \\ --prominent-compile-errors Buffer compile errors and display at end
1133 \\ --summary [mode] Control the printing of the build summary
1134 \\ all Print the build summary in its entirety
1135 \\ new Omit cached steps
1136 \\ failures (Default) Only print failed steps
1137 \\ none Do not print the build summary
1138 \\ -j<N> Limit concurrent jobs (default is to use all CPU cores)
1139 \\ --maxrss <bytes> Limit memory usage (default is to use available memory)
1140 \\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss
1141 \\ --fetch Exit after fetching dependency tree
1142 \\
1143 \\Project-Specific Options:
1144 \\
1145 );
1146
1147 const arena = b.allocator;
1148 if (b.available_options_list.items.len == 0) {
1149 try out_stream.print(" (none)\n", .{});
1150 } else {
1151 for (b.available_options_list.items) |option| {
1152 const name = try fmt.allocPrint(arena, " -D{s}=[{s}]", .{
1153 option.name,
1154 @tagName(option.type_id),
1155 });
1156 try out_stream.print("{s:<30} {s}\n", .{ name, option.description });
1157 if (option.enum_options) |enum_options| {
1158 const padding = " " ** 33;
1159 try out_stream.writeAll(padding ++ "Supported Values:\n");
1160 for (enum_options) |enum_option| {
1161 try out_stream.print(padding ++ " {s}\n", .{enum_option});
1162 }
1163 }
1164 }
1165 }
1166
1167 try out_stream.writeAll(
1168 \\
1169 \\System Integration Options:
1170 \\ --search-prefix [path] Add a path to look for binaries, libraries, headers
1171 \\ --sysroot [path] Set the system root directory (usually /)
1172 \\ --libc [file] Provide a file which specifies libc paths
1173 \\
1174 \\ --host-target [triple] Use the provided target as the host
1175 \\ --host-cpu [cpu] Use the provided CPU as the host
1176 \\ --host-dynamic-linker [path] Use the provided dynamic linker as the host
1177 \\
1178 \\ --system [pkgdir] Disable package fetching; enable all integrations
1179 \\ -fsys=[name] Enable a system integration
1180 \\ -fno-sys=[name] Disable a system integration
1181 \\
1182 \\ Available System Integrations: Enabled:
1183 \\
1184 );
1185 if (b.graph.system_library_options.entries.len == 0) {
1186 try out_stream.writeAll(" (none) -\n");
1187 } else {
1188 for (b.graph.system_library_options.keys(), b.graph.system_library_options.values()) |k, v| {
1189 const status = switch (v) {
1190 .declared_enabled => "yes",
1191 .declared_disabled => "no",
1192 .user_enabled, .user_disabled => unreachable, // already emitted error
1193 };
1194 try out_stream.print(" {s:<43} {s}\n", .{ k, status });
1195 }
1196 }
1197
1198 try out_stream.writeAll(
1199 \\
1200 \\Advanced Options:
1201 \\ -freference-trace[=num] How many lines of reference trace should be shown per compile error
1202 \\ -fno-reference-trace Disable reference trace
1203 \\ --build-file [file] Override path to build.zig
1204 \\ --cache-dir [path] Override path to local Zig cache directory
1205 \\ --global-cache-dir [path] Override path to global Zig cache directory
1206 \\ --zig-lib-dir [arg] Override path to Zig lib directory
1207 \\ --build-runner [file] Override path to build runner
1208 \\ --seed [integer] For shuffling dependency traversal order (default: random)
1209 \\ --debug-log [scope] Enable debugging the compiler
1210 \\ --debug-pkg-config Fail if unknown pkg-config flags encountered
1211 \\ --verbose-link Enable compiler debug output for linking
1212 \\ --verbose-air Enable compiler debug output for Zig AIR
1213 \\ --verbose-llvm-ir[=file] Enable compiler debug output for LLVM IR
1214 \\ --verbose-llvm-bc=[file] Enable compiler debug output for LLVM BC
1215 \\ --verbose-cimport Enable compiler debug output for C imports
1216 \\ --verbose-cc Enable compiler debug output for C compilation
1217 \\ --verbose-llvm-cpu-features Enable compiler debug output for LLVM CPU features
1218 \\
1219 );
1220}
1221
1222fn nextArg(args: [][:0]const u8, idx: *usize) ?[:0]const u8 {
1223 if (idx.* >= args.len) return null;
1224 defer idx.* += 1;
1225 return args[idx.*];
1226}
1227
1228fn nextArgOrFatal(args: [][:0]const u8, idx: *usize) [:0]const u8 {
1229 return nextArg(args, idx) orelse {
1230 std.debug.print("expected argument after '{s}'\n access the help menu with 'zig build -h'\n", .{args[idx.*]});
1231 process.exit(1);
1232 };
1233}
1234
1235fn argsRest(args: [][:0]const u8, idx: usize) ?[][:0]const u8 {
1236 if (idx >= args.len) return null;
1237 return args[idx..];
1238}
1239
1240fn cleanExit() void {
1241 // Perhaps in the future there could be an Advanced Options flag such as
1242 // --debug-build-runner-leaks which would make this function return instead
1243 // of calling exit.
1244 process.exit(0);
1245}
1246
1247const Color = enum { auto, off, on };
1248const Summary = enum { all, new, failures, none };
1249
1250fn get_tty_conf(color: Color, stderr: File) std.io.tty.Config {
1251 return switch (color) {
1252 .auto => std.io.tty.detectConfig(stderr),
1253 .on => .escape_codes,
1254 .off => .no_color,
1255 };
1256}
1257
1258fn renderOptions(ttyconf: std.io.tty.Config) std.zig.ErrorBundle.RenderOptions {
1259 return .{
1260 .ttyconf = ttyconf,
1261 .include_source_line = ttyconf != .no_color,
1262 .include_reference_trace = ttyconf != .no_color,
1263 };
1264}
1265
1266fn fatalWithHint(comptime f: []const u8, args: anytype) noreturn {
1267 std.debug.print(f ++ "\n access the help menu with 'zig build -h'\n", args);
1268 process.exit(1);
1269}
1270
1271fn fatal(comptime f: []const u8, args: anytype) noreturn {
1272 std.debug.print(f ++ "\n", args);
1273 process.exit(1);
1274}
1275
1276fn validateSystemLibraryOptions(b: *std.Build) void {
1277 var bad = false;
1278 for (b.graph.system_library_options.keys(), b.graph.system_library_options.values()) |k, v| {
1279 switch (v) {
1280 .user_disabled, .user_enabled => {
1281 // The user tried to enable or disable a system library integration, but
1282 // the build script did not recognize that option.
1283 std.debug.print("system library name not recognized by build script: '{s}'\n", .{k});
1284 bad = true;
1285 },
1286 .declared_disabled, .declared_enabled => {},
1287 }
1288 }
1289 if (bad) {
1290 std.debug.print(" access the help menu with 'zig build -h'\n", .{});
1291 process.exit(1);
1292 }
1293}
src/main.zig+1
...@@ -4995,6 +4995,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -4995,6 +4995,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
4995 } else .{4995 } else .{
4996 .root = .{4996 .root = .{
4997 .root_dir = zig_lib_directory,4997 .root_dir = zig_lib_directory,
4998 .sub_path = "compiler",
4998 },4999 },
4999 .root_src_path = "build_runner.zig",5000 .root_src_path = "build_runner.zig",
5000 };5001 };