From 19a97629b10336d506c70cce9c6a4ea7159c1590 Mon Sep 17 00:00:00 2001 From: Techatrix Date: Tue, 3 Feb 2026 09:38:31 +0100 Subject: [PATCH 1/3] update to Zig `0.16.0-dev.2471+e9eadee00` --- src/DocumentStore.zig | 5 +- src/configuration.zig | 3 +- src/features/diagnostics.zig | 238 +++++++++++++---------------------- src/translate_c.zig | 61 ++++----- 4 files changed, 127 insertions(+), 180 deletions(-) diff --git a/src/DocumentStore.zig b/src/DocumentStore.zig index 932224c54..37fa7d05f 100644 --- a/src/DocumentStore.zig +++ b/src/DocumentStore.zig @@ -1198,8 +1198,9 @@ fn loadBuildConfiguration(self: *DocumentStore, build_file_uri: Uri, build_file_ self.io, .{ .argv = args, - .cwd = cwd, - .max_output_bytes = 16 * 1024 * 1024, + .cwd = .{ .path = cwd }, + .stderr_limit = .limited(16 * 1024 * 1024), + .stdout_limit = .limited(16 * 1024 * 1024), }, ); }; diff --git a/src/configuration.zig b/src/configuration.zig index 6882daa3f..3e20e745f 100644 --- a/src/configuration.zig +++ b/src/configuration.zig @@ -334,7 +334,8 @@ pub const Manager = struct { io, .{ .argv = &argv, - .max_output_bytes = 16 * 1024 * 1024, + .stderr_limit = .limited(16 * 1024 * 1024), + .stdout_limit = .limited(16 * 1024 * 1024), }, ) catch |err| switch (err) { error.Canceled => return error.Canceled, diff --git a/src/features/diagnostics.zig b/src/features/diagnostics.zig index 9fb1f5d0f..781c910e9 100644 --- a/src/features/diagnostics.zig +++ b/src/features/diagnostics.zig @@ -330,41 +330,39 @@ fn getErrorBundleFromAstCheck( comptime std.debug.assert(std.process.can_spawn); - var stderr_bytes: []u8 = ""; - defer allocator.free(stderr_bytes); - - { - var process = std.process.spawn(io, .{ - .argv = &.{ zig_exe_path, "ast-check", "--color", "off" }, - .stdin = .pipe, - .stdout = .ignore, - .stderr = .pipe, - }) catch |err| switch (err) { - error.Canceled => return error.Canceled, - else => { - log.warn("Failed to spawn zig ast-check process, error: {}", .{err}); - return .empty; - }, - }; - try process.stdin.?.writeStreamingAll(io, source); - process.stdin.?.close(io); - - process.stdin = null; + var process = std.process.spawn(io, .{ + .argv = &.{ zig_exe_path, "ast-check", "--color", "off" }, + .stdin = .pipe, + .stdout = .ignore, + .stderr = .pipe, + }) catch |err| switch (err) { + error.Canceled => return error.Canceled, + else => { + log.warn("Failed to spawn zig ast-check process, error: {}", .{err}); + return .empty; + }, + }; + try process.stdin.?.writeStreamingAll(io, source); + process.stdin.?.close(io); - stderr_bytes = try readToEndAlloc(io, allocator, process.stderr.?, .limited(16 * 1024 * 1024)); + process.stdin = null; - const term = process.wait(io) catch |err| switch (err) { - error.Canceled => return error.Canceled, - else => { - log.warn("Failed to await zig ast-check process, error: {}", .{err}); - return .empty; - }, - }; + var buffer: [4096]u8 = undefined; + var file_reader = process.stderr.?.readerStreaming(io, &buffer); + const stderr = file_reader.interface.allocRemaining(allocator, .limited(16 * 1024 * 1024)) catch |err| switch (err) { + error.ReadFailed => return file_reader.err.?, + error.OutOfMemory, error.StreamTooLong => |e| return e, + }; + defer allocator.free(stderr); - if (term != .exited) return .empty; + const term = try process.wait(io); + switch (term) { + .exited => return try getErrorBundleFromStderr(allocator, stderr, true, .{ .single_source_file = source }), + .signal => |sig| std.log.err("zig ast-check failed with signal {t} and stderr:\n{s}", .{ sig, stderr }), + .stopped => |sig| std.log.err("zig ast-check stopped with signal {d} and stderr:\n{s}", .{ sig, stderr }), + .unknown => |code| std.log.err("zig ast-check failed for unknown reason with code {d} and stderr:\n{s}", .{ code, stderr }), } - - return try getErrorBundleFromStderr(allocator, stderr_bytes, true, .{ .single_source_file = source }); + return .empty; } pub fn getErrorBundleFromStderr( @@ -538,27 +536,15 @@ pub const BuildOnSave = struct { .stdin = .pipe, .stdout = .pipe, .stderr = .pipe, - .cwd = options.workspace_path, + .cwd = .{ .path = options.workspace_path }, }) catch |err| switch (err) { error.Canceled => return error.Canceled, else => { - log.err("failed to spawn zig build process: {}", .{err}); + log.err("failed to spawn zig build-on-save process: {}", .{err}); return null; }, }; - - errdefer { - child_process.stdin.?.close(options.io); - child_process.stdin = null; - - _ = terminateChildProcessReportError( - options.io, - options.allocator, - &child_process, - "zig build runner", - .kill, - ); - } + errdefer child_process.kill(options.io); const duped_workspace_path = try options.allocator.dupe(u8, options.workspace_path); errdefer options.allocator.free(duped_workspace_path); @@ -613,23 +599,20 @@ pub const BuildOnSave = struct { collection: *DiagnosticsCollection, workspace_path: []const u8, ) void { - defer { - allocator.free(workspace_path); - - state.mutex.lockUncancelable(io); - defer state.mutex.unlock(io); + defer allocator.free(workspace_path); + + var multi_reader_buffer: std.Io.File.MultiReader.Buffer(2) = undefined; + var multi_reader: std.Io.File.MultiReader = undefined; + defer multi_reader.deinit(); + multi_reader.init( + allocator, + io, + multi_reader_buffer.toStreams(), + &.{ state.child_process.stdout.?, state.child_process.stderr.? }, + ); - state.child_process.stdin.?.close(io); - state.child_process.stdin = null; - - _ = terminateChildProcessReportError( - io, - allocator, - &state.child_process, - "zig build runner", - .wait, - ); - } + const stdout = multi_reader.reader(0); + const stderr = multi_reader.reader(1); var diagnostic_tags: std.AutoArrayHashMapUnmanaged(DiagnosticsCollection.Tag, void) = .empty; defer diagnostic_tags.deinit(allocator); @@ -641,29 +624,25 @@ pub const BuildOnSave = struct { }; } - var read_buffer: [@sizeOf(ServerToClient.Header)]u8 = undefined; - var file_reader = state.child_process.stdout.?.reader(io, &read_buffer); - const reader = &file_reader.interface; - - while (true) { - const header = reader.takeStruct(ServerToClient.Header, .little) catch |err| switch (err) { - error.ReadFailed => switch (file_reader.err.?) { - error.Canceled => return, - else => return log.err("failed to receive message from zig build runner: {}", .{file_reader.err.?}), - }, - error.EndOfStream => break, - }; - const body = reader.readAlloc(allocator, header.bytes_len) catch |err| switch (err) { - error.ReadFailed => switch (file_reader.err.?) { - error.Canceled => return, - else => return log.err("failed to receive message from zig build runner: {}", .{file_reader.err.?}), - }, - error.EndOfStream, error.OutOfMemory => { - log.err("failed to receive message from zig build runner: {}", .{err}); - return; - }, + outer: while (true) { + while (stdout.bufferedLen() < @sizeOf(ServerToClient.Header)) { + multi_reader.fill(64, .none) catch |err| switch (err) { + error.Canceled => break :outer, + else => break :outer log.err("failed to receive message from zig build-on-save runner: {}", .{err}), + }; + } + const header = stdout.takeStruct(ServerToClient.Header, .little) catch unreachable; + while (stdout.bufferedLen() < header.bytes_len) { + multi_reader.fill(64, .none) catch |err| switch (err) { + error.Canceled => break :outer, + else => break :outer log.err("failed to receive message from zig build-on-save runner: {}", .{err}), + }; + } + multi_reader.checkAnyError() catch |err| switch (err) { + error.Canceled => break :outer, + else => break :outer log.err("failed to receive message from zig build-on-save runner: {}", .{err}), }; - defer allocator.free(body); + const body = stdout.take(header.bytes_len) catch unreachable; switch (header.tag) { .watch_error_bundle => { @@ -674,17 +653,39 @@ pub const BuildOnSave = struct { workspace_path, &diagnostic_tags, ) catch |err| switch (err) { - error.Canceled => return, - else => |e| log.err("failed to handle error bundle message from zig build runner: {}", .{e}), + error.Canceled => break :outer, + else => |e| log.err("failed to handle error bundle message from zig build-on-save runner: {}", .{e}), }; }, else => |tag| { - log.warn("received unexpected message from zig build runner: {}", .{tag}); + log.warn("received unexpected message from zig build-on-save runner: {}", .{tag}); + break :outer; }, } } - log.debug("zig build runner process has exited", .{}); + const old_cancel_protect = io.swapCancelProtection(.blocked); + defer _ = io.swapCancelProtection(old_cancel_protect); + + state.mutex.lockUncancelable(io); + defer state.mutex.unlock(io); + + state.child_process.stdin.?.close(io); + state.child_process.stdin = null; + + const term = state.child_process.wait(io) catch |err| { + log.warn("Failed to await zig build-on-save runner: {}", .{err}); + return; + }; + + const stderr_msg_prefix = if (stderr.bufferedLen() > 0) "and stderr:\n" else ""; + + switch (term) { + .exited => |code| if (code != 0) log.warn("zig build-on-save runner exited with with code: {d}{s}{s}", .{ code, stderr_msg_prefix, stderr.buffered() }), + .signal => |sig| std.log.err("zig build-on-save runner failed with signal {t}{s}{s}", .{ sig, stderr_msg_prefix, stderr.buffered() }), + .stopped => |sig| std.log.err("zig build-on-save runner stopped with signal {d}{s}{s}", .{ sig, stderr_msg_prefix, stderr.buffered() }), + .unknown => |code| std.log.err("zig build-on-save runner failed for unknown reason with code {d}{s}{s}", .{ code, stderr_msg_prefix, stderr.buffered() }), + } } fn handleWatchErrorBundle( @@ -728,64 +729,3 @@ pub const BuildOnSave = struct { try collection.publishDiagnostics(); } }; - -fn terminateChildProcessReportError( - io: std.Io, - allocator: std.mem.Allocator, - child_process: *std.process.Child, - name: []const u8, - kind: enum { wait, kill }, -) bool { - const old_cancel_protect = io.swapCancelProtection(.blocked); - defer _ = io.swapCancelProtection(old_cancel_protect); - - const stderr = if (child_process.stderr) |stderr| - readToEndAlloc(io, allocator, stderr, .limited(16 * 1024 * 1024)) catch "" - else - ""; - defer allocator.free(stderr); - - const term = switch (kind) { - .wait => child_process.wait(io) catch |err| { - log.warn("Failed to await {s}: {}", .{ name, err }); - return false; - }, - .kill => blk: { - child_process.kill(io); - break :blk std.process.Child.Term{ .exited = 0 }; - }, - }; - - switch (term) { - .exited => |code| if (code != 0) { - if (stderr.len != 0) { - log.warn("{s} exited with non-zero status: {}\nstderr:\n{s}", .{ name, code, stderr }); - } else { - log.warn("{s} exited with non-zero status: {}", .{ name, code }); - } - }, - else => { - if (stderr.len != 0) { - log.warn("{s} exitied abnormally: {t}\nstderr:\n{s}", .{ name, term, stderr }); - } else { - log.warn("{s} exitied abnormally: {t}", .{ name, term }); - } - }, - } - - return true; -} - -fn readToEndAlloc( - io: std.Io, - allocator: std.mem.Allocator, - file: std.Io.File, - limit: std.Io.Limit, -) (std.Io.File.Reader.Error || error{ OutOfMemory, StreamTooLong })![]u8 { - var buffer: [1024]u8 = undefined; - var file_reader = file.readerStreaming(io, &buffer); - return file_reader.interface.allocRemaining(allocator, limit) catch |err| switch (err) { - error.ReadFailed => return file_reader.err.?, - error.OutOfMemory, error.StreamTooLong => |e| return e, - }; -} diff --git a/src/translate_c.zig b/src/translate_c.zig index 120c94ad8..9b2db39c9 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -179,7 +179,7 @@ pub fn translate( .argv = argv.items, .stdin = .pipe, .stdout = .pipe, - .stderr = .ignore, + .stderr = .pipe, }) catch |err| switch (err) { error.Canceled => return error.Canceled, else => { @@ -187,12 +187,7 @@ pub fn translate( return null; }, }; - - errdefer |err| if (!zig_builtin.is_test) reportTranslateError(io, allocator, process.stderr, argv.items, @errorName(err)); - - defer _ = process.wait(io) catch |wait_err| { - log.err("zig translate-c process did not terminate, error: {}", .{wait_err}); - }; + defer process.kill(io); { var stdin_writer = process.stdin.?.writer(io, &.{}); @@ -209,20 +204,37 @@ pub fn translate( }, .little) catch return @as(std.Io.File.Writer.Error!?Result, stdin_writer.err.?); } - var poller = std.Io.poll(allocator, enum { stdout }, .{ .stdout = process.stdout.? }); - defer poller.deinit(); - const stdout = poller.reader(.stdout); + var multi_reader_buffer: std.Io.File.MultiReader.Buffer(2) = undefined; + var multi_reader: std.Io.File.MultiReader = undefined; + defer multi_reader.deinit(); + multi_reader.init( + allocator, + io, + multi_reader_buffer.toStreams(), + &.{ process.stdout.?, process.stderr.? }, + ); + + const stdout = multi_reader.reader(0); + const stderr = multi_reader.reader(1); - while (true) { - const timeout: u64 = 20 * std.time.ns_per_s; + outer: while (true) { + const timeout: std.Io.Timeout = .{ .duration = .{ .clock = .awake, .raw = .fromSeconds(90) } }; while (stdout.buffered().len < @sizeOf(InMessage.Header)) { - if (!try poller.pollTimeout(timeout)) return error.EndOfStream; + multi_reader.fill(64, timeout) catch |err| switch (err) { + error.EndOfStream => break :outer, + else => |e| return e, + }; } const header = stdout.takeStruct(InMessage.Header, .little) catch unreachable; while (stdout.buffered().len < header.bytes_len) { - if (!try poller.pollTimeout(timeout)) return error.EndOfStream; + multi_reader.fill(64, timeout) catch |err| switch (err) { + error.EndOfStream => break :outer, + else => |e| return e, + }; } + try multi_reader.checkAnyError(); + const body = stdout.take(header.bytes_len) catch unreachable; var reader: std.Io.Reader = .fixed(body); @@ -269,22 +281,15 @@ pub fn translate( else => {}, } } -} -fn reportTranslateError(io: std.Io, allocator: std.mem.Allocator, stderr: ?std.Io.File, argv: []const []const u8, err_name: []const u8) void { - const joined = std.mem.join(allocator, " ", argv) catch return; - defer allocator.free(joined); - if (stderr) |file| { - var buffer: [1024]u8 = undefined; - var file_reader = file.readerStreaming(io, &buffer); - const old_cancel_protect = io.swapCancelProtection(.blocked); - defer _ = io.swapCancelProtection(old_cancel_protect); - const stderr_output = file_reader.interface.allocRemaining(allocator, .limited(16 * 1024 * 1024)) catch return; - defer allocator.free(stderr_output); - log.err("failed zig translate-c command:\n{s}\nstderr:{s}\nerror:{s}\n", .{ joined, stderr_output, err_name }); - } else { - log.err("failed zig translate-c command:\n{s}\nerror:{s}\n", .{ joined, err_name }); + const term = try process.wait(io); + switch (term) { + .exited => |code| std.log.err("zig translate-c failed with code {d} and stderr:\n{s}", .{ code, stderr.buffered() }), + .signal => |sig| std.log.err("zig translate-c failed with signal {t} and stderr:\n{s}", .{ sig, stderr.buffered() }), + .stopped => |sig| std.log.err("zig translate-c stopped with signal {d} and stderr:\n{s}", .{ sig, stderr.buffered() }), + .unknown => |code| std.log.err("zig translate-c failed for unknown reason with code {d} and stderr:\n{s}", .{ code, stderr.buffered() }), } + return null; } fn extractString(str: []const u8) []const u8 { From 91431380e895662d4d9246c6adfdc1076be3e455 Mon Sep 17 00:00:00 2001 From: Techatrix Date: Tue, 3 Feb 2026 09:38:43 +0100 Subject: [PATCH 2/3] set minimum zig version to `0.16.0-dev.2471+e9eadee00` --- build.zig.zon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.zig.zon b/build.zig.zon index f1a08a4d4..0e253fc65 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -10,7 +10,7 @@ // nix flake update --commit-lock-file // ``` // If you do not use Nix, a ZLS maintainer can take care of this. - .minimum_zig_version = "0.16.0-dev.2368+380ea6fb5", + .minimum_zig_version = "0.16.0-dev.2471+e9eadee00", // If you do not use Nix, a ZLS maintainer can take care of this. // Whenever the dependencies are updated, run the following command: // ```bash From ba66320f3af5678266d4dd204e68371ab06ca656 Mon Sep 17 00:00:00 2001 From: Techatrix Date: Thu, 5 Feb 2026 10:03:47 +0100 Subject: [PATCH 3/3] flake.lock: Update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/41e216c0ca66c83b12ab7a98cc326b5db01db646?narHash=sha256-I7Lmgj3owOTBGuauy9FL6qdpeK2umDoe07lM4V%2BPnyA%3D' (2026-01-31) → 'github:NixOS/nixpkgs/e576e3c9cf9bad747afcddd9e34f51d18c855b4e?narHash=sha256-tlFqNG/uzz2%2B%2BaAmn4v8J0vAkV3z7XngeIIB3rM3650%3D' (2026-02-03) • Updated input 'zig-overlay': 'github:mitchellh/zig-overlay/d80fa53a23c4cf621cd72d9a6fb71392968ea04c?narHash=sha256-7WBmKtSvs%2BlaiTJlBp7fVJGppIFmGvsEYHOv1myFisI%3D' (2026-01-31) → 'github:mitchellh/zig-overlay/364ec9318bc70fc1ec084cb41e80e90d6770dd2b?narHash=sha256-q7nu9F2ZtmIU9BMarY8BLIEfiwPwrjXGXLvOdFSvQP4%3D' (2026-02-04) --- flake.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index cace08f5c..449527b96 100644 --- a/flake.lock +++ b/flake.lock @@ -36,11 +36,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1769900590, - "narHash": "sha256-I7Lmgj3owOTBGuauy9FL6qdpeK2umDoe07lM4V+PnyA=", + "lastModified": 1770136044, + "narHash": "sha256-tlFqNG/uzz2++aAmn4v8J0vAkV3z7XngeIIB3rM3650=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "41e216c0ca66c83b12ab7a98cc326b5db01db646", + "rev": "e576e3c9cf9bad747afcddd9e34f51d18c855b4e", "type": "github" }, "original": { @@ -80,11 +80,11 @@ ] }, "locked": { - "lastModified": 1769861710, - "narHash": "sha256-7WBmKtSvs+laiTJlBp7fVJGppIFmGvsEYHOv1myFisI=", + "lastModified": 1770165742, + "narHash": "sha256-q7nu9F2ZtmIU9BMarY8BLIEfiwPwrjXGXLvOdFSvQP4=", "owner": "mitchellh", "repo": "zig-overlay", - "rev": "d80fa53a23c4cf621cd72d9a6fb71392968ea04c", + "rev": "364ec9318bc70fc1ec084cb41e80e90d6770dd2b", "type": "github" }, "original": {