zig

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

commit 4e3d14f590160013e655f69e249997ff597f8e93 (tree)
parent c8b583885d75524fc92cc02a9d00a49a76f2ea70
Author: Andrew Kelley <andrew@ziglang.org>
Date:   Tue, 28 Apr 2026 22:15:09 -0700

maker: update more Run step logic

Diffstat:
Mlib/compiler/Maker/Step.zig | 8++++----
Mlib/compiler/Maker/Step/Run.zig | 632+++++++++++++++++++++++++++++++++++++++++--------------------------------------
2 files changed, 330 insertions(+), 310 deletions(-)

diff --git a/lib/compiler/Maker/Step.zig b/lib/compiler/Maker/Step.zig @@ -18,10 +18,10 @@ const assert = std.debug.assert; const WebServer = @import("WebServer.zig"); const Maker = @import("../Maker.zig"); -const Compile = @import("Step/Compile.zig"); -const Run = @import("Step/Run.zig"); -const InstallArtifact = @import("Step/InstallArtifact.zig"); -const InstallFile = @import("Step/InstallFile.zig"); +pub const Compile = @import("Step/Compile.zig"); +pub const Run = @import("Step/Run.zig"); +pub const InstallArtifact = @import("Step/InstallArtifact.zig"); +pub const InstallFile = @import("Step/InstallFile.zig"); /// Avoid false sharing. _: void align(std.atomic.cache_line) = {}, diff --git a/lib/compiler/Maker/Step/Run.zig b/lib/compiler/Maker/Step/Run.zig @@ -325,9 +325,10 @@ pub fn make( /// * The wait fails, indicating the child closed stdout and stderr fn waitZigTest( run: *Run, + run_index: Configuration.Step.Index, maker: *Maker, child: *process.Child, - options: Step.MakeOptions, + progress_node: std.Progress.Node, multi_reader: *Io.File.MultiReader, opt_metadata: *?TestMetadata, results: *Step.TestResults, @@ -342,9 +343,11 @@ fn waitZigTest( ns_elapsed: u64, }, } { - const gpa = run.step.owner.allocator; - const arena = run.step.owner.allocator; - const io = run.step.owner.graph.io; + const graph = maker.graph; + const gpa = maker.gpa; + const io = graph.io; + const arena = graph.arena; // TODO don't leak into the process arena + const step = maker.stepByIndex(run_index); var sub_prog_node: ?std.Progress.Node = null; defer if (sub_prog_node) |n| n.end(); @@ -367,10 +370,10 @@ fn waitZigTest( // start and it acknowledging the test starting, we terminate the child and raise an error. This // *should* never happen, but could in theory be caused by some very unlucky IB in a test. const response_timeout: Io.Clock.Duration = t: { - const ns = @max(options.unit_test_timeout_ns orelse 0, 60 * std.time.ns_per_s); + const ns = @max(maker.unit_test_timeout_ns orelse 0, 60 * std.time.ns_per_s); break :t .{ .clock = .awake, .raw = .fromNanoseconds(ns) }; }; - const test_timeout: ?Io.Clock.Duration = if (options.unit_test_timeout_ns) |ns| .{ + const test_timeout: ?Io.Clock.Duration = if (maker.unit_test_timeout_ns) |ns| .{ .clock = .awake, .raw = .fromNanoseconds(ns), } else null; @@ -428,7 +431,7 @@ fn waitZigTest( var body_r: std.Io.Reader = .fixed(body); switch (header.tag) { .zig_version => { - if (!std.mem.eql(u8, builtin.zig_version_string, body)) return run.step.fail( + if (!std.mem.eql(u8, builtin.zig_version_string, body)) return step.fail( maker, "zig version mismatch build runner vs compiler: '{s}' vs '{s}'", .{ builtin.zig_version_string, body }, @@ -451,14 +454,14 @@ fn waitZigTest( const string_bytes = body_r.take(tm_hdr.string_bytes_len) catch unreachable; - options.progress_node.setEstimatedTotalItems(names.len); + progress_node.setEstimatedTotalItems(names.len); opt_metadata.* = .{ .string_bytes = try arena.dupe(u8, string_bytes), .ns_per_test = try arena.alloc(u64, results.test_count), .names = names, .expected_panic_msgs = expected_panic_msgs, .next_index = 0, - .prog_node = options.progress_node, + .prog_node = progress_node, }; @memset(opt_metadata.*.?.ns_per_test, std.math.maxInt(u64)); @@ -494,20 +497,20 @@ fn waitZigTest( const stderr_bytes = std.mem.trim(u8, stderr.buffered(), "\n"); stderr.tossBuffered(); if (stderr_bytes.len == 0) { - try run.step.addError("'{s}' failed without output", .{name}); + try step.addError(maker, "'{s}' failed without output", .{name}); } else { - try run.step.addError("'{s}' failed:\n{s}", .{ name, stderr_bytes }); + try step.addError(maker, "'{s}' failed:\n{s}", .{ name, stderr_bytes }); } } else if (leak_count > 0) { const name = md.testName(tr_hdr.index); const stderr_bytes = std.mem.trim(u8, stderr.buffered(), "\n"); stderr.tossBuffered(); - try run.step.addError("'{s}' leaked {d} allocations:\n{s}", .{ name, leak_count, stderr_bytes }); + try step.addError(maker, "'{s}' leaked {d} allocations:\n{s}", .{ name, leak_count, stderr_bytes }); } else if (log_err_count > 0) { const name = md.testName(tr_hdr.index); const stderr_bytes = std.mem.trim(u8, stderr.buffered(), "\n"); stderr.tossBuffered(); - try run.step.addError("'{s}' logged {d} errors:\n{s}", .{ name, log_err_count, stderr_bytes }); + try step.addError(maker, "'{s}' logged {d} errors:\n{s}", .{ name, log_err_count, stderr_bytes }); } active_test_index = null; @@ -525,6 +528,7 @@ fn waitZigTest( const FuzzTestRunner = struct { run: *Run, + run_index: Configuration.Step.Index, ctx: FuzzContext, coverage_id: ?u64, @@ -572,16 +576,18 @@ const FuzzTestRunner = struct { fn init( run: *Run, + run_index: Configuration.Step.Index, ctx: FuzzContext, progress_node: std.Progress.Node, spawn_options: process.SpawnOptions, ) !FuzzTestRunner { - const step_owner = run.step.owner; - const gpa = step_owner.allocator; - const io = step_owner.graph.io; + const maker = ctx.fuzz.maker; + const graph = maker.graph; + const gpa = maker.gpa; + const io = graph.io; const n_instances = switch (ctx.fuzz.mode) { - .forever => step_owner.graph.max_jobs orelse @min( + .forever => graph.max_jobs orelse @min( std.Thread.getCpuCount() catch 1, (std.math.maxInt(u32) - 2) / 3, ), @@ -613,6 +619,7 @@ const FuzzTestRunner = struct { return .{ .run = run, + .run_index = run_index, .ctx = ctx, .coverage_id = null, @@ -625,9 +632,13 @@ const FuzzTestRunner = struct { } fn deinit(f: *FuzzTestRunner) void { - const step_owner = f.run.step.owner; - const gpa = step_owner.allocator; - const io = step_owner.graph.io; + const maker = f.ctx.fuzz.maker; + const run_index = f.run_index; + + const graph = maker.graph; + const gpa = maker.gpa; + const io = graph.io; + const step = maker.stepByIndex(run_index); f.batch.cancel(io); gpa.free(f.batch.storage); @@ -639,13 +650,18 @@ const FuzzTestRunner = struct { instance.progress_node.end(); total_rss += instance.child.resource_usage_statistics.getMaxRss() orelse 0; } - f.run.step.result_peak_rss = @max(f.run.step.result_peak_rss, total_rss); + step.result_peak_rss = @max(step.result_peak_rss, total_rss); gpa.free(f.instances); } fn startInstances(f: *FuzzTestRunner) !void { - const step_owner = f.run.step.owner; - const io = step_owner.graph.io; + const maker = f.ctx.fuzz.maker; + const run_index = f.run_index; + const run = f.run; + + const graph = maker.graph; + const io = graph.io; + const step = maker.stepByIndex(run_index); for (0.., f.instances) |id, *instance| { const id32: u32 = @intCast(id); @@ -653,14 +669,14 @@ const FuzzTestRunner = struct { .forever => sendRunFuzzTestMessage( io, instance.child.stdin.?, - f.run.fuzz_tests.items, + run.fuzz_tests.items, .forever, id32, ), .limit => |limit| sendRunFuzzTestMessage( io, instance.child.stdin.?, - f.run.fuzz_tests.items, + run.fuzz_tests.items, .iterations, limit.amount, ), @@ -670,7 +686,8 @@ const FuzzTestRunner = struct { instance.child.stdin.?.close(io); instance.child.stdin = null; const term = try instance.child.wait(io); - return f.run.step.fail( + return step.fail( + maker, "unable to write stdin ({t}); test process unexpectedly {f}", .{ write_err, fmtTerm(term) }, ); @@ -682,8 +699,9 @@ const FuzzTestRunner = struct { } fn listen(f: *FuzzTestRunner) !void { - const step_owner = f.run.step.owner; - const io = step_owner.graph.io; + const maker = f.ctx.fuzz.maker; + const graph = maker.graph; + const io = graph.io; while (true) { try f.batch.awaitConcurrent(io, .none); @@ -714,10 +732,15 @@ const FuzzTestRunner = struct { } fn completeStdoutRead(f: *FuzzTestRunner, id: u32, n: usize) !void { - const step_owner = f.run.step.owner; - const gpa = step_owner.allocator; - const io = step_owner.graph.io; + const maker = f.ctx.fuzz.maker; const instance = &f.instances[id]; + const run_index = f.run_index; + const run = f.run; + + const graph = maker.graph; + const gpa = maker.gpa; + const io = graph.io; + const step = maker.stepByIndex(run_index); instance.message.items.len += n; const total_read = instance.message.items.len; @@ -735,7 +758,8 @@ const FuzzTestRunner = struct { switch (header.tag) { .zig_version => { - if (!std.mem.eql(u8, builtin.zig_version_string, body)) return f.run.step.fail( + if (!std.mem.eql(u8, builtin.zig_version_string, body)) return step.fail( + maker, "zig version mismatch build runner vs compiler: '{s}' vs '{s}'", .{ builtin.zig_version_string, body }, ); @@ -750,14 +774,14 @@ const FuzzTestRunner = struct { const fuzz = f.ctx.fuzz; fuzz.queue_mutex.lockUncancelable(io); defer fuzz.queue_mutex.unlock(io); - try fuzz.msg_queue.append(fuzz.gpa, .{ .coverage = .{ + try fuzz.msg_queue.append(gpa, .{ .coverage = .{ .id = f.coverage_id.?, .cumulative = .{ .runs = cumulative_runs, .unique = cumulative_unique, .coverage = cumulative_coverage, }, - .run = f.run, + .run = run_index, } }); fuzz.queue_cond.signal(io); }, @@ -768,7 +792,7 @@ const FuzzTestRunner = struct { fuzz.queue_mutex.lockUncancelable(io); defer fuzz.queue_mutex.unlock(io); - try fuzz.msg_queue.append(fuzz.gpa, .{ .entry_point = .{ + try fuzz.msg_queue.append(gpa, .{ .entry_point = .{ .addr = addr, .coverage_id = f.coverage_id.?, } }); @@ -776,7 +800,7 @@ const FuzzTestRunner = struct { }, .fuzz_test_change => { const test_i = std.mem.readInt(u32, body[0..4], .little); - instance.progress_node.setName(f.run.fuzz_tests.items[test_i]); + instance.progress_node.setName(run.fuzz_tests.items[test_i]); }, .broadcast_fuzz_input => { if (f.instances.len == 1) { @@ -823,8 +847,8 @@ const FuzzTestRunner = struct { } fn addStdoutRead(f: *FuzzTestRunner, id: u32, end: usize) !void { - const step_owner = f.run.step.owner; - const gpa = step_owner.allocator; + const maker = f.ctx.fuzz.maker; + const gpa = maker.gpa; const instance = &f.instances[id]; try instance.message.ensureTotalCapacity(gpa, end); @@ -837,8 +861,8 @@ const FuzzTestRunner = struct { } fn addStderrRead(f: *FuzzTestRunner, id: u32) !void { - const step_owner = f.run.step.owner; - const gpa = step_owner.allocator; + const maker = f.ctx.fuzz.maker; + const gpa = maker.gpa; const instance = &f.instances[id]; try instance.stderr.ensureUnusedCapacity(gpa, 1); @@ -861,24 +885,31 @@ const FuzzTestRunner = struct { } fn instanceEos(f: *FuzzTestRunner, id: u32) !void { - const step_owner = f.run.step.owner; - const io = step_owner.graph.io; + const maker = f.ctx.fuzz.maker; const instance = &f.instances[id]; + const run_index = f.run_index; + + const graph = maker.graph; + const io = graph.io; + const step = maker.stepByIndex(run_index); instance.child.stdin.?.close(io); instance.child.stdin = null; const term = try instance.child.wait(io); if (!termMatches(.{ .exited = 0 }, term)) { - f.run.step.result_stderr = try f.mergedStderr(); + step.result_stderr = try f.mergedStderr(); try f.saveCrash(id, term); - return f.run.step.fail("test process unexpectedly {f}", .{fmtTerm(term)}); + return step.fail(maker, "test process unexpectedly {f}", .{fmtTerm(term)}); } } fn saveCrash(f: *FuzzTestRunner, id: u32, term: process.Child.Term) !void { - const fuzz = f.context.fuzz; + const fuzz = f.ctx.fuzz; + const run_index = f.run_index; + const run = f.run; + const maker = fuzz.maker; - const step = &f.run.step; + const step = maker.stepByIndex(run_index); const graph = maker.graph; const io = graph.io; const cache_root = graph.local_cache_root; @@ -906,7 +937,7 @@ const FuzzTestRunner = struct { error.FileNotFound => return, error.WouldBlock => continue, // Can not be from // the crashed instance since it is still locked. - else => return step.fail("failed to open file '{f}{s}': {t}", .{ + else => return step.fail(maker, "failed to open file '{f}{s}': {t}", .{ cache_root, in_name, e, }), }; @@ -915,7 +946,7 @@ const FuzzTestRunner = struct { const header = in_r.interface.takeStruct(InputHeader, .little) catch |e| { in_f.close(io); switch (e) { - error.ReadFailed => return step.fail("failed to read file '{f}{s}': {t}", .{ + error.ReadFailed => return step.fail(maker, "failed to read file '{f}{s}': {t}", .{ cache_root, in_name, in_r.err.?, }), error.EndOfStream => continue, @@ -924,7 +955,7 @@ const FuzzTestRunner = struct { if (header.pc_digest == f.coverage_id.? and header.instance_id == id and - header.test_i < f.run.fuzz_tests.items.len) + header.test_i < run.fuzz_tests.items.len) { break header; } @@ -937,7 +968,7 @@ const FuzzTestRunner = struct { const crash_name = "f" ++ Io.Dir.path.sep_str ++ "crash"; const out = cache_root.handle.createFile(io, crash_name, .{ .lock = .exclusive, // Multiple run steps could have found a crash at the same time - }) catch |e| return step.fail("failed to create file '{f}{s}': {t}", .{ + }) catch |e| return step.fail(maker, "failed to create file '{f}{s}': {t}", .{ cache_root, crash_name, e, }); defer out.close(io); @@ -945,16 +976,16 @@ const FuzzTestRunner = struct { var out_w_buf: [512]u8 = undefined; var out_w = out.writerStreaming(io, &out_w_buf); _ = out_w.interface.sendFileAll(&in_r, .limited(header.len)) catch |e| switch (e) { - error.ReadFailed => return step.fail("failed to read file '{f}{s}': {t}", .{ + error.ReadFailed => return step.fail(maker, "failed to read file '{f}{s}': {t}", .{ cache_root, in_name, in_r.err.?, }), - error.WriteFailed => return step.fail("failed to write file '{f}{s}': {t}", .{ + error.WriteFailed => return step.fail(maker, "failed to write file '{f}{s}': {t}", .{ cache_root, crash_name, out_w.err.?, }), }; - return f.run.step.fail("test '{s}' {f}; input saved to '{f}{s}'", .{ - f.run.fuzz_tests.items[header.test_i], + return step.fail(maker, "test '{s}' {f}; input saved to '{f}{s}'", .{ + run.fuzz_tests.items[header.test_i], fmtTerm(term), cache_root, crash_name, @@ -967,8 +998,8 @@ const FuzzTestRunner = struct { assert(f.broadcast.items.len == 0); assert(from_id < f.instances.len); - const step_owner = f.run.step.owner; - const gpa = step_owner.allocator; + const maker = f.ctx.fuzz.maker; + const gpa = maker.gpa; var out_header: OutHeader = .{ .tag = .new_fuzz_input, @@ -1010,8 +1041,9 @@ const FuzzTestRunner = struct { } fn mergedStderr(f: *FuzzTestRunner) std.mem.Allocator.Error![]const u8 { - const step_owner = f.run.step.owner; - const arena = step_owner.allocator; + const maker = f.ctx.fuzz.maker; + const graph = maker.graph; + const arena = graph.arena; // TODO don't leak into the process arena // Collect any available stderr while (f.batch.next()) |completion| { @@ -1035,11 +1067,12 @@ const FuzzTestRunner = struct { fn evalFuzzTest( run: *Run, + run_index: Configuration.Step.Index, + progress_node: std.Progress.Node, spawn_options: process.SpawnOptions, - options: Step.MakeOptions, fuzz_context: FuzzContext, ) !void { - var f: FuzzTestRunner = try .init(run, fuzz_context, options.progress_node, spawn_options); + var f: FuzzTestRunner = try .init(run, run_index, fuzz_context, progress_node, spawn_options); defer f.deinit(); try f.startInstances(); try f.listen(); @@ -1049,23 +1082,25 @@ const StdioPollEnum = enum { stdout, stderr }; fn evalZigTest( run: *Run, + run_index: Configuration.Step.Index, maker: *Maker, + progress_node: std.Progress.Node, spawn_options: process.SpawnOptions, - options: Step.MakeOptions, fuzz_context: ?FuzzContext, ) !void { if (fuzz_context != null) { - try evalFuzzTest(run, spawn_options, options, fuzz_context.?); + try evalFuzzTest(run, run_index, progress_node, spawn_options, fuzz_context.?); return; } - const step_owner = run.step.owner; - const gpa = step_owner.allocator; - const arena = step_owner.allocator; - const io = step_owner.graph.io; + const graph = maker.graph; + const gpa = maker.gpa; + const io = graph.io; + const arena = graph.arena; // TODO don't leak into the process arena + const step = maker.stepByIndex(run_index); // We will update this every time a child runs. - run.step.result_peak_rss = 0; + step.result_peak_rss = 0; var test_results: Step.TestResults = .{ .test_count = 0, @@ -1087,16 +1122,18 @@ fn evalZigTest( defer if (!child_killed) { child.kill(io); multi_reader.deinit(); - run.step.result_peak_rss = @max( - run.step.result_peak_rss, + step.result_peak_rss = @max( + step.result_peak_rss, child.resource_usage_statistics.getMaxRss() orelse 0, ); }; switch (try waitZigTest( run, + run_index, + maker, &child, - options, + progress_node, &multi_reader, &test_metadata, &test_results, @@ -1109,7 +1146,7 @@ fn evalZigTest( error.ReadFailed => return stderr_fr.err.?, error.EndOfStream => {}, } - run.step.result_stderr = try arena.dupe(u8, stderr_fr.interface.buffered()); + step.result_stderr = try arena.dupe(u8, stderr_fr.interface.buffered()); // Clean up everything and wait for the child to exit. child.stdin.?.close(io); @@ -1117,14 +1154,16 @@ fn evalZigTest( multi_reader.deinit(); child_killed = true; const term = try child.wait(io); - run.step.result_peak_rss = @max( - run.step.result_peak_rss, + step.result_peak_rss = @max( + step.result_peak_rss, child.resource_usage_statistics.getMaxRss() orelse 0, ); // The individual unit test results are irrelevant: the test runner itself broke! // Fail immediately without populating `s.test_results`. - return run.step.fail(maker, "unable to write stdin ({t}); test process unexpectedly {f}", .{ err, fmtTerm(term) }); + return step.fail(maker, "unable to write stdin ({t}); test process unexpectedly {f}", .{ + err, fmtTerm(term), + }); }, .no_poll => |no_poll| { // This might be a success (we requested exit and the child dutifully closed stdout) or @@ -1138,8 +1177,8 @@ fn evalZigTest( multi_reader.deinit(); child_killed = true; const term = try child.wait(io); - run.step.result_peak_rss = @max( - run.step.result_peak_rss, + step.result_peak_rss = @max( + step.result_peak_rss, child.resource_usage_statistics.getMaxRss() orelse 0, ); @@ -1148,7 +1187,7 @@ fn evalZigTest( // test, and continue to the next test. test_metadata.?.ns_per_test[test_index] = no_poll.ns_elapsed; test_results.crash_count += 1; - try run.step.addError("'{s}' {f}{s}{s}", .{ + try step.addError(maker, "'{s}' {f}{s}{s}", .{ test_metadata.?.testName(test_index), fmtTerm(term), if (stderr_owned.len != 0) " with stderr:\n" else "", @@ -1158,22 +1197,22 @@ fn evalZigTest( } // Report an error if the child terminated uncleanly or if we were still trying to run more tests. - run.step.result_stderr = stderr_owned; + step.result_stderr = stderr_owned; 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! // Fail immediately without populating `s.test_results`. - return run.step.fail(maker, "test process unexpectedly {f}", .{fmtTerm(term)}); + return step.fail(maker, "test process unexpectedly {f}", .{fmtTerm(term)}); } // We're done with all of the tests! Commit the test results and return. - run.step.test_results = test_results; + step.test_results = test_results; if (test_metadata) |tm| { run.cached_test_metadata = tm.toCachedTestMetadata(); - if (options.web_server) |ws| { - if (run.step.owner.graph.time_report) { + if (maker.web_server) |*ws| { + if (graph.time_report) { ws.updateTimeReportRunTest( - run, + run_index, &run.cached_test_metadata.?, tm.ns_per_test, ); @@ -1191,7 +1230,7 @@ fn evalZigTest( // the next test. test_metadata.?.ns_per_test[test_index] = timeout.ns_elapsed; test_results.timeout_count += 1; - try run.step.addError("'{s}' timed out after {f}{s}{s}", .{ + 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 "", @@ -1200,10 +1239,10 @@ fn evalZigTest( continue; } // Just log an error and let the child be killed. - run.step.result_stderr = try arena.dupe(u8, stderr); + step.result_stderr = try arena.dupe(u8, stderr); // The individual unit test results in `results` are irrelevant: the test runner // is broken! Fail immediately without populating `s.test_results`. - return run.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; @@ -1323,27 +1362,35 @@ fn sendRunFuzzTestMessage( } } -fn evalGeneric(run: *Run, maker: *Maker, spawn_options: process.SpawnOptions) !EvalGenericResult { +fn evalGeneric( + run_index: Configuration.Step.Index, + maker: *Maker, + spawn_options: process.SpawnOptions, +) !EvalGenericResult { const graph = maker.graph; const io = graph.io; - const arena = graph.allocator; // TODO don't leak into the process arena + const arena = graph.arena; // TODO don't leak into the process arena const gpa = maker.gpa; + const conf = &maker.scanned_config.configuration; + const conf_step = run_index.ptr(conf); + const conf_run = conf_step.extended.get(conf.extra).run; + const step = maker.stepByIndex(run_index); var child = try process.spawn(io, spawn_options); defer child.kill(io); - switch (run.stdin) { + switch (conf_run.stdin.u) { .bytes => |bytes| { - child.stdin.?.writeStreamingAll(io, bytes) catch |err| { - return run.step.fail(maker, "unable to write stdin: {t}", .{err}); + child.stdin.?.writeStreamingAll(io, bytes.slice(conf)) catch |err| { + return step.fail(maker, "failed to write stdin: {t}", .{err}); }; child.stdin.?.close(io); child.stdin = null; }, .lazy_path => |lazy_path| { - const path = lazy_path.getPath3(graph, &run.step); + const path = try maker.resolveLazyPathIndex(arena, lazy_path, run_index); const file = path.root_dir.handle.openFile(io, path.subPathOrDot(), .{}) catch |err| { - return run.step.fail(maker, "unable to open stdin file: {t}", .{err}); + return step.fail(maker, "failed to open stdin file: {t}", .{err}); }; defer file.close(io); // TODO https://github.com/ziglang/zig/issues/23955 @@ -1352,15 +1399,15 @@ fn evalGeneric(run: *Run, maker: *Maker, spawn_options: process.SpawnOptions) !E var write_buffer: [1024]u8 = undefined; var stdin_writer = child.stdin.?.writerStreaming(io, &write_buffer); _ = stdin_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) { - error.ReadFailed => return run.step.fail(maker, "failed to read from {f}: {t}", .{ + error.ReadFailed => return step.fail(maker, "failed to read from {f}: {t}", .{ path, file_reader.err.?, }), - error.WriteFailed => return run.step.fail(maker, "failed to write to stdin: {t}", .{ + error.WriteFailed => return step.fail(maker, "failed to write to stdin: {t}", .{ stdin_writer.err.?, }), }; stdin_writer.interface.flush() catch |err| switch (err) { - error.WriteFailed => return run.step.fail(maker, "failed to write to stdin: {t}", .{ + error.WriteFailed => return step.fail(maker, "failed to write to stdin: {t}", .{ stdin_writer.err.?, }), }; @@ -1384,7 +1431,7 @@ fn evalGeneric(run: *Run, maker: *Maker, spawn_options: process.SpawnOptions) !E const stderr_reader = multi_reader.reader(1); while (multi_reader.fill(64, .none)) |_| { - if (run.stdio_limit.toInt()) |limit| { + if (conf_run.stdio_limit.value) |limit| { if (stdout_reader.buffered().len > limit) return error.StdoutStreamTooLong; if (stderr_reader.buffered().len > limit) @@ -1404,7 +1451,8 @@ fn evalGeneric(run: *Run, maker: *Maker, spawn_options: process.SpawnOptions) !E stderr_bytes = try multi_reader.toOwnedSlice(1); } else { var stdout_reader = stdout.readerStreaming(io, &.{}); - stdout_bytes = stdout_reader.interface.allocRemaining(arena, run.stdio_limit) catch |err| switch (err) { + const stdio_limit: Io.Limit = if (conf_run.stdio_limit.value) |x| .limited(x) else .unlimited; + stdout_bytes = stdout_reader.interface.allocRemaining(arena, stdio_limit) catch |err| switch (err) { error.OutOfMemory => |e| return e, error.ReadFailed => return stdout_reader.err.?, error.StreamTooLong => return error.StdoutStreamTooLong, @@ -1412,7 +1460,8 @@ fn evalGeneric(run: *Run, maker: *Maker, spawn_options: process.SpawnOptions) !E } } else if (child.stderr) |stderr| { var stderr_reader = stderr.readerStreaming(io, &.{}); - stderr_bytes = stderr_reader.interface.allocRemaining(arena, run.stdio_limit) catch |err| switch (err) { + const stdio_limit: Io.Limit = if (conf_run.stdio_limit.value) |x| .limited(x) else .unlimited; + stderr_bytes = stderr_reader.interface.allocRemaining(arena, stdio_limit) catch |err| switch (err) { error.OutOfMemory => |e| return e, error.ReadFailed => return stderr_reader.err.?, error.StreamTooLong => return error.StderrStreamTooLong, @@ -1421,16 +1470,16 @@ fn evalGeneric(run: *Run, maker: *Maker, spawn_options: process.SpawnOptions) !E if (stderr_bytes) |bytes| if (bytes.len > 0) { // Treat stderr as an error message. - const stderr_is_diagnostic = run.captured_stderr == null and switch (run.stdio) { - .check => |checks| !checksContainStderr(checks.items), + const stderr_is_diagnostic = conf_run.captured_stderr.value == null and switch (conf_run.flags.stdio) { + .check => !checksContainStderr(&conf_run), else => true, }; if (stderr_is_diagnostic) { - run.step.result_stderr = bytes; + step.result_stderr = bytes; } }; - run.step.result_peak_rss = child.resource_usage_statistics.getMaxRss() orelse 0; + step.result_peak_rss = child.resource_usage_statistics.getMaxRss() orelse 0; return .{ .term = try child.wait(io), @@ -1452,7 +1501,7 @@ pub fn rerunInFuzzMode( ) !void { const maker = fuzz.maker; const graph = maker.graph; - const step = &run.step; + const step = maker.stepByIndex(run_index); const io = graph.io; const arena = graph.arena; // TODO don't leak into the process arena const gpa = maker.gpa; @@ -1535,9 +1584,9 @@ pub fn rerunInFuzzMode( } } - if (run.step.result_failed_command) |cmd| { - fuzz.gpa.free(cmd); - run.step.result_failed_command = null; + if (step.result_failed_command) |cmd| { + gpa.free(cmd); + step.result_failed_command = null; } const has_side_effects = false; @@ -1549,8 +1598,6 @@ pub fn rerunInFuzzMode( }); } -const CapturedStdIo = void; // TODO get it from Configuration - fn populateGeneratedPaths( maker: *Maker, output_placeholders: []const IndexedOutput, @@ -1712,142 +1759,153 @@ fn runCommand( if (true) @panic("TODO"); - const opt_generic_result = spawnChildAndCollect(run_index, run, maker, progress_node, argv, &environ_map, has_side_effects, fuzz_context) catch |err| term: { - // InvalidExe: cpu arch mismatch - // FileNotFound: can happen with a wrong dynamic linker path - if (err == error.InvalidExe or err == error.FileNotFound) interpret: { - // TODO: learn the target from the binary directly rather than from - // relying on it being a Compile step. This will make this logic - // work even for the edge case that the binary was produced by a - // third party. - const exe = switch (run.argv.items[0]) { - .artifact => |exe| exe.artifact, - else => break :interpret, - }; - switch (exe.kind) { - .exe, .@"test" => {}, - else => break :interpret, - } + const opt_generic_result = spawnChildAndCollect( + run_index, + run, + maker, + progress_node, + argv, + environ_map, + has_side_effects, + fuzz_context, + ) catch |err| term: { + switch (err) { + error.InvalidExe, // cpu arch mismatch + error.FileNotFound, // can happen with a wrong dynamic linker path + => interpret: { + // TODO: learn the target from the binary directly rather than from + // relying on it being a Compile step. This will make this logic + // work even for the edge case that the binary was produced by a + // third party. + const exe = switch (run.argv.items[0]) { + .artifact => |exe| exe.artifact, + else => break :interpret, + }; + switch (exe.kind) { + .exe, .@"test" => {}, + else => break :interpret, + } - const root_target = exe.rootModuleTarget(); - const need_cross_libc = exe.is_linking_libc and - (root_target.isGnuLibC() or (root_target.isMuslLibC() and exe.linkage == .dynamic)); - const other_target = exe.root_module.resolved_target.?.result; - switch (std.zig.system.getExternalExecutor(io, &graph.host.result, &other_target, .{ - .qemu_fixes_dl = need_cross_libc and graph.libc_runtimes_dir != null, - .link_libc = exe.is_linking_libc, - })) { - .native, .rosetta => { - if (allow_skip) return error.MakeSkipped; - break :interpret; - }, - .wine => |bin_name| { - if (graph.enable_wine) { - try interp_argv.append(bin_name); - try interp_argv.appendSlice(argv); - - // Wine's excessive stderr logging is only situationally helpful. Disable it by default, but - // allow the user to override it (e.g. with `WINEDEBUG=err+all`) if desired. - if (environ_map.get("WINEDEBUG") == null) { - try environ_map.put("WINEDEBUG", "-all"); + const root_target = exe.rootModuleTarget(); + const need_cross_libc = exe.is_linking_libc and + (root_target.isGnuLibC() or (root_target.isMuslLibC() and exe.linkage == .dynamic)); + const other_target = exe.root_module.resolved_target.?.result; + switch (std.zig.system.getExternalExecutor(io, &graph.host.result, &other_target, .{ + .qemu_fixes_dl = need_cross_libc and graph.libc_runtimes_dir != null, + .link_libc = exe.is_linking_libc, + })) { + .native, .rosetta => { + if (allow_skip) return error.MakeSkipped; + break :interpret; + }, + .wine => |bin_name| { + if (graph.enable_wine) { + try interp_argv.append(bin_name); + try interp_argv.appendSlice(argv); + + // Wine's excessive stderr logging is only situationally helpful. Disable it by default, but + // allow the user to override it (e.g. with `WINEDEBUG=err+all`) if desired. + if (environ_map.get("WINEDEBUG") == null) { + try environ_map.put("WINEDEBUG", "-all"); + } + } else { + return failForeign(conf_run, maker, run_index, "-fwine", argv[0], exe); } - } else { - return failForeign(run, "-fwine", argv[0], exe); - } - }, - .qemu => |bin_name| { - if (graph.enable_qemu) { - try interp_argv.append(bin_name); - - if (need_cross_libc) { - if (graph.libc_runtimes_dir) |dir| { - try interp_argv.append("-L"); - try interp_argv.append(try Dir.path.join(arena, &.{ - dir, - try if (root_target.isGnuLibC()) std.zig.target.glibcRuntimeTriple( - arena, - root_target.cpu.arch, - root_target.os.tag, - root_target.abi, - ) else if (root_target.isMuslLibC()) std.zig.target.muslRuntimeTriple( - arena, - root_target.cpu.arch, - root_target.abi, - ) else unreachable, - })); - } else return failForeign(run, "--libc-runtimes", argv[0], exe); + }, + .qemu => |bin_name| { + if (graph.enable_qemu) { + try interp_argv.append(bin_name); + + if (need_cross_libc) { + if (graph.libc_runtimes_dir) |dir| { + try interp_argv.append("-L"); + try interp_argv.append(try Dir.path.join(arena, &.{ + dir, + try if (root_target.isGnuLibC()) std.zig.target.glibcRuntimeTriple( + arena, + root_target.cpu.arch, + root_target.os.tag, + root_target.abi, + ) else if (root_target.isMuslLibC()) std.zig.target.muslRuntimeTriple( + arena, + root_target.cpu.arch, + root_target.abi, + ) else unreachable, + })); + } else return failForeign(conf_run, maker, run_index, "--libc-runtimes", argv[0], exe); + } + + try interp_argv.appendSlice(argv); + } else return failForeign(conf_run, maker, run_index, "-fqemu", argv[0], exe); + }, + .darling => |bin_name| { + if (graph.enable_darling) { + try interp_argv.append(bin_name); + try interp_argv.appendSlice(argv); + } else { + return failForeign(conf_run, maker, run_index, "-fdarling", argv[0], exe); + } + }, + .wasmtime => |bin_name| { + if (graph.enable_wasmtime) { + try interp_argv.append(bin_name); + try interp_argv.append("--dir=."); + // Wasmtime doeesn't inherit environment variables from the parent process + // by default. '-S inherit-env' was added in Wasmtime version 20. + try interp_argv.append("-Sinherit-env"); + try interp_argv.append(argv[0]); + try interp_argv.appendSlice(argv[1..]); + } else { + return failForeign(conf_run, maker, run_index, "-fwasmtime", argv[0], exe); } + }, + .bad_dl => |foreign_dl| { + if (allow_skip) return error.MakeSkipped; - try interp_argv.appendSlice(argv); - } else return failForeign(run, "-fqemu", argv[0], exe); - }, - .darling => |bin_name| { - if (graph.enable_darling) { - try interp_argv.append(bin_name); - try interp_argv.appendSlice(argv); - } else { - return failForeign(run, "-fdarling", argv[0], exe); - } - }, - .wasmtime => |bin_name| { - if (graph.enable_wasmtime) { - try interp_argv.append(bin_name); - try interp_argv.append("--dir=."); - // Wasmtime doeesn't inherit environment variables from the parent process - // by default. '-S inherit-env' was added in Wasmtime version 20. - try interp_argv.append("-Sinherit-env"); - try interp_argv.append(argv[0]); - try interp_argv.appendSlice(argv[1..]); - } else { - return failForeign(run, "-fwasmtime", argv[0], exe); - } - }, - .bad_dl => |foreign_dl| { - if (allow_skip) return error.MakeSkipped; + const host_dl = graph.host.result.dynamic_linker.get() orelse "(none)"; - const host_dl = graph.host.result.dynamic_linker.get() orelse "(none)"; + return step.fail(maker, + \\the host system is unable to execute binaries from the target + \\ because the host dynamic linker is '{s}', + \\ while the target dynamic linker is '{s}'. + \\ consider setting the dynamic linker or enabling skip_foreign_checks in the Run step + , .{ host_dl, foreign_dl }); + }, + .bad_os_or_cpu => { + if (allow_skip) return error.MakeSkipped; - return step.fail(maker, - \\the host system is unable to execute binaries from the target - \\ because the host dynamic linker is '{s}', - \\ while the target dynamic linker is '{s}'. - \\ consider setting the dynamic linker or enabling skip_foreign_checks in the Run step - , .{ host_dl, foreign_dl }); - }, - .bad_os_or_cpu => { - if (allow_skip) return error.MakeSkipped; - - const host_name = try graph.host.result.zigTriple(arena); - const foreign_name = try root_target.zigTriple(arena); - - return step.fail(maker, "the host system ({s}) is unable to execute binaries from the target ({s})", .{ - host_name, foreign_name, - }); - }, - } + const host_name = try graph.host.result.zigTriple(arena); + const foreign_name = try root_target.zigTriple(arena); - if (root_target.os.tag == .windows) { - // On Windows we don't have rpaths so we have to add .dll search paths to PATH - addPathForDynLibs(exe); - } + return step.fail(maker, "the host system ({s}) is unable to execute binaries from the target ({s})", .{ + host_name, foreign_name, + }); + }, + } - gpa.free(step.result_failed_command.?); - step.result_failed_command = null; - try Step.handleVerbose(step.owner, cwd, run.environ_map, interp_argv.items); + if (root_target.os.tag == .windows) { + // On Windows we don't have rpaths so we have to add .dll search paths to PATH + addPathForDynLibs(exe); + } - break :term spawnChildAndCollect(run_index, run, maker, progress_node, interp_argv.items, &environ_map, has_side_effects, fuzz_context) catch |e| { - if (!run.failing_to_execute_foreign_is_an_error) return error.MakeSkipped; - if (e == error.MakeFailed) return error.MakeFailed; // error already reported - return step.fail(maker, "unable to spawn interpreter {s}: {t}", .{ interp_argv.items[0], e }); - }; - } - if (err == error.MakeFailed) return error.MakeFailed; // error already reported + gpa.free(step.result_failed_command.?); + step.result_failed_command = null; + try graph.handleVerbose(cwd, run.environ_map, interp_argv.items); + break :term spawnChildAndCollect(run_index, run, maker, progress_node, interp_argv.items, &environ_map, has_side_effects, fuzz_context) catch |e| { + if (!run.failing_to_execute_foreign_is_an_error) return error.MakeSkipped; + if (e == error.MakeFailed) return error.MakeFailed; // error already reported + return step.fail(maker, "unable to spawn interpreter {s}: {t}", .{ interp_argv.items[0], e }); + }; + }, + error.MakeFailed, error.OutOfMemory, error.Canceled => |e| return e, + else => {}, + } return step.fail(maker, "failed to spawn and capture stdio from {s}: {t}", .{ argv[0], err }); }; const generic_result = opt_generic_result orelse { - assert(run.stdio == .zig_test); + assert(conf_run.flags.stdio == .zig_test); // Specific errors have already been reported, and test results are populated. All we need // to do is report step failure if any test failed. if (!step.test_results.isSuccess()) return error.MakeFailed; @@ -1855,20 +1913,20 @@ fn runCommand( }; assert(fuzz_context == null); - assert(run.stdio != .zig_test); + assert(conf_run.flags.stdio != .zig_test); // Capture stdout and stderr to GeneratedFile objects. const Stream = struct { - captured: ?*CapturedStdIo, + captured: ?Configuration.Step.Run.CapturedStream, bytes: ?[]const u8, }; for ([_]Stream{ .{ - .captured = run.captured_stdout, + .captured = conf_run.captured_stdout.value, .bytes = generic_result.stdout, }, .{ - .captured = run.captured_stderr, + .captured = conf_run.captured_stderr.value, .bytes = generic_result.stderr, }, }) |stream| { @@ -1898,7 +1956,7 @@ fn runCommand( } } - switch (run.stdio) { + switch (conf_run.flags.stdio) { .zig_test => unreachable, .check => |checks| for (checks.items) |check| switch (check) { .expect_stderr_exact => |expected_bytes| { @@ -1970,7 +2028,7 @@ fn runCommand( }; if (bad_exit) { if (generic_result.stderr) |bytes| { - run.step.result_stderr = bytes; + step.result_stderr = bytes; } } @@ -1995,9 +2053,8 @@ fn spawnChildAndCollect( has_side_effects: bool, fuzz_context: ?FuzzContext, ) !?EvalGenericResult { - const step = run.step; + const step = maker.stepByIndex(run_index); const graph = maker.graph; - const gpa = maker.gpa; const io = graph.io; const arena = graph.arena; // TODO don't leak into process arena const conf = &maker.scanned_config.configuration; @@ -2006,17 +2063,17 @@ fn spawnChildAndCollect( if (fuzz_context != null) { assert(!has_side_effects); - assert(run.stdio == .zig_test); + assert(conf_run.flags.stdio == .zig_test); } - const child_cwd: process.Child.Cwd = if (conf_run.cwd) |lazy_cwd| + const child_cwd: process.Child.Cwd = if (conf_run.cwd.value) |lazy_cwd| .{ .path = try maker.resolveLazyPathIndexAbs(arena, lazy_cwd, run_index) } else .inherit; // If an error occurs, it's caused by this command: assert(step.result_failed_command == null); - step.result_failed_command = try Step.allocPrintCmd(gpa, child_cwd, .{ + step.result_failed_command = try std.zig.allocPrintCmd(arena, child_cwd, .{ .child = environ_map, .parent = &graph.environ_map, }, argv); @@ -2028,22 +2085,22 @@ fn spawnChildAndCollect( .cwd = child_cwd, .environ_map = environ_map, .request_resource_usage_statistics = true, - .stdin = if (run.stdin != .none) s: { - assert(run.stdio != .inherit); + .stdin = if (conf_run.stdin.u != .none) s: { + assert(conf_run.flags.stdio != .inherit); break :s .pipe; - } else switch (run.stdio) { + } else switch (conf_run.flags.stdio) { .infer_from_args => if (has_side_effects) .inherit else .ignore, .inherit => .inherit, .check => .ignore, .zig_test => .pipe, }, - .stdout = if (run.captured_stdout != null) .pipe else switch (run.stdio) { + .stdout = if (conf_run.captured_stdout.value != null) .pipe else switch (conf_run.flags.stdio) { .infer_from_args => if (has_side_effects) .inherit else .ignore, .inherit => .inherit, - .check => |checks| if (checksContainStdout(checks.items)) .pipe else .ignore, + .check => if (checksContainStdout(&conf_run)) .pipe else .ignore, .zig_test => .pipe, }, - .stderr = if (run.captured_stderr != null) .pipe else switch (run.stdio) { + .stderr = if (conf_run.captured_stderr.value != null) .pipe else switch (conf_run.flags.stdio) { .infer_from_args => if (has_side_effects) .inherit else .pipe, .inherit => .inherit, .check => .pipe, @@ -2051,9 +2108,9 @@ fn spawnChildAndCollect( }, }; - if (run.stdio == .zig_test) { + if (conf_run.flags.stdio == .zig_test) { const started: Io.Clock.Timestamp = .now(io, .awake); - const result = evalZigTest(run, 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, }; @@ -2062,7 +2119,7 @@ fn spawnChildAndCollect( return null; } else { const inherit = spawn_options.stdout == .inherit or spawn_options.stderr == .inherit; - if (!run.disable_zig_progress and !inherit) { + if (!conf_run.flags.disable_zig_progress and !inherit) { spawn_options.progress_node = progress_node; } const terminal_mode: Io.Terminal.Mode = if (inherit) m: { @@ -2070,10 +2127,10 @@ fn spawnChildAndCollect( break :m stderr.terminal_mode; } else .no_color; defer if (inherit) io.unlockStderr(); - try setColorEnvironmentVariables(run, environ_map, terminal_mode); + try setColorEnvironmentVariables(&conf_run, environ_map, terminal_mode); const started: Io.Clock.Timestamp = .now(io, .awake); - const result = evalGeneric(run, maker, spawn_options) catch |err| switch (err) { + const result = evalGeneric(run_index, maker, spawn_options) catch |err| switch (err) { error.Canceled => |e| return e, else => |e| e, }; @@ -2106,8 +2163,12 @@ fn termMatches(expected: ?process.Child.Term, actual: process.Child.Term) bool { }; } -fn setColorEnvironmentVariables(run: *Run, environ_map: *EnvMap, terminal_mode: Io.Terminal.Mode) !void { - color: switch (run.color) { +fn setColorEnvironmentVariables( + conf_run: *const Configuration.Step.Run, + environ_map: *EnvMap, + terminal_mode: Io.Terminal.Mode, +) !void { + color: switch (conf_run.flags.color) { .manual => {}, .enable => { try environ_map.put("CLICOLOR_FORCE", "1"); @@ -2122,8 +2183,8 @@ fn setColorEnvironmentVariables(run: *Run, environ_map: *EnvMap, terminal_mode: .escape_codes => continue :color .enable, }, .auto => { - const capture_stderr = run.captured_stderr != null or switch (run.stdio) { - .check => |checks| checksContainStderr(checks.items), + const capture_stderr = conf_run.captured_stderr.value != null or switch (conf_run.flags.stdio) { + .check => checksContainStderr(conf_run), .infer_from_args, .inherit, .zig_test => false, }; if (capture_stderr) { @@ -2135,53 +2196,12 @@ fn setColorEnvironmentVariables(run: *Run, environ_map: *EnvMap, terminal_mode: } } -fn checksContainStdout(checks: []const @This().StdIo.Check) bool { - for (checks) |check| switch (check) { - .expect_stderr_exact, - .expect_stderr_match, - .expect_term, - => continue, - - .expect_stdout_exact, - .expect_stdout_match, - => return true, - }; - return false; -} - -fn checksContainStderr(checks: []const @This().StdIo.Check) bool { - for (checks) |check| switch (check) { - .expect_stdout_exact, - .expect_stdout_match, - .expect_term, - => continue, - - .expect_stderr_exact, - .expect_stderr_match, - => return true, - }; - return false; -} - -/// Returns whether the Run step has side effects *other than* updating the output arguments. -fn hasSideEffects(run: Run) bool { - if (run.has_side_effects) return true; - return switch (run.stdio) { - .infer_from_args => !run.hasAnyOutputArgs(), - .inherit => true, - .check => false, - .zig_test => false, - }; +fn checksContainStdout(conf_run: *const Configuration.Step.Run) bool { + return conf_run.expect_stdout_exact.value != null or conf_run.expect_stdout_match.slice.len != 0; } -fn hasAnyOutputArgs(run: Run) bool { - if (run.captured_stdout != null) return true; - if (run.captured_stderr != null) return true; - for (run.argv.items) |arg| switch (arg) { - .output_file, .output_directory => return true, - else => continue, - }; - return false; +fn checksContainStderr(conf_run: *const Configuration.Step.Run) bool { + return conf_run.expect_stderr_exact.value != null or conf_run.expect_stderr_match.slice.len != 0; } /// If `path` is cwd-relative, make it relative to the cwd of the child instead. @@ -2225,13 +2245,13 @@ fn addPathForDynLibs(artifact: Configuration.Step.Index) void { compile.isDynamicLibrary()) { @panic("TODO"); - //addPathDir(run, Dir.path.dirname(compile.getEmittedBin().getPath2(b, &run.step)).?); + //addPathDir(run, Dir.path.dirname(compile.getEmittedBin().getPath2(b, step)).?); } } } fn failForeign( - run: *Run, + conf_run: *const Configuration.Step.Run, maker: *Maker, step_index: Configuration.Step.Index, suggested_flag: []const u8, @@ -2239,9 +2259,9 @@ fn failForeign( exe: *Step.Compile, ) Step.ExtendedMakeError { const step = maker.stepByIndex(step_index); - switch (run.stdio) { + switch (conf_run.flags.stdio) { .check, .zig_test => { - if (run.skip_foreign_checks) return error.MakeSkipped; + if (conf_run.flags.skip_foreign_checks) return error.MakeSkipped; const graph = maker.graph; const process_arena = graph.arena; // TODO don't leak into process arena