diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index f87a121993..5df77014b5 100644 --- a/lib/compiler/build_runner.zig +++ b/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 { diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 5a5bce5127..c13b137ff6 100644 --- a/lib/std/Build.zig +++ b/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, }); diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 7b72d9a825..72eb66e530 100644 --- a/lib/std/Build/Step.zig +++ b/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); diff --git a/lib/std/Build/Step/Fmt.zig b/lib/std/Build/Step/Fmt.zig index a364dfa6f4..5adebdc454 100644 --- a/lib/std/Build/Step/Fmt.zig +++ b/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); } diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 39eb276b48..52d1a72649 100644 --- a/lib/std/Build/Step/Run.zig +++ b/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);