Support for user-configurable build options

This commit is contained in:
J.C. Moyer 2022-09-27 13:34:16 -04:00
parent ecf4e112a5
commit 0b0e6a7cb9
3 changed files with 127 additions and 17 deletions

View File

@ -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 <options>`
relative_builtin_path: ?[]const u8 = null,
/// If provided, this list of options will be passed to `build.zig`.
build_options: ?[]BuildOption = null,

View File

@ -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,11 +180,21 @@ 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{
// 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,
@ -175,7 +209,16 @@ fn loadBuildConfiguration(context: LoadBuildConfigContext) !void {
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,

View File

@ -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);