authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-08-26 17:45:43+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-10-18 09:28:42+01:00
log75adbf40ca1bd607b11f73513667e81d2b341690
tree44a76c3e56688122609b59d5a21ff0a6dfda2915
parenta388a8e5a7193831a349f6d00e04bd15211a931f
signaturelock-open Commit is signed but in an unrecognized format.

build runner: remove `--prominent-compile-errors`, introduce `--error-style`

The new `--error-style` option decides how build failures are printed. The default mode "verbose" prints all context including the step graph fragment and the failed command (if any). The alternative mode "minimal" prints only the failed step itself, and does not print the failed command. There are also "verbose_clear" and "minimal_clear" modes, which have the distinction that the output is cleared (through ANSI escape codes) between updates, preventing different updates from being confused in the output. If `--error-style` is not specified, the environment variable `ZIG_BUILD_ERROR_STYLE` is checked before falling back to the default of "verbose"; this means the value can effectively be chosen system-wide since it is generally a personal preference. Also introduced is a `--multiline-errors` option which decides how to print errors which span multiple lines. By default, non-initial lines are indented to align with the first. Alternatively, a leading newline can be printed to align everyting on the first column, or no special treatment can be applied, resulting in misaligned output. Again, there is an environment variable (`ZIG_BUILD_MULTILINE_ERRORS`) to specify a preferred default if the option is not explicitly provided. Resolves: #23472

3 files changed, 167 insertions(+), 129 deletions(-)

lib/compiler/build_runner.zig+163-127
......@@ -103,12 +103,13 @@ pub fn main() !void {
103103
104104 var install_prefix: ?[]const u8 = null;
105105 var dir_list = std.Build.DirList{};
106 var error_style: ErrorStyle = .verbose;
107 var multiline_errors: MultilineErrors = .indent;
106108 var summary: ?Summary = null;
107109 var max_rss: u64 = 0;
108110 var skip_oom_steps = false;
109111 var test_timeout_ms: ?u64 = null;
110112 var color: Color = .auto;
111 var prominent_compile_errors = false;
112113 var help_menu = false;
113114 var steps_menu = false;
114115 var output_tmp_nonce: ?[16]u8 = null;
......@@ -117,6 +118,18 @@ pub fn main() !void {
117118 var debounce_interval_ms: u16 = 50;
118119 var webui_listen: ?std.net.Address = null;
119120
121 if (try std.zig.EnvVar.ZIG_BUILD_ERROR_STYLE.get(arena)) |str| {
122 if (std.meta.stringToEnum(ErrorStyle, str)) |style| {
123 error_style = style;
124 }
125 }
126
127 if (try std.zig.EnvVar.ZIG_BUILD_MULTILINE_ERRORS.get(arena)) |str| {
128 if (std.meta.stringToEnum(MultilineErrors, str)) |style| {
129 multiline_errors = style;
130 }
131 }
132
120133 while (nextArg(args, &arg_idx)) |arg| {
121134 if (mem.startsWith(u8, arg, "-Z")) {
122135 if (arg.len != 18) fatalWithHint("bad argument: '{s}'", .{arg});
......@@ -197,11 +210,23 @@ pub fn main() !void {
197210 arg, next_arg,
198211 });
199212 };
213 } else if (mem.eql(u8, arg, "--error-style")) {
214 const next_arg = nextArg(args, &arg_idx) orelse
215 fatalWithHint("expected style after '{s}'", .{arg});
216 error_style = std.meta.stringToEnum(ErrorStyle, next_arg) orelse {
217 fatalWithHint("expected style after '{s}', found '{s}'", .{ arg, next_arg });
218 };
219 } else if (mem.eql(u8, arg, "--multiline-errors")) {
220 const next_arg = nextArg(args, &arg_idx) orelse
221 fatalWithHint("expected style after '{s}'", .{arg});
222 multiline_errors = std.meta.stringToEnum(MultilineErrors, next_arg) orelse {
223 fatalWithHint("expected style after '{s}', found '{s}'", .{ arg, next_arg });
224 };
200225 } else if (mem.eql(u8, arg, "--summary")) {
201226 const next_arg = nextArg(args, &arg_idx) orelse
202 fatalWithHint("expected [all|new|failures|none] after '{s}'", .{arg});
227 fatalWithHint("expected [all|new|failures|line|none] after '{s}'", .{arg});
203228 summary = std.meta.stringToEnum(Summary, next_arg) orelse {
204 fatalWithHint("expected [all|new|failures|none] after '{s}', found '{s}'", .{
229 fatalWithHint("expected [all|new|failures|line|none] after '{s}', found '{s}'", .{
205230 arg, next_arg,
206231 });
207232 };
......@@ -273,8 +298,6 @@ pub fn main() !void {
273298 builder.verbose_cc = true;
274299 } else if (mem.eql(u8, arg, "--verbose-llvm-cpu-features")) {
275300 builder.verbose_llvm_cpu_features = true;
276 } else if (mem.eql(u8, arg, "--prominent-compile-errors")) {
277 prominent_compile_errors = true;
278301 } else if (mem.eql(u8, arg, "--watch")) {
279302 watch = true;
280303 } else if (mem.eql(u8, arg, "--time-report")) {
......@@ -466,10 +489,11 @@ pub fn main() !void {
466489 .web_server = undefined, // set after `prepare`
467490 .memory_blocked_steps = .empty,
468491 .step_stack = .empty,
469 .prominent_compile_errors = prominent_compile_errors,
470492
471493 .claimed_rss = 0,
472 .summary = summary orelse if (watch) .new else .failures,
494 .error_style = error_style,
495 .multiline_errors = multiline_errors,
496 .summary = summary orelse if (watch or webui_listen != null) .line else .failures,
473497 .ttyconf = ttyconf,
474498 .stderr = stderr,
475499 .thread_pool = undefined,
......@@ -485,8 +509,14 @@ pub fn main() !void {
485509 }
486510
487511 prepare(arena, builder, targets.items, &run, graph.random_seed) catch |err| switch (err) {
488 error.UncleanExit => process.exit(1),
489 else => return err,
512 error.DependencyLoopDetected => {
513 // Perhaps in the future there could be an Advanced Options flag such as
514 // --debug-build-runner-leaks which would make this code return instead of
515 // calling exit.
516 std.debug.lockStdErr();
517 process.exit(1);
518 },
519 else => |e| return e,
490520 };
491521
492522 var w: Watch = w: {
......@@ -516,22 +546,20 @@ pub fn main() !void {
516546 ws.start() catch |err| fatal("failed to start web server: {s}", .{@errorName(err)});
517547 }
518548
519 rebuild: while (true) {
549 rebuild: while (true) : (if (run.error_style.clearOnUpdate()) {
550 const bw = std.debug.lockStderrWriter(&stdio_buffer_allocation);
551 defer std.debug.unlockStderrWriter();
552 try bw.writeAll("\x1B[2J\x1B[3J\x1B[H");
553 }) {
520554 if (run.web_server) |*ws| ws.startBuild();
521555
522 runStepNames(
556 try runStepNames(
523557 builder,
524558 targets.items,
525559 main_progress_node,
526560 &run,
527561 fuzz,
528 ) catch |err| switch (err) {
529 error.UncleanExit => {
530 assert(!run.watch and run.web_server == null);
531 process.exit(1);
532 },
533 else => return err,
534 };
562 );
535563
536564 if (run.web_server) |*web_server| {
537565 if (fuzz) |mode| if (mode != .forever) fatal(
......@@ -542,10 +570,6 @@ pub fn main() !void {
542570 web_server.finishBuild(.{ .fuzz = fuzz != null });
543571 }
544572
545 if (!watch and run.web_server == null) {
546 return cleanExit();
547 }
548
549573 if (run.web_server) |*ws| {
550574 assert(!watch); // fatal error after CLI parsing
551575 while (true) switch (ws.wait()) {
......@@ -626,18 +650,14 @@ const Run = struct {
626650 memory_blocked_steps: std.ArrayListUnmanaged(*Step),
627651 /// Allocated into `gpa`.
628652 step_stack: std.AutoArrayHashMapUnmanaged(*Step, void),
629 prominent_compile_errors: bool,
630653 thread_pool: std.Thread.Pool,
631654
632655 claimed_rss: usize,
656 error_style: ErrorStyle,
657 multiline_errors: MultilineErrors,
633658 summary: Summary,
634659 ttyconf: tty.Config,
635660 stderr: File,
636
637 fn cleanExit(run: Run) void {
638 if (run.watch or run.web_server != null) return;
639 return runner.cleanExit();
640 }
641661};
642662
643663fn prepare(
......@@ -671,10 +691,7 @@ fn prepare(
671691 rand.shuffle(*Step, starting_steps);
672692
673693 for (starting_steps) |s| {
674 constructGraphAndCheckForDependencyLoop(gpa, b, s, &run.step_stack, rand) catch |err| switch (err) {
675 error.DependencyLoopDetected => return uncleanExit(),
676 else => |e| return e,
677 };
694 try constructGraphAndCheckForDependencyLoop(gpa, b, s, &run.step_stack, rand);
678695 }
679696
680697 {
......@@ -827,26 +844,25 @@ fn runStepNames(
827844 // Every test has a state
828845 assert(test_pass_count + test_skip_count + test_fail_count + test_crash_count + test_timeout_count == test_count);
829846
830 // A proper command line application defaults to silently succeeding.
831 // The user may request verbose mode if they have a different preference.
832 const failures_only = switch (run.summary) {
833 .failures, .none => true,
834 else => false,
835 };
836847 if (failure_count == 0) {
837848 std.Progress.setStatus(.success);
838 if (failures_only) return run.cleanExit();
839849 } else {
840850 std.Progress.setStatus(.failure);
841851 }
842852
843 if (run.summary != .none) {
853 summary: {
854 switch (run.summary) {
855 .all, .new, .line => {},
856 .failures => if (failure_count == 0) break :summary,
857 .none => break :summary,
858 }
859
844860 const w = std.debug.lockStderrWriter(&stdio_buffer_allocation);
845861 defer std.debug.unlockStderrWriter();
846862
847863 const total_count = success_count + failure_count + pending_count + skipped_count;
848864 ttyconf.setColor(w, .cyan) catch {};
849 w.writeAll("\nBuild Summary:") catch {};
865 w.writeAll("Build Summary:") catch {};
850866 ttyconf.setColor(w, .reset) catch {};
851867 w.print(" {d}/{d} steps succeeded", .{ success_count, total_count }) catch {};
852868 if (skipped_count > 0) w.print(", {d} skipped", .{skipped_count}) catch {};
......@@ -862,6 +878,8 @@ fn runStepNames(
862878
863879 w.writeAll("\n") catch {};
864880
881 if (run.summary == .line) break :summary;
882
865883 // Print a fancy tree with build results.
866884 var step_stack_copy = try step_stack.clone(gpa);
867885 defer step_stack_copy.deinit(gpa);
......@@ -877,7 +895,7 @@ fn runStepNames(
877895 i -= 1;
878896 const step = b.top_level_steps.get(step_names[i]).?.step;
879897 const found = switch (run.summary) {
880 .all, .none => unreachable,
898 .all, .line, .none => unreachable,
881899 .failures => step.state != .success,
882900 .new => !step.result_cached,
883901 };
......@@ -894,28 +912,19 @@ fn runStepNames(
894912 w.writeByte('\n') catch {};
895913 }
896914
897 if (failure_count == 0) {
898 return run.cleanExit();
899 }
900
901 // Finally, render compile errors at the bottom of the terminal.
902 if (run.prominent_compile_errors and total_compile_errors > 0) {
903 for (step_stack.keys()) |s| {
904 if (s.result_error_bundle.errorMessageCount() > 0) {
905 s.result_error_bundle.renderToStdErr(.{ .ttyconf = ttyconf });
906 }
907 }
915 if (run.watch or run.web_server != null) return;
908916
909 if (!run.watch and run.web_server == null) {
910 // Signal to parent process that we have printed compile errors. The
911 // parent process may choose to omit the "following command failed"
912 // line in this case.
913 std.debug.lockStdErr();
914 process.exit(2);
915 }
916 }
917 // Perhaps in the future there could be an Advanced Options flag such as
918 // --debug-build-runner-leaks which would make this code return instead of
919 // calling exit.
917920
918 if (!run.watch and run.web_server == null) return uncleanExit();
921 const code: u8 = code: {
922 if (failure_count == 0) break :code 0; // success
923 if (run.error_style.verboseContext()) break :code 1; // failure; print build command
924 break :code 2; // failure; do not print build command
925 };
926 std.debug.lockStdErr();
927 process.exit(code);
919928}
920929
921930const PrintNode = struct {
......@@ -1124,7 +1133,7 @@ fn printTreeStep(
11241133 const first = step_stack.swapRemove(s);
11251134 const summary = run.summary;
11261135 const skip = switch (summary) {
1127 .none => unreachable,
1136 .none, .line => unreachable,
11281137 .all => false,
11291138 .new => s.result_cached,
11301139 .failures => s.state == .success,
......@@ -1157,7 +1166,7 @@ fn printTreeStep(
11571166
11581167 const step = s.dependencies.items[i];
11591168 const found = switch (summary) {
1160 .all, .none => unreachable,
1169 .all, .line, .none => unreachable,
11611170 .failures => step.state != .success,
11621171 .new => !step.result_cached,
11631172 };
......@@ -1316,15 +1325,13 @@ fn workerMakeOneStep(
13161325 });
13171326
13181327 // No matter the result, we want to display error/warning messages.
1319 const show_compile_errors = !run.prominent_compile_errors and
1320 s.result_error_bundle.errorMessageCount() > 0;
1328 const show_compile_errors = s.result_error_bundle.errorMessageCount() > 0;
13211329 const show_error_msgs = s.result_error_msgs.items.len > 0;
13221330 const show_stderr = s.result_stderr.len > 0;
1323
13241331 if (show_error_msgs or show_compile_errors or show_stderr) {
13251332 const bw = std.debug.lockStderrWriter(&stdio_buffer_allocation);
13261333 defer std.debug.unlockStderrWriter();
1327 printErrorMessages(run.gpa, s, .{ .ttyconf = run.ttyconf }, bw, run.prominent_compile_errors) catch {};
1334 printErrorMessages(run.gpa, s, .{ .ttyconf = run.ttyconf }, bw, run.error_style, run.multiline_errors) catch {};
13281335 }
13291336
13301337 handle_result: {
......@@ -1388,37 +1395,46 @@ pub fn printErrorMessages(
13881395 failing_step: *Step,
13891396 options: std.zig.ErrorBundle.RenderOptions,
13901397 stderr: *Writer,
1391 prominent_compile_errors: bool,
1398 error_style: ErrorStyle,
1399 multiline_errors: MultilineErrors,
13921400) !void {
1393 // Provide context for where these error messages are coming from by
1394 // printing the corresponding Step subtree.
1395
1396 var step_stack: std.ArrayListUnmanaged(*Step) = .empty;
1397 defer step_stack.deinit(gpa);
1398 try step_stack.append(gpa, failing_step);
1399 while (step_stack.items[step_stack.items.len - 1].dependants.items.len != 0) {
1400 try step_stack.append(gpa, step_stack.items[step_stack.items.len - 1].dependants.items[0]);
1401 }
1402
1403 // Now, `step_stack` has the subtree that we want to print, in reverse order.
14041401 const ttyconf = options.ttyconf;
1405 try ttyconf.setColor(stderr, .dim);
1406 var indent: usize = 0;
1407 while (step_stack.pop()) |s| : (indent += 1) {
1408 if (indent > 0) {
1409 try stderr.splatByteAll(' ', (indent - 1) * 3);
1410 try printChildNodePrefix(stderr, ttyconf);
1402
1403 if (error_style.verboseContext()) {
1404 // Provide context for where these error messages are coming from by
1405 // printing the corresponding Step subtree.
1406 var step_stack: std.ArrayListUnmanaged(*Step) = .empty;
1407 defer step_stack.deinit(gpa);
1408 try step_stack.append(gpa, failing_step);
1409 while (step_stack.items[step_stack.items.len - 1].dependants.items.len != 0) {
1410 try step_stack.append(gpa, step_stack.items[step_stack.items.len - 1].dependants.items[0]);
14111411 }
14121412
1413 try stderr.writeAll(s.name);
1413 // Now, `step_stack` has the subtree that we want to print, in reverse order.
1414 try ttyconf.setColor(stderr, .dim);
1415 var indent: usize = 0;
1416 while (step_stack.pop()) |s| : (indent += 1) {
1417 if (indent > 0) {
1418 try stderr.splatByteAll(' ', (indent - 1) * 3);
1419 try printChildNodePrefix(stderr, ttyconf);
1420 }
14141421
1415 if (s == failing_step) {
1416 try printStepFailure(s, stderr, ttyconf);
1417 } else {
1418 try stderr.writeAll("\n");
1422 try stderr.writeAll(s.name);
1423
1424 if (s == failing_step) {
1425 try printStepFailure(s, stderr, ttyconf);
1426 } else {
1427 try stderr.writeAll("\n");
1428 }
14191429 }
1430 try ttyconf.setColor(stderr, .reset);
1431 } else {
1432 // Just print the failing step itself.
1433 try ttyconf.setColor(stderr, .dim);
1434 try stderr.writeAll(failing_step.name);
1435 try printStepFailure(failing_step, stderr, ttyconf);
1436 try ttyconf.setColor(stderr, .reset);
14201437 }
1421 try ttyconf.setColor(stderr, .reset);
14221438
14231439 if (failing_step.result_stderr.len > 0) {
14241440 try stderr.writeAll(failing_step.result_stderr);
......@@ -1427,30 +1443,38 @@ pub fn printErrorMessages(
14271443 }
14281444 }
14291445
1430 if (!prominent_compile_errors and failing_step.result_error_bundle.errorMessageCount() > 0) {
1431 try failing_step.result_error_bundle.renderToWriter(options, stderr);
1432 }
1446 try failing_step.result_error_bundle.renderToWriter(options, stderr);
14331447
14341448 for (failing_step.result_error_msgs.items) |msg| {
14351449 try ttyconf.setColor(stderr, .red);
1436 try stderr.writeAll("error: ");
1450 try stderr.writeAll("error:");
14371451 try ttyconf.setColor(stderr, .reset);
1438 // If the message has multiple lines, indent the non-initial ones to align them with the 'error:' text.
1439 var it = std.mem.splitScalar(u8, msg, '\n');
1440 try stderr.writeAll(it.first());
1441 while (it.next()) |line| {
1442 try stderr.print("\n {s}", .{line});
1452 if (std.mem.indexOfScalar(u8, msg, '\n') == null) {
1453 try stderr.print(" {s}\n", .{msg});
1454 } else switch (multiline_errors) {
1455 .indent => {
1456 var it = std.mem.splitScalar(u8, msg, '\n');
1457 try stderr.print(" {s}\n", .{it.first()});
1458 while (it.next()) |line| {
1459 try stderr.print(" {s}\n", .{line});
1460 }
1461 },
1462 .newline => try stderr.print("\n{s}\n", .{msg}),
1463 .none => try stderr.print(" {s}\n", .{msg}),
14431464 }
1444 try stderr.writeAll("\n");
14451465 }
14461466
1447 if (failing_step.result_failed_command) |cmd_str| {
1448 try ttyconf.setColor(stderr, .red);
1449 try stderr.writeAll("failed command: ");
1450 try ttyconf.setColor(stderr, .reset);
1451 try stderr.writeAll(cmd_str);
1452 try stderr.writeByte('\n');
1467 if (error_style.verboseContext()) {
1468 if (failing_step.result_failed_command) |cmd_str| {
1469 try ttyconf.setColor(stderr, .red);
1470 try stderr.writeAll("failed command: ");
1471 try ttyconf.setColor(stderr, .reset);
1472 try stderr.writeAll(cmd_str);
1473 try stderr.writeByte('\n');
1474 }
14531475 }
1476
1477 try stderr.writeByte('\n');
14541478}
14551479
14561480fn printSteps(builder: *std.Build, w: *Writer) !void {
......@@ -1505,11 +1529,20 @@ fn printUsage(b: *std.Build, w: *Writer) !void {
15051529 \\ -l, --list-steps Print available steps
15061530 \\ --verbose Print commands before executing them
15071531 \\ --color [auto|off|on] Enable or disable colored error messages
1508 \\ --prominent-compile-errors Buffer compile errors and display at end
1532 \\ --error-style [style] Control how build errors are printed
1533 \\ verbose (Default) Report errors with full context
1534 \\ minimal Report errors after summary, excluding context like command lines
1535 \\ verbose_clear Like 'verbose', but clear the terminal at the start of each update
1536 \\ minimal_clear Like 'minimal', but clear the terminal at the start of each update
1537 \\ --multiline-errors [style] Control how multi-line error messages are printed
1538 \\ indent (Default) Indent non-initial lines to align with initial line
1539 \\ newline Include a leading newline so that the error message is on its own lines
1540 \\ none Print as usual so the first line is misaligned
15091541 \\ --summary [mode] Control the printing of the build summary
15101542 \\ all Print the build summary in its entirety
15111543 \\ new Omit cached steps
1512 \\ failures (Default) Only print failed steps
1544 \\ failures (Default if short-lived) Only print failed steps
1545 \\ line (Default if long-lived) Only print the single-line summary
15131546 \\ none Do not print the build summary
15141547 \\ -j<N> Limit concurrent jobs (default is to use all CPU cores)
15151548 \\ --maxrss <bytes> Limit memory usage (default is to use available memory)
......@@ -1633,24 +1666,27 @@ fn argsRest(args: []const [:0]const u8, idx: usize) ?[]const [:0]const u8 {
16331666 return args[idx..];
16341667}
16351668
1636/// Perhaps in the future there could be an Advanced Options flag such as
1637/// --debug-build-runner-leaks which would make this function return instead of
1638/// calling exit.
1639fn cleanExit() void {
1640 std.debug.lockStdErr();
1641 process.exit(0);
1642}
1643
1644/// Perhaps in the future there could be an Advanced Options flag such as
1645/// --debug-build-runner-leaks which would make this function return instead of
1646/// calling exit.
1647fn uncleanExit() error{UncleanExit} {
1648 std.debug.lockStdErr();
1649 process.exit(1);
1650}
1651
16521669const Color = std.zig.Color;
1653const Summary = enum { all, new, failures, none };
1670const ErrorStyle = enum {
1671 verbose,
1672 minimal,
1673 verbose_clear,
1674 minimal_clear,
1675 fn verboseContext(s: ErrorStyle) bool {
1676 return switch (s) {
1677 .verbose, .verbose_clear => true,
1678 .minimal, .minimal_clear => false,
1679 };
1680 }
1681 fn clearOnUpdate(s: ErrorStyle) bool {
1682 return switch (s) {
1683 .verbose, .minimal => false,
1684 .verbose_clear, .minimal_clear => true,
1685 };
1686 }
1687};
1688const MultilineErrors = enum { indent, newline, none };
1689const Summary = enum { all, new, failures, line, none };
16541690
16551691fn get_tty_conf(color: Color, stderr: File) tty.Config {
16561692 return switch (color) {
lib/std/Build/Fuzz.zig+2-2
......@@ -178,7 +178,7 @@ fn rebuildTestsWorkerRunFallible(run: *Step.Run, gpa: Allocator, ttyconf: std.Io
178178 var buf: [256]u8 = undefined;
179179 const w = std.debug.lockStderrWriter(&buf);
180180 defer std.debug.unlockStderrWriter();
181 build_runner.printErrorMessages(gpa, &compile.step, .{ .ttyconf = ttyconf }, w, false) catch {};
181 build_runner.printErrorMessages(gpa, &compile.step, .{ .ttyconf = ttyconf }, w, .verbose, .indent) catch {};
182182 }
183183
184184 const rebuilt_bin_path = result catch |err| switch (err) {
......@@ -204,7 +204,7 @@ fn fuzzWorkerRun(
204204 var buf: [256]u8 = undefined;
205205 const w = std.debug.lockStderrWriter(&buf);
206206 defer std.debug.unlockStderrWriter();
207 build_runner.printErrorMessages(gpa, &run.step, .{ .ttyconf = fuzz.ttyconf }, w, false) catch {};
207 build_runner.printErrorMessages(gpa, &run.step, .{ .ttyconf = fuzz.ttyconf }, w, .verbose, .indent) catch {};
208208 return;
209209 },
210210 else => {
lib/std/zig.zig+2
......@@ -697,6 +697,8 @@ pub const EnvVar = enum {
697697 ZIG_LIB_DIR,
698698 ZIG_LIBC,
699699 ZIG_BUILD_RUNNER,
700 ZIG_BUILD_ERROR_STYLE,
701 ZIG_BUILD_MULTILINE_ERRORS,
700702 ZIG_VERBOSE_LINK,
701703 ZIG_VERBOSE_CC,
702704 ZIG_BTRFS_WORKAROUND,