diff --git a/src/BuildAssociatedConfig.zig b/src/BuildAssociatedConfig.zig index ba0bd5d..d597942 100644 --- a/src/BuildAssociatedConfig.zig +++ b/src/BuildAssociatedConfig.zig @@ -1,4 +1,44 @@ -// Configuration options related to a specific `BuildFile`. +//! Configuration options related to a specific `BuildFile`. +const std = @import("std"); + +pub const BuildOption = struct { + name: []const u8, + value: ?[]const u8 = null, + + /// Frees the strings assocated with this `BuildOption` and invalidates `self`. + pub fn deinit(self: *BuildOption, allocator: std.mem.Allocator) void { + allocator.free(self.name); + if (self.value) |val| { + allocator.free(val); + } + self.* = undefined; + } + + /// Duplicates the `BuildOption`, copying internal strings. Caller owns returned option with contents + /// allocated using `allocator`. + pub fn dupe(self: BuildOption, allocator: std.mem.Allocator) !BuildOption { + const copy_name = try allocator.dupe(u8, self.name); + errdefer allocator.free(copy_name); + const copy_value = if (self.value) |val| + try allocator.dupe(u8, val) + else + null; + return BuildOption{ + .name = copy_name, + .value = copy_value, + }; + } + + /// Formats the `BuildOption` as a command line parameter compatible with `zig build`. This will either be + /// `-Dname=value` or `-Dname`. Caller owns returned slice allocated using `allocator`. + pub fn formatParam(self: BuildOption, allocator: std.mem.Allocator) ![]const u8 { + if (self.value) |val| { + return try std.fmt.allocPrint(allocator, "-D{s}={s}", .{ self.name, val }); + } else { + return try std.fmt.allocPrint(allocator, "-D{s}", .{self.name}); + } + } +}; /// If provided this path is used when resolving `@import("builtin")` /// It is relative to the directory containing the `build.zig` @@ -6,3 +46,6 @@ /// This file should contain the output of: /// `zig build-exe/build-lib/build-obj --show-builtin ` relative_builtin_path: ?[]const u8 = null, + +/// If provided, this list of options will be passed to `build.zig`. +build_options: ?[]BuildOption = null, diff --git a/src/DocumentStore.zig b/src/DocumentStore.zig index 6dce261..67cbb64 100644 --- a/src/DocumentStore.zig +++ b/src/DocumentStore.zig @@ -23,9 +23,16 @@ const BuildFile = struct { uri: []const u8, config: BuildFileConfig, builtin_uri: ?[]const u8 = null, + build_options: ?[]BuildAssociatedConfig.BuildOption = null, pub fn destroy(self: *BuildFile, allocator: std.mem.Allocator) void { if (self.builtin_uri) |builtin_uri| allocator.free(builtin_uri); + if (self.build_options) |opts| { + for (opts) |*opt| { + opt.deinit(allocator); + } + allocator.free(opts); + } allocator.destroy(self); } }; @@ -115,6 +122,8 @@ fn loadBuildAssociatedConfiguration(allocator: std.mem.Allocator, build_file: *B const config_file_path = try std.fs.path.join(allocator, &[_][]const u8{ directory_path, "zls.build.json" }); defer allocator.free(config_file_path); + log.info("Attempting to load build-associated config from {s}", .{config_file_path}); + var config_file = std.fs.cwd().openFile(config_file_path, .{}) catch |err| { if (err == error.FileNotFound) return; return err; @@ -133,6 +142,21 @@ fn loadBuildAssociatedConfiguration(allocator: std.mem.Allocator, build_file: *B defer allocator.free(absolute_builtin_path); build_file.builtin_uri = try URI.fromPath(allocator, absolute_builtin_path); } + + if (build_associated_config.build_options) |opts| { + // Copy options out of json parse result since they will be invalidated + var build_opts = try std.ArrayListUnmanaged(BuildAssociatedConfig.BuildOption).initCapacity(allocator, opts.len); + errdefer { + for (build_opts.items) |*opt| { + opt.deinit(allocator); + } + build_opts.deinit(allocator); + } + for (opts) |opt| { + try build_opts.append(allocator, try opt.dupe(allocator)); + } + build_file.build_options = build_opts.toOwnedSlice(allocator); + } } const LoadBuildConfigContext = struct { @@ -156,26 +180,45 @@ fn loadBuildConfiguration(context: LoadBuildConfigContext) !void { const global_cache_path = context.global_cache_path; const zig_exe_path = context.zig_exe_path; + var arena = std.heap.ArenaAllocator.init(allocator); + defer arena.deinit(); + const arena_allocator = arena.allocator(); + const build_file_path = context.build_file_path orelse try URI.parse(allocator, build_file.uri); defer if (context.build_file_path == null) allocator.free(build_file_path); const directory_path = build_file_path[0 .. build_file_path.len - "build.zig".len]; - const args: []const []const u8 = &[_][]const u8{ - zig_exe_path, - "run", - build_runner_path, - "--cache-dir", - global_cache_path, - "--pkg-begin", - "@build@", - build_file_path, - "--pkg-end", - "--", - zig_exe_path, - directory_path, - context.cache_root, - context.global_cache_root, - }; + // enough capacity for arguments always passed to the build runner plus a couple build options + var arglist = try std.ArrayListUnmanaged([]const u8).initCapacity(arena_allocator, 32); + defer arglist.deinit(arena_allocator); + + try arglist.appendSlice( + arena_allocator, + &[_][]const u8{ + zig_exe_path, + "run", + build_runner_path, + "--cache-dir", + global_cache_path, + "--pkg-begin", + "@build@", + build_file_path, + "--pkg-end", + "--", + zig_exe_path, + directory_path, + context.cache_root, + context.global_cache_root, + }, + ); + + if (build_file.build_options) |opts| { + for (opts) |opt| { + try arglist.append(allocator, try opt.formatParam(arena_allocator)); + } + } + + const args: []const []const u8 = arglist.items; const zig_run_result = try std.ChildProcess.exec(.{ .allocator = allocator, diff --git a/src/special/build_runner.zig b/src/special/build_runner.zig index fe17799..50a9c1a 100644 --- a/src/special/build_runner.zig +++ b/src/special/build_runner.zig @@ -57,6 +57,30 @@ pub fn main() !void { defer builder.destroy(); + while (nextArg(args, &arg_idx)) |arg| { + if (std.mem.startsWith(u8, arg, "-D")) { + const option_contents = arg[2..]; + if (option_contents.len == 0) { + log.err("Expected option name after '-D'\n\n", .{}); + return error.InvalidArgs; + } + if (std.mem.indexOfScalar(u8, option_contents, '=')) |name_end| { + const option_name = option_contents[0..name_end]; + const option_value = option_contents[name_end + 1 ..]; + if (try builder.addUserInputOption(option_name, option_value)) { + log.err("Option conflict '-D{s}'\n\n", .{option_name}); + return error.InvalidArgs; + } + } else { + const option_name = option_contents; + if (try builder.addUserInputFlag(option_name)) { + log.err("Option conflict '-D{s}'\n\n", .{option_name}); + return error.InvalidArgs; + } + } + } + } + builder.resolveInstallPrefix(null, Builder.DirList{}); try runBuild(builder);