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 @@...@@ -1,3 +1,4 @@
1const Maker = @This();
1const builtin = @import("builtin");2const builtin = @import("builtin");
23
3const std = @import("std");4const std = @import("std");
...@@ -26,6 +27,28 @@ pub const std_options: std.Options = .{...@@ -26,6 +27,28 @@ pub const std_options: std.Options = .{
26 .http_disable_tls = true,27 .http_disable_tls = true,
27};28};
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
29pub fn main(init: process.Init.Minimal) !void {52pub fn main(init: process.Init.Minimal) !void {
30 // The build runner is often short-lived, but thanks to `--watch` and `--webui`, that's not53 // The build runner is often short-lived, but thanks to `--watch` and `--webui`, that's not
31 // always the case. So, we do need a true gpa for some things.54 // 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 {...@@ -467,7 +490,7 @@ pub fn main(init: process.Init.Minimal) !void {
467 .sub_path = cwd_relative,490 .sub_path = cwd_relative,
468 } else try install_prefix_path.join(arena, "include");491 } else try install_prefix_path.join(arena, "include");
469492
470 var run: Run = .{493 var maker: Maker = .{
471 .gpa = gpa,494 .gpa = gpa,
472 .graph = &graph,495 .graph = &graph,
473 .scanned_config = &scanned_config,496 .scanned_config = &scanned_config,
...@@ -495,16 +518,16 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -495,16 +518,16 @@ pub fn main(init: process.Init.Minimal) !void {
495 .summary = summary orelse if (watch or webui_listen != null) .line else .failures,518 .summary = summary orelse if (watch or webui_listen != null) .line else .failures,
496 };519 };
497 defer {520 defer {
498 run.memory_blocked_steps.deinit(gpa);521 maker.memory_blocked_steps.deinit(gpa);
499 run.step_stack.deinit(gpa);522 maker.step_stack.deinit(gpa);
500 }523 }
501524
502 if (run.available_rss == 0) {525 if (maker.available_rss == 0) {
503 run.available_rss = process.totalSystemMemory() catch std.math.maxInt(u64);526 maker.available_rss = process.totalSystemMemory() catch std.math.maxInt(u64);
504 run.max_rss_is_default = true;527 maker.max_rss_is_default = true;
505 }528 }
506529
507 run.prepare(step_names.items) catch |err| switch (err) {530 maker.prepare(step_names.items) catch |err| switch (err) {
508 error.DependencyLoopDetected, error.InsufficientMemory => {531 error.DependencyLoopDetected, error.InsufficientMemory => {
509 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};532 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};
510 process.exit(1);533 process.exit(1);
...@@ -515,17 +538,17 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -515,17 +538,17 @@ pub fn main(init: process.Init.Minimal) !void {
515 var w: Watch = w: {538 var w: Watch = w: {
516 if (!watch) break :w undefined;539 if (!watch) break :w undefined;
517 if (!Watch.have_impl) fatal("--watch not yet implemented for {t}", .{builtin.os.tag});540 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);
519 };542 };
520543
521 const now = Io.Clock.Timestamp.now(io, .awake);544 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: {
524 if (builtin.single_threaded) unreachable; // `fatal` above547 if (builtin.single_threaded) unreachable; // `fatal` above
525 break :ws .init(.{548 break :ws .init(.{
526 .gpa = gpa,549 .gpa = gpa,
527 .graph = &graph,550 .graph = &graph,
528 .all_steps = run.step_stack.keys(),551 .all_steps = maker.step_stack.keys(),
529 .root_prog_node = main_progress_node,552 .root_prog_node = main_progress_node,
530 .watch = watch,553 .watch = watch,
531 .listen_address = listen_address,554 .listen_address = listen_address,
...@@ -534,20 +557,20 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -534,20 +557,20 @@ pub fn main(init: process.Init.Minimal) !void {
534 });557 });
535 } else null;558 } else null;
536559
537 if (run.web_server) |*ws| {560 if (maker.web_server) |*ws| {
538 ws.start() catch |err| fatal("failed to start web server: {t}", .{err});561 ws.start() catch |err| fatal("failed to start web server: {t}", .{err});
539 }562 }
540563
541 rebuild: while (true) : (if (run.error_style.clearOnUpdate()) {564 rebuild: while (true) : (if (maker.error_style.clearOnUpdate()) {
542 const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode);565 const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode);
543 defer io.unlockStderr();566 defer io.unlockStderr();
544 try stderr.file_writer.interface.writeAll("\x1B[2J\x1B[3J\x1B[H");567 try stderr.file_writer.interface.writeAll("\x1B[2J\x1B[3J\x1B[H");
545 }) {568 }) {
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| {
551 if (fuzz) |mode| if (mode != .forever) fatal(574 if (fuzz) |mode| if (mode != .forever) fatal(
552 "error: limited fuzzing is not implemented yet for --webui",575 "error: limited fuzzing is not implemented yet for --webui",
553 .{},576 .{},
...@@ -556,13 +579,13 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -556,13 +579,13 @@ pub fn main(init: process.Init.Minimal) !void {
556 web_server.finishBuild(.{ .fuzz = fuzz != null });579 web_server.finishBuild(.{ .fuzz = fuzz != null });
557 }580 }
558581
559 if (run.web_server) |*ws| {582 if (maker.web_server) |*ws| {
560 const c = &scanned_config.configuration;583 const c = &scanned_config.configuration;
561 assert(!watch); // fatal error after CLI parsing584 assert(!watch); // fatal error after CLI parsing
562 while (true) switch (try ws.wait()) {585 while (true) switch (try ws.wait()) {
563 .rebuild => {586 .rebuild => {
564 for (run.step_stack.keys()) |step_index| {587 for (maker.step_stack.keys()) |step_index| {
565 const step = run.stepByIndex(step_index);588 const step = maker.stepByIndex(step_index);
566 step.state = .precheck_done;589 step.state = .precheck_done;
567 const deps = step_index.ptr(c).deps.slice(c);590 const deps = step_index.ptr(c).deps.slice(c);
568 step.pending_deps = @intCast(deps.len);591 step.pending_deps = @intCast(deps.len);
...@@ -576,7 +599,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -576,7 +599,7 @@ pub fn main(init: process.Init.Minimal) !void {
576 // Comptime-known guard to prevent including the logic below when `!Watch.have_impl`.599 // Comptime-known guard to prevent including the logic below when `!Watch.have_impl`.
577 if (!Watch.have_impl) unreachable;600 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
581 // Wait until a file system notification arrives. Read all such events604 // Wait until a file system notification arrives. Read all such events
582 // until the buffer is empty. Then wait for a debounce interval, resetting605 // until the buffer is empty. Then wait for a debounce interval, resetting
...@@ -585,7 +608,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -585,7 +608,7 @@ pub fn main(init: process.Init.Minimal) !void {
585 // recursive dependants.608 // recursive dependants.
586 var caption_buf: [std.Progress.Node.max_name_len]u8 = undefined;609 var caption_buf: [std.Progress.Node.max_name_len]u8 = undefined;
587 const caption = std.fmt.bufPrint(&caption_buf, "watching {d} directories, {d} processes", .{610 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()),
589 }) catch &caption_buf;612 }) catch &caption_buf;
590 var debouncing_node = main_progress_node.start(caption, 0);613 var debouncing_node = main_progress_node.start(caption, 0);
591 var in_debounce = false;614 var in_debounce = false;
...@@ -593,7 +616,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -593,7 +616,7 @@ pub fn main(init: process.Init.Minimal) !void {
593 .timeout => {616 .timeout => {
594 assert(in_debounce);617 assert(in_debounce);
595 debouncing_node.end();618 debouncing_node.end();
596 markFailedStepsDirty(gpa, run.steps, run.step_stack.keys());619 markFailedStepsDirty(gpa, maker.steps, maker.step_stack.keys());
597 continue :rebuild;620 continue :rebuild;
598 },621 },
599 .dirty => if (!in_debounce) {622 .dirty => if (!in_debounce) {
...@@ -634,436 +657,386 @@ fn countSubProcesses(make_steps: []Step, all_steps: []const Configuration.Step.I...@@ -634,436 +657,386 @@ fn countSubProcesses(make_steps: []Step, all_steps: []const Configuration.Step.I
634 return count;657 return count;
635}658}
636659
637const Run = struct {660const InstallPaths = struct {
638 gpa: Allocator,661 prefix: Path,
639 graph: *Graph,662 lib: Path,
640 install_paths: InstallPaths,663 bin: Path,
641 scanned_config: *const ScannedConfig,664 include: Path,
642 steps: []Step,665};
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 };
666666
667 fn stepByIndex(run: *const Run, i: Configuration.Step.Index) *Step {667fn stepByIndex(maker: *const Maker, i: Configuration.Step.Index) *Step {
668 return &run.steps[@intFromEnum(i)];668 return &maker.steps[@intFromEnum(i)];
669 }669}
670670
671 fn prepare(run: *Run, step_names: []const []const u8) !void {671fn prepare(maker: *Maker, step_names: []const []const u8) !void {
672 const gpa = run.gpa;672 const gpa = maker.gpa;
673 const graph = run.graph;673 const graph = maker.graph;
674 const arena = graph.arena;674 const arena = graph.arena;
675 const seed: u32 = graph.random_seed;675 const seed: u32 = graph.random_seed;
676 const step_stack = &run.step_stack;676 const step_stack = &maker.step_stack;
677 const c = &run.scanned_config.configuration;677 const c = &maker.scanned_config.configuration;
678678
679 @memset(run.steps, .{});679 @memset(maker.steps, .{});
680680
681 if (step_names.len == 0) {681 if (step_names.len == 0) {
682 try step_stack.put(gpa, c.default_step, {});682 try step_stack.put(gpa, c.default_step, {});
683 } else {683 } else {
684 try step_stack.ensureUnusedCapacity(gpa, step_names.len);684 try step_stack.ensureUnusedCapacity(gpa, step_names.len);
685 for (0..step_names.len) |i| {685 for (0..step_names.len) |i| {
686 const step_name = step_names[step_names.len - i - 1];686 const step_name = step_names[step_names.len - i - 1];
687 const s = run.scanned_config.top_level_steps.get(step_name) orelse {687 const s = maker.scanned_config.top_level_steps.get(step_name) orelse {
688 log.info("to list available steps: zig build -l", .{});688 log.info("to list available steps: zig build -l", .{});
689 fatal("no such step: {s}", .{step_name});689 fatal("no such step: {s}", .{step_name});
690 };690 };
691 step_stack.putAssumeCapacity(s, {});691 step_stack.putAssumeCapacity(s, {});
692 }
693 }692 }
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);697 var rng = std.Random.DefaultPrng.init(seed);
698 const rand = rng.random();698 const rand = rng.random();
699 rand.shuffle(Configuration.Step.Index, starting_steps);699 rand.shuffle(Configuration.Step.Index, starting_steps);
700700
701 for (starting_steps) |s| {701 for (starting_steps) |s| {
702 try constructGraphAndCheckForDependencyLoop(gpa, c, run.steps, s, &run.step_stack, rand);702 try constructGraphAndCheckForDependencyLoop(gpa, c, maker.steps, s, &maker.step_stack, rand);
703 }703 }
704704
705 {705 {
706 // Check that we have enough memory to complete the build.706 // Check that we have enough memory to complete the build.
707 var any_problems = false;707 var any_problems = false;
708 var max_needed: usize = 0;708 var max_needed: usize = 0;
709 for (step_stack.keys()) |step_index| {709 for (step_stack.keys()) |step_index| {
710 const make_step = run.stepByIndex(step_index);710 const make_step = maker.stepByIndex(step_index);
711 const conf_step = step_index.ptr(c);711 const conf_step = step_index.ptr(c);
712 const max_rss = conf_step.max_rss.toBytes();712 const max_rss = conf_step.max_rss.toBytes();
713 if (max_rss == 0) continue;713 if (max_rss == 0) continue;
714 max_needed = @max(max_needed, max_rss);714 max_needed = @max(max_needed, max_rss);
715 if (max_rss > run.available_rss) {715 if (max_rss > maker.available_rss) {
716 if (run.skip_oom_steps) {716 if (maker.skip_oom_steps) {
717 make_step.state = .skipped_oom;717 make_step.state = .skipped_oom;
718 for (make_step.dependants.items) |dependant| {718 for (make_step.dependants.items) |dependant| {
719 run.stepByIndex(dependant).pending_deps -= 1;719 maker.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;
729 }720 }
730 }721 } else {
731 }722 log.err("{s}{s}: this step declares an upper bound of {d} bytes of memory, exceeding the available {d} bytes of memory", .{
732 if (any_problems) {723 conf_step.owner.depPrefixSlice(c),
733 if (run.max_rss_is_default) {724 conf_step.name.slice(c),
734 std.log.info("use --maxrss {d} to proceed, risking system memory exhaustion", .{725 max_rss,
735 max_needed,726 maker.available_rss,
736 });727 });
728 any_problems = true;
737 }729 }
738 return error.InsufficientMemory;
739 }730 }
740 }731 }
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 }
741 }740 }
741}
742742
743 fn makeStepNames(743fn makeStepNames(
744 run: *Run,744 maker: *Maker,
745 step_names: []const []const u8,745 step_names: []const []const u8,
746 parent_prog_node: std.Progress.Node,746 parent_prog_node: std.Progress.Node,
747 fuzz: ?Fuzz.Mode,747 fuzz: ?Fuzz.Mode,
748 ) !void {748) !void {
749 const graph = run.graph;749 const graph = maker.graph;
750 const gpa = run.gpa;750 const gpa = maker.gpa;
751 const io = graph.io;751 const io = graph.io;
752 const step_stack = &run.step_stack;752 const step_stack = &maker.step_stack;
753 const top_level_steps = &run.scanned_config.top_level_steps;753 const top_level_steps = &maker.scanned_config.top_level_steps;
754 const c = &run.scanned_config.configuration;754 const c = &maker.scanned_config.configuration;
755755
756 {756 {
757 // Collect the initial set of tasks (those with no outstanding dependencies) into a buffer,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 thinking758 // 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.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;760 var initial_set: std.ArrayList(Configuration.Step.Index) = .empty;
761 defer initial_set.deinit(gpa);761 defer initial_set.deinit(gpa);
762 try initial_set.ensureUnusedCapacity(gpa, step_stack.count());762 try initial_set.ensureUnusedCapacity(gpa, step_stack.count());
763 for (step_stack.keys()) |step_index| {763 for (step_stack.keys()) |step_index| {
764 const s = run.stepByIndex(step_index);764 const s = maker.stepByIndex(step_index);
765 if (s.state == .precheck_done and s.pending_deps == 0) {765 if (s.state == .precheck_done and s.pending_deps == 0) {
766 initial_set.appendAssumeCapacity(step_index);766 initial_set.appendAssumeCapacity(step_index);
767 }
768 }767 }
768 }
769769
770 const step_prog = parent_prog_node.start("steps", step_stack.count());770 const step_prog = parent_prog_node.start("steps", step_stack.count());
771 defer step_prog.end();771 defer step_prog.end();
772772
773 var group: Io.Group = .init;773 var group: Io.Group = .init;
774 defer group.cancel(io);774 defer group.cancel(io);
775 // Start working on all of the initial steps...775 // Start working on all of the initial steps...
776 for (initial_set.items) |step_index| try stepReady(run, &group, step_index, step_prog);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.777 // ...and `makeStep` will trigger every other step when their last dependency finishes.
778 try group.await(io);778 try group.await(io);
779 }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;783 var test_pass_count: usize = 0;
784 var test_skip_count: usize = 0;784 var test_skip_count: usize = 0;
785 var test_fail_count: usize = 0;785 var test_fail_count: usize = 0;
786 var test_crash_count: usize = 0;786 var test_crash_count: usize = 0;
787 var test_timeout_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;791 var success_count: usize = 0;
792 var skipped_count: usize = 0;792 var skipped_count: usize = 0;
793 var failure_count: usize = 0;793 var failure_count: usize = 0;
794 var pending_count: usize = 0;794 var pending_count: usize = 0;
795 var total_compile_errors: usize = 0;795 var total_compile_errors: usize = 0;
796796
797 var cleanup_task = io.async(cleanTmpFiles, .{ io, step_stack.keys() });797 var cleanup_task = io.async(cleanTmpFiles, .{ io, step_stack.keys() });
798 defer cleanup_task.await(io);798 defer cleanup_task.await(io);
799799
800 for (step_stack.keys()) |step_index| {800 for (step_stack.keys()) |step_index| {
801 const make_step = run.stepByIndex(step_index);801 const make_step = maker.stepByIndex(step_index);
802 test_pass_count += make_step.test_results.passCount();802 test_pass_count += make_step.test_results.passCount();
803 test_skip_count += make_step.test_results.skip_count;803 test_skip_count += make_step.test_results.skip_count;
804 test_fail_count += make_step.test_results.fail_count;804 test_fail_count += make_step.test_results.fail_count;
805 test_crash_count += make_step.test_results.crash_count;805 test_crash_count += make_step.test_results.crash_count;
806 test_timeout_count += make_step.test_results.timeout_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) {810 switch (make_step.state) {
811 .precheck_unstarted => unreachable,811 .precheck_unstarted => unreachable,
812 .precheck_started => unreachable,812 .precheck_started => unreachable,
813 .precheck_done => unreachable,813 .precheck_done => unreachable,
814 .dependency_failure => pending_count += 1,814 .dependency_failure => pending_count += 1,
815 .success => success_count += 1,815 .success => success_count += 1,
816 .skipped, .skipped_oom => skipped_count += 1,816 .skipped, .skipped_oom => skipped_count += 1,
817 .failure => {817 .failure => {
818 failure_count += 1;818 failure_count += 1;
819 const compile_errors_len = make_step.result_error_bundle.errorMessageCount();819 const compile_errors_len = make_step.result_error_bundle.errorMessageCount();
820 if (compile_errors_len > 0) {820 if (compile_errors_len > 0) {
821 total_compile_errors += compile_errors_len;821 total_compile_errors += compile_errors_len;
822 }822 }
823 },823 },
824 }
825 }824 }
825 }
826826
827 if (fuzz) |mode| blk: {827 if (fuzz) |mode| blk: {
828 switch (builtin.os.tag) {828 switch (builtin.os.tag) {
829 // Current implementation depends on two things that need to be ported to Windows: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.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 resolving831 // * COFF/PE support added to `std.debug.Info` (it needs a batching API for resolving
832 // many addresses to source locations).832 // many addresses to source locations).
833 .windows => fatal("--fuzz not yet implemented for {t}", .{builtin.os.tag}),833 .windows => fatal("--fuzz not yet implemented for {t}", .{builtin.os.tag}),
834 else => {},834 else => {},
835 }835 }
836 if (@bitSizeOf(usize) != 64) {836 if (@bitSizeOf(usize) != 64) {
837 // Current implementation depends on posix.mmap()'s second parameter, `length: usize`,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 case838 // being compatible with file system's u64 return value. This is not the case
839 // on 32-bit platforms.839 // on 32-bit platforms.
840 // Affects or affected by issues #5185, #22523, and #22464.840 // Affects or affected by issues #5185, #22523, and #22464.
841 fatal("--fuzz not yet implemented on {d}-bit platforms", .{@bitSizeOf(usize)});841 fatal("--fuzz not yet implemented on {d}-bit platforms", .{@bitSizeOf(usize)});
842 }842 }
843
844 switch (mode) {
845 .forever => break :blk,
846 .limit => {},
847 }
848843
849 assert(mode == .limit);844 switch (mode) {
850 var f = Fuzz.init(845 .forever => break :blk,
851 gpa,846 .limit => {},
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 }847 }
862848
863 // Every test has a state849 assert(mode == .limit);
864 assert(test_pass_count + test_skip_count + test_fail_count + test_crash_count + test_timeout_count == test_count);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) {863 // Every test has a state
867 std.Progress.setStatus(.success);864 assert(test_pass_count + test_skip_count + test_fail_count + test_crash_count + test_timeout_count == test_count);
868 } else {
869 std.Progress.setStatus(.failure);
870 }
871865
872 summary: {866 if (failure_count == 0) {
873 switch (run.summary) {867 std.Progress.setStatus(.success);
874 .all, .new, .line => {},868 } else {
875 .failures => if (failure_count == 0) break :summary,869 std.Progress.setStatus(.failure);
876 .none => break :summary,870 }
877 }
878871
879 const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode);872 summary: {
880 defer io.unlockStderr();873 switch (maker.summary) {
881 const t = stderr.terminal();874 .all, .new, .line => {},
882 const w = &stderr.file_writer.interface;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;879 const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode);
885 t.setColor(.cyan) catch {};880 defer io.unlockStderr();
886 t.setColor(.bold) catch {};881 const t = stderr.terminal();
887 w.writeAll("Build Summary: ") catch {};882 const w = &stderr.file_writer.interface;
888 t.setColor(.reset) catch {};883
889 w.print("{d}/{d} steps succeeded", .{ success_count, total_count }) catch {};884 const total_count = success_count + failure_count + pending_count + skipped_count;
890 {885 t.setColor(.cyan) catch {};
891 t.setColor(.dim) catch {};886 t.setColor(.bold) catch {};
892 var first = true;887 w.writeAll("Build Summary: ") catch {};
893 if (skipped_count > 0) {888 t.setColor(.reset) catch {};
894 w.print("{s}{d} skipped", .{ if (first) " (" else ", ", skipped_count }) catch {};889 w.print("{d}/{d} steps succeeded", .{ success_count, total_count }) catch {};
895 first = false;890 {
896 }891 t.setColor(.dim) catch {};
897 if (failure_count > 0) {892 var first = true;
898 w.print("{s}{d} failed", .{ if (first) " (" else ", ", failure_count }) catch {};893 if (skipped_count > 0) {
899 first = false;894 w.print("{s}{d} skipped", .{ if (first) " (" else ", ", skipped_count }) catch {};
900 }895 first = false;
901 if (!first) w.writeByte(')') catch {};
902 t.setColor(.reset) catch {};
903 }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 {};
903 }
904904
905 if (test_count > 0) {905 if (test_count > 0) {
906 w.print("; {d}/{d} tests passed", .{ test_pass_count, test_count }) catch {};906 w.print("; {d}/{d} tests passed", .{ test_pass_count, test_count }) catch {};
907 t.setColor(.dim) catch {};907 t.setColor(.dim) catch {};
908 var first = true;908 var first = true;
909 if (test_skip_count > 0) {909 if (test_skip_count > 0) {
910 w.print("{s}{d} skipped", .{ if (first) " (" else ", ", test_skip_count }) catch {};910 w.print("{s}{d} skipped", .{ if (first) " (" else ", ", test_skip_count }) catch {};
911 first = false;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 {};
927 }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 {};
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.933 // Print a fancy tree with build results.
934 var step_stack_copy = try step_stack.clone(gpa);934 var step_stack_copy = try step_stack.clone(gpa);
935 defer step_stack_copy.deinit(gpa);935 defer step_stack_copy.deinit(gpa);
936936
937 var print_node: PrintNode = .{ .parent = null };937 var print_node: PrintNode = .{ .parent = null };
938 if (step_names.len == 0) {938 if (step_names.len == 0) {
939 print_node.last = true;939 print_node.last = true;
940 printTreeStep(run, c.default_step, t, &print_node, &step_stack_copy) catch |err| switch (err) {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) {
941 error.Canceled => |e| return e,964 error.Canceled => |e| return e,
942 else => {},965 else => {},
943 };966 };
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 }
968 }967 }
969 w.writeByte('\n') catch {};
970 }968 }
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 as974 // 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 of975 // --debug-build-runner-leaks which would make this code return instead of
976 // calling exit.976 // calling exit.
977977
978 const code: u8 = code: {978 const code: u8 = code: {
979 if (failure_count == 0) break :code 0; // success979 if (failure_count == 0) break :code 0; // success
980 if (run.error_style.verboseContext()) break :code 1; // failure; print build command980 if (maker.error_style.verboseContext()) break :code 1; // failure; print build command
981 break :code 2; // failure; do not print build command981 break :code 2; // failure; do not print build command
982 };982 };
983 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};983 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};
984 process.exit(code);984 process.exit(code);
985 }985}
986986
987 fn stepReady(987fn stepReady(
988 run: *Run,988 maker: *Maker,
989 group: *Io.Group,989 group: *Io.Group,
990 step_index: Configuration.Step.Index,990 step_index: Configuration.Step.Index,
991 root_prog_node: std.Progress.Node,991 root_prog_node: std.Progress.Node,
992 ) Io.Cancelable!void {992) Io.Cancelable!void {
993 const graph = run.graph;993 const graph = maker.graph;
994 const io = graph.io;994 const io = graph.io;
995 const c = &run.scanned_config.configuration;995 const c = &maker.scanned_config.configuration;
996 const max_rss = step_index.ptr(c).max_rss.toBytes();996 const max_rss = step_index.ptr(c).max_rss.toBytes();
997 if (max_rss != 0) {997 if (max_rss != 0) {
998 try run.max_rss_mutex.lock(io);998 try maker.max_rss_mutex.lock(io);
999 defer run.max_rss_mutex.unlock(io);999 defer maker.max_rss_mutex.unlock(io);
1000 if (run.available_rss < max_rss) {1000 if (maker.available_rss < max_rss) {
1001 // Running this step right now could possibly exceed the allotted RSS.1001 // Running this step right now could possibly exceed the allotted RSS.
1002 run.memory_blocked_steps.append(run.gpa, step_index) catch1002 maker.memory_blocked_steps.append(maker.gpa, step_index) catch
1003 @panic("TODO eliminate memory allocation here");1003 @panic("TODO eliminate memory allocation here");
1004 return;1004 return;
1005 }
1006 run.available_rss -= max_rss;
1007 }1005 }
1008 group.async(io, makeStep, .{ run, group, step_index, root_prog_node });1006 maker.available_rss -= max_rss;
1009 }1007 }
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-ready1011/// 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 must1012/// 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 RSS1013/// 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 `run.available_rss`) and queue any viable memory-blocked1014/// 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`.1015/// steps after "make" completes for `s`.
1016 fn makeStep(1016fn makeStep(
1017 run: *Run,1017 maker: *Maker,
1018 group: *Io.Group,1018 group: *Io.Group,
1019 step_index: Configuration.Step.Index,1019 step_index: Configuration.Step.Index,
1020 root_prog_node: std.Progress.Node,1020 root_prog_node: std.Progress.Node,
1021 ) Io.Cancelable!void {1021) Io.Cancelable!void {
1022 const graph = run.graph;1022 const graph = maker.graph;
1023 const io = graph.io;1023 const io = graph.io;
1024 const gpa = run.gpa;1024 const gpa = maker.gpa;
1025 const c = &run.scanned_config.configuration;1025 const c = &maker.scanned_config.configuration;
1026 const conf_step = step_index.ptr(c);1026 const conf_step = step_index.ptr(c);
1027 const step_name = conf_step.name.slice(c);1027 const step_name = conf_step.name.slice(c);
1028 const deps = conf_step.deps.slice(c);1028 const deps = conf_step.deps.slice(c);
1029 const make_step = run.stepByIndex(step_index);1029 const make_step = maker.stepByIndex(step_index);
10301030
1031 {1031 {
1032 const step_prog_node = root_prog_node.start(step_name, 0);1032 const step_prog_node = root_prog_node.start(step_name, 0);
1033 defer step_prog_node.end();1033 defer step_prog_node.end();
10341034
1035 if (run.web_server) |*ws| ws.updateStepStatus(step_index, .wip);1035 if (maker.web_server) |*ws| ws.updateStepStatus(step_index, .wip);
10361036
1037 const new_state: Step.State = for (deps) |dep_index| {1037 const new_state: Step.State = for (deps) |dep_index| {
1038 const dep_make_step = run.stepByIndex(dep_index);1038 const dep_make_step = maker.stepByIndex(dep_index);
1039 switch (@atomicLoad(Step.State, &dep_make_step.state, .monotonic)) {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) {
1067 .precheck_unstarted => unreachable,1040 .precheck_unstarted => unreachable,
1068 .precheck_started => unreachable,1041 .precheck_started => unreachable,
1069 .precheck_done => unreachable,1042 .precheck_done => unreachable,
...@@ -1071,234 +1044,260 @@ const Run = struct {...@@ -1071,234 +1044,260 @@ const Run = struct {
1071 .failure,1044 .failure,
1072 .dependency_failure,1045 .dependency_failure,
1073 .skipped_oom,1046 .skipped_oom,
1074 => {1047 => break .dependency_failure,
1075 if (run.web_server) |*ws| ws.updateStepStatus(step_index, .failure);
1076 std.Progress.setStatus(.failure_working);
1077 },
10781048
1079 .success,1049 .success, .skipped => {},
1080 .skipped,
1081 => {
1082 if (run.web_server) |*ws| ws.updateStepStatus(step_index, .success);
1083 },
1084 }1050 }
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 },
1085 }1084 }
1085 }
10861086
1087 // No matter the result, we want to display error/warning messages.1087 // No matter the result, we want to display error/warning messages.
1088 if (make_step.result_error_bundle.errorMessageCount() > 0 or1088 if (make_step.result_error_bundle.errorMessageCount() > 0 or
1089 make_step.result_error_msgs.items.len > 0 or1089 make_step.result_error_msgs.items.len > 0 or
1090 make_step.result_stderr.len > 0)1090 make_step.result_stderr.len > 0)
1091 {1091 {
1092 const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode);1092 const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode);
1093 defer io.unlockStderr();1093 defer io.unlockStderr();
1094 printErrorMessages(gpa, c, run.steps, step_index, .{}, stderr.terminal(), run.error_style, run.multiline_errors) catch |err| switch (err) {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.?) {
1095 error.Canceled => |e| return e,1097 error.Canceled => |e| return e,
1096 error.WriteFailed => switch (stderr.file_writer.err.?) {
1097 error.Canceled => |e| return e,
1098 else => {},
1099 },
1100 else => {},1098 else => {},
1101 };1099 },
1102 }1100 else => {},
1101 };
1102 }
11031103
1104 const max_rss = conf_step.max_rss.toBytes();1104 const max_rss = conf_step.max_rss.toBytes();
1105 if (max_rss != 0) {1105 if (max_rss != 0) {
1106 var dispatch_set: std.ArrayList(Configuration.Step.Index) = .empty;1106 var dispatch_set: std.ArrayList(Configuration.Step.Index) = .empty;
1107 defer dispatch_set.deinit(gpa);1107 defer dispatch_set.deinit(gpa);
11081108
1109 // Release our RSS claim and kick off some blocked steps if possible. We use `dispatch_set`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.1110 // as a staging buffer to avoid recursing into `makeStep` while `maker.max_rss_mutex` is held.
1111 {1111 {
1112 try run.max_rss_mutex.lock(io);1112 try maker.max_rss_mutex.lock(io);
1113 defer run.max_rss_mutex.unlock(io);1113 defer maker.max_rss_mutex.unlock(io);
1114 run.available_rss += max_rss;1114 maker.available_rss += max_rss;
1115 dispatch_set.ensureUnusedCapacity(gpa, run.memory_blocked_steps.items.len) catch1115 dispatch_set.ensureUnusedCapacity(gpa, maker.memory_blocked_steps.items.len) catch
1116 @panic("TODO eliminate memory allocation here");1116 @panic("TODO eliminate memory allocation here");
1117 while (run.memory_blocked_steps.getLast()) |candidate_index| {1117 while (maker.memory_blocked_steps.getLast()) |candidate_index| {
1118 const candidate_max_rss = candidate_index.ptr(c).max_rss.toBytes();1118 const candidate_max_rss = candidate_index.ptr(c).max_rss.toBytes();
1119 if (run.available_rss < candidate_max_rss) break;1119 if (maker.available_rss < candidate_max_rss) break;
1120 assert(run.memory_blocked_steps.pop() == candidate_index);1120 assert(maker.memory_blocked_steps.pop() == candidate_index);
1121 dispatch_set.appendAssumeCapacity(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 });
1126 }1122 }
1127 }1123 }
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| {1129 for (make_step.dependants.items) |dependant_index| {
1130 const dependant = run.stepByIndex(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.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) {1132 if (@atomicRmw(u32, &dependant.pending_deps, .Sub, 1, .acq_rel) == 1) {
1133 try stepReady(run, group, dependant_index, root_prog_node);1133 try stepReady(maker, group, dependant_index, root_prog_node);
1134 }
1135 }1134 }
1136 }1135 }
1136}
11371137
1138 fn printTreeStep(1138fn printTreeStep(
1139 run: *const Run,1139 maker: *const Maker,
1140 step_index: Configuration.Step.Index,1140 step_index: Configuration.Step.Index,
1141 stderr: Io.Terminal,1141 stderr: Io.Terminal,
1142 parent_node: *PrintNode,1142 parent_node: *PrintNode,
1143 step_stack: *std.AutoArrayHashMapUnmanaged(Configuration.Step.Index, void),1143 step_stack: *std.AutoArrayHashMapUnmanaged(Configuration.Step.Index, void),
1144 ) !void {1144) !void {
1145 const writer = stderr.writer;1145 const writer = stderr.writer;
1146 const first = step_stack.swapRemove(step_index);1146 const first = step_stack.swapRemove(step_index);
1147 const summary = run.summary;1147 const summary = maker.summary;
1148 const c = &run.scanned_config.configuration;1148 const c = &maker.scanned_config.configuration;
1149 const conf_step = step_index.ptr(c);1149 const conf_step = step_index.ptr(c);
1150 const make_step = run.stepByIndex(step_index);1150 const make_step = maker.stepByIndex(step_index);
1151 const skip = switch (summary) {1151 const skip = switch (summary) {
1152 .none, .line => unreachable,1152 .none, .line => unreachable,
1153 .all => false,1153 .all => false,
1154 .new => make_step.result_cached,1154 .new => make_step.result_cached,
1155 .failures => make_step.state == .success,1155 .failures => make_step.state == .success,
1156 };1156 };
1157 if (skip) return;1157 if (skip) return;
1158 try printPrefix(parent_node, stderr);1158 try printPrefix(parent_node, stderr);
11591159
1160 if (parent_node.parent != null) {1160 if (parent_node.parent != null) {
1161 if (parent_node.last) {1161 if (parent_node.last) {
1162 try printChildNodePrefix(stderr);1162 try printChildNodePrefix(stderr);
1163 } else {1163 } else {
1164 try writer.writeAll(switch (stderr.mode) {1164 try writer.writeAll(switch (stderr.mode) {
1165 .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─1165 .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─
1166 else => "+- ",1166 else => "+- ",
1167 });1167 });
1168 }
1169 }1168 }
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.1173 // dep_prefix omitted here because it is redundant with the tree.
1174 try writer.writeAll(conf_step.name.slice(c));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) {1178 if (first) {
1179 try printStepStatus(run, step_index, stderr);1179 try printStepStatus(maker, step_index, stderr);
11801180
1181 const last_index = if (summary == .all) deps.len -| 1 else blk: {1181 const last_index = if (summary == .all) deps.len -| 1 else blk: {
1182 var i: usize = deps.len;1182 var i: usize = deps.len;
1183 while (i > 0) {1183 while (i > 0) {
1184 i -= 1;1184 i -= 1;
11851185
1186 const dep_index = deps[i];1186 const dep_index = deps[i];
1187 const dep = run.stepByIndex(dep_index);1187 const dep = maker.stepByIndex(dep_index);
1188 const found = switch (summary) {1188 const found = switch (summary) {
1189 .all, .line, .none => unreachable,1189 .all, .line, .none => unreachable,
1190 .failures => dep.state != .success,1190 .failures => dep.state != .success,
1191 .new => !dep.result_cached,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,
1201 };1192 };
1202 try printTreeStep(run, dep, stderr, &print_node, step_stack);1193 if (found) break :blk i;
1203 }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,
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");
1204 } else {1207 } else {
1205 if (deps.len == 0) {1208 try writer.print(" (+{d} more reused dependencies)\n", .{deps.len});
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);
1211 }1209 }
1210 try stderr.setColor(.reset);
1212 }1211 }
1212}
12131213
1214 fn printStepStatus(run: *const Run, step_index: Configuration.Step.Index, stderr: Io.Terminal) !void {1214fn printStepStatus(maker: *const Maker, step_index: Configuration.Step.Index, stderr: Io.Terminal) !void {
1215 const s = run.stepByIndex(step_index);1215 const s = maker.stepByIndex(step_index);
1216 const writer = stderr.writer;1216 const writer = stderr.writer;
1217 switch (s.state) {1217 switch (s.state) {
1218 .precheck_unstarted => unreachable,1218 .precheck_unstarted => unreachable,
1219 .precheck_started => unreachable,1219 .precheck_started => unreachable,
1220 .precheck_done => unreachable,1220 .precheck_done => unreachable,
12211221
1222 .dependency_failure => {1222 .dependency_failure => {
1223 try stderr.setColor(.dim);1223 try stderr.setColor(.dim);
1224 try writer.writeAll(" transitive failure\n");1224 try writer.writeAll(" transitive failure\n");
1225 try stderr.setColor(.reset);1225 try stderr.setColor(.reset);
1226 },1226 },
12271227
1228 .success => {1228 .success => {
1229 try stderr.setColor(.green);1229 try stderr.setColor(.green);
1230 if (s.result_cached) {1230 if (s.result_cached) {
1231 try writer.writeAll(" cached");1231 try writer.writeAll(" cached");
1232 } else if (s.test_results.test_count > 0) {1232 } else if (s.test_results.test_count > 0) {
1233 const pass_count = s.test_results.passCount();1233 const pass_count = s.test_results.passCount();
1234 assert(s.test_results.test_count == pass_count + s.test_results.skip_count);1234 assert(s.test_results.test_count == pass_count + s.test_results.skip_count);
1235 try writer.print(" {d} pass", .{pass_count});1235 try writer.print(" {d} pass", .{pass_count});
1236 if (s.test_results.skip_count > 0) {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 }
1242 try stderr.setColor(.reset);1237 try stderr.setColor(.reset);
1243 try writer.print(" ({d} total)", .{s.test_results.test_count});1238 try writer.writeAll(", ");
1244 } else {1239 try stderr.setColor(.yellow);
1245 try writer.writeAll(" success");1240 try writer.print("{d} skip", .{s.test_results.skip_count});
1246 }1241 }
1247 try stderr.setColor(.reset);1242 try stderr.setColor(.reset);
1248 if (s.result_duration_ns) |ns| {1243 try writer.print(" ({d} total)", .{s.test_results.test_count});
1249 try stderr.setColor(.dim);1244 } else {
1250 if (ns >= std.time.ns_per_min) {1245 try writer.writeAll(" success");
1251 try writer.print(" {d}m", .{ns / std.time.ns_per_min});1246 }
1252 } else if (ns >= std.time.ns_per_s) {1247 try stderr.setColor(.reset);
1253 try writer.print(" {d}s", .{ns / std.time.ns_per_s});1248 if (s.result_duration_ns) |ns| {
1254 } else if (ns >= std.time.ns_per_ms) {1249 try stderr.setColor(.dim);
1255 try writer.print(" {d}ms", .{ns / std.time.ns_per_ms});1250 if (ns >= std.time.ns_per_min) {
1256 } else if (ns >= std.time.ns_per_us) {1251 try writer.print(" {d}m", .{ns / std.time.ns_per_min});
1257 try writer.print(" {d}us", .{ns / std.time.ns_per_us});1252 } else if (ns >= std.time.ns_per_s) {
1258 } else {1253 try writer.print(" {d}s", .{ns / std.time.ns_per_s});
1259 try writer.print(" {d}ns", .{ns});1254 } else if (ns >= std.time.ns_per_ms) {
1260 }1255 try writer.print(" {d}ms", .{ns / std.time.ns_per_ms});
1261 try stderr.setColor(.reset);1256 } else if (ns >= std.time.ns_per_us) {
1262 }1257 try writer.print(" {d}us", .{ns / std.time.ns_per_us});
1263 if (s.result_peak_rss != 0) {1258 } else {
1264 const rss = s.result_peak_rss;1259 try writer.print(" {d}ns", .{ns});
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);
1276 }1260 }
1277 try writer.writeAll("\n");
1278 },
1279 .skipped => {
1280 try stderr.setColor(.yellow);
1281 try writer.writeAll(" skipped\n");
1282 try stderr.setColor(.reset);1261 try stderr.setColor(.reset);
1283 },1262 }
1284 .skipped_oom => {1263 if (s.result_peak_rss != 0) {
1285 const c = &run.scanned_config.configuration;1264 const rss = s.result_peak_rss;
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);1265 try stderr.setColor(.dim);
1290 try writer.print(" upper bound of {d} exceeded runner limit ({d})\n", .{1266 if (rss >= 1000_000_000) {
1291 max_rss, run.available_rss,1267 try writer.print(" MaxRSS:{d}G", .{rss / 1000_000_000});
1292 });1268 } else if (rss >= 1000_000) {
1293 try stderr.setColor(.reset);1269 try writer.print(" MaxRSS:{d}M", .{rss / 1000_000});
1294 },1270 } else if (rss >= 1000) {
1295 .failure => {1271 try writer.print(" MaxRSS:{d}K", .{rss / 1000});
1296 try printStepFailure(run.steps, step_index, stderr, false);1272 } else {
1273 try writer.print(" MaxRSS:{d}B", .{rss});
1274 }
1297 try stderr.setColor(.reset);1275 try stderr.setColor(.reset);
1298 },1276 }
1299 }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 },
1300 }1299 }
1301};1300}
13021301
1303fn printStepFailure(1302fn printStepFailure(
1304 make_steps: []Step,1303 make_steps: []Step,