zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit ef050483dff9f4fe57107b4c3ddba9a2692bdef4 (tree)
parent 3262698fb171fb2ed33cf6786c0573e922ce9a80
Author: Andrew Kelley <andrew@ziglang.org>
Date:   Wed, 18 Feb 2026 14:06:43 -0800

build maker: rename Run to Maker

Diffstat:
Mlib/compiler/maker.zig | 1175+++++++++++++++++++++++++++++++++++++++----------------------------------------
1 file changed, 587 insertions(+), 588 deletions(-)

diff --git a/lib/compiler/maker.zig b/lib/compiler/maker.zig @@ -1,3 +1,4 @@ +const Maker = @This(); const builtin = @import("builtin"); const std = @import("std"); @@ -26,6 +27,28 @@ pub const std_options: std.Options = .{ .http_disable_tls = true, }; +gpa: Allocator, +graph: *Graph, +install_paths: InstallPaths, +scanned_config: *const ScannedConfig, +steps: []Step, + +available_rss: usize, +max_rss_is_default: bool, +max_rss_mutex: Io.Mutex, +skip_oom_steps: bool, +unit_test_timeout_ns: ?u64, +watch: bool, +web_server: if (!builtin.single_threaded) ?WebServer else ?noreturn, +/// Allocated into `gpa`. +memory_blocked_steps: std.ArrayList(Configuration.Step.Index), +/// Allocated into `gpa`. +step_stack: std.AutoArrayHashMapUnmanaged(Configuration.Step.Index, void), + +error_style: ErrorStyle, +multiline_errors: MultilineErrors, +summary: Summary, + pub fn main(init: process.Init.Minimal) !void { // The build runner is often short-lived, but thanks to `--watch` and `--webui`, that's not // 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 { .sub_path = cwd_relative, } else try install_prefix_path.join(arena, "include"); - var run: Run = .{ + var maker: Maker = .{ .gpa = gpa, .graph = &graph, .scanned_config = &scanned_config, @@ -495,16 +518,16 @@ pub fn main(init: process.Init.Minimal) !void { .summary = summary orelse if (watch or webui_listen != null) .line else .failures, }; defer { - run.memory_blocked_steps.deinit(gpa); - run.step_stack.deinit(gpa); + maker.memory_blocked_steps.deinit(gpa); + maker.step_stack.deinit(gpa); } - if (run.available_rss == 0) { - run.available_rss = process.totalSystemMemory() catch std.math.maxInt(u64); - run.max_rss_is_default = true; + if (maker.available_rss == 0) { + maker.available_rss = process.totalSystemMemory() catch std.math.maxInt(u64); + maker.max_rss_is_default = true; } - run.prepare(step_names.items) catch |err| switch (err) { + maker.prepare(step_names.items) catch |err| switch (err) { error.DependencyLoopDetected, error.InsufficientMemory => { _ = io.lockStderr(&.{}, graph.stderr_mode) catch {}; process.exit(1); @@ -515,17 +538,17 @@ pub fn main(init: process.Init.Minimal) !void { var w: Watch = w: { if (!watch) break :w undefined; if (!Watch.have_impl) fatal("--watch not yet implemented for {t}", .{builtin.os.tag}); - break :w try .init(graph.cache.cwd, &scanned_config.configuration, run.steps); + break :w try .init(graph.cache.cwd, &scanned_config.configuration, maker.steps); }; const now = Io.Clock.Timestamp.now(io, .awake); - run.web_server = if (webui_listen) |listen_address| ws: { + maker.web_server = if (webui_listen) |listen_address| ws: { if (builtin.single_threaded) unreachable; // `fatal` above break :ws .init(.{ .gpa = gpa, .graph = &graph, - .all_steps = run.step_stack.keys(), + .all_steps = maker.step_stack.keys(), .root_prog_node = main_progress_node, .watch = watch, .listen_address = listen_address, @@ -534,20 +557,20 @@ pub fn main(init: process.Init.Minimal) !void { }); } else null; - if (run.web_server) |*ws| { + if (maker.web_server) |*ws| { ws.start() catch |err| fatal("failed to start web server: {t}", .{err}); } - rebuild: while (true) : (if (run.error_style.clearOnUpdate()) { + rebuild: while (true) : (if (maker.error_style.clearOnUpdate()) { const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode); defer io.unlockStderr(); try stderr.file_writer.interface.writeAll("\x1B[2J\x1B[3J\x1B[H"); }) { - if (run.web_server) |*ws| ws.startBuild(); + if (maker.web_server) |*ws| ws.startBuild(); - try run.makeStepNames(step_names.items, main_progress_node, fuzz); + try maker.makeStepNames(step_names.items, main_progress_node, fuzz); - if (run.web_server) |*web_server| { + if (maker.web_server) |*web_server| { if (fuzz) |mode| if (mode != .forever) fatal( "error: limited fuzzing is not implemented yet for --webui", .{}, @@ -556,13 +579,13 @@ pub fn main(init: process.Init.Minimal) !void { web_server.finishBuild(.{ .fuzz = fuzz != null }); } - if (run.web_server) |*ws| { + if (maker.web_server) |*ws| { const c = &scanned_config.configuration; assert(!watch); // fatal error after CLI parsing while (true) switch (try ws.wait()) { .rebuild => { - for (run.step_stack.keys()) |step_index| { - const step = run.stepByIndex(step_index); + for (maker.step_stack.keys()) |step_index| { + const step = maker.stepByIndex(step_index); step.state = .precheck_done; const deps = step_index.ptr(c).deps.slice(c); step.pending_deps = @intCast(deps.len); @@ -576,7 +599,7 @@ pub fn main(init: process.Init.Minimal) !void { // Comptime-known guard to prevent including the logic below when `!Watch.have_impl`. if (!Watch.have_impl) unreachable; - try w.update(gpa, run.step_stack.keys()); + try w.update(gpa, maker.step_stack.keys()); // Wait until a file system notification arrives. Read all such events // until the buffer is empty. Then wait for a debounce interval, resetting @@ -585,7 +608,7 @@ pub fn main(init: process.Init.Minimal) !void { // recursive dependants. var caption_buf: [std.Progress.Node.max_name_len]u8 = undefined; const caption = std.fmt.bufPrint(&caption_buf, "watching {d} directories, {d} processes", .{ - w.dir_count, countSubProcesses(run.steps, run.step_stack.keys()), + w.dir_count, countSubProcesses(maker.steps, maker.step_stack.keys()), }) catch &caption_buf; var debouncing_node = main_progress_node.start(caption, 0); var in_debounce = false; @@ -593,7 +616,7 @@ pub fn main(init: process.Init.Minimal) !void { .timeout => { assert(in_debounce); debouncing_node.end(); - markFailedStepsDirty(gpa, run.steps, run.step_stack.keys()); + markFailedStepsDirty(gpa, maker.steps, maker.step_stack.keys()); continue :rebuild; }, .dirty => if (!in_debounce) { @@ -634,436 +657,386 @@ fn countSubProcesses(make_steps: []Step, all_steps: []const Configuration.Step.I return count; } -const Run = struct { - gpa: Allocator, - graph: *Graph, - install_paths: InstallPaths, - scanned_config: *const ScannedConfig, - steps: []Step, - - available_rss: usize, - max_rss_is_default: bool, - max_rss_mutex: Io.Mutex, - skip_oom_steps: bool, - unit_test_timeout_ns: ?u64, - watch: bool, - web_server: if (!builtin.single_threaded) ?WebServer else ?noreturn, - /// Allocated into `gpa`. - memory_blocked_steps: std.ArrayList(Configuration.Step.Index), - /// Allocated into `gpa`. - step_stack: std.AutoArrayHashMapUnmanaged(Configuration.Step.Index, void), - - error_style: ErrorStyle, - multiline_errors: MultilineErrors, - summary: Summary, - - const InstallPaths = struct { - prefix: Path, - lib: Path, - bin: Path, - include: Path, - }; +const InstallPaths = struct { + prefix: Path, + lib: Path, + bin: Path, + include: Path, +}; - fn stepByIndex(run: *const Run, i: Configuration.Step.Index) *Step { - return &run.steps[@intFromEnum(i)]; - } +fn stepByIndex(maker: *const Maker, i: Configuration.Step.Index) *Step { + return &maker.steps[@intFromEnum(i)]; +} - fn prepare(run: *Run, step_names: []const []const u8) !void { - const gpa = run.gpa; - const graph = run.graph; - const arena = graph.arena; - const seed: u32 = graph.random_seed; - const step_stack = &run.step_stack; - const c = &run.scanned_config.configuration; +fn prepare(maker: *Maker, step_names: []const []const u8) !void { + const gpa = maker.gpa; + const graph = maker.graph; + const arena = graph.arena; + const seed: u32 = graph.random_seed; + const step_stack = &maker.step_stack; + const c = &maker.scanned_config.configuration; - @memset(run.steps, .{}); + @memset(maker.steps, .{}); - if (step_names.len == 0) { - try step_stack.put(gpa, c.default_step, {}); - } else { - try step_stack.ensureUnusedCapacity(gpa, step_names.len); - for (0..step_names.len) |i| { - const step_name = step_names[step_names.len - i - 1]; - const s = run.scanned_config.top_level_steps.get(step_name) orelse { - log.info("to list available steps: zig build -l", .{}); - fatal("no such step: {s}", .{step_name}); - }; - step_stack.putAssumeCapacity(s, {}); - } + if (step_names.len == 0) { + try step_stack.put(gpa, c.default_step, {}); + } else { + try step_stack.ensureUnusedCapacity(gpa, step_names.len); + for (0..step_names.len) |i| { + const step_name = step_names[step_names.len - i - 1]; + const s = maker.scanned_config.top_level_steps.get(step_name) orelse { + log.info("to list available steps: zig build -l", .{}); + fatal("no such step: {s}", .{step_name}); + }; + step_stack.putAssumeCapacity(s, {}); } + } - const starting_steps = try arena.dupe(Configuration.Step.Index, step_stack.keys()); + const starting_steps = try arena.dupe(Configuration.Step.Index, step_stack.keys()); - var rng = std.Random.DefaultPrng.init(seed); - const rand = rng.random(); - rand.shuffle(Configuration.Step.Index, starting_steps); + var rng = std.Random.DefaultPrng.init(seed); + const rand = rng.random(); + rand.shuffle(Configuration.Step.Index, starting_steps); - for (starting_steps) |s| { - try constructGraphAndCheckForDependencyLoop(gpa, c, run.steps, s, &run.step_stack, rand); - } + for (starting_steps) |s| { + try constructGraphAndCheckForDependencyLoop(gpa, c, maker.steps, s, &maker.step_stack, rand); + } - { - // Check that we have enough memory to complete the build. - var any_problems = false; - var max_needed: usize = 0; - for (step_stack.keys()) |step_index| { - const make_step = run.stepByIndex(step_index); - const conf_step = step_index.ptr(c); - const max_rss = conf_step.max_rss.toBytes(); - if (max_rss == 0) continue; - max_needed = @max(max_needed, max_rss); - if (max_rss > run.available_rss) { - if (run.skip_oom_steps) { - make_step.state = .skipped_oom; - for (make_step.dependants.items) |dependant| { - run.stepByIndex(dependant).pending_deps -= 1; - } - } else { - log.err("{s}{s}: this step declares an upper bound of {d} bytes of memory, exceeding the available {d} bytes of memory", .{ - conf_step.owner.depPrefixSlice(c), - conf_step.name.slice(c), - max_rss, - run.available_rss, - }); - any_problems = true; + { + // Check that we have enough memory to complete the build. + var any_problems = false; + var max_needed: usize = 0; + for (step_stack.keys()) |step_index| { + const make_step = maker.stepByIndex(step_index); + const conf_step = step_index.ptr(c); + const max_rss = conf_step.max_rss.toBytes(); + if (max_rss == 0) continue; + max_needed = @max(max_needed, max_rss); + if (max_rss > maker.available_rss) { + if (maker.skip_oom_steps) { + make_step.state = .skipped_oom; + for (make_step.dependants.items) |dependant| { + maker.stepByIndex(dependant).pending_deps -= 1; } - } - } - if (any_problems) { - if (run.max_rss_is_default) { - std.log.info("use --maxrss {d} to proceed, risking system memory exhaustion", .{ - max_needed, + } else { + log.err("{s}{s}: this step declares an upper bound of {d} bytes of memory, exceeding the available {d} bytes of memory", .{ + conf_step.owner.depPrefixSlice(c), + conf_step.name.slice(c), + max_rss, + maker.available_rss, }); + any_problems = true; } - return error.InsufficientMemory; } } + if (any_problems) { + if (maker.max_rss_is_default) { + std.log.info("use --maxrss {d} to proceed, risking system memory exhaustion", .{ + max_needed, + }); + } + return error.InsufficientMemory; + } } +} - fn makeStepNames( - run: *Run, - step_names: []const []const u8, - parent_prog_node: std.Progress.Node, - fuzz: ?Fuzz.Mode, - ) !void { - const graph = run.graph; - const gpa = run.gpa; - const io = graph.io; - const step_stack = &run.step_stack; - const top_level_steps = &run.scanned_config.top_level_steps; - const c = &run.scanned_config.configuration; - - { - // Collect the initial set of tasks (those with no outstanding dependencies) into a buffer, - // then spawn them. The buffer is so that we don't race with `makeStep` and end up thinking - // a step is initial when it actually became ready due to an earlier initial step. - var initial_set: std.ArrayList(Configuration.Step.Index) = .empty; - defer initial_set.deinit(gpa); - try initial_set.ensureUnusedCapacity(gpa, step_stack.count()); - for (step_stack.keys()) |step_index| { - const s = run.stepByIndex(step_index); - if (s.state == .precheck_done and s.pending_deps == 0) { - initial_set.appendAssumeCapacity(step_index); - } +fn makeStepNames( + maker: *Maker, + step_names: []const []const u8, + parent_prog_node: std.Progress.Node, + fuzz: ?Fuzz.Mode, +) !void { + const graph = maker.graph; + const gpa = maker.gpa; + const io = graph.io; + const step_stack = &maker.step_stack; + const top_level_steps = &maker.scanned_config.top_level_steps; + const c = &maker.scanned_config.configuration; + + { + // Collect the initial set of tasks (those with no outstanding dependencies) into a buffer, + // then spawn them. The buffer is so that we don't race with `makeStep` and end up thinking + // a step is initial when it actually became ready due to an earlier initial step. + var initial_set: std.ArrayList(Configuration.Step.Index) = .empty; + defer initial_set.deinit(gpa); + try initial_set.ensureUnusedCapacity(gpa, step_stack.count()); + for (step_stack.keys()) |step_index| { + const s = maker.stepByIndex(step_index); + if (s.state == .precheck_done and s.pending_deps == 0) { + initial_set.appendAssumeCapacity(step_index); } + } - const step_prog = parent_prog_node.start("steps", step_stack.count()); - defer step_prog.end(); + const step_prog = parent_prog_node.start("steps", step_stack.count()); + defer step_prog.end(); - var group: Io.Group = .init; - defer group.cancel(io); - // Start working on all of the initial steps... - for (initial_set.items) |step_index| try stepReady(run, &group, step_index, step_prog); - // ...and `makeStep` will trigger every other step when their last dependency finishes. - try group.await(io); - } + var group: Io.Group = .init; + defer group.cancel(io); + // Start working on all of the initial steps... + for (initial_set.items) |step_index| try stepReady(maker, &group, step_index, step_prog); + // ...and `makeStep` will trigger every other step when their last dependency finishes. + try group.await(io); + } - assert(run.memory_blocked_steps.items.len == 0); + assert(maker.memory_blocked_steps.items.len == 0); - var test_pass_count: usize = 0; - var test_skip_count: usize = 0; - var test_fail_count: usize = 0; - var test_crash_count: usize = 0; - var test_timeout_count: usize = 0; + var test_pass_count: usize = 0; + var test_skip_count: usize = 0; + var test_fail_count: usize = 0; + var test_crash_count: usize = 0; + var test_timeout_count: usize = 0; - var test_count: usize = 0; + var test_count: usize = 0; - var success_count: usize = 0; - var skipped_count: usize = 0; - var failure_count: usize = 0; - var pending_count: usize = 0; - var total_compile_errors: usize = 0; + var success_count: usize = 0; + var skipped_count: usize = 0; + var failure_count: usize = 0; + var pending_count: usize = 0; + var total_compile_errors: usize = 0; - var cleanup_task = io.async(cleanTmpFiles, .{ io, step_stack.keys() }); - defer cleanup_task.await(io); + var cleanup_task = io.async(cleanTmpFiles, .{ io, step_stack.keys() }); + defer cleanup_task.await(io); - for (step_stack.keys()) |step_index| { - const make_step = run.stepByIndex(step_index); - test_pass_count += make_step.test_results.passCount(); - test_skip_count += make_step.test_results.skip_count; - test_fail_count += make_step.test_results.fail_count; - test_crash_count += make_step.test_results.crash_count; - test_timeout_count += make_step.test_results.timeout_count; + for (step_stack.keys()) |step_index| { + const make_step = maker.stepByIndex(step_index); + test_pass_count += make_step.test_results.passCount(); + test_skip_count += make_step.test_results.skip_count; + test_fail_count += make_step.test_results.fail_count; + test_crash_count += make_step.test_results.crash_count; + test_timeout_count += make_step.test_results.timeout_count; - test_count += make_step.test_results.test_count; + test_count += make_step.test_results.test_count; - switch (make_step.state) { - .precheck_unstarted => unreachable, - .precheck_started => unreachable, - .precheck_done => unreachable, - .dependency_failure => pending_count += 1, - .success => success_count += 1, - .skipped, .skipped_oom => skipped_count += 1, - .failure => { - failure_count += 1; - const compile_errors_len = make_step.result_error_bundle.errorMessageCount(); - if (compile_errors_len > 0) { - total_compile_errors += compile_errors_len; - } - }, - } + switch (make_step.state) { + .precheck_unstarted => unreachable, + .precheck_started => unreachable, + .precheck_done => unreachable, + .dependency_failure => pending_count += 1, + .success => success_count += 1, + .skipped, .skipped_oom => skipped_count += 1, + .failure => { + failure_count += 1; + const compile_errors_len = make_step.result_error_bundle.errorMessageCount(); + if (compile_errors_len > 0) { + total_compile_errors += compile_errors_len; + } + }, } + } - if (fuzz) |mode| blk: { - switch (builtin.os.tag) { - // Current implementation depends on two things that need to be ported to Windows: - // * Memory-mapping to share data between the fuzzer and build runner. - // * COFF/PE support added to `std.debug.Info` (it needs a batching API for resolving - // many addresses to source locations). - .windows => fatal("--fuzz not yet implemented for {t}", .{builtin.os.tag}), - else => {}, - } - if (@bitSizeOf(usize) != 64) { - // Current implementation depends on posix.mmap()'s second parameter, `length: usize`, - // being compatible with file system's u64 return value. This is not the case - // on 32-bit platforms. - // Affects or affected by issues #5185, #22523, and #22464. - fatal("--fuzz not yet implemented on {d}-bit platforms", .{@bitSizeOf(usize)}); - } - - switch (mode) { - .forever => break :blk, - .limit => {}, - } + if (fuzz) |mode| blk: { + switch (builtin.os.tag) { + // Current implementation depends on two things that need to be ported to Windows: + // * Memory-mapping to share data between the fuzzer and build runner. + // * COFF/PE support added to `std.debug.Info` (it needs a batching API for resolving + // many addresses to source locations). + .windows => fatal("--fuzz not yet implemented for {t}", .{builtin.os.tag}), + else => {}, + } + if (@bitSizeOf(usize) != 64) { + // Current implementation depends on posix.mmap()'s second parameter, `length: usize`, + // being compatible with file system's u64 return value. This is not the case + // on 32-bit platforms. + // Affects or affected by issues #5185, #22523, and #22464. + fatal("--fuzz not yet implemented on {d}-bit platforms", .{@bitSizeOf(usize)}); + } - assert(mode == .limit); - var f = Fuzz.init( - gpa, - io, - step_stack.keys(), - parent_prog_node, - mode, - ) catch |err| fatal("failed to start fuzzer: {t}", .{err}); - defer f.deinit(); - - f.start(); - try f.waitAndPrintReport(); + switch (mode) { + .forever => break :blk, + .limit => {}, } - // Every test has a state - assert(test_pass_count + test_skip_count + test_fail_count + test_crash_count + test_timeout_count == test_count); + assert(mode == .limit); + var f = Fuzz.init( + gpa, + io, + step_stack.keys(), + parent_prog_node, + mode, + ) catch |err| fatal("failed to start fuzzer: {t}", .{err}); + defer f.deinit(); + + f.start(); + try f.waitAndPrintReport(); + } - if (failure_count == 0) { - std.Progress.setStatus(.success); - } else { - std.Progress.setStatus(.failure); - } + // Every test has a state + assert(test_pass_count + test_skip_count + test_fail_count + test_crash_count + test_timeout_count == test_count); - summary: { - switch (run.summary) { - .all, .new, .line => {}, - .failures => if (failure_count == 0) break :summary, - .none => break :summary, - } + if (failure_count == 0) { + std.Progress.setStatus(.success); + } else { + std.Progress.setStatus(.failure); + } - const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode); - defer io.unlockStderr(); - const t = stderr.terminal(); - const w = &stderr.file_writer.interface; + summary: { + switch (maker.summary) { + .all, .new, .line => {}, + .failures => if (failure_count == 0) break :summary, + .none => break :summary, + } - const total_count = success_count + failure_count + pending_count + skipped_count; - t.setColor(.cyan) catch {}; - t.setColor(.bold) catch {}; - w.writeAll("Build Summary: ") catch {}; - t.setColor(.reset) catch {}; - w.print("{d}/{d} steps succeeded", .{ success_count, total_count }) catch {}; - { - t.setColor(.dim) catch {}; - var first = true; - if (skipped_count > 0) { - w.print("{s}{d} skipped", .{ if (first) " (" else ", ", skipped_count }) catch {}; - first = false; - } - if (failure_count > 0) { - w.print("{s}{d} failed", .{ if (first) " (" else ", ", failure_count }) catch {}; - first = false; - } - if (!first) w.writeByte(')') catch {}; - t.setColor(.reset) catch {}; + const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode); + defer io.unlockStderr(); + const t = stderr.terminal(); + const w = &stderr.file_writer.interface; + + const total_count = success_count + failure_count + pending_count + skipped_count; + t.setColor(.cyan) catch {}; + t.setColor(.bold) catch {}; + w.writeAll("Build Summary: ") catch {}; + t.setColor(.reset) catch {}; + w.print("{d}/{d} steps succeeded", .{ success_count, total_count }) catch {}; + { + t.setColor(.dim) catch {}; + var first = true; + if (skipped_count > 0) { + w.print("{s}{d} skipped", .{ if (first) " (" else ", ", skipped_count }) catch {}; + first = false; } + if (failure_count > 0) { + w.print("{s}{d} failed", .{ if (first) " (" else ", ", failure_count }) catch {}; + first = false; + } + if (!first) w.writeByte(')') catch {}; + t.setColor(.reset) catch {}; + } - if (test_count > 0) { - w.print("; {d}/{d} tests passed", .{ test_pass_count, test_count }) catch {}; - t.setColor(.dim) catch {}; - var first = true; - if (test_skip_count > 0) { - w.print("{s}{d} skipped", .{ if (first) " (" else ", ", test_skip_count }) catch {}; - first = false; - } - if (test_fail_count > 0) { - w.print("{s}{d} failed", .{ if (first) " (" else ", ", test_fail_count }) catch {}; - first = false; - } - if (test_crash_count > 0) { - w.print("{s}{d} crashed", .{ if (first) " (" else ", ", test_crash_count }) catch {}; - first = false; - } - if (test_timeout_count > 0) { - w.print("{s}{d} timed out", .{ if (first) " (" else ", ", test_timeout_count }) catch {}; - first = false; - } - if (!first) w.writeByte(')') catch {}; - t.setColor(.reset) catch {}; + if (test_count > 0) { + w.print("; {d}/{d} tests passed", .{ test_pass_count, test_count }) catch {}; + t.setColor(.dim) catch {}; + var first = true; + if (test_skip_count > 0) { + w.print("{s}{d} skipped", .{ if (first) " (" else ", ", test_skip_count }) catch {}; + first = false; } + if (test_fail_count > 0) { + w.print("{s}{d} failed", .{ if (first) " (" else ", ", test_fail_count }) catch {}; + first = false; + } + if (test_crash_count > 0) { + w.print("{s}{d} crashed", .{ if (first) " (" else ", ", test_crash_count }) catch {}; + first = false; + } + if (test_timeout_count > 0) { + w.print("{s}{d} timed out", .{ if (first) " (" else ", ", test_timeout_count }) catch {}; + first = false; + } + if (!first) w.writeByte(')') catch {}; + t.setColor(.reset) catch {}; + } - w.writeAll("\n") catch {}; + w.writeAll("\n") catch {}; - if (run.summary == .line) break :summary; + if (maker.summary == .line) break :summary; - // Print a fancy tree with build results. - var step_stack_copy = try step_stack.clone(gpa); - defer step_stack_copy.deinit(gpa); + // Print a fancy tree with build results. + var step_stack_copy = try step_stack.clone(gpa); + defer step_stack_copy.deinit(gpa); - var print_node: PrintNode = .{ .parent = null }; - if (step_names.len == 0) { - print_node.last = true; - printTreeStep(run, c.default_step, t, &print_node, &step_stack_copy) catch |err| switch (err) { + var print_node: PrintNode = .{ .parent = null }; + if (step_names.len == 0) { + print_node.last = true; + printTreeStep(maker, c.default_step, t, &print_node, &step_stack_copy) catch |err| switch (err) { + error.Canceled => |e| return e, + else => {}, + }; + } else { + const last_index = if (maker.summary == .all) top_level_steps.count() else blk: { + var i: usize = step_names.len; + while (i > 0) { + i -= 1; + const step_index = top_level_steps.get(step_names[i]).?; + const step = maker.stepByIndex(step_index); + const found = switch (maker.summary) { + .all, .line, .none => unreachable, + .failures => step.state != .success, + .new => !step.result_cached, + }; + if (found) break :blk i; + } + break :blk top_level_steps.count(); + }; + for (step_names, 0..) |step_name, i| { + const step_index = top_level_steps.get(step_name).?; + print_node.last = i + 1 == last_index; + printTreeStep(maker, step_index, t, &print_node, &step_stack_copy) catch |err| switch (err) { error.Canceled => |e| return e, else => {}, }; - } else { - const last_index = if (run.summary == .all) top_level_steps.count() else blk: { - var i: usize = step_names.len; - while (i > 0) { - i -= 1; - const step_index = top_level_steps.get(step_names[i]).?; - const step = run.stepByIndex(step_index); - const found = switch (run.summary) { - .all, .line, .none => unreachable, - .failures => step.state != .success, - .new => !step.result_cached, - }; - if (found) break :blk i; - } - break :blk top_level_steps.count(); - }; - for (step_names, 0..) |step_name, i| { - const step_index = top_level_steps.get(step_name).?; - print_node.last = i + 1 == last_index; - printTreeStep(run, step_index, t, &print_node, &step_stack_copy) catch |err| switch (err) { - error.Canceled => |e| return e, - else => {}, - }; - } } - w.writeByte('\n') catch {}; } + w.writeByte('\n') catch {}; + } - if (run.watch or run.web_server != null) return; + if (maker.watch or maker.web_server != null) return; - // Perhaps in the future there could be an Advanced Options flag such as - // --debug-build-runner-leaks which would make this code return instead of - // calling exit. + // Perhaps in the future there could be an Advanced Options flag such as + // --debug-build-runner-leaks which would make this code return instead of + // calling exit. - const code: u8 = code: { - if (failure_count == 0) break :code 0; // success - if (run.error_style.verboseContext()) break :code 1; // failure; print build command - break :code 2; // failure; do not print build command - }; - _ = io.lockStderr(&.{}, graph.stderr_mode) catch {}; - process.exit(code); - } + const code: u8 = code: { + if (failure_count == 0) break :code 0; // success + if (maker.error_style.verboseContext()) break :code 1; // failure; print build command + break :code 2; // failure; do not print build command + }; + _ = io.lockStderr(&.{}, graph.stderr_mode) catch {}; + process.exit(code); +} - fn stepReady( - run: *Run, - group: *Io.Group, - step_index: Configuration.Step.Index, - root_prog_node: std.Progress.Node, - ) Io.Cancelable!void { - const graph = run.graph; - const io = graph.io; - const c = &run.scanned_config.configuration; - const max_rss = step_index.ptr(c).max_rss.toBytes(); - if (max_rss != 0) { - try run.max_rss_mutex.lock(io); - defer run.max_rss_mutex.unlock(io); - if (run.available_rss < max_rss) { - // Running this step right now could possibly exceed the allotted RSS. - run.memory_blocked_steps.append(run.gpa, step_index) catch - @panic("TODO eliminate memory allocation here"); - return; - } - run.available_rss -= max_rss; +fn stepReady( + maker: *Maker, + group: *Io.Group, + step_index: Configuration.Step.Index, + root_prog_node: std.Progress.Node, +) Io.Cancelable!void { + const graph = maker.graph; + const io = graph.io; + const c = &maker.scanned_config.configuration; + const max_rss = step_index.ptr(c).max_rss.toBytes(); + if (max_rss != 0) { + try maker.max_rss_mutex.lock(io); + defer maker.max_rss_mutex.unlock(io); + if (maker.available_rss < max_rss) { + // Running this step right now could possibly exceed the allotted RSS. + maker.memory_blocked_steps.append(maker.gpa, step_index) catch + @panic("TODO eliminate memory allocation here"); + return; } - group.async(io, makeStep, .{ run, group, step_index, root_prog_node }); + maker.available_rss -= max_rss; } + group.async(io, makeStep, .{ maker, group, step_index, root_prog_node }); +} - /// Runs the "make" function of the single step `s`, updates its state, and then spawns newly-ready - /// dependant steps in `group`. If `s` makes an RSS claim (i.e. `s.max_rss != 0`), the caller must - /// have already subtracted this value from `run.available_rss`. This function will release the RSS - /// claim (i.e. add `s.max_rss` back into `run.available_rss`) and queue any viable memory-blocked - /// steps after "make" completes for `s`. - fn makeStep( - run: *Run, - group: *Io.Group, - step_index: Configuration.Step.Index, - root_prog_node: std.Progress.Node, - ) Io.Cancelable!void { - const graph = run.graph; - const io = graph.io; - const gpa = run.gpa; - const c = &run.scanned_config.configuration; - const conf_step = step_index.ptr(c); - const step_name = conf_step.name.slice(c); - const deps = conf_step.deps.slice(c); - const make_step = run.stepByIndex(step_index); - - { - const step_prog_node = root_prog_node.start(step_name, 0); - defer step_prog_node.end(); - - if (run.web_server) |*ws| ws.updateStepStatus(step_index, .wip); - - const new_state: Step.State = for (deps) |dep_index| { - const dep_make_step = run.stepByIndex(dep_index); - switch (@atomicLoad(Step.State, &dep_make_step.state, .monotonic)) { - .precheck_unstarted => unreachable, - .precheck_started => unreachable, - .precheck_done => unreachable, - - .failure, - .dependency_failure, - .skipped_oom, - => break .dependency_failure, - - .success, .skipped => {}, - } - } else if (make_step.make(.{ - .progress_node = step_prog_node, - .watch = run.watch, - .web_server = if (run.web_server) |*ws| ws else null, - .unit_test_timeout_ns = run.unit_test_timeout_ns, - .gpa = gpa, - })) state: { - break :state .success; - } else |err| switch (err) { - error.MakeFailed => .failure, - error.MakeSkipped => .skipped, - }; - - @atomicStore(Step.State, &make_step.state, new_state, .monotonic); - - switch (new_state) { +/// Runs the "make" function of the single step `s`, updates its state, and then spawns newly-ready +/// dependant steps in `group`. If `s` makes an RSS claim (i.e. `s.max_rss != 0`), the caller must +/// have already subtracted this value from `maker.available_rss`. This function will release the RSS +/// claim (i.e. add `s.max_rss` back into `maker.available_rss`) and queue any viable memory-blocked +/// steps after "make" completes for `s`. +fn makeStep( + maker: *Maker, + group: *Io.Group, + step_index: Configuration.Step.Index, + root_prog_node: std.Progress.Node, +) Io.Cancelable!void { + const graph = maker.graph; + const io = graph.io; + const gpa = maker.gpa; + const c = &maker.scanned_config.configuration; + const conf_step = step_index.ptr(c); + const step_name = conf_step.name.slice(c); + const deps = conf_step.deps.slice(c); + const make_step = maker.stepByIndex(step_index); + + { + const step_prog_node = root_prog_node.start(step_name, 0); + defer step_prog_node.end(); + + if (maker.web_server) |*ws| ws.updateStepStatus(step_index, .wip); + + const new_state: Step.State = for (deps) |dep_index| { + const dep_make_step = maker.stepByIndex(dep_index); + switch (@atomicLoad(Step.State, &dep_make_step.state, .monotonic)) { .precheck_unstarted => unreachable, .precheck_started => unreachable, .precheck_done => unreachable, @@ -1071,234 +1044,260 @@ const Run = struct { .failure, .dependency_failure, .skipped_oom, - => { - if (run.web_server) |*ws| ws.updateStepStatus(step_index, .failure); - std.Progress.setStatus(.failure_working); - }, + => break .dependency_failure, - .success, - .skipped, - => { - if (run.web_server) |*ws| ws.updateStepStatus(step_index, .success); - }, + .success, .skipped => {}, } + } else if (make_step.make(.{ + .progress_node = step_prog_node, + .watch = maker.watch, + .web_server = if (maker.web_server) |*ws| ws else null, + .unit_test_timeout_ns = maker.unit_test_timeout_ns, + .gpa = gpa, + })) state: { + break :state .success; + } else |err| switch (err) { + error.MakeFailed => .failure, + error.MakeSkipped => .skipped, + }; + + @atomicStore(Step.State, &make_step.state, new_state, .monotonic); + + switch (new_state) { + .precheck_unstarted => unreachable, + .precheck_started => unreachable, + .precheck_done => unreachable, + + .failure, + .dependency_failure, + .skipped_oom, + => { + if (maker.web_server) |*ws| ws.updateStepStatus(step_index, .failure); + std.Progress.setStatus(.failure_working); + }, + + .success, + .skipped, + => { + if (maker.web_server) |*ws| ws.updateStepStatus(step_index, .success); + }, } + } - // No matter the result, we want to display error/warning messages. - if (make_step.result_error_bundle.errorMessageCount() > 0 or - make_step.result_error_msgs.items.len > 0 or - make_step.result_stderr.len > 0) - { - const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode); - defer io.unlockStderr(); - printErrorMessages(gpa, c, run.steps, step_index, .{}, stderr.terminal(), run.error_style, run.multiline_errors) catch |err| switch (err) { + // No matter the result, we want to display error/warning messages. + if (make_step.result_error_bundle.errorMessageCount() > 0 or + make_step.result_error_msgs.items.len > 0 or + make_step.result_stderr.len > 0) + { + const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode); + defer io.unlockStderr(); + printErrorMessages(gpa, c, maker.steps, step_index, .{}, stderr.terminal(), maker.error_style, maker.multiline_errors) catch |err| switch (err) { + error.Canceled => |e| return e, + error.WriteFailed => switch (stderr.file_writer.err.?) { error.Canceled => |e| return e, - error.WriteFailed => switch (stderr.file_writer.err.?) { - error.Canceled => |e| return e, - else => {}, - }, else => {}, - }; - } + }, + else => {}, + }; + } - const max_rss = conf_step.max_rss.toBytes(); - if (max_rss != 0) { - var dispatch_set: std.ArrayList(Configuration.Step.Index) = .empty; - defer dispatch_set.deinit(gpa); - - // Release our RSS claim and kick off some blocked steps if possible. We use `dispatch_set` - // as a staging buffer to avoid recursing into `makeStep` while `run.max_rss_mutex` is held. - { - try run.max_rss_mutex.lock(io); - defer run.max_rss_mutex.unlock(io); - run.available_rss += max_rss; - dispatch_set.ensureUnusedCapacity(gpa, run.memory_blocked_steps.items.len) catch - @panic("TODO eliminate memory allocation here"); - while (run.memory_blocked_steps.getLast()) |candidate_index| { - const candidate_max_rss = candidate_index.ptr(c).max_rss.toBytes(); - if (run.available_rss < candidate_max_rss) break; - assert(run.memory_blocked_steps.pop() == candidate_index); - dispatch_set.appendAssumeCapacity(candidate_index); - } - } - for (dispatch_set.items) |candidate| { - group.async(io, makeStep, .{ run, group, candidate, root_prog_node }); + const max_rss = conf_step.max_rss.toBytes(); + if (max_rss != 0) { + var dispatch_set: std.ArrayList(Configuration.Step.Index) = .empty; + defer dispatch_set.deinit(gpa); + + // Release our RSS claim and kick off some blocked steps if possible. We use `dispatch_set` + // as a staging buffer to avoid recursing into `makeStep` while `maker.max_rss_mutex` is held. + { + try maker.max_rss_mutex.lock(io); + defer maker.max_rss_mutex.unlock(io); + maker.available_rss += max_rss; + dispatch_set.ensureUnusedCapacity(gpa, maker.memory_blocked_steps.items.len) catch + @panic("TODO eliminate memory allocation here"); + while (maker.memory_blocked_steps.getLast()) |candidate_index| { + const candidate_max_rss = candidate_index.ptr(c).max_rss.toBytes(); + if (maker.available_rss < candidate_max_rss) break; + assert(maker.memory_blocked_steps.pop() == candidate_index); + dispatch_set.appendAssumeCapacity(candidate_index); } } + for (dispatch_set.items) |candidate| { + group.async(io, makeStep, .{ maker, group, candidate, root_prog_node }); + } + } - for (make_step.dependants.items) |dependant_index| { - const dependant = run.stepByIndex(dependant_index); - // `.acq_rel` synchronizes with itself to ensure all dependencies' final states are visible when this hits 0. - if (@atomicRmw(u32, &dependant.pending_deps, .Sub, 1, .acq_rel) == 1) { - try stepReady(run, group, dependant_index, root_prog_node); - } + for (make_step.dependants.items) |dependant_index| { + const dependant = maker.stepByIndex(dependant_index); + // `.acq_rel` synchronizes with itself to ensure all dependencies' final states are visible when this hits 0. + if (@atomicRmw(u32, &dependant.pending_deps, .Sub, 1, .acq_rel) == 1) { + try stepReady(maker, group, dependant_index, root_prog_node); } } +} - fn printTreeStep( - run: *const Run, - step_index: Configuration.Step.Index, - stderr: Io.Terminal, - parent_node: *PrintNode, - step_stack: *std.AutoArrayHashMapUnmanaged(Configuration.Step.Index, void), - ) !void { - const writer = stderr.writer; - const first = step_stack.swapRemove(step_index); - const summary = run.summary; - const c = &run.scanned_config.configuration; - const conf_step = step_index.ptr(c); - const make_step = run.stepByIndex(step_index); - const skip = switch (summary) { - .none, .line => unreachable, - .all => false, - .new => make_step.result_cached, - .failures => make_step.state == .success, - }; - if (skip) return; - try printPrefix(parent_node, stderr); +fn printTreeStep( + maker: *const Maker, + step_index: Configuration.Step.Index, + stderr: Io.Terminal, + parent_node: *PrintNode, + step_stack: *std.AutoArrayHashMapUnmanaged(Configuration.Step.Index, void), +) !void { + const writer = stderr.writer; + const first = step_stack.swapRemove(step_index); + const summary = maker.summary; + const c = &maker.scanned_config.configuration; + const conf_step = step_index.ptr(c); + const make_step = maker.stepByIndex(step_index); + const skip = switch (summary) { + .none, .line => unreachable, + .all => false, + .new => make_step.result_cached, + .failures => make_step.state == .success, + }; + if (skip) return; + try printPrefix(parent_node, stderr); - if (parent_node.parent != null) { - if (parent_node.last) { - try printChildNodePrefix(stderr); - } else { - try writer.writeAll(switch (stderr.mode) { - .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─ - else => "+- ", - }); - } + if (parent_node.parent != null) { + if (parent_node.last) { + try printChildNodePrefix(stderr); + } else { + try writer.writeAll(switch (stderr.mode) { + .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─ + else => "+- ", + }); } + } - if (!first) try stderr.setColor(.dim); + if (!first) try stderr.setColor(.dim); - // dep_prefix omitted here because it is redundant with the tree. - try writer.writeAll(conf_step.name.slice(c)); + // dep_prefix omitted here because it is redundant with the tree. + try writer.writeAll(conf_step.name.slice(c)); - const deps = conf_step.deps.slice(c); + const deps = conf_step.deps.slice(c); - if (first) { - try printStepStatus(run, step_index, stderr); + if (first) { + try printStepStatus(maker, step_index, stderr); - const last_index = if (summary == .all) deps.len -| 1 else blk: { - var i: usize = deps.len; - while (i > 0) { - i -= 1; + const last_index = if (summary == .all) deps.len -| 1 else blk: { + var i: usize = deps.len; + while (i > 0) { + i -= 1; - const dep_index = deps[i]; - const dep = run.stepByIndex(dep_index); - const found = switch (summary) { - .all, .line, .none => unreachable, - .failures => dep.state != .success, - .new => !dep.result_cached, - }; - if (found) break :blk i; - } - break :blk deps.len -| 1; - }; - for (deps, 0..) |dep, i| { - var print_node: PrintNode = .{ - .parent = parent_node, - .last = i == last_index, + const dep_index = deps[i]; + const dep = maker.stepByIndex(dep_index); + const found = switch (summary) { + .all, .line, .none => unreachable, + .failures => dep.state != .success, + .new => !dep.result_cached, }; - try printTreeStep(run, dep, stderr, &print_node, step_stack); + if (found) break :blk i; } + break :blk deps.len -| 1; + }; + for (deps, 0..) |dep, i| { + var print_node: PrintNode = .{ + .parent = parent_node, + .last = i == last_index, + }; + try printTreeStep(maker, dep, stderr, &print_node, step_stack); + } + } else { + if (deps.len == 0) { + try writer.writeAll(" (reused)\n"); } else { - if (deps.len == 0) { - try writer.writeAll(" (reused)\n"); - } else { - try writer.print(" (+{d} more reused dependencies)\n", .{deps.len}); - } - try stderr.setColor(.reset); + try writer.print(" (+{d} more reused dependencies)\n", .{deps.len}); } + try stderr.setColor(.reset); } +} - fn printStepStatus(run: *const Run, step_index: Configuration.Step.Index, stderr: Io.Terminal) !void { - const s = run.stepByIndex(step_index); - const writer = stderr.writer; - switch (s.state) { - .precheck_unstarted => unreachable, - .precheck_started => unreachable, - .precheck_done => unreachable, - - .dependency_failure => { - try stderr.setColor(.dim); - try writer.writeAll(" transitive failure\n"); - try stderr.setColor(.reset); - }, +fn printStepStatus(maker: *const Maker, step_index: Configuration.Step.Index, stderr: Io.Terminal) !void { + const s = maker.stepByIndex(step_index); + const writer = stderr.writer; + switch (s.state) { + .precheck_unstarted => unreachable, + .precheck_started => unreachable, + .precheck_done => unreachable, + + .dependency_failure => { + try stderr.setColor(.dim); + try writer.writeAll(" transitive failure\n"); + try stderr.setColor(.reset); + }, - .success => { - try stderr.setColor(.green); - if (s.result_cached) { - try writer.writeAll(" cached"); - } else if (s.test_results.test_count > 0) { - const pass_count = s.test_results.passCount(); - assert(s.test_results.test_count == pass_count + s.test_results.skip_count); - try writer.print(" {d} pass", .{pass_count}); - if (s.test_results.skip_count > 0) { - try stderr.setColor(.reset); - try writer.writeAll(", "); - try stderr.setColor(.yellow); - try writer.print("{d} skip", .{s.test_results.skip_count}); - } + .success => { + try stderr.setColor(.green); + if (s.result_cached) { + try writer.writeAll(" cached"); + } else if (s.test_results.test_count > 0) { + const pass_count = s.test_results.passCount(); + assert(s.test_results.test_count == pass_count + s.test_results.skip_count); + try writer.print(" {d} pass", .{pass_count}); + if (s.test_results.skip_count > 0) { try stderr.setColor(.reset); - try writer.print(" ({d} total)", .{s.test_results.test_count}); - } else { - try writer.writeAll(" success"); + try writer.writeAll(", "); + try stderr.setColor(.yellow); + try writer.print("{d} skip", .{s.test_results.skip_count}); } try stderr.setColor(.reset); - if (s.result_duration_ns) |ns| { - try stderr.setColor(.dim); - if (ns >= std.time.ns_per_min) { - try writer.print(" {d}m", .{ns / std.time.ns_per_min}); - } else if (ns >= std.time.ns_per_s) { - try writer.print(" {d}s", .{ns / std.time.ns_per_s}); - } else if (ns >= std.time.ns_per_ms) { - try writer.print(" {d}ms", .{ns / std.time.ns_per_ms}); - } else if (ns >= std.time.ns_per_us) { - try writer.print(" {d}us", .{ns / std.time.ns_per_us}); - } else { - try writer.print(" {d}ns", .{ns}); - } - try stderr.setColor(.reset); - } - if (s.result_peak_rss != 0) { - const rss = s.result_peak_rss; - try stderr.setColor(.dim); - if (rss >= 1000_000_000) { - try writer.print(" MaxRSS:{d}G", .{rss / 1000_000_000}); - } else if (rss >= 1000_000) { - try writer.print(" MaxRSS:{d}M", .{rss / 1000_000}); - } else if (rss >= 1000) { - try writer.print(" MaxRSS:{d}K", .{rss / 1000}); - } else { - try writer.print(" MaxRSS:{d}B", .{rss}); - } - try stderr.setColor(.reset); + try writer.print(" ({d} total)", .{s.test_results.test_count}); + } else { + try writer.writeAll(" success"); + } + try stderr.setColor(.reset); + if (s.result_duration_ns) |ns| { + try stderr.setColor(.dim); + if (ns >= std.time.ns_per_min) { + try writer.print(" {d}m", .{ns / std.time.ns_per_min}); + } else if (ns >= std.time.ns_per_s) { + try writer.print(" {d}s", .{ns / std.time.ns_per_s}); + } else if (ns >= std.time.ns_per_ms) { + try writer.print(" {d}ms", .{ns / std.time.ns_per_ms}); + } else if (ns >= std.time.ns_per_us) { + try writer.print(" {d}us", .{ns / std.time.ns_per_us}); + } else { + try writer.print(" {d}ns", .{ns}); } - try writer.writeAll("\n"); - }, - .skipped => { - try stderr.setColor(.yellow); - try writer.writeAll(" skipped\n"); try stderr.setColor(.reset); - }, - .skipped_oom => { - const c = &run.scanned_config.configuration; - const max_rss = step_index.ptr(c).max_rss.toBytes(); - try stderr.setColor(.yellow); - try writer.writeAll(" skipped (not enough memory)"); + } + if (s.result_peak_rss != 0) { + const rss = s.result_peak_rss; try stderr.setColor(.dim); - try writer.print(" upper bound of {d} exceeded runner limit ({d})\n", .{ - max_rss, run.available_rss, - }); - try stderr.setColor(.reset); - }, - .failure => { - try printStepFailure(run.steps, step_index, stderr, false); + if (rss >= 1000_000_000) { + try writer.print(" MaxRSS:{d}G", .{rss / 1000_000_000}); + } else if (rss >= 1000_000) { + try writer.print(" MaxRSS:{d}M", .{rss / 1000_000}); + } else if (rss >= 1000) { + try writer.print(" MaxRSS:{d}K", .{rss / 1000}); + } else { + try writer.print(" MaxRSS:{d}B", .{rss}); + } try stderr.setColor(.reset); - }, - } + } + try writer.writeAll("\n"); + }, + .skipped => { + try stderr.setColor(.yellow); + try writer.writeAll(" skipped\n"); + try stderr.setColor(.reset); + }, + .skipped_oom => { + const c = &maker.scanned_config.configuration; + const max_rss = step_index.ptr(c).max_rss.toBytes(); + try stderr.setColor(.yellow); + try writer.writeAll(" skipped (not enough memory)"); + try stderr.setColor(.dim); + try writer.print(" upper bound of {d} exceeded runner limit ({d})\n", .{ + max_rss, maker.available_rss, + }); + try stderr.setColor(.reset); + }, + .failure => { + try printStepFailure(maker.steps, step_index, stderr, false); + try stderr.setColor(.reset); + }, } -}; +} fn printStepFailure( make_steps: []Step,