feature. See also
. The project being documented here (as the example) is the Zig library itself.
Run.runCommand
fn runCommand(
arena: Allocator,
run: *Run,
run_index: Configuration.Step.Index,
maker: *Maker,
progress_node: std.Progress.Node,
argv: []const []const u8,
has_side_effects: bool,
output_dir_path: []const u8,
fuzz_context: ?FuzzContext,
) Step.ExtendedMakeError!void
File
Code
fn runCommand(
arena: Allocator,
run: *Run,
run_index: Configuration.Step.Index,
maker: *Maker,
progress_node: std.Progress.Node,
argv: []const []const u8,
has_side_effects: bool,
output_dir_path: []const u8,
fuzz_context: ?FuzzContext,
) Step.ExtendedMakeError!void {
const graph = maker.graph;
const gpa = maker.gpa;
const step = maker.stepByIndex(run_index);
const io = graph.io;
const cache_root = graph.local_cache_root;
const conf = &maker.scanned_config.configuration;
const conf_step = run_index.ptr(conf);
const conf_run = conf_step.extended.get(conf.extra).run;
const cwd: process.Child.Cwd = if (conf_run.cwd.value) |lazy_cwd|
.{ .path = try maker.resolveLazyPathIndexAbs(arena, lazy_cwd, run_index) }
else
.inherit;
const allow_skip = switch (conf_run.flags.stdio) {
.check, .zig_test => conf_run.flags.skip_foreign_checks,
else => false,
};
var interp_argv: std.ArrayList([]const u8) = .empty;
var environ_map: std.process.Environ.Map = .init(gpa);
defer environ_map.deinit();
// tweak the environment below.
if (conf_run.environ_map.value) |env_map_index| {
const conf_env_map = env_map_index.get(conf);
for (conf_env_map.keys.slice(conf), conf_env_map.values.slice(conf)) |k, v| {
try environ_map.put(k.slice(conf), v.slice(conf));
}
} else {
try environ_map.putAll(&graph.environ_map);
}
// .dll search paths because Windows doesn't have rpaths.
const arg0 = conf_run.args.slice[0].get(conf);
if (arg0.producer.value) |producer_index| {
const producer_step = producer_index.ptr(conf);
const producer = producer_step.extended.get(conf.extra).compile;
const root_module = producer.root_module.get(conf);
const root_module_target = root_module.resolved_target.get(conf).?.result.get(conf);
if (root_module_target.flags.os_tag == .windows) {
try addPathForDynLibs(maker, arena, producer_index, &environ_map, argv[0]);
}
}
const cwd_string = switch (cwd) {
.path => |p| p,
.dir => unreachable,
.inherit => null,
};
try graph.handleVerbose(cwd_string, &environ_map, argv);
const opt_generic_result = spawnChildAndCollect(
arena,
run_index,
run,
maker,
progress_node,
argv,
&environ_map,
has_side_effects,
fuzz_context,
) catch |err| term: {
switch (err) {
error.InvalidExe,
error.FileNotFound,
=> interpret: {
const producer_index = arg0.producer.value orelse break :interpret;
const producer_step = producer_index.ptr(conf);
const producer = producer_step.extended.get(conf.extra).compile;
switch (producer.flags3.kind) {
.exe, .@"test" => {},
else => break :interpret,
}
const root_module = producer.root_module.get(conf);
const root_module_target = root_module.resolved_target.get(conf).?.result.get(conf);
const root_target = root_module_target.unwrapTarget(conf);
const link_libc = maker.stepByIndex(producer_index).extended.compile.is_linking_libc;
const host: std.Target = std.zig.system.resolveTargetQuery(io, .{}) catch |he| switch (he) {
error.Canceled => |e| return e,
else => builtin.target,
};
const need_cross_libc = link_libc and root_target.os.tag == .linux and
switch (producer.flags2.linkage) {
.static => false,
.dynamic => true,
.default => root_target.isGnuLibC(),
};
switch (std.zig.system.getExternalExecutor(io, &root_target, .{
.host_cpu_arch = host.cpu.arch,
.host_os_tag = host.os.tag,
.qemu_fixes_dl = need_cross_libc and graph.libc_runtimes_dir != null,
.link_libc = link_libc,
})) {
.native, .rosetta => {
if (allow_skip) return error.MakeSkipped;
break :interpret;
},
.wine => |bin_name| {
if (graph.enable_wine) {
try interp_argv.ensureUnusedCapacity(arena, 1 + argv.len);
interp_argv.appendAssumeCapacity(bin_name);
interp_argv.appendSliceAssumeCapacity(argv);
// 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(arena, &conf_run, maker, run_index, "-fwine", argv[0], &root_target, &host);
}
},
.qemu => |bin_name| {
if (graph.enable_qemu) {
try interp_argv.ensureUnusedCapacity(arena, 3 + argv.len);
interp_argv.appendAssumeCapacity(bin_name);
if (need_cross_libc) {
if (graph.libc_runtimes_dir) |dir| {
interp_argv.appendAssumeCapacity("-L");
interp_argv.appendAssumeCapacity(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(arena, &conf_run, maker, run_index, "--libc-runtimes", argv[0], &root_target, &host);
}
interp_argv.appendSliceAssumeCapacity(argv);
} else return failForeign(arena, &conf_run, maker, run_index, "-fqemu", argv[0], &root_target, &host);
},
.darling => |bin_name| {
if (graph.enable_darling) {
try interp_argv.ensureUnusedCapacity(arena, 1 + argv.len);
interp_argv.appendAssumeCapacity(bin_name);
interp_argv.appendSliceAssumeCapacity(argv);
} else {
return failForeign(arena, &conf_run, maker, run_index, "-fdarling", argv[0], &root_target, &host);
}
},
.wasmtime => |bin_name| {
if (graph.enable_wasmtime) {
try interp_argv.ensureUnusedCapacity(arena, 3 + argv.len + conf_run.preopen_names.slice.len);
interp_argv.appendAssumeCapacity(bin_name);
interp_argv.appendAssumeCapacity("--dir=.");
for (conf_run.preopen_names.slice, conf_run.preopen_paths.slice) |name, lazy_path| {
const path = try maker.resolveLazyPath(arena, lazy_path.get(conf), run_index);
path.root_dir.handle.createDirPath(io, path.subPathOrDot()) catch |e|
return step.fail(maker, "failed creating directory {f}: {t}", .{ path, e });
interp_argv.appendAssumeCapacity(try arena.print("--dir={f}::{s}", .{ path, name.slice(conf) }));
}
// by default. '-S inherit-env' was added in Wasmtime version 20.
interp_argv.appendAssumeCapacity("-Sinherit-env");
interp_argv.appendSliceAssumeCapacity(argv);
// with `WASMTIME_BACKTRACE_DETAILS=0`) if desired.
if (environ_map.get("WASMTIME_BACKTRACE_DETAILS") == null) {
try environ_map.put("WASMTIME_BACKTRACE_DETAILS", "1");
}
} else {
return failForeign(arena, &conf_run, maker, run_index, "-fwasmtime", argv[0], &root_target, &host);
}
},
.bad_dl => |foreign_dl| {
if (allow_skip) return error.MakeSkipped;
const host_dl = host.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;
const host_name = try host.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,
});
},
}
step.clearFailedCommand(gpa);
try graph.handleVerbose(cwd_string, &environ_map, interp_argv.items);
break :term spawnChildAndCollect(
arena,
run_index,
run,
maker,
progress_node,
interp_argv.items,
&environ_map,
has_side_effects,
fuzz_context,
) catch |e| {
if (!conf_run.flags.failing_to_execute_foreign_is_an_error) return error.MakeSkipped;
if (e == error.MakeFailed) return error.MakeFailed;
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(conf_run.flags.stdio == .zig_test);
// to do is report step failure if any test failed.
if (!step.test_results.isSuccess()) return error.MakeFailed;
return;
};
assert(fuzz_context == null);
assert(conf_run.flags.stdio != .zig_test);
const Stream = struct {
captured: ?Configuration.Step.Run.CapturedStream,
bytes: ?[]const u8,
trim_whitespace: Configuration.Step.Run.TrimWhitespace,
};
for (&[_]Stream{
.{
.captured = conf_run.captured_stdout.value,
.bytes = generic_result.stdout,
.trim_whitespace = conf_run.flags.stdout_trim_whitespace,
},
.{
.captured = conf_run.captured_stderr.value,
.bytes = generic_result.stderr,
.trim_whitespace = conf_run.flags.stderr_trim_whitespace,
},
}) |*stream| {
if (stream.captured) |captured| {
const output_path: Path = .{
.root_dir = cache_root,
.sub_path = try Dir.path.join(graph.arena, &.{
output_dir_path, captured.basename.slice(conf),
}),
};
maker.generatedPath(captured.generated_file).* = output_path;
const sub_path_parent = output_path.dirname().?;
sub_path_parent.root_dir.handle.createDirPath(io, sub_path_parent.sub_path) catch |err|
return step.fail(maker, "unable to make path {f}: {t}", .{ sub_path_parent, err });
const data = switch (stream.trim_whitespace) {
.none => stream.bytes.?,
.all => mem.trim(u8, stream.bytes.?, &std.ascii.whitespace),
.leading => mem.trimStart(u8, stream.bytes.?, &std.ascii.whitespace),
.trailing => mem.trimEnd(u8, stream.bytes.?, &std.ascii.whitespace),
};
output_path.root_dir.handle.writeFile(io, .{
.sub_path = output_path.sub_path,
.data = data,
}) catch |err| return step.fail(maker, "unable to write file {f}: {t}", .{ output_path, err });
}
}
switch (conf_run.flags.stdio) {
.zig_test => unreachable,
.check => {
if (conf_run.expect_stderr_exact.value) |bytes| {
const expected_bytes = bytes.slice(conf);
if (!mem.eql(u8, expected_bytes, generic_result.stderr.?)) {
return step.fail(maker,
\\========= expected this stderr: =========
\\{s}
\\========= but found: ====================
\\{s}
, .{
expected_bytes,
generic_result.stderr.?,
});
}
}
if (conf_run.expect_stdout_exact.value) |bytes| {
const expected_bytes = bytes.slice(conf);
if (!mem.eql(u8, expected_bytes, generic_result.stdout.?)) {
return step.fail(maker,
\\========= expected this stdout: =========
\\{s}
\\========= but found: ====================
\\{s}
, .{
expected_bytes,
generic_result.stdout.?,
});
}
}
for (conf_run.expect_stderr_match.slice) |bytes| {
const match = bytes.slice(conf);
if (mem.find(u8, generic_result.stderr.?, match) == null) {
return step.fail(maker,
\\========= expected to find in stderr: =========
\\{s}
\\========= but stderr does not contain it: =====
\\{s}
, .{
match,
generic_result.stderr.?,
});
}
}
for (conf_run.expect_stdout_match.slice) |bytes| {
const match = bytes.slice(conf);
if (mem.find(u8, generic_result.stdout.?, match) == null) {
return step.fail(maker,
\\========= expected to find in stdout: =========
\\{s}
\\========= but stdout does not contain it: =====
\\{s}
, .{
match,
generic_result.stdout.?,
});
}
}
if (conf_run.expect_term_value.value) |expected_term_value| {
const expected_term: process.Child.Term = switch (conf_run.flags2.expect_term_status) {
.exited => .{ .exited = @intCast(expected_term_value) },
.signal => .{ .signal = @fromBackingInt(@intCast(expected_term_value)) },
.stopped => .{ .stopped = @fromBackingInt(@intCast(expected_term_value)) },
.unknown => .{ .unknown = expected_term_value },
};
if (!termMatches(expected_term, generic_result.term)) {
return step.fail(maker, "process {f} (expected {f})", .{
fmtTerm(generic_result.term),
fmtTerm(expected_term),
});
}
}
const snapshots: []const ?struct {
path: Cache.Path,
result: enum { stderr, stdout },
} = &.{
if (conf_run.expect_stderr_snapshot.value) |path| .{
.path = try maker.resolveLazyPathIndex(arena, path, run_index),
.result = .stderr,
} else null,
if (conf_run.expect_stdout_snapshot.value) |path| .{
.path = try maker.resolveLazyPathIndex(arena, path, run_index),
.result = .stdout,
} else null,
};
for (snapshots) |opt_snapshot| {
const snapshot = opt_snapshot orelse continue;
const file = snapshot.path.root_dir.handle.openFile(io, snapshot.path.sub_path, .{}) catch |err|
return step.fail(maker, "unable to open snapshot file {f}: {t}", .{ snapshot.path, err });
defer file.close(io);
var file_reader = file.reader(io, &.{});
const snapshot_contents = file_reader.interface.allocRemaining(gpa, .unlimited) catch |err|
return step.fail(maker, "unable to read snapshot file {f}: {t}", .{ snapshot.path, err });
defer gpa.free(snapshot_contents);
const result = switch (snapshot.result) {
.stderr => generic_result.stderr.?,
.stdout => generic_result.stdout.?,
};
if (std.mem.findDiff(u8, snapshot_contents, result)) |diff_index| {
var diff_line_number: usize = 1;
for (snapshot_contents[0..diff_index]) |value| {
if (value == '\n') diff_line_number += 1;
}
return step.fail(maker,
\\
\\========= snapshot file: =========
\\{f}
\\========= contained: =============
\\{s}
\\========= {t} output was: ========
\\{s}
\\==================================
\\first difference on line {d}:
\\expected:
\\{f}
\\found:
\\{f}
, .{
snapshot.path,
snapshot_contents,
snapshot.result,
result,
diff_line_number,
fmtSnapshotIndicatorLine(snapshot_contents, diff_index),
fmtSnapshotIndicatorLine(result, diff_index),
});
}
}
},
else => {
if (!generic_result.term.success()) {
if (generic_result.stderr) |bytes| {
try step.setResultStderr(gpa, bytes);
}
}
try step.handleChildProcessTerm(maker, generic_result.term);
},
}
}