Commit a388a8e5a7
Changed files (5)
lib
compiler
lib/compiler/build_runner.zig
@@ -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 {
lib/std/Build/Step/Fmt.zig
@@ -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);
}
lib/std/Build/Step/Run.zig
@@ -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);
lib/std/Build/Step.zig
@@ -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);
lib/std/Build.zig
@@ -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,
});