Skip to content

Commit ca82ada

Browse files
committed
fix: avoid treating modules named build.zig as build scripts
1 parent 8da87d4 commit ca82ada

2 files changed

Lines changed: 74 additions & 14 deletions

File tree

src/DocumentStore.zig

Lines changed: 65 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -763,11 +763,21 @@ pub fn getBuildFile(self: *DocumentStore, uri: Uri) ?*BuildFile {
763763
/// invalidates any pointers into `DocumentStore.build_files`
764764
/// **Thread safe** takes an exclusive lock
765765
/// This function does not protect against data races from modifying the BuildFile
766-
fn getOrLoadBuildFile(self: *DocumentStore, uri: Uri) error{ Canceled, OutOfMemory }!*BuildFile {
766+
fn getOrLoadBuildFile(
767+
self: *DocumentStore,
768+
uri: Uri,
769+
source_handle: ?*const Handle,
770+
) error{ Canceled, OutOfMemory }!?*BuildFile {
767771
comptime std.debug.assert(supports_build_system);
768772

773+
// A filename alone is not sufficient because ordinary modules may be named `build.zig`.
774+
if (source_handle) |handle| std.debug.assert(handle.uri.eql(uri));
775+
769776
if (self.getBuildFile(uri)) |build_file| return build_file;
770777

778+
const handle = source_handle orelse (try self.getOrLoadHandle(uri) orelse return null);
779+
if (!isBuildScript(&handle.tree)) return null;
780+
771781
const new_build_file: *BuildFile = blk: {
772782
try self.mutex.lock(self.io);
773783
defer self.mutex.unlock(self.io);
@@ -1244,6 +1254,43 @@ pub fn isBuildFile(uri: Uri) bool {
12441254
return std.mem.endsWith(u8, uri.raw, "/build.zig");
12451255
}
12461256

1257+
/// Returns whether the parsed source exposes the entry point required by `zig build`.
1258+
pub fn isBuildScript(tree: *const Ast) bool {
1259+
std.debug.assert(tree.mode == .zig);
1260+
1261+
for (tree.rootDecls()) |decl| {
1262+
var buffer: [1]Ast.Node.Index = undefined;
1263+
const fn_proto = tree.fullFnProto(&buffer, decl) orelse continue;
1264+
const visibility_token = fn_proto.visib_token orelse continue;
1265+
const name_token = fn_proto.name_token orelse continue;
1266+
1267+
std.debug.assert(tree.tokenTag(visibility_token) == .keyword_pub);
1268+
std.debug.assert(tree.tokenTag(name_token) == .identifier);
1269+
if (std.mem.eql(u8, tree.tokenSlice(name_token), "build")) return true;
1270+
}
1271+
return false;
1272+
}
1273+
1274+
test isBuildScript {
1275+
var build_script = try Ast.parse(std.testing.allocator,
1276+
\\const std = @import("std");
1277+
\\pub fn build(b: *std.Build) void {
1278+
\\ _ = b;
1279+
\\}
1280+
, .zig);
1281+
defer build_script.deinit(std.testing.allocator);
1282+
try std.testing.expect(isBuildScript(&build_script));
1283+
1284+
var module = try Ast.parse(std.testing.allocator,
1285+
\\const ParseOptions = @import("../error_handling.zig").ParseOptions;
1286+
\\pub fn parse(options: ParseOptions) void {
1287+
\\ _ = options;
1288+
\\}
1289+
, .zig);
1290+
defer module.deinit(std.testing.allocator);
1291+
try std.testing.expect(!isBuildScript(&module));
1292+
}
1293+
12471294
pub fn isBuiltinFile(uri: Uri) bool {
12481295
return std.mem.endsWith(u8, uri.raw, "/builtin.zig");
12491296
}
@@ -1460,7 +1507,7 @@ fn collectPotentialBuildFiles(self: *DocumentStore, uri: Uri) error{ Canceled, O
14601507
const build_file_uri: Uri = try .fromPath(self.allocator, build_path);
14611508
defer build_file_uri.deinit(self.allocator);
14621509

1463-
const build_file = try self.getOrLoadBuildFile(build_file_uri);
1510+
const build_file = try self.getOrLoadBuildFile(build_file_uri, null) orelse continue;
14641511
potential_build_files.appendAssumeCapacity(build_file);
14651512
}
14661513
// The potential build files that come first should have higher priority.
@@ -1541,18 +1588,6 @@ fn createAndStoreDocument(
15411588
.uri => {},
15421589
};
15431590

1544-
if (supports_build_system and options.lsp_synced and isBuildFile(uri) and !isInStd(uri)) {
1545-
switch (options.load_build_file_behaviour) {
1546-
.load_but_dont_update => {
1547-
_ = try store.getOrLoadBuildFile(uri);
1548-
},
1549-
.only_update => {
1550-
store.invalidateBuildFile(uri);
1551-
},
1552-
.never => {},
1553-
}
1554-
}
1555-
15561591
const handle_future: *Handle.Future = handle_future: {
15571592
try store.mutex.lock(store.io);
15581593
defer store.mutex.unlock(store.io);
@@ -1628,6 +1663,22 @@ fn createAndStoreDocument(
16281663
};
16291664
old_handle.deinit(store.allocator);
16301665

1666+
if (supports_build_system and options.lsp_synced and isBuildFile(uri) and !isInStd(uri) and isBuildScript(&handle_future.handle.tree)) {
1667+
switch (options.load_build_file_behaviour) {
1668+
.load_but_dont_update => {
1669+
_ = try store.getOrLoadBuildFile(uri, &handle_future.handle);
1670+
},
1671+
.only_update => {
1672+
if (store.getBuildFile(uri)) |build_file| {
1673+
store.invalidateBuildFile(build_file.uri);
1674+
} else {
1675+
_ = try store.getOrLoadBuildFile(uri, &handle_future.handle);
1676+
}
1677+
},
1678+
.never => {},
1679+
}
1680+
}
1681+
16311682
handle_future.err = null;
16321683
return &handle_future.handle;
16331684
}

src/Server.zig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,15 @@ const Workspace = struct {
833833
};
834834
defer args.server.allocator.free(workspace_path);
835835

836+
const build_file_path = try std.Io.Dir.path.resolve(args.server.allocator, &.{ workspace_path, "build.zig" });
837+
defer args.server.allocator.free(build_file_path);
838+
const build_file_uri: Uri = try .fromPath(args.server.allocator, build_file_path);
839+
defer build_file_uri.deinit(args.server.allocator);
840+
841+
// Avoid spawning `zig build` for a regular module named `build.zig`.
842+
const build_file_handle = try args.server.document_store.getOrLoadHandle(build_file_uri) orelse return;
843+
if (!DocumentStore.isBuildScript(&build_file_handle.tree)) return;
844+
836845
std.debug.assert(workspace.build_on_save == null);
837846
workspace.build_on_save = BuildOnSave.init(.{
838847
.io = args.server.io,

0 commit comments

Comments
 (0)