mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 05:44:20 +00:00
std.Build: separate errors from failed commands
Recording the command in a separate field will give the build runner more freedom to choose how and when the command should be printed.
This commit is contained in:
parent
e4456d03f3
commit
a388a8e5a7
5 changed files with 71 additions and 99 deletions
|
|
@ -1443,6 +1443,14 @@ pub fn printErrorMessages(
|
|||
}
|
||||
try stderr.writeAll("\n");
|
||||
}
|
||||
|
||||
if (failing_step.result_failed_command) |cmd_str| {
|
||||
try ttyconf.setColor(stderr, .red);
|
||||
try stderr.writeAll("failed command: ");
|
||||
try ttyconf.setColor(stderr, .reset);
|
||||
try stderr.writeAll(cmd_str);
|
||||
try stderr.writeByte('\n');
|
||||
}
|
||||
}
|
||||
|
||||
fn printSteps(builder: *std.Build, w: *Writer) !void {
|
||||
|
|
|
|||
|
|
@ -1594,20 +1594,6 @@ pub fn validateUserInputDidItFail(b: *Build) bool {
|
|||
return b.invalid_user_input;
|
||||
}
|
||||
|
||||
fn allocPrintCmd(gpa: Allocator, opt_cwd: ?[]const u8, argv: []const []const u8) error{OutOfMemory}![]u8 {
|
||||
var buf: ArrayList(u8) = .empty;
|
||||
if (opt_cwd) |cwd| try buf.print(gpa, "cd {s} && ", .{cwd});
|
||||
for (argv) |arg| {
|
||||
try buf.print(gpa, "{s} ", .{arg});
|
||||
}
|
||||
return buf.toOwnedSlice(gpa);
|
||||
}
|
||||
|
||||
fn printCmd(ally: Allocator, cwd: ?[]const u8, argv: []const []const u8) void {
|
||||
const text = allocPrintCmd(ally, cwd, argv) catch @panic("OOM");
|
||||
std.debug.print("{s}\n", .{text});
|
||||
}
|
||||
|
||||
/// This creates the install step and adds it to the dependencies of the
|
||||
/// top-level install step, using all the default options.
|
||||
/// See `addInstallArtifact` for a more flexible function.
|
||||
|
|
@ -1857,14 +1843,14 @@ pub fn runAllowFail(
|
|||
pub fn run(b: *Build, argv: []const []const u8) []u8 {
|
||||
if (!process.can_spawn) {
|
||||
std.debug.print("unable to spawn the following command: cannot spawn child process\n{s}\n", .{
|
||||
try allocPrintCmd(b.allocator, null, argv),
|
||||
try Step.allocPrintCmd(b.allocator, null, argv),
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var code: u8 = undefined;
|
||||
return b.runAllowFail(argv, &code, .Inherit) catch |err| {
|
||||
const printed_cmd = allocPrintCmd(b.allocator, null, argv) catch @panic("OOM");
|
||||
const printed_cmd = Step.allocPrintCmd(b.allocator, null, argv) catch @panic("OOM");
|
||||
std.debug.print("unable to spawn the following command: {s}\n{s}\n", .{
|
||||
@errorName(err), printed_cmd,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -56,6 +56,9 @@ result_cached: bool,
|
|||
result_duration_ns: ?u64,
|
||||
/// 0 means unavailable or not reported.
|
||||
result_peak_rss: usize,
|
||||
/// If the step is failed and this field is populated, this is the command which failed.
|
||||
/// This field may be populated even if the step succeeded.
|
||||
result_failed_command: ?[]const u8,
|
||||
test_results: TestResults,
|
||||
|
||||
/// The return address associated with creation of this step that can be useful
|
||||
|
|
@ -257,6 +260,7 @@ pub fn init(options: StepOptions) Step {
|
|||
.result_cached = false,
|
||||
.result_duration_ns = null,
|
||||
.result_peak_rss = 0,
|
||||
.result_failed_command = null,
|
||||
.test_results = .{},
|
||||
};
|
||||
}
|
||||
|
|
@ -336,20 +340,20 @@ pub fn dump(step: *Step, w: *std.Io.Writer, tty_config: std.Io.tty.Config) void
|
|||
}
|
||||
}
|
||||
|
||||
pub fn evalChildProcess(s: *Step, argv: []const []const u8) ![]u8 {
|
||||
const run_result = try captureChildProcess(s, std.Progress.Node.none, argv);
|
||||
try handleChildProcessTerm(s, run_result.term, null, argv);
|
||||
return run_result.stdout;
|
||||
}
|
||||
|
||||
/// Populates `s.result_failed_command`.
|
||||
pub fn captureChildProcess(
|
||||
s: *Step,
|
||||
gpa: Allocator,
|
||||
progress_node: std.Progress.Node,
|
||||
argv: []const []const u8,
|
||||
) !std.process.Child.RunResult {
|
||||
const arena = s.owner.allocator;
|
||||
|
||||
try handleChildProcUnsupported(s, null, argv);
|
||||
// If an error occurs, it's happened in this command:
|
||||
assert(s.result_failed_command == null);
|
||||
s.result_failed_command = try allocPrintCmd(gpa, null, argv);
|
||||
|
||||
try handleChildProcUnsupported(s);
|
||||
try handleVerbose(s.owner, null, argv);
|
||||
|
||||
const result = std.process.Child.run(.{
|
||||
|
|
@ -386,6 +390,7 @@ pub const ZigProcess = struct {
|
|||
|
||||
/// Assumes that argv contains `--listen=-` and that the process being spawned
|
||||
/// is the zig compiler - the same version that compiled the build runner.
|
||||
/// Populates `s.result_failed_command`.
|
||||
pub fn evalZigProcess(
|
||||
s: *Step,
|
||||
argv: []const []const u8,
|
||||
|
|
@ -394,6 +399,10 @@ pub fn evalZigProcess(
|
|||
web_server: ?*Build.WebServer,
|
||||
gpa: Allocator,
|
||||
) !?Path {
|
||||
// If an error occurs, it's happened in this command:
|
||||
assert(s.result_failed_command == null);
|
||||
s.result_failed_command = try allocPrintCmd(gpa, null, argv);
|
||||
|
||||
if (s.getZigProcess()) |zp| update: {
|
||||
assert(watch);
|
||||
if (std.Progress.have_ipc) if (zp.progress_ipc_fd) |fd| prog_node.setIpcFd(fd);
|
||||
|
|
@ -410,8 +419,9 @@ pub fn evalZigProcess(
|
|||
else => |e| return e,
|
||||
};
|
||||
|
||||
if (s.result_error_bundle.errorMessageCount() > 0)
|
||||
if (s.result_error_bundle.errorMessageCount() > 0) {
|
||||
return s.fail("{d} compilation errors", .{s.result_error_bundle.errorMessageCount()});
|
||||
}
|
||||
|
||||
if (s.result_error_msgs.items.len > 0 and result == null) {
|
||||
// Crash detected.
|
||||
|
|
@ -420,7 +430,7 @@ pub fn evalZigProcess(
|
|||
};
|
||||
s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0;
|
||||
s.clearZigProcess(gpa);
|
||||
try handleChildProcessTerm(s, term, null, argv);
|
||||
try handleChildProcessTerm(s, term);
|
||||
return error.MakeFailed;
|
||||
}
|
||||
|
||||
|
|
@ -430,7 +440,7 @@ pub fn evalZigProcess(
|
|||
const b = s.owner;
|
||||
const arena = b.allocator;
|
||||
|
||||
try handleChildProcUnsupported(s, null, argv);
|
||||
try handleChildProcUnsupported(s);
|
||||
try handleVerbose(s.owner, null, argv);
|
||||
|
||||
var child = std.process.Child.init(argv, arena);
|
||||
|
|
@ -484,16 +494,11 @@ pub fn evalZigProcess(
|
|||
else => {},
|
||||
};
|
||||
|
||||
try handleChildProcessTerm(s, term, null, argv);
|
||||
try handleChildProcessTerm(s, term);
|
||||
}
|
||||
|
||||
// This is intentionally printed for failure on the first build but not for
|
||||
// subsequent rebuilds.
|
||||
if (s.result_error_bundle.errorMessageCount() > 0) {
|
||||
return s.fail("the following command failed with {d} compilation errors:\n{s}", .{
|
||||
s.result_error_bundle.errorMessageCount(),
|
||||
try allocPrintCmd(arena, null, argv),
|
||||
});
|
||||
return s.fail("{d} compilation errors", .{s.result_error_bundle.errorMessageCount()});
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
@ -696,54 +701,38 @@ pub fn handleVerbose2(
|
|||
}
|
||||
}
|
||||
|
||||
pub inline fn handleChildProcUnsupported(
|
||||
s: *Step,
|
||||
opt_cwd: ?[]const u8,
|
||||
argv: []const []const u8,
|
||||
) error{ OutOfMemory, MakeFailed }!void {
|
||||
/// Asserts that the caller has already populated `s.result_failed_command`.
|
||||
pub inline fn handleChildProcUnsupported(s: *Step) error{ OutOfMemory, MakeFailed }!void {
|
||||
if (!std.process.can_spawn) {
|
||||
return s.fail(
|
||||
"unable to execute the following command: host cannot spawn child processes\n{s}",
|
||||
.{try allocPrintCmd(s.owner.allocator, opt_cwd, argv)},
|
||||
);
|
||||
return s.fail("unable to spawn process: host cannot spawn child processes", .{});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handleChildProcessTerm(
|
||||
s: *Step,
|
||||
term: std.process.Child.Term,
|
||||
opt_cwd: ?[]const u8,
|
||||
argv: []const []const u8,
|
||||
) error{ MakeFailed, OutOfMemory }!void {
|
||||
const arena = s.owner.allocator;
|
||||
/// Asserts that the caller has already populated `s.result_failed_command`.
|
||||
pub fn handleChildProcessTerm(s: *Step, term: std.process.Child.Term) error{ MakeFailed, OutOfMemory }!void {
|
||||
assert(s.result_failed_command != null);
|
||||
switch (term) {
|
||||
.Exited => |code| {
|
||||
if (code != 0) {
|
||||
return s.fail(
|
||||
"the following command exited with error code {d}:\n{s}",
|
||||
.{ code, try allocPrintCmd(arena, opt_cwd, argv) },
|
||||
);
|
||||
return s.fail("process exited with error code {d}", .{code});
|
||||
}
|
||||
},
|
||||
.Signal, .Stopped, .Unknown => {
|
||||
return s.fail(
|
||||
"the following command terminated unexpectedly:\n{s}",
|
||||
.{try allocPrintCmd(arena, opt_cwd, argv)},
|
||||
);
|
||||
return s.fail("process terminated unexpectedly", .{});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn allocPrintCmd(
|
||||
arena: Allocator,
|
||||
gpa: Allocator,
|
||||
opt_cwd: ?[]const u8,
|
||||
argv: []const []const u8,
|
||||
) Allocator.Error![]u8 {
|
||||
return allocPrintCmd2(arena, opt_cwd, null, argv);
|
||||
return allocPrintCmd2(gpa, opt_cwd, null, argv);
|
||||
}
|
||||
|
||||
pub fn allocPrintCmd2(
|
||||
arena: Allocator,
|
||||
gpa: Allocator,
|
||||
opt_cwd: ?[]const u8,
|
||||
opt_env: ?*const std.process.EnvMap,
|
||||
argv: []const []const u8,
|
||||
|
|
@ -783,11 +772,13 @@ pub fn allocPrintCmd2(
|
|||
}
|
||||
};
|
||||
|
||||
var aw: std.Io.Writer.Allocating = .init(arena);
|
||||
var aw: std.Io.Writer.Allocating = .init(gpa);
|
||||
defer aw.deinit();
|
||||
const writer = &aw.writer;
|
||||
if (opt_cwd) |cwd| writer.print("cd {s} && ", .{cwd}) catch return error.OutOfMemory;
|
||||
if (opt_env) |env| {
|
||||
const process_env_map = std.process.getEnvMap(arena) catch std.process.EnvMap.init(arena);
|
||||
var process_env_map = std.process.getEnvMap(gpa) catch std.process.EnvMap.init(gpa);
|
||||
defer process_env_map.deinit();
|
||||
var it = env.iterator();
|
||||
while (it.next()) |entry| {
|
||||
const key = entry.key_ptr.*;
|
||||
|
|
@ -973,11 +964,14 @@ fn addWatchInputFromPath(step: *Step, path: Build.Cache.Path, basename: []const
|
|||
pub fn reset(step: *Step, gpa: Allocator) void {
|
||||
assert(step.state == .precheck_done);
|
||||
|
||||
if (step.result_failed_command) |cmd| gpa.free(cmd);
|
||||
|
||||
step.result_error_msgs.clearRetainingCapacity();
|
||||
step.result_stderr = "";
|
||||
step.result_cached = false;
|
||||
step.result_duration_ns = null;
|
||||
step.result_peak_rss = 0;
|
||||
step.result_failed_command = null;
|
||||
step.test_results = .{};
|
||||
|
||||
step.result_error_bundle.deinit(gpa);
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
|
|||
argv.appendAssumeCapacity(b.pathFromRoot(p));
|
||||
}
|
||||
|
||||
const run_result = try step.captureChildProcess(prog_node, argv.items);
|
||||
const run_result = try step.captureChildProcess(options.gpa, prog_node, argv.items);
|
||||
if (fmt.check) switch (run_result.term) {
|
||||
.Exited => |code| if (code != 0 and run_result.stdout.len != 0) {
|
||||
var it = std.mem.tokenizeScalar(u8, run_result.stdout, '\n');
|
||||
|
|
@ -77,5 +77,5 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
|
|||
},
|
||||
else => {},
|
||||
};
|
||||
try step.handleChildProcessTerm(run_result.term, null, argv.items);
|
||||
try step.handleChildProcessTerm(run_result.term);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1212,10 +1212,11 @@ fn runCommand(
|
|||
const step = &run.step;
|
||||
const b = step.owner;
|
||||
const arena = b.allocator;
|
||||
const gpa = options.gpa;
|
||||
|
||||
const cwd: ?[]const u8 = if (run.cwd) |lazy_cwd| lazy_cwd.getPath2(b, step) else null;
|
||||
|
||||
try step.handleChildProcUnsupported(cwd, argv);
|
||||
try step.handleChildProcUnsupported();
|
||||
try Step.handleVerbose2(step.owner, cwd, run.env_map, argv);
|
||||
|
||||
const allow_skip = switch (run.stdio) {
|
||||
|
|
@ -1365,6 +1366,8 @@ fn runCommand(
|
|||
run.addPathForDynLibs(exe);
|
||||
}
|
||||
|
||||
gpa.free(step.result_failed_command.?);
|
||||
step.result_failed_command = null;
|
||||
try Step.handleVerbose2(step.owner, cwd, run.env_map, interp_argv.items);
|
||||
|
||||
break :term spawnChildAndCollect(run, interp_argv.items, env_map, has_side_effects, options, fuzz_context) catch |e| {
|
||||
|
|
@ -1380,18 +1383,11 @@ fn runCommand(
|
|||
return step.fail("failed to spawn and capture stdio from {s}: {s}", .{ argv[0], @errorName(err) });
|
||||
};
|
||||
|
||||
const final_argv = if (interp_argv.items.len == 0) argv else interp_argv.items;
|
||||
|
||||
const generic_result = opt_generic_result orelse {
|
||||
assert(run.stdio == .zig_test);
|
||||
// Specific errors have already been reported. All we need to do is detect those and
|
||||
// report the general "test failed" error, which includes the command argv.
|
||||
if (!step.test_results.isSuccess() or step.result_error_msgs.items.len > 0) {
|
||||
return step.fail(
|
||||
"the following test command failed:\n{s}",
|
||||
.{try Step.allocPrintCmd(arena, cwd, final_argv)},
|
||||
);
|
||||
}
|
||||
// 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;
|
||||
return;
|
||||
};
|
||||
|
||||
|
|
@ -1445,93 +1441,77 @@ fn runCommand(
|
|||
.expect_stderr_exact => |expected_bytes| {
|
||||
if (!mem.eql(u8, expected_bytes, generic_result.stderr.?)) {
|
||||
return step.fail(
|
||||
\\
|
||||
\\========= expected this stderr: =========
|
||||
\\{s}
|
||||
\\========= but found: ====================
|
||||
\\{s}
|
||||
\\========= from the following command: ===
|
||||
\\{s}
|
||||
, .{
|
||||
expected_bytes,
|
||||
generic_result.stderr.?,
|
||||
try Step.allocPrintCmd(arena, cwd, final_argv),
|
||||
});
|
||||
}
|
||||
},
|
||||
.expect_stderr_match => |match| {
|
||||
if (mem.indexOf(u8, generic_result.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,
|
||||
generic_result.stderr.?,
|
||||
try Step.allocPrintCmd(arena, cwd, final_argv),
|
||||
});
|
||||
}
|
||||
},
|
||||
.expect_stdout_exact => |expected_bytes| {
|
||||
if (!mem.eql(u8, expected_bytes, generic_result.stdout.?)) {
|
||||
return step.fail(
|
||||
\\
|
||||
\\========= expected this stdout: =========
|
||||
\\{s}
|
||||
\\========= but found: ====================
|
||||
\\{s}
|
||||
\\========= from the following command: ===
|
||||
\\{s}
|
||||
, .{
|
||||
expected_bytes,
|
||||
generic_result.stdout.?,
|
||||
try Step.allocPrintCmd(arena, cwd, final_argv),
|
||||
});
|
||||
}
|
||||
},
|
||||
.expect_stdout_match => |match| {
|
||||
if (mem.indexOf(u8, generic_result.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,
|
||||
generic_result.stdout.?,
|
||||
try Step.allocPrintCmd(arena, cwd, final_argv),
|
||||
});
|
||||
}
|
||||
},
|
||||
.expect_term => |expected_term| {
|
||||
if (!termMatches(expected_term, generic_result.term)) {
|
||||
return step.fail("the following command {f} (expected {f}):\n{s}", .{
|
||||
return step.fail("process {f} (expected {f})", .{
|
||||
fmtTerm(generic_result.term),
|
||||
fmtTerm(expected_term),
|
||||
try Step.allocPrintCmd(arena, cwd, final_argv),
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
else => {
|
||||
// On failure, print stderr if captured.
|
||||
// On failure, report captured stderr like normal standard error output.
|
||||
const bad_exit = switch (generic_result.term) {
|
||||
.Exited => |code| code != 0,
|
||||
.Signal, .Stopped, .Unknown => true,
|
||||
};
|
||||
if (bad_exit) {
|
||||
if (generic_result.stderr) |bytes| {
|
||||
run.step.result_stderr = bytes;
|
||||
}
|
||||
}
|
||||
|
||||
if (bad_exit) if (generic_result.stderr) |err| {
|
||||
try step.addError("stderr:\n{s}", .{err});
|
||||
};
|
||||
|
||||
try step.handleChildProcessTerm(generic_result.term, cwd, final_argv);
|
||||
try step.handleChildProcessTerm(generic_result.term);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -1594,6 +1574,10 @@ fn spawnChildAndCollect(
|
|||
child.stdin_behavior = .Pipe;
|
||||
}
|
||||
|
||||
// If an error occurs, it's caused by this command:
|
||||
assert(run.step.result_failed_command == null);
|
||||
run.step.result_failed_command = try Step.allocPrintCmd(options.gpa, child.cwd, argv);
|
||||
|
||||
if (run.stdio == .zig_test) {
|
||||
var timer = try std.time.Timer.start();
|
||||
const res = try evalZigTest(run, &child, options, fuzz_context);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue