From 3b052372c263481f7cb15f9e0cb0f1260e00f978 Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Mon, 25 May 2020 03:22:39 +0300 Subject: [PATCH 01/11] Added custom build runner, install it along with the zls binary --- build.zig | 4 ++ src/main.zig | 2 +- src/special/build_runner.zig | 89 ++++++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 src/special/build_runner.zig diff --git a/build.zig b/build.zig index 9fddd1b..2b66c73 100644 --- a/build.zig +++ b/build.zig @@ -29,10 +29,14 @@ pub fn build(b: *std.build.Builder) !void { b.option(bool, "allocation_info", "Enable use of debugging allocator and info logging.") orelse false, ); + exe.addPackage(.{ .name = "known-folders", .path = "src/known-folders/known-folders.zig" }); + exe.setTarget(target); exe.setBuildMode(mode); exe.install(); + b.installFile("src/special/build_runner.zig", "bin/build_runner.zig"); + const run_cmd = exe.run(); run_cmd.step.dependOn(b.getInstallStep()); diff --git a/src/main.zig b/src/main.zig index 76a1dc3..2199560 100644 --- a/src/main.zig +++ b/src/main.zig @@ -870,7 +870,7 @@ pub fn main() anyerror!void { defer std.json.parseFree(Config, config, config_parse_options); config_read: { - const known_folders = @import("known-folders/known-folders.zig"); + const known_folders = @import("known-folders"); const res = try known_folders.getPath(allocator, .local_configuration); if (res) |local_config_path| { defer allocator.free(local_config_path); diff --git a/src/special/build_runner.zig b/src/special/build_runner.zig new file mode 100644 index 0000000..4ec7ee2 --- /dev/null +++ b/src/special/build_runner.zig @@ -0,0 +1,89 @@ +const root = @import("build.zig"); +const std = @import("std"); +const io = std.io; +const fmt = std.fmt; +const Builder = std.build.Builder; +const Pkg = std.build.Pkg; +const LibExeObjStep = std.build.LibExeObjStep; +const ArrayList = std.ArrayList; +///! This is a modified build runner to extract information out of build.zig +///! Modified from std.special.build_runner + +// We use a custom Allocator to intercept the creation of steps +const InterceptAllocator = struct { + base_allocator: *std.mem.Allocator, + allocator: std.mem.Allocator, + steps: std.ArrayListUnmanaged(*LibExeObjStep), + + fn init(base_allocator: *std.mem.Allocator) InterceptAllocator { + return .{ + .base_allocator = base_allocator, + .allocator = .{ + .reallocFn = realloc, + .shrinkFn = shrink, + }, + .steps = .{}, + }; + } + + // TODO: Check LibExeObjStep has a unique size. + fn realloc(allocator: *std.mem.Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { + const self = @fieldParentPtr(InterceptAllocator, "allocator", allocator); + var data = try self.base_allocator.reallocFn(self.base_allocator, old_mem, old_align, new_size, new_align); + if (old_mem.len == 0 and new_size == @sizeOf(LibExeObjStep)) { + try self.steps.append(self.base_allocator, @ptrCast(*LibExeObjStep, @alignCast(@alignOf(LibExeObjStep), data.ptr))); + } + return data; + } + + fn shrink(allocator: *std.mem.Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { + const self = @fieldParentPtr(InterceptAllocator, "allocator", allocator); + return self.base_allocator.shrinkFn(self.base_allocator, old_mem, old_align, new_size, new_align); + } + + fn deinit(self: *InterceptAllocator) void { + self.steps.deinit(self.base_allocator); + } +}; + +pub fn main() !void { + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena.deinit(); + + var intercept = InterceptAllocator.init(&arena.allocator); + defer intercept.deinit(); + const allocator = &intercept.allocator; + + const builder = try Builder.create(allocator, "", "", ""); + defer builder.destroy(); + + try runBuild(builder); + + const stdout_stream = io.getStdOut().outStream(); + + // TODO: We currently add packages from every step., + // Should we error out or keep one step or something similar? + // We also flatten them, we should probably keep the nested structure. + for (intercept.steps.items) |step| { + for (step.packages.items) |pkg| { + try processPackage(stdout_stream, pkg); + } + } +} + +fn processPackage(out_stream: var, pkg: Pkg) anyerror!void { + try out_stream.print("{}\x00{}\n", .{ pkg.name, pkg.path }); + if (pkg.dependencies) |dependencies| { + for (dependencies) |dep| { + try processPackage(out_stream, dep); + } + } +} + +fn runBuild(builder: *Builder) anyerror!void { + switch (@typeInfo(@TypeOf(root.build).ReturnType)) { + .Void => root.build(builder), + .ErrorUnion => try root.build(builder), + else => @compileError("expected return type of build to be 'void' or '!void'"), + } +} From 1f23ba84907f1492dbd6125a62d8ef04d47d9b45 Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Mon, 25 May 2020 11:28:38 +0300 Subject: [PATCH 02/11] Use new build.zig functions to get the correct steps --- src/special/build_runner.zig | 59 +++++++----------------------------- 1 file changed, 11 insertions(+), 48 deletions(-) diff --git a/src/special/build_runner.zig b/src/special/build_runner.zig index 4ec7ee2..79467a6 100644 --- a/src/special/build_runner.zig +++ b/src/special/build_runner.zig @@ -4,55 +4,17 @@ const io = std.io; const fmt = std.fmt; const Builder = std.build.Builder; const Pkg = std.build.Pkg; -const LibExeObjStep = std.build.LibExeObjStep; +const InstallArtifactStep = std.build.InstallArtifactStep; const ArrayList = std.ArrayList; + ///! This is a modified build runner to extract information out of build.zig -///! Modified from std.special.build_runner - -// We use a custom Allocator to intercept the creation of steps -const InterceptAllocator = struct { - base_allocator: *std.mem.Allocator, - allocator: std.mem.Allocator, - steps: std.ArrayListUnmanaged(*LibExeObjStep), - - fn init(base_allocator: *std.mem.Allocator) InterceptAllocator { - return .{ - .base_allocator = base_allocator, - .allocator = .{ - .reallocFn = realloc, - .shrinkFn = shrink, - }, - .steps = .{}, - }; - } - - // TODO: Check LibExeObjStep has a unique size. - fn realloc(allocator: *std.mem.Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { - const self = @fieldParentPtr(InterceptAllocator, "allocator", allocator); - var data = try self.base_allocator.reallocFn(self.base_allocator, old_mem, old_align, new_size, new_align); - if (old_mem.len == 0 and new_size == @sizeOf(LibExeObjStep)) { - try self.steps.append(self.base_allocator, @ptrCast(*LibExeObjStep, @alignCast(@alignOf(LibExeObjStep), data.ptr))); - } - return data; - } - - fn shrink(allocator: *std.mem.Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { - const self = @fieldParentPtr(InterceptAllocator, "allocator", allocator); - return self.base_allocator.shrinkFn(self.base_allocator, old_mem, old_align, new_size, new_align); - } - - fn deinit(self: *InterceptAllocator) void { - self.steps.deinit(self.base_allocator); - } -}; +///! Modified from the std.special.build_runner pub fn main() !void { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); - var intercept = InterceptAllocator.init(&arena.allocator); - defer intercept.deinit(); - const allocator = &intercept.allocator; + const allocator = &arena.allocator; const builder = try Builder.create(allocator, "", "", ""); defer builder.destroy(); @@ -61,12 +23,13 @@ pub fn main() !void { const stdout_stream = io.getStdOut().outStream(); - // TODO: We currently add packages from every step., - // Should we error out or keep one step or something similar? - // We also flatten them, we should probably keep the nested structure. - for (intercept.steps.items) |step| { - for (step.packages.items) |pkg| { - try processPackage(stdout_stream, pkg); + for (builder.getInstallStep().dependencies.items) |step| { + std.debug.warn("step.id {}\n", .{step.id}); + if (step.cast(InstallArtifactStep)) |install_exe| { + std.debug.warn("libexeobj!\n", .{}); + for (install_exe.artifact.packages.items) |pkg| { + try processPackage(stdout_stream, pkg); + } } } } From f008a776e2a016f32c337683321e4c1f941a2243 Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Mon, 25 May 2020 11:30:12 +0300 Subject: [PATCH 03/11] Removed debug traces, added comment --- src/special/build_runner.zig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/special/build_runner.zig b/src/special/build_runner.zig index 79467a6..3241e1f 100644 --- a/src/special/build_runner.zig +++ b/src/special/build_runner.zig @@ -23,10 +23,11 @@ pub fn main() !void { const stdout_stream = io.getStdOut().outStream(); + // TODO: We currently add packages from every LibExeObj step that the install step depends on. + // Should we error out or keep one step or something similar? + // We also flatten them, we should probably keep the nested structure. for (builder.getInstallStep().dependencies.items) |step| { - std.debug.warn("step.id {}\n", .{step.id}); if (step.cast(InstallArtifactStep)) |install_exe| { - std.debug.warn("libexeobj!\n", .{}); for (install_exe.artifact.packages.items) |pkg| { try processPackage(stdout_stream, pkg); } From 4542abf145031363fe7914867a661452a8de4f6b Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Mon, 25 May 2020 16:18:00 +0300 Subject: [PATCH 04/11] Check for the existence of zig in PATH in main() --- src/main.zig | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/main.zig b/src/main.zig index 2199560..0afbe3f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -888,6 +888,37 @@ pub fn main() anyerror!void { } } + // Find the zig executable in PATH + var has_zig = false; + + find_zig: { + const env_path = std.process.getEnvVarOwned(allocator, "PATH") catch |err| switch (err) { + error.EnvironmentVariableNotFound => { + std.debug.warn("Could not get PATH.\n", .{}); + break :find_zig; + }, + else => return err, + }; + defer allocator.free(env_path); + + var it = std.mem.tokenize(env_path,&[_]u8{std.fs.path.delimiter}); + const exe_extension = @as(std.zig.CrossTarget, .{}).exeFileExt(); + const zig_exe = try std.fmt.allocPrint(allocator, "zig{}", .{exe_extension}); + defer allocator.free(zig_exe); + + while (it.next()) |path| { + const full_path = try std.fs.path.join(allocator, &[_][]const u8{ + path, + zig_exe, + }); + var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; + const zig_path = std.os.realpath(full_path, &buf) catch continue; + std.debug.warn("Found zig in PATH: {}\n", .{zig_path}); + has_zig = true; + break :find_zig; + } + } + try document_store.init(allocator); defer document_store.deinit(); From 155c5b2a95c1ed1732e5252118df7b422cb1b022 Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Mon, 25 May 2020 17:24:44 +0300 Subject: [PATCH 05/11] Started working on build file logic --- src/document_store.zig | 83 ++++++++++++++++++++++++++++++++++++++++-- src/main.zig | 8 +++- src/uri.zig | 7 ++++ 3 files changed, 93 insertions(+), 5 deletions(-) diff --git a/src/document_store.zig b/src/document_store.zig index e5a57de..e88d001 100644 --- a/src/document_store.zig +++ b/src/document_store.zig @@ -5,12 +5,25 @@ const analysis = @import("analysis.zig"); const DocumentStore = @This(); +const BuildFile = struct { + const Pkg = struct { + name: []const u8, + uri: []const u8, + }; + + uri: []const u8, + packages: std.ArrayListUnmanaged(Pkg), +}; + pub const Handle = struct { document: types.TextDocument, count: usize, import_uris: std.ArrayList([]const u8), tree: *std.zig.ast.Tree, + associated_build_file: ?*BuildFile, + is_build_file: ?*BuildFile, + pub fn uri(handle: Handle) []const u8 { return handle.document.uri; } @@ -18,16 +31,21 @@ pub const Handle = struct { allocator: *std.mem.Allocator, handles: std.StringHashMap(*Handle), +has_zig: bool, +build_files: std.ArrayListUnmanaged(*BuildFile), -pub fn init(self: *DocumentStore, allocator: *std.mem.Allocator) !void { +pub fn init(self: *DocumentStore, allocator: *std.mem.Allocator, has_zig: bool) !void { self.allocator = allocator; self.handles = std.StringHashMap(*Handle).init(allocator); - errdefer self.handles.deinit(); + self.has_zig = has_zig; + self.build_files = .{}; } +const NewDocumentError = std.fs.File.ReadError || URI.UriParseError || error{StreamTooLong}; + /// This function asserts the document is not open yet and takes ownership /// of the uri and text passed in. -fn newDocument(self: *DocumentStore, uri: []const u8, text: []u8) !*Handle { +fn newDocument(self: *DocumentStore, uri: []const u8, text: []u8) NewDocumentError!*Handle { std.debug.warn("Opened document: {}\n", .{uri}); var handle = try self.allocator.create(Handle); @@ -42,8 +60,67 @@ fn newDocument(self: *DocumentStore, uri: []const u8, text: []u8) !*Handle { .mem = text, }, .tree = try std.zig.parse(self.allocator, text), + .associated_build_file = null, + .is_build_file = null, }; + if (std.mem.endsWith(u8, uri, "build.zig") and self.has_zig) { + std.debug.warn("Document is a build file, extracting packages...\n", .{}); + // This is a build file. + // @TODO Here copy the runner, run `zig run`, parse the output, + // make a BuildFile pointer called build_file + var build_file = try self.allocator.create(BuildFile); + + build_file.* = .{ + .uri = try std.mem.dupe(self.allocator, u8, uri), + .packages = .{}, + }; + + handle.is_build_file = build_file; + } else if (self.has_zig) associate_build_file: { + // Look into build files to see if we already have one that fits + for (self.build_files.items) |build_file| { + // @TODO: Check if this is correct + const build_file_base_uri = build_file.uri[0 .. std.mem.lastIndexOfScalar(u8, build_file.uri, '/').? + 1]; + + if (std.mem.startsWith(u8, uri, build_file_base_uri)) { + std.debug.warn("Found an associated build file: {}\n", .{build_file.uri}); + handle.associated_build_file = build_file; + break :associate_build_file; + } + } + // Otherwise, try to find a build file. + var curr_path = try URI.parse(self.allocator, uri); + defer self.allocator.free(curr_path); + while (true) { + // @TODO Add temporary traces to see what is going on. + + // std.fs.path.sep + if (std.mem.lastIndexOfScalar(u8, curr_path[0 .. curr_path.len - 1], std.fs.path.sep)) |idx| { + // This includes the last separator + curr_path = curr_path[0..idx + 1]; + var candidate_path = try std.mem.concat(self.allocator, u8, &[_][]const u8 { curr_path, "build.zig" }); + defer self.allocator.free(candidate_path); + // Try to open the file, read it and add the new document if we find it. + var file = std.fs.cwd().openFile(candidate_path, .{ .read = true, .write = false }) catch continue; + defer file.close(); + + const build_file_text = try file.inStream().readAllAlloc(self.allocator, std.math.maxInt(usize)); + errdefer self.allocator.free(build_file_text); + + const build_file_uri = try URI.fromPath(self.allocator, candidate_path); + errdefer self.allocator.free(build_file_uri); + + const build_file_handle = try self.newDocument(build_file_uri, build_file_text); + handle.associated_build_file = build_file_handle.is_build_file; + break; + } else break :associate_build_file; + } + } + + // @TODO: Handle the text refresh if we have a is_build_file + // @TODO: Handle package imports if we have an associated_build_file + try self.handles.putNoClobber(uri, handle); return handle; } diff --git a/src/main.zig b/src/main.zig index 0afbe3f..c6a3895 100644 --- a/src/main.zig +++ b/src/main.zig @@ -891,6 +891,8 @@ pub fn main() anyerror!void { // Find the zig executable in PATH var has_zig = false; + // TODO: Should we just spawn a child process that calls "zig version" or something + // and check that way? find_zig: { const env_path = std.process.getEnvVarOwned(allocator, "PATH") catch |err| switch (err) { error.EnvironmentVariableNotFound => { @@ -901,16 +903,18 @@ pub fn main() anyerror!void { }; defer allocator.free(env_path); - var it = std.mem.tokenize(env_path,&[_]u8{std.fs.path.delimiter}); const exe_extension = @as(std.zig.CrossTarget, .{}).exeFileExt(); const zig_exe = try std.fmt.allocPrint(allocator, "zig{}", .{exe_extension}); defer allocator.free(zig_exe); + var it = std.mem.tokenize(env_path,&[_]u8{std.fs.path.delimiter}); while (it.next()) |path| { const full_path = try std.fs.path.join(allocator, &[_][]const u8{ path, zig_exe, }); + defer allocator.free(full_path); + var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; const zig_path = std.os.realpath(full_path, &buf) catch continue; std.debug.warn("Found zig in PATH: {}\n", .{zig_path}); @@ -919,7 +923,7 @@ pub fn main() anyerror!void { } } - try document_store.init(allocator); + try document_store.init(allocator, has_zig); defer document_store.deinit(); workspace_folder_configs = std.StringHashMap(?Config).init(allocator); diff --git a/src/uri.zig b/src/uri.zig index ad9d4fb..59ea9d6 100644 --- a/src/uri.zig +++ b/src/uri.zig @@ -41,6 +41,13 @@ pub fn fromPath(allocator: *std.mem.Allocator, path: []const u8) ![]const u8 { return buf.toOwnedSlice(); } +pub const UriParseError = error { + UriBadScheme, + UriBadHexChar, + UriBadEscape, + OutOfMemory, +}; + // Original code: https://github.com/andersfr/zig-lsp/blob/master/uri.zig fn parseHex(c: u8) !u8 { return switch (c) { From 5fb68c9dc789395121972d0c3e9e9ed4a93f9fbc Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Mon, 25 May 2020 17:48:25 +0300 Subject: [PATCH 06/11] Do not try to find build files in a std and subdirectories --- src/document_store.zig | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/document_store.zig b/src/document_store.zig index e88d001..cdf5e8f 100644 --- a/src/document_store.zig +++ b/src/document_store.zig @@ -64,23 +64,26 @@ fn newDocument(self: *DocumentStore, uri: []const u8, text: []u8) NewDocumentErr .is_build_file = null, }; - if (std.mem.endsWith(u8, uri, "build.zig") and self.has_zig) { + // TODO: Better logic? + const in_std = std.mem.indexOf(u8, uri, "/std/") != null; + if (self.has_zig and std.mem.endsWith(u8, uri, "build.zig") and !in_std) { std.debug.warn("Document is a build file, extracting packages...\n", .{}); // This is a build file. // @TODO Here copy the runner, run `zig run`, parse the output, // make a BuildFile pointer called build_file var build_file = try self.allocator.create(BuildFile); + errdefer self.allocator.destroy(build_file); build_file.* = .{ .uri = try std.mem.dupe(self.allocator, u8, uri), .packages = .{}, }; + try self.build_files.append(self.allocator, build_file); handle.is_build_file = build_file; - } else if (self.has_zig) associate_build_file: { + } else if (self.has_zig and !in_std) associate_build_file: { // Look into build files to see if we already have one that fits for (self.build_files.items) |build_file| { - // @TODO: Check if this is correct const build_file_base_uri = build_file.uri[0 .. std.mem.lastIndexOfScalar(u8, build_file.uri, '/').? + 1]; if (std.mem.startsWith(u8, uri, build_file_base_uri)) { @@ -93,6 +96,7 @@ fn newDocument(self: *DocumentStore, uri: []const u8, text: []u8) NewDocumentErr var curr_path = try URI.parse(self.allocator, uri); defer self.allocator.free(curr_path); while (true) { + if (curr_path.len == 0) break :associate_build_file; // @TODO Add temporary traces to see what is going on. // std.fs.path.sep @@ -112,6 +116,7 @@ fn newDocument(self: *DocumentStore, uri: []const u8, text: []u8) NewDocumentErr errdefer self.allocator.free(build_file_uri); const build_file_handle = try self.newDocument(build_file_uri, build_file_text); + handle.associated_build_file = build_file_handle.is_build_file; break; } else break :associate_build_file; From 4bf97bebc9c4d70c021f14b0fa522d3b0a0bcbf9 Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Mon, 25 May 2020 19:33:08 +0300 Subject: [PATCH 07/11] Seems to work, needs cleanup --- src/document_store.zig | 121 ++++++++++++++++++++++++++++++++++++----- src/main.zig | 19 +++++-- 2 files changed, 119 insertions(+), 21 deletions(-) diff --git a/src/document_store.zig b/src/document_store.zig index cdf5e8f..ec55625 100644 --- a/src/document_store.zig +++ b/src/document_store.zig @@ -29,23 +29,98 @@ pub const Handle = struct { } }; +// @TODO Deinit the new stuff allocator: *std.mem.Allocator, handles: std.StringHashMap(*Handle), has_zig: bool, build_files: std.ArrayListUnmanaged(*BuildFile), +build_runner_path: []const u8, -pub fn init(self: *DocumentStore, allocator: *std.mem.Allocator, has_zig: bool) !void { +pub fn init( + self: *DocumentStore, + allocator: *std.mem.Allocator, + has_zig: bool, + build_runner_path: []const u8, +) !void { self.allocator = allocator; self.handles = std.StringHashMap(*Handle).init(allocator); self.has_zig = has_zig; self.build_files = .{}; + self.build_runner_path = build_runner_path; } -const NewDocumentError = std.fs.File.ReadError || URI.UriParseError || error{StreamTooLong}; +const LoadPackagesContext = struct { + build_file: *BuildFile, + allocator: *std.mem.Allocator, + build_runner_path: []const u8, +}; + +fn loadPackages(context: LoadPackagesContext) !void { + const allocator = context.allocator; + const build_file = context.build_file; + const build_runner_path = context.build_runner_path; + + const directory_path = try URI.parse(allocator, build_file.uri[0 .. build_file.uri.len - "build.zig".len]); + defer allocator.free(directory_path); + + const target_path = try std.fs.path.resolve(allocator, &[_][]const u8{ directory_path, "build_runner.zig" }); + defer allocator.free(target_path); + + try std.fs.copyFileAbsolute(build_runner_path, target_path, .{}); + defer std.fs.deleteFileAbsolute(target_path) catch {}; + + const zig_run_result = try std.ChildProcess.exec(.{ + .allocator = allocator, + .argv = &[_][]const u8{ "zig", "run", "build_runner.zig" }, + .cwd = directory_path, + }); + + defer { + allocator.free(zig_run_result.stdout); + allocator.free(zig_run_result.stderr); + } + + switch (zig_run_result.term) { + .Exited => |exit_code| { + if (exit_code == 0) { + std.debug.warn("Finished zig run for build file {}\n", .{build_file.uri}); + + for (build_file.packages.items) |old_pkg| { + allocator.free(old_pkg.name); + allocator.free(old_pkg.uri); + } + + build_file.packages.shrink(allocator, 0); + var line_it = std.mem.split(zig_run_result.stdout, "\n"); + while (line_it.next()) |line| { + if (std.mem.indexOfScalar(u8, line, '\x00')) |zero_byte_idx| { + const name = line[0..zero_byte_idx]; + const rel_path = line[zero_byte_idx + 1 ..]; + + const pkg_abs_path = try std.fs.path.resolve(allocator, &[_][]const u8{ directory_path, rel_path }); + defer allocator.free(pkg_abs_path); + + const pkg_uri = try URI.fromPath(allocator, pkg_abs_path); + errdefer allocator.free(pkg_uri); + + const duped_name = try std.mem.dupe(allocator, u8, name); + errdefer allocator.free(duped_name); + + (try build_file.packages.addOne(allocator)).* = .{ + .name = duped_name, + .uri = pkg_uri, + }; + } + } + } + }, + else => {}, + } +} /// This function asserts the document is not open yet and takes ownership /// of the uri and text passed in. -fn newDocument(self: *DocumentStore, uri: []const u8, text: []u8) NewDocumentError!*Handle { +fn newDocument(self: *DocumentStore, uri: []const u8, text: []u8) anyerror!*Handle { std.debug.warn("Opened document: {}\n", .{uri}); var handle = try self.allocator.create(Handle); @@ -64,13 +139,11 @@ fn newDocument(self: *DocumentStore, uri: []const u8, text: []u8) NewDocumentErr .is_build_file = null, }; - // TODO: Better logic? + // TODO: Better logic for detecting std or subdirectories? const in_std = std.mem.indexOf(u8, uri, "/std/") != null; - if (self.has_zig and std.mem.endsWith(u8, uri, "build.zig") and !in_std) { + if (self.has_zig and std.mem.endsWith(u8, uri, "/build.zig") and !in_std) { std.debug.warn("Document is a build file, extracting packages...\n", .{}); // This is a build file. - // @TODO Here copy the runner, run `zig run`, parse the output, - // make a BuildFile pointer called build_file var build_file = try self.allocator.create(BuildFile); errdefer self.allocator.destroy(build_file); @@ -81,6 +154,15 @@ fn newDocument(self: *DocumentStore, uri: []const u8, text: []u8) NewDocumentErr try self.build_files.append(self.allocator, build_file); handle.is_build_file = build_file; + + // TODO: Do this in a separate thread? + // It can take quite long. + const context = LoadPackagesContext{ + .build_file = build_file, + .allocator = self.allocator, + .build_runner_path = self.build_runner_path, + }; + try loadPackages(context); } else if (self.has_zig and !in_std) associate_build_file: { // Look into build files to see if we already have one that fits for (self.build_files.items) |build_file| { @@ -97,13 +179,11 @@ fn newDocument(self: *DocumentStore, uri: []const u8, text: []u8) NewDocumentErr defer self.allocator.free(curr_path); while (true) { if (curr_path.len == 0) break :associate_build_file; - // @TODO Add temporary traces to see what is going on. - // std.fs.path.sep if (std.mem.lastIndexOfScalar(u8, curr_path[0 .. curr_path.len - 1], std.fs.path.sep)) |idx| { // This includes the last separator - curr_path = curr_path[0..idx + 1]; - var candidate_path = try std.mem.concat(self.allocator, u8, &[_][]const u8 { curr_path, "build.zig" }); + curr_path = curr_path[0 .. idx + 1]; + var candidate_path = try std.mem.concat(self.allocator, u8, &[_][]const u8{ curr_path, "build.zig" }); defer self.allocator.free(candidate_path); // Try to open the file, read it and add the new document if we find it. var file = std.fs.cwd().openFile(candidate_path, .{ .read = true, .write = false }) catch continue; @@ -123,9 +203,6 @@ fn newDocument(self: *DocumentStore, uri: []const u8, text: []u8) NewDocumentErr } } - // @TODO: Handle the text refresh if we have a is_build_file - // @TODO: Handle package imports if we have an associated_build_file - try self.handles.putNoClobber(uri, handle); return handle; } @@ -296,6 +373,13 @@ pub fn applyChanges( } try self.refreshDocument(handle, zig_lib_path); + if (handle.is_build_file) |build_file| { + try loadPackages(.{ + .build_file = build_file, + .allocator = self.allocator, + .build_runner_path = self.build_runner_path, + }); + } } pub fn uriFromImportStr( @@ -313,7 +397,14 @@ pub fn uriFromImportStr( } else if (std.mem.eql(u8, import_str, "builtin")) { return null; // TODO find the correct zig-cache folder } else if (!std.mem.endsWith(u8, import_str, ".zig")) { - return null; // TODO find packages based on build.zig + if (handle.associated_build_file) |build_file| { + for (build_file.packages.items) |pkg| { + if (std.mem.eql(u8, import_str, pkg.name)) { + return try std.mem.dupe(allocator, u8, pkg.uri); + } + } + } + return null; } else { // Find relative uri const path = try URI.parse(allocator, handle.uri()); diff --git a/src/main.zig b/src/main.zig index c6a3895..ecac4c3 100644 --- a/src/main.zig +++ b/src/main.zig @@ -687,7 +687,7 @@ fn processJsonRpc(parser: *std.json.Parser, json: []const u8, config: Config) !v const start_time = std.time.milliTimestamp(); defer { const end_time = std.time.milliTimestamp(); - std.debug.warn("Took {}ms to process method {}\n", .{end_time - start_time, method}); + std.debug.warn("Took {}ms to process method {}\n", .{ end_time - start_time, method }); } // Core @@ -880,10 +880,10 @@ pub fn main() anyerror!void { } } - var exec_dir_bytes: [std.fs.MAX_PATH_BYTES]u8 = undefined; - const exec_dir_path = std.fs.selfExeDirPath(&exec_dir_bytes) catch break :config_read; + var exe_dir_bytes: [std.fs.MAX_PATH_BYTES]u8 = undefined; + const exe_dir_path = std.fs.selfExeDirPath(&exe_dir_bytes) catch break :config_read; - if (loadConfig(exec_dir_path)) |conf| { + if (loadConfig(exe_dir_path)) |conf| { config = conf; } } @@ -907,7 +907,7 @@ pub fn main() anyerror!void { const zig_exe = try std.fmt.allocPrint(allocator, "zig{}", .{exe_extension}); defer allocator.free(zig_exe); - var it = std.mem.tokenize(env_path,&[_]u8{std.fs.path.delimiter}); + var it = std.mem.tokenize(env_path, &[_]u8{std.fs.path.delimiter}); while (it.next()) |path| { const full_path = try std.fs.path.join(allocator, &[_][]const u8{ path, @@ -923,7 +923,14 @@ pub fn main() anyerror!void { } } - try document_store.init(allocator, has_zig); + { + var exe_dir_bytes: [std.fs.MAX_PATH_BYTES]u8 = undefined; + const exe_dir_path = try std.fs.selfExeDirPath(&exe_dir_bytes); + + const document_runner_path = try std.fs.path.resolve(allocator, &[_][]const u8{ exe_dir_path, "build_runner.zig" }); + try document_store.init(allocator, has_zig, document_runner_path); + } + defer document_store.deinit(); workspace_folder_configs = std.StringHashMap(?Config).init(allocator); From 02745f1472aceec66f44ad62f01503c7f9ca4fb5 Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Mon, 25 May 2020 19:49:04 +0300 Subject: [PATCH 08/11] Free build files in document store deinit --- src/document_store.zig | 17 +++++++++++++---- src/main.zig | 1 + 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/document_store.zig b/src/document_store.zig index ec55625..80913c0 100644 --- a/src/document_store.zig +++ b/src/document_store.zig @@ -29,7 +29,6 @@ pub const Handle = struct { } }; -// @TODO Deinit the new stuff allocator: *std.mem.Allocator, handles: std.StringHashMap(*Handle), has_zig: bool, @@ -157,12 +156,11 @@ fn newDocument(self: *DocumentStore, uri: []const u8, text: []u8) anyerror!*Hand // TODO: Do this in a separate thread? // It can take quite long. - const context = LoadPackagesContext{ + try loadPackages(.{ .build_file = build_file, .allocator = self.allocator, .build_runner_path = self.build_runner_path, - }; - try loadPackages(context); + }); } else if (self.has_zig and !in_std) associate_build_file: { // Look into build files to see if we already have one that fits for (self.build_files.items) |build_file| { @@ -587,4 +585,15 @@ pub fn deinit(self: *DocumentStore) void { } self.handles.deinit(); + + for (self.build_files.items) |build_file| { + for (build_file.packages.items) |pkg| { + self.allocator.free(pkg.name); + self.allocator.free(pkg.uri); + } + self.allocator.free(build_file.uri); + self.allocator.destroy(build_file); + } + + self.build_files.deinit(self.allocator); } diff --git a/src/main.zig b/src/main.zig index ecac4c3..ec0d373 100644 --- a/src/main.zig +++ b/src/main.zig @@ -871,6 +871,7 @@ pub fn main() anyerror!void { config_read: { const known_folders = @import("known-folders"); + const res = try known_folders.getPath(allocator, .local_configuration); if (res) |local_config_path| { defer allocator.free(local_config_path); From b82fb9c790d389ea66b45b79d4e73d0a1b8cf917 Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Mon, 25 May 2020 20:04:23 +0300 Subject: [PATCH 09/11] Added build_runner_path configuration option --- README.md | 1 + src/config.zig | 4 ++++ src/main.zig | 8 +++++--- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index abf15c2..a85c752 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ The following options are currently available. | `enable_snippets` | `bool` | `false` | Enables snippet completion, set to false for compatibility with language clients that do not support snippets (such as ale). | | `zig_lib_path` | `?[]const u8` | `null` | zig library path, e.g. `/path/to/zig/lib/zig`, used to analyze std library imports. | | `warn_style` | `bool` | `false` | Enables warnings for style *guideline* mismatches | +| `build_runner_path` | `?[]const u8` | `null` | Path to the build_runner.zig file provided by zls. This option must be present in one of the global configuration files to have any effect. `null` is equivalent to `${executable_directory}/build_runner.zig` | ## Usage diff --git a/src/config.zig b/src/config.zig index 9a6cd13..c209875 100644 --- a/src/config.zig +++ b/src/config.zig @@ -9,3 +9,7 @@ zig_lib_path: ?[]const u8 = null, /// Whether to pay attention to style issues. This is opt-in since the style /// guide explicitly states that the style info provided is a guideline only. warn_style: bool = false, + +/// Path to the build_runner.zig file. This option must be present in one of +/// the global configuration directories to have any effect. +build_runner_path: ?[]const u8 = null, diff --git a/src/main.zig b/src/main.zig index ec0d373..f93abd1 100644 --- a/src/main.zig +++ b/src/main.zig @@ -924,12 +924,14 @@ pub fn main() anyerror!void { } } - { + if (config.build_runner_path) |build_runner_path| { + try document_store.init(allocator, has_zig, try std.mem.dupe(allocator, u8, build_runner_path)); + } else { var exe_dir_bytes: [std.fs.MAX_PATH_BYTES]u8 = undefined; const exe_dir_path = try std.fs.selfExeDirPath(&exe_dir_bytes); - const document_runner_path = try std.fs.path.resolve(allocator, &[_][]const u8{ exe_dir_path, "build_runner.zig" }); - try document_store.init(allocator, has_zig, document_runner_path); + const build_runner_path = try std.fs.path.resolve(allocator, &[_][]const u8{ exe_dir_path, "build_runner.zig" }); + try document_store.init(allocator, has_zig, build_runner_path); } defer document_store.deinit(); From 379891f937ceeeb5466c4e8844cec7c5c631970b Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Mon, 25 May 2020 20:21:53 +0300 Subject: [PATCH 10/11] Dont copy the build runner if a file with that name already exists --- src/document_store.zig | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/document_store.zig b/src/document_store.zig index 80913c0..911236a 100644 --- a/src/document_store.zig +++ b/src/document_store.zig @@ -65,6 +65,22 @@ fn loadPackages(context: LoadPackagesContext) !void { const target_path = try std.fs.path.resolve(allocator, &[_][]const u8{ directory_path, "build_runner.zig" }); defer allocator.free(target_path); + // For example, instead of testing if a file exists and then opening it, just + // open it and handle the error for file not found. + var file_exists = true; + check_file_exists: { + var fhandle = std.fs.cwd().openFile(target_path, .{.read = true, .write = false }) catch |err| switch (err) { + error.FileNotFound => { + file_exists = false; + break :check_file_exists; + }, + else => break :check_file_exists, + }; + fhandle.close(); + } + + if (file_exists) return error.BuildRunnerFileExists; + try std.fs.copyFileAbsolute(build_runner_path, target_path, .{}); defer std.fs.deleteFileAbsolute(target_path) catch {}; @@ -156,11 +172,13 @@ fn newDocument(self: *DocumentStore, uri: []const u8, text: []u8) anyerror!*Hand // TODO: Do this in a separate thread? // It can take quite long. - try loadPackages(.{ + loadPackages(.{ .build_file = build_file, .allocator = self.allocator, .build_runner_path = self.build_runner_path, - }); + }) catch { + std.debug.warn("Failed to load packages of build file {}\n", .{build_file.uri}); + }; } else if (self.has_zig and !in_std) associate_build_file: { // Look into build files to see if we already have one that fits for (self.build_files.items) |build_file| { @@ -372,11 +390,13 @@ pub fn applyChanges( try self.refreshDocument(handle, zig_lib_path); if (handle.is_build_file) |build_file| { - try loadPackages(.{ + loadPackages(.{ .build_file = build_file, .allocator = self.allocator, .build_runner_path = self.build_runner_path, - }); + }) catch { + std.debug.warn("Failed to load packages of build file {}\n", .{build_file.uri}); + }; } } From 8d9b96e74997781c614c822a6aeb0745dc48792f Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Mon, 25 May 2020 20:42:58 +0300 Subject: [PATCH 11/11] Added build file ref counting and freeing --- src/document_store.zig | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/document_store.zig b/src/document_store.zig index 911236a..39d9ad7 100644 --- a/src/document_store.zig +++ b/src/document_store.zig @@ -11,6 +11,7 @@ const BuildFile = struct { uri: []const u8, }; + refs: usize, uri: []const u8, packages: std.ArrayListUnmanaged(Pkg), }; @@ -163,6 +164,7 @@ fn newDocument(self: *DocumentStore, uri: []const u8, text: []u8) anyerror!*Hand errdefer self.allocator.destroy(build_file); build_file.* = .{ + .refs = 1, .uri = try std.mem.dupe(self.allocator, u8, uri), .packages = .{}, }; @@ -186,6 +188,7 @@ fn newDocument(self: *DocumentStore, uri: []const u8, text: []u8) anyerror!*Hand if (std.mem.startsWith(u8, uri, build_file_base_uri)) { std.debug.warn("Found an associated build file: {}\n", .{build_file.uri}); + build_file.refs += 1; handle.associated_build_file = build_file; break :associate_build_file; } @@ -227,6 +230,9 @@ pub fn openDocument(self: *DocumentStore, uri: []const u8, text: []const u8) !*H if (self.handles.get(uri)) |entry| { std.debug.warn("Document already open: {}, incrementing count\n", .{uri}); entry.value.count += 1; + if (entry.value.is_build_file) |build_file| { + build_file.refs += 1; + } std.debug.warn("New count: {}\n", .{entry.value.count}); return entry.value; } @@ -239,9 +245,41 @@ pub fn openDocument(self: *DocumentStore, uri: []const u8, text: []const u8) !*H return try self.newDocument(duped_uri, duped_text); } +fn decrementBuildFileRefs(self: *DocumentStore, build_file_ptr: *?*BuildFile) void { + const build_file = build_file_ptr.*.?; + + build_file.refs -= 1; + if (build_file.refs == 0) { + std.debug.warn("Freeing build file {}\n", .{build_file.uri}); + // Free the build file, set the pointer to null. + for (build_file.packages.items) |pkg| { + self.allocator.free(pkg.name); + self.allocator.free(pkg.uri); + } + + build_file.packages.deinit(self.allocator); + + // Decrement count of the document since one count comes + // from the build file existing. + self.decrementCount(build_file.uri); + self.allocator.free(build_file.uri); + + // Remove the build file from the array list + _ = self.build_files.swapRemove(std.mem.indexOfScalar(*BuildFile, self.build_files.items, build_file).?); + + self.allocator.destroy(build_file); + build_file_ptr.* = null; + } +} + fn decrementCount(self: *DocumentStore, uri: []const u8) void { if (self.handles.get(uri)) |entry| { entry.value.count -= 1; + + if (entry.value.associated_build_file != null) { + self.decrementBuildFileRefs(&entry.value.associated_build_file); + } + if (entry.value.count > 0) return;