From 349053e32239fb718b446c4f6a4c92387d6bb1b5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 25 May 2026 23:36:03 -0700 Subject: [PATCH 1/3] Maker: memory usage optimizations - choose smp_allocator depending on optimization mode - organize the globals - avoid pessimistically allocating failed command string - recover the PkgConfig memory - recover the memory from captureChildProcess - make Step.result_stderr gpa-owned so it doesn't leak when a step that fails with stderr is re-run - recover memory from evalZigTest and evalGeneric child process stdio streams --- lib/compiler/Maker.zig | 98 ++++++++++++++------------ lib/compiler/Maker/PkgConfig.zig | 21 ++++-- lib/compiler/Maker/Step.zig | 63 ++++++++++++----- lib/compiler/Maker/Step/Compile.zig | 12 ++-- lib/compiler/Maker/Step/Fmt.zig | 2 +- lib/compiler/Maker/Step/Run.zig | 81 +++++++++++---------- lib/compiler/Maker/Step/TranslateC.zig | 2 +- 7 files changed, 165 insertions(+), 114 deletions(-) diff --git a/lib/compiler/Maker.zig b/lib/compiler/Maker.zig index 07ca3d436352ff001131f7dd79074c5d797ce934..13d8e3f3da0f06e1a29d9fcc1610b9787c2aa2d2 100644 --- a/lib/compiler/Maker.zig +++ b/lib/compiler/Maker.zig @@ -16,6 +16,7 @@ const fmt = std.fmt; const log = std.log; const mem = std.mem; const process = std.process; +const Color = std.zig.Color; const Fuzz = @import("Maker/Fuzz.zig"); const Graph = @import("Maker/Graph.zig"); @@ -55,12 +56,59 @@ error_style: ErrorStyle, multiline_errors: MultilineErrors, summary: Summary, +var safe_allocator_instance: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{}); +var stdio_buffer_allocation: [256]u8 = undefined; +var stdout_writer_allocation: Io.File.Writer = undefined; +var debug_maker_leaks: bool = false; + +const is_debug_mode = builtin.mode == .Debug; +const use_safe_allocator = switch (builtin.mode) { + .Debug, .ReleaseSafe => true, + .ReleaseFast, .ReleaseSmall => false, +}; + +const InstallPaths = struct { + prefix: Path, + lib: Path, + bin: Path, + include: Path, +}; + +const PrintNode = struct { + parent: ?*PrintNode, + last: bool = false, +}; + +const ErrorStyle = enum { + verbose, + minimal, + verbose_clear, + minimal_clear, + fn verboseContext(s: ErrorStyle) bool { + return switch (s) { + .verbose, .verbose_clear => true, + .minimal, .minimal_clear => false, + }; + } + fn clearOnUpdate(s: ErrorStyle) bool { + return switch (s) { + .verbose, .minimal => false, + .verbose_clear, .minimal_clear => true, + }; + } +}; +const MultilineErrors = enum { indent, newline, none }; +const Summary = enum { all, new, failures, line, none }; + 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. - var safe_gpa_state: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{}); - defer _ = safe_gpa_state.deinit(); - const gpa = safe_gpa_state.allocator(); + // The build runner is long-lived in the following use cases: + // * `--watch` mode + // * `--webui` mode + // * A project that has a large, complex build graph. + const gpa = if (use_safe_allocator) safe_allocator_instance.allocator() else std.heap.smp_allocator; + defer if (use_safe_allocator) { + _ = safe_allocator_instance.deinit(); + }; var threaded: std.Io.Threaded = .init(gpa, .{ .environ = init.environ, @@ -689,13 +737,6 @@ fn countSubProcesses(maker: *Maker) usize { return count; } -const InstallPaths = struct { - prefix: Path, - lib: Path, - bin: Path, - include: Path, -}; - pub fn stepByIndex(maker: *const Maker, i: Configuration.Step.Index) *Step { return &maker.steps[@intFromEnum(i)]; } @@ -1018,6 +1059,7 @@ fn makeStepNames( fn deinit(maker: *Maker) void { const gpa = maker.gpa; for (maker.steps) |*step| { + step.clearResultStderr(gpa); step.clearFailedCommand(gpa); step.clearErrorBundle(gpa); step.inputs.deinit(gpa); @@ -1424,11 +1466,6 @@ fn printStepFailure( } } -const PrintNode = struct { - parent: ?*PrintNode, - last: bool = false, -}; - fn printPrefix(node: *PrintNode, stderr: Io.Terminal) !void { const parent = node.parent orelse return; const writer = stderr.writer; @@ -1657,28 +1694,6 @@ fn argsRest(args: []const [:0]const u8, idx: usize) ?[]const [:0]const u8 { return args[idx..]; } -const Color = std.zig.Color; -const ErrorStyle = enum { - verbose, - minimal, - verbose_clear, - minimal_clear, - fn verboseContext(s: ErrorStyle) bool { - return switch (s) { - .verbose, .verbose_clear => true, - .minimal, .minimal_clear => false, - }; - } - fn clearOnUpdate(s: ErrorStyle) bool { - return switch (s) { - .verbose, .minimal => false, - .verbose_clear, .minimal_clear => true, - }; - } -}; -const MultilineErrors = enum { indent, newline, none }; -const Summary = enum { all, new, failures, line, none }; - fn fatalWithHint(comptime f: []const u8, args: anytype) noreturn { log.info("to access the help menu: zig build -h", .{}); fatal(f, args); @@ -1701,9 +1716,6 @@ fn cleanTmpFiles(maker: *Maker, steps: []const Configuration.Step.Index) void { } } -var stdio_buffer_allocation: [256]u8 = undefined; -var stdout_writer_allocation: Io.File.Writer = undefined; - fn initStdoutWriter(io: Io) *Writer { stdout_writer_allocation = Io.File.stdout().writerStreaming(io, &stdio_buffer_allocation); return &stdout_writer_allocation.interface; @@ -2043,8 +2055,6 @@ fn removePoisonedConfiguration(io: Io, scanned_config: *const ScannedConfig) voi } } -const is_debug_mode = builtin.mode == .Debug; -var debug_maker_leaks: bool = false; inline fn debugMakerLeaks() bool { if (!is_debug_mode) return false; return debug_maker_leaks; diff --git a/lib/compiler/Maker/PkgConfig.zig b/lib/compiler/Maker/PkgConfig.zig index 09fcbacfbdab1c07973a6fabd942f406a3ebb941..56bf8f08d721aee1cfd0e678cc8f320e3b540d98 100644 --- a/lib/compiler/Maker/PkgConfig.zig +++ b/lib/compiler/Maker/PkgConfig.zig @@ -2,6 +2,7 @@ const std = @import("std"); const Io = std.Io; const mem = std.mem; const assert = std.debug.assert; +const Allocator = std.mem.Allocator; const Maker = @import("../Maker.zig"); const Step = @import("Step.zig"); @@ -23,6 +24,7 @@ pub const Result = std.zig.PkgConfig.Parsed; pub fn run( maker: *Maker, step: *Step, + arena: Allocator, progress_node: std.Progress.Node, lib_name: []const u8, /// If true, reports failure error messages on step rather than returning @@ -31,7 +33,6 @@ pub fn run( ) RunError!Result { const pc = &maker.pkg_config; const graph = maker.graph; - const arena = graph.arena; // TODO don't leak into process arena const pkg_config_exe = getExe(graph); const pkgs = try getPkgs(maker, step, progress_node, force); @@ -41,7 +42,7 @@ pub fn run( }; const pkg = pkgs.all[found_index]; - const stdout = try captureChildProcess(maker, step, .{ + const stdout = try captureChildProcess(maker, step, arena, .{ .argv = &.{ pkg_config_exe, pkg.name, "--cflags", "--libs" }, .progress_node = progress_node, .allow_failure = !force, @@ -69,11 +70,16 @@ fn getExe(graph: *const Graph) []const u8 { return std.zig.PkgConfig.exe(&graph.environ_map); } -fn getPkgs(maker: *Maker, step: *Step, progress_node: std.Progress.Node, force: bool) RunError!std.zig.PkgConfig { +fn getPkgs( + maker: *Maker, + step: *Step, + progress_node: std.Progress.Node, + force: bool, +) RunError!std.zig.PkgConfig { const graph = maker.graph; - const arena = graph.arena; // TODO don't leak into process arena const io = graph.io; const pc = &maker.pkg_config; + const arena = graph.arena; try pc.mutex.lock(io); defer pc.mutex.unlock(io); @@ -81,7 +87,7 @@ fn getPkgs(maker: *Maker, step: *Step, progress_node: std.Progress.Node, force: if (pc.pkgs) |pkgs| return pkgs; const pkg_config_exe = getExe(graph); - const stdout = try captureChildProcess(maker, step, .{ + const stdout = try captureChildProcess(maker, step, arena, .{ .argv = &.{ pkg_config_exe, "--list-all" }, .progress_node = progress_node, .allow_failure = !force, @@ -102,11 +108,12 @@ fn getPkgs(maker: *Maker, step: *Step, progress_node: std.Progress.Node, force: return result; } -fn captureChildProcess(maker: *Maker, step: *Step, options: Step.CaptureChildProcessOptions) ![]const u8 { - const captured = step.captureChildProcess(maker, options) catch |err| switch (err) { +fn captureChildProcess(maker: *Maker, step: *Step, arena: Allocator, options: Step.CaptureChildProcessOptions) ![]const u8 { + const captured = step.captureChildProcess(maker, arena, options) catch |err| switch (err) { error.FileNotFound => return error.PkgConfigUnavailable, else => |e| return e, }; + if (captured.stderr.len != 0) try step.setResultStderr(maker.gpa, captured.stderr); assert(step.result_failed_command != null); if (captured.term.success()) return captured.stdout; if (!options.allow_failure) return step.fail(maker, "{s} {f}", .{ options.argv[0], captured.term }); diff --git a/lib/compiler/Maker/Step.zig b/lib/compiler/Maker/Step.zig index 69d3676c32f4567c40cd97be29de8196f65f7756..6a6f5c1cf3718c2dce6c957283e528d7b0844ac4 100644 --- a/lib/compiler/Maker/Step.zig +++ b/lib/compiler/Maker/Step.zig @@ -54,8 +54,10 @@ dependants: std.ArrayList(Configuration.Step.Index) = .empty, inputs: Inputs = .init, pending_deps: u32 = undefined, +/// Array list and internal memory owned by process arena. result_error_msgs: std.ArrayList([]const u8) = .empty, result_error_bundle: std.zig.ErrorBundle = .empty, +/// Owned by `Maker.gpa`. result_stderr: []const u8 = "", result_cached: bool = false, /// Indicates error information is missing due to allocation failure. @@ -310,9 +312,8 @@ pub fn reset(step: *Step, maker: *Maker) void { const gpa = maker.gpa; clearFailedCommand(step, gpa); - + clearResultStderr(step, gpa); step.result_error_msgs.clearRetainingCapacity(); - step.result_stderr = ""; step.result_cached = false; step.result_duration_ns = null; step.result_peak_rss = 0; @@ -332,20 +333,23 @@ pub const CaptureChildProcessOptions = struct { allow_failure: bool = false, }; -/// Populates `s.result_failed_command`. -pub fn captureChildProcess(s: *Step, maker: *Maker, options: CaptureChildProcessOptions) !std.process.RunResult { +/// Populates `s.result_failed_command` unconditionally. +pub fn captureChildProcess( + s: *Step, + maker: *Maker, + allocator: Allocator, + options: CaptureChildProcessOptions, +) !std.process.RunResult { const gpa = maker.gpa; const graph = maker.graph; - const arena = graph.arena; // TODO stop leaking into process arena const io = graph.io; - clearFailedCommand(s, gpa); - s.result_failed_command = try std.zig.allocPrintCmd(gpa, options.argv, .{}); + s.setFailedCommand(gpa, options.argv, .{}); try handleChildProcUnsupported(s, maker); try graph.handleVerbose(null, null, options.argv); - const result = std.process.run(arena, io, .{ + const result = std.process.run(allocator, io, .{ .argv = options.argv, .environ_map = options.environ_map orelse &graph.environ_map, .progress_node = options.progress_node, @@ -358,7 +362,7 @@ pub fn captureChildProcess(s: *Step, maker: *Maker, options: CaptureChildProcess return s.fail(maker, "failed to run {s}: {t}", .{ options.argv[0], err }); }; - if (result.stderr.len > 0) try s.result_error_msgs.append(arena, result.stderr); + if (result.stderr.len > 0) try s.result_error_msgs.append(graph.arena, result.stderr); return result; } @@ -375,6 +379,21 @@ pub fn clearFailedCommand(s: *Step, gpa: Allocator) void { } } +pub fn setFailedCommand( + s: *Step, + gpa: Allocator, + argv: []const []const u8, + options: std.zig.AllocPrintCmdOptions, +) void { + s.clearFailedCommand(gpa); + s.result_failed_command = std.zig.allocPrintCmd(gpa, argv, options) catch |err| switch (err) { + error.OutOfMemory => { + s.result_oom = true; + return; + }, + }; +} + pub const FailError = error{ OutOfMemory, MakeFailed }; pub fn fail(step: *Step, maker: *const Maker, comptime fmt: []const u8, args: anytype) FailError { @@ -410,7 +429,8 @@ pub const ZigProcess = struct { /// Assumes that argv contains `--listen=-` and that the process being spawned /// is the zig compiler - the same version that compiled the build runner. -/// Populates `s.result_failed_command`. +/// +/// Populates `s.result_failed_command` on failure. pub fn evalZigProcess( step_index: Configuration.Step.Index, maker: *Maker, @@ -424,8 +444,7 @@ pub fn evalZigProcess( const io = graph.io; // If an error occurs, it's happened in this command: - clearFailedCommand(s, gpa); - s.result_failed_command = try std.zig.allocPrintCmd(gpa, argv, .{}); + errdefer s.setFailedCommand(gpa, argv, .{}); if (s.getZigProcess()) |zp| update: { assert(watch); @@ -683,16 +702,12 @@ fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void { }; } -/// Asserts that the caller has already populated `s.result_failed_command`. pub inline fn handleChildProcUnsupported(s: *Step, maker: *Maker) FailError!void { - assert(s.result_failed_command != null); if (!std.process.can_spawn) return s.fail(maker, "unable to spawn process: host cannot spawn child processes", .{}); } -/// Asserts that the caller has already populated `s.result_failed_command`. pub fn handleChildProcessTerm(s: *Step, maker: *Maker, term: std.process.Child.Term) FailError!void { - assert(s.result_failed_command != null); if (!term.success()) return s.fail(maker, "process {f}", .{term}); } @@ -861,3 +876,19 @@ fn oomWrap(s: *Step, result: error{OutOfMemory}!void) void { s.result_oom = true; }; } + +pub fn clearResultStderr(step: *Step, gpa: Allocator) void { + if (step.result_stderr.len != 0) { + gpa.free(step.result_stderr); + step.result_stderr = ""; + } +} + +pub fn setResultStderr(step: *Step, gpa: Allocator, bytes: []const u8) Allocator.Error!void { + takeResultStderr(step, gpa, try gpa.dupe(u8, bytes)); +} + +pub fn takeResultStderr(step: *Step, gpa: Allocator, owned: []const u8) void { + clearResultStderr(step, gpa); + step.result_stderr = owned; +} diff --git a/lib/compiler/Maker/Step/Compile.zig b/lib/compiler/Maker/Step/Compile.zig index 0b66dc282c801cd3cabff5c0a72e69cb36aab0c2..67006980770166273380ae96f8d592051aab8497 100644 --- a/lib/compiler/Maker/Step/Compile.zig +++ b/lib/compiler/Maker/Step/Compile.zig @@ -328,10 +328,10 @@ fn lowerZigArgs( const pkg_conf_node = progress_node.start("pkg-config", 0); defer pkg_conf_node.end(); - if (PkgConfig.run(maker, step, pkg_conf_node, system_lib_name, force)) |result| { - try zig_args.appendSlice(gpa, result.cflags); - try zig_args.appendSlice(gpa, result.libs); - try seen_system_libs.put(arena, system_lib.name, result.cflags); + if (PkgConfig.run(maker, step, arena, pkg_conf_node, system_lib_name, force)) |pc| { + try zig_args.appendSlice(gpa, pc.cflags); + try zig_args.appendSlice(gpa, pc.libs); + try seen_system_libs.put(arena, system_lib.name, pc.cflags); break :l; } else |err| switch (err) { error.PkgConfigUnavailable, @@ -960,8 +960,8 @@ pub fn rebuildInFuzzMode( const arena = arena_allocator.allocator(); step.result_error_msgs.clearRetainingCapacity(); - step.result_stderr = ""; - + step.clearResultStderr(gpa); + step.clearErrorBundle(gpa); step.result_error_bundle.deinit(gpa); step.result_error_bundle = std.zig.ErrorBundle.empty; diff --git a/lib/compiler/Maker/Step/Fmt.zig b/lib/compiler/Maker/Step/Fmt.zig index 281686bed57d488d5db088d9f1c2593edf4f6371..ef54df4fdc5c06942b4b8943f26bf01b7587cc59 100644 --- a/lib/compiler/Maker/Step/Fmt.zig +++ b/lib/compiler/Maker/Step/Fmt.zig @@ -43,7 +43,7 @@ pub fn make( argv.appendAssumeCapacity(try maker.resolveLazyPathIndexAbs(arena, lp, step_index)); } - const run_result = step.captureChildProcess(maker, .{ + const run_result = step.captureChildProcess(maker, arena, .{ .progress_node = progress_node, .argv = argv.items, .allow_failure = false, diff --git a/lib/compiler/Maker/Step/Run.zig b/lib/compiler/Maker/Step/Run.zig index e59fec11efd18b5fd714a27b67e90ba4762e5bf3..6e81674e89ec50600d49b10fa67bb3f863468b9c 100644 --- a/lib/compiler/Maker/Step/Run.zig +++ b/lib/compiler/Maker/Step/Run.zig @@ -713,7 +713,7 @@ const FuzzTestRunner = struct { } } - fn listen(f: *FuzzTestRunner, arena: Allocator) !void { + fn listen(f: *FuzzTestRunner) !void { const maker = f.ctx.fuzz.maker; const graph = maker.graph; const io = graph.io; @@ -737,7 +737,7 @@ const FuzzTestRunner = struct { else => |read_e| return read_e, }), 2 => try f.completeStderrRead(id, result.file_read_streaming catch |e| switch (e) { - error.EndOfStream => return f.instanceEos(arena, id), + error.EndOfStream => return f.instanceEos(id), else => |read_e| return read_e, }), else => unreachable, @@ -899,8 +899,9 @@ const FuzzTestRunner = struct { } }); } - fn instanceEos(f: *FuzzTestRunner, arena: Allocator, id: u32) !void { + fn instanceEos(f: *FuzzTestRunner, id: u32) !void { const maker = f.ctx.fuzz.maker; + const gpa = maker.gpa; const instance = &f.instances[id]; const run_index = f.run_index; @@ -912,7 +913,7 @@ const FuzzTestRunner = struct { instance.child.stdin = null; const term = try instance.child.wait(io); if (!termMatches(.{ .exited = 0 }, term)) { - step.result_stderr = try f.mergedStderr(arena); + step.takeResultStderr(gpa, try f.mergedStderr(gpa)); try f.saveCrash(id, term); return step.fail(maker, "test process unexpectedly {f}", .{fmtTerm(term)}); } @@ -1055,7 +1056,7 @@ const FuzzTestRunner = struct { } } - fn mergedStderr(f: *FuzzTestRunner, arena: Allocator) Allocator.Error![]const u8 { + fn mergedStderr(f: *FuzzTestRunner, gpa: Allocator) Allocator.Error![]const u8 { // Collect any available stderr while (f.batch.next()) |completion| { if (completion.index % 3 != 2) continue; @@ -1065,7 +1066,7 @@ const FuzzTestRunner = struct { var stderr_len: usize = 0; for (f.instances) |*instance| stderr_len += instance.stderr.items.len; - const stderr = try arena.alloc(u8, stderr_len); + const stderr = try gpa.alloc(u8, stderr_len); stderr_len = 0; for (f.instances) |*instance| { @@ -1086,13 +1087,12 @@ fn evalFuzzTest( var f: FuzzTestRunner = try .init(run, run_index, fuzz_context, progress_node, spawn_options); defer f.deinit(); try f.startInstances(); - try f.listen(fuzz_context.fuzz.maker.graph.arena); + try f.listen(); } const StdioPollEnum = enum { stdout, stderr }; fn evalZigTest( - arena: Allocator, run: *Run, run_index: Configuration.Step.Index, maker: *Maker, @@ -1140,7 +1140,7 @@ fn evalZigTest( }; switch (try waitZigTest( - arena, + graph.arena, run, run_index, maker, @@ -1158,7 +1158,7 @@ fn evalZigTest( error.ReadFailed => return stderr_fr.err.?, error.EndOfStream => {}, } - step.result_stderr = try arena.dupe(u8, stderr_fr.interface.buffered()); + step.takeResultStderr(gpa, try multi_reader.toOwnedSlice(1)); // Clean up everything and wait for the child to exit. child.stdin.?.close(io); @@ -1180,8 +1180,9 @@ fn evalZigTest( .no_poll => |no_poll| { // This might be a success (we requested exit and the child dutifully closed stdout) or // a crash of some kind. Either way, the child will terminate by itself -- wait for it. - const stderr_reader = multi_reader.reader(1); - const stderr_owned = try arena.dupe(u8, stderr_reader.buffered()); + const stderr_owned = try multi_reader.toOwnedSlice(1); + var keep_stderr_owned = false; + defer if (!keep_stderr_owned) gpa.free(stderr_owned); // Clean up everything and wait for the child to exit. child.stdin.?.close(io); @@ -1209,7 +1210,9 @@ fn evalZigTest( } // Report an error if the child terminated uncleanly or if we were still trying to run more tests. - step.result_stderr = stderr_owned; + step.takeResultStderr(gpa, stderr_owned); + keep_stderr_owned = true; + const tests_done = test_metadata != null and test_metadata.?.next_index == std.math.maxInt(u32); if (!tests_done or !termMatches(.{ .exited = 0 }, term)) { // The individual unit test results are irrelevant: the test runner itself broke! @@ -1234,9 +1237,10 @@ fn evalZigTest( return; }, .timeout => |timeout| { - const stderr_reader = multi_reader.reader(1); - const stderr = stderr_reader.buffered(); - stderr_reader.tossBuffered(); + const stderr_owned = try multi_reader.toOwnedSlice(1); + var keep_stderr_owned = false; + defer if (!keep_stderr_owned) gpa.free(stderr_owned); + if (timeout.active_test_index) |test_index| { // A test was running. Report the timeout against that test, and continue on to // the next test. @@ -1245,16 +1249,20 @@ fn evalZigTest( try step.addError(maker, "'{s}' timed out after {f}{s}{s}", .{ test_metadata.?.testName(test_index), Io.Duration{ .nanoseconds = timeout.ns_elapsed }, - if (stderr.len != 0) " with stderr:\n" else "", - std.mem.trim(u8, stderr, "\n"), + if (stderr_owned.len != 0) " with stderr:\n" else "", + std.mem.trim(u8, stderr_owned, "\n"), }); continue; } // Just log an error and let the child be killed. - step.result_stderr = try arena.dupe(u8, stderr); + step.takeResultStderr(gpa, stderr_owned); + keep_stderr_owned = true; + // The individual unit test results in `results` are irrelevant: the test runner // is broken! Fail immediately without populating `s.test_results`. - return step.fail(maker, "test runner failed to respond for {f}", .{Io.Duration{ .nanoseconds = timeout.ns_elapsed }}); + return step.fail(maker, "test runner failed to respond for {f}", .{ + Io.Duration{ .nanoseconds = timeout.ns_elapsed }, + }); }, } comptime unreachable; @@ -1456,8 +1464,8 @@ fn evalGeneric( try multi_reader.checkAnyError(); - stdout_bytes = try multi_reader.toOwnedSlice(0); - stderr_bytes = try multi_reader.toOwnedSlice(1); + stdout_bytes = multi_reader.reader(0).buffered(); + stderr_bytes = multi_reader.reader(1).buffered(); } else { var stdout_reader = stdout.readerStreaming(io, &.{}); const stdio_limit: Io.Limit = if (conf_run.stdio_limit.value) |x| .limited(x) else .unlimited; @@ -1484,7 +1492,7 @@ fn evalGeneric( else => true, }; if (stderr_is_diagnostic) { - step.result_stderr = bytes; + try step.setResultStderr(maker.gpa, bytes); } }; @@ -2084,16 +2092,11 @@ fn runCommand( }, else => { // On failure, report captured stderr like normal standard error output. - const bad_exit = switch (generic_result.term) { - .exited => |code| code != 0, - .signal, .stopped, .unknown => true, - }; - if (bad_exit) { + if (!generic_result.term.success()) { if (generic_result.stderr) |bytes| { - step.result_stderr = bytes; + try step.setResultStderr(gpa, bytes); } } - try step.handleChildProcessTerm(maker, generic_result.term); }, } @@ -2135,13 +2138,13 @@ fn spawnChildAndCollect( .inherit; // If an error occurs, it's caused by this command: - step.clearFailedCommand(gpa); - step.result_failed_command = try std.zig.allocPrintCmd(gpa, argv, .{ - .cwd = switch (child_cwd) { - .path => |p| p, - .dir => unreachable, - .inherit => null, - }, + const cwd_string = switch (child_cwd) { + .path => |p| p, + .dir => unreachable, + .inherit => null, + }; + errdefer step.setFailedCommand(gpa, argv, .{ + .cwd = cwd_string, .child_env = environ_map, .parent_env = &graph.environ_map, }); @@ -2178,7 +2181,7 @@ fn spawnChildAndCollect( if (conf_run.flags.stdio == .zig_test) { const started: Io.Clock.Timestamp = .now(io, .awake); - const result = evalZigTest(graph.arena, run, run_index, maker, progress_node, spawn_options, fuzz_context) catch |err| switch (err) { + const result = evalZigTest(run, run_index, maker, progress_node, spawn_options, fuzz_context) catch |err| switch (err) { error.Canceled => |e| return e, else => |e| e, }; @@ -2198,7 +2201,7 @@ fn spawnChildAndCollect( try setColorEnvironmentVariables(&conf_run, environ_map, terminal_mode); const started: Io.Clock.Timestamp = .now(io, .awake); - const result = evalGeneric(graph.arena, run_index, maker, spawn_options) catch |err| switch (err) { + const result = evalGeneric(arena, run_index, maker, spawn_options) catch |err| switch (err) { error.Canceled => |e| return e, else => |e| e, }; diff --git a/lib/compiler/Maker/Step/TranslateC.zig b/lib/compiler/Maker/Step/TranslateC.zig index 84176dde10f0fde70a9175803c3a325888920d82..f0355d8bcc913c4694bc547fdef8bd2de46ceeb5 100644 --- a/lib/compiler/Maker/Step/TranslateC.zig +++ b/lib/compiler/Maker/Step/TranslateC.zig @@ -112,7 +112,7 @@ pub fn make( const pkg_conf_node = progress_node.start("pkg-config", 0); defer pkg_conf_node.end(); - if (PkgConfig.run(maker, step, pkg_conf_node, system_lib_name, force)) |result| { + if (PkgConfig.run(maker, step, arena, pkg_conf_node, system_lib_name, force)) |result| { try argv.appendSlice(arena, result.cflags); try argv.appendSlice(arena, result.libs); try seen_system_libs.put(arena, system_lib.name, result.cflags); -- 2.54.0 From 29df938c229b4dd30e399413fe3263374a62753c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 26 May 2026 00:07:39 -0700 Subject: [PATCH 2/3] zig build: add "first time setup" to progress node when building the maker, so that users who freshly downloaded zig don't worry that it will take this long every time they run zig build --- src/main.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.zig b/src/main.zig index bd73f5a08a4d36cf65517b161d13b548d6ee19d6..4bdafc5280e92f66c009262f55356b2bcb18ce24 100644 --- a/src/main.zig +++ b/src/main.zig @@ -5808,7 +5808,7 @@ const MakeRunner = struct { }; fn compileMakeRunner(gpa: Allocator, arena: Allocator, io: Io, options: MakeRunner.Options) !MakeRunner { - const compile_prog_node = options.parent_prog_node.start("Compile Maker", 0); + const compile_prog_node = options.parent_prog_node.start("Compiling maker (first time setup)", 0); defer compile_prog_node.end(); const strip = options.optimize_mode != .Debug; -- 2.54.0 From 33680ced58de7efb5bfe18fc95d2b67fc8496aed Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 26 May 2026 09:02:09 -0700 Subject: [PATCH 3/3] CI: switch which script gets --maker-opt=Debug --- ci/x86_64-linux-debug-llvm.sh | 1 - ci/x86_64-linux-debug.sh | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/x86_64-linux-debug-llvm.sh b/ci/x86_64-linux-debug-llvm.sh index d4cbf3ce3cdd7b621e5031351257c0066d6eeedd..750243130a3619998ed4ca5dc5fe81043fb251f7 100755 --- a/ci/x86_64-linux-debug-llvm.sh +++ b/ci/x86_64-linux-debug-llvm.sh @@ -49,7 +49,6 @@ stage3-debug/bin/zig build \ -Dno-lib stage3-debug/bin/zig build test docs \ - --maker-opt=Debug \ --maxrss ${ZSF_MAX_RSS:-0} \ -Dlldb=$HOME/deps/lldb-zig/Debug-e0a42bb34/bin/lldb \ -Dlibc-test-path=$HOME/deps/libc-test-f2bac77 \ diff --git a/ci/x86_64-linux-debug.sh b/ci/x86_64-linux-debug.sh index e6d6c083539f9aa2b7ea3a24204a5371374f7d97..92f088fc541317ba0c19cae33b1af7bc6eed6bc7 100755 --- a/ci/x86_64-linux-debug.sh +++ b/ci/x86_64-linux-debug.sh @@ -48,6 +48,7 @@ stage3-debug/bin/zig build \ -Dno-lib stage3-debug/bin/zig build test docs \ + --maker-opt=Debug \ --maxrss ${ZSF_MAX_RSS:-0} \ -Dlldb=$HOME/deps/lldb-zig/Debug-e0a42bb34/bin/lldb \ -fqemu \ -- 2.54.0