diff --git a/lib/compiler/Maker.zig b/lib/compiler/Maker.zig index 0a7ea5cb1f5520d4c606f58049c7f1185f43ae09..739ddd0bb8183c8d9040f73fc11c1341054ff828 100644 --- a/lib/compiler/Maker.zig +++ b/lib/compiler/Maker.zig @@ -740,6 +740,9 @@ pub fn main(init: process.Init.Minimal) !void { var configure_source_files: Cache.Manifest.SelfContainedFiles = .empty; defer configure_source_files.deinit(gpa); + var configure_error_bundle: std.zig.ErrorBundle = .empty; + defer configure_error_bundle.deinit(gpa); + // If this fails, we can still start the server and wait for user // to request a rebuild. If it returns error.FailedButCacheIntact // we can even still do file system watching and automatically @@ -762,6 +765,7 @@ pub fn main(init: process.Init.Minimal) !void { .print_configuration = print_configuration, .forks = forks.items, .src_files = &configure_source_files, + .error_bundle = if (protocol_server != null) &configure_error_bundle else null, })) |scanned_config| { if (help_menu) { scanned_config.printUsage(&graph, initStdoutWriter(io)) catch |err| switch (err) { @@ -1048,8 +1052,8 @@ pub fn main(init: process.Init.Minimal) !void { _ = io.lockStderr(&.{}, graph.stderr_mode) catch {}; process.exit(1); } - if (protocol_server != null) { - fatal("(zig build system) TODO send error messages to client when build.zig compilation fails", .{}); + if (protocol_server) |s| { + try s.serveErrorBundle(.bsp_configuration_failed, configure_error_bundle); } if (watch_flag and can_fs_watch) { fatal("(zig build system) TODO set up fs watching even when build.zig compilation fails", .{}); @@ -1087,6 +1091,7 @@ const ConfigureOptions = struct { print_configuration: PrintConfiguration, forks: []Fork, src_files: *Cache.Manifest.SelfContainedFiles, + error_bundle: ?*std.zig.ErrorBundle = null, }; fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { @@ -1471,6 +1476,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig { .arch_os_abi = target_arch_os_abi, .progress_node = compile_prog_node, .skip_log_cmdline_on_compile_errors = !graph.verbose, + .error_bundle = options.error_bundle, })) |r| r.path else |err| return err; defer gpa.free(configure_exe_path.sub_path); diff --git a/lib/std/zig.zig b/lib/std/zig.zig index 57bda0bbfd85ad057dd45dae7b482f226012b2ae..286654f1bd123b8fc6732cc70a5017e970d00f68 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -1713,6 +1713,9 @@ pub const BuildExeSubprocessOptions = struct { cpu_features: ?[]const u8 = null, progress_node: std.Progress.Node = .none, skip_log_cmdline_on_compile_errors: bool = false, + /// If this is provided, compilation errors are sent here. Otherwise, they are printed to stderr. + /// Must be an initialized `ErrorBundle`; if it is updated then it is cleared first. + error_bundle: ?*ErrorBundle = null, }; pub const BuildExeSubprocessError = error{ @@ -1781,8 +1784,10 @@ pub fn buildExeSubprocess( var result: ?Cache.Path = null; defer if (result) |r| gpa.free(r.sub_path); - var result_error_bundle: ErrorBundle = .empty; - defer result_error_bundle.deinit(gpa); + var default_error_bundle: ErrorBundle = .empty; + defer default_error_bundle.deinit(gpa); + + const error_bundle = options.error_bundle orelse &default_error_bundle; var received_fs_inputs = false; var cache_hit = false; @@ -1816,8 +1821,8 @@ pub fn buildExeSubprocess( } }, .error_bundle => { - result_error_bundle.deinit(gpa); - result_error_bundle = Server.allocErrorBundle(gpa, body) catch |err| switch (err) { + error_bundle.deinit(gpa); + error_bundle.* = Server.allocErrorBundle(gpa, body) catch |err| switch (err) { error.EndOfStream => break, else => |e| return e, }; @@ -1884,8 +1889,8 @@ pub fn buildExeSubprocess( return error.AlreadyReported; } - if (result_error_bundle.errorMessageCount() > 0) { - result_error_bundle.renderToStderr(io, .{}, .auto) catch |err| switch (err) { + if (default_error_bundle.errorMessageCount() > 0) { + default_error_bundle.renderToStderr(io, .{}, .auto) catch |err| switch (err) { error.Canceled => |e| return e, else => |e| { log.err("failed rendering error bundle: {t}", .{e}); @@ -1893,12 +1898,17 @@ pub fn buildExeSubprocess( }, }; if (!options.skip_log_cmdline_on_compile_errors) log.err("command reported {d} compilation errors: {f}", .{ - result_error_bundle.errorMessageCount(), cmd, + default_error_bundle.errorMessageCount(), cmd, }); if (received_fs_inputs) return error.FailedButCacheIntact; return error.AlreadyReported; } + if (error_bundle.errorMessageCount() > 0) { + if (received_fs_inputs) return error.FailedButCacheIntact; + return error.AlreadyReported; + } + const base_path = result orelse { log.err("command failed to report result: {f}", .{cmd}); return error.AlreadyReported; diff --git a/lib/std/zig/Server.zig b/lib/std/zig/Server.zig index a96f186f4460c585bffb9fc8cd42f030675d172c..4c7e3a85441554dba1a6fe6d92b530d90edcbf39 100644 --- a/lib/std/zig/Server.zig +++ b/lib/std/zig/Server.zig @@ -83,6 +83,10 @@ pub const Message = struct { /// Body is a cwd relative path to the configuration file. /// This message only applies to the build system protocol. bsp_configuration, + /// build.zig itself failed to build from source. + /// Body is an `ErrorBundle` + /// This message only applies to the build system protocol. + bsp_configuration_failed, /// Does not have a body. /// This message only applies to the build system protocol. bsp_build_started, @@ -307,7 +311,7 @@ pub fn serveTestResults(s: *Server, msg: OutMessage.TestResults) !void { try s.out.flush(); } -pub fn serveErrorBundle(s: *Server, error_bundle: std.zig.ErrorBundle) !void { +pub fn serveErrorBundle(s: *Server, tag: Message.Tag, error_bundle: std.zig.ErrorBundle) !void { const eb_hdr: OutMessage.ErrorBundle = .{ .extra_len = @intCast(error_bundle.extra.len), .string_bytes_len = @intCast(error_bundle.string_bytes.len), @@ -315,7 +319,7 @@ pub fn serveErrorBundle(s: *Server, error_bundle: std.zig.ErrorBundle) !void { const bytes_len = @sizeOf(OutMessage.ErrorBundle) + 4 * error_bundle.extra.len + error_bundle.string_bytes.len; try s.serveMessageHeader(.{ - .tag = .error_bundle, + .tag = tag, .bytes_len = @intCast(bytes_len), }); try s.out.writeStruct(eb_hdr, .little); @@ -327,31 +331,13 @@ pub fn serveErrorBundle(s: *Server, error_bundle: std.zig.ErrorBundle) !void { pub fn allocErrorBundle(gpa: Allocator, body: []const u8) error{ OutOfMemory, EndOfStream }!std.zig.ErrorBundle { var r: Reader = .fixed(body); const hdr = r.takeStruct(OutMessage.ErrorBundle, .little) catch |err| switch (err) { - error.EndOfStream => |e| return e, error.ReadFailed => unreachable, - }; - - var eb: std.zig.ErrorBundle = .{ - .string_bytes = &.{}, - .extra = &.{}, - }; - errdefer eb.deinit(gpa); - - const extra = try gpa.alloc(u32, hdr.extra_len); - eb.extra = extra; - const string_bytes = try gpa.alloc(u8, hdr.string_bytes_len); - eb.string_bytes = string_bytes; - - r.readSliceEndian(u32, extra, .little) catch |err| switch (err) { error.EndOfStream => |e| return e, - error.ReadFailed => unreachable, }; - r.readSliceAll(string_bytes) catch |err| switch (err) { - error.EndOfStream => |e| return e, + return std.zig.ErrorBundle.readAlloc(&r, gpa, hdr.extra_len, hdr.string_bytes_len) catch |err| switch (err) { error.ReadFailed => unreachable, + else => |e| return e, }; - - return eb; } pub const TestMetadata = struct { diff --git a/tools/bsp.zig b/tools/bsp.zig index e58205fd958ad569ec76fbf4703671546d0062d5..3db201ea51b499f4246f65d80ed88e9cb188bc43 100644 --- a/tools/bsp.zig +++ b/tools/bsp.zig @@ -7,6 +7,7 @@ const Configuration = std.Build.Configuration; const Client = std.zig.Client; const Server = std.zig.Server; const log = std.log.scoped(.bsp); +const panic = std.debug.panic; pub fn main(init: std.process.Init) !void { const io = init.io; @@ -32,7 +33,7 @@ pub fn main(init: std.process.Init) !void { .stdin = .pipe, .stdout = .pipe, .stderr = .pipe, - }) catch |err| std.debug.panic("failed to spawn process: {}", .{err}); + }) catch |err| panic("failed to spawn process: {}", .{err}); errdefer child_process.kill(io); var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined; @@ -66,10 +67,10 @@ pub fn main(init: std.process.Init) !void { }, }; const body = client_stdout.take(header.bytes_len) catch unreachable; - log.debug("received {f} ({d} bytes)", .{ fmtEnum(header.tag), body.len }); + log.debug("received {} ({d} bytes)", .{ header.tag, body.len }); if (header.tag != .bsp_handshake) { - log.err("received unexpected message: {f}", .{fmtEnum(header.tag)}); + log.err("received unexpected message: {}", .{header.tag}); return error.UnexpectedMessage; } @@ -94,17 +95,26 @@ pub fn main(init: std.process.Init) !void { const body = client_stdout.take(header.bytes_len) catch unreachable; log.debug("received {t} ({d} bytes)", .{ header.tag, body.len }); - if (header.tag != .bsp_configuration) { - log.err("received unexpected message: {f}", .{fmtEnum(header.tag)}); - return error.UnexpectedMessage; + switch (header.tag) { + .bsp_configuration => {}, + .bsp_configuration_failed => { + var eb = try Server.allocErrorBundle(gpa, body); + defer eb.deinit(gpa); + try eb.renderToStderr(io, .{}, .auto); + panic("configuration failed", .{}); + }, + else => { + log.err("received unexpected message: {}", .{header.tag}); + return error.UnexpectedMessage; + }, } const configuration_path = body; var file = Io.Dir.cwd().openFile(io, configuration_path, .{}) catch |err| - std.debug.panic("failed to open configuration file {q}: {t}", .{ configuration_path, err }); + panic("failed to open configuration file {q}: {t}", .{ configuration_path, err }); defer file.close(io); break :configuration Configuration.loadFile(conf_arena, io, file) catch |err| - std.debug.panic("failed to load configuration file {q}: {t}", .{ configuration_path, err }); + panic("failed to load configuration file {q}: {t}", .{ configuration_path, err }); }; const c = &configuration; @@ -157,7 +167,7 @@ pub fn main(init: std.process.Init) !void { if (std.fmt.parseInt(u32, arg, 10)) |i| @fromBackingInt(i) else |_| - top_level_steps.get(arg) orelse std.debug.panic("unexpected step name or index", .{}); + top_level_steps.get(arg) orelse panic("unexpected step name or index", .{}); try steps.append(gpa, step); } } @@ -178,7 +188,7 @@ pub fn main(init: std.process.Init) !void { }, }; const body = client_stdout.take(header.bytes_len) catch unreachable; - log.debug("received {f} ({d} bytes)", .{ fmtEnum(header.tag), body.len }); + log.debug("received {} ({d} bytes)", .{ header.tag, body.len }); switch (header.tag) { .bsp_build_started => {}, @@ -214,7 +224,7 @@ pub fn main(init: std.process.Init) !void { } }, .bsp_configuration => @panic("TODO"), - else => std.debug.panic("received unexpected message: {f}", .{fmtEnum(header.tag)}), + else => panic("received unexpected message: {}", .{header.tag}), } } continue; @@ -242,29 +252,3 @@ pub fn main(init: std.process.Init) !void { log.err("maker {f}", .{term}); } } - -const FormatEnum = union(enum) { - named: []const u8, - unnamed: usize, - - pub fn format( - e: FormatEnum, - writer: *std.Io.Writer, - ) std.Io.Writer.Error!void { - switch (e) { - .named => |name| { - try writer.writeByte('.'); - try writer.writeAll(name); - }, - .unnamed => |number| try writer.print("0x{x}", .{number}), - } - } -}; - -fn fmtEnum(e: anytype) FormatEnum { - if (std.enums.tagName(@TypeOf(e), e)) |name| { - return .{ .named = name }; - } else { - return .{ .unnamed = @backingInt(e) }; - } -}