authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-02-18 14:06:43-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-05-25 18:54:33-07:00
logef050483dff9f4fe57107b4c3ddba9a2692bdef4
tree653ab93c6235931023a31b3934b9195b2917a497
parent3262698fb171fb2ed33cf6786c0573e922ce9a80

build maker: rename Run to Maker


1 files changed, 587 insertions(+), 588 deletions(-)

lib/compiler/maker.zig+587-588
......@@ -1,3 +1,4 @@
1const Maker = @This();
12const builtin = @import("builtin");
23
34const std = @import("std");
......@@ -26,6 +27,28 @@ pub const std_options: std.Options = .{
2627 .http_disable_tls = true,
2728};
2829
30gpa: Allocator,
31graph: *Graph,
32install_paths: InstallPaths,
33scanned_config: *const ScannedConfig,
34steps: []Step,
35
36available_rss: usize,
37max_rss_is_default: bool,
38max_rss_mutex: Io.Mutex,
39skip_oom_steps: bool,
40unit_test_timeout_ns: ?u64,
41watch: bool,
42web_server: if (!builtin.single_threaded) ?WebServer else ?noreturn,
43/// Allocated into `gpa`.
44memory_blocked_steps: std.ArrayList(Configuration.Step.Index),
45/// Allocated into `gpa`.
46step_stack: std.AutoArrayHashMapUnmanaged(Configuration.Step.Index, void),
47
48error_style: ErrorStyle,
49multiline_errors: MultilineErrors,
50summary: Summary,
51
2952pub fn main(init: process.Init.Minimal) !void {
3053 // The build runner is often short-lived, but thanks to `--watch` and `--webui`, that's not
3154 // always the case. So, we do need a true gpa for some things.
......@@ -467,7 +490,7 @@ pub fn main(init: process.Init.Minimal) !void {
467490 .sub_path = cwd_relative,
468491 } else try install_prefix_path.join(arena, "include");
469492
470 var run: Run = .{
493 var maker: Maker = .{
471494 .gpa = gpa,
472495 .graph = &graph,
473496 .scanned_config = &scanned_config,
......@@ -495,16 +518,16 @@ pub fn main(init: process.Init.Minimal) !void {
495518 .summary = summary orelse if (watch or webui_listen != null) .line else .failures,
496519 };
497520 defer {
498 run.memory_blocked_steps.deinit(gpa);
499 run.step_stack.deinit(gpa);
521 maker.memory_blocked_steps.deinit(gpa);
522 maker.step_stack.deinit(gpa);
500523 }
501524
502 if (run.available_rss == 0) {
503 run.available_rss = process.totalSystemMemory() catch std.math.maxInt(u64);
504 run.max_rss_is_default = true;
525 if (maker.available_rss == 0) {
526 maker.available_rss = process.totalSystemMemory() catch std.math.maxInt(u64);
527 maker.max_rss_is_default = true;
505528 }
506529
507 run.prepare(step_names.items) catch |err| switch (err) {
530 maker.prepare(step_names.items) catch |err| switch (err) {
508531 error.DependencyLoopDetected, error.InsufficientMemory => {
509532 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};
510533 process.exit(1);
......@@ -515,17 +538,17 @@ pub fn main(init: process.Init.Minimal) !void {
515538 var w: Watch = w: {
516539 if (!watch) break :w undefined;
517540 if (!Watch.have_impl) fatal("--watch not yet implemented for {t}", .{builtin.os.tag});
518 break :w try .init(graph.cache.cwd, &scanned_config.configuration, run.steps);
541 break :w try .init(graph.cache.cwd, &scanned_config.configuration, maker.steps);
519542 };
520543
521544 const now = Io.Clock.Timestamp.now(io, .awake);
522545
523 run.web_server = if (webui_listen) |listen_address| ws: {
546 maker.web_server = if (webui_listen) |listen_address| ws: {
524547 if (builtin.single_threaded) unreachable; // `fatal` above
525548 break :ws .init(.{
526549 .gpa = gpa,
527550 .graph = &graph,
528 .all_steps = run.step_stack.keys(),
551 .all_steps = maker.step_stack.keys(),
529552 .root_prog_node = main_progress_node,
530553 .watch = watch,
531554 .listen_address = listen_address,
......@@ -534,20 +557,20 @@ pub fn main(init: process.Init.Minimal) !void {
534557 });
535558 } else null;
536559
537 if (run.web_server) |*ws| {
560 if (maker.web_server) |*ws| {
538561 ws.start() catch |err| fatal("failed to start web server: {t}", .{err});
539562 }
540563
541 rebuild: while (true) : (if (run.error_style.clearOnUpdate()) {
564 rebuild: while (true) : (if (maker.error_style.clearOnUpdate()) {
542565 const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode);
543566 defer io.unlockStderr();
544567 try stderr.file_writer.interface.writeAll("\x1B[2J\x1B[3J\x1B[H");
545568 }) {
546 if (run.web_server) |*ws| ws.startBuild();
569 if (maker.web_server) |*ws| ws.startBuild();
547570
548 try run.makeStepNames(step_names.items, main_progress_node, fuzz);
571 try maker.makeStepNames(step_names.items, main_progress_node, fuzz);
549572
550 if (run.web_server) |*web_server| {
573 if (maker.web_server) |*web_server| {
551574 if (fuzz) |mode| if (mode != .forever) fatal(
552575 "error: limited fuzzing is not implemented yet for --webui",
553576 .{},
......@@ -556,13 +579,13 @@ pub fn main(init: process.Init.Minimal) !void {
556579 web_server.finishBuild(.{ .fuzz = fuzz != null });
557580 }
558581
559 if (run.web_server) |*ws| {
582 if (maker.web_server) |*ws| {
560583 const c = &scanned_config.configuration;
561584 assert(!watch); // fatal error after CLI parsing
562585 while (true) switch (try ws.wait()) {
563586 .rebuild => {
564 for (run.step_stack.keys()) |step_index| {
565 const step = run.stepByIndex(step_index);
587 for (maker.step_stack.keys()) |step_index| {
588 const step = maker.stepByIndex(step_index);
566589 step.state = .precheck_done;
567590 const deps = step_index.ptr(c).deps.slice(c);
568591 step.pending_deps = @intCast(deps.len);
......@@ -576,7 +599,7 @@ pub fn main(init: process.Init.Minimal) !void {
576599 // Comptime-known guard to prevent including the logic below when `!Watch.have_impl`.
577600 if (!Watch.have_impl) unreachable;
578601
579 try w.update(gpa, run.step_stack.keys());
602 try w.update(gpa, maker.step_stack.keys());
580603
581604 // Wait until a file system notification arrives. Read all such events
582605 // until the buffer is empty. Then wait for a debounce interval, resetting
......@@ -585,7 +608,7 @@ pub fn main(init: process.Init.Minimal) !void {
585608 // recursive dependants.
586609 var caption_buf: [std.Progress.Node.max_name_len]u8 = undefined;
587610 const caption = std.fmt.bufPrint(&caption_buf, "watching {d} directories, {d} processes", .{
588 w.dir_count, countSubProcesses(run.steps, run.step_stack.keys()),
611 w.dir_count, countSubProcesses(maker.steps, maker.step_stack.keys()),
589612 }) catch &caption_buf;
590613 var debouncing_node = main_progress_node.start(caption, 0);
591614 var in_debounce = false;
......@@ -593,7 +616,7 @@ pub fn main(init: process.Init.Minimal) !void {
593616 .timeout => {
594617 assert(in_debounce);
595618 debouncing_node.end();
596 markFailedStepsDirty(gpa, run.steps, run.step_stack.keys());
619 markFailedStepsDirty(gpa, maker.steps, maker.step_stack.keys());
597620 continue :rebuild;
598621 },
599622 .dirty => if (!in_debounce) {
......@@ -634,436 +657,386 @@ fn countSubProcesses(make_steps: []Step, all_steps: []const Configuration.Step.I
634657 return count;
635658}
636659
637const Run = struct {
638 gpa: Allocator,
639 graph: *Graph,
640 install_paths: InstallPaths,
641 scanned_config: *const ScannedConfig,
642 steps: []Step,
643
644 available_rss: usize,
645 max_rss_is_default: bool,
646 max_rss_mutex: Io.Mutex,
647 skip_oom_steps: bool,
648 unit_test_timeout_ns: ?u64,
649 watch: bool,
650 web_server: if (!builtin.single_threaded) ?WebServer else ?noreturn,
651 /// Allocated into `gpa`.
652 memory_blocked_steps: std.ArrayList(Configuration.Step.Index),
653 /// Allocated into `gpa`.
654 step_stack: std.AutoArrayHashMapUnmanaged(Configuration.Step.Index, void),
655
656 error_style: ErrorStyle,
657 multiline_errors: MultilineErrors,
658 summary: Summary,
659
660 const InstallPaths = struct {
661 prefix: Path,
662 lib: Path,
663 bin: Path,
664 include: Path,
665 };
660const InstallPaths = struct {
661 prefix: Path,
662 lib: Path,
663 bin: Path,
664 include: Path,
665};
666666
667 fn stepByIndex(run: *const Run, i: Configuration.Step.Index) *Step {
668 return &run.steps[@intFromEnum(i)];
669 }
667fn stepByIndex(maker: *const Maker, i: Configuration.Step.Index) *Step {
668 return &maker.steps[@intFromEnum(i)];
669}
670670
671 fn prepare(run: *Run, step_names: []const []const u8) !void {
672 const gpa = run.gpa;
673 const graph = run.graph;
674 const arena = graph.arena;
675 const seed: u32 = graph.random_seed;
676 const step_stack = &run.step_stack;
677 const c = &run.scanned_config.configuration;
671fn prepare(maker: *Maker, step_names: []const []const u8) !void {
672 const gpa = maker.gpa;
673 const graph = maker.graph;
674 const arena = graph.arena;
675 const seed: u32 = graph.random_seed;
676 const step_stack = &maker.step_stack;
677 const c = &maker.scanned_config.configuration;
678678
679 @memset(run.steps, .{});
679 @memset(maker.steps, .{});
680680
681 if (step_names.len == 0) {
682 try step_stack.put(gpa, c.default_step, {});
683 } else {
684 try step_stack.ensureUnusedCapacity(gpa, step_names.len);
685 for (0..step_names.len) |i| {
686 const step_name = step_names[step_names.len - i - 1];
687 const s = run.scanned_config.top_level_steps.get(step_name) orelse {
688 log.info("to list available steps: zig build -l", .{});
689 fatal("no such step: {s}", .{step_name});
690 };
691 step_stack.putAssumeCapacity(s, {});
692 }
681 if (step_names.len == 0) {
682 try step_stack.put(gpa, c.default_step, {});
683 } else {
684 try step_stack.ensureUnusedCapacity(gpa, step_names.len);
685 for (0..step_names.len) |i| {
686 const step_name = step_names[step_names.len - i - 1];
687 const s = maker.scanned_config.top_level_steps.get(step_name) orelse {
688 log.info("to list available steps: zig build -l", .{});
689 fatal("no such step: {s}", .{step_name});
690 };
691 step_stack.putAssumeCapacity(s, {});
693692 }
693 }
694694
695 const starting_steps = try arena.dupe(Configuration.Step.Index, step_stack.keys());
695 const starting_steps = try arena.dupe(Configuration.Step.Index, step_stack.keys());
696696
697 var rng = std.Random.DefaultPrng.init(seed);
698 const rand = rng.random();
699 rand.shuffle(Configuration.Step.Index, starting_steps);
697 var rng = std.Random.DefaultPrng.init(seed);
698 const rand = rng.random();
699 rand.shuffle(Configuration.Step.Index, starting_steps);
700700
701 for (starting_steps) |s| {
702 try constructGraphAndCheckForDependencyLoop(gpa, c, run.steps, s, &run.step_stack, rand);
703 }
701 for (starting_steps) |s| {
702 try constructGraphAndCheckForDependencyLoop(gpa, c, maker.steps, s, &maker.step_stack, rand);
703 }
704704
705 {
706 // Check that we have enough memory to complete the build.
707 var any_problems = false;
708 var max_needed: usize = 0;
709 for (step_stack.keys()) |step_index| {
710 const make_step = run.stepByIndex(step_index);
711 const conf_step = step_index.ptr(c);
712 const max_rss = conf_step.max_rss.toBytes();
713 if (max_rss == 0) continue;
714 max_needed = @max(max_needed, max_rss);
715 if (max_rss > run.available_rss) {
716 if (run.skip_oom_steps) {
717 make_step.state = .skipped_oom;
718 for (make_step.dependants.items) |dependant| {
719 run.stepByIndex(dependant).pending_deps -= 1;
720 }
721 } else {
722 log.err("{s}{s}: this step declares an upper bound of {d} bytes of memory, exceeding the available {d} bytes of memory", .{
723 conf_step.owner.depPrefixSlice(c),
724 conf_step.name.slice(c),
725 max_rss,
726 run.available_rss,
727 });
728 any_problems = true;
705 {
706 // Check that we have enough memory to complete the build.
707 var any_problems = false;
708 var max_needed: usize = 0;
709 for (step_stack.keys()) |step_index| {
710 const make_step = maker.stepByIndex(step_index);
711 const conf_step = step_index.ptr(c);
712 const max_rss = conf_step.max_rss.toBytes();
713 if (max_rss == 0) continue;
714 max_needed = @max(max_needed, max_rss);
715 if (max_rss > maker.available_rss) {
716 if (maker.skip_oom_steps) {
717 make_step.state = .skipped_oom;
718 for (make_step.dependants.items) |dependant| {
719 maker.stepByIndex(dependant).pending_deps -= 1;
729720 }
730 }
731 }
732 if (any_problems) {
733 if (run.max_rss_is_default) {
734 std.log.info("use --maxrss {d} to proceed, risking system memory exhaustion", .{
735 max_needed,
721 } else {
722 log.err("{s}{s}: this step declares an upper bound of {d} bytes of memory, exceeding the available {d} bytes of memory", .{
723 conf_step.owner.depPrefixSlice(c),
724 conf_step.name.slice(c),
725 max_rss,
726 maker.available_rss,
736727 });
728 any_problems = true;
737729 }
738 return error.InsufficientMemory;
739730 }
740731 }
732 if (any_problems) {
733 if (maker.max_rss_is_default) {
734 std.log.info("use --maxrss {d} to proceed, risking system memory exhaustion", .{
735 max_needed,
736 });
737 }
738 return error.InsufficientMemory;
739 }
741740 }
741}
742742
743 fn makeStepNames(
744 run: *Run,
745 step_names: []const []const u8,
746 parent_prog_node: std.Progress.Node,
747 fuzz: ?Fuzz.Mode,
748 ) !void {
749 const graph = run.graph;
750 const gpa = run.gpa;
751 const io = graph.io;
752 const step_stack = &run.step_stack;
753 const top_level_steps = &run.scanned_config.top_level_steps;
754 const c = &run.scanned_config.configuration;
755
756 {
757 // Collect the initial set of tasks (those with no outstanding dependencies) into a buffer,
758 // then spawn them. The buffer is so that we don't race with `makeStep` and end up thinking
759 // a step is initial when it actually became ready due to an earlier initial step.
760 var initial_set: std.ArrayList(Configuration.Step.Index) = .empty;
761 defer initial_set.deinit(gpa);
762 try initial_set.ensureUnusedCapacity(gpa, step_stack.count());
763 for (step_stack.keys()) |step_index| {
764 const s = run.stepByIndex(step_index);
765 if (s.state == .precheck_done and s.pending_deps == 0) {
766 initial_set.appendAssumeCapacity(step_index);
767 }
743fn makeStepNames(
744 maker: *Maker,
745 step_names: []const []const u8,
746 parent_prog_node: std.Progress.Node,
747 fuzz: ?Fuzz.Mode,
748) !void {
749 const graph = maker.graph;
750 const gpa = maker.gpa;
751 const io = graph.io;
752 const step_stack = &maker.step_stack;
753 const top_level_steps = &maker.scanned_config.top_level_steps;
754 const c = &maker.scanned_config.configuration;
755
756 {
757 // Collect the initial set of tasks (those with no outstanding dependencies) into a buffer,
758 // then spawn them. The buffer is so that we don't race with `makeStep` and end up thinking
759 // a step is initial when it actually became ready due to an earlier initial step.
760 var initial_set: std.ArrayList(Configuration.Step.Index) = .empty;
761 defer initial_set.deinit(gpa);
762 try initial_set.ensureUnusedCapacity(gpa, step_stack.count());
763 for (step_stack.keys()) |step_index| {
764 const s = maker.stepByIndex(step_index);
765 if (s.state == .precheck_done and s.pending_deps == 0) {
766 initial_set.appendAssumeCapacity(step_index);
768767 }
768 }
769769
770 const step_prog = parent_prog_node.start("steps", step_stack.count());
771 defer step_prog.end();
770 const step_prog = parent_prog_node.start("steps", step_stack.count());
771 defer step_prog.end();
772772
773 var group: Io.Group = .init;
774 defer group.cancel(io);
775 // Start working on all of the initial steps...
776 for (initial_set.items) |step_index| try stepReady(run, &group, step_index, step_prog);
777 // ...and `makeStep` will trigger every other step when their last dependency finishes.
778 try group.await(io);
779 }
773 var group: Io.Group = .init;
774 defer group.cancel(io);
775 // Start working on all of the initial steps...
776 for (initial_set.items) |step_index| try stepReady(maker, &group, step_index, step_prog);
777 // ...and `makeStep` will trigger every other step when their last dependency finishes.
778 try group.await(io);
779 }
780780
781 assert(run.memory_blocked_steps.items.len == 0);
781 assert(maker.memory_blocked_steps.items.len == 0);
782782
783 var test_pass_count: usize = 0;
784 var test_skip_count: usize = 0;
785 var test_fail_count: usize = 0;
786 var test_crash_count: usize = 0;
787 var test_timeout_count: usize = 0;
783 var test_pass_count: usize = 0;
784 var test_skip_count: usize = 0;
785 var test_fail_count: usize = 0;
786 var test_crash_count: usize = 0;
787 var test_timeout_count: usize = 0;
788788
789 var test_count: usize = 0;
789 var test_count: usize = 0;
790790
791 var success_count: usize = 0;
792 var skipped_count: usize = 0;
793 var failure_count: usize = 0;
794 var pending_count: usize = 0;
795 var total_compile_errors: usize = 0;
791 var success_count: usize = 0;
792 var skipped_count: usize = 0;
793 var failure_count: usize = 0;
794 var pending_count: usize = 0;
795 var total_compile_errors: usize = 0;
796796
797 var cleanup_task = io.async(cleanTmpFiles, .{ io, step_stack.keys() });
798 defer cleanup_task.await(io);
797 var cleanup_task = io.async(cleanTmpFiles, .{ io, step_stack.keys() });
798 defer cleanup_task.await(io);
799799
800 for (step_stack.keys()) |step_index| {
801 const make_step = run.stepByIndex(step_index);
802 test_pass_count += make_step.test_results.passCount();
803 test_skip_count += make_step.test_results.skip_count;
804 test_fail_count += make_step.test_results.fail_count;
805 test_crash_count += make_step.test_results.crash_count;
806 test_timeout_count += make_step.test_results.timeout_count;
800 for (step_stack.keys()) |step_index| {
801 const make_step = maker.stepByIndex(step_index);
802 test_pass_count += make_step.test_results.passCount();
803 test_skip_count += make_step.test_results.skip_count;
804 test_fail_count += make_step.test_results.fail_count;
805 test_crash_count += make_step.test_results.crash_count;
806 test_timeout_count += make_step.test_results.timeout_count;
807807
808 test_count += make_step.test_results.test_count;
808 test_count += make_step.test_results.test_count;
809809
810 switch (make_step.state) {
811 .precheck_unstarted => unreachable,
812 .precheck_started => unreachable,
813 .precheck_done => unreachable,
814 .dependency_failure => pending_count += 1,
815 .success => success_count += 1,
816 .skipped, .skipped_oom => skipped_count += 1,
817 .failure => {
818 failure_count += 1;
819 const compile_errors_len = make_step.result_error_bundle.errorMessageCount();
820 if (compile_errors_len > 0) {
821 total_compile_errors += compile_errors_len;
822 }
823 },
824 }
810 switch (make_step.state) {
811 .precheck_unstarted => unreachable,
812 .precheck_started => unreachable,
813 .precheck_done => unreachable,
814 .dependency_failure => pending_count += 1,
815 .success => success_count += 1,
816 .skipped, .skipped_oom => skipped_count += 1,
817 .failure => {
818 failure_count += 1;
819 const compile_errors_len = make_step.result_error_bundle.errorMessageCount();
820 if (compile_errors_len > 0) {
821 total_compile_errors += compile_errors_len;
822 }
823 },
825824 }
825 }
826826
827 if (fuzz) |mode| blk: {
828 switch (builtin.os.tag) {
829 // Current implementation depends on two things that need to be ported to Windows:
830 // * Memory-mapping to share data between the fuzzer and build runner.
831 // * COFF/PE support added to `std.debug.Info` (it needs a batching API for resolving
832 // many addresses to source locations).
833 .windows => fatal("--fuzz not yet implemented for {t}", .{builtin.os.tag}),
834 else => {},
835 }
836 if (@bitSizeOf(usize) != 64) {
837 // Current implementation depends on posix.mmap()'s second parameter, `length: usize`,
838 // being compatible with file system's u64 return value. This is not the case
839 // on 32-bit platforms.
840 // Affects or affected by issues #5185, #22523, and #22464.
841 fatal("--fuzz not yet implemented on {d}-bit platforms", .{@bitSizeOf(usize)});
842 }
843
844 switch (mode) {
845 .forever => break :blk,
846 .limit => {},
847 }
827 if (fuzz) |mode| blk: {
828 switch (builtin.os.tag) {
829 // Current implementation depends on two things that need to be ported to Windows:
830 // * Memory-mapping to share data between the fuzzer and build runner.
831 // * COFF/PE support added to `std.debug.Info` (it needs a batching API for resolving
832 // many addresses to source locations).
833 .windows => fatal("--fuzz not yet implemented for {t}", .{builtin.os.tag}),
834 else => {},
835 }
836 if (@bitSizeOf(usize) != 64) {
837 // Current implementation depends on posix.mmap()'s second parameter, `length: usize`,
838 // being compatible with file system's u64 return value. This is not the case
839 // on 32-bit platforms.
840 // Affects or affected by issues #5185, #22523, and #22464.
841 fatal("--fuzz not yet implemented on {d}-bit platforms", .{@bitSizeOf(usize)});
842 }
848843
849 assert(mode == .limit);
850 var f = Fuzz.init(
851 gpa,
852 io,
853 step_stack.keys(),
854 parent_prog_node,
855 mode,
856 ) catch |err| fatal("failed to start fuzzer: {t}", .{err});
857 defer f.deinit();
858
859 f.start();
860 try f.waitAndPrintReport();
844 switch (mode) {
845 .forever => break :blk,
846 .limit => {},
861847 }
862848
863 // Every test has a state
864 assert(test_pass_count + test_skip_count + test_fail_count + test_crash_count + test_timeout_count == test_count);
849 assert(mode == .limit);
850 var f = Fuzz.init(
851 gpa,
852 io,
853 step_stack.keys(),
854 parent_prog_node,
855 mode,
856 ) catch |err| fatal("failed to start fuzzer: {t}", .{err});
857 defer f.deinit();
858
859 f.start();
860 try f.waitAndPrintReport();
861 }
865862
866 if (failure_count == 0) {
867 std.Progress.setStatus(.success);
868 } else {
869 std.Progress.setStatus(.failure);
870 }
863 // Every test has a state
864 assert(test_pass_count + test_skip_count + test_fail_count + test_crash_count + test_timeout_count == test_count);
871865
872 summary: {
873 switch (run.summary) {
874 .all, .new, .line => {},
875 .failures => if (failure_count == 0) break :summary,
876 .none => break :summary,
877 }
866 if (failure_count == 0) {
867 std.Progress.setStatus(.success);
868 } else {
869 std.Progress.setStatus(.failure);
870 }
878871
879 const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode);
880 defer io.unlockStderr();
881 const t = stderr.terminal();
882 const w = &stderr.file_writer.interface;
872 summary: {
873 switch (maker.summary) {
874 .all, .new, .line => {},
875 .failures => if (failure_count == 0) break :summary,
876 .none => break :summary,
877 }
883878
884 const total_count = success_count + failure_count + pending_count + skipped_count;
885 t.setColor(.cyan) catch {};
886 t.setColor(.bold) catch {};
887 w.writeAll("Build Summary: ") catch {};
888 t.setColor(.reset) catch {};
889 w.print("{d}/{d} steps succeeded", .{ success_count, total_count }) catch {};
890 {
891 t.setColor(.dim) catch {};
892 var first = true;
893 if (skipped_count > 0) {
894 w.print("{s}{d} skipped", .{ if (first) " (" else ", ", skipped_count }) catch {};
895 first = false;
896 }
897 if (failure_count > 0) {
898 w.print("{s}{d} failed", .{ if (first) " (" else ", ", failure_count }) catch {};
899 first = false;
900 }
901 if (!first) w.writeByte(')') catch {};
902 t.setColor(.reset) catch {};
879 const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode);
880 defer io.unlockStderr();
881 const t = stderr.terminal();
882 const w = &stderr.file_writer.interface;
883
884 const total_count = success_count + failure_count + pending_count + skipped_count;
885 t.setColor(.cyan) catch {};
886 t.setColor(.bold) catch {};
887 w.writeAll("Build Summary: ") catch {};
888 t.setColor(.reset) catch {};
889 w.print("{d}/{d} steps succeeded", .{ success_count, total_count }) catch {};
890 {
891 t.setColor(.dim) catch {};
892 var first = true;
893 if (skipped_count > 0) {
894 w.print("{s}{d} skipped", .{ if (first) " (" else ", ", skipped_count }) catch {};
895 first = false;
903896 }
897 if (failure_count > 0) {
898 w.print("{s}{d} failed", .{ if (first) " (" else ", ", failure_count }) catch {};
899 first = false;
900 }
901 if (!first) w.writeByte(')') catch {};
902 t.setColor(.reset) catch {};
903 }
904904
905 if (test_count > 0) {
906 w.print("; {d}/{d} tests passed", .{ test_pass_count, test_count }) catch {};
907 t.setColor(.dim) catch {};
908 var first = true;
909 if (test_skip_count > 0) {
910 w.print("{s}{d} skipped", .{ if (first) " (" else ", ", test_skip_count }) catch {};
911 first = false;
912 }
913 if (test_fail_count > 0) {
914 w.print("{s}{d} failed", .{ if (first) " (" else ", ", test_fail_count }) catch {};
915 first = false;
916 }
917 if (test_crash_count > 0) {
918 w.print("{s}{d} crashed", .{ if (first) " (" else ", ", test_crash_count }) catch {};
919 first = false;
920 }
921 if (test_timeout_count > 0) {
922 w.print("{s}{d} timed out", .{ if (first) " (" else ", ", test_timeout_count }) catch {};
923 first = false;
924 }
925 if (!first) w.writeByte(')') catch {};
926 t.setColor(.reset) catch {};
905 if (test_count > 0) {
906 w.print("; {d}/{d} tests passed", .{ test_pass_count, test_count }) catch {};
907 t.setColor(.dim) catch {};
908 var first = true;
909 if (test_skip_count > 0) {
910 w.print("{s}{d} skipped", .{ if (first) " (" else ", ", test_skip_count }) catch {};
911 first = false;
927912 }
913 if (test_fail_count > 0) {
914 w.print("{s}{d} failed", .{ if (first) " (" else ", ", test_fail_count }) catch {};
915 first = false;
916 }
917 if (test_crash_count > 0) {
918 w.print("{s}{d} crashed", .{ if (first) " (" else ", ", test_crash_count }) catch {};
919 first = false;
920 }
921 if (test_timeout_count > 0) {
922 w.print("{s}{d} timed out", .{ if (first) " (" else ", ", test_timeout_count }) catch {};
923 first = false;
924 }
925 if (!first) w.writeByte(')') catch {};
926 t.setColor(.reset) catch {};
927 }
928928
929 w.writeAll("\n") catch {};
929 w.writeAll("\n") catch {};
930930
931 if (run.summary == .line) break :summary;
931 if (maker.summary == .line) break :summary;
932932
933 // Print a fancy tree with build results.
934 var step_stack_copy = try step_stack.clone(gpa);
935 defer step_stack_copy.deinit(gpa);
933 // Print a fancy tree with build results.
934 var step_stack_copy = try step_stack.clone(gpa);
935 defer step_stack_copy.deinit(gpa);
936936
937 var print_node: PrintNode = .{ .parent = null };
938 if (step_names.len == 0) {
939 print_node.last = true;
940 printTreeStep(run, c.default_step, t, &print_node, &step_stack_copy) catch |err| switch (err) {
937 var print_node: PrintNode = .{ .parent = null };
938 if (step_names.len == 0) {
939 print_node.last = true;
940 printTreeStep(maker, c.default_step, t, &print_node, &step_stack_copy) catch |err| switch (err) {
941 error.Canceled => |e| return e,
942 else => {},
943 };
944 } else {
945 const last_index = if (maker.summary == .all) top_level_steps.count() else blk: {
946 var i: usize = step_names.len;
947 while (i > 0) {
948 i -= 1;
949 const step_index = top_level_steps.get(step_names[i]).?;
950 const step = maker.stepByIndex(step_index);
951 const found = switch (maker.summary) {
952 .all, .line, .none => unreachable,
953 .failures => step.state != .success,
954 .new => !step.result_cached,
955 };
956 if (found) break :blk i;
957 }
958 break :blk top_level_steps.count();
959 };
960 for (step_names, 0..) |step_name, i| {
961 const step_index = top_level_steps.get(step_name).?;
962 print_node.last = i + 1 == last_index;
963 printTreeStep(maker, step_index, t, &print_node, &step_stack_copy) catch |err| switch (err) {
941964 error.Canceled => |e| return e,
942965 else => {},
943966 };
944 } else {
945 const last_index = if (run.summary == .all) top_level_steps.count() else blk: {
946 var i: usize = step_names.len;
947 while (i > 0) {
948 i -= 1;
949 const step_index = top_level_steps.get(step_names[i]).?;
950 const step = run.stepByIndex(step_index);
951 const found = switch (run.summary) {
952 .all, .line, .none => unreachable,
953 .failures => step.state != .success,
954 .new => !step.result_cached,
955 };
956 if (found) break :blk i;
957 }
958 break :blk top_level_steps.count();
959 };
960 for (step_names, 0..) |step_name, i| {
961 const step_index = top_level_steps.get(step_name).?;
962 print_node.last = i + 1 == last_index;
963 printTreeStep(run, step_index, t, &print_node, &step_stack_copy) catch |err| switch (err) {
964 error.Canceled => |e| return e,
965 else => {},
966 };
967 }
968967 }
969 w.writeByte('\n') catch {};
970968 }
969 w.writeByte('\n') catch {};
970 }
971971
972 if (run.watch or run.web_server != null) return;
972 if (maker.watch or maker.web_server != null) return;
973973
974 // Perhaps in the future there could be an Advanced Options flag such as
975 // --debug-build-runner-leaks which would make this code return instead of
976 // calling exit.
974 // Perhaps in the future there could be an Advanced Options flag such as
975 // --debug-build-runner-leaks which would make this code return instead of
976 // calling exit.
977977
978 const code: u8 = code: {
979 if (failure_count == 0) break :code 0; // success
980 if (run.error_style.verboseContext()) break :code 1; // failure; print build command
981 break :code 2; // failure; do not print build command
982 };
983 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};
984 process.exit(code);
985 }
978 const code: u8 = code: {
979 if (failure_count == 0) break :code 0; // success
980 if (maker.error_style.verboseContext()) break :code 1; // failure; print build command
981 break :code 2; // failure; do not print build command
982 };
983 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};
984 process.exit(code);
985}
986986
987 fn stepReady(
988 run: *Run,
989 group: *Io.Group,
990 step_index: Configuration.Step.Index,
991 root_prog_node: std.Progress.Node,
992 ) Io.Cancelable!void {
993 const graph = run.graph;
994 const io = graph.io;
995 const c = &run.scanned_config.configuration;
996 const max_rss = step_index.ptr(c).max_rss.toBytes();
997 if (max_rss != 0) {
998 try run.max_rss_mutex.lock(io);
999 defer run.max_rss_mutex.unlock(io);
1000 if (run.available_rss < max_rss) {
1001 // Running this step right now could possibly exceed the allotted RSS.
1002 run.memory_blocked_steps.append(run.gpa, step_index) catch
1003 @panic("TODO eliminate memory allocation here");
1004 return;
1005 }
1006 run.available_rss -= max_rss;
987fn stepReady(
988 maker: *Maker,
989 group: *Io.Group,
990 step_index: Configuration.Step.Index,
991 root_prog_node: std.Progress.Node,
992) Io.Cancelable!void {
993 const graph = maker.graph;
994 const io = graph.io;
995 const c = &maker.scanned_config.configuration;
996 const max_rss = step_index.ptr(c).max_rss.toBytes();
997 if (max_rss != 0) {
998 try maker.max_rss_mutex.lock(io);
999 defer maker.max_rss_mutex.unlock(io);
1000 if (maker.available_rss < max_rss) {
1001 // Running this step right now could possibly exceed the allotted RSS.
1002 maker.memory_blocked_steps.append(maker.gpa, step_index) catch
1003 @panic("TODO eliminate memory allocation here");
1004 return;
10071005 }
1008 group.async(io, makeStep, .{ run, group, step_index, root_prog_node });
1006 maker.available_rss -= max_rss;
10091007 }
1008 group.async(io, makeStep, .{ maker, group, step_index, root_prog_node });
1009}
10101010
1011 /// Runs the "make" function of the single step `s`, updates its state, and then spawns newly-ready
1012 /// dependant steps in `group`. If `s` makes an RSS claim (i.e. `s.max_rss != 0`), the caller must
1013 /// have already subtracted this value from `run.available_rss`. This function will release the RSS
1014 /// claim (i.e. add `s.max_rss` back into `run.available_rss`) and queue any viable memory-blocked
1015 /// steps after "make" completes for `s`.
1016 fn makeStep(
1017 run: *Run,
1018 group: *Io.Group,
1019 step_index: Configuration.Step.Index,
1020 root_prog_node: std.Progress.Node,
1021 ) Io.Cancelable!void {
1022 const graph = run.graph;
1023 const io = graph.io;
1024 const gpa = run.gpa;
1025 const c = &run.scanned_config.configuration;
1026 const conf_step = step_index.ptr(c);
1027 const step_name = conf_step.name.slice(c);
1028 const deps = conf_step.deps.slice(c);
1029 const make_step = run.stepByIndex(step_index);
1030
1031 {
1032 const step_prog_node = root_prog_node.start(step_name, 0);
1033 defer step_prog_node.end();
1034
1035 if (run.web_server) |*ws| ws.updateStepStatus(step_index, .wip);
1036
1037 const new_state: Step.State = for (deps) |dep_index| {
1038 const dep_make_step = run.stepByIndex(dep_index);
1039 switch (@atomicLoad(Step.State, &dep_make_step.state, .monotonic)) {
1040 .precheck_unstarted => unreachable,
1041 .precheck_started => unreachable,
1042 .precheck_done => unreachable,
1043
1044 .failure,
1045 .dependency_failure,
1046 .skipped_oom,
1047 => break .dependency_failure,
1048
1049 .success, .skipped => {},
1050 }
1051 } else if (make_step.make(.{
1052 .progress_node = step_prog_node,
1053 .watch = run.watch,
1054 .web_server = if (run.web_server) |*ws| ws else null,
1055 .unit_test_timeout_ns = run.unit_test_timeout_ns,
1056 .gpa = gpa,
1057 })) state: {
1058 break :state .success;
1059 } else |err| switch (err) {
1060 error.MakeFailed => .failure,
1061 error.MakeSkipped => .skipped,
1062 };
1063
1064 @atomicStore(Step.State, &make_step.state, new_state, .monotonic);
1065
1066 switch (new_state) {
1011/// Runs the "make" function of the single step `s`, updates its state, and then spawns newly-ready
1012/// dependant steps in `group`. If `s` makes an RSS claim (i.e. `s.max_rss != 0`), the caller must
1013/// have already subtracted this value from `maker.available_rss`. This function will release the RSS
1014/// claim (i.e. add `s.max_rss` back into `maker.available_rss`) and queue any viable memory-blocked
1015/// steps after "make" completes for `s`.
1016fn makeStep(
1017 maker: *Maker,
1018 group: *Io.Group,
1019 step_index: Configuration.Step.Index,
1020 root_prog_node: std.Progress.Node,
1021) Io.Cancelable!void {
1022 const graph = maker.graph;
1023 const io = graph.io;
1024 const gpa = maker.gpa;
1025 const c = &maker.scanned_config.configuration;
1026 const conf_step = step_index.ptr(c);
1027 const step_name = conf_step.name.slice(c);
1028 const deps = conf_step.deps.slice(c);
1029 const make_step = maker.stepByIndex(step_index);
1030
1031 {
1032 const step_prog_node = root_prog_node.start(step_name, 0);
1033 defer step_prog_node.end();
1034
1035 if (maker.web_server) |*ws| ws.updateStepStatus(step_index, .wip);
1036
1037 const new_state: Step.State = for (deps) |dep_index| {
1038 const dep_make_step = maker.stepByIndex(dep_index);
1039 switch (@atomicLoad(Step.State, &dep_make_step.state, .monotonic)) {
10671040 .precheck_unstarted => unreachable,
10681041 .precheck_started => unreachable,
10691042 .precheck_done => unreachable,
......@@ -1071,234 +1044,260 @@ const Run = struct {
10711044 .failure,
10721045 .dependency_failure,
10731046 .skipped_oom,
1074 => {
1075 if (run.web_server) |*ws| ws.updateStepStatus(step_index, .failure);
1076 std.Progress.setStatus(.failure_working);
1077 },
1047 => break .dependency_failure,
10781048
1079 .success,
1080 .skipped,
1081 => {
1082 if (run.web_server) |*ws| ws.updateStepStatus(step_index, .success);
1083 },
1049 .success, .skipped => {},
10841050 }
1051 } else if (make_step.make(.{
1052 .progress_node = step_prog_node,
1053 .watch = maker.watch,
1054 .web_server = if (maker.web_server) |*ws| ws else null,
1055 .unit_test_timeout_ns = maker.unit_test_timeout_ns,
1056 .gpa = gpa,
1057 })) state: {
1058 break :state .success;
1059 } else |err| switch (err) {
1060 error.MakeFailed => .failure,
1061 error.MakeSkipped => .skipped,
1062 };
1063
1064 @atomicStore(Step.State, &make_step.state, new_state, .monotonic);
1065
1066 switch (new_state) {
1067 .precheck_unstarted => unreachable,
1068 .precheck_started => unreachable,
1069 .precheck_done => unreachable,
1070
1071 .failure,
1072 .dependency_failure,
1073 .skipped_oom,
1074 => {
1075 if (maker.web_server) |*ws| ws.updateStepStatus(step_index, .failure);
1076 std.Progress.setStatus(.failure_working);
1077 },
1078
1079 .success,
1080 .skipped,
1081 => {
1082 if (maker.web_server) |*ws| ws.updateStepStatus(step_index, .success);
1083 },
10851084 }
1085 }
10861086
1087 // No matter the result, we want to display error/warning messages.
1088 if (make_step.result_error_bundle.errorMessageCount() > 0 or
1089 make_step.result_error_msgs.items.len > 0 or
1090 make_step.result_stderr.len > 0)
1091 {
1092 const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode);
1093 defer io.unlockStderr();
1094 printErrorMessages(gpa, c, run.steps, step_index, .{}, stderr.terminal(), run.error_style, run.multiline_errors) catch |err| switch (err) {
1087 // No matter the result, we want to display error/warning messages.
1088 if (make_step.result_error_bundle.errorMessageCount() > 0 or
1089 make_step.result_error_msgs.items.len > 0 or
1090 make_step.result_stderr.len > 0)
1091 {
1092 const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode);
1093 defer io.unlockStderr();
1094 printErrorMessages(gpa, c, maker.steps, step_index, .{}, stderr.terminal(), maker.error_style, maker.multiline_errors) catch |err| switch (err) {
1095 error.Canceled => |e| return e,
1096 error.WriteFailed => switch (stderr.file_writer.err.?) {
10951097 error.Canceled => |e| return e,
1096 error.WriteFailed => switch (stderr.file_writer.err.?) {
1097 error.Canceled => |e| return e,
1098 else => {},
1099 },
11001098 else => {},
1101 };
1102 }
1099 },
1100 else => {},
1101 };
1102 }
11031103
1104 const max_rss = conf_step.max_rss.toBytes();
1105 if (max_rss != 0) {
1106 var dispatch_set: std.ArrayList(Configuration.Step.Index) = .empty;
1107 defer dispatch_set.deinit(gpa);
1108
1109 // Release our RSS claim and kick off some blocked steps if possible. We use `dispatch_set`
1110 // as a staging buffer to avoid recursing into `makeStep` while `run.max_rss_mutex` is held.
1111 {
1112 try run.max_rss_mutex.lock(io);
1113 defer run.max_rss_mutex.unlock(io);
1114 run.available_rss += max_rss;
1115 dispatch_set.ensureUnusedCapacity(gpa, run.memory_blocked_steps.items.len) catch
1116 @panic("TODO eliminate memory allocation here");
1117 while (run.memory_blocked_steps.getLast()) |candidate_index| {
1118 const candidate_max_rss = candidate_index.ptr(c).max_rss.toBytes();
1119 if (run.available_rss < candidate_max_rss) break;
1120 assert(run.memory_blocked_steps.pop() == candidate_index);
1121 dispatch_set.appendAssumeCapacity(candidate_index);
1122 }
1123 }
1124 for (dispatch_set.items) |candidate| {
1125 group.async(io, makeStep, .{ run, group, candidate, root_prog_node });
1104 const max_rss = conf_step.max_rss.toBytes();
1105 if (max_rss != 0) {
1106 var dispatch_set: std.ArrayList(Configuration.Step.Index) = .empty;
1107 defer dispatch_set.deinit(gpa);
1108
1109 // Release our RSS claim and kick off some blocked steps if possible. We use `dispatch_set`
1110 // as a staging buffer to avoid recursing into `makeStep` while `maker.max_rss_mutex` is held.
1111 {
1112 try maker.max_rss_mutex.lock(io);
1113 defer maker.max_rss_mutex.unlock(io);
1114 maker.available_rss += max_rss;
1115 dispatch_set.ensureUnusedCapacity(gpa, maker.memory_blocked_steps.items.len) catch
1116 @panic("TODO eliminate memory allocation here");
1117 while (maker.memory_blocked_steps.getLast()) |candidate_index| {
1118 const candidate_max_rss = candidate_index.ptr(c).max_rss.toBytes();
1119 if (maker.available_rss < candidate_max_rss) break;
1120 assert(maker.memory_blocked_steps.pop() == candidate_index);
1121 dispatch_set.appendAssumeCapacity(candidate_index);
11261122 }
11271123 }
1124 for (dispatch_set.items) |candidate| {
1125 group.async(io, makeStep, .{ maker, group, candidate, root_prog_node });
1126 }
1127 }
11281128
1129 for (make_step.dependants.items) |dependant_index| {
1130 const dependant = run.stepByIndex(dependant_index);
1131 // `.acq_rel` synchronizes with itself to ensure all dependencies' final states are visible when this hits 0.
1132 if (@atomicRmw(u32, &dependant.pending_deps, .Sub, 1, .acq_rel) == 1) {
1133 try stepReady(run, group, dependant_index, root_prog_node);
1134 }
1129 for (make_step.dependants.items) |dependant_index| {
1130 const dependant = maker.stepByIndex(dependant_index);
1131 // `.acq_rel` synchronizes with itself to ensure all dependencies' final states are visible when this hits 0.
1132 if (@atomicRmw(u32, &dependant.pending_deps, .Sub, 1, .acq_rel) == 1) {
1133 try stepReady(maker, group, dependant_index, root_prog_node);
11351134 }
11361135 }
1136}
11371137
1138 fn printTreeStep(
1139 run: *const Run,
1140 step_index: Configuration.Step.Index,
1141 stderr: Io.Terminal,
1142 parent_node: *PrintNode,
1143 step_stack: *std.AutoArrayHashMapUnmanaged(Configuration.Step.Index, void),
1144 ) !void {
1145 const writer = stderr.writer;
1146 const first = step_stack.swapRemove(step_index);
1147 const summary = run.summary;
1148 const c = &run.scanned_config.configuration;
1149 const conf_step = step_index.ptr(c);
1150 const make_step = run.stepByIndex(step_index);
1151 const skip = switch (summary) {
1152 .none, .line => unreachable,
1153 .all => false,
1154 .new => make_step.result_cached,
1155 .failures => make_step.state == .success,
1156 };
1157 if (skip) return;
1158 try printPrefix(parent_node, stderr);
1138fn printTreeStep(
1139 maker: *const Maker,
1140 step_index: Configuration.Step.Index,
1141 stderr: Io.Terminal,
1142 parent_node: *PrintNode,
1143 step_stack: *std.AutoArrayHashMapUnmanaged(Configuration.Step.Index, void),
1144) !void {
1145 const writer = stderr.writer;
1146 const first = step_stack.swapRemove(step_index);
1147 const summary = maker.summary;
1148 const c = &maker.scanned_config.configuration;
1149 const conf_step = step_index.ptr(c);
1150 const make_step = maker.stepByIndex(step_index);
1151 const skip = switch (summary) {
1152 .none, .line => unreachable,
1153 .all => false,
1154 .new => make_step.result_cached,
1155 .failures => make_step.state == .success,
1156 };
1157 if (skip) return;
1158 try printPrefix(parent_node, stderr);
11591159
1160 if (parent_node.parent != null) {
1161 if (parent_node.last) {
1162 try printChildNodePrefix(stderr);
1163 } else {
1164 try writer.writeAll(switch (stderr.mode) {
1165 .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─
1166 else => "+- ",
1167 });
1168 }
1160 if (parent_node.parent != null) {
1161 if (parent_node.last) {
1162 try printChildNodePrefix(stderr);
1163 } else {
1164 try writer.writeAll(switch (stderr.mode) {
1165 .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─
1166 else => "+- ",
1167 });
11691168 }
1169 }
11701170
1171 if (!first) try stderr.setColor(.dim);
1171 if (!first) try stderr.setColor(.dim);
11721172
1173 // dep_prefix omitted here because it is redundant with the tree.
1174 try writer.writeAll(conf_step.name.slice(c));
1173 // dep_prefix omitted here because it is redundant with the tree.
1174 try writer.writeAll(conf_step.name.slice(c));
11751175
1176 const deps = conf_step.deps.slice(c);
1176 const deps = conf_step.deps.slice(c);
11771177
1178 if (first) {
1179 try printStepStatus(run, step_index, stderr);
1178 if (first) {
1179 try printStepStatus(maker, step_index, stderr);
11801180
1181 const last_index = if (summary == .all) deps.len -| 1 else blk: {
1182 var i: usize = deps.len;
1183 while (i > 0) {
1184 i -= 1;
1181 const last_index = if (summary == .all) deps.len -| 1 else blk: {
1182 var i: usize = deps.len;
1183 while (i > 0) {
1184 i -= 1;
11851185
1186 const dep_index = deps[i];
1187 const dep = run.stepByIndex(dep_index);
1188 const found = switch (summary) {
1189 .all, .line, .none => unreachable,
1190 .failures => dep.state != .success,
1191 .new => !dep.result_cached,
1192 };
1193 if (found) break :blk i;
1194 }
1195 break :blk deps.len -| 1;
1196 };
1197 for (deps, 0..) |dep, i| {
1198 var print_node: PrintNode = .{
1199 .parent = parent_node,
1200 .last = i == last_index,
1186 const dep_index = deps[i];
1187 const dep = maker.stepByIndex(dep_index);
1188 const found = switch (summary) {
1189 .all, .line, .none => unreachable,
1190 .failures => dep.state != .success,
1191 .new => !dep.result_cached,
12011192 };
1202 try printTreeStep(run, dep, stderr, &print_node, step_stack);
1193 if (found) break :blk i;
12031194 }
1195 break :blk deps.len -| 1;
1196 };
1197 for (deps, 0..) |dep, i| {
1198 var print_node: PrintNode = .{
1199 .parent = parent_node,
1200 .last = i == last_index,
1201 };
1202 try printTreeStep(maker, dep, stderr, &print_node, step_stack);
1203 }
1204 } else {
1205 if (deps.len == 0) {
1206 try writer.writeAll(" (reused)\n");
12041207 } else {
1205 if (deps.len == 0) {
1206 try writer.writeAll(" (reused)\n");
1207 } else {
1208 try writer.print(" (+{d} more reused dependencies)\n", .{deps.len});
1209 }
1210 try stderr.setColor(.reset);
1208 try writer.print(" (+{d} more reused dependencies)\n", .{deps.len});
12111209 }
1210 try stderr.setColor(.reset);
12121211 }
1212}
12131213
1214 fn printStepStatus(run: *const Run, step_index: Configuration.Step.Index, stderr: Io.Terminal) !void {
1215 const s = run.stepByIndex(step_index);
1216 const writer = stderr.writer;
1217 switch (s.state) {
1218 .precheck_unstarted => unreachable,
1219 .precheck_started => unreachable,
1220 .precheck_done => unreachable,
1221
1222 .dependency_failure => {
1223 try stderr.setColor(.dim);
1224 try writer.writeAll(" transitive failure\n");
1225 try stderr.setColor(.reset);
1226 },
1214fn printStepStatus(maker: *const Maker, step_index: Configuration.Step.Index, stderr: Io.Terminal) !void {
1215 const s = maker.stepByIndex(step_index);
1216 const writer = stderr.writer;
1217 switch (s.state) {
1218 .precheck_unstarted => unreachable,
1219 .precheck_started => unreachable,
1220 .precheck_done => unreachable,
1221
1222 .dependency_failure => {
1223 try stderr.setColor(.dim);
1224 try writer.writeAll(" transitive failure\n");
1225 try stderr.setColor(.reset);
1226 },
12271227
1228 .success => {
1229 try stderr.setColor(.green);
1230 if (s.result_cached) {
1231 try writer.writeAll(" cached");
1232 } else if (s.test_results.test_count > 0) {
1233 const pass_count = s.test_results.passCount();
1234 assert(s.test_results.test_count == pass_count + s.test_results.skip_count);
1235 try writer.print(" {d} pass", .{pass_count});
1236 if (s.test_results.skip_count > 0) {
1237 try stderr.setColor(.reset);
1238 try writer.writeAll(", ");
1239 try stderr.setColor(.yellow);
1240 try writer.print("{d} skip", .{s.test_results.skip_count});
1241 }
1228 .success => {
1229 try stderr.setColor(.green);
1230 if (s.result_cached) {
1231 try writer.writeAll(" cached");
1232 } else if (s.test_results.test_count > 0) {
1233 const pass_count = s.test_results.passCount();
1234 assert(s.test_results.test_count == pass_count + s.test_results.skip_count);
1235 try writer.print(" {d} pass", .{pass_count});
1236 if (s.test_results.skip_count > 0) {
12421237 try stderr.setColor(.reset);
1243 try writer.print(" ({d} total)", .{s.test_results.test_count});
1244 } else {
1245 try writer.writeAll(" success");
1238 try writer.writeAll(", ");
1239 try stderr.setColor(.yellow);
1240 try writer.print("{d} skip", .{s.test_results.skip_count});
12461241 }
12471242 try stderr.setColor(.reset);
1248 if (s.result_duration_ns) |ns| {
1249 try stderr.setColor(.dim);
1250 if (ns >= std.time.ns_per_min) {
1251 try writer.print(" {d}m", .{ns / std.time.ns_per_min});
1252 } else if (ns >= std.time.ns_per_s) {
1253 try writer.print(" {d}s", .{ns / std.time.ns_per_s});
1254 } else if (ns >= std.time.ns_per_ms) {
1255 try writer.print(" {d}ms", .{ns / std.time.ns_per_ms});
1256 } else if (ns >= std.time.ns_per_us) {
1257 try writer.print(" {d}us", .{ns / std.time.ns_per_us});
1258 } else {
1259 try writer.print(" {d}ns", .{ns});
1260 }
1261 try stderr.setColor(.reset);
1262 }
1263 if (s.result_peak_rss != 0) {
1264 const rss = s.result_peak_rss;
1265 try stderr.setColor(.dim);
1266 if (rss >= 1000_000_000) {
1267 try writer.print(" MaxRSS:{d}G", .{rss / 1000_000_000});
1268 } else if (rss >= 1000_000) {
1269 try writer.print(" MaxRSS:{d}M", .{rss / 1000_000});
1270 } else if (rss >= 1000) {
1271 try writer.print(" MaxRSS:{d}K", .{rss / 1000});
1272 } else {
1273 try writer.print(" MaxRSS:{d}B", .{rss});
1274 }
1275 try stderr.setColor(.reset);
1243 try writer.print(" ({d} total)", .{s.test_results.test_count});
1244 } else {
1245 try writer.writeAll(" success");
1246 }
1247 try stderr.setColor(.reset);
1248 if (s.result_duration_ns) |ns| {
1249 try stderr.setColor(.dim);
1250 if (ns >= std.time.ns_per_min) {
1251 try writer.print(" {d}m", .{ns / std.time.ns_per_min});
1252 } else if (ns >= std.time.ns_per_s) {
1253 try writer.print(" {d}s", .{ns / std.time.ns_per_s});
1254 } else if (ns >= std.time.ns_per_ms) {
1255 try writer.print(" {d}ms", .{ns / std.time.ns_per_ms});
1256 } else if (ns >= std.time.ns_per_us) {
1257 try writer.print(" {d}us", .{ns / std.time.ns_per_us});
1258 } else {
1259 try writer.print(" {d}ns", .{ns});
12761260 }
1277 try writer.writeAll("\n");
1278 },
1279 .skipped => {
1280 try stderr.setColor(.yellow);
1281 try writer.writeAll(" skipped\n");
12821261 try stderr.setColor(.reset);
1283 },
1284 .skipped_oom => {
1285 const c = &run.scanned_config.configuration;
1286 const max_rss = step_index.ptr(c).max_rss.toBytes();
1287 try stderr.setColor(.yellow);
1288 try writer.writeAll(" skipped (not enough memory)");
1262 }
1263 if (s.result_peak_rss != 0) {
1264 const rss = s.result_peak_rss;
12891265 try stderr.setColor(.dim);
1290 try writer.print(" upper bound of {d} exceeded runner limit ({d})\n", .{
1291 max_rss, run.available_rss,
1292 });
1293 try stderr.setColor(.reset);
1294 },
1295 .failure => {
1296 try printStepFailure(run.steps, step_index, stderr, false);
1266 if (rss >= 1000_000_000) {
1267 try writer.print(" MaxRSS:{d}G", .{rss / 1000_000_000});
1268 } else if (rss >= 1000_000) {
1269 try writer.print(" MaxRSS:{d}M", .{rss / 1000_000});
1270 } else if (rss >= 1000) {
1271 try writer.print(" MaxRSS:{d}K", .{rss / 1000});
1272 } else {
1273 try writer.print(" MaxRSS:{d}B", .{rss});
1274 }
12971275 try stderr.setColor(.reset);
1298 },
1299 }
1276 }
1277 try writer.writeAll("\n");
1278 },
1279 .skipped => {
1280 try stderr.setColor(.yellow);
1281 try writer.writeAll(" skipped\n");
1282 try stderr.setColor(.reset);
1283 },
1284 .skipped_oom => {
1285 const c = &maker.scanned_config.configuration;
1286 const max_rss = step_index.ptr(c).max_rss.toBytes();
1287 try stderr.setColor(.yellow);
1288 try writer.writeAll(" skipped (not enough memory)");
1289 try stderr.setColor(.dim);
1290 try writer.print(" upper bound of {d} exceeded runner limit ({d})\n", .{
1291 max_rss, maker.available_rss,
1292 });
1293 try stderr.setColor(.reset);
1294 },
1295 .failure => {
1296 try printStepFailure(maker.steps, step_index, stderr, false);
1297 try stderr.setColor(.reset);
1298 },
13001299 }
1301};
1300}
13021301
13031302fn printStepFailure(
13041303 make_steps: []Step,