authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-09-25 11:47:25-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-09-25 11:47:25-07:00
logd09c1aef02788bcea0a3e3b0f7507c4f3e823295
treed2b6e2868baddb5f0e7fe79b326d31a900d09c1c
parent7cef4755c33ca77137fb7e4d83146e27b5703685

maker: serialize compilation errors of configurer over the build protocol

serves an ErrorBundle as a configuration_failed message instead of configuration message upon connection. at this point the protocol enters an invalid state and the process will need to be restarted in order to reconfigure. in the future, build server should outlive reconfigurations. closes #36389

4 files changed, 54 insertions(+), 68 deletions(-)

lib/compiler/Maker.zig+8-2
......@@ -740,6 +740,9 @@ pub fn main(init: process.Init.Minimal) !void {
740740 var configure_source_files: Cache.Manifest.SelfContainedFiles = .empty;
741741 defer configure_source_files.deinit(gpa);
742742
743 var configure_error_bundle: std.zig.ErrorBundle = .empty;
744 defer configure_error_bundle.deinit(gpa);
745
743746 // If this fails, we can still start the server and wait for user
744747 // to request a rebuild. If it returns error.FailedButCacheIntact
745748 // we can even still do file system watching and automatically
......@@ -762,6 +765,7 @@ pub fn main(init: process.Init.Minimal) !void {
762765 .print_configuration = print_configuration,
763766 .forks = forks.items,
764767 .src_files = &configure_source_files,
768 .error_bundle = if (protocol_server != null) &configure_error_bundle else null,
765769 })) |scanned_config| {
766770 if (help_menu) {
767771 scanned_config.printUsage(&graph, initStdoutWriter(io)) catch |err| switch (err) {
......@@ -1048,8 +1052,8 @@ pub fn main(init: process.Init.Minimal) !void {
10481052 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};
10491053 process.exit(1);
10501054 }
1051 if (protocol_server != null) {
1052 fatal("(zig build system) TODO send error messages to client when build.zig compilation fails", .{});
1055 if (protocol_server) |s| {
1056 try s.serveErrorBundle(.bsp_configuration_failed, configure_error_bundle);
10531057 }
10541058 if (watch_flag and can_fs_watch) {
10551059 fatal("(zig build system) TODO set up fs watching even when build.zig compilation fails", .{});
......@@ -1087,6 +1091,7 @@ const ConfigureOptions = struct {
10871091 print_configuration: PrintConfiguration,
10881092 forks: []Fork,
10891093 src_files: *Cache.Manifest.SelfContainedFiles,
1094 error_bundle: ?*std.zig.ErrorBundle = null,
10901095};
10911096
10921097fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
......@@ -1471,6 +1476,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
14711476 .arch_os_abi = target_arch_os_abi,
14721477 .progress_node = compile_prog_node,
14731478 .skip_log_cmdline_on_compile_errors = !graph.verbose,
1479 .error_bundle = options.error_bundle,
14741480 })) |r| r.path else |err| return err;
14751481 defer gpa.free(configure_exe_path.sub_path);
14761482
lib/std/zig.zig+17-7
......@@ -1713,6 +1713,9 @@ pub const BuildExeSubprocessOptions = struct {
17131713 cpu_features: ?[]const u8 = null,
17141714 progress_node: std.Progress.Node = .none,
17151715 skip_log_cmdline_on_compile_errors: bool = false,
1716 /// If this is provided, compilation errors are sent here. Otherwise, they are printed to stderr.
1717 /// Must be an initialized `ErrorBundle`; if it is updated then it is cleared first.
1718 error_bundle: ?*ErrorBundle = null,
17161719};
17171720
17181721pub const BuildExeSubprocessError = error{
......@@ -1781,8 +1784,10 @@ pub fn buildExeSubprocess(
17811784 var result: ?Cache.Path = null;
17821785 defer if (result) |r| gpa.free(r.sub_path);
17831786
1784 var result_error_bundle: ErrorBundle = .empty;
1785 defer result_error_bundle.deinit(gpa);
1787 var default_error_bundle: ErrorBundle = .empty;
1788 defer default_error_bundle.deinit(gpa);
1789
1790 const error_bundle = options.error_bundle orelse &default_error_bundle;
17861791
17871792 var received_fs_inputs = false;
17881793 var cache_hit = false;
......@@ -1816,8 +1821,8 @@ pub fn buildExeSubprocess(
18161821 }
18171822 },
18181823 .error_bundle => {
1819 result_error_bundle.deinit(gpa);
1820 result_error_bundle = Server.allocErrorBundle(gpa, body) catch |err| switch (err) {
1824 error_bundle.deinit(gpa);
1825 error_bundle.* = Server.allocErrorBundle(gpa, body) catch |err| switch (err) {
18211826 error.EndOfStream => break,
18221827 else => |e| return e,
18231828 };
......@@ -1884,8 +1889,8 @@ pub fn buildExeSubprocess(
18841889 return error.AlreadyReported;
18851890 }
18861891
1887 if (result_error_bundle.errorMessageCount() > 0) {
1888 result_error_bundle.renderToStderr(io, .{}, .auto) catch |err| switch (err) {
1892 if (default_error_bundle.errorMessageCount() > 0) {
1893 default_error_bundle.renderToStderr(io, .{}, .auto) catch |err| switch (err) {
18891894 error.Canceled => |e| return e,
18901895 else => |e| {
18911896 log.err("failed rendering error bundle: {t}", .{e});
......@@ -1893,12 +1898,17 @@ pub fn buildExeSubprocess(
18931898 },
18941899 };
18951900 if (!options.skip_log_cmdline_on_compile_errors) log.err("command reported {d} compilation errors: {f}", .{
1896 result_error_bundle.errorMessageCount(), cmd,
1901 default_error_bundle.errorMessageCount(), cmd,
18971902 });
18981903 if (received_fs_inputs) return error.FailedButCacheIntact;
18991904 return error.AlreadyReported;
19001905 }
19011906
1907 if (error_bundle.errorMessageCount() > 0) {
1908 if (received_fs_inputs) return error.FailedButCacheIntact;
1909 return error.AlreadyReported;
1910 }
1911
19021912 const base_path = result orelse {
19031913 log.err("command failed to report result: {f}", .{cmd});
19041914 return error.AlreadyReported;
lib/std/zig/Server.zig+8-22
......@@ -83,6 +83,10 @@ pub const Message = struct {
8383 /// Body is a cwd relative path to the configuration file.
8484 /// This message only applies to the build system protocol.
8585 bsp_configuration,
86 /// build.zig itself failed to build from source.
87 /// Body is an `ErrorBundle`
88 /// This message only applies to the build system protocol.
89 bsp_configuration_failed,
8690 /// Does not have a body.
8791 /// This message only applies to the build system protocol.
8892 bsp_build_started,
......@@ -307,7 +311,7 @@ pub fn serveTestResults(s: *Server, msg: OutMessage.TestResults) !void {
307311 try s.out.flush();
308312}
309313
310pub fn serveErrorBundle(s: *Server, error_bundle: std.zig.ErrorBundle) !void {
314pub fn serveErrorBundle(s: *Server, tag: Message.Tag, error_bundle: std.zig.ErrorBundle) !void {
311315 const eb_hdr: OutMessage.ErrorBundle = .{
312316 .extra_len = @intCast(error_bundle.extra.len),
313317 .string_bytes_len = @intCast(error_bundle.string_bytes.len),
......@@ -315,7 +319,7 @@ pub fn serveErrorBundle(s: *Server, error_bundle: std.zig.ErrorBundle) !void {
315319 const bytes_len = @sizeOf(OutMessage.ErrorBundle) +
316320 4 * error_bundle.extra.len + error_bundle.string_bytes.len;
317321 try s.serveMessageHeader(.{
318 .tag = .error_bundle,
322 .tag = tag,
319323 .bytes_len = @intCast(bytes_len),
320324 });
321325 try s.out.writeStruct(eb_hdr, .little);
......@@ -327,31 +331,13 @@ pub fn serveErrorBundle(s: *Server, error_bundle: std.zig.ErrorBundle) !void {
327331pub fn allocErrorBundle(gpa: Allocator, body: []const u8) error{ OutOfMemory, EndOfStream }!std.zig.ErrorBundle {
328332 var r: Reader = .fixed(body);
329333 const hdr = r.takeStruct(OutMessage.ErrorBundle, .little) catch |err| switch (err) {
330 error.EndOfStream => |e| return e,
331334 error.ReadFailed => unreachable,
332 };
333
334 var eb: std.zig.ErrorBundle = .{
335 .string_bytes = &.{},
336 .extra = &.{},
337 };
338 errdefer eb.deinit(gpa);
339
340 const extra = try gpa.alloc(u32, hdr.extra_len);
341 eb.extra = extra;
342 const string_bytes = try gpa.alloc(u8, hdr.string_bytes_len);
343 eb.string_bytes = string_bytes;
344
345 r.readSliceEndian(u32, extra, .little) catch |err| switch (err) {
346335 error.EndOfStream => |e| return e,
347 error.ReadFailed => unreachable,
348336 };
349 r.readSliceAll(string_bytes) catch |err| switch (err) {
350 error.EndOfStream => |e| return e,
337 return std.zig.ErrorBundle.readAlloc(&r, gpa, hdr.extra_len, hdr.string_bytes_len) catch |err| switch (err) {
351338 error.ReadFailed => unreachable,
339 else => |e| return e,
352340 };
353
354 return eb;
355341}
356342
357343pub const TestMetadata = struct {
tools/bsp.zig+21-37
......@@ -7,6 +7,7 @@ const Configuration = std.Build.Configuration;
77const Client = std.zig.Client;
88const Server = std.zig.Server;
99const log = std.log.scoped(.bsp);
10const panic = std.debug.panic;
1011
1112pub fn main(init: std.process.Init) !void {
1213 const io = init.io;
......@@ -32,7 +33,7 @@ pub fn main(init: std.process.Init) !void {
3233 .stdin = .pipe,
3334 .stdout = .pipe,
3435 .stderr = .pipe,
35 }) catch |err| std.debug.panic("failed to spawn process: {}", .{err});
36 }) catch |err| panic("failed to spawn process: {}", .{err});
3637 errdefer child_process.kill(io);
3738
3839 var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined;
......@@ -66,10 +67,10 @@ pub fn main(init: std.process.Init) !void {
6667 },
6768 };
6869 const body = client_stdout.take(header.bytes_len) catch unreachable;
69 log.debug("received {f} ({d} bytes)", .{ fmtEnum(header.tag), body.len });
70 log.debug("received {} ({d} bytes)", .{ header.tag, body.len });
7071
7172 if (header.tag != .bsp_handshake) {
72 log.err("received unexpected message: {f}", .{fmtEnum(header.tag)});
73 log.err("received unexpected message: {}", .{header.tag});
7374 return error.UnexpectedMessage;
7475 }
7576
......@@ -94,17 +95,26 @@ pub fn main(init: std.process.Init) !void {
9495 const body = client_stdout.take(header.bytes_len) catch unreachable;
9596 log.debug("received {t} ({d} bytes)", .{ header.tag, body.len });
9697
97 if (header.tag != .bsp_configuration) {
98 log.err("received unexpected message: {f}", .{fmtEnum(header.tag)});
99 return error.UnexpectedMessage;
98 switch (header.tag) {
99 .bsp_configuration => {},
100 .bsp_configuration_failed => {
101 var eb = try Server.allocErrorBundle(gpa, body);
102 defer eb.deinit(gpa);
103 try eb.renderToStderr(io, .{}, .auto);
104 panic("configuration failed", .{});
105 },
106 else => {
107 log.err("received unexpected message: {}", .{header.tag});
108 return error.UnexpectedMessage;
109 },
100110 }
101111
102112 const configuration_path = body;
103113 var file = Io.Dir.cwd().openFile(io, configuration_path, .{}) catch |err|
104 std.debug.panic("failed to open configuration file {q}: {t}", .{ configuration_path, err });
114 panic("failed to open configuration file {q}: {t}", .{ configuration_path, err });
105115 defer file.close(io);
106116 break :configuration Configuration.loadFile(conf_arena, io, file) catch |err|
107 std.debug.panic("failed to load configuration file {q}: {t}", .{ configuration_path, err });
117 panic("failed to load configuration file {q}: {t}", .{ configuration_path, err });
108118 };
109119 const c = &configuration;
110120
......@@ -157,7 +167,7 @@ pub fn main(init: std.process.Init) !void {
157167 if (std.fmt.parseInt(u32, arg, 10)) |i|
158168 @fromBackingInt(i)
159169 else |_|
160 top_level_steps.get(arg) orelse std.debug.panic("unexpected step name or index", .{});
170 top_level_steps.get(arg) orelse panic("unexpected step name or index", .{});
161171 try steps.append(gpa, step);
162172 }
163173 }
......@@ -178,7 +188,7 @@ pub fn main(init: std.process.Init) !void {
178188 },
179189 };
180190 const body = client_stdout.take(header.bytes_len) catch unreachable;
181 log.debug("received {f} ({d} bytes)", .{ fmtEnum(header.tag), body.len });
191 log.debug("received {} ({d} bytes)", .{ header.tag, body.len });
182192
183193 switch (header.tag) {
184194 .bsp_build_started => {},
......@@ -214,7 +224,7 @@ pub fn main(init: std.process.Init) !void {
214224 }
215225 },
216226 .bsp_configuration => @panic("TODO"),
217 else => std.debug.panic("received unexpected message: {f}", .{fmtEnum(header.tag)}),
227 else => panic("received unexpected message: {}", .{header.tag}),
218228 }
219229 }
220230 continue;
......@@ -242,29 +252,3 @@ pub fn main(init: std.process.Init) !void {
242252 log.err("maker {f}", .{term});
243253 }
244254}
245
246const FormatEnum = union(enum) {
247 named: []const u8,
248 unnamed: usize,
249
250 pub fn format(
251 e: FormatEnum,
252 writer: *std.Io.Writer,
253 ) std.Io.Writer.Error!void {
254 switch (e) {
255 .named => |name| {
256 try writer.writeByte('.');
257 try writer.writeAll(name);
258 },
259 .unnamed => |number| try writer.print("0x{x}", .{number}),
260 }
261 }
262};
263
264fn fmtEnum(e: anytype) FormatEnum {
265 if (std.enums.tagName(@TypeOf(e), e)) |name| {
266 return .{ .named = name };
267 } else {
268 return .{ .unnamed = @backingInt(e) };
269 }
270}