From 3426c6839262ae6f41587478438f33ea41907d40 Mon Sep 17 00:00:00 2001 From: Techatrix Date: Wed, 15 Jan 2025 16:08:58 +0100 Subject: [PATCH 1/7] update build runner to require Zig 0.14.0-dev.2534+12d64c456 --- build.zig | 4 +- src/Server.zig | 21 +++++---- src/build_runner/master.zig | 91 +++++------------------------------- src/build_runner/shared.zig | 43 ----------------- src/features/diagnostics.zig | 43 +++++++++++++++++ 5 files changed, 68 insertions(+), 134 deletions(-) diff --git a/build.zig b/build.zig index 86ad0a143..49df61f29 100644 --- a/build.zig +++ b/build.zig @@ -18,14 +18,14 @@ const zls_version: std.SemanticVersion = .{ .major = 0, .minor = 14, .patch = 0, const minimum_build_zig_version = "0.14.0-dev.2633+bc846c379"; /// Specify the minimum Zig version that is required to run ZLS: -/// make zig compiler processes live across rebuilds +/// std.Build: add new functions to create artifacts/Step.Compile from existing module /// /// Examples of reasons that would cause the minimum runtime version to be bumped are: /// - breaking change to the Zig Syntax /// - breaking change to AstGen (i.e `zig ast-check`) /// /// A breaking change to the Zig Build System should be handled by updating ZLS's build runner (see src\build_runner) -const minimum_runtime_zig_version = "0.14.0-dev.310+9d38e82b5"; +const minimum_runtime_zig_version = "0.14.0-dev.2534+12d64c456"; const release_targets = [_]std.Target.Query{ .{ .cpu_arch = .x86_64, .os_tag = .windows }, diff --git a/src/Server.zig b/src/Server.zig index 1ac3025e2..241463484 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -36,6 +36,8 @@ const hover_handler = @import("features/hover.zig"); const selection_range = @import("features/selection_range.zig"); const diagnostics_gen = @import("features/diagnostics.zig"); +const BuildOnSave = diagnostics_gen.BuildOnSave; + const log = std.log.scoped(.server); // public fields @@ -733,7 +735,7 @@ fn handleConfiguration(server: *Server, json: std.json.Value) error{OutOfMemory} const Workspace = struct { uri: types.URI, - build_on_save: if (build_runner_shared.isBuildOnSaveSupportedComptime()) ?diagnostics_gen.BuildOnSave else void, + build_on_save: if (BuildOnSave.isSupportedComptime()) ?BuildOnSave else void, fn init(server: *Server, uri: types.URI) error{OutOfMemory}!Workspace { const duped_uri = try server.allocator.dupe(u8, uri); @@ -741,12 +743,12 @@ const Workspace = struct { return .{ .uri = duped_uri, - .build_on_save = if (build_runner_shared.isBuildOnSaveSupportedComptime()) null else {}, + .build_on_save = if (BuildOnSave.isSupportedComptime()) null else {}, }; } fn deinit(workspace: *Workspace, allocator: std.mem.Allocator) void { - if (build_runner_shared.isBuildOnSaveSupportedComptime()) { + if (BuildOnSave.isSupportedComptime()) { if (workspace.build_on_save) |*build_on_save| build_on_save.deinit(); } allocator.free(workspace.uri); @@ -759,10 +761,9 @@ const Workspace = struct { /// Whether the build on save process should be restarted if it is already running. restart: bool, }) error{OutOfMemory}!void { - comptime std.debug.assert(build_runner_shared.isBuildOnSaveSupportedComptime()); - comptime std.debug.assert(build_options.version.order(.{ .major = 0, .minor = 14, .patch = 0 }) == .lt); // Update `isBuildOnSaveSupportedRuntime` and build runner + comptime std.debug.assert(BuildOnSave.isSupportedComptime()); - const build_on_save_supported = if (args.runtime_zig_version) |version| build_runner_shared.isBuildOnSaveSupportedRuntime(version) else false; + const build_on_save_supported = if (args.runtime_zig_version) |version| BuildOnSave.isSupportedRuntime(version) else false; 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; @@ -785,7 +786,7 @@ const Workspace = struct { }; defer args.server.allocator.free(workspace_path); - workspace.build_on_save = @as(diagnostics_gen.BuildOnSave, undefined); + workspace.build_on_save = @as(BuildOnSave, undefined); workspace.build_on_save.?.init(.{ .allocator = args.server.allocator, .workspace_path = workspace_path, @@ -971,7 +972,7 @@ pub fn updateConfiguration( } } - if (build_runner_shared.isBuildOnSaveSupportedComptime() and + if (BuildOnSave.isSupportedComptime() and options.resolve and // If the client supports the `workspace/configuration` request, defer // build on save initialization until after we have received workspace @@ -1070,7 +1071,7 @@ pub fn updateConfiguration( } if (server.config.enable_build_on_save orelse false) { - if (!build_runner_shared.isBuildOnSaveSupportedComptime()) { + if (!BuildOnSave.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)) { @@ -1079,7 +1080,7 @@ pub fn updateConfiguration( log.warn("'enable_build_on_save' is ignored because it is not supported by {s}", .{server.client_capabilities.client_name orelse "your editor"}); } else if (server.status == .initialized and options.resolve and resolve_result.build_runner_version == .unresolved and server.config.build_runner_path == null) { log.warn("'enable_build_on_save' is ignored because no build runner is available", .{}); - } else if (server.status == .initialized and options.resolve and resolve_result.zig_runtime_version != null and !build_runner_shared.isBuildOnSaveSupportedRuntime(resolve_result.zig_runtime_version.?)) { + } else if (server.status == .initialized and options.resolve and resolve_result.zig_runtime_version != null and !BuildOnSave.isSupportedRuntime(resolve_result.zig_runtime_version.?)) { // There is one edge-case where build on save is not supported because of Linux pre 5.17 log.warn("'enable_build_on_save' is not supported by Zig {}", .{resolve_result.zig_runtime_version.?}); } diff --git a/src/build_runner/master.zig b/src/build_runner/master.zig index 476242253..930571b93 100644 --- a/src/build_runner/master.zig +++ b/src/build_runner/master.zig @@ -4,7 +4,6 @@ //! - master (0.14.0-dev.2046+b8795b4d0 and later) //! //! Handling multiple Zig versions can be achieved by branching on the `builtin.zig_version` at comptime. -//! As an example, see how `child_type_coercion_version` is used to deal with breaking changes. //! //! You can test out the build runner on ZLS's `build.zig` with the following command: //! `zig build --build-runner src/build_runner/master.zig` @@ -29,11 +28,6 @@ pub const dependencies = @import("@dependencies"); // ----------- List of Zig versions that introduced breaking changes ----------- -const child_type_coercion_version = - std.SemanticVersion.parse("0.14.0-dev.2506+32354d119") catch unreachable; -const accept_root_module_version = - std.SemanticVersion.parse("0.14.0-dev.2534+12d64c456") catch unreachable; - // ----------------------------------------------------------------------------- ///! This is a modified build runner to extract information out of build.zig @@ -317,9 +311,7 @@ pub fn main() !void { var prog_node = main_progress_node.start("Configure", 0); defer prog_node.end(); try builder.runBuild(root); - if (comptime builtin.zig_version.order(accept_root_module_version) != .lt) { - createModuleDependencies(builder) catch @panic("OOM"); - } + createModuleDependencies(builder) catch @panic("OOM"); } if (graph.needed_lazy_dependencies.entries.len != 0) { @@ -384,6 +376,11 @@ 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); + } + const suicide_thread = try std.Thread.spawn(.{}, struct { fn do() void { _ = std.io.getStdIn().reader().readByte() catch process.exit(1); @@ -392,9 +389,6 @@ pub fn main() !void { }.do, .{}); suicide_thread.detach(); - if (!shared.isBuildOnSaveSupportedComptime()) return; - if (!shared.isBuildOnSaveSupportedRuntime(builtin.zig_version)) return; - var w = try Watch.init(); const gpa = arena; @@ -768,25 +762,20 @@ fn workerMakeOneStep( } } -const ArgsType = if (builtin.zig_version.order(child_type_coercion_version) == .lt) - [][:0]const u8 -else - []const [:0]const u8; - -fn nextArg(args: ArgsType, idx: *usize) ?[:0]const u8 { +fn nextArg(args: []const [:0]const u8, idx: *usize) ?[:0]const u8 { if (idx.* >= args.len) return null; defer idx.* += 1; return args[idx.*]; } -fn nextArgOrFatal(args: ArgsType, idx: *usize) [:0]const u8 { +fn nextArgOrFatal(args: []const [:0]const u8, idx: *usize) [:0]const u8 { return nextArg(args, idx) orelse { std.debug.print("expected argument after '{s}'\n access the help menu with 'zig build -h'\n", .{args[idx.* - 1]}); process.exit(1); }; } -fn argsRest(args: ArgsType, idx: usize) ?ArgsType { +fn argsRest(args: []const [:0]const u8, idx: usize) ?[]const [:0]const u8 { if (idx >= args.len) return null; return args[idx..]; } @@ -1088,15 +1077,8 @@ fn extractBuildInformation( var step_dependencies: std.AutoArrayHashMapUnmanaged(*Step, void) = .{}; defer step_dependencies.deinit(gpa); - const DependencyItem = struct { - compile: ?*std.Build.Step.Compile, - module: *std.Build.Module, - }; - - var dependency_set: std.AutoArrayHashMapUnmanaged(DependencyItem, []const u8) = .{}; - defer dependency_set.deinit(gpa); - - if (comptime builtin.zig_version.order(accept_root_module_version) != .lt) { + // collect step dependencies + { var modules: std.AutoArrayHashMapUnmanaged(*std.Build.Module, void) = .{}; defer modules.deinit(gpa); @@ -1119,46 +1101,6 @@ fn extractBuildInformation( for (modules.keys()) |module| { try helper.addModuleDependencies(gpa, &step_dependencies, module); } - } else { - var dependency_iterator: std.Build.Module.DependencyIterator = .{ - .allocator = gpa, - .index = 0, - .set = .{}, - .chase_dyn_libs = true, - }; - defer dependency_iterator.deinit(); - - // collect root modules of `Step.Compile` - for (steps.keys()) |step| { - const compile = step.cast(Step.Compile) orelse continue; - - dependency_iterator.set.ensureUnusedCapacity(arena, compile.root_module.import_table.count() + 1) catch @panic("OOM"); - dependency_iterator.set.putAssumeCapacity(.{ - .module = &compile.root_module, - .compile = compile, - }, "root"); - } - - // collect public modules - for (b.modules.values()) |module| { - dependency_iterator.set.ensureUnusedCapacity(gpa, module.import_table.count() + 1) catch @panic("OOM"); - dependency_iterator.set.putAssumeCapacity(.{ - .module = module, - .compile = null, - }, "root"); - } - - var dependency_items: std.ArrayListUnmanaged(std.Build.Module.DependencyIterator.Item) = .{}; - defer dependency_items.deinit(gpa); - - // collect all dependencies - while (dependency_iterator.next()) |item| { - try helper.addModuleDependencies(gpa, &step_dependencies, item.module); - _ = try dependency_set.fetchPut(gpa, .{ - .module = item.module, - .compile = item.compile, - }, item.name); - } } prepare(gpa, b, &step_dependencies, run, seed) catch |err| switch (err) { @@ -1181,16 +1123,7 @@ fn extractBuildInformation( defer packages.deinit(); // extract packages and include paths - if (comptime builtin.zig_version.order(accept_root_module_version) == .lt) { - for (dependency_set.keys(), dependency_set.values()) |item, name| { - try helper.processItem(gpa, item.module, item.compile, name, &packages, &include_dirs); - for (item.module.import_table.keys(), item.module.import_table.values()) |import_name, import| { - if (import.root_source_file) |root_source_file| { - _ = try packages.addPackage(import_name, root_source_file.getPath(item.module.owner)); - } - } - } - } else { + { for (steps.keys()) |step| { const compile = step.cast(Step.Compile) orelse continue; const graph = compile.root_module.getGraph(); diff --git a/src/build_runner/shared.zig b/src/build_runner/shared.zig index 3d5872659..18b9d64e4 100644 --- a/src/build_runner/shared.zig +++ b/src/build_runner/shared.zig @@ -144,46 +144,3 @@ pub const ServerToClient = struct { string_bytes_len: u32, }; }; - -const windows_support_version = std.SemanticVersion.parse("0.14.0-dev.625+2de0e2eca") catch unreachable; -const kqueue_support_version = std.SemanticVersion.parse("0.14.0-dev.2046+b8795b4d0") catch unreachable; -/// The Zig version which added `std.Build.Watch.have_impl` -const have_impl_flag_version = kqueue_support_version; - -/// Returns true if is comptime known that build on save is supported. -pub inline fn isBuildOnSaveSupportedComptime() bool { - if (!std.process.can_spawn) return false; - if (builtin.single_threaded) return false; - return true; -} - -pub fn isBuildOnSaveSupportedRuntime(runtime_zig_version: std.SemanticVersion) bool { - if (!isBuildOnSaveSupportedComptime()) return false; - - if (builtin.os.tag == .linux) blk: { - // std.build.Watch requires `FAN_REPORT_TARGET_FID` which is Linux 5.17+ - const utsname = std.posix.uname(); - const version = std.SemanticVersion.parse(&utsname.release) catch break :blk; - if (version.order(.{ .major = 5, .minor = 17, .patch = 0 }) != .lt) break :blk; - return false; - } - - // This code path is present to support runtime Zig version before `0.14.0-dev.2046+b8795b4d0`. - // The main motivation is to keep support for the latest mach nominated zig version which is `0.14.0-dev.1911+3bf89f55c`. - return switch (builtin.os.tag) { - .linux => true, - .windows => runtime_zig_version.order(windows_support_version) != .lt, - .dragonfly, - .freebsd, - .netbsd, - .openbsd, - .ios, - .macos, - .tvos, - .visionos, - .watchos, - .haiku, - => runtime_zig_version.order(kqueue_support_version) != .lt, - else => false, - }; -} diff --git a/src/features/diagnostics.zig b/src/features/diagnostics.zig index 716f9ac02..09c954b93 100644 --- a/src/features/diagnostics.zig +++ b/src/features/diagnostics.zig @@ -1,6 +1,7 @@ //! Implementation of [`textDocument/publishDiagnostics`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_publishDiagnostics) const std = @import("std"); +const builtin = @import("builtin"); const Ast = std.zig.Ast; const log = std.log.scoped(.diag); @@ -537,6 +538,48 @@ pub const BuildOnSave = struct { self.* = undefined; } + const windows_support_version = std.SemanticVersion.parse("0.14.0-dev.625+2de0e2eca") catch unreachable; + const kqueue_support_version = std.SemanticVersion.parse("0.14.0-dev.2046+b8795b4d0") catch unreachable; + + /// Returns true if is comptime known that build on save is supported. + pub inline fn isSupportedComptime() bool { + if (!std.process.can_spawn) return false; + if (builtin.single_threaded) return false; + return true; + } + + pub fn isSupportedRuntime(runtime_zig_version: std.SemanticVersion) bool { + if (!isSupportedComptime()) return false; + + if (builtin.os.tag == .linux) blk: { + // std.build.Watch requires `FAN_REPORT_TARGET_FID` which is Linux 5.17+ + const utsname = std.posix.uname(); + const version = std.SemanticVersion.parse(&utsname.release) catch break :blk; + if (version.order(.{ .major = 5, .minor = 17, .patch = 0 }) != .lt) break :blk; + return false; + } + + // We can't rely on `std.Build.Watch.have_impl` because we need to + // check the runtime Zig version instead of Zig version that ZLS + // has been built with. + return switch (builtin.os.tag) { + .linux => true, + .windows => runtime_zig_version.order(windows_support_version) != .lt, + .dragonfly, + .freebsd, + .netbsd, + .openbsd, + .ios, + .macos, + .tvos, + .visionos, + .watchos, + .haiku, + => runtime_zig_version.order(kqueue_support_version) != .lt, + else => false, + }; + } + fn loop( self: *BuildOnSave, collection: *DiagnosticsCollection, From c6a70b031e77f9fcb08d0fada780a9c4418f47cb Mon Sep 17 00:00:00 2001 From: Techatrix Date: Thu, 9 Jan 2025 20:10:02 +0100 Subject: [PATCH 2/7] build_runner: collect c macro definitions from build.zig fixes #2110 --- src/DocumentStore.zig | 44 ++++++++++++++++++- src/build_runner/master.zig | 39 +++++++++------- src/build_runner/shared.zig | 1 + src/translate_c.zig | 5 ++- tests/build_runner_cases/add_module.json | 3 +- tests/build_runner_cases/define_c_macro.json | 18 ++++++++ tests/build_runner_cases/define_c_macro.zig | 8 ++++ tests/build_runner_cases/empty.json | 3 +- .../module_self_import.json | 3 +- .../multiple_module_import_names.json | 3 +- tests/language_features/cimport.zig | 8 +++- 11 files changed, 111 insertions(+), 24 deletions(-) create mode 100644 tests/build_runner_cases/define_c_macro.json create mode 100644 tests/build_runner_cases/define_c_macro.zig diff --git a/src/DocumentStore.zig b/src/DocumentStore.zig index 498c0c6f8..6487cf45a 100644 --- a/src/DocumentStore.zig +++ b/src/DocumentStore.zig @@ -1489,6 +1489,34 @@ pub fn collectIncludeDirs( return collected_all; } +/// returns `true` if all c macro definitions could be collected +/// may return `false` because macros from a build.zig may not have been resolved already +/// **Thread safe** takes a shared lock +pub fn collectCMacros( + store: *DocumentStore, + allocator: std.mem.Allocator, + handle: *Handle, + c_macros: *std.ArrayListUnmanaged([]const u8), +) !bool { + const collected_all = switch (try handle.getAssociatedBuildFileUri2(store)) { + .none => true, + .unresolved => false, + .resolved => |build_file_uri| blk: { + const build_file = store.getBuildFile(build_file_uri).?; + const build_config = build_file.tryLockConfig() orelse break :blk false; + defer build_file.unlockConfig(); + + try c_macros.ensureUnusedCapacity(allocator, build_config.c_macros.len); + for (build_config.c_macros) |c_macro| { + c_macros.appendAssumeCapacity(try allocator.dupe(u8, c_macro)); + } + break :blk true; + }, + }; + + return collected_all; +} + /// returns the document behind `@cImport()` where `node` is the `cImport` node /// if a cImport can't be translated e.g. requires computing a /// comptime value `resolveCImport` will return null @@ -1533,10 +1561,24 @@ pub fn resolveCImport(self: *DocumentStore, handle: *Handle, node: Ast.Node.Inde return null; }; + var c_macros: std.ArrayListUnmanaged([]const u8) = .empty; + defer { + for (c_macros.items) |c_macro| { + self.allocator.free(c_macro); + } + c_macros.deinit(self.allocator); + } + + const collected_all_c_macros = self.collectCMacros(self.allocator, handle, &c_macros) catch |err| { + log.err("failed to resolve include paths: {}", .{err}); + return null; + }; + const maybe_result = translate_c.translate( self.allocator, self.config, include_dirs.items, + c_macros.items, source, ) catch |err| switch (err) { error.OutOfMemory => |e| return e, @@ -1547,7 +1589,7 @@ pub fn resolveCImport(self: *DocumentStore, handle: *Handle, node: Ast.Node.Inde }; var result = maybe_result orelse return null; - if (result == .failure and !collected_all_include_dirs) { + if (result == .failure and (!collected_all_include_dirs or !collected_all_c_macros)) { result.deinit(self.allocator); return null; } diff --git a/src/build_runner/master.zig b/src/build_runner/master.zig index 930571b93..fc70c7794 100644 --- a/src/build_runner/master.zig +++ b/src/build_runner/master.zig @@ -1026,13 +1026,19 @@ fn extractBuildInformation( name: []const u8, packages: *Packages, include_dirs: *std.StringArrayHashMapUnmanaged(void), + c_macros: *std.StringArrayHashMapUnmanaged(void), ) !void { if (module.root_source_file) |root_source_file| { _ = try packages.addPackage(name, root_source_file.getPath(module.owner)); } if (compile) |exe| { - try processPkgConfig(allocator, include_dirs, exe); + try processPkgConfig(allocator, include_dirs, c_macros, exe); + } + + try c_macros.ensureUnusedCapacity(allocator, module.c_macros.items.len); + for (module.c_macros.items) |c_macro| { + c_macros.putAssumeCapacity(c_macro, {}); } for (module.include_dirs.items) |include_dir| { @@ -1119,6 +1125,9 @@ fn extractBuildInformation( var include_dirs: std.StringArrayHashMapUnmanaged(void) = .{}; defer include_dirs.deinit(gpa); + var c_macros: std.StringArrayHashMapUnmanaged(void) = .{}; + defer c_macros.deinit(gpa); + var packages: Packages = .{ .allocator = gpa }; defer packages.deinit(); @@ -1127,20 +1136,20 @@ fn extractBuildInformation( for (steps.keys()) |step| { const compile = step.cast(Step.Compile) orelse continue; const graph = compile.root_module.getGraph(); - try helper.processItem(gpa, compile.root_module, compile, "root", &packages, &include_dirs); + try helper.processItem(gpa, compile.root_module, compile, "root", &packages, &include_dirs, &c_macros); for (graph.modules) |module| { for (module.import_table.keys(), module.import_table.values()) |name, import| { - try helper.processItem(gpa, import, null, name, &packages, &include_dirs); + try helper.processItem(gpa, import, null, name, &packages, &include_dirs, &c_macros); } } } for (b.modules.values()) |root_module| { const graph = root_module.getGraph(); - try helper.processItem(gpa, root_module, null, "root", &packages, &include_dirs); + try helper.processItem(gpa, root_module, null, "root", &packages, &include_dirs, &c_macros); for (graph.modules) |module| { for (module.import_table.keys(), module.import_table.values()) |name, import| { - try helper.processItem(gpa, import, null, name, &packages, &include_dirs); + try helper.processItem(gpa, import, null, name, &packages, &include_dirs, &c_macros); } } } @@ -1190,6 +1199,7 @@ fn extractBuildInformation( .include_dirs = include_dirs.keys(), .top_level_steps = b.top_level_steps.keys(), .available_options = available_options, + .c_macros = c_macros.keys(), }, .{ .whitespace = .indent_2, @@ -1201,6 +1211,7 @@ fn extractBuildInformation( fn processPkgConfig( allocator: std.mem.Allocator, include_dirs: *std.StringArrayHashMapUnmanaged(void), + c_macros: *std.StringArrayHashMapUnmanaged(void), exe: *Step.Compile, ) !void { for (exe.root_module.link_objects.items) |link_object| { @@ -1209,7 +1220,7 @@ fn processPkgConfig( if (system_lib.use_pkg_config == .no) continue; - getPkgConfigIncludes(allocator, include_dirs, exe, system_lib.name) catch |err| switch (err) { + const args = copied_from_zig.runPkgConfig(exe, system_lib.name) catch |err| switch (err) { error.PkgConfigInvalidOutput, error.PkgConfigCrashed, error.PkgConfigFailed, @@ -1218,31 +1229,25 @@ fn processPkgConfig( => switch (system_lib.use_pkg_config) { .yes => { // pkg-config failed, so zig will not add any include paths + continue; }, .force => { std.log.warn("pkg-config failed for library {s}", .{system_lib.name}); + continue; }, .no => unreachable, }, else => |e| return e, }; - } -} - -fn getPkgConfigIncludes( - allocator: std.mem.Allocator, - include_dirs: *std.StringArrayHashMapUnmanaged(void), - exe: *Step.Compile, - name: []const u8, -) !void { - if (copied_from_zig.runPkgConfig(exe, name)) |args| { for (args) |arg| { if (std.mem.startsWith(u8, arg, "-I")) { const candidate = arg[2..]; try include_dirs.put(allocator, candidate, {}); + } else if (std.mem.startsWith(u8, arg, "-D")) { + try c_macros.put(allocator, arg, {}); } } - } else |err| return err; + } } // TODO: Having a copy of this is not very nice diff --git a/src/build_runner/shared.zig b/src/build_runner/shared.zig index 18b9d64e4..792500e09 100644 --- a/src/build_runner/shared.zig +++ b/src/build_runner/shared.zig @@ -11,6 +11,7 @@ pub const BuildConfig = struct { include_dirs: []const []const u8, top_level_steps: []const []const u8, available_options: std.json.ArrayHashMap(AvailableOption), + c_macros: []const []const u8 = &.{}, pub const DepsBuildRoots = Package; pub const Package = struct { diff --git a/src/translate_c.zig b/src/translate_c.zig index 318302c90..4f16137cb 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -118,6 +118,7 @@ pub fn translate( allocator: std.mem.Allocator, config: Config, include_dirs: []const []const u8, + c_macros: []const []const u8, source: []const u8, ) !?Result { const tracy_zone = tracy.trace(@src()); @@ -166,7 +167,7 @@ pub fn translate( "--listen=-", }; - const argc = base_args.len + 2 * include_dirs.len + 1; + const argc = base_args.len + 2 * include_dirs.len + c_macros.len + 1; var argv: std.ArrayListUnmanaged([]const u8) = try .initCapacity(allocator, argc); defer argv.deinit(allocator); @@ -177,6 +178,8 @@ pub fn translate( argv.appendAssumeCapacity(include_dir); } + argv.appendSliceAssumeCapacity(c_macros); + argv.appendAssumeCapacity(file_path); var process: std.process.Child = .init(argv.items, allocator); diff --git a/tests/build_runner_cases/add_module.json b/tests/build_runner_cases/add_module.json index fa62c8df9..a51926a19 100644 --- a/tests/build_runner_cases/add_module.json +++ b/tests/build_runner_cases/add_module.json @@ -11,5 +11,6 @@ "install", "uninstall" ], - "available_options": {} + "available_options": {}, + "c_macros": [] } \ No newline at end of file diff --git a/tests/build_runner_cases/define_c_macro.json b/tests/build_runner_cases/define_c_macro.json new file mode 100644 index 000000000..b61f3eff0 --- /dev/null +++ b/tests/build_runner_cases/define_c_macro.json @@ -0,0 +1,18 @@ +{ + "deps_build_roots": [], + "packages": [ + { + "name": "root", + "path": "root.zig" + } + ], + "include_dirs": [], + "top_level_steps": [ + "install", + "uninstall" + ], + "available_options": {}, + "c_macros": [ + "-Dkey=value" + ] +} \ No newline at end of file diff --git a/tests/build_runner_cases/define_c_macro.zig b/tests/build_runner_cases/define_c_macro.zig new file mode 100644 index 000000000..c1cc03fdd --- /dev/null +++ b/tests/build_runner_cases/define_c_macro.zig @@ -0,0 +1,8 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const foo = b.addModule("foo", .{ + .root_source_file = b.path("root.zig"), + }); + foo.addCMacro("key", "value"); +} diff --git a/tests/build_runner_cases/empty.json b/tests/build_runner_cases/empty.json index 8b55ee76a..bc46f1e1c 100644 --- a/tests/build_runner_cases/empty.json +++ b/tests/build_runner_cases/empty.json @@ -6,5 +6,6 @@ "install", "uninstall" ], - "available_options": {} + "available_options": {}, + "c_macros": [] } \ No newline at end of file diff --git a/tests/build_runner_cases/module_self_import.json b/tests/build_runner_cases/module_self_import.json index b6149690a..2c9d4df8c 100644 --- a/tests/build_runner_cases/module_self_import.json +++ b/tests/build_runner_cases/module_self_import.json @@ -15,5 +15,6 @@ "install", "uninstall" ], - "available_options": {} + "available_options": {}, + "c_macros": [] } \ No newline at end of file diff --git a/tests/build_runner_cases/multiple_module_import_names.json b/tests/build_runner_cases/multiple_module_import_names.json index 0efa251a3..35d25ffb0 100644 --- a/tests/build_runner_cases/multiple_module_import_names.json +++ b/tests/build_runner_cases/multiple_module_import_names.json @@ -35,5 +35,6 @@ "install", "uninstall" ], - "available_options": {} + "available_options": {}, + "c_macros": [] } \ No newline at end of file diff --git a/tests/language_features/cimport.zig b/tests/language_features/cimport.zig index b74d28afa..1e97c96b0 100644 --- a/tests/language_features/cimport.zig +++ b/tests/language_features/cimport.zig @@ -109,7 +109,13 @@ fn testTranslate(c_source: []const u8) !translate_c.Result { var ctx: Context = try .init(); defer ctx.deinit(); - var result = (try translate_c.translate(allocator, zls.DocumentStore.Config.fromMainConfig(ctx.server.config), &.{}, c_source)).?; + var result = (try translate_c.translate( + allocator, + zls.DocumentStore.Config.fromMainConfig(ctx.server.config), + &.{}, + &.{}, + c_source, + )).?; errdefer result.deinit(allocator); switch (result) { From 9374ec1bd41f5e327e3d30cc032e7d74b7853ba3 Mon Sep 17 00:00:00 2001 From: Techatrix Date: Wed, 15 Jan 2025 18:02:17 +0100 Subject: [PATCH 3/7] =?UTF-8?q?build=5Frunner:=20handle=20non=20semver=20?= =?UTF-8?q?=C4=BAinux=20kernel=20release=20versions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Server.zig | 7 +++--- src/features/diagnostics.zig | 41 ++++++++++++++++++++++++++++++++---- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/Server.zig b/src/Server.zig index 241463484..a62c07389 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -763,7 +763,7 @@ const Workspace = struct { }) error{OutOfMemory}!void { comptime std.debug.assert(BuildOnSave.isSupportedComptime()); - const build_on_save_supported = if (args.runtime_zig_version) |version| BuildOnSave.isSupportedRuntime(version) else false; + const build_on_save_supported = if (args.runtime_zig_version) |version| BuildOnSave.isSupportedRuntime(version, false) else false; 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; @@ -1080,9 +1080,8 @@ pub fn updateConfiguration( log.warn("'enable_build_on_save' is ignored because it is not supported by {s}", .{server.client_capabilities.client_name orelse "your editor"}); } else if (server.status == .initialized and options.resolve and resolve_result.build_runner_version == .unresolved and server.config.build_runner_path == null) { log.warn("'enable_build_on_save' is ignored because no build runner is available", .{}); - } else if (server.status == .initialized and options.resolve and resolve_result.zig_runtime_version != null and !BuildOnSave.isSupportedRuntime(resolve_result.zig_runtime_version.?)) { - // There is one edge-case where build on save is not supported because of Linux pre 5.17 - log.warn("'enable_build_on_save' is not supported by Zig {}", .{resolve_result.zig_runtime_version.?}); + } else if (server.status == .initialized and options.resolve and resolve_result.zig_runtime_version != null and !BuildOnSave.isSupportedRuntime(resolve_result.zig_runtime_version.?, true)) { + // already logged } } diff --git a/src/features/diagnostics.zig b/src/features/diagnostics.zig index 09c954b93..4fae81ce5 100644 --- a/src/features/diagnostics.zig +++ b/src/features/diagnostics.zig @@ -548,21 +548,25 @@ pub const BuildOnSave = struct { return true; } - pub fn isSupportedRuntime(runtime_zig_version: std.SemanticVersion) bool { - if (!isSupportedComptime()) return false; + pub fn isSupportedRuntime(runtime_zig_version: std.SemanticVersion, log_message: bool) bool { + comptime std.debug.assert(isSupportedComptime()); if (builtin.os.tag == .linux) blk: { // std.build.Watch requires `FAN_REPORT_TARGET_FID` which is Linux 5.17+ const utsname = std.posix.uname(); - const version = std.SemanticVersion.parse(&utsname.release) catch break :blk; + const version = parseUnameKernelVersion(&utsname.release) catch |err| { + if (log_message) log.warn("failed to parse kernel version '{s}': {}", .{ utsname.release, err }); + break :blk; + }; if (version.order(.{ .major = 5, .minor = 17, .patch = 0 }) != .lt) break :blk; + if (log_message) log.err("Build-On-Save is not supported by Linux '{s}' (requires 5.17+)", .{utsname.release}); return false; } // We can't rely on `std.Build.Watch.have_impl` because we need to // check the runtime Zig version instead of Zig version that ZLS // has been built with. - return switch (builtin.os.tag) { + const zig_version_supported = switch (builtin.os.tag) { .linux => true, .windows => runtime_zig_version.order(windows_support_version) != .lt, .dragonfly, @@ -578,6 +582,35 @@ pub const BuildOnSave = struct { => runtime_zig_version.order(kqueue_support_version) != .lt, else => false, }; + + if (!zig_version_supported) { + log.warn("Build-On-Save is not supported on {s} by Zig {}", .{ @tagName(builtin.os.tag), runtime_zig_version }); + } + + return zig_version_supported; + } + + fn parseUnameKernelVersion(kernel_version: []const u8) !std.SemanticVersion { + const extra_index = std.mem.indexOfAny(u8, kernel_version, "-+"); + const required = kernel_version[0..(extra_index orelse kernel_version.len)]; + var it = std.mem.splitScalar(u8, required, '.'); + return .{ + .major = try std.fmt.parseUnsigned(usize, it.next() orelse return error.InvalidVersion, 10), + .minor = try std.fmt.parseUnsigned(usize, it.next() orelse return error.InvalidVersion, 10), + .patch = try std.fmt.parseUnsigned(usize, it.next() orelse return error.InvalidVersion, 10), + }; + } + + test parseUnameKernelVersion { + try std.testing.expectFmt("5.17.0", "{}", .{try parseUnameKernelVersion("5.17.0")}); + try std.testing.expectFmt("6.12.9", "{}", .{try parseUnameKernelVersion("6.12.9-rc7")}); + try std.testing.expectFmt("6.6.71", "{}", .{try parseUnameKernelVersion("6.6.71-42-generic")}); + try std.testing.expectFmt("5.15.167", "{}", .{try parseUnameKernelVersion("5.15.167.4-microsoft-standard-WSL2")}); // WSL2 + try std.testing.expectFmt("4.4.0", "{}", .{try parseUnameKernelVersion("4.4.0-20241-Microsoft")}); // WSL1 + + try std.testing.expectError(error.InvalidCharacter, parseUnameKernelVersion("")); + try std.testing.expectError(error.InvalidVersion, parseUnameKernelVersion("5")); + try std.testing.expectError(error.InvalidVersion, parseUnameKernelVersion("5.5")); } fn loop( From 99f3164a7450a5b79e634b1789bc3e6475a94372 Mon Sep 17 00:00:00 2001 From: Techatrix Date: Wed, 15 Jan 2025 19:08:23 +0100 Subject: [PATCH 4/7] set the minimum linux kernel version for build on save to 6.5 https://github.com/ziglang/zig/issues/20720 --- src/features/diagnostics.zig | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/features/diagnostics.zig b/src/features/diagnostics.zig index 4fae81ce5..c209e19bb 100644 --- a/src/features/diagnostics.zig +++ b/src/features/diagnostics.zig @@ -552,14 +552,17 @@ pub const BuildOnSave = struct { comptime std.debug.assert(isSupportedComptime()); if (builtin.os.tag == .linux) blk: { - // std.build.Watch requires `FAN_REPORT_TARGET_FID` which is Linux 5.17+ + // std.build.Watch requires `AT_HANDLE_FID` which is Linux 6.5+ + // https://github.com/ziglang/zig/issues/20720 + const minimum_linux_version: std.SemanticVersion = .{ .major = 6, .minor = 5, .patch = 0 }; + const utsname = std.posix.uname(); const version = parseUnameKernelVersion(&utsname.release) catch |err| { if (log_message) log.warn("failed to parse kernel version '{s}': {}", .{ utsname.release, err }); break :blk; }; - if (version.order(.{ .major = 5, .minor = 17, .patch = 0 }) != .lt) break :blk; - if (log_message) log.err("Build-On-Save is not supported by Linux '{s}' (requires 5.17+)", .{utsname.release}); + if (version.order(minimum_linux_version) != .lt) break :blk; + if (log_message) log.err("Build-On-Save is not supported by Linux '{s}' (requires at least {})", .{ utsname.release, minimum_linux_version }); return false; } From b881f6c862e592c4f7cff68dc1a95cb1c0ebec1f Mon Sep 17 00:00:00 2001 From: Techatrix Date: Tue, 21 Jan 2025 20:27:02 +0100 Subject: [PATCH 5/7] improve log messages when build on save is unavailable --- src/Server.zig | 26 +++++++---- src/build_runner/shared.zig | 90 ++++++++++++++++++++++++++++++++++++ src/features/diagnostics.zig | 78 ------------------------------- 3 files changed, 107 insertions(+), 87 deletions(-) diff --git a/src/Server.zig b/src/Server.zig index a62c07389..a90957fc7 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -37,6 +37,7 @@ const selection_range = @import("features/selection_range.zig"); const diagnostics_gen = @import("features/diagnostics.zig"); const BuildOnSave = diagnostics_gen.BuildOnSave; +const BuildOnSaveSupport = build_runner_shared.BuildOnSaveSupport; const log = std.log.scoped(.server); @@ -735,7 +736,7 @@ fn handleConfiguration(server: *Server, json: std.json.Value) error{OutOfMemory} const Workspace = struct { uri: types.URI, - build_on_save: if (BuildOnSave.isSupportedComptime()) ?BuildOnSave else void, + build_on_save: if (BuildOnSaveSupport.isSupportedComptime()) ?BuildOnSave else void, fn init(server: *Server, uri: types.URI) error{OutOfMemory}!Workspace { const duped_uri = try server.allocator.dupe(u8, uri); @@ -743,12 +744,12 @@ const Workspace = struct { return .{ .uri = duped_uri, - .build_on_save = if (BuildOnSave.isSupportedComptime()) null else {}, + .build_on_save = if (BuildOnSaveSupport.isSupportedComptime()) null else {}, }; } fn deinit(workspace: *Workspace, allocator: std.mem.Allocator) void { - if (BuildOnSave.isSupportedComptime()) { + if (BuildOnSaveSupport.isSupportedComptime()) { if (workspace.build_on_save) |*build_on_save| build_on_save.deinit(); } allocator.free(workspace.uri); @@ -761,9 +762,9 @@ const Workspace = struct { /// Whether the build on save process should be restarted if it is already running. restart: bool, }) error{OutOfMemory}!void { - comptime std.debug.assert(BuildOnSave.isSupportedComptime()); + comptime std.debug.assert(BuildOnSaveSupport.isSupportedComptime()); - const build_on_save_supported = if (args.runtime_zig_version) |version| BuildOnSave.isSupportedRuntime(version, false) else false; + const build_on_save_supported = if (args.runtime_zig_version) |version| BuildOnSaveSupport.isSupportedRuntime(version) == .supported else false; 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; @@ -972,7 +973,7 @@ pub fn updateConfiguration( } } - if (BuildOnSave.isSupportedComptime() and + if (BuildOnSaveSupport.isSupportedComptime() and options.resolve and // If the client supports the `workspace/configuration` request, defer // build on save initialization until after we have received workspace @@ -1071,7 +1072,9 @@ pub fn updateConfiguration( } if (server.config.enable_build_on_save orelse false) { - if (!BuildOnSave.isSupportedComptime()) { + 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()) { // 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,8 +1083,13 @@ pub fn updateConfiguration( log.warn("'enable_build_on_save' is ignored because it is not supported by {s}", .{server.client_capabilities.client_name orelse "your editor"}); } else if (server.status == .initialized and options.resolve and resolve_result.build_runner_version == .unresolved and server.config.build_runner_path == null) { log.warn("'enable_build_on_save' is ignored because no build runner is available", .{}); - } else if (server.status == .initialized and options.resolve and resolve_result.zig_runtime_version != null and !BuildOnSave.isSupportedRuntime(resolve_result.zig_runtime_version.?, true)) { - // already logged + } 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 }), + } } } diff --git a/src/build_runner/shared.zig b/src/build_runner/shared.zig index 792500e09..65c505595 100644 --- a/src/build_runner/shared.zig +++ b/src/build_runner/shared.zig @@ -145,3 +145,93 @@ pub const ServerToClient = struct { string_bytes_len: u32, }; }; + +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, + + 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; + const kqueue_support_version = std.SemanticVersion.parse("0.14.0-dev.2046+b8795b4d0") catch unreachable; + + // We can't rely on `std.Build.Watch.have_impl` because we need to + // check the runtime Zig version instead of Zig version that ZLS + // has been built with. + pub const minimum_zig_version = switch (builtin.os.tag) { + .linux => linux_support_version, + .windows => windows_support_version, + .dragonfly, + .freebsd, + .netbsd, + .openbsd, + .ios, + .macos, + .tvos, + .visionos, + .watchos, + .haiku, + => kqueue_support_version, + else => {}, + }; + + /// std.build.Watch requires `AT_HANDLE_FID` which is Linux 6.5+ + /// https://github.com/ziglang/zig/issues/20720 + pub const minimum_linux_version: std.SemanticVersion = .{ .major = 6, .minor = 5, .patch = 0 }; + + /// Returns true if is comptime known that build on save is supported. + 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; + } + + pub fn isSupportedRuntime(runtime_zig_version: std.SemanticVersion) BuildOnSaveSupport { + comptime std.debug.assert(isSupportedComptime()); + + if (builtin.os.tag == .linux) blk: { + const utsname = std.posix.uname(); + const unparsed_version = std.mem.sliceTo(&utsname.release, 0); + const version = parseUnameKernelVersion(unparsed_version) catch + return .{ .invalid_linux_kernel_version = utsname.release }; + + if (version.order(minimum_linux_version) != .lt) break :blk; + std.debug.assert(version.build == null and version.pre == null); // Otherwise, returning the `std.SemanticVersion` would be unsafe + return .{ + .unsupported_linux_kernel_version = version, + }; + } + + if (runtime_zig_version.order(minimum_zig_version) == .lt) { + return .unsupported_zig_version; + } + + return .supported; + } +}; + +/// Parses a Linux Kernel Version. The result will ignore pre-release and build metadata. +fn parseUnameKernelVersion(kernel_version: []const u8) !std.SemanticVersion { + const extra_index = std.mem.indexOfAny(u8, kernel_version, "-+"); + const required = kernel_version[0..(extra_index orelse kernel_version.len)]; + var it = std.mem.splitScalar(u8, required, '.'); + return .{ + .major = try std.fmt.parseUnsigned(usize, it.next() orelse return error.InvalidVersion, 10), + .minor = try std.fmt.parseUnsigned(usize, it.next() orelse return error.InvalidVersion, 10), + .patch = try std.fmt.parseUnsigned(usize, it.next() orelse return error.InvalidVersion, 10), + }; +} + +test parseUnameKernelVersion { + try std.testing.expectFmt("5.17.0", "{}", .{try parseUnameKernelVersion("5.17.0")}); + try std.testing.expectFmt("6.12.9", "{}", .{try parseUnameKernelVersion("6.12.9-rc7")}); + try std.testing.expectFmt("6.6.71", "{}", .{try parseUnameKernelVersion("6.6.71-42-generic")}); + try std.testing.expectFmt("5.15.167", "{}", .{try parseUnameKernelVersion("5.15.167.4-microsoft-standard-WSL2")}); // WSL2 + try std.testing.expectFmt("4.4.0", "{}", .{try parseUnameKernelVersion("4.4.0-20241-Microsoft")}); // WSL1 + + try std.testing.expectError(error.InvalidCharacter, parseUnameKernelVersion("")); + try std.testing.expectError(error.InvalidVersion, parseUnameKernelVersion("5")); + try std.testing.expectError(error.InvalidVersion, parseUnameKernelVersion("5.5")); +} diff --git a/src/features/diagnostics.zig b/src/features/diagnostics.zig index c209e19bb..b3ef9ad63 100644 --- a/src/features/diagnostics.zig +++ b/src/features/diagnostics.zig @@ -538,84 +538,6 @@ pub const BuildOnSave = struct { self.* = undefined; } - const windows_support_version = std.SemanticVersion.parse("0.14.0-dev.625+2de0e2eca") catch unreachable; - const kqueue_support_version = std.SemanticVersion.parse("0.14.0-dev.2046+b8795b4d0") catch unreachable; - - /// Returns true if is comptime known that build on save is supported. - pub inline fn isSupportedComptime() bool { - if (!std.process.can_spawn) return false; - if (builtin.single_threaded) return false; - return true; - } - - pub fn isSupportedRuntime(runtime_zig_version: std.SemanticVersion, log_message: bool) bool { - comptime std.debug.assert(isSupportedComptime()); - - if (builtin.os.tag == .linux) blk: { - // std.build.Watch requires `AT_HANDLE_FID` which is Linux 6.5+ - // https://github.com/ziglang/zig/issues/20720 - const minimum_linux_version: std.SemanticVersion = .{ .major = 6, .minor = 5, .patch = 0 }; - - const utsname = std.posix.uname(); - const version = parseUnameKernelVersion(&utsname.release) catch |err| { - if (log_message) log.warn("failed to parse kernel version '{s}': {}", .{ utsname.release, err }); - break :blk; - }; - if (version.order(minimum_linux_version) != .lt) break :blk; - if (log_message) log.err("Build-On-Save is not supported by Linux '{s}' (requires at least {})", .{ utsname.release, minimum_linux_version }); - return false; - } - - // We can't rely on `std.Build.Watch.have_impl` because we need to - // check the runtime Zig version instead of Zig version that ZLS - // has been built with. - const zig_version_supported = switch (builtin.os.tag) { - .linux => true, - .windows => runtime_zig_version.order(windows_support_version) != .lt, - .dragonfly, - .freebsd, - .netbsd, - .openbsd, - .ios, - .macos, - .tvos, - .visionos, - .watchos, - .haiku, - => runtime_zig_version.order(kqueue_support_version) != .lt, - else => false, - }; - - if (!zig_version_supported) { - log.warn("Build-On-Save is not supported on {s} by Zig {}", .{ @tagName(builtin.os.tag), runtime_zig_version }); - } - - return zig_version_supported; - } - - fn parseUnameKernelVersion(kernel_version: []const u8) !std.SemanticVersion { - const extra_index = std.mem.indexOfAny(u8, kernel_version, "-+"); - const required = kernel_version[0..(extra_index orelse kernel_version.len)]; - var it = std.mem.splitScalar(u8, required, '.'); - return .{ - .major = try std.fmt.parseUnsigned(usize, it.next() orelse return error.InvalidVersion, 10), - .minor = try std.fmt.parseUnsigned(usize, it.next() orelse return error.InvalidVersion, 10), - .patch = try std.fmt.parseUnsigned(usize, it.next() orelse return error.InvalidVersion, 10), - }; - } - - test parseUnameKernelVersion { - try std.testing.expectFmt("5.17.0", "{}", .{try parseUnameKernelVersion("5.17.0")}); - try std.testing.expectFmt("6.12.9", "{}", .{try parseUnameKernelVersion("6.12.9-rc7")}); - try std.testing.expectFmt("6.6.71", "{}", .{try parseUnameKernelVersion("6.6.71-42-generic")}); - try std.testing.expectFmt("5.15.167", "{}", .{try parseUnameKernelVersion("5.15.167.4-microsoft-standard-WSL2")}); // WSL2 - try std.testing.expectFmt("4.4.0", "{}", .{try parseUnameKernelVersion("4.4.0-20241-Microsoft")}); // WSL1 - - try std.testing.expectError(error.InvalidCharacter, parseUnameKernelVersion("")); - try std.testing.expectError(error.InvalidVersion, parseUnameKernelVersion("5")); - try std.testing.expectError(error.InvalidVersion, parseUnameKernelVersion("5.5")); - } - fn loop( self: *BuildOnSave, collection: *DiagnosticsCollection, From 932c7a62f6a250d2a0412b218aceffea0878ca9a Mon Sep 17 00:00:00 2001 From: Techatrix Date: Wed, 22 Jan 2025 01:21:34 +0100 Subject: [PATCH 6/7] refactor build on save implementation --- src/Server.zig | 5 +-- src/features/diagnostics.zig | 81 +++++++++++++++++------------------- 2 files changed, 40 insertions(+), 46 deletions(-) diff --git a/src/Server.zig b/src/Server.zig index a90957fc7..a6e823acf 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -787,8 +787,8 @@ const Workspace = struct { }; defer args.server.allocator.free(workspace_path); - workspace.build_on_save = @as(BuildOnSave, undefined); - workspace.build_on_save.?.init(.{ + std.debug.assert(workspace.build_on_save == null); + workspace.build_on_save = BuildOnSave.init(.{ .allocator = args.server.allocator, .workspace_path = workspace_path, .build_on_save_args = args.server.config.build_on_save_args, @@ -798,7 +798,6 @@ const Workspace = struct { .build_runner_path = build_runner_path, .collection = &args.server.diagnostics_collection, }) catch |err| { - workspace.build_on_save = null; log.err("failed to initilize Build-On-Save for '{s}': {}", .{ workspace.uri, err }); return; }; diff --git a/src/features/diagnostics.zig b/src/features/diagnostics.zig index b3ef9ad63..eccbc1adb 100644 --- a/src/features/diagnostics.zig +++ b/src/features/diagnostics.zig @@ -431,10 +431,9 @@ fn getErrorBundleFromAstCheck( return try error_bundle.toOwnedBundle(""); } -/// This struct is not relocatable after initilization. pub const BuildOnSave = struct { allocator: std.mem.Allocator, - child_process: std.process.Child, + child_process: *std.process.Child, thread: std.Thread, const shared = @import("../build_runner/shared.zig"); @@ -453,8 +452,9 @@ pub const BuildOnSave = struct { collection: *DiagnosticsCollection, }; - pub fn init(self: *BuildOnSave, options: InitOptions) !void { - self.* = undefined; + pub fn init(options: InitOptions) !?BuildOnSave { + const child_process = try options.allocator.create(std.process.Child); + errdefer options.allocator.destroy(child_process); const base_args: []const []const u8 = &.{ options.zig_exe_path, @@ -475,59 +475,54 @@ pub const BuildOnSave = struct { if (options.check_step_only) argv.appendAssumeCapacity("--check-only"); argv.appendSliceAssumeCapacity(options.build_on_save_args); - var child_process: std.process.Child = .init(argv.items, options.allocator); + child_process.* = .init(argv.items, options.allocator); child_process.stdin_behavior = .Pipe; child_process.stdout_behavior = .Pipe; child_process.stderr_behavior = .Pipe; child_process.cwd = options.workspace_path; child_process.spawn() catch |err| { + options.allocator.destroy(child_process); log.err("failed to spawn zig build process: {}", .{err}); - return; + return null; }; errdefer { _ = terminateChildProcessReportError( - &child_process, + child_process, options.allocator, "zig build runner", .kill, ); } - self.* = .{ - .allocator = options.allocator, - .child_process = child_process, - .thread = undefined, // set below - }; - const duped_workspace_path = try options.allocator.dupe(u8, options.workspace_path); errdefer options.allocator.free(duped_workspace_path); - self.thread = try std.Thread.spawn(.{ .allocator = options.allocator }, loop, .{ - self, + const thread = try std.Thread.spawn(.{ .allocator = options.allocator }, loop, .{ + options.allocator, + child_process, options.collection, duped_workspace_path, }); + errdefer comptime unreachable; + + return .{ + .allocator = options.allocator, + .child_process = child_process, + .thread = thread, + }; } pub fn deinit(self: *BuildOnSave) void { - // this write tells the child process to exit - self.child_process.stdin.?.writeAll("\xaa") catch |err| { - if (err != error.BrokenPipe) { - log.warn("failed to send message to zig build runner: {}", .{err}); - } - _ = terminateChildProcessReportError( - &self.child_process, - self.allocator, - "zig build runner", - .kill, - ); - return; - }; + defer self.* = undefined; + defer self.allocator.destroy(self.child_process); + + self.child_process.stdin.?.close(); + self.child_process.stdin = null; const success = terminateChildProcessReportError( - &self.child_process, + self.child_process, self.allocator, "zig build runner", .wait, @@ -535,25 +530,25 @@ pub const BuildOnSave = struct { if (!success) return; self.thread.join(); - self.* = undefined; } fn loop( - self: *BuildOnSave, + allocator: std.mem.Allocator, + child_process: *std.process.Child, collection: *DiagnosticsCollection, workspace_path: []const u8, ) void { - defer self.allocator.free(workspace_path); + defer allocator.free(workspace_path); var transport: Transport = .init(.{ - .gpa = self.allocator, - .in = self.child_process.stdout.?, - .out = self.child_process.stdin.?, + .gpa = allocator, + .in = child_process.stdout.?, + .out = child_process.stdin.?, }); defer transport.deinit(); var diagnostic_tags: std.AutoArrayHashMapUnmanaged(DiagnosticsCollection.Tag, void) = .empty; - defer diagnostic_tags.deinit(self.allocator); + defer diagnostic_tags.deinit(allocator); defer { for (diagnostic_tags.keys()) |tag| collection.clearErrorBundle(tag); @@ -575,7 +570,7 @@ pub const BuildOnSave = struct { switch (@as(ServerToClient.Tag, @enumFromInt(header.tag))) { .watch_error_bundle => { handleWatchErrorBundle( - self, + allocator, &transport, collection, workspace_path, @@ -593,7 +588,7 @@ pub const BuildOnSave = struct { } fn handleWatchErrorBundle( - self: *BuildOnSave, + allocator: std.mem.Allocator, transport: *Transport, collection: *DiagnosticsCollection, workspace_path: []const u8, @@ -601,11 +596,11 @@ pub const BuildOnSave = struct { ) !void { const header = try transport.reader().readStructEndian(ServerToClient.ErrorBundle, .little); - const extra = try transport.receiveSlice(self.allocator, u32, header.extra_len); - defer self.allocator.free(extra); + const extra = try transport.receiveSlice(allocator, u32, header.extra_len); + defer allocator.free(extra); - const string_bytes = try transport.receiveBytes(self.allocator, header.string_bytes_len); - defer self.allocator.free(string_bytes); + const string_bytes = try transport.receiveBytes(allocator, header.string_bytes_len); + defer allocator.free(string_bytes); const error_bundle: std.zig.ErrorBundle = .{ .string_bytes = string_bytes, .extra = extra }; @@ -615,7 +610,7 @@ pub const BuildOnSave = struct { const diagnostic_tag: DiagnosticsCollection.Tag = @enumFromInt(@as(u32, @truncate(hasher.final()))); - try diagnostic_tags.put(self.allocator, diagnostic_tag, {}); + try diagnostic_tags.put(allocator, diagnostic_tag, {}); try collection.pushErrorBundle(diagnostic_tag, header.cycle, workspace_path, error_bundle); try collection.publishDiagnostics(); From 55c97feefab8186418053d965d06545472cdc890 Mon Sep 17 00:00:00 2001 From: Techatrix Date: Fri, 24 Jan 2025 16:53:12 +0100 Subject: [PATCH 7/7] 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 a6e823acf..dbc6bfea9 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -737,6 +737,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); @@ -745,6 +746,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 {}, }; } @@ -755,6 +757,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 @@ -764,7 +776,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; @@ -1071,9 +1093,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)) { @@ -1085,9 +1105,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)}), } } } @@ -1463,6 +1484,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,