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 {...@@ -740,6 +740,9 @@ pub fn main(init: process.Init.Minimal) !void {
740 var configure_source_files: Cache.Manifest.SelfContainedFiles = .empty;740 var configure_source_files: Cache.Manifest.SelfContainedFiles = .empty;
741 defer configure_source_files.deinit(gpa);741 defer configure_source_files.deinit(gpa);
742742
743 var configure_error_bundle: std.zig.ErrorBundle = .empty;
744 defer configure_error_bundle.deinit(gpa);
745
743 // If this fails, we can still start the server and wait for user746 // If this fails, we can still start the server and wait for user
744 // to request a rebuild. If it returns error.FailedButCacheIntact747 // to request a rebuild. If it returns error.FailedButCacheIntact
745 // we can even still do file system watching and automatically748 // we can even still do file system watching and automatically
...@@ -762,6 +765,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -762,6 +765,7 @@ pub fn main(init: process.Init.Minimal) !void {
762 .print_configuration = print_configuration,765 .print_configuration = print_configuration,
763 .forks = forks.items,766 .forks = forks.items,
764 .src_files = &configure_source_files,767 .src_files = &configure_source_files,
768 .error_bundle = if (protocol_server != null) &configure_error_bundle else null,
765 })) |scanned_config| {769 })) |scanned_config| {
766 if (help_menu) {770 if (help_menu) {
767 scanned_config.printUsage(&graph, initStdoutWriter(io)) catch |err| switch (err) {771 scanned_config.printUsage(&graph, initStdoutWriter(io)) catch |err| switch (err) {
...@@ -1048,8 +1052,8 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -1048,8 +1052,8 @@ pub fn main(init: process.Init.Minimal) !void {
1048 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};1052 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};
1049 process.exit(1);1053 process.exit(1);
1050 }1054 }
1051 if (protocol_server != null) {1055 if (protocol_server) |s| {
1052 fatal("(zig build system) TODO send error messages to client when build.zig compilation fails", .{});1056 try s.serveErrorBundle(.bsp_configuration_failed, configure_error_bundle);
1053 }1057 }
1054 if (watch_flag and can_fs_watch) {1058 if (watch_flag and can_fs_watch) {
1055 fatal("(zig build system) TODO set up fs watching even when build.zig compilation fails", .{});1059 fatal("(zig build system) TODO set up fs watching even when build.zig compilation fails", .{});
...@@ -1087,6 +1091,7 @@ const ConfigureOptions = struct {...@@ -1087,6 +1091,7 @@ const ConfigureOptions = struct {
1087 print_configuration: PrintConfiguration,1091 print_configuration: PrintConfiguration,
1088 forks: []Fork,1092 forks: []Fork,
1089 src_files: *Cache.Manifest.SelfContainedFiles,1093 src_files: *Cache.Manifest.SelfContainedFiles,
1094 error_bundle: ?*std.zig.ErrorBundle = null,
1090};1095};
10911096
1092fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {1097fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
...@@ -1471,6 +1476,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {...@@ -1471,6 +1476,7 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
1471 .arch_os_abi = target_arch_os_abi,1476 .arch_os_abi = target_arch_os_abi,
1472 .progress_node = compile_prog_node,1477 .progress_node = compile_prog_node,
1473 .skip_log_cmdline_on_compile_errors = !graph.verbose,1478 .skip_log_cmdline_on_compile_errors = !graph.verbose,
1479 .error_bundle = options.error_bundle,
1474 })) |r| r.path else |err| return err;1480 })) |r| r.path else |err| return err;
1475 defer gpa.free(configure_exe_path.sub_path);1481 defer gpa.free(configure_exe_path.sub_path);
14761482
lib/std/zig.zig+17-7
...@@ -1713,6 +1713,9 @@ pub const BuildExeSubprocessOptions = struct {...@@ -1713,6 +1713,9 @@ pub const BuildExeSubprocessOptions = struct {
1713 cpu_features: ?[]const u8 = null,1713 cpu_features: ?[]const u8 = null,
1714 progress_node: std.Progress.Node = .none,1714 progress_node: std.Progress.Node = .none,
1715 skip_log_cmdline_on_compile_errors: bool = false,1715 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,
1716};1719};
17171720
1718pub const BuildExeSubprocessError = error{1721pub const BuildExeSubprocessError = error{
...@@ -1781,8 +1784,10 @@ pub fn buildExeSubprocess(...@@ -1781,8 +1784,10 @@ pub fn buildExeSubprocess(
1781 var result: ?Cache.Path = null;1784 var result: ?Cache.Path = null;
1782 defer if (result) |r| gpa.free(r.sub_path);1785 defer if (result) |r| gpa.free(r.sub_path);
17831786
1784 var result_error_bundle: ErrorBundle = .empty;1787 var default_error_bundle: ErrorBundle = .empty;
1785 defer result_error_bundle.deinit(gpa);1788 defer default_error_bundle.deinit(gpa);
1789
1790 const error_bundle = options.error_bundle orelse &default_error_bundle;
17861791
1787 var received_fs_inputs = false;1792 var received_fs_inputs = false;
1788 var cache_hit = false;1793 var cache_hit = false;
...@@ -1816,8 +1821,8 @@ pub fn buildExeSubprocess(...@@ -1816,8 +1821,8 @@ pub fn buildExeSubprocess(
1816 }1821 }
1817 },1822 },
1818 .error_bundle => {1823 .error_bundle => {
1819 result_error_bundle.deinit(gpa);1824 error_bundle.deinit(gpa);
1820 result_error_bundle = Server.allocErrorBundle(gpa, body) catch |err| switch (err) {1825 error_bundle.* = Server.allocErrorBundle(gpa, body) catch |err| switch (err) {
1821 error.EndOfStream => break,1826 error.EndOfStream => break,
1822 else => |e| return e,1827 else => |e| return e,
1823 };1828 };
...@@ -1884,8 +1889,8 @@ pub fn buildExeSubprocess(...@@ -1884,8 +1889,8 @@ pub fn buildExeSubprocess(
1884 return error.AlreadyReported;1889 return error.AlreadyReported;
1885 }1890 }
18861891
1887 if (result_error_bundle.errorMessageCount() > 0) {1892 if (default_error_bundle.errorMessageCount() > 0) {
1888 result_error_bundle.renderToStderr(io, .{}, .auto) catch |err| switch (err) {1893 default_error_bundle.renderToStderr(io, .{}, .auto) catch |err| switch (err) {
1889 error.Canceled => |e| return e,1894 error.Canceled => |e| return e,
1890 else => |e| {1895 else => |e| {
1891 log.err("failed rendering error bundle: {t}", .{e});1896 log.err("failed rendering error bundle: {t}", .{e});
...@@ -1893,12 +1898,17 @@ pub fn buildExeSubprocess(...@@ -1893,12 +1898,17 @@ pub fn buildExeSubprocess(
1893 },1898 },
1894 };1899 };
1895 if (!options.skip_log_cmdline_on_compile_errors) log.err("command reported {d} compilation errors: {f}", .{1900 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,
1897 });1902 });
1898 if (received_fs_inputs) return error.FailedButCacheIntact;1903 if (received_fs_inputs) return error.FailedButCacheIntact;
1899 return error.AlreadyReported;1904 return error.AlreadyReported;
1900 }1905 }
19011906
1907 if (error_bundle.errorMessageCount() > 0) {
1908 if (received_fs_inputs) return error.FailedButCacheIntact;
1909 return error.AlreadyReported;
1910 }
1911
1902 const base_path = result orelse {1912 const base_path = result orelse {
1903 log.err("command failed to report result: {f}", .{cmd});1913 log.err("command failed to report result: {f}", .{cmd});
1904 return error.AlreadyReported;1914 return error.AlreadyReported;
lib/std/zig/Server.zig+8-22
...@@ -83,6 +83,10 @@ pub const Message = struct {...@@ -83,6 +83,10 @@ pub const Message = struct {
83 /// Body is a cwd relative path to the configuration file.83 /// Body is a cwd relative path to the configuration file.
84 /// This message only applies to the build system protocol.84 /// This message only applies to the build system protocol.
85 bsp_configuration,85 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,
86 /// Does not have a body.90 /// Does not have a body.
87 /// This message only applies to the build system protocol.91 /// This message only applies to the build system protocol.
88 bsp_build_started,92 bsp_build_started,
...@@ -307,7 +311,7 @@ pub fn serveTestResults(s: *Server, msg: OutMessage.TestResults) !void {...@@ -307,7 +311,7 @@ pub fn serveTestResults(s: *Server, msg: OutMessage.TestResults) !void {
307 try s.out.flush();311 try s.out.flush();
308}312}
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 {
311 const eb_hdr: OutMessage.ErrorBundle = .{315 const eb_hdr: OutMessage.ErrorBundle = .{
312 .extra_len = @intCast(error_bundle.extra.len),316 .extra_len = @intCast(error_bundle.extra.len),
313 .string_bytes_len = @intCast(error_bundle.string_bytes.len),317 .string_bytes_len = @intCast(error_bundle.string_bytes.len),
...@@ -315,7 +319,7 @@ pub fn serveErrorBundle(s: *Server, error_bundle: std.zig.ErrorBundle) !void {...@@ -315,7 +319,7 @@ pub fn serveErrorBundle(s: *Server, error_bundle: std.zig.ErrorBundle) !void {
315 const bytes_len = @sizeOf(OutMessage.ErrorBundle) +319 const bytes_len = @sizeOf(OutMessage.ErrorBundle) +
316 4 * error_bundle.extra.len + error_bundle.string_bytes.len;320 4 * error_bundle.extra.len + error_bundle.string_bytes.len;
317 try s.serveMessageHeader(.{321 try s.serveMessageHeader(.{
318 .tag = .error_bundle,322 .tag = tag,
319 .bytes_len = @intCast(bytes_len),323 .bytes_len = @intCast(bytes_len),
320 });324 });
321 try s.out.writeStruct(eb_hdr, .little);325 try s.out.writeStruct(eb_hdr, .little);
...@@ -327,31 +331,13 @@ pub fn serveErrorBundle(s: *Server, error_bundle: std.zig.ErrorBundle) !void {...@@ -327,31 +331,13 @@ pub fn serveErrorBundle(s: *Server, error_bundle: std.zig.ErrorBundle) !void {
327pub fn allocErrorBundle(gpa: Allocator, body: []const u8) error{ OutOfMemory, EndOfStream }!std.zig.ErrorBundle {331pub fn allocErrorBundle(gpa: Allocator, body: []const u8) error{ OutOfMemory, EndOfStream }!std.zig.ErrorBundle {
328 var r: Reader = .fixed(body);332 var r: Reader = .fixed(body);
329 const hdr = r.takeStruct(OutMessage.ErrorBundle, .little) catch |err| switch (err) {333 const hdr = r.takeStruct(OutMessage.ErrorBundle, .little) catch |err| switch (err) {
330 error.EndOfStream => |e| return e,
331 error.ReadFailed => unreachable,334 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) {
346 error.EndOfStream => |e| return e,335 error.EndOfStream => |e| return e,
347 error.ReadFailed => unreachable,
348 };336 };
349 r.readSliceAll(string_bytes) catch |err| switch (err) {337 return std.zig.ErrorBundle.readAlloc(&r, gpa, hdr.extra_len, hdr.string_bytes_len) catch |err| switch (err) {
350 error.EndOfStream => |e| return e,
351 error.ReadFailed => unreachable,338 error.ReadFailed => unreachable,
339 else => |e| return e,
352 };340 };
353
354 return eb;
355}341}
356342
357pub const TestMetadata = struct {343pub const TestMetadata = struct {
tools/bsp.zig+21-37
...@@ -7,6 +7,7 @@ const Configuration = std.Build.Configuration;...@@ -7,6 +7,7 @@ const Configuration = std.Build.Configuration;
7const Client = std.zig.Client;7const Client = std.zig.Client;
8const Server = std.zig.Server;8const Server = std.zig.Server;
9const log = std.log.scoped(.bsp);9const log = std.log.scoped(.bsp);
10const panic = std.debug.panic;
1011
11pub fn main(init: std.process.Init) !void {12pub fn main(init: std.process.Init) !void {
12 const io = init.io;13 const io = init.io;
...@@ -32,7 +33,7 @@ pub fn main(init: std.process.Init) !void {...@@ -32,7 +33,7 @@ pub fn main(init: std.process.Init) !void {
32 .stdin = .pipe,33 .stdin = .pipe,
33 .stdout = .pipe,34 .stdout = .pipe,
34 .stderr = .pipe,35 .stderr = .pipe,
35 }) catch |err| std.debug.panic("failed to spawn process: {}", .{err});36 }) catch |err| panic("failed to spawn process: {}", .{err});
36 errdefer child_process.kill(io);37 errdefer child_process.kill(io);
3738
38 var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined;39 var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined;
...@@ -66,10 +67,10 @@ pub fn main(init: std.process.Init) !void {...@@ -66,10 +67,10 @@ pub fn main(init: std.process.Init) !void {
66 },67 },
67 };68 };
68 const body = client_stdout.take(header.bytes_len) catch unreachable;69 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
71 if (header.tag != .bsp_handshake) {72 if (header.tag != .bsp_handshake) {
72 log.err("received unexpected message: {f}", .{fmtEnum(header.tag)});73 log.err("received unexpected message: {}", .{header.tag});
73 return error.UnexpectedMessage;74 return error.UnexpectedMessage;
74 }75 }
7576
...@@ -94,17 +95,26 @@ pub fn main(init: std.process.Init) !void {...@@ -94,17 +95,26 @@ pub fn main(init: std.process.Init) !void {
94 const body = client_stdout.take(header.bytes_len) catch unreachable;95 const body = client_stdout.take(header.bytes_len) catch unreachable;
95 log.debug("received {t} ({d} bytes)", .{ header.tag, body.len });96 log.debug("received {t} ({d} bytes)", .{ header.tag, body.len });
9697
97 if (header.tag != .bsp_configuration) {98 switch (header.tag) {
98 log.err("received unexpected message: {f}", .{fmtEnum(header.tag)});99 .bsp_configuration => {},
99 return error.UnexpectedMessage;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 },
100 }110 }
101111
102 const configuration_path = body;112 const configuration_path = body;
103 var file = Io.Dir.cwd().openFile(io, configuration_path, .{}) catch |err|113 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 });
105 defer file.close(io);115 defer file.close(io);
106 break :configuration Configuration.loadFile(conf_arena, io, file) catch |err|116 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 });
108 };118 };
109 const c = &configuration;119 const c = &configuration;
110120
...@@ -157,7 +167,7 @@ pub fn main(init: std.process.Init) !void {...@@ -157,7 +167,7 @@ pub fn main(init: std.process.Init) !void {
157 if (std.fmt.parseInt(u32, arg, 10)) |i|167 if (std.fmt.parseInt(u32, arg, 10)) |i|
158 @fromBackingInt(i)168 @fromBackingInt(i)
159 else |_|169 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", .{});
161 try steps.append(gpa, step);171 try steps.append(gpa, step);
162 }172 }
163 }173 }
...@@ -178,7 +188,7 @@ pub fn main(init: std.process.Init) !void {...@@ -178,7 +188,7 @@ pub fn main(init: std.process.Init) !void {
178 },188 },
179 };189 };
180 const body = client_stdout.take(header.bytes_len) catch unreachable;190 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
183 switch (header.tag) {193 switch (header.tag) {
184 .bsp_build_started => {},194 .bsp_build_started => {},
...@@ -214,7 +224,7 @@ pub fn main(init: std.process.Init) !void {...@@ -214,7 +224,7 @@ pub fn main(init: std.process.Init) !void {
214 }224 }
215 },225 },
216 .bsp_configuration => @panic("TODO"),226 .bsp_configuration => @panic("TODO"),
217 else => std.debug.panic("received unexpected message: {f}", .{fmtEnum(header.tag)}),227 else => panic("received unexpected message: {}", .{header.tag}),
218 }228 }
219 }229 }
220 continue;230 continue;
...@@ -242,29 +252,3 @@ pub fn main(init: std.process.Init) !void {...@@ -242,29 +252,3 @@ pub fn main(init: std.process.Init) !void {
242 log.err("maker {f}", .{term});252 log.err("maker {f}", .{term});
243 }253 }
244}254}
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}