From 6a16b270b1298733f1570a675689766091809c2a Mon Sep 17 00:00:00 2001 From: Techatrix Date: Fri, 24 Jan 2025 16:53:12 +0100 Subject: [PATCH] add a fallback mode for build on save when using a pre 6.5 linux kernel --- src/Server.zig | 41 ++++++++++++++---- src/build_runner/master.zig | 80 +++++++++++++++++++++++++++++------- src/build_runner/shared.zig | 8 +++- src/features/diagnostics.zig | 4 ++ 4 files changed, 110 insertions(+), 23 deletions(-) diff --git a/src/Server.zig b/src/Server.zig index 652c849d3..91f35f38c 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -738,6 +738,7 @@ fn handleConfiguration(server: *Server, json: std.json.Value) error{OutOfMemory} const Workspace = struct { uri: types.URI, build_on_save: if (BuildOnSaveSupport.isSupportedComptime()) ?BuildOnSave else void, + build_on_save_mode: if (BuildOnSaveSupport.isSupportedComptime()) ?enum { watch, manual } else void, fn init(server: *Server, uri: types.URI) error{OutOfMemory}!Workspace { const duped_uri = try server.allocator.dupe(u8, uri); @@ -746,6 +747,7 @@ const Workspace = struct { return .{ .uri = duped_uri, .build_on_save = if (BuildOnSaveSupport.isSupportedComptime()) null else {}, + .build_on_save_mode = if (BuildOnSaveSupport.isSupportedComptime()) null else {}, }; } @@ -756,6 +758,16 @@ const Workspace = struct { allocator.free(workspace.uri); } + fn sendManualWatchUpdate(workspace: *Workspace) void { + comptime std.debug.assert(BuildOnSaveSupport.isSupportedComptime()); + + const build_on_save = if (workspace.build_on_save) |*build_on_save| build_on_save else return; + const mode = workspace.build_on_save_mode orelse return; + if (mode != .manual) return; + + build_on_save.sendManualWatchUpdate(); + } + fn refreshBuildOnSave(workspace: *Workspace, args: struct { server: *Server, /// If null, build on save will be disabled @@ -765,7 +777,17 @@ const Workspace = struct { }) error{OutOfMemory}!void { comptime std.debug.assert(BuildOnSaveSupport.isSupportedComptime()); - const build_on_save_supported = if (args.runtime_zig_version) |version| BuildOnSaveSupport.isSupportedRuntime(version) == .supported else false; + if (args.runtime_zig_version) |runtime_zig_version| { + workspace.build_on_save_mode = switch (BuildOnSaveSupport.isSupportedRuntime(runtime_zig_version)) { + .supported => .watch, + // If if build on save has been explicitly enabled, fallback to the implementation with manual updates + else => if (args.server.config.enable_build_on_save orelse false) .manual else null, + }; + } else { + workspace.build_on_save_mode = null; + } + + const build_on_save_supported = workspace.build_on_save_mode != null; const build_on_save_wanted = args.server.config.enable_build_on_save orelse true; const enable = build_on_save_supported and build_on_save_wanted; @@ -1066,9 +1088,7 @@ pub fn updateConfiguration( } if (server.config.enable_build_on_save orelse false) { - if (!BuildOnSaveSupport.isSupportedComptime() and @TypeOf(BuildOnSaveSupport.minimum_zig_version) == void) { - log.info("'enable_build_on_save' is ignored because it is not supported on {s}", .{@tagName(zig_builtin.os.tag)}); - } else if (!BuildOnSaveSupport.isSupportedComptime()) { + if (!BuildOnSaveSupport.isSupportedComptime()) { // This message is not very helpful but it relatively uncommon to happen anyway. log.info("'enable_build_on_save' is ignored because build on save is not supported by this ZLS build", .{}); } else if (server.status == .initialized and (server.config.zig_exe_path == null or server.config.zig_lib_path == null)) { @@ -1080,9 +1100,10 @@ pub fn updateConfiguration( } else if (server.status == .initialized and options.resolve and resolve_result.zig_runtime_version != null) { switch (BuildOnSaveSupport.isSupportedRuntime(resolve_result.zig_runtime_version.?)) { .supported => {}, - .invalid_linux_kernel_version => |*utsname_release| log.warn("'enable_build_on_save' is ignored because it because the Linux version '{s}' could not be parsed", .{std.mem.sliceTo(utsname_release, 0)}), - .unsupported_linux_kernel_version => |kernel_version| log.warn("'enable_build_on_save' is ignored because it is not supported by Linux '{}' (requires at least {})", .{ kernel_version, BuildOnSaveSupport.minimum_linux_version }), - .unsupported_zig_version => log.warn("'enable_build_on_save' is ignored because it is not supported on {s} by Zig {} (requires at least {})", .{ @tagName(zig_builtin.os.tag), resolve_result.zig_runtime_version.?, BuildOnSaveSupport.minimum_zig_version }), + .invalid_linux_kernel_version => |*utsname_release| log.warn("Build-On-Save cannot run in watch mode because it because the Linux version '{s}' could not be parsed", .{std.mem.sliceTo(utsname_release, 0)}), + .unsupported_linux_kernel_version => |kernel_version| log.warn("Build-On-Save cannot run in watch mode because it is not supported by Linux '{}' (requires at least {})", .{ kernel_version, BuildOnSaveSupport.minimum_linux_version }), + .unsupported_zig_version => log.warn("Build-On-Save cannot run in watch mode because it is not supported on {s} by Zig {} (requires at least {})", .{ @tagName(zig_builtin.os.tag), resolve_result.zig_runtime_version.?, BuildOnSaveSupport.minimum_zig_version }), + .unsupported_os => log.warn("Build-On-Save cannot run in watch mode because it is not supported on {s}", .{@tagName(zig_builtin.os.tag)}), } } } @@ -1458,6 +1479,12 @@ fn saveDocumentHandler(server: *Server, arena: std.mem.Allocator, notification: ); server.allocator.free(json_message); } + + if (BuildOnSaveSupport.isSupportedComptime()) { + for (server.workspaces.items) |*workspace| { + workspace.sendManualWatchUpdate(); + } + } } fn closeDocumentHandler(server: *Server, _: std.mem.Allocator, notification: types.DidCloseTextDocumentParams) error{}!void { diff --git a/src/build_runner/master.zig b/src/build_runner/master.zig index fc70c7794..33c6a22be 100644 --- a/src/build_runner/master.zig +++ b/src/build_runner/master.zig @@ -21,7 +21,6 @@ const mem = std.mem; const process = std.process; const ArrayList = std.ArrayList; const Step = std.Build.Step; -const Watch = std.Build.Watch; const Allocator = std.mem.Allocator; pub const dependencies = @import("@dependencies"); @@ -376,20 +375,22 @@ pub fn main() !void { return; } - if (!std.Build.Watch.have_impl) { - std.log.warn("Build-On-Save is not supported on {} by Zig {}", .{ builtin.target.os.tag, builtin.zig_version }); - process.exit(1); - } + var w = try Watch.init(); - const suicide_thread = try std.Thread.spawn(.{}, struct { - fn do() void { - _ = std.io.getStdIn().reader().readByte() catch process.exit(1); - process.exit(0); + const message_thread = try std.Thread.spawn(.{}, struct { + fn do(ww: *Watch) void { + while (std.io.getStdIn().reader().readByte()) |tag| { + switch (tag) { + '\x00' => ww.trigger(), + else => process.exit(1), + } + } else |err| switch (err) { + error.EndOfStream => process.exit(0), + else => process.exit(1), + } } - }.do, .{}); - suicide_thread.detach(); - - var w = try Watch.init(); + }.do, .{&w}); + message_thread.detach(); const gpa = arena; var transport = Transport.init(.{ @@ -430,7 +431,7 @@ pub fn main() !void { // if any more events come in. After the debounce interval has passed, // trigger a rebuild on all steps with modified inputs, as well as their // recursive dependants. - var debounce_timeout: Watch.Timeout = .none; + var debounce_timeout: std.Build.Watch.Timeout = .none; while (true) switch (try w.wait(gpa, debounce_timeout)) { .timeout => { markFailedStepsDirty(gpa, step_stack.keys()); @@ -457,6 +458,57 @@ fn markFailedStepsDirty(gpa: Allocator, all_steps: []const *Step) void { }; } +/// A wrapper around `std.Build.Watch` that supports manually triggering recompilations. +const Watch = struct { + fs_watch: std.Build.Watch, + supports_fs_watch: bool, + manual_event: std.Thread.ResetEvent, + steps: []const *Step, + + fn init() !Watch { + return .{ + .fs_watch = if (@TypeOf(std.Build.Watch) != void) try std.Build.Watch.init() else {}, + .supports_fs_watch = @TypeOf(std.Build.Watch) != void and shared.BuildOnSaveSupport.isSupportedRuntime(builtin.zig_version) == .supported, + .manual_event = .{}, + .steps = &.{}, + }; + } + + fn update(w: *Watch, gpa: Allocator, steps: []const *Step) !void { + if (@TypeOf(std.Build.Watch) != void and w.supports_fs_watch) { + return try w.fs_watch.update(gpa, steps); + } + w.steps = steps; + } + + fn trigger(w: *Watch) void { + if (w.supports_fs_watch) { + @panic("received manualy filesystem event even though std.Build.Watch is supported"); + } + w.manual_event.set(); + } + + fn wait(w: *Watch, gpa: Allocator, timeout: std.Build.Watch.Timeout) !std.Build.Watch.WaitResult { + if (@TypeOf(std.Build.Watch) != void and w.supports_fs_watch) { + return try w.fs_watch.wait(gpa, timeout); + } + switch (timeout) { + .none => w.manual_event.wait(), + .ms => |ms| w.manual_event.timedWait(@as(u64, ms) * std.time.ns_per_ms) catch return .timeout, + } + w.manual_event.reset(); + markStepsDirty(gpa, w.steps); + return .dirty; + } + + fn markStepsDirty(gpa: Allocator, all_steps: []const *Step) void { + for (all_steps) |step| switch (step.state) { + .precheck_done => continue, + else => step.recursiveReset(gpa), + }; + } +}; + const Run = struct { max_rss: u64, max_rss_is_default: bool, diff --git a/src/build_runner/shared.zig b/src/build_runner/shared.zig index 65c505595..b27e1fbbe 100644 --- a/src/build_runner/shared.zig +++ b/src/build_runner/shared.zig @@ -150,7 +150,8 @@ pub const BuildOnSaveSupport = union(enum) { supported, invalid_linux_kernel_version: if (builtin.os.tag == .linux) std.meta.FieldType(std.posix.utsname, .release) else noreturn, unsupported_linux_kernel_version: if (builtin.os.tag == .linux) std.SemanticVersion else noreturn, - unsupported_zig_version: void, + unsupported_zig_version: if (@TypeOf(minimum_zig_version) != void) void else noreturn, + unsupported_os: if (@TypeOf(minimum_zig_version) == void) void else noreturn, const linux_support_version = std.SemanticVersion.parse("0.14.0-dev.283+1d20ff11d") catch unreachable; const windows_support_version = std.SemanticVersion.parse("0.14.0-dev.625+2de0e2eca") catch unreachable; @@ -184,7 +185,6 @@ pub const BuildOnSaveSupport = union(enum) { pub inline fn isSupportedComptime() bool { if (!std.process.can_spawn) return false; if (builtin.single_threaded) return false; - if (@TypeOf(minimum_zig_version) == void) return false; return true; } @@ -204,6 +204,10 @@ pub const BuildOnSaveSupport = union(enum) { }; } + if (@TypeOf(minimum_zig_version) == void) { + return .unsupported_os; + } + if (runtime_zig_version.order(minimum_zig_version) == .lt) { return .unsupported_zig_version; } diff --git a/src/features/diagnostics.zig b/src/features/diagnostics.zig index eccbc1adb..64cd71972 100644 --- a/src/features/diagnostics.zig +++ b/src/features/diagnostics.zig @@ -532,6 +532,10 @@ pub const BuildOnSave = struct { self.thread.join(); } + pub fn sendManualWatchUpdate(self: *BuildOnSave) void { + self.child_process.stdin.?.writeAll("\x00") catch {}; + } + fn loop( allocator: std.mem.Allocator, child_process: *std.process.Child,