| ... | ... | @@ -512,12 +512,10 @@ pub fn main(init: process.Init.Minimal) !void { |
| 512 | 512 | else => |e| return e, |
| 513 | 513 | }; |
| 514 | 514 | |
| 515 | | if (true) @panic("TODO"); |
| 516 | | |
| 517 | 515 | var w: Watch = w: { |
| 518 | 516 | if (!watch) break :w undefined; |
| 519 | 517 | if (!Watch.have_impl) fatal("--watch not yet implemented for {t}", .{builtin.os.tag}); |
| 520 | | break :w try .init(graph.cache.cwd); |
| 518 | break :w try .init(graph.cache.cwd, &scanned_config.configuration, run.steps); |
| 521 | 519 | }; |
| 522 | 520 | |
| 523 | 521 | const now = Io.Clock.Timestamp.now(io, .awake); |
| ... | ... | @@ -532,6 +530,7 @@ pub fn main(init: process.Init.Minimal) !void { |
| 532 | 530 | .watch = watch, |
| 533 | 531 | .listen_address = listen_address, |
| 534 | 532 | .base_timestamp = now, |
| 533 | .configuration = &scanned_config.configuration, |
| 535 | 534 | }); |
| 536 | 535 | } else null; |
| 537 | 536 | |
| ... | ... | @@ -546,7 +545,7 @@ pub fn main(init: process.Init.Minimal) !void { |
| 546 | 545 | }) { |
| 547 | 546 | if (run.web_server) |*ws| ws.startBuild(); |
| 548 | 547 | |
| 549 | | try run.makeStepNames(step_names, main_progress_node, fuzz); |
| 548 | try run.makeStepNames(step_names.items, main_progress_node, fuzz); |
| 550 | 549 | |
| 551 | 550 | if (run.web_server) |*web_server| { |
| 552 | 551 | if (fuzz) |mode| if (mode != .forever) fatal( |
| ... | ... | @@ -558,12 +557,15 @@ pub fn main(init: process.Init.Minimal) !void { |
| 558 | 557 | } |
| 559 | 558 | |
| 560 | 559 | if (run.web_server) |*ws| { |
| 560 | const c = &scanned_config.configuration; |
| 561 | 561 | assert(!watch); // fatal error after CLI parsing |
| 562 | 562 | while (true) switch (try ws.wait()) { |
| 563 | 563 | .rebuild => { |
| 564 | | for (run.step_stack.keys()) |step| { |
| 564 | for (run.step_stack.keys()) |step_index| { |
| 565 | const step = run.stepByIndex(step_index); |
| 565 | 566 | step.state = .precheck_done; |
| 566 | | step.pending_deps = @intCast(step.dependencies.items.len); |
| 567 | const deps = step_index.ptr(c).deps.slice(c); |
| 568 | step.pending_deps = @intCast(deps.len); |
| 567 | 569 | step.reset(gpa); |
| 568 | 570 | } |
| 569 | 571 | continue :rebuild; |
| ... | ... | @@ -583,7 +585,7 @@ pub fn main(init: process.Init.Minimal) !void { |
| 583 | 585 | // recursive dependants. |
| 584 | 586 | var caption_buf: [std.Progress.Node.max_name_len]u8 = undefined; |
| 585 | 587 | const caption = std.fmt.bufPrint(&caption_buf, "watching {d} directories, {d} processes", .{ |
| 586 | | w.dir_count, countSubProcesses(run.step_stack.keys()), |
| 588 | w.dir_count, countSubProcesses(run.steps, run.step_stack.keys()), |
| 587 | 589 | }) catch &caption_buf; |
| 588 | 590 | var debouncing_node = main_progress_node.start(caption, 0); |
| 589 | 591 | var in_debounce = false; |
| ... | ... | @@ -591,7 +593,7 @@ pub fn main(init: process.Init.Minimal) !void { |
| 591 | 593 | .timeout => { |
| 592 | 594 | assert(in_debounce); |
| 593 | 595 | debouncing_node.end(); |
| 594 | | markFailedStepsDirty(gpa, run.step_stack.keys()); |
| 596 | markFailedStepsDirty(gpa, run.steps, run.step_stack.keys()); |
| 595 | 597 | continue :rebuild; |
| 596 | 598 | }, |
| 597 | 599 | .dirty => if (!in_debounce) { |
| ... | ... | @@ -604,22 +606,29 @@ pub fn main(init: process.Init.Minimal) !void { |
| 604 | 606 | } |
| 605 | 607 | } |
| 606 | 608 | |
| 607 | | fn markFailedStepsDirty(gpa: Allocator, all_steps: []const *Step) void { |
| 608 | | for (all_steps) |step| switch (step.state) { |
| 609 | | .dependency_failure, .failure, .skipped => _ = step.invalidateResult(gpa), |
| 610 | | else => continue, |
| 611 | | }; |
| 609 | fn markFailedStepsDirty(gpa: Allocator, make_steps: []Step, all_steps: []const Configuration.Step.Index) void { |
| 610 | for (all_steps) |step_index| { |
| 611 | const step = &make_steps[@intFromEnum(step_index)]; |
| 612 | switch (step.state) { |
| 613 | .dependency_failure, .failure, .skipped => _ = step.invalidateResult(gpa), |
| 614 | else => continue, |
| 615 | } |
| 616 | } |
| 612 | 617 | // Now that all dirty steps have been found, the remaining steps that |
| 613 | 618 | // succeeded from last run shall be marked "cached". |
| 614 | | for (all_steps) |step| switch (step.state) { |
| 615 | | .success => step.result_cached = true, |
| 616 | | else => continue, |
| 617 | | }; |
| 619 | for (all_steps) |step_index| { |
| 620 | const step = &make_steps[@intFromEnum(step_index)]; |
| 621 | switch (step.state) { |
| 622 | .success => step.result_cached = true, |
| 623 | else => continue, |
| 624 | } |
| 625 | } |
| 618 | 626 | } |
| 619 | 627 | |
| 620 | | fn countSubProcesses(all_steps: []const *Step) usize { |
| 628 | fn countSubProcesses(make_steps: []Step, all_steps: []const Configuration.Step.Index) usize { |
| 621 | 629 | var count: usize = 0; |
| 622 | | for (all_steps) |s| { |
| 630 | for (all_steps) |step_index| { |
| 631 | const s = &make_steps[@intFromEnum(step_index)]; |
| 623 | 632 | count += @intFromBool(s.getZigProcess() != null); |
| 624 | 633 | } |
| 625 | 634 | return count; |
| ... | ... | @@ -707,7 +716,7 @@ const Run = struct { |
| 707 | 716 | if (run.skip_oom_steps) { |
| 708 | 717 | make_step.state = .skipped_oom; |
| 709 | 718 | for (make_step.dependants.items) |dependant| { |
| 710 | | dependant.pending_deps -= 1; |
| 719 | run.stepByIndex(dependant).pending_deps -= 1; |
| 711 | 720 | } |
| 712 | 721 | } else { |
| 713 | 722 | log.err("{s}{s}: this step declares an upper bound of {d} bytes of memory, exceeding the available {d} bytes of memory", .{ |
| ... | ... | @@ -742,17 +751,19 @@ const Run = struct { |
| 742 | 751 | const io = graph.io; |
| 743 | 752 | const step_stack = &run.step_stack; |
| 744 | 753 | const top_level_steps = &run.scanned_config.top_level_steps; |
| 754 | const c = &run.scanned_config.configuration; |
| 745 | 755 | |
| 746 | 756 | { |
| 747 | 757 | // Collect the initial set of tasks (those with no outstanding dependencies) into a buffer, |
| 748 | 758 | // then spawn them. The buffer is so that we don't race with `makeStep` and end up thinking |
| 749 | 759 | // a step is initial when it actually became ready due to an earlier initial step. |
| 750 | | var initial_set: std.ArrayList(*Step) = .empty; |
| 760 | var initial_set: std.ArrayList(Configuration.Step.Index) = .empty; |
| 751 | 761 | defer initial_set.deinit(gpa); |
| 752 | 762 | try initial_set.ensureUnusedCapacity(gpa, step_stack.count()); |
| 753 | | for (step_stack.keys()) |s| { |
| 763 | for (step_stack.keys()) |step_index| { |
| 764 | const s = run.stepByIndex(step_index); |
| 754 | 765 | if (s.state == .precheck_done and s.pending_deps == 0) { |
| 755 | | initial_set.appendAssumeCapacity(s); |
| 766 | initial_set.appendAssumeCapacity(step_index); |
| 756 | 767 | } |
| 757 | 768 | } |
| 758 | 769 | |
| ... | ... | @@ -762,7 +773,7 @@ const Run = struct { |
| 762 | 773 | var group: Io.Group = .init; |
| 763 | 774 | defer group.cancel(io); |
| 764 | 775 | // Start working on all of the initial steps... |
| 765 | | for (initial_set.items) |s| try stepReady(&group, s, step_prog, run); |
| 776 | for (initial_set.items) |step_index| try stepReady(run, &group, step_index, step_prog); |
| 766 | 777 | // ...and `makeStep` will trigger every other step when their last dependency finishes. |
| 767 | 778 | try group.await(io); |
| 768 | 779 | } |
| ... | ... | @@ -786,16 +797,17 @@ const Run = struct { |
| 786 | 797 | var cleanup_task = io.async(cleanTmpFiles, .{ io, step_stack.keys() }); |
| 787 | 798 | defer cleanup_task.await(io); |
| 788 | 799 | |
| 789 | | for (step_stack.keys()) |s| { |
| 790 | | test_pass_count += s.test_results.passCount(); |
| 791 | | test_skip_count += s.test_results.skip_count; |
| 792 | | test_fail_count += s.test_results.fail_count; |
| 793 | | test_crash_count += s.test_results.crash_count; |
| 794 | | test_timeout_count += s.test_results.timeout_count; |
| 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; |
| 795 | 807 | |
| 796 | | test_count += s.test_results.test_count; |
| 808 | test_count += make_step.test_results.test_count; |
| 797 | 809 | |
| 798 | | switch (s.state) { |
| 810 | switch (make_step.state) { |
| 799 | 811 | .precheck_unstarted => unreachable, |
| 800 | 812 | .precheck_started => unreachable, |
| 801 | 813 | .precheck_done => unreachable, |
| ... | ... | @@ -804,7 +816,7 @@ const Run = struct { |
| 804 | 816 | .skipped, .skipped_oom => skipped_count += 1, |
| 805 | 817 | .failure => { |
| 806 | 818 | failure_count += 1; |
| 807 | | const compile_errors_len = s.result_error_bundle.errorMessageCount(); |
| 819 | const compile_errors_len = make_step.result_error_bundle.errorMessageCount(); |
| 808 | 820 | if (compile_errors_len > 0) { |
| 809 | 821 | total_compile_errors += compile_errors_len; |
| 810 | 822 | } |
| ... | ... | @@ -925,13 +937,17 @@ const Run = struct { |
| 925 | 937 | var print_node: PrintNode = .{ .parent = null }; |
| 926 | 938 | if (step_names.len == 0) { |
| 927 | 939 | print_node.last = true; |
| 928 | | printTreeStep(graph, graph.default_step, run, t, &print_node, &step_stack_copy) catch {}; |
| 940 | printTreeStep(run, c.default_step, t, &print_node, &step_stack_copy) catch |err| switch (err) { |
| 941 | error.Canceled => |e| return e, |
| 942 | else => {}, |
| 943 | }; |
| 929 | 944 | } else { |
| 930 | 945 | const last_index = if (run.summary == .all) top_level_steps.count() else blk: { |
| 931 | 946 | var i: usize = step_names.len; |
| 932 | 947 | while (i > 0) { |
| 933 | 948 | i -= 1; |
| 934 | | const step = top_level_steps.get(step_names[i]).?.step; |
| 949 | const step_index = top_level_steps.get(step_names[i]).?; |
| 950 | const step = run.stepByIndex(step_index); |
| 935 | 951 | const found = switch (run.summary) { |
| 936 | 952 | .all, .line, .none => unreachable, |
| 937 | 953 | .failures => step.state != .success, |
| ... | ... | @@ -942,9 +958,12 @@ const Run = struct { |
| 942 | 958 | break :blk top_level_steps.count(); |
| 943 | 959 | }; |
| 944 | 960 | for (step_names, 0..) |step_name, i| { |
| 945 | | const tls = top_level_steps.get(step_name).?; |
| 961 | const step_index = top_level_steps.get(step_name).?; |
| 946 | 962 | print_node.last = i + 1 == last_index; |
| 947 | | printTreeStep(graph, &tls.step, run, t, &print_node, &step_stack_copy) catch {}; |
| 963 | printTreeStep(run, step_index, t, &print_node, &step_stack_copy) catch |err| switch (err) { |
| 964 | error.Canceled => |e| return e, |
| 965 | else => {}, |
| 966 | }; |
| 948 | 967 | } |
| 949 | 968 | } |
| 950 | 969 | w.writeByte('\n') catch {}; |
| ... | ... | @@ -964,120 +983,331 @@ const Run = struct { |
| 964 | 983 | _ = io.lockStderr(&.{}, graph.stderr_mode) catch {}; |
| 965 | 984 | process.exit(code); |
| 966 | 985 | } |
| 967 | | }; |
| 968 | 986 | |
| 969 | | const PrintNode = struct { |
| 970 | | parent: ?*PrintNode, |
| 971 | | last: bool = false, |
| 972 | | }; |
| 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; |
| 1007 | } |
| 1008 | group.async(io, makeStep, .{ run, group, step_index, root_prog_node }); |
| 1009 | } |
| 973 | 1010 | |
| 974 | | fn printPrefix(node: *PrintNode, stderr: Io.Terminal) !void { |
| 975 | | const parent = node.parent orelse return; |
| 976 | | const writer = stderr.writer; |
| 977 | | if (parent.parent == null) return; |
| 978 | | try printPrefix(parent, stderr); |
| 979 | | if (parent.last) { |
| 980 | | try writer.writeAll(" "); |
| 981 | | } else { |
| 982 | | try writer.writeAll(switch (stderr.mode) { |
| 983 | | .escape_codes => "\x1B\x28\x30\x78\x1B\x28\x42 ", // │ |
| 984 | | else => "| ", |
| 985 | | }); |
| 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) { |
| 1067 | .precheck_unstarted => unreachable, |
| 1068 | .precheck_started => unreachable, |
| 1069 | .precheck_done => unreachable, |
| 1070 | |
| 1071 | .failure, |
| 1072 | .dependency_failure, |
| 1073 | .skipped_oom, |
| 1074 | => { |
| 1075 | if (run.web_server) |*ws| ws.updateStepStatus(step_index, .failure); |
| 1076 | std.Progress.setStatus(.failure_working); |
| 1077 | }, |
| 1078 | |
| 1079 | .success, |
| 1080 | .skipped, |
| 1081 | => { |
| 1082 | if (run.web_server) |*ws| ws.updateStepStatus(step_index, .success); |
| 1083 | }, |
| 1084 | } |
| 1085 | } |
| 1086 | |
| 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) { |
| 1095 | error.Canceled => |e| return e, |
| 1096 | error.WriteFailed => switch (stderr.file_writer.err.?) { |
| 1097 | error.Canceled => |e| return e, |
| 1098 | else => {}, |
| 1099 | }, |
| 1100 | else => {}, |
| 1101 | }; |
| 1102 | } |
| 1103 | |
| 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 }); |
| 1126 | } |
| 1127 | } |
| 1128 | |
| 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 | } |
| 1135 | } |
| 986 | 1136 | } |
| 987 | | } |
| 988 | 1137 | |
| 989 | | fn printChildNodePrefix(stderr: Io.Terminal) !void { |
| 990 | | try stderr.writer.writeAll(switch (stderr.mode) { |
| 991 | | .escape_codes => "\x1B\x28\x30\x6d\x71\x1B\x28\x42 ", // └─ |
| 992 | | else => "+- ", |
| 993 | | }); |
| 994 | | } |
| 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); |
| 995 | 1159 | |
| 996 | | fn printStepStatus(s: *Step, stderr: Io.Terminal, run: *const Run) !void { |
| 997 | | const writer = stderr.writer; |
| 998 | | switch (s.state) { |
| 999 | | .precheck_unstarted => unreachable, |
| 1000 | | .precheck_started => unreachable, |
| 1001 | | .precheck_done => unreachable, |
| 1002 | | |
| 1003 | | .dependency_failure => { |
| 1004 | | try stderr.setColor(.dim); |
| 1005 | | try writer.writeAll(" transitive failure\n"); |
| 1006 | | try stderr.setColor(.reset); |
| 1007 | | }, |
| 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 | } |
| 1169 | } |
| 1008 | 1170 | |
| 1009 | | .success => { |
| 1010 | | try stderr.setColor(.green); |
| 1011 | | if (s.result_cached) { |
| 1012 | | try writer.writeAll(" cached"); |
| 1013 | | } else if (s.test_results.test_count > 0) { |
| 1014 | | const pass_count = s.test_results.passCount(); |
| 1015 | | assert(s.test_results.test_count == pass_count + s.test_results.skip_count); |
| 1016 | | try writer.print(" {d} pass", .{pass_count}); |
| 1017 | | if (s.test_results.skip_count > 0) { |
| 1018 | | try stderr.setColor(.reset); |
| 1019 | | try writer.writeAll(", "); |
| 1020 | | try stderr.setColor(.yellow); |
| 1021 | | try writer.print("{d} skip", .{s.test_results.skip_count}); |
| 1171 | if (!first) try stderr.setColor(.dim); |
| 1172 | |
| 1173 | // dep_prefix omitted here because it is redundant with the tree. |
| 1174 | try writer.writeAll(conf_step.name.slice(c)); |
| 1175 | |
| 1176 | const deps = conf_step.deps.slice(c); |
| 1177 | |
| 1178 | if (first) { |
| 1179 | try printStepStatus(run, step_index, stderr); |
| 1180 | |
| 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; |
| 1185 | |
| 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; |
| 1022 | 1194 | } |
| 1023 | | try stderr.setColor(.reset); |
| 1024 | | try writer.print(" ({d} total)", .{s.test_results.test_count}); |
| 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(run, dep, stderr, &print_node, step_stack); |
| 1203 | } |
| 1204 | } else { |
| 1205 | if (deps.len == 0) { |
| 1206 | try writer.writeAll(" (reused)\n"); |
| 1025 | 1207 | } else { |
| 1026 | | try writer.writeAll(" success"); |
| 1208 | try writer.print(" (+{d} more reused dependencies)\n", .{deps.len}); |
| 1027 | 1209 | } |
| 1028 | 1210 | try stderr.setColor(.reset); |
| 1029 | | if (s.result_duration_ns) |ns| { |
| 1211 | } |
| 1212 | } |
| 1213 | |
| 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 => { |
| 1030 | 1223 | try stderr.setColor(.dim); |
| 1031 | | if (ns >= std.time.ns_per_min) { |
| 1032 | | try writer.print(" {d}m", .{ns / std.time.ns_per_min}); |
| 1033 | | } else if (ns >= std.time.ns_per_s) { |
| 1034 | | try writer.print(" {d}s", .{ns / std.time.ns_per_s}); |
| 1035 | | } else if (ns >= std.time.ns_per_ms) { |
| 1036 | | try writer.print(" {d}ms", .{ns / std.time.ns_per_ms}); |
| 1037 | | } else if (ns >= std.time.ns_per_us) { |
| 1038 | | try writer.print(" {d}us", .{ns / std.time.ns_per_us}); |
| 1224 | try writer.writeAll(" transitive failure\n"); |
| 1225 | try stderr.setColor(.reset); |
| 1226 | }, |
| 1227 | |
| 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 | } |
| 1242 | try stderr.setColor(.reset); |
| 1243 | try writer.print(" ({d} total)", .{s.test_results.test_count}); |
| 1039 | 1244 | } else { |
| 1040 | | try writer.print(" {d}ns", .{ns}); |
| 1245 | try writer.writeAll(" success"); |
| 1041 | 1246 | } |
| 1042 | 1247 | try stderr.setColor(.reset); |
| 1043 | | } |
| 1044 | | if (s.result_peak_rss != 0) { |
| 1045 | | const rss = s.result_peak_rss; |
| 1046 | | try stderr.setColor(.dim); |
| 1047 | | if (rss >= 1000_000_000) { |
| 1048 | | try writer.print(" MaxRSS:{d}G", .{rss / 1000_000_000}); |
| 1049 | | } else if (rss >= 1000_000) { |
| 1050 | | try writer.print(" MaxRSS:{d}M", .{rss / 1000_000}); |
| 1051 | | } else if (rss >= 1000) { |
| 1052 | | try writer.print(" MaxRSS:{d}K", .{rss / 1000}); |
| 1053 | | } else { |
| 1054 | | try writer.print(" MaxRSS:{d}B", .{rss}); |
| 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); |
| 1055 | 1276 | } |
| 1277 | try writer.writeAll("\n"); |
| 1278 | }, |
| 1279 | .skipped => { |
| 1280 | try stderr.setColor(.yellow); |
| 1281 | try writer.writeAll(" skipped\n"); |
| 1056 | 1282 | try stderr.setColor(.reset); |
| 1057 | | } |
| 1058 | | try writer.writeAll("\n"); |
| 1059 | | }, |
| 1060 | | .skipped => { |
| 1061 | | try stderr.setColor(.yellow); |
| 1062 | | try writer.writeAll(" skipped\n"); |
| 1063 | | try stderr.setColor(.reset); |
| 1064 | | }, |
| 1065 | | .skipped_oom => { |
| 1066 | | try stderr.setColor(.yellow); |
| 1067 | | try writer.writeAll(" skipped (not enough memory)"); |
| 1068 | | try stderr.setColor(.dim); |
| 1069 | | try writer.print(" upper bound of {d} exceeded runner limit ({d})\n", .{ s.max_rss, run.available_rss }); |
| 1070 | | try stderr.setColor(.reset); |
| 1071 | | }, |
| 1072 | | .failure => { |
| 1073 | | try printStepFailure(s, stderr, false); |
| 1074 | | try stderr.setColor(.reset); |
| 1075 | | }, |
| 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)"); |
| 1289 | 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); |
| 1297 | try stderr.setColor(.reset); |
| 1298 | }, |
| 1299 | } |
| 1076 | 1300 | } |
| 1077 | | } |
| 1301 | }; |
| 1078 | 1302 | |
| 1079 | | fn printStepFailure(s: *Step, stderr: Io.Terminal, dim: bool) !void { |
| 1303 | fn printStepFailure( |
| 1304 | make_steps: []Step, |
| 1305 | step_index: Configuration.Step.Index, |
| 1306 | stderr: Io.Terminal, |
| 1307 | dim: bool, |
| 1308 | ) !void { |
| 1080 | 1309 | const w = stderr.writer; |
| 1310 | const s = &make_steps[@intFromEnum(step_index)]; |
| 1081 | 1311 | if (s.result_error_bundle.errorMessageCount() > 0) { |
| 1082 | 1312 | try stderr.setColor(.red); |
| 1083 | 1313 | try w.print(" {d} errors\n", .{ |
| ... | ... | @@ -1160,79 +1390,33 @@ fn printStepFailure(s: *Step, stderr: Io.Terminal, dim: bool) !void { |
| 1160 | 1390 | } |
| 1161 | 1391 | } |
| 1162 | 1392 | |
| 1163 | | fn printTreeStep( |
| 1164 | | graph: *Graph, |
| 1165 | | s: *Step, |
| 1166 | | run: *const Run, |
| 1167 | | stderr: Io.Terminal, |
| 1168 | | parent_node: *PrintNode, |
| 1169 | | step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void), |
| 1170 | | ) !void { |
| 1171 | | const writer = stderr.writer; |
| 1172 | | const first = step_stack.swapRemove(s); |
| 1173 | | const summary = run.summary; |
| 1174 | | const skip = switch (summary) { |
| 1175 | | .none, .line => unreachable, |
| 1176 | | .all => false, |
| 1177 | | .new => s.result_cached, |
| 1178 | | .failures => s.state == .success, |
| 1179 | | }; |
| 1180 | | if (skip) return; |
| 1181 | | try printPrefix(parent_node, stderr); |
| 1182 | | |
| 1183 | | if (parent_node.parent != null) { |
| 1184 | | if (parent_node.last) { |
| 1185 | | try printChildNodePrefix(stderr); |
| 1186 | | } else { |
| 1187 | | try writer.writeAll(switch (stderr.mode) { |
| 1188 | | .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─ |
| 1189 | | else => "+- ", |
| 1190 | | }); |
| 1191 | | } |
| 1192 | | } |
| 1193 | | |
| 1194 | | if (!first) try stderr.setColor(.dim); |
| 1195 | | |
| 1196 | | // dep_prefix omitted here because it is redundant with the tree. |
| 1197 | | try writer.writeAll(s.name); |
| 1198 | | |
| 1199 | | if (first) { |
| 1200 | | try printStepStatus(s, stderr, run); |
| 1201 | | |
| 1202 | | const last_index = if (summary == .all) s.dependencies.items.len -| 1 else blk: { |
| 1203 | | var i: usize = s.dependencies.items.len; |
| 1204 | | while (i > 0) { |
| 1205 | | i -= 1; |
| 1393 | const PrintNode = struct { |
| 1394 | parent: ?*PrintNode, |
| 1395 | last: bool = false, |
| 1396 | }; |
| 1206 | 1397 | |
| 1207 | | const step = s.dependencies.items[i]; |
| 1208 | | const found = switch (summary) { |
| 1209 | | .all, .line, .none => unreachable, |
| 1210 | | .failures => step.state != .success, |
| 1211 | | .new => !step.result_cached, |
| 1212 | | }; |
| 1213 | | if (found) break :blk i; |
| 1214 | | } |
| 1215 | | break :blk s.dependencies.items.len -| 1; |
| 1216 | | }; |
| 1217 | | for (s.dependencies.items, 0..) |dep, i| { |
| 1218 | | var print_node: PrintNode = .{ |
| 1219 | | .parent = parent_node, |
| 1220 | | .last = i == last_index, |
| 1221 | | }; |
| 1222 | | try printTreeStep(graph, dep, run, stderr, &print_node, step_stack); |
| 1223 | | } |
| 1398 | fn printPrefix(node: *PrintNode, stderr: Io.Terminal) !void { |
| 1399 | const parent = node.parent orelse return; |
| 1400 | const writer = stderr.writer; |
| 1401 | if (parent.parent == null) return; |
| 1402 | try printPrefix(parent, stderr); |
| 1403 | if (parent.last) { |
| 1404 | try writer.writeAll(" "); |
| 1224 | 1405 | } else { |
| 1225 | | if (s.dependencies.items.len == 0) { |
| 1226 | | try writer.writeAll(" (reused)\n"); |
| 1227 | | } else { |
| 1228 | | try writer.print(" (+{d} more reused dependencies)\n", .{ |
| 1229 | | s.dependencies.items.len, |
| 1230 | | }); |
| 1231 | | } |
| 1232 | | try stderr.setColor(.reset); |
| 1406 | try writer.writeAll(switch (stderr.mode) { |
| 1407 | .escape_codes => "\x1B\x28\x30\x78\x1B\x28\x42 ", // │ |
| 1408 | else => "| ", |
| 1409 | }); |
| 1233 | 1410 | } |
| 1234 | 1411 | } |
| 1235 | 1412 | |
| 1413 | fn printChildNodePrefix(stderr: Io.Terminal) !void { |
| 1414 | try stderr.writer.writeAll(switch (stderr.mode) { |
| 1415 | .escape_codes => "\x1B\x28\x30\x6d\x71\x1B\x28\x42 ", // └─ |
| 1416 | else => "+- ", |
| 1417 | }); |
| 1418 | } |
| 1419 | |
| 1236 | 1420 | /// Traverse the dependency graph depth-first and make it undirected by having |
| 1237 | 1421 | /// steps know their dependants (they only know dependencies at start). |
| 1238 | 1422 | /// Along the way, check that there is no dependency loop, and record the steps |
| ... | ... | @@ -1252,14 +1436,14 @@ fn constructGraphAndCheckForDependencyLoop( |
| 1252 | 1436 | step_stack: *std.AutoArrayHashMapUnmanaged(Configuration.Step.Index, void), |
| 1253 | 1437 | rand: std.Random, |
| 1254 | 1438 | ) error{ DependencyLoopDetected, OutOfMemory }!void { |
| 1255 | | const s: *Step = &steps[@intFromEnum(step_index)]; |
| 1256 | | switch (s.state) { |
| 1439 | const make_step: *Step = &steps[@intFromEnum(step_index)]; |
| 1440 | switch (make_step.state) { |
| 1257 | 1441 | .precheck_started => { |
| 1258 | 1442 | log.err("dependency loop detected: {s}", .{step_index.ptr(c).name.slice(c)}); |
| 1259 | 1443 | return error.DependencyLoopDetected; |
| 1260 | 1444 | }, |
| 1261 | 1445 | .precheck_unstarted => { |
| 1262 | | s.state = .precheck_started; |
| 1446 | make_step.state = .precheck_started; |
| 1263 | 1447 | |
| 1264 | 1448 | const step = step_index.ptr(c); |
| 1265 | 1449 | const dependencies = step.deps.slice(c); |
| ... | ... | @@ -1275,7 +1459,7 @@ fn constructGraphAndCheckForDependencyLoop( |
| 1275 | 1459 | for (deps) |dep| { |
| 1276 | 1460 | const dep_step: *Step = &steps[@intFromEnum(dep)]; |
| 1277 | 1461 | try step_stack.put(gpa, dep, {}); |
| 1278 | | try dep_step.dependants.append(gpa, s); |
| 1462 | try dep_step.dependants.append(gpa, step_index); |
| 1279 | 1463 | constructGraphAndCheckForDependencyLoop(gpa, c, steps, dep, step_stack, rand) catch |err| switch (err) { |
| 1280 | 1464 | error.DependencyLoopDetected => { |
| 1281 | 1465 | log.info("needed by: {s}", .{step_index.ptr(c).name.slice(c)}); |
| ... | ... | @@ -1285,8 +1469,8 @@ fn constructGraphAndCheckForDependencyLoop( |
| 1285 | 1469 | }; |
| 1286 | 1470 | } |
| 1287 | 1471 | |
| 1288 | | s.state = .precheck_done; |
| 1289 | | s.pending_deps = @intCast(dependencies.len); |
| 1472 | make_step.state = .precheck_done; |
| 1473 | make_step.pending_deps = @intCast(dependencies.len); |
| 1290 | 1474 | }, |
| 1291 | 1475 | .precheck_done => {}, |
| 1292 | 1476 | |
| ... | ... | @@ -1299,140 +1483,11 @@ fn constructGraphAndCheckForDependencyLoop( |
| 1299 | 1483 | } |
| 1300 | 1484 | } |
| 1301 | 1485 | |
| 1302 | | /// Runs the "make" function of the single step `s`, updates its state, and then spawns newly-ready |
| 1303 | | /// dependant steps in `group`. If `s` makes an RSS claim (i.e. `s.max_rss != 0`), the caller must |
| 1304 | | /// have already subtracted this value from `run.available_rss`. This function will release the RSS |
| 1305 | | /// claim (i.e. add `s.max_rss` back into `run.available_rss`) and queue any viable memory-blocked |
| 1306 | | /// steps after "make" completes for `s`. |
| 1307 | | fn makeStep( |
| 1308 | | graph: *Graph, |
| 1309 | | group: *Io.Group, |
| 1310 | | s: *Step, |
| 1311 | | root_prog_node: std.Progress.Node, |
| 1312 | | run: *Run, |
| 1313 | | ) Io.Cancelable!void { |
| 1314 | | const io = graph.io; |
| 1315 | | const gpa = run.gpa; |
| 1316 | | |
| 1317 | | { |
| 1318 | | const step_prog_node = root_prog_node.start(s.name, 0); |
| 1319 | | defer step_prog_node.end(); |
| 1320 | | |
| 1321 | | if (run.web_server) |*ws| ws.updateStepStatus(s, .wip); |
| 1322 | | |
| 1323 | | const new_state: Step.State = for (s.dependencies.items) |dep| { |
| 1324 | | switch (@atomicLoad(Step.State, &dep.state, .monotonic)) { |
| 1325 | | .precheck_unstarted => unreachable, |
| 1326 | | .precheck_started => unreachable, |
| 1327 | | .precheck_done => unreachable, |
| 1328 | | |
| 1329 | | .failure, |
| 1330 | | .dependency_failure, |
| 1331 | | .skipped_oom, |
| 1332 | | => break .dependency_failure, |
| 1333 | | |
| 1334 | | .success, .skipped => {}, |
| 1335 | | } |
| 1336 | | } else if (s.make(.{ |
| 1337 | | .progress_node = step_prog_node, |
| 1338 | | .watch = run.watch, |
| 1339 | | .web_server = if (run.web_server) |*ws| ws else null, |
| 1340 | | .unit_test_timeout_ns = run.unit_test_timeout_ns, |
| 1341 | | .gpa = gpa, |
| 1342 | | })) state: { |
| 1343 | | break :state .success; |
| 1344 | | } else |err| switch (err) { |
| 1345 | | error.MakeFailed => .failure, |
| 1346 | | error.MakeSkipped => .skipped, |
| 1347 | | }; |
| 1348 | | |
| 1349 | | @atomicStore(Step.State, &s.state, new_state, .monotonic); |
| 1350 | | |
| 1351 | | switch (new_state) { |
| 1352 | | .precheck_unstarted => unreachable, |
| 1353 | | .precheck_started => unreachable, |
| 1354 | | .precheck_done => unreachable, |
| 1355 | | |
| 1356 | | .failure, |
| 1357 | | .dependency_failure, |
| 1358 | | .skipped_oom, |
| 1359 | | => { |
| 1360 | | if (run.web_server) |*ws| ws.updateStepStatus(s, .failure); |
| 1361 | | std.Progress.setStatus(.failure_working); |
| 1362 | | }, |
| 1363 | | |
| 1364 | | .success, |
| 1365 | | .skipped, |
| 1366 | | => { |
| 1367 | | if (run.web_server) |*ws| ws.updateStepStatus(s, .success); |
| 1368 | | }, |
| 1369 | | } |
| 1370 | | } |
| 1371 | | |
| 1372 | | // No matter the result, we want to display error/warning messages. |
| 1373 | | if (s.result_error_bundle.errorMessageCount() > 0 or |
| 1374 | | s.result_error_msgs.items.len > 0 or |
| 1375 | | s.result_stderr.len > 0) |
| 1376 | | { |
| 1377 | | const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode); |
| 1378 | | defer io.unlockStderr(); |
| 1379 | | printErrorMessages(gpa, s, .{}, stderr.terminal(), run.error_style, run.multiline_errors) catch {}; |
| 1380 | | } |
| 1381 | | |
| 1382 | | if (s.max_rss != 0) { |
| 1383 | | var dispatch_set: std.ArrayList(*Step) = .empty; |
| 1384 | | defer dispatch_set.deinit(gpa); |
| 1385 | | |
| 1386 | | // Release our RSS claim and kick off some blocked steps if possible. We use `dispatch_set` |
| 1387 | | // as a staging buffer to avoid recursing into `makeStep` while `run.max_rss_mutex` is held. |
| 1388 | | { |
| 1389 | | try run.max_rss_mutex.lock(io); |
| 1390 | | defer run.max_rss_mutex.unlock(io); |
| 1391 | | run.available_rss += s.max_rss; |
| 1392 | | try dispatch_set.ensureUnusedCapacity(gpa, run.memory_blocked_steps.items.len); |
| 1393 | | while (run.memory_blocked_steps.getLast()) |candidate| { |
| 1394 | | if (run.available_rss < candidate.max_rss) break; |
| 1395 | | assert(run.memory_blocked_steps.pop() == candidate); |
| 1396 | | dispatch_set.appendAssumeCapacity(candidate); |
| 1397 | | } |
| 1398 | | } |
| 1399 | | for (dispatch_set.items) |candidate| { |
| 1400 | | group.async(io, makeStep, .{ graph, group, candidate, root_prog_node, run }); |
| 1401 | | } |
| 1402 | | } |
| 1403 | | |
| 1404 | | for (s.dependants.items) |dependant| { |
| 1405 | | // `.acq_rel` synchronizes with itself to ensure all dependencies' final states are visible when this hits 0. |
| 1406 | | if (@atomicRmw(u32, &dependant.pending_deps, .Sub, 1, .acq_rel) == 1) { |
| 1407 | | try stepReady(graph, group, dependant, root_prog_node, run); |
| 1408 | | } |
| 1409 | | } |
| 1410 | | } |
| 1411 | | |
| 1412 | | fn stepReady( |
| 1413 | | graph: *Graph, |
| 1414 | | group: *Io.Group, |
| 1415 | | s: *Step, |
| 1416 | | root_prog_node: std.Progress.Node, |
| 1417 | | run: *Run, |
| 1418 | | ) !void { |
| 1419 | | const io = graph.io; |
| 1420 | | if (s.max_rss != 0) { |
| 1421 | | try run.max_rss_mutex.lock(io); |
| 1422 | | defer run.max_rss_mutex.unlock(io); |
| 1423 | | if (run.available_rss < s.max_rss) { |
| 1424 | | // Running this step right now could possibly exceed the allotted RSS. |
| 1425 | | try run.memory_blocked_steps.append(run.gpa, s); |
| 1426 | | return; |
| 1427 | | } |
| 1428 | | run.available_rss -= s.max_rss; |
| 1429 | | } |
| 1430 | | group.async(io, makeStep, .{ graph, group, s, root_prog_node, run }); |
| 1431 | | } |
| 1432 | | |
| 1433 | 1486 | pub fn printErrorMessages( |
| 1434 | 1487 | gpa: Allocator, |
| 1435 | | failing_step: *Step, |
| 1488 | c: *const Configuration, |
| 1489 | make_steps: []Step, |
| 1490 | failing_step_index: Configuration.Step.Index, |
| 1436 | 1491 | options: std.zig.ErrorBundle.RenderOptions, |
| 1437 | 1492 | stderr: Io.Terminal, |
| 1438 | 1493 | error_style: ErrorStyle, |
| ... | ... | @@ -1442,26 +1497,28 @@ pub fn printErrorMessages( |
| 1442 | 1497 | if (error_style.verboseContext()) { |
| 1443 | 1498 | // Provide context for where these error messages are coming from by |
| 1444 | 1499 | // printing the corresponding Step subtree. |
| 1445 | | var step_stack: std.ArrayList(*Step) = .empty; |
| 1500 | var step_stack: std.ArrayList(Configuration.Step.Index) = .empty; |
| 1446 | 1501 | defer step_stack.deinit(gpa); |
| 1447 | | try step_stack.append(gpa, failing_step); |
| 1448 | | while (step_stack.items[step_stack.items.len - 1].dependants.items.len != 0) { |
| 1449 | | try step_stack.append(gpa, step_stack.items[step_stack.items.len - 1].dependants.items[0]); |
| 1502 | try step_stack.append(gpa, failing_step_index); |
| 1503 | while (true) { |
| 1504 | const last_step = &make_steps[@intFromEnum(step_stack.items[step_stack.items.len - 1])]; |
| 1505 | if (last_step.dependants.items.len == 0) break; |
| 1506 | try step_stack.append(gpa, last_step.dependants.items[0]); |
| 1450 | 1507 | } |
| 1451 | 1508 | |
| 1452 | 1509 | // Now, `step_stack` has the subtree that we want to print, in reverse order. |
| 1453 | 1510 | try stderr.setColor(.dim); |
| 1454 | 1511 | var indent: usize = 0; |
| 1455 | | while (step_stack.pop()) |s| : (indent += 1) { |
| 1512 | while (step_stack.pop()) |step_index| : (indent += 1) { |
| 1456 | 1513 | if (indent > 0) { |
| 1457 | 1514 | try writer.splatByteAll(' ', (indent - 1) * 3); |
| 1458 | 1515 | try printChildNodePrefix(stderr); |
| 1459 | 1516 | } |
| 1460 | 1517 | |
| 1461 | | try writer.writeAll(s.name); |
| 1518 | try writer.writeAll(step_index.ptr(c).name.slice(c)); |
| 1462 | 1519 | |
| 1463 | | if (s == failing_step) { |
| 1464 | | try printStepFailure(s, stderr, true); |
| 1520 | if (step_index == failing_step_index) { |
| 1521 | try printStepFailure(make_steps, step_index, stderr, true); |
| 1465 | 1522 | } else { |
| 1466 | 1523 | try writer.writeAll("\n"); |
| 1467 | 1524 | } |
| ... | ... | @@ -1470,11 +1527,13 @@ pub fn printErrorMessages( |
| 1470 | 1527 | } else { |
| 1471 | 1528 | // Just print the failing step itself. |
| 1472 | 1529 | try stderr.setColor(.dim); |
| 1473 | | try writer.writeAll(failing_step.name); |
| 1474 | | try printStepFailure(failing_step, stderr, true); |
| 1530 | try writer.writeAll(failing_step_index.ptr(c).name.slice(c)); |
| 1531 | try printStepFailure(make_steps, failing_step_index, stderr, true); |
| 1475 | 1532 | try stderr.setColor(.reset); |
| 1476 | 1533 | } |
| 1477 | 1534 | |
| 1535 | const failing_step = &make_steps[@intFromEnum(failing_step_index)]; |
| 1536 | |
| 1478 | 1537 | if (failing_step.result_stderr.len > 0) { |
| 1479 | 1538 | try writer.writeAll(failing_step.result_stderr); |
| 1480 | 1539 | if (!mem.endsWith(u8, failing_step.result_stderr, "\n")) { |
| ... | ... | @@ -1567,9 +1626,10 @@ fn fatalWithHint(comptime f: []const u8, args: anytype) noreturn { |
| 1567 | 1626 | fatal(f, args); |
| 1568 | 1627 | } |
| 1569 | 1628 | |
| 1570 | | fn cleanTmpFiles(io: Io, steps: []const *Step) void { |
| 1571 | | for (steps) |step| { |
| 1572 | | const wf = step.cast(Step.WriteFile) orelse continue; |
| 1629 | fn cleanTmpFiles(io: Io, steps: []const Configuration.Step.Index) void { |
| 1630 | for (steps) |step_index| { |
| 1631 | if (true) @panic("TODO"); |
| 1632 | const wf = step_index.cast(std.Build.Step.WriteFile) orelse continue; |
| 1573 | 1633 | if (wf.mode != .tmp) continue; |
| 1574 | 1634 | const path = wf.generated_directory.path orelse continue; |
| 1575 | 1635 | Io.Dir.cwd().deleteTree(io, path) catch |err| { |