diff --git a/lib/compiler/resinator/main.zig b/lib/compiler/resinator/main.zig index e598a4512230..41bbd08c0ceb 100644 --- a/lib/compiler/resinator/main.zig +++ b/lib/compiler/resinator/main.zig @@ -25,26 +25,48 @@ pub fn main() !void { std.os.exit(1); } const zig_lib_dir = args[1]; + var cli_args = args[2..]; + + var zig_integration = false; + if (cli_args.len > 0 and std.mem.eql(u8, cli_args[0], "--zig-integration")) { + zig_integration = true; + cli_args = args[3..]; + } + + var error_handler: ErrorHandler = switch (zig_integration) { + true => .{ + .server = .{ + .out = std.io.getStdOut(), + .in = undefined, // won't be receiving messages + .receive_fifo = undefined, // won't be receiving messages + }, + }, + false => .{ + .tty = stderr_config, + }, + }; var options = options: { var cli_diagnostics = cli.Diagnostics.init(allocator); defer cli_diagnostics.deinit(); - var options = cli.parse(allocator, args[2..], &cli_diagnostics) catch |err| switch (err) { + var options = cli.parse(allocator, cli_args, &cli_diagnostics) catch |err| switch (err) { error.ParseError => { - cli_diagnostics.renderToStdErr(args, stderr_config); + try error_handler.emitCliDiagnostics(allocator, cli_args, &cli_diagnostics); std.os.exit(1); }, else => |e| return e, }; try options.maybeAppendRC(std.fs.cwd()); - // print any warnings/notes - cli_diagnostics.renderToStdErr(args, stderr_config); - // If there was something printed, then add an extra newline separator - // so that there is a clear separation between the cli diagnostics and whatever - // gets printed after - if (cli_diagnostics.errors.items.len > 0) { - try stderr.writeAll("\n"); + if (!zig_integration) { + // print any warnings/notes + cli_diagnostics.renderToStdErr(args, stderr_config); + // If there was something printed, then add an extra newline separator + // so that there is a clear separation between the cli diagnostics and whatever + // gets printed after + if (cli_diagnostics.errors.items.len > 0) { + try stderr.writeAll("\n"); + } } break :options options; }; @@ -55,6 +77,9 @@ pub fn main() !void { return; } + // Don't allow verbose when integrating with Zig via stdout + options.verbose = false; + const stdout_writer = std.io.getStdOut().writer(); if (options.verbose) { try options.dumpVerbose(stdout_writer); @@ -86,13 +111,13 @@ pub fn main() !void { else => |e| { switch (e) { error.MsvcIncludesNotFound => { - try renderErrorMessage(stderr.writer(), stderr_config, .err, "MSVC include paths could not be automatically detected", .{}); + try error_handler.emitMessage(allocator, .err, "MSVC include paths could not be automatically detected", .{}); }, error.MingwIncludesNotFound => { - try renderErrorMessage(stderr.writer(), stderr_config, .err, "MinGW include paths could not be automatically detected", .{}); + try error_handler.emitMessage(allocator, .err, "MinGW include paths could not be automatically detected", .{}); }, } - try renderErrorMessage(stderr.writer(), stderr_config, .note, "to disable auto includes, use the option /:auto-includes none", .{}); + try error_handler.emitMessage(allocator, .note, "to disable auto includes, use the option /:auto-includes none", .{}); std.os.exit(1); }, }; @@ -117,20 +142,16 @@ pub fn main() !void { preprocess.preprocess(&comp, preprocessed_buf.writer(), argv.items, maybe_dependencies_list) catch |err| switch (err) { error.GeneratedSourceError => { - // extra newline to separate this line from the aro errors - try renderErrorMessage(stderr.writer(), stderr_config, .err, "failed during preprocessor setup (this is always a bug):\n", .{}); - aro.Diagnostics.render(&comp, stderr_config); + try error_handler.emitAroDiagnostics(allocator, "failed during preprocessor setup (this is always a bug):", &comp); std.os.exit(1); }, // ArgError can occur if e.g. the .rc file is not found error.ArgError, error.PreprocessError => { - // extra newline to separate this line from the aro errors - try renderErrorMessage(stderr.writer(), stderr_config, .err, "failed during preprocessing:\n", .{}); - aro.Diagnostics.render(&comp, stderr_config); + try error_handler.emitAroDiagnostics(allocator, "failed during preprocessing:", &comp); std.os.exit(1); }, error.StreamTooLong => { - try renderErrorMessage(stderr.writer(), stderr_config, .err, "failed during preprocessing: maximum file size exceeded", .{}); + try error_handler.emitMessage(allocator, .err, "failed during preprocessing: maximum file size exceeded", .{}); std.os.exit(1); }, error.OutOfMemory => |e| return e, @@ -139,7 +160,7 @@ pub fn main() !void { break :full_input try preprocessed_buf.toOwnedSlice(); } else { break :full_input std.fs.cwd().readFileAlloc(allocator, options.input_filename, std.math.maxInt(usize)) catch |err| { - try renderErrorMessage(stderr.writer(), stderr_config, .err, "unable to read input file path '{s}': {s}", .{ options.input_filename, @errorName(err) }); + try error_handler.emitMessage(allocator, .err, "unable to read input file path '{s}': {s}", .{ options.input_filename, @errorName(err) }); std.os.exit(1); }; } @@ -159,14 +180,14 @@ pub fn main() !void { const final_input = removeComments(mapping_results.result, mapping_results.result, &mapping_results.mappings) catch |err| switch (err) { error.InvalidSourceMappingCollapse => { - try renderErrorMessage(stderr.writer(), stderr_config, .err, "failed during comment removal; this is a known bug", .{}); + try error_handler.emitMessage(allocator, .err, "failed during comment removal; this is a known bug", .{}); std.os.exit(1); }, else => |e| return e, }; var output_file = std.fs.cwd().createFile(options.output_filename, .{}) catch |err| { - try renderErrorMessage(stderr.writer(), stderr_config, .err, "unable to create output file '{s}': {s}", .{ options.output_filename, @errorName(err) }); + try error_handler.emitMessage(allocator, .err, "unable to create output file '{s}': {s}", .{ options.output_filename, @errorName(err) }); std.os.exit(1); }; var output_file_closed = false; @@ -193,7 +214,7 @@ pub fn main() !void { .warn_instead_of_error_on_invalid_code_page = options.warn_instead_of_error_on_invalid_code_page, }) catch |err| switch (err) { error.ParseError, error.CompileError => { - diagnostics.renderToStdErr(std.fs.cwd(), final_input, stderr_config, mapping_results.mappings); + try error_handler.emitDiagnostics(allocator, std.fs.cwd(), final_input, &diagnostics, mapping_results.mappings); // Delete the output file on error output_file.close(); output_file_closed = true; @@ -207,12 +228,14 @@ pub fn main() !void { try output_buffered_stream.flush(); // print any warnings/notes - diagnostics.renderToStdErr(std.fs.cwd(), final_input, stderr_config, mapping_results.mappings); + if (!zig_integration) { + diagnostics.renderToStdErr(std.fs.cwd(), final_input, stderr_config, mapping_results.mappings); + } // write the depfile if (options.depfile_path) |depfile_path| { var depfile = std.fs.cwd().createFile(depfile_path, .{}) catch |err| { - try renderErrorMessage(stderr.writer(), stderr_config, .err, "unable to create depfile '{s}': {s}", .{ depfile_path, @errorName(err) }); + try error_handler.emitMessage(allocator, .err, "unable to create depfile '{s}': {s}", .{ depfile_path, @errorName(err) }); std.os.exit(1); }; defer depfile.close(); @@ -296,3 +319,390 @@ fn getIncludePaths(arena: std.mem.Allocator, auto_includes_option: cli.Options.A } } } + +const ErrorBundle = std.zig.ErrorBundle; +const SourceMappings = @import("source_mapping.zig").SourceMappings; + +const ErrorHandler = union(enum) { + server: std.zig.Server, + tty: std.io.tty.Config, + + pub fn emitCliDiagnostics( + self: *ErrorHandler, + allocator: std.mem.Allocator, + args: []const []const u8, + diagnostics: *cli.Diagnostics, + ) !void { + switch (self.*) { + .server => |*server| { + var error_bundle = try cliDiagnosticsToErrorBundle(allocator, diagnostics); + defer error_bundle.deinit(allocator); + + try server.serveErrorBundle(error_bundle); + }, + .tty => { + diagnostics.renderToStdErr(args, self.tty); + }, + } + } + + pub fn emitAroDiagnostics( + self: *ErrorHandler, + allocator: std.mem.Allocator, + fail_msg: []const u8, + comp: *aro.Compilation, + ) !void { + switch (self.*) { + .server => |*server| { + var error_bundle = try aroDiagnosticsToErrorBundle(allocator, fail_msg, comp); + defer error_bundle.deinit(allocator); + + try server.serveErrorBundle(error_bundle); + }, + .tty => { + // extra newline to separate this line from the aro errors + try renderErrorMessage(std.io.getStdErr().writer(), self.tty, .err, "{s}\n", .{fail_msg}); + aro.Diagnostics.render(comp, self.tty); + }, + } + } + + pub fn emitDiagnostics( + self: *ErrorHandler, + allocator: std.mem.Allocator, + cwd: std.fs.Dir, + source: []const u8, + diagnostics: *Diagnostics, + mappings: SourceMappings, + ) !void { + switch (self.*) { + .server => |*server| { + var error_bundle = try diagnosticsToErrorBundle(allocator, source, diagnostics, mappings); + defer error_bundle.deinit(allocator); + + try server.serveErrorBundle(error_bundle); + }, + .tty => { + diagnostics.renderToStdErr(cwd, source, self.tty, mappings); + }, + } + } + + pub fn emitMessage( + self: *ErrorHandler, + allocator: std.mem.Allocator, + msg_type: @import("utils.zig").ErrorMessageType, + comptime format: []const u8, + args: anytype, + ) !void { + switch (self.*) { + .server => |*server| { + // only emit errors + if (msg_type != .err) return; + + var error_bundle = try errorStringToErrorBundle(allocator, format, args); + defer error_bundle.deinit(allocator); + + try server.serveErrorBundle(error_bundle); + }, + .tty => { + try renderErrorMessage(std.io.getStdErr().writer(), self.tty, msg_type, format, args); + }, + } + } +}; + +fn cliDiagnosticsToErrorBundle( + gpa: std.mem.Allocator, + diagnostics: *cli.Diagnostics, +) !ErrorBundle { + @setCold(true); + + var bundle: ErrorBundle.Wip = undefined; + try bundle.init(gpa); + errdefer bundle.deinit(); + + try bundle.addRootErrorMessage(.{ + .msg = try bundle.addString("invalid command line option(s)"), + }); + + var cur_err: ?ErrorBundle.ErrorMessage = null; + var cur_notes: std.ArrayListUnmanaged(ErrorBundle.ErrorMessage) = .{}; + defer cur_notes.deinit(gpa); + for (diagnostics.errors.items) |err_details| { + switch (err_details.type) { + .err => { + if (cur_err) |err| { + try flushErrorMessageIntoBundle(&bundle, err, cur_notes.items); + } + cur_err = .{ + .msg = try bundle.addString(err_details.msg.items), + }; + cur_notes.clearRetainingCapacity(); + }, + .warning => cur_err = null, + .note => { + if (cur_err == null) continue; + cur_err.?.notes_len += 1; + try cur_notes.append(gpa, .{ + .msg = try bundle.addString(err_details.msg.items), + }); + }, + } + } + if (cur_err) |err| { + try flushErrorMessageIntoBundle(&bundle, err, cur_notes.items); + } + + return try bundle.toOwnedBundle(""); +} + +fn diagnosticsToErrorBundle( + gpa: std.mem.Allocator, + source: []const u8, + diagnostics: *Diagnostics, + mappings: SourceMappings, +) !ErrorBundle { + @setCold(true); + + var bundle: ErrorBundle.Wip = undefined; + try bundle.init(gpa); + errdefer bundle.deinit(); + + var msg_buf: std.ArrayListUnmanaged(u8) = .{}; + defer msg_buf.deinit(gpa); + var cur_err: ?ErrorBundle.ErrorMessage = null; + var cur_notes: std.ArrayListUnmanaged(ErrorBundle.ErrorMessage) = .{}; + defer cur_notes.deinit(gpa); + for (diagnostics.errors.items) |err_details| { + switch (err_details.type) { + .hint => continue, + // Clear the current error so that notes don't bleed into unassociated errors + .warning => { + cur_err = null; + continue; + }, + .note => if (cur_err == null) continue, + .err => {}, + } + const corresponding_span = mappings.getCorrespondingSpan(err_details.token.line_number).?; + const err_line = corresponding_span.start_line; + const err_filename = mappings.files.get(corresponding_span.filename_offset); + + const source_line_start = err_details.token.getLineStartForErrorDisplay(source); + // Treat tab stops as 1 column wide for error display purposes, + // and add one to get a 1-based column + const column = err_details.token.calculateColumn(source, 1, source_line_start) + 1; + + msg_buf.clearRetainingCapacity(); + try err_details.render(msg_buf.writer(gpa), source, diagnostics.strings.items); + + const src_loc = src_loc: { + var src_loc: ErrorBundle.SourceLocation = .{ + .src_path = try bundle.addString(err_filename), + .line = @intCast(err_line - 1), // 1-based -> 0-based + .column = @intCast(column - 1), // 1-based -> 0-based + .span_start = 0, + .span_main = 0, + .span_end = 0, + }; + if (err_details.print_source_line) { + const source_line = err_details.token.getLineForErrorDisplay(source, source_line_start); + const visual_info = err_details.visualTokenInfo(source_line_start, source_line_start + source_line.len); + src_loc.span_start = @intCast(visual_info.point_offset - visual_info.before_len); + src_loc.span_main = @intCast(visual_info.point_offset); + src_loc.span_end = @intCast(visual_info.point_offset + 1 + visual_info.after_len); + src_loc.source_line = try bundle.addString(source_line); + } + break :src_loc try bundle.addSourceLocation(src_loc); + }; + + switch (err_details.type) { + .err => { + if (cur_err) |err| { + try flushErrorMessageIntoBundle(&bundle, err, cur_notes.items); + } + cur_err = .{ + .msg = try bundle.addString(msg_buf.items), + .src_loc = src_loc, + }; + cur_notes.clearRetainingCapacity(); + }, + .note => { + cur_err.?.notes_len += 1; + try cur_notes.append(gpa, .{ + .msg = try bundle.addString(msg_buf.items), + .src_loc = src_loc, + }); + }, + .warning, .hint => unreachable, + } + } + if (cur_err) |err| { + try flushErrorMessageIntoBundle(&bundle, err, cur_notes.items); + } + + return try bundle.toOwnedBundle(""); +} + +fn flushErrorMessageIntoBundle(wip: *ErrorBundle.Wip, msg: ErrorBundle.ErrorMessage, notes: []const ErrorBundle.ErrorMessage) !void { + try wip.addRootErrorMessage(msg); + const notes_start = try wip.reserveNotes(@intCast(notes.len)); + for (notes_start.., notes) |i, note| { + wip.extra.items[i] = @intFromEnum(wip.addErrorMessageAssumeCapacity(note)); + } +} + +fn errorStringToErrorBundle(allocator: std.mem.Allocator, comptime format: []const u8, args: anytype) !ErrorBundle { + @setCold(true); + var bundle: ErrorBundle.Wip = undefined; + try bundle.init(allocator); + errdefer bundle.deinit(); + try bundle.addRootErrorMessage(.{ + .msg = try bundle.printString(format, args), + }); + return try bundle.toOwnedBundle(""); +} + +fn aroDiagnosticsToErrorBundle( + gpa: std.mem.Allocator, + fail_msg: []const u8, + comp: *aro.Compilation, +) !ErrorBundle { + @setCold(true); + + var bundle: ErrorBundle.Wip = undefined; + try bundle.init(gpa); + errdefer bundle.deinit(); + + try bundle.addRootErrorMessage(.{ + .msg = try bundle.addString(fail_msg), + }); + + var msg_writer = MsgWriter.init(gpa); + defer msg_writer.deinit(); + var cur_err: ?ErrorBundle.ErrorMessage = null; + var cur_notes: std.ArrayListUnmanaged(ErrorBundle.ErrorMessage) = .{}; + defer cur_notes.deinit(gpa); + for (comp.diagnostics.list.items) |msg| { + switch (msg.kind) { + // Clear the current error so that notes don't bleed into unassociated errors + .off, .warning => { + cur_err = null; + continue; + }, + .note => if (cur_err == null) continue, + .@"fatal error", .@"error" => {}, + .default => unreachable, + } + msg_writer.resetRetainingCapacity(); + aro.Diagnostics.renderMessage(comp, &msg_writer, msg); + + const src_loc = src_loc: { + if (msg_writer.path) |src_path| { + var src_loc: ErrorBundle.SourceLocation = .{ + .src_path = try bundle.addString(src_path), + .line = msg_writer.line - 1, // 1-based -> 0-based + .column = msg_writer.col - 1, // 1-based -> 0-based + .span_start = 0, + .span_main = 0, + .span_end = 0, + }; + if (msg_writer.source_line) |source_line| { + src_loc.span_start = msg_writer.span_main - 1; // 1-based -> 0-based + src_loc.span_main = msg_writer.span_main - 1; // 1-based -> 0-based + src_loc.span_end = msg_writer.span_main; // 1-based -> 0-based + src_loc.source_line = try bundle.addString(source_line); + } + break :src_loc try bundle.addSourceLocation(src_loc); + } + break :src_loc ErrorBundle.SourceLocationIndex.none; + }; + + switch (msg.kind) { + .@"fatal error", .@"error" => { + if (cur_err) |err| { + try flushErrorMessageIntoBundle(&bundle, err, cur_notes.items); + } + cur_err = .{ + .msg = try bundle.addString(msg_writer.buf.items), + .src_loc = src_loc, + }; + cur_notes.clearRetainingCapacity(); + }, + .note => { + cur_err.?.notes_len += 1; + try cur_notes.append(gpa, .{ + .msg = try bundle.addString(msg_writer.buf.items), + .src_loc = src_loc, + }); + }, + .off, .warning, .default => unreachable, + } + } + if (cur_err) |err| { + try flushErrorMessageIntoBundle(&bundle, err, cur_notes.items); + } + + return try bundle.toOwnedBundle(""); +} + +// Similar to aro.Diagnostics.MsgWriter but: +// - Writers to an ArrayList +// - Only prints the message itself (no location, source line, error: prefix, etc) +// - Keeps track of source path/line/col instead +const MsgWriter = struct { + buf: std.ArrayList(u8), + path: ?[]const u8 = null, + // 1-indexed + line: u32 = undefined, + col: u32 = undefined, + source_line: ?[]const u8 = null, + span_main: u32 = undefined, + + fn init(allocator: std.mem.Allocator) MsgWriter { + return .{ + .buf = std.ArrayList(u8).init(allocator), + }; + } + + fn deinit(m: *MsgWriter) void { + m.buf.deinit(); + } + + fn resetRetainingCapacity(m: *MsgWriter) void { + m.buf.clearRetainingCapacity(); + m.path = null; + m.source_line = null; + } + + pub fn print(m: *MsgWriter, comptime fmt: []const u8, args: anytype) void { + m.buf.writer().print(fmt, args) catch {}; + } + + pub fn write(m: *MsgWriter, msg: []const u8) void { + m.buf.writer().writeAll(msg) catch {}; + } + + pub fn setColor(m: *MsgWriter, color: std.io.tty.Color) void { + _ = m; + _ = color; + } + + pub fn location(m: *MsgWriter, path: []const u8, line: u32, col: u32) void { + m.path = path; + m.line = line; + m.col = col; + } + + pub fn start(m: *MsgWriter, kind: aro.Diagnostics.Kind) void { + _ = m; + _ = kind; + } + + pub fn end(m: *MsgWriter, maybe_line: ?[]const u8, col: u32, end_with_splice: bool) void { + _ = end_with_splice; + m.source_line = maybe_line; + m.span_main = col; + } +}; diff --git a/lib/compiler/resinator/utils.zig b/lib/compiler/resinator/utils.zig index d216f07838fd..b69222fe0373 100644 --- a/lib/compiler/resinator/utils.zig +++ b/lib/compiler/resinator/utils.zig @@ -82,9 +82,11 @@ pub fn isNonAsciiDigit(c: u21) bool { }; } +pub const ErrorMessageType = enum { err, warning, note }; + /// Used for generic colored errors/warnings/notes, more context-specific error messages /// are handled elsewhere. -pub fn renderErrorMessage(writer: anytype, config: std.io.tty.Config, msg_type: enum { err, warning, note }, comptime format: []const u8, args: anytype) !void { +pub fn renderErrorMessage(writer: anytype, config: std.io.tty.Config, msg_type: ErrorMessageType, comptime format: []const u8, args: anytype) !void { switch (msg_type) { .err => { try config.setColor(writer, .bold); diff --git a/src/Compilation.zig b/src/Compilation.zig index 93d09752204d..d5c14d7ba8ad 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -4683,6 +4683,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 try argv.appendSlice(&.{ self_exe_path, "rc", + "--zig-integration", "/:depfile", out_dep_path, "/:depfile-fmt", @@ -4702,30 +4703,78 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 try argv.appendSlice(rc_src.extra_flags); try argv.appendSlice(&.{ "--", rc_src.src_path, out_res_path }); - var child = std.ChildProcess.init(argv.items, arena); - child.stdin_behavior = .Ignore; - child.stdout_behavior = .Ignore; - child.stderr_behavior = .Pipe; + { + var child = std.ChildProcess.init(argv.items, arena); + child.stdin_behavior = .Ignore; + child.stdout_behavior = .Pipe; + child.stderr_behavior = .Pipe; - try child.spawn(); + child.spawn() catch |err| { + return comp.failWin32Resource(win32_resource, "unable to spawn {s} rc: {s}", .{ argv.items[0], @errorName(err) }); + }; - const stderr_reader = child.stderr.?.reader(); - const stderr = try stderr_reader.readAllAlloc(arena, 10 * 1024 * 1024); - const term = child.wait() catch |err| { - return comp.failWin32Resource(win32_resource, "unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); - }; + var poller = std.io.poll(comp.gpa, enum { stdout }, .{ + .stdout = child.stdout.?, + }); + defer poller.deinit(); - switch (term) { - .Exited => |code| { - if (code != 0) { - log.err("zig rc failed with stderr:\n{s}", .{stderr}); - return comp.failWin32Resource(win32_resource, "zig rc exited with code {d}", .{code}); + const stdout = poller.fifo(.stdout); + + poll: while (true) { + while (stdout.readableLength() < @sizeOf(std.zig.Server.Message.Header)) { + if (!(try poller.poll())) break :poll; } - }, - else => { - log.err("zig rc terminated with stderr:\n{s}", .{stderr}); - return comp.failWin32Resource(win32_resource, "zig rc terminated unexpectedly", .{}); - }, + const header = stdout.reader().readStruct(std.zig.Server.Message.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) { + // We expect exactly one ErrorBundle, and if any error_bundle header is + // sent then it's a fatal error. + .error_bundle => { + const EbHdr = std.zig.Server.Message.ErrorBundle; + const eb_hdr = @as(*align(1) const EbHdr, @ptrCast(body)); + const extra_bytes = + body[@sizeOf(EbHdr)..][0 .. @sizeOf(u32) * eb_hdr.extra_len]; + const string_bytes = + body[@sizeOf(EbHdr) + extra_bytes.len ..][0..eb_hdr.string_bytes_len]; + const unaligned_extra = std.mem.bytesAsSlice(u32, extra_bytes); + const extra_array = try comp.gpa.alloc(u32, unaligned_extra.len); + @memcpy(extra_array, unaligned_extra); + const error_bundle = .{ + .string_bytes = try comp.gpa.dupe(u8, string_bytes), + .extra = extra_array, + }; + return comp.failWin32ResourceWithOwnedBundle(win32_resource, error_bundle); + }, + else => {}, // ignore other messages + } + + stdout.discard(body.len); + } + + // Just in case there's a failure that didn't send an ErrorBundle (e.g. an error return trace) + const stderr_reader = child.stderr.?.reader(); + const stderr = try stderr_reader.readAllAlloc(arena, 10 * 1024 * 1024); + + const term = child.wait() catch |err| { + return comp.failWin32Resource(win32_resource, "unable to wait for {s} rc: {s}", .{ argv.items[0], @errorName(err) }); + }; + + switch (term) { + .Exited => |code| { + if (code != 0) { + log.err("zig rc failed with stderr:\n{s}", .{stderr}); + return comp.failWin32Resource(win32_resource, "zig rc exited with code {d}", .{code}); + } + }, + else => { + log.err("zig rc terminated with stderr:\n{s}", .{stderr}); + return comp.failWin32Resource(win32_resource, "zig rc terminated unexpectedly", .{}); + }, + } } // Read depfile and update cache manifest