mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 14:23:09 +00:00
Previously this code asserted that a fifo's readable length was greater than or equal to the length of its readable slice, which was an invalid assertion. This code avoids making that assumption.
1236 lines
44 KiB
Zig
1236 lines
44 KiB
Zig
const std = @import("../std.zig");
|
|
const builtin = @import("builtin");
|
|
const Step = std.Build.Step;
|
|
const CompileStep = std.Build.CompileStep;
|
|
const WriteFileStep = std.Build.WriteFileStep;
|
|
const fs = std.fs;
|
|
const mem = std.mem;
|
|
const process = std.process;
|
|
const ArrayList = std.ArrayList;
|
|
const EnvMap = process.EnvMap;
|
|
const Allocator = mem.Allocator;
|
|
const ExecError = std.Build.ExecError;
|
|
const assert = std.debug.assert;
|
|
|
|
const RunStep = @This();
|
|
|
|
pub const base_id: Step.Id = .run;
|
|
|
|
step: Step,
|
|
|
|
/// See also addArg and addArgs to modifying this directly
|
|
argv: ArrayList(Arg),
|
|
|
|
/// Set this to modify the current working directory
|
|
/// TODO change this to a Build.Cache.Directory to better integrate with
|
|
/// future child process cwd API.
|
|
cwd: ?[]const u8,
|
|
|
|
/// Override this field to modify the environment, or use setEnvironmentVariable
|
|
env_map: ?*EnvMap,
|
|
|
|
/// Configures whether the RunStep is considered to have side-effects, and also
|
|
/// whether the RunStep will inherit stdio streams, forwarding them to the
|
|
/// parent process, in which case will require a global lock to prevent other
|
|
/// steps from interfering with stdio while the subprocess associated with this
|
|
/// RunStep is running.
|
|
/// If the RunStep is determined to not have side-effects, then execution will
|
|
/// be skipped if all output files are up-to-date and input files are
|
|
/// unchanged.
|
|
stdio: StdIo = .infer_from_args,
|
|
/// This field must be `null` if stdio is `inherit`.
|
|
stdin: ?[]const u8 = null,
|
|
|
|
/// Additional file paths relative to build.zig that, when modified, indicate
|
|
/// that the RunStep should be re-executed.
|
|
/// If the RunStep is determined to have side-effects, this field is ignored
|
|
/// and the RunStep is always executed when it appears in the build graph.
|
|
extra_file_dependencies: []const []const u8 = &.{},
|
|
|
|
/// After adding an output argument, this step will by default rename itself
|
|
/// for a better display name in the build summary.
|
|
/// This can be disabled by setting this to false.
|
|
rename_step_with_output_arg: bool = true,
|
|
|
|
/// If this is true, a RunStep which is configured to check the output of the
|
|
/// executed binary will not fail the build if the binary cannot be executed
|
|
/// due to being for a foreign binary to the host system which is running the
|
|
/// build graph.
|
|
/// Command-line arguments such as -fqemu and -fwasmtime may affect whether a
|
|
/// binary is detected as foreign, as well as system configuration such as
|
|
/// Rosetta (macOS) and binfmt_misc (Linux).
|
|
/// If this RunStep is considered to have side-effects, then this flag does
|
|
/// nothing.
|
|
skip_foreign_checks: bool = false,
|
|
|
|
/// If stderr or stdout exceeds this amount, the child process is killed and
|
|
/// the step fails.
|
|
max_stdio_size: usize = 10 * 1024 * 1024,
|
|
|
|
captured_stdout: ?*Output = null,
|
|
captured_stderr: ?*Output = null,
|
|
|
|
has_side_effects: bool = false,
|
|
|
|
pub const StdIo = union(enum) {
|
|
/// Whether the RunStep has side-effects will be determined by whether or not one
|
|
/// of the args is an output file (added with `addOutputFileArg`).
|
|
/// If the RunStep is determined to have side-effects, this is the same as `inherit`.
|
|
/// The step will fail if the subprocess crashes or returns a non-zero exit code.
|
|
infer_from_args,
|
|
/// Causes the RunStep to be considered to have side-effects, and therefore
|
|
/// always execute when it appears in the build graph.
|
|
/// It also means that this step will obtain a global lock to prevent other
|
|
/// steps from running in the meantime.
|
|
/// The step will fail if the subprocess crashes or returns a non-zero exit code.
|
|
inherit,
|
|
/// Causes the RunStep to be considered to *not* have side-effects. The
|
|
/// process will be re-executed if any of the input dependencies are
|
|
/// modified. The exit code and standard I/O streams will be checked for
|
|
/// certain conditions, and the step will succeed or fail based on these
|
|
/// conditions.
|
|
/// Note that an explicit check for exit code 0 needs to be added to this
|
|
/// list if such a check is desireable.
|
|
check: std.ArrayList(Check),
|
|
/// This RunStep is running a zig unit test binary and will communicate
|
|
/// extra metadata over the IPC protocol.
|
|
zig_test,
|
|
|
|
pub const Check = union(enum) {
|
|
expect_stderr_exact: []const u8,
|
|
expect_stderr_match: []const u8,
|
|
expect_stdout_exact: []const u8,
|
|
expect_stdout_match: []const u8,
|
|
expect_term: std.process.Child.Term,
|
|
};
|
|
};
|
|
|
|
pub const Arg = union(enum) {
|
|
artifact: *CompileStep,
|
|
file_source: std.Build.FileSource,
|
|
directory_source: std.Build.FileSource,
|
|
bytes: []u8,
|
|
output: *Output,
|
|
};
|
|
|
|
pub const Output = struct {
|
|
generated_file: std.Build.GeneratedFile,
|
|
prefix: []const u8,
|
|
basename: []const u8,
|
|
};
|
|
|
|
pub fn create(owner: *std.Build, name: []const u8) *RunStep {
|
|
const self = owner.allocator.create(RunStep) catch @panic("OOM");
|
|
self.* = .{
|
|
.step = Step.init(.{
|
|
.id = base_id,
|
|
.name = name,
|
|
.owner = owner,
|
|
.makeFn = make,
|
|
}),
|
|
.argv = ArrayList(Arg).init(owner.allocator),
|
|
.cwd = null,
|
|
.env_map = null,
|
|
};
|
|
return self;
|
|
}
|
|
|
|
pub fn setName(self: *RunStep, name: []const u8) void {
|
|
self.step.name = name;
|
|
self.rename_step_with_output_arg = false;
|
|
}
|
|
|
|
pub fn addArtifactArg(self: *RunStep, artifact: *CompileStep) void {
|
|
self.argv.append(Arg{ .artifact = artifact }) catch @panic("OOM");
|
|
self.step.dependOn(&artifact.step);
|
|
}
|
|
|
|
/// This provides file path as a command line argument to the command being
|
|
/// run, and returns a FileSource which can be used as inputs to other APIs
|
|
/// throughout the build system.
|
|
pub fn addOutputFileArg(rs: *RunStep, basename: []const u8) std.Build.FileSource {
|
|
return addPrefixedOutputFileArg(rs, "", basename);
|
|
}
|
|
|
|
pub fn addPrefixedOutputFileArg(
|
|
rs: *RunStep,
|
|
prefix: []const u8,
|
|
basename: []const u8,
|
|
) std.Build.FileSource {
|
|
const b = rs.step.owner;
|
|
|
|
const output = b.allocator.create(Output) catch @panic("OOM");
|
|
output.* = .{
|
|
.prefix = prefix,
|
|
.basename = basename,
|
|
.generated_file = .{ .step = &rs.step },
|
|
};
|
|
rs.argv.append(.{ .output = output }) catch @panic("OOM");
|
|
|
|
if (rs.rename_step_with_output_arg) {
|
|
rs.setName(b.fmt("{s} ({s})", .{ rs.step.name, basename }));
|
|
}
|
|
|
|
return .{ .generated = &output.generated_file };
|
|
}
|
|
|
|
pub fn addFileSourceArg(self: *RunStep, file_source: std.Build.FileSource) void {
|
|
self.argv.append(.{
|
|
.file_source = file_source.dupe(self.step.owner),
|
|
}) catch @panic("OOM");
|
|
file_source.addStepDependencies(&self.step);
|
|
}
|
|
|
|
pub fn addDirectorySourceArg(self: *RunStep, directory_source: std.Build.FileSource) void {
|
|
self.argv.append(.{
|
|
.directory_source = directory_source.dupe(self.step.owner),
|
|
}) catch @panic("OOM");
|
|
directory_source.addStepDependencies(&self.step);
|
|
}
|
|
|
|
pub fn addArg(self: *RunStep, arg: []const u8) void {
|
|
self.argv.append(.{ .bytes = self.step.owner.dupe(arg) }) catch @panic("OOM");
|
|
}
|
|
|
|
pub fn addArgs(self: *RunStep, args: []const []const u8) void {
|
|
for (args) |arg| {
|
|
self.addArg(arg);
|
|
}
|
|
}
|
|
|
|
pub fn clearEnvironment(self: *RunStep) void {
|
|
const b = self.step.owner;
|
|
const new_env_map = b.allocator.create(EnvMap) catch @panic("OOM");
|
|
new_env_map.* = EnvMap.init(b.allocator);
|
|
self.env_map = new_env_map;
|
|
}
|
|
|
|
pub fn addPathDir(self: *RunStep, search_path: []const u8) void {
|
|
const b = self.step.owner;
|
|
const env_map = getEnvMapInternal(self);
|
|
|
|
const key = "PATH";
|
|
var prev_path = env_map.get(key);
|
|
|
|
if (prev_path) |pp| {
|
|
const new_path = b.fmt("{s}" ++ [1]u8{fs.path.delimiter} ++ "{s}", .{ pp, search_path });
|
|
env_map.put(key, new_path) catch @panic("OOM");
|
|
} else {
|
|
env_map.put(key, b.dupePath(search_path)) catch @panic("OOM");
|
|
}
|
|
}
|
|
|
|
pub fn getEnvMap(self: *RunStep) *EnvMap {
|
|
return getEnvMapInternal(self);
|
|
}
|
|
|
|
fn getEnvMapInternal(self: *RunStep) *EnvMap {
|
|
const arena = self.step.owner.allocator;
|
|
return self.env_map orelse {
|
|
const env_map = arena.create(EnvMap) catch @panic("OOM");
|
|
env_map.* = process.getEnvMap(arena) catch @panic("unhandled error");
|
|
self.env_map = env_map;
|
|
return env_map;
|
|
};
|
|
}
|
|
|
|
pub fn setEnvironmentVariable(self: *RunStep, key: []const u8, value: []const u8) void {
|
|
const b = self.step.owner;
|
|
const env_map = self.getEnvMap();
|
|
env_map.put(b.dupe(key), b.dupe(value)) catch @panic("unhandled error");
|
|
}
|
|
|
|
pub fn removeEnvironmentVariable(self: *RunStep, key: []const u8) void {
|
|
self.getEnvMap().remove(key);
|
|
}
|
|
|
|
/// Adds a check for exact stderr match. Does not add any other checks.
|
|
pub fn expectStdErrEqual(self: *RunStep, bytes: []const u8) void {
|
|
const new_check: StdIo.Check = .{ .expect_stderr_exact = self.step.owner.dupe(bytes) };
|
|
self.addCheck(new_check);
|
|
}
|
|
|
|
/// Adds a check for exact stdout match as well as a check for exit code 0, if
|
|
/// there is not already an expected termination check.
|
|
pub fn expectStdOutEqual(self: *RunStep, bytes: []const u8) void {
|
|
const new_check: StdIo.Check = .{ .expect_stdout_exact = self.step.owner.dupe(bytes) };
|
|
self.addCheck(new_check);
|
|
if (!self.hasTermCheck()) {
|
|
self.expectExitCode(0);
|
|
}
|
|
}
|
|
|
|
pub fn expectExitCode(self: *RunStep, code: u8) void {
|
|
const new_check: StdIo.Check = .{ .expect_term = .{ .Exited = code } };
|
|
self.addCheck(new_check);
|
|
}
|
|
|
|
pub fn hasTermCheck(self: RunStep) bool {
|
|
for (self.stdio.check.items) |check| switch (check) {
|
|
.expect_term => return true,
|
|
else => continue,
|
|
};
|
|
return false;
|
|
}
|
|
|
|
pub fn addCheck(self: *RunStep, new_check: StdIo.Check) void {
|
|
switch (self.stdio) {
|
|
.infer_from_args => {
|
|
self.stdio = .{ .check = std.ArrayList(StdIo.Check).init(self.step.owner.allocator) };
|
|
self.stdio.check.append(new_check) catch @panic("OOM");
|
|
},
|
|
.check => |*checks| checks.append(new_check) catch @panic("OOM"),
|
|
else => @panic("illegal call to addCheck: conflicting helper method calls. Suggest to directly set stdio field of RunStep instead"),
|
|
}
|
|
}
|
|
|
|
pub fn captureStdErr(self: *RunStep) std.Build.FileSource {
|
|
assert(self.stdio != .inherit);
|
|
|
|
if (self.captured_stderr) |output| return .{ .generated = &output.generated_file };
|
|
|
|
const output = self.step.owner.allocator.create(Output) catch @panic("OOM");
|
|
output.* = .{
|
|
.prefix = "",
|
|
.basename = "stderr",
|
|
.generated_file = .{ .step = &self.step },
|
|
};
|
|
self.captured_stderr = output;
|
|
return .{ .generated = &output.generated_file };
|
|
}
|
|
|
|
pub fn captureStdOut(self: *RunStep) *std.Build.GeneratedFile {
|
|
assert(self.stdio != .inherit);
|
|
|
|
if (self.captured_stdout) |output| return .{ .generated = &output.generated_file };
|
|
|
|
const output = self.step.owner.allocator.create(Output) catch @panic("OOM");
|
|
output.* = .{
|
|
.prefix = "",
|
|
.basename = "stdout",
|
|
.generated_file = .{ .step = &self.step },
|
|
};
|
|
self.captured_stdout = output;
|
|
return .{ .generated = &output.generated_file };
|
|
}
|
|
|
|
/// Returns whether the RunStep has side effects *other than* updating the output arguments.
|
|
fn hasSideEffects(self: RunStep) bool {
|
|
if (self.has_side_effects) return true;
|
|
return switch (self.stdio) {
|
|
.infer_from_args => !self.hasAnyOutputArgs(),
|
|
.inherit => true,
|
|
.check => false,
|
|
.zig_test => false,
|
|
};
|
|
}
|
|
|
|
fn hasAnyOutputArgs(self: RunStep) bool {
|
|
if (self.captured_stdout != null) return true;
|
|
if (self.captured_stderr != null) return true;
|
|
for (self.argv.items) |arg| switch (arg) {
|
|
.output => return true,
|
|
else => continue,
|
|
};
|
|
return false;
|
|
}
|
|
|
|
fn checksContainStdout(checks: []const 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 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;
|
|
}
|
|
|
|
fn make(step: *Step, prog_node: *std.Progress.Node) !void {
|
|
const b = step.owner;
|
|
const arena = b.allocator;
|
|
const self = @fieldParentPtr(RunStep, "step", step);
|
|
const has_side_effects = self.hasSideEffects();
|
|
|
|
var argv_list = ArrayList([]const u8).init(arena);
|
|
var output_placeholders = ArrayList(struct {
|
|
index: usize,
|
|
output: *Output,
|
|
}).init(arena);
|
|
|
|
var man = b.cache.obtain();
|
|
defer man.deinit();
|
|
|
|
for (self.argv.items) |arg| {
|
|
switch (arg) {
|
|
.bytes => |bytes| {
|
|
try argv_list.append(bytes);
|
|
man.hash.addBytes(bytes);
|
|
},
|
|
.file_source => |file| {
|
|
const file_path = file.getPath(b);
|
|
try argv_list.append(file_path);
|
|
_ = try man.addFile(file_path, null);
|
|
},
|
|
.directory_source => |file| {
|
|
const file_path = file.getPath(b);
|
|
try argv_list.append(file_path);
|
|
man.hash.addBytes(file_path);
|
|
},
|
|
.artifact => |artifact| {
|
|
if (artifact.target.isWindows()) {
|
|
// On Windows we don't have rpaths so we have to add .dll search paths to PATH
|
|
self.addPathForDynLibs(artifact);
|
|
}
|
|
const file_path = artifact.installed_path orelse
|
|
artifact.getOutputSource().getPath(b);
|
|
|
|
try argv_list.append(file_path);
|
|
|
|
_ = try man.addFile(file_path, null);
|
|
},
|
|
.output => |output| {
|
|
man.hash.addBytes(output.prefix);
|
|
man.hash.addBytes(output.basename);
|
|
// Add a placeholder into the argument list because we need the
|
|
// manifest hash to be updated with all arguments before the
|
|
// object directory is computed.
|
|
try argv_list.append("");
|
|
try output_placeholders.append(.{
|
|
.index = argv_list.items.len - 1,
|
|
.output = output,
|
|
});
|
|
},
|
|
}
|
|
}
|
|
|
|
if (self.captured_stdout) |output| {
|
|
man.hash.addBytes(output.basename);
|
|
}
|
|
|
|
if (self.captured_stderr) |output| {
|
|
man.hash.addBytes(output.basename);
|
|
}
|
|
|
|
hashStdIo(&man.hash, self.stdio);
|
|
|
|
if (has_side_effects) {
|
|
try runCommand(self, argv_list.items, has_side_effects, null, prog_node);
|
|
return;
|
|
}
|
|
|
|
for (self.extra_file_dependencies) |file_path| {
|
|
_ = try man.addFile(b.pathFromRoot(file_path), null);
|
|
}
|
|
|
|
if (try step.cacheHit(&man)) {
|
|
// cache hit, skip running command
|
|
const digest = man.final();
|
|
for (output_placeholders.items) |placeholder| {
|
|
placeholder.output.generated_file.path = try b.cache_root.join(arena, &.{
|
|
"o", &digest, placeholder.output.basename,
|
|
});
|
|
}
|
|
|
|
if (self.captured_stdout) |output| {
|
|
output.generated_file.path = try b.cache_root.join(arena, &.{
|
|
"o", &digest, output.basename,
|
|
});
|
|
}
|
|
|
|
if (self.captured_stderr) |output| {
|
|
output.generated_file.path = try b.cache_root.join(arena, &.{
|
|
"o", &digest, output.basename,
|
|
});
|
|
}
|
|
|
|
step.result_cached = true;
|
|
return;
|
|
}
|
|
|
|
const digest = man.final();
|
|
|
|
for (output_placeholders.items) |placeholder| {
|
|
const output_components = .{ "o", &digest, placeholder.output.basename };
|
|
const output_sub_path = try fs.path.join(arena, &output_components);
|
|
const output_sub_dir_path = fs.path.dirname(output_sub_path).?;
|
|
b.cache_root.handle.makePath(output_sub_dir_path) catch |err| {
|
|
return step.fail("unable to make path '{}{s}': {s}", .{
|
|
b.cache_root, output_sub_dir_path, @errorName(err),
|
|
});
|
|
};
|
|
const output_path = try b.cache_root.join(arena, &output_components);
|
|
placeholder.output.generated_file.path = output_path;
|
|
const cli_arg = if (placeholder.output.prefix.len == 0)
|
|
output_path
|
|
else
|
|
b.fmt("{s}{s}", .{ placeholder.output.prefix, output_path });
|
|
argv_list.items[placeholder.index] = cli_arg;
|
|
}
|
|
|
|
try runCommand(self, argv_list.items, has_side_effects, &digest, prog_node);
|
|
|
|
try step.writeManifest(&man);
|
|
}
|
|
|
|
fn formatTerm(
|
|
term: ?std.process.Child.Term,
|
|
comptime fmt: []const u8,
|
|
options: std.fmt.FormatOptions,
|
|
writer: anytype,
|
|
) !void {
|
|
_ = fmt;
|
|
_ = options;
|
|
if (term) |t| switch (t) {
|
|
.Exited => |code| try writer.print("exited with code {}", .{code}),
|
|
.Signal => |sig| try writer.print("terminated with signal {}", .{sig}),
|
|
.Stopped => |sig| try writer.print("stopped with signal {}", .{sig}),
|
|
.Unknown => |code| try writer.print("terminated for unknown reason with code {}", .{code}),
|
|
} else {
|
|
try writer.writeAll("exited with any code");
|
|
}
|
|
}
|
|
fn fmtTerm(term: ?std.process.Child.Term) std.fmt.Formatter(formatTerm) {
|
|
return .{ .data = term };
|
|
}
|
|
|
|
fn termMatches(expected: ?std.process.Child.Term, actual: std.process.Child.Term) bool {
|
|
return if (expected) |e| switch (e) {
|
|
.Exited => |expected_code| switch (actual) {
|
|
.Exited => |actual_code| expected_code == actual_code,
|
|
else => false,
|
|
},
|
|
.Signal => |expected_sig| switch (actual) {
|
|
.Signal => |actual_sig| expected_sig == actual_sig,
|
|
else => false,
|
|
},
|
|
.Stopped => |expected_sig| switch (actual) {
|
|
.Stopped => |actual_sig| expected_sig == actual_sig,
|
|
else => false,
|
|
},
|
|
.Unknown => |expected_code| switch (actual) {
|
|
.Unknown => |actual_code| expected_code == actual_code,
|
|
else => false,
|
|
},
|
|
} else switch (actual) {
|
|
.Exited => true,
|
|
else => false,
|
|
};
|
|
}
|
|
|
|
fn runCommand(
|
|
self: *RunStep,
|
|
argv: []const []const u8,
|
|
has_side_effects: bool,
|
|
digest: ?*const [std.Build.Cache.hex_digest_len]u8,
|
|
prog_node: *std.Progress.Node,
|
|
) !void {
|
|
const step = &self.step;
|
|
const b = step.owner;
|
|
const arena = b.allocator;
|
|
|
|
try step.handleChildProcUnsupported(self.cwd, argv);
|
|
try Step.handleVerbose2(step.owner, self.cwd, self.env_map, argv);
|
|
|
|
const allow_skip = switch (self.stdio) {
|
|
.check, .zig_test => self.skip_foreign_checks,
|
|
else => false,
|
|
};
|
|
|
|
var interp_argv = std.ArrayList([]const u8).init(b.allocator);
|
|
defer interp_argv.deinit();
|
|
|
|
const result = spawnChildAndCollect(self, argv, has_side_effects, prog_node) 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 CompileStep. This will make this logic
|
|
// work even for the edge case that the binary was produced by a
|
|
// third party.
|
|
const exe = switch (self.argv.items[0]) {
|
|
.artifact => |exe| exe,
|
|
else => break :interpret,
|
|
};
|
|
switch (exe.kind) {
|
|
.exe, .@"test" => {},
|
|
else => break :interpret,
|
|
}
|
|
|
|
const need_cross_glibc = exe.target.isGnuLibC() and exe.is_linking_libc;
|
|
switch (b.host.getExternalExecutor(exe.target_info, .{
|
|
.qemu_fixes_dl = need_cross_glibc and b.glibc_runtimes_dir != null,
|
|
.link_libc = exe.is_linking_libc,
|
|
})) {
|
|
.native, .rosetta => {
|
|
if (allow_skip) return error.MakeSkipped;
|
|
break :interpret;
|
|
},
|
|
.wine => |bin_name| {
|
|
if (b.enable_wine) {
|
|
try interp_argv.append(bin_name);
|
|
try interp_argv.appendSlice(argv);
|
|
} else {
|
|
return failForeign(self, "-fwine", argv[0], exe);
|
|
}
|
|
},
|
|
.qemu => |bin_name| {
|
|
if (b.enable_qemu) {
|
|
const glibc_dir_arg = if (need_cross_glibc)
|
|
b.glibc_runtimes_dir orelse return
|
|
else
|
|
null;
|
|
|
|
try interp_argv.append(bin_name);
|
|
|
|
if (glibc_dir_arg) |dir| {
|
|
// TODO look into making this a call to `linuxTriple`. This
|
|
// needs the directory to be called "i686" rather than
|
|
// "x86" which is why we do it manually here.
|
|
const fmt_str = "{s}" ++ fs.path.sep_str ++ "{s}-{s}-{s}";
|
|
const cpu_arch = exe.target.getCpuArch();
|
|
const os_tag = exe.target.getOsTag();
|
|
const abi = exe.target.getAbi();
|
|
const cpu_arch_name: []const u8 = if (cpu_arch == .x86)
|
|
"i686"
|
|
else
|
|
@tagName(cpu_arch);
|
|
const full_dir = try std.fmt.allocPrint(b.allocator, fmt_str, .{
|
|
dir, cpu_arch_name, @tagName(os_tag), @tagName(abi),
|
|
});
|
|
|
|
try interp_argv.append("-L");
|
|
try interp_argv.append(full_dir);
|
|
}
|
|
|
|
try interp_argv.appendSlice(argv);
|
|
} else {
|
|
return failForeign(self, "-fqemu", argv[0], exe);
|
|
}
|
|
},
|
|
.darling => |bin_name| {
|
|
if (b.enable_darling) {
|
|
try interp_argv.append(bin_name);
|
|
try interp_argv.appendSlice(argv);
|
|
} else {
|
|
return failForeign(self, "-fdarling", argv[0], exe);
|
|
}
|
|
},
|
|
.wasmtime => |bin_name| {
|
|
if (b.enable_wasmtime) {
|
|
try interp_argv.append(bin_name);
|
|
try interp_argv.append("--dir=.");
|
|
try interp_argv.append(argv[0]);
|
|
try interp_argv.append("--");
|
|
try interp_argv.appendSlice(argv[1..]);
|
|
} else {
|
|
return failForeign(self, "-fwasmtime", argv[0], exe);
|
|
}
|
|
},
|
|
.bad_dl => |foreign_dl| {
|
|
if (allow_skip) return error.MakeSkipped;
|
|
|
|
const host_dl = b.host.dynamic_linker.get() orelse "(none)";
|
|
|
|
return step.fail(
|
|
\\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 b.host.target.zigTriple(b.allocator);
|
|
const foreign_name = try exe.target.zigTriple(b.allocator);
|
|
|
|
return step.fail("the host system ({s}) is unable to execute binaries from the target ({s})", .{
|
|
host_name, foreign_name,
|
|
});
|
|
},
|
|
}
|
|
|
|
if (exe.target.isWindows()) {
|
|
// On Windows we don't have rpaths so we have to add .dll search paths to PATH
|
|
self.addPathForDynLibs(exe);
|
|
}
|
|
|
|
try Step.handleVerbose2(step.owner, self.cwd, self.env_map, interp_argv.items);
|
|
|
|
break :term spawnChildAndCollect(self, interp_argv.items, has_side_effects, prog_node) catch |e| {
|
|
return step.fail("unable to spawn interpreter {s}: {s}", .{
|
|
interp_argv.items[0], @errorName(e),
|
|
});
|
|
};
|
|
}
|
|
|
|
return step.fail("unable to spawn {s}: {s}", .{ argv[0], @errorName(err) });
|
|
};
|
|
|
|
step.result_duration_ns = result.elapsed_ns;
|
|
step.result_peak_rss = result.peak_rss;
|
|
step.test_results = result.stdio.test_results;
|
|
|
|
// Capture stdout and stderr to GeneratedFile objects.
|
|
const Stream = struct {
|
|
captured: ?*Output,
|
|
is_null: bool,
|
|
bytes: []const u8,
|
|
};
|
|
for ([_]Stream{
|
|
.{
|
|
.captured = self.captured_stdout,
|
|
.is_null = result.stdio.stdout_null,
|
|
.bytes = result.stdio.stdout,
|
|
},
|
|
.{
|
|
.captured = self.captured_stderr,
|
|
.is_null = result.stdio.stderr_null,
|
|
.bytes = result.stdio.stderr,
|
|
},
|
|
}) |stream| {
|
|
if (stream.captured) |output| {
|
|
assert(!stream.is_null);
|
|
|
|
const output_components = .{ "o", digest.?, output.basename };
|
|
const output_path = try b.cache_root.join(arena, &output_components);
|
|
output.generated_file.path = output_path;
|
|
|
|
const sub_path = try fs.path.join(arena, &output_components);
|
|
const sub_path_dirname = fs.path.dirname(sub_path).?;
|
|
b.cache_root.handle.makePath(sub_path_dirname) catch |err| {
|
|
return step.fail("unable to make path '{}{s}': {s}", .{
|
|
b.cache_root, sub_path_dirname, @errorName(err),
|
|
});
|
|
};
|
|
b.cache_root.handle.writeFile(sub_path, stream.bytes) catch |err| {
|
|
return step.fail("unable to write file '{}{s}': {s}", .{
|
|
b.cache_root, sub_path, @errorName(err),
|
|
});
|
|
};
|
|
}
|
|
}
|
|
|
|
const final_argv = if (interp_argv.items.len == 0) argv else interp_argv.items;
|
|
|
|
switch (self.stdio) {
|
|
.check => |checks| for (checks.items) |check| switch (check) {
|
|
.expect_stderr_exact => |expected_bytes| {
|
|
assert(!result.stdio.stderr_null);
|
|
if (!mem.eql(u8, expected_bytes, result.stdio.stderr)) {
|
|
return step.fail(
|
|
\\
|
|
\\========= expected this stderr: =========
|
|
\\{s}
|
|
\\========= but found: ====================
|
|
\\{s}
|
|
\\========= from the following command: ===
|
|
\\{s}
|
|
, .{
|
|
expected_bytes,
|
|
result.stdio.stderr,
|
|
try Step.allocPrintCmd(arena, self.cwd, final_argv),
|
|
});
|
|
}
|
|
},
|
|
.expect_stderr_match => |match| {
|
|
assert(!result.stdio.stderr_null);
|
|
if (mem.indexOf(u8, result.stdio.stderr, match) == null) {
|
|
return step.fail(
|
|
\\
|
|
\\========= expected to find in stderr: =========
|
|
\\{s}
|
|
\\========= but stderr does not contain it: =====
|
|
\\{s}
|
|
\\========= from the following command: =========
|
|
\\{s}
|
|
, .{
|
|
match,
|
|
result.stdio.stderr,
|
|
try Step.allocPrintCmd(arena, self.cwd, final_argv),
|
|
});
|
|
}
|
|
},
|
|
.expect_stdout_exact => |expected_bytes| {
|
|
assert(!result.stdio.stdout_null);
|
|
if (!mem.eql(u8, expected_bytes, result.stdio.stdout)) {
|
|
return step.fail(
|
|
\\
|
|
\\========= expected this stdout: =========
|
|
\\{s}
|
|
\\========= but found: ====================
|
|
\\{s}
|
|
\\========= from the following command: ===
|
|
\\{s}
|
|
, .{
|
|
expected_bytes,
|
|
result.stdio.stdout,
|
|
try Step.allocPrintCmd(arena, self.cwd, final_argv),
|
|
});
|
|
}
|
|
},
|
|
.expect_stdout_match => |match| {
|
|
assert(!result.stdio.stdout_null);
|
|
if (mem.indexOf(u8, result.stdio.stdout, match) == null) {
|
|
return step.fail(
|
|
\\
|
|
\\========= expected to find in stdout: =========
|
|
\\{s}
|
|
\\========= but stdout does not contain it: =====
|
|
\\{s}
|
|
\\========= from the following command: =========
|
|
\\{s}
|
|
, .{
|
|
match,
|
|
result.stdio.stdout,
|
|
try Step.allocPrintCmd(arena, self.cwd, final_argv),
|
|
});
|
|
}
|
|
},
|
|
.expect_term => |expected_term| {
|
|
if (!termMatches(expected_term, result.term)) {
|
|
return step.fail("the following command {} (expected {}):\n{s}", .{
|
|
fmtTerm(result.term),
|
|
fmtTerm(expected_term),
|
|
try Step.allocPrintCmd(arena, self.cwd, final_argv),
|
|
});
|
|
}
|
|
},
|
|
},
|
|
.zig_test => {
|
|
const expected_term: std.process.Child.Term = .{ .Exited = 0 };
|
|
if (!termMatches(expected_term, result.term)) {
|
|
return step.fail("the following command {} (expected {}):\n{s}", .{
|
|
fmtTerm(result.term),
|
|
fmtTerm(expected_term),
|
|
try Step.allocPrintCmd(arena, self.cwd, final_argv),
|
|
});
|
|
}
|
|
if (!result.stdio.test_results.isSuccess()) {
|
|
return step.fail(
|
|
"the following test command failed:\n{s}",
|
|
.{try Step.allocPrintCmd(arena, self.cwd, final_argv)},
|
|
);
|
|
}
|
|
},
|
|
else => {
|
|
try step.handleChildProcessTerm(result.term, self.cwd, final_argv);
|
|
},
|
|
}
|
|
}
|
|
|
|
const ChildProcResult = struct {
|
|
term: std.process.Child.Term,
|
|
elapsed_ns: u64,
|
|
peak_rss: usize,
|
|
|
|
stdio: StdIoResult,
|
|
};
|
|
|
|
fn spawnChildAndCollect(
|
|
self: *RunStep,
|
|
argv: []const []const u8,
|
|
has_side_effects: bool,
|
|
prog_node: *std.Progress.Node,
|
|
) !ChildProcResult {
|
|
const b = self.step.owner;
|
|
const arena = b.allocator;
|
|
|
|
var child = std.process.Child.init(argv, arena);
|
|
if (self.cwd) |cwd| {
|
|
child.cwd = b.pathFromRoot(cwd);
|
|
} else {
|
|
child.cwd = b.build_root.path;
|
|
child.cwd_dir = b.build_root.handle;
|
|
}
|
|
child.env_map = self.env_map orelse b.env_map;
|
|
child.request_resource_usage_statistics = true;
|
|
|
|
child.stdin_behavior = switch (self.stdio) {
|
|
.infer_from_args => if (has_side_effects) .Inherit else .Close,
|
|
.inherit => .Inherit,
|
|
.check => .Close,
|
|
.zig_test => .Pipe,
|
|
};
|
|
child.stdout_behavior = switch (self.stdio) {
|
|
.infer_from_args => if (has_side_effects) .Inherit else .Ignore,
|
|
.inherit => .Inherit,
|
|
.check => |checks| if (checksContainStdout(checks.items)) .Pipe else .Ignore,
|
|
.zig_test => .Pipe,
|
|
};
|
|
child.stderr_behavior = switch (self.stdio) {
|
|
.infer_from_args => if (has_side_effects) .Inherit else .Pipe,
|
|
.inherit => .Inherit,
|
|
.check => .Pipe,
|
|
.zig_test => .Pipe,
|
|
};
|
|
if (self.captured_stdout != null) child.stdout_behavior = .Pipe;
|
|
if (self.captured_stderr != null) child.stderr_behavior = .Pipe;
|
|
if (self.stdin != null) {
|
|
assert(child.stdin_behavior != .Inherit);
|
|
child.stdin_behavior = .Pipe;
|
|
}
|
|
|
|
try child.spawn();
|
|
var timer = try std.time.Timer.start();
|
|
|
|
const result = if (self.stdio == .zig_test)
|
|
evalZigTest(self, &child, prog_node)
|
|
else
|
|
evalGeneric(self, &child);
|
|
|
|
const term = try child.wait();
|
|
const elapsed_ns = timer.read();
|
|
|
|
return .{
|
|
.stdio = try result,
|
|
.term = term,
|
|
.elapsed_ns = elapsed_ns,
|
|
.peak_rss = child.resource_usage_statistics.getMaxRss() orelse 0,
|
|
};
|
|
}
|
|
|
|
const StdIoResult = struct {
|
|
// These use boolean flags instead of optionals as a workaround for
|
|
// https://github.com/ziglang/zig/issues/14783
|
|
stdout: []const u8,
|
|
stderr: []const u8,
|
|
stdout_null: bool,
|
|
stderr_null: bool,
|
|
test_results: Step.TestResults,
|
|
};
|
|
|
|
fn evalZigTest(
|
|
self: *RunStep,
|
|
child: *std.process.Child,
|
|
prog_node: *std.Progress.Node,
|
|
) !StdIoResult {
|
|
const gpa = self.step.owner.allocator;
|
|
const arena = self.step.owner.allocator;
|
|
|
|
var poller = std.io.poll(gpa, enum { stdout, stderr }, .{
|
|
.stdout = child.stdout.?,
|
|
.stderr = child.stderr.?,
|
|
});
|
|
defer poller.deinit();
|
|
|
|
try sendMessage(child.stdin.?, .query_test_metadata);
|
|
|
|
const Header = std.zig.Server.Message.Header;
|
|
|
|
const stdout = poller.fifo(.stdout);
|
|
const stderr = poller.fifo(.stderr);
|
|
|
|
var fail_count: u32 = 0;
|
|
var skip_count: u32 = 0;
|
|
var leak_count: u32 = 0;
|
|
var test_count: u32 = 0;
|
|
|
|
var metadata: ?TestMetadata = null;
|
|
|
|
var sub_prog_node: ?std.Progress.Node = null;
|
|
defer if (sub_prog_node) |*n| n.end();
|
|
|
|
poll: while (true) {
|
|
while (stdout.readableLength() < @sizeOf(Header)) {
|
|
if (!(try poller.poll())) break :poll;
|
|
}
|
|
const header = stdout.reader().readStruct(Header) catch unreachable;
|
|
while (stdout.readableLength() < header.bytes_len) {
|
|
if (!(try poller.poll())) break :poll;
|
|
}
|
|
const body = stdout.readableSliceOfLen(header.bytes_len);
|
|
|
|
switch (header.tag) {
|
|
.zig_version => {
|
|
if (!std.mem.eql(u8, builtin.zig_version_string, body)) {
|
|
return self.step.fail(
|
|
"zig version mismatch build runner vs compiler: '{s}' vs '{s}'",
|
|
.{ builtin.zig_version_string, body },
|
|
);
|
|
}
|
|
},
|
|
.test_metadata => {
|
|
const TmHdr = std.zig.Server.Message.TestMetadata;
|
|
const tm_hdr = @ptrCast(*align(1) const TmHdr, body);
|
|
test_count = tm_hdr.tests_len;
|
|
|
|
const names_bytes = body[@sizeOf(TmHdr)..][0 .. test_count * @sizeOf(u32)];
|
|
const async_frame_lens_bytes = body[@sizeOf(TmHdr) + names_bytes.len ..][0 .. test_count * @sizeOf(u32)];
|
|
const expected_panic_msgs_bytes = body[@sizeOf(TmHdr) + names_bytes.len + async_frame_lens_bytes.len ..][0 .. test_count * @sizeOf(u32)];
|
|
const string_bytes = body[@sizeOf(TmHdr) + names_bytes.len + async_frame_lens_bytes.len + expected_panic_msgs_bytes.len ..][0..tm_hdr.string_bytes_len];
|
|
|
|
const names = std.mem.bytesAsSlice(u32, names_bytes);
|
|
const async_frame_lens = std.mem.bytesAsSlice(u32, async_frame_lens_bytes);
|
|
const expected_panic_msgs = std.mem.bytesAsSlice(u32, expected_panic_msgs_bytes);
|
|
const names_aligned = try arena.alloc(u32, names.len);
|
|
for (names_aligned, names) |*dest, src| dest.* = src;
|
|
|
|
const async_frame_lens_aligned = try arena.alloc(u32, async_frame_lens.len);
|
|
for (async_frame_lens_aligned, async_frame_lens) |*dest, src| dest.* = src;
|
|
|
|
const expected_panic_msgs_aligned = try arena.alloc(u32, expected_panic_msgs.len);
|
|
for (expected_panic_msgs_aligned, expected_panic_msgs) |*dest, src| dest.* = src;
|
|
|
|
prog_node.setEstimatedTotalItems(names.len);
|
|
metadata = .{
|
|
.string_bytes = try arena.dupe(u8, string_bytes),
|
|
.names = names_aligned,
|
|
.async_frame_lens = async_frame_lens_aligned,
|
|
.expected_panic_msgs = expected_panic_msgs_aligned,
|
|
.next_index = 0,
|
|
.prog_node = prog_node,
|
|
};
|
|
|
|
try requestNextTest(child.stdin.?, &metadata.?, &sub_prog_node);
|
|
},
|
|
.test_results => {
|
|
const md = metadata.?;
|
|
|
|
const TrHdr = std.zig.Server.Message.TestResults;
|
|
const tr_hdr = @ptrCast(*align(1) const TrHdr, body);
|
|
fail_count += @boolToInt(tr_hdr.flags.fail);
|
|
skip_count += @boolToInt(tr_hdr.flags.skip);
|
|
leak_count += @boolToInt(tr_hdr.flags.leak);
|
|
|
|
if (tr_hdr.flags.fail or tr_hdr.flags.leak) {
|
|
const name = std.mem.sliceTo(md.string_bytes[md.names[tr_hdr.index]..], 0);
|
|
const msg = std.mem.trim(u8, stderr.readableSlice(0), "\n");
|
|
const label = if (tr_hdr.flags.fail) "failed" else "leaked";
|
|
if (msg.len > 0) {
|
|
try self.step.addError("'{s}' {s}: {s}", .{ name, label, msg });
|
|
} else {
|
|
try self.step.addError("'{s}' {s}", .{ name, label });
|
|
}
|
|
stderr.discard(msg.len);
|
|
}
|
|
|
|
try requestNextTest(child.stdin.?, &metadata.?, &sub_prog_node);
|
|
},
|
|
else => {}, // ignore other messages
|
|
}
|
|
|
|
stdout.discard(body.len);
|
|
}
|
|
|
|
if (stderr.readableLength() > 0) {
|
|
const msg = std.mem.trim(u8, try stderr.toOwnedSlice(), "\n");
|
|
if (msg.len > 0) try self.step.result_error_msgs.append(arena, msg);
|
|
}
|
|
|
|
// Send EOF to stdin.
|
|
child.stdin.?.close();
|
|
child.stdin = null;
|
|
|
|
return .{
|
|
.stdout = &.{},
|
|
.stderr = &.{},
|
|
.stdout_null = true,
|
|
.stderr_null = true,
|
|
.test_results = .{
|
|
.test_count = test_count,
|
|
.fail_count = fail_count,
|
|
.skip_count = skip_count,
|
|
.leak_count = leak_count,
|
|
},
|
|
};
|
|
}
|
|
|
|
const TestMetadata = struct {
|
|
names: []const u32,
|
|
async_frame_lens: []const u32,
|
|
expected_panic_msgs: []const u32,
|
|
string_bytes: []const u8,
|
|
next_index: u32,
|
|
prog_node: *std.Progress.Node,
|
|
|
|
fn testName(tm: TestMetadata, index: u32) []const u8 {
|
|
return std.mem.sliceTo(tm.string_bytes[tm.names[index]..], 0);
|
|
}
|
|
};
|
|
|
|
fn requestNextTest(in: fs.File, metadata: *TestMetadata, sub_prog_node: *?std.Progress.Node) !void {
|
|
while (metadata.next_index < metadata.names.len) {
|
|
const i = metadata.next_index;
|
|
metadata.next_index += 1;
|
|
|
|
if (metadata.async_frame_lens[i] != 0) continue;
|
|
if (metadata.expected_panic_msgs[i] != 0) continue;
|
|
|
|
const name = metadata.testName(i);
|
|
if (sub_prog_node.*) |*n| n.end();
|
|
sub_prog_node.* = metadata.prog_node.start(name, 0);
|
|
|
|
try sendRunTestMessage(in, i);
|
|
return;
|
|
} else {
|
|
try sendMessage(in, .exit);
|
|
}
|
|
}
|
|
|
|
fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void {
|
|
const header: std.zig.Client.Message.Header = .{
|
|
.tag = tag,
|
|
.bytes_len = 0,
|
|
};
|
|
try file.writeAll(std.mem.asBytes(&header));
|
|
}
|
|
|
|
fn sendRunTestMessage(file: std.fs.File, index: u32) !void {
|
|
const header: std.zig.Client.Message.Header = .{
|
|
.tag = .run_test,
|
|
.bytes_len = 4,
|
|
};
|
|
const full_msg = std.mem.asBytes(&header) ++ std.mem.asBytes(&index);
|
|
try file.writeAll(full_msg);
|
|
}
|
|
|
|
fn evalGeneric(self: *RunStep, child: *std.process.Child) !StdIoResult {
|
|
const arena = self.step.owner.allocator;
|
|
|
|
if (self.stdin) |stdin| {
|
|
child.stdin.?.writeAll(stdin) catch |err| {
|
|
return self.step.fail("unable to write stdin: {s}", .{@errorName(err)});
|
|
};
|
|
child.stdin.?.close();
|
|
child.stdin = null;
|
|
}
|
|
|
|
// These are not optionals, as a workaround for
|
|
// https://github.com/ziglang/zig/issues/14783
|
|
var stdout_bytes: []const u8 = undefined;
|
|
var stderr_bytes: []const u8 = undefined;
|
|
var stdout_null = true;
|
|
var stderr_null = true;
|
|
|
|
if (child.stdout) |stdout| {
|
|
if (child.stderr) |stderr| {
|
|
var poller = std.io.poll(arena, enum { stdout, stderr }, .{
|
|
.stdout = stdout,
|
|
.stderr = stderr,
|
|
});
|
|
defer poller.deinit();
|
|
|
|
while (try poller.poll()) {
|
|
if (poller.fifo(.stdout).count > self.max_stdio_size)
|
|
return error.StdoutStreamTooLong;
|
|
if (poller.fifo(.stderr).count > self.max_stdio_size)
|
|
return error.StderrStreamTooLong;
|
|
}
|
|
|
|
stdout_bytes = try poller.fifo(.stdout).toOwnedSlice();
|
|
stderr_bytes = try poller.fifo(.stderr).toOwnedSlice();
|
|
stdout_null = false;
|
|
stderr_null = false;
|
|
} else {
|
|
stdout_bytes = try stdout.reader().readAllAlloc(arena, self.max_stdio_size);
|
|
stdout_null = false;
|
|
}
|
|
} else if (child.stderr) |stderr| {
|
|
stderr_bytes = try stderr.reader().readAllAlloc(arena, self.max_stdio_size);
|
|
stderr_null = false;
|
|
}
|
|
|
|
if (!stderr_null and stderr_bytes.len > 0) {
|
|
// Treat stderr as an error message.
|
|
const stderr_is_diagnostic = self.captured_stderr == null and switch (self.stdio) {
|
|
.check => |checks| !checksContainStderr(checks.items),
|
|
else => true,
|
|
};
|
|
if (stderr_is_diagnostic) {
|
|
try self.step.result_error_msgs.append(arena, stderr_bytes);
|
|
}
|
|
}
|
|
|
|
return .{
|
|
.stdout = stdout_bytes,
|
|
.stderr = stderr_bytes,
|
|
.stdout_null = stdout_null,
|
|
.stderr_null = stderr_null,
|
|
.test_results = .{},
|
|
};
|
|
}
|
|
|
|
fn addPathForDynLibs(self: *RunStep, artifact: *CompileStep) void {
|
|
const b = self.step.owner;
|
|
for (artifact.link_objects.items) |link_object| {
|
|
switch (link_object) {
|
|
.other_step => |other| {
|
|
if (other.target.isWindows() and other.isDynamicLibrary()) {
|
|
addPathDir(self, fs.path.dirname(other.getOutputSource().getPath(b)).?);
|
|
addPathForDynLibs(self, other);
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
}
|
|
|
|
fn failForeign(
|
|
self: *RunStep,
|
|
suggested_flag: []const u8,
|
|
argv0: []const u8,
|
|
exe: *CompileStep,
|
|
) error{ MakeFailed, MakeSkipped, OutOfMemory } {
|
|
switch (self.stdio) {
|
|
.check, .zig_test => {
|
|
if (self.skip_foreign_checks)
|
|
return error.MakeSkipped;
|
|
|
|
const b = self.step.owner;
|
|
const host_name = try b.host.target.zigTriple(b.allocator);
|
|
const foreign_name = try exe.target.zigTriple(b.allocator);
|
|
|
|
return self.step.fail(
|
|
\\unable to spawn foreign binary '{s}' ({s}) on host system ({s})
|
|
\\ consider using {s} or enabling skip_foreign_checks in the Run step
|
|
, .{ argv0, foreign_name, host_name, suggested_flag });
|
|
},
|
|
else => {
|
|
return self.step.fail("unable to spawn foreign binary '{s}'", .{argv0});
|
|
},
|
|
}
|
|
}
|
|
|
|
fn hashStdIo(hh: *std.Build.Cache.HashHelper, stdio: StdIo) void {
|
|
switch (stdio) {
|
|
.infer_from_args, .inherit, .zig_test => {},
|
|
.check => |checks| for (checks.items) |check| {
|
|
hh.add(@as(std.meta.Tag(StdIo.Check), check));
|
|
switch (check) {
|
|
.expect_stderr_exact,
|
|
.expect_stderr_match,
|
|
.expect_stdout_exact,
|
|
.expect_stdout_match,
|
|
=> |s| hh.addBytes(s),
|
|
|
|
.expect_term => |term| {
|
|
hh.add(@as(std.meta.Tag(std.process.Child.Term), term));
|
|
switch (term) {
|
|
.Exited => |x| hh.add(x),
|
|
.Signal, .Stopped, .Unknown => |x| hh.add(x),
|
|
}
|
|
},
|
|
}
|
|
},
|
|
}
|
|
}
|