Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/DiagnosticsCollection.zig
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ pub const Tag = enum(u32) {
/// - ast-check
/// - warn_style
parse,
/// - Build On Save
/// - Build Runner
/// - build system
_,
};

Expand Down
72 changes: 45 additions & 27 deletions src/DocumentStore.zig
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ const tracy = @import("tracy");
const DocumentScope = @import("DocumentScope.zig");
const DiagnosticsCollection = @import("DiagnosticsCollection.zig");
const TrigramStore = @import("TrigramStore.zig");

const BuildConfig = @compileError("https://github.com/zigtools/zls/issues/3208");
const bsp = @import("bsp.zig");

const DocumentStore = @This();

Expand All @@ -35,13 +34,16 @@ lsp_capabilities: struct {
supports_inlay_hints_refresh: bool = false,
} = .{},

/// https://github.com/zigtools/zls/issues/3208
pub const supports_build_system = false and std.process.can_spawn;
pub const supports_build_system = std.process.can_spawn;

pub const Config = struct {
environ_map: *const std.process.Environ.Map,
zig_exe_path: ?[]const u8,
zig_lib_dir: ?std.Build.Cache.Directory,
zig_cache_dir: switch (builtin.os.tag) {
.wasi => void,
else => ?std.Build.Cache.Directory,
},
builtin_path: ?[]const u8,
global_cache_dir: ?std.Build.Cache.Directory,
wasi_preopens: switch (builtin.os.tag) {
Expand All @@ -61,11 +63,7 @@ pub const BuildFile = struct {
mutex: std.Io.Mutex = .init,
build_runner_state: BuildRunnerState = .idle,
version: u32 = 0,
/// contains information extracted from running build.zig with a custom build runner
/// e.g. include paths & packages
/// TODO this field should not be nullable, callsites should await the build config to be resolved
/// and then continue instead of dealing with missing information.
config: ?std.json.Parsed(BuildConfig) = null,
config: ?bsp.BuildConfig = null,
} = .{},

const BuildRunnerState = enum {
Expand All @@ -74,9 +72,9 @@ pub const BuildFile = struct {
running_but_already_invalidated,
};

pub fn tryLockConfig(self: *BuildFile, io: std.Io) ?BuildConfig {
pub fn tryLockConfig(self: *BuildFile, io: std.Io) ?bsp.BuildConfig {
self.impl.mutex.lockUncancelable(io);
return if (self.impl.config) |cfg| cfg.value else {
return self.impl.config orelse {
self.impl.mutex.unlock(io);
return null;
};
Expand Down Expand Up @@ -152,7 +150,7 @@ pub const BuildFile = struct {

fn deinit(self: *BuildFile, allocator: std.mem.Allocator) void {
self.uri.deinit(allocator);
if (self.impl.config) |cfg| cfg.deinit();
if (self.impl.config) |*cfg| cfg.deinit(allocator);
if (self.builtin_uri) |builtin_uri| builtin_uri.deinit(allocator);
if (self.build_associated_config) |cfg| cfg.deinit();
}
Expand Down Expand Up @@ -921,6 +919,7 @@ pub fn invalidateBuildFile(self: *DocumentStore, build_file_uri: Uri) void {
if (self.config.zig_exe_path == null) return;
if (self.config.global_cache_dir == null) return;
if (self.config.zig_lib_dir == null) return;
if (self.config.zig_cache_dir == null) return;

const build_file = self.getBuildFile(build_file_uri) orelse return;

Expand Down Expand Up @@ -1083,14 +1082,19 @@ fn notifyBuildStart(self: *DocumentStore) void {
},
};

const params = .{
.token = progress_token,
.value = lsp.types.window.work_done_progress.Begin{
.title = "Loading build configuration",
},
};

transport.writeNotification(
self.io,
self.allocator,
"$/progress",
lsp.types.window.work_done_progress.Begin,
.{
.title = "Loading build configuration",
},
@TypeOf(params),
params,
.{ .emit_null_optional_fields = false },
) catch |err| switch (err) {
error.Canceled => unreachable,
Expand All @@ -1117,17 +1121,22 @@ fn notifyBuildEnd(self: *DocumentStore, status: EndStatus) void {
const old_cancel_protect = self.io.swapCancelProtection(.blocked);
defer _ = self.io.swapCancelProtection(old_cancel_protect);

transport.writeNotification(
self.io,
self.allocator,
"$/progress",
lsp.types.window.work_done_progress.End,
.{
const params = .{
.token = progress_token,
.value = lsp.types.window.work_done_progress.End{
.message = switch (status) {
.failed => "Failed",
.success => "Success",
},
},
};

transport.writeNotification(
self.io,
self.allocator,
"$/progress",
@TypeOf(params),
params,
.{ .emit_null_optional_fields = false },
) catch |err| switch (err) {
error.Canceled => unreachable,
Expand Down Expand Up @@ -1162,11 +1171,20 @@ fn invalidateBuildFileWorker(self: *DocumentStore, build_file: *BuildFile) std.I
build_file.impl.version += 1;
const new_version = build_file.impl.version;

const loadBuildConfiguration = if (true) @compileError("https://github.com/zigtools/zls/issues/3208");
const build_config = loadBuildConfiguration(self, build_file.uri, new_version) catch |err| switch (err) {
var build_config = bsp.loadBuildConfiguration(
self.io,
self.allocator,
self.config.environ_map,
self.config.zig_exe_path.?,
self.config.zig_lib_dir.?,
self.config.zig_cache_dir.?,
self.diagnostics_collection,
build_file.uri,
new_version,
) catch |err| switch (err) {
error.Canceled => return error.Canceled,
else => |e| {
if (e != error.RunFailed) { // already logged
if (e != error.AlreadyReported) {
log.err("Failed to load build configuration for {s} (error: {})", .{ build_file.uri.raw, e });
}
self.notifyBuildEnd(.failed);
Expand All @@ -1186,15 +1204,15 @@ fn invalidateBuildFileWorker(self: *DocumentStore, build_file: *BuildFile) std.I
build_file.impl.build_runner_state = .idle;
build_file.impl.mutex.unlock(self.io);

if (old_config) |*config| config.deinit();
if (old_config) |*config| config.deinit(self.allocator);
self.notifyBuildEnd(.success);
break;
},
.running_but_already_invalidated => {
build_file.impl.build_runner_state = .running;
build_file.impl.mutex.unlock(self.io);

build_config.deinit();
build_config.deinit(self.allocator);
continue;
},
}
Expand Down
102 changes: 45 additions & 57 deletions src/Server.zig
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,15 @@ const offsets = @import("offsets.zig");
const tracy = @import("tracy");
const diff = @import("diff.zig");
const Uri = @import("Uri.zig");
const bsp = @import("bsp.zig");
const InternPool = @import("analyser/analyser.zig").InternPool;
const DiagnosticsCollection = @import("DiagnosticsCollection.zig");

const semantic_tokens = @import("features/semantic_tokens.zig");
const code_actions = @import("features/code_actions.zig");
const diagnostics_gen = @import("features/diagnostics.zig");

const BuildOnSave = @compileError("https://github.com/zigtools/zls/issues/3208");
const BuildOnSaveSupport = struct {
pub inline fn isSupportedComptime() bool {
return false;
}
};
const BuildOnSave = bsp.BuildOnSave;

const log = std.log.scoped(.server);

Expand Down Expand Up @@ -753,59 +749,39 @@ fn handleConfiguration(server: *Server, json: std.json.Value) error{ Canceled, O

const Workspace = struct {
uri: Uri,
build_on_save: if (BuildOnSaveSupport.isSupportedComptime()) ?BuildOnSave else void,
build_on_save_mode: if (BuildOnSaveSupport.isSupportedComptime()) ?enum { watch, manual } else void,
build_on_save: if (BuildOnSave.isSupportedComptime()) ?BuildOnSave else ?noreturn,

fn init(server: *Server, uri: Uri) error{OutOfMemory}!Workspace {
const duped_uri = try uri.dupe(server.allocator);
errdefer duped_uri.deinit(server.allocator);

return .{
.uri = duped_uri,
.build_on_save = if (BuildOnSaveSupport.isSupportedComptime()) null else {},
.build_on_save_mode = if (BuildOnSaveSupport.isSupportedComptime()) null else {},
.build_on_save = null,
};
}

fn deinit(workspace: *Workspace, allocator: std.mem.Allocator) void {
if (BuildOnSaveSupport.isSupportedComptime()) {
if (workspace.build_on_save) |*build_on_save| build_on_save.deinit();
}
if (workspace.build_on_save) |*build_on_save| build_on_save.deinit();
workspace.uri.deinit(allocator);
}

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,
/// Whether the build on save process should be restarted if it is already running.
restart: bool,
}) error{ Canceled, OutOfMemory }!void {
comptime std.debug.assert(BuildOnSaveSupport.isSupportedComptime());
comptime std.debug.assert(BuildOnSave.isSupportedComptime());

const config = &args.server.config_manager.config;
const io = args.server.io;
const gpa = args.server.allocator;
const config_manager = args.server.config_manager;
const config = &config_manager.config;

if (args.server.config_manager.zig_exe) |zig_exe| {
workspace.build_on_save_mode = switch (BuildOnSaveSupport.isSupportedRuntime(zig_exe.version)) {
.supported => .watch,
// If if build on save has been explicitly enabled, fallback to the implementation with manual updates
else => if (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 = config.enable_build_on_save orelse true;
const enable = build_on_save_supported and build_on_save_wanted;
const enable = if (config_manager.zig_exe) |zig_exe| switch (BuildOnSave.isSupportedRuntime(zig_exe.version)) {
.supported => config.enable_build_on_save orelse true,
else => false,
} else false;

if (workspace.build_on_save) |*build_on_save| {
if (enable and !args.restart) return;
Expand All @@ -817,28 +793,41 @@ const Workspace = struct {
if (!enable) return;

const zig_exe_path = config.zig_exe_path orelse return;
const zig_lib_path = config.zig_lib_path orelse return;
const zig_lib_dir = config_manager.zig_lib_dir orelse return;

const workspace_path = workspace.uri.toFsPath(args.server.allocator) catch |err| switch (err) {
const build_root_path = workspace.uri.toFsPath(gpa) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.UnsupportedScheme => return,
};
defer args.server.allocator.free(workspace_path);
errdefer gpa.free(build_root_path);

const build_root_handle = std.Io.Dir.cwd().openDir(io, build_root_path, .{}) catch |err| {
log.err("failed to open workspace folder '{s}': {t}", .{ build_root_path, err });
return;
};
errdefer build_root_handle.close(io);

const build_root: std.Build.Cache.Directory = .{
.path = build_root_path,
.handle = build_root_handle,
};

std.debug.assert(workspace.build_on_save == null);
workspace.build_on_save = BuildOnSave.init(.{
.io = args.server.io,
.allocator = args.server.allocator,
.workspace_path = workspace_path,
.io = io,
.gpa = gpa,
.environ_map = config_manager.environ_map,
.build_root = build_root,
.build_on_save_args = config.build_on_save_args,
.check_step_only = config.enable_build_on_save == null,
.zig_exe_path = zig_exe_path,
.zig_lib_path = zig_lib_path,
.collection = &args.server.diagnostics_collection,
.zig_lib_dir = zig_lib_dir,
.diagnostics = &args.server.diagnostics_collection,
}) catch |err| switch (err) {
error.Canceled => return error.Canceled,
error.AlreadyReported => return,
else => {
log.err("failed to initilize Build-On-Save for '{s}': {}", .{ workspace.uri.raw, err });
log.err("failed to initilize Build-On-Save for '{f}': {t}", .{ build_root, err });
return;
},
};
Expand All @@ -849,7 +838,7 @@ fn addWorkspace(server: *Server, uri: Uri) error{ Canceled, OutOfMemory }!void {
try server.workspaces.ensureUnusedCapacity(server.allocator, 1);
server.workspaces.appendAssumeCapacity(try Workspace.init(server, uri));

if (BuildOnSaveSupport.isSupportedComptime() and
if (BuildOnSave.isSupportedComptime() and
// Don't initialize build on save until initialization finished.
// If the client supports the `workspace/configuration` request, wait
// until we have received workspace configuration from the server.
Expand Down Expand Up @@ -991,7 +980,7 @@ pub fn resolveConfiguration(server: *Server) error{ Canceled, OutOfMemory }!void

server.document_store.config = createDocumentStoreConfig(server.config_manager);

if (BuildOnSaveSupport.isSupportedComptime() and
if (BuildOnSave.isSupportedComptime() and
// If the client supports the `workspace/configuration` request, defer
// build on save initialization until after we have received workspace
// configuration from the server
Expand Down Expand Up @@ -1081,20 +1070,18 @@ pub fn resolveConfiguration(server: *Server) error{ Canceled, OutOfMemory }!void
}

if (server.config_manager.config.enable_build_on_save orelse false) {
if (!BuildOnSaveSupport.isSupportedComptime()) {
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_manager.config.zig_exe_path == null or server.config_manager.zig_lib_dir == null)) {
log.warn("'enable_build_on_save' is ignored because Zig could not be found", .{});
} else if (!server.client_capabilities.supports_publish_diagnostics) {
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 server.config_manager.zig_exe != null) {
switch (BuildOnSaveSupport.isSupportedRuntime(server.config_manager.zig_exe.?.version)) {
switch (BuildOnSave.isSupportedRuntime(server.config_manager.zig_exe.?.version)) {
.supported => {},
.invalid_linux_kernel_version => |*utsname_release| log.warn("Build-On-Save cannot run in watch mode 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 '{f}' (requires at least {f})", .{ 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 {t} by Zig {f} (requires at least {f})", .{ zig_builtin.os.tag, server.resolved_config.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 {t}", .{zig_builtin.os.tag}),
.unsupported_linux_kernel_version => |kernel_version| log.warn("Build-On-Save cannot run in watch mode because it is not supported by Linux '{f}' (requires at least {f})", .{ kernel_version, BuildOnSave.Supported.minimum_linux_version }),
}
}
}
Expand All @@ -1117,6 +1104,7 @@ fn createDocumentStoreConfig(config_manager: *const configuration.Manager) Docum
.environ_map = config_manager.environ_map,
.zig_exe_path = config_manager.config.zig_exe_path,
.zig_lib_dir = config_manager.zig_lib_dir,
.zig_cache_dir = config_manager.zig_cache_dir,
.builtin_path = config_manager.config.builtin_path,
.global_cache_dir = config_manager.global_cache_dir,
.wasi_preopens = config_manager.wasi_preopens,
Expand Down Expand Up @@ -1193,9 +1181,9 @@ fn saveDocumentHandler(server: *Server, arena: std.mem.Allocator, notification:
server.allocator.free(json_message);
}

if (BuildOnSaveSupport.isSupportedComptime()) {
for (server.workspaces.items) |*workspace| {
workspace.sendManualWatchUpdate();
for (server.workspaces.items) |*workspace| {
if (workspace.build_on_save) |*build_on_save| {
build_on_save.sendManualWatchUpdate();
}
}
}
Expand Down
Loading
Loading