Skip to content

Commit

Permalink
make the Zig+ZLS compatibility check more strict
Browse files Browse the repository at this point in the history
The compatibility check was previously only executed on the config
resolve at startup/initialize. This check has moved to the
updateConfiguration function which also includes updates that happen 
through in-editor configuration.

ZLS will also warn when the minimum Zig version is not met at runtime.
The only exception is when using Zig `0.11.*` or `0.10.*` (no development builds).
  • Loading branch information
Techatrix committed Mar 2, 2024
1 parent e91d1d2 commit f25fafe
Show file tree
Hide file tree
Showing 8 changed files with 85 additions and 94 deletions.
10 changes: 9 additions & 1 deletion build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const min_zig_string = "0.12.0-dev.3071+6f7354a04";
const Build = blk: {
const current_zig = builtin.zig_version;
const min_zig = std.SemanticVersion.parse(min_zig_string) catch unreachable;
const is_current_zig_tagged_release = current_zig.pre == null;
const is_current_zig_tagged_release = current_zig.pre == null and current_zig.build == null;
if (current_zig.order(min_zig) == .lt) {
const message = std.fmt.comptimePrint(
\\Your Zig version does not meet the minimum build requirement:
Expand All @@ -20,6 +20,7 @@ const Build = blk: {
\\
++ if (is_current_zig_tagged_release)
\\Please download or compile a tagged release of ZLS.
\\ -> https://github.com/zigtools/zls/releases
\\ -> https://github.com/zigtools/zls/releases/tag/{[current_version]}
else
\\You can take one of the following actions to resolve this issue:
Expand Down Expand Up @@ -344,3 +345,10 @@ fn getTracyModule(

return tracy_module;
}

comptime {
const min_zig = std.SemanticVersion.parse(min_zig_string) catch unreachable;
const min_zig_simple = std.SemanticVersion{ .major = min_zig.major, .minor = min_zig.minor, .patch = 0 };
const zls_version_simple = std.SemanticVersion{ .major = zls_version.major, .minor = zls_version.minor, .patch = 0 };
std.debug.assert(zls_version_simple.order(min_zig_simple) == .eq);
}
6 changes: 1 addition & 5 deletions src/DocumentStore.zig
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ const BuildAssociatedConfig = @import("BuildAssociatedConfig.zig");
const BuildConfig = @import("build_runner/BuildConfig.zig");
const tracy = @import("tracy");
const Config = @import("Config.zig");
const ZigVersionWrapper = @import("ZigVersionWrapper.zig");
const translate_c = @import("translate_c.zig");
const ComptimeInterpreter = @import("ComptimeInterpreter.zig");
const AstGen = std.zig.AstGen;
Expand Down Expand Up @@ -599,8 +598,6 @@ pub const ErrorMessage = struct {
allocator: std.mem.Allocator,
/// the DocumentStore assumes that `config` is not modified while calling one of its functions.
config: *const Config,
/// the DocumentStore assumes that `runtime_zig_version` is not modified while calling one of its functions.
runtime_zig_version: *const ?ZigVersionWrapper,
lock: std.Thread.RwLock = .{},
thread_pool: if (builtin.single_threaded) void else *std.Thread.Pool,
handles: std.StringArrayHashMapUnmanaged(*Handle) = .{},
Expand Down Expand Up @@ -772,8 +769,7 @@ pub fn refreshDocument(self: *DocumentStore, uri: Uri, new_text: [:0]const u8) !
/// Invalidates a build files.
/// **Thread safe** takes a shared lock
pub fn invalidateBuildFile(self: *DocumentStore, build_file_uri: Uri) error{OutOfMemory}!void {
std.debug.assert(std.process.can_spawn);
if (!std.process.can_spawn) return;
comptime std.debug.assert(std.process.can_spawn);

if (self.config.zig_exe_path == null) return;
if (self.config.build_runner_path == null) return;
Expand Down
129 changes: 67 additions & 62 deletions src/Server.zig
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ const tracy = @import("tracy");
const diff = @import("diff.zig");
const ComptimeInterpreter = @import("ComptimeInterpreter.zig");
const InternPool = @import("analyser/analyser.zig").InternPool;
const ZigVersionWrapper = @import("ZigVersionWrapper.zig");
const Transport = @import("Transport.zig");
const known_folders = @import("known-folders");
const BuildRunnerVersion = @import("build_runner/BuildRunnerVersion.zig").BuildRunnerVersion;
Expand Down Expand Up @@ -59,7 +58,6 @@ running_build_on_save_processes: std.atomic.Value(usize) = std.atomic.Value(usiz
zig_ast_check_lock: std.Thread.Mutex = .{},
config_arena: std.heap.ArenaAllocator.State = .{},
client_capabilities: ClientCapabilities = .{},
runtime_zig_version: ?ZigVersionWrapper = null,

// Code was based off of https://github.com/andersfr/zig-lsp/blob/master/server.zig

Expand Down Expand Up @@ -556,35 +554,6 @@ fn initializeHandler(server: *Server, _: std.mem.Allocator, request: types.Initi
};
}

if (server.runtime_zig_version) |zig_version_wrapper| {
const zig_version = zig_version_wrapper.version;

const zig_version_simple = std.SemanticVersion{
.major = zig_version.major,
.minor = zig_version.minor,
.patch = 0,
};
const zls_version_simple = std.SemanticVersion{
.major = build_options.version.major,
.minor = build_options.version.minor,
.patch = 0,
};

switch (zig_version_simple.order(zls_version_simple)) {
.lt => {
server.showMessage(.Warning,
\\Zig `{s}` is older than ZLS `{s}`. Update Zig to avoid unexpected behavior.
, .{ zig_version_wrapper.raw_string, build_options.version_string });
},
.eq => {},
.gt => {
server.showMessage(.Warning,
\\Zig `{s}` is newer than ZLS `{s}`. Update ZLS to avoid unexpected behavior.
, .{ zig_version_wrapper.raw_string, build_options.version_string });
},
}
}

return .{
.serverInfo = .{
.name = "zls",
Expand Down Expand Up @@ -845,7 +814,8 @@ pub fn updateConfiguration(server: *Server, new_config: configuration.Configurat
}

server.validateConfiguration(&new_cfg);
try server.resolveConfiguration(server.allocator, config_arena, &new_cfg);
const resolve_result = try resolveConfiguration(server.allocator, config_arena, &new_cfg);
defer resolve_result.deinit();
server.validateConfiguration(&new_cfg);

// <---------------------------------------------------------->
Expand Down Expand Up @@ -890,13 +860,6 @@ pub fn updateConfiguration(server: *Server, new_config: configuration.Configurat
}
}

if (server.config.zig_exe_path == null and
server.runtime_zig_version != null)
{
server.runtime_zig_version.?.free();
server.runtime_zig_version = null;
}

if (new_zig_exe_path or new_build_runner_path) blk: {
if (!std.process.can_spawn) break :blk;

Expand Down Expand Up @@ -937,6 +900,44 @@ pub fn updateConfiguration(server: *Server, new_config: configuration.Configurat
server.showMessage(.Warning, "zig executable could not be found", .{});
}

if (resolve_result.zig_runtime_version) |zig_version| version_check: {
const min_zig_string = comptime std.SemanticVersion.parse(build_options.min_zig_string) catch unreachable;

const zig_version_is_tagged = zig_version.pre == null and zig_version.build == null;
const zls_version_is_tagged = build_options.version.pre == null and build_options.version.build == null;

const zig_version_simple = std.SemanticVersion{ .major = zig_version.major, .minor = zig_version.minor, .patch = 0 };
const zls_version_simple = std.SemanticVersion{ .major = build_options.version.major, .minor = build_options.version.minor, .patch = 0 };

if (zig_version_is_tagged != zls_version_is_tagged) {
if (zig_version_is_tagged) {
server.showMessage(
.Warning,
"Zig {} should be used with ZLS {} but ZLS {} is being used.",
.{ zig_version, zig_version_simple, build_options.version },
);
} else if (zls_version_is_tagged) {
server.showMessage(
.Warning,
"ZLS {} should be used with Zig {} but found Zig {}. ",
.{ build_options.version, zls_version_simple, zig_version },
);
} else unreachable;
break :version_check;
}

if (zig_version.order(min_zig_string) == .lt) {
// don't report a warning when using a Zig version that has a matching build runner
if (resolve_result.build_runner_version != null and resolve_result.build_runner_version.? != .master) break :version_check;
server.showMessage(
.Warning,
"ZLS {s} requires at least Zig {s} but got Zig {}. Update Zig to avoid unexpected behavior.",
.{ build_options.version_string, build_options.min_zig_string, zig_version },
);
break :version_check;
}
}

if (server.config.prefer_ast_check_as_child_process) {
if (!std.process.can_spawn) {
log.info("'prefer_ast_check_as_child_process' is ignored because your OS can't spawn a child process", .{});
Expand Down Expand Up @@ -1048,13 +1049,29 @@ fn validateConfiguration(server: *Server, config: *configuration.Configuration)
}
}

const ResolveConfigurationResult = struct {
zig_env: ?std.json.Parsed(configuration.Env),
zig_runtime_version: ?std.SemanticVersion,
build_runner_version: ?BuildRunnerVersion,

fn deinit(result: ResolveConfigurationResult) void {
if (result.zig_env) |parsed| parsed.deinit();
}
};

fn resolveConfiguration(
server: *Server,
allocator: std.mem.Allocator,
/// try leaking as little memory as possible since the ArenaAllocator is only deinit on exit
config_arena: std.mem.Allocator,
config: *configuration.Configuration,
) error{OutOfMemory}!void {
) error{OutOfMemory}!ResolveConfigurationResult {
var result: ResolveConfigurationResult = .{
.zig_env = null,
.zig_runtime_version = null,
.build_runner_version = null,
};
errdefer result.deinit();

if (config.zig_exe_path == null) blk: {
if (zig_builtin.is_test) unreachable;
if (!std.process.can_spawn) break :blk;
Expand All @@ -1064,8 +1081,8 @@ fn resolveConfiguration(

if (config.zig_exe_path) |exe_path| blk: {
if (!std.process.can_spawn) break :blk;
const env = configuration.getZigEnv(config, exe_path) orelse break :blk;
defer env.deinit();
result.zig_env = configuration.getZigEnv(allocator, exe_path);
const env = result.zig_env orelse break :blk;

if (config.zig_lib_path == null) {
if (env.value.lib_dir) |lib_dir| resolve_lib_failed: {
Expand All @@ -1089,22 +1106,10 @@ fn resolveConfiguration(
config.build_runner_global_cache_path = try config_arena.dupe(u8, env.value.global_cache_dir);
}

if (server.runtime_zig_version) |current_version| current_version.free();
server.runtime_zig_version = null;

const duped_zig_version_string = try server.allocator.dupe(u8, env.value.version);
errdefer server.allocator.free(duped_zig_version_string);

const version = std.SemanticVersion.parse(duped_zig_version_string) catch |err| {
result.zig_runtime_version = std.SemanticVersion.parse(env.value.version) catch |err| {
log.err("zig env returned a zig version that is an invalid semantic version: {}", .{err});
break :blk;
};

server.runtime_zig_version = .{
.version = version,
.allocator = server.allocator,
.raw_string = duped_zig_version_string,
};
}

if (config.global_cache_path == null) blk: {
Expand All @@ -1126,11 +1131,11 @@ fn resolveConfiguration(
if (config.build_runner_path == null) blk: {
if (!std.process.can_spawn) break :blk;
const global_cache_path = config.global_cache_path orelse break :blk;
const zig_version = server.runtime_zig_version orelse break :blk;
const zig_version = result.zig_runtime_version orelse break :blk;

const build_runner_version = BuildRunnerVersion.selectBuildRunnerVersion(zig_version.version);
result.build_runner_version = BuildRunnerVersion.selectBuildRunnerVersion(zig_version) orelse break :blk;

const build_runner_file_name = try std.fmt.allocPrint(allocator, "build_runner_{s}.zig", .{@tagName(build_runner_version)});
const build_runner_file_name = try std.fmt.allocPrint(allocator, "build_runner_{s}.zig", .{@tagName(result.build_runner_version.?)});
defer allocator.free(build_runner_file_name);

const build_runner_path = try std.fs.path.join(config_arena, &[_][]const u8{ global_cache_path, build_runner_file_name });
Expand All @@ -1148,7 +1153,7 @@ fn resolveConfiguration(

std.fs.cwd().writeFile2(.{
.sub_path = build_runner_path,
.data = switch (build_runner_version) {
.data = switch (result.build_runner_version.?) {
inline else => |tag| @embedFile("build_runner/" ++ @tagName(tag) ++ ".zig"),
},
}) catch |err| {
Expand Down Expand Up @@ -1194,6 +1199,8 @@ fn resolveConfiguration(

config.builtin_path = builtin_path;
}

return result;
}

fn openDocumentHandler(server: *Server, _: std.mem.Allocator, notification: types.DidOpenTextDocumentParams) Error!void {
Expand Down Expand Up @@ -1817,7 +1824,6 @@ pub fn create(allocator: std.mem.Allocator) !*Server {
.document_store = .{
.allocator = allocator,
.config = &server.config,
.runtime_zig_version = &server.runtime_zig_version,
.thread_pool = if (zig_builtin.single_threaded) {} else undefined, // set below
},
.job_queue = std.fifo.LinearFifo(Job, .Dynamic).init(allocator),
Expand Down Expand Up @@ -1851,7 +1857,6 @@ pub fn destroy(server: *Server) void {
server.document_store.deinit();
server.ip.deinit(server.allocator);
server.client_capabilities.deinit(server.allocator);
if (server.runtime_zig_version) |zig_version| zig_version.free();
server.config_arena.promote(server.allocator).deinit();
server.allocator.destroy(server);
}
Expand Down
13 changes: 0 additions & 13 deletions src/ZigVersionWrapper.zig

This file was deleted.

18 changes: 8 additions & 10 deletions src/build_runner/BuildRunnerVersion.zig
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pub const BuildRunnerVersion = enum {
@"0.11.0",
@"0.10.0",

pub fn selectBuildRunnerVersion(runtime_zig_version: std.SemanticVersion) BuildRunnerVersion {
pub fn selectBuildRunnerVersion(runtime_zig_version: std.SemanticVersion) ?BuildRunnerVersion {
const runtime_zig_version_simple = std.SemanticVersion{
.major = runtime_zig_version.major,
.minor = runtime_zig_version.minor,
Expand All @@ -22,18 +22,16 @@ pub const BuildRunnerVersion = enum {

return switch (runtime_zig_version_simple.order(zls_version_simple)) {
.eq, .gt => .master,
.lt => blk: {
const available_versions = std.meta.tags(BuildRunnerVersion);
for (available_versions[1..]) |build_runner_version| {
const version = std.SemanticVersion.parse(@tagName(build_runner_version)) catch unreachable;
.lt => {
const available_versions = comptime std.meta.tags(BuildRunnerVersion);
inline for (available_versions[1..]) |build_runner_version| {
const version = comptime std.SemanticVersion.parse(@tagName(build_runner_version)) catch unreachable;
switch (runtime_zig_version.order(version)) {
.eq, .gt => break :blk build_runner_version,
.lt => {},
.eq => return build_runner_version,
.lt, .gt => {},
}
}

// failed to find compatible build runner, falling back to oldest supported version
break :blk available_versions[available_versions.len - 1];
return null;
},
};
}
Expand Down
1 change: 0 additions & 1 deletion src/configuration.zig
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
const std = @import("std");
const builtin = @import("builtin");

const ZigVersionWrapper = @import("ZigVersionWrapper.zig");
const tracy = @import("tracy");
const known_folders = @import("known-folders");

Expand Down
1 change: 0 additions & 1 deletion src/zls.zig
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ pub const ComptimeInterpreter = @import("ComptimeInterpreter.zig");
pub const diff = @import("diff.zig");
pub const analyser = @import("analyser/analyser.zig");
pub const configuration = @import("configuration.zig");
pub const ZigVersionWrapper = @import("ZigVersionWrapper.zig");
pub const DocumentScope = @import("DocumentScope.zig");

pub const signature_help = @import("features/signature_help.zig");
Expand Down
1 change: 0 additions & 1 deletion tests/language_features/comptime_interpreter.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ const zls = @import("zls");
const builtin = @import("builtin");

const Ast = std.zig.Ast;
const ZigVersionWrapper = zls.ZigVersionWrapper;
const ComptimeInterpreter = zls.ComptimeInterpreter;
const InternPool = zls.analyser.InternPool;
const Index = InternPool.Index;
Expand Down

0 comments on commit f25fafe

Please sign in to comment.