build system: introduce system library integration

* New --help section
* Add b.systemLibraryOption
* Rework the build runner CLI logic a bit
This commit is contained in:
Andrew Kelley 2024-01-31 22:02:27 -07:00
parent f2e249e920
commit 8f867eaf84
2 changed files with 189 additions and 118 deletions

View File

@ -76,6 +76,8 @@ pub fn main() !void {
cache.addPrefix(global_cache_directory);
cache.hash.addBytes(builtin.zig_version_string);
var system_library_options: std.StringArrayHashMapUnmanaged(std.Build.SystemLibraryMode) = .{};
const builder = try std.Build.create(
arena,
zig_exe,
@ -85,8 +87,8 @@ pub fn main() !void {
host,
&cache,
dependencies.root_deps,
&system_library_options,
);
defer builder.destroy();
var targets = ArrayList([]const u8).init(arena);
var debug_log_scopes = ArrayList([]const u8).init(arena);
@ -100,64 +102,50 @@ pub fn main() !void {
var color: Color = .auto;
var seed: u32 = 0;
var prominent_compile_errors: bool = false;
var help_menu: bool = false;
var steps_menu: bool = false;
const stderr_stream = io.getStdErr().writer();
const stdout_stream = io.getStdOut().writer();
while (nextArg(args, &arg_idx)) |arg| {
if (mem.startsWith(u8, arg, "-D")) {
const option_contents = arg[2..];
if (option_contents.len == 0) {
std.debug.print("Expected option name after '-D'\n\n", .{});
usageAndErr(builder, false, stderr_stream);
}
if (option_contents.len == 0)
fatalWithHint("expected option name after '-D'", .{});
if (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))
usageAndErr(builder, false, stderr_stream);
fatal(" access the help menu with 'zig build -h'", .{});
} else {
if (try builder.addUserInputFlag(option_contents))
usageAndErr(builder, false, stderr_stream);
fatal(" access the help menu with 'zig build -h'", .{});
}
} else if (mem.startsWith(u8, arg, "-")) {
if (mem.eql(u8, arg, "--verbose")) {
builder.verbose = true;
} else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
return usage(builder, false, stdout_stream);
help_menu = true;
} else if (mem.eql(u8, arg, "-p") or mem.eql(u8, arg, "--prefix")) {
install_prefix = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected argument after {s}\n\n", .{arg});
usageAndErr(builder, false, stderr_stream);
};
install_prefix = nextArgOrFatal(args, &arg_idx);
} else if (mem.eql(u8, arg, "-l") or mem.eql(u8, arg, "--list-steps")) {
return steps(builder, false, stdout_stream);
steps_menu = true;
} else if (mem.eql(u8, arg, "--system-lib")) {
const name = nextArgOrFatal(args, &arg_idx);
builder.system_library_options.put(arena, name, .user_enabled) catch @panic("OOM");
} else if (mem.eql(u8, arg, "--no-system-lib")) {
const name = nextArgOrFatal(args, &arg_idx);
builder.system_library_options.put(arena, name, .user_disabled) catch @panic("OOM");
} else if (mem.eql(u8, arg, "--prefix-lib-dir")) {
dir_list.lib_dir = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected argument after {s}\n\n", .{arg});
usageAndErr(builder, false, stderr_stream);
};
dir_list.lib_dir = nextArgOrFatal(args, &arg_idx);
} else if (mem.eql(u8, arg, "--prefix-exe-dir")) {
dir_list.exe_dir = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected argument after {s}\n\n", .{arg});
usageAndErr(builder, false, stderr_stream);
};
dir_list.exe_dir = nextArgOrFatal(args, &arg_idx);
} else if (mem.eql(u8, arg, "--prefix-include-dir")) {
dir_list.include_dir = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected argument after {s}\n\n", .{arg});
usageAndErr(builder, false, stderr_stream);
};
dir_list.include_dir = nextArgOrFatal(args, &arg_idx);
} else if (mem.eql(u8, arg, "--sysroot")) {
const sysroot = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected argument after {s}\n\n", .{arg});
usageAndErr(builder, false, stderr_stream);
};
builder.sysroot = sysroot;
builder.sysroot = nextArgOrFatal(args, &arg_idx);
} else if (mem.eql(u8, arg, "--maxrss")) {
const max_rss_text = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected argument after {s}\n\n", .{arg});
usageAndErr(builder, false, stderr_stream);
};
const max_rss_text = nextArgOrFatal(args, &arg_idx);
max_rss = std.fmt.parseIntSizeSuffix(max_rss_text, 10) catch |err| {
std.debug.print("invalid byte size: '{s}': {s}\n", .{
max_rss_text, @errorName(err),
@ -167,66 +155,45 @@ pub fn main() !void {
} else if (mem.eql(u8, arg, "--skip-oom-steps")) {
skip_oom_steps = true;
} else if (mem.eql(u8, arg, "--search-prefix")) {
const search_prefix = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected argument after {s}\n\n", .{arg});
usageAndErr(builder, false, stderr_stream);
};
const search_prefix = nextArgOrFatal(args, &arg_idx);
builder.addSearchPrefix(search_prefix);
} else if (mem.eql(u8, arg, "--libc")) {
const libc_file = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected argument after {s}\n\n", .{arg});
usageAndErr(builder, false, stderr_stream);
};
builder.libc_file = libc_file;
builder.libc_file = nextArgOrFatal(args, &arg_idx);
} else if (mem.eql(u8, arg, "--color")) {
const next_arg = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected [auto|on|off] after {s}\n\n", .{arg});
usageAndErr(builder, false, stderr_stream);
};
const next_arg = nextArg(args, &arg_idx) orelse
fatalWithHint("expected [auto|on|off] after '{s}'", .{arg});
color = std.meta.stringToEnum(Color, next_arg) orelse {
std.debug.print("Expected [auto|on|off] after {s}, found '{s}'\n\n", .{ arg, next_arg });
usageAndErr(builder, false, stderr_stream);
fatalWithHint("expected [auto|on|off] after '{s}', found '{s}'", .{
arg, next_arg,
});
};
} else if (mem.eql(u8, arg, "--summary")) {
const next_arg = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected [all|failures|none] after {s}\n\n", .{arg});
usageAndErr(builder, false, stderr_stream);
};
const next_arg = nextArg(args, &arg_idx) orelse
fatalWithHint("expected [all|failures|none] after '{s}'", .{arg});
summary = std.meta.stringToEnum(Summary, next_arg) orelse {
std.debug.print("Expected [all|failures|none] after {s}, found '{s}'\n\n", .{ arg, next_arg });
usageAndErr(builder, false, stderr_stream);
fatalWithHint("expected [all|failures|none] after '{s}', found '{s}'", .{
arg, next_arg,
});
};
} else if (mem.eql(u8, arg, "--zig-lib-dir")) {
builder.zig_lib_dir = .{ .cwd_relative = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected argument after {s}\n\n", .{arg});
usageAndErr(builder, false, stderr_stream);
} };
builder.zig_lib_dir = .{ .cwd_relative = nextArgOrFatal(args, &arg_idx) };
} else if (mem.eql(u8, arg, "--seed")) {
const next_arg = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected u32 after {s}\n\n", .{arg});
usageAndErr(builder, false, stderr_stream);
};
const next_arg = nextArg(args, &arg_idx) orelse
fatalWithHint("expected u32 after '{s}'", .{arg});
seed = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err| {
std.debug.print("unable to parse seed '{s}' as 32-bit integer: {s}\n", .{
fatal("unable to parse seed '{s}' as 32-bit integer: {s}\n", .{
next_arg, @errorName(err),
});
process.exit(1);
};
} else if (mem.eql(u8, arg, "--debug-log")) {
const next_arg = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected argument after {s}\n\n", .{arg});
usageAndErr(builder, false, stderr_stream);
};
const next_arg = nextArgOrFatal(args, &arg_idx);
try debug_log_scopes.append(next_arg);
} else if (mem.eql(u8, arg, "--debug-pkg-config")) {
builder.debug_pkg_config = true;
} else if (mem.eql(u8, arg, "--debug-compile-errors")) {
builder.debug_compile_errors = true;
} else if (mem.eql(u8, arg, "--glibc-runtimes")) {
builder.glibc_runtimes_dir = nextArg(args, &arg_idx) orelse {
std.debug.print("Expected argument after {s}\n\n", .{arg});
usageAndErr(builder, false, stderr_stream);
};
builder.glibc_runtimes_dir = nextArgOrFatal(args, &arg_idx);
} else if (mem.eql(u8, arg, "--verbose-link")) {
builder.verbose_link = true;
} else if (mem.eql(u8, arg, "--verbose-air")) {
@ -292,8 +259,7 @@ pub fn main() !void {
builder.args = argsRest(args, arg_idx);
break;
} else {
std.debug.print("Unrecognized argument: {s}\n\n", .{arg});
usageAndErr(builder, false, stderr_stream);
fatalWithHint("unrecognized argument: '{s}'", .{arg});
}
} else {
try targets.append(arg);
@ -319,8 +285,17 @@ pub fn main() !void {
try builder.runBuild(root);
}
if (builder.validateUserInputDidItFail())
usageAndErr(builder, true, stderr_stream);
if (builder.validateUserInputDidItFail()) {
fatal(" access the help menu with 'zig build -h'", .{});
}
validateSystemLibraryOptions(builder);
if (help_menu)
return usage(builder, stdout_stream);
if (steps_menu)
return steps(builder, stdout_stream);
var run: Run = .{
.max_rss = max_rss,
@ -389,7 +364,7 @@ fn runStepNames(
for (0..step_names.len) |i| {
const step_name = step_names[step_names.len - i - 1];
const s = b.top_level_steps.get(step_name) orelse {
std.debug.print("no step named '{s}'. Access the help menu with 'zig build -h'\n", .{step_name});
std.debug.print("no step named '{s}'\n access the help menu with 'zig build -h'\n", .{step_name});
process.exit(1);
};
step_stack.putAssumeCapacity(&s.step, {});
@ -1037,13 +1012,7 @@ fn printErrorMessages(b: *std.Build, failing_step: *Step, run: *const Run) !void
}
}
fn steps(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !void {
// run the build script to collect the options
if (!already_ran_build) {
builder.resolveInstallPrefix(null, .{});
try builder.runBuild(root);
}
fn steps(builder: *std.Build, out_stream: anytype) !void {
const allocator = builder.allocator;
for (builder.top_level_steps.values()) |top_level_step| {
const name = if (&top_level_step.step == builder.default_step)
@ -1054,29 +1023,22 @@ fn steps(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi
}
}
fn usage(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !void {
// run the build script to collect the options
if (!already_ran_build) {
builder.resolveInstallPrefix(null, .{});
try builder.runBuild(root);
}
fn usage(b: *std.Build, out_stream: anytype) !void {
try out_stream.print(
\\
\\Usage: {s} build [steps] [options]
\\
\\Steps:
\\
, .{builder.zig_exe});
try steps(builder, true, out_stream);
, .{b.zig_exe});
try steps(b, out_stream);
try out_stream.writeAll(
\\
\\General Options:
\\ -p, --prefix [path] Override default install prefix
\\ --prefix-lib-dir [path] Override default library directory path
\\ --prefix-exe-dir [path] Override default executable directory path
\\ --prefix-include-dir [path] Override default include directory path
\\ -p, --prefix [path] Where to put installed files (default: zig-out)
\\ --prefix-lib-dir [path] Where to put installed libraries
\\ --prefix-exe-dir [path] Where to put installed executables
\\ --prefix-include-dir [path] Where to put installed C header files
\\
\\ --sysroot [path] Set the system root directory (usually /)
\\ --search-prefix [path] Add a path to look for binaries, libraries, headers
@ -1116,16 +1078,15 @@ fn usage(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi
\\
);
const allocator = builder.allocator;
if (builder.available_options_list.items.len == 0) {
const arena = b.allocator;
if (b.available_options_list.items.len == 0) {
try out_stream.print(" (none)\n", .{});
} else {
for (builder.available_options_list.items) |option| {
const name = try fmt.allocPrint(allocator, " -D{s}=[{s}]", .{
for (b.available_options_list.items) |option| {
const name = try fmt.allocPrint(arena, " -D{s}=[{s}]", .{
option.name,
@tagName(option.type_id),
});
defer allocator.free(name);
try out_stream.print("{s:<30} {s}\n", .{ name, option.description });
if (option.enum_options) |enum_options| {
const padding = " " ** 33;
@ -1137,6 +1098,31 @@ fn usage(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi
}
}
try out_stream.writeAll(
\\
\\System Integration Options:
\\ --system [dir] System Package Mode. Disable fetching; prefer system libs
\\ --host-target [triple] Use the provided target as the host
\\ --host-cpu [cpu] Use the provided CPU as the host
\\ --system-lib [name] Use the system-provided library
\\ --no-system-lib [name] Do not use the system-provided library
\\
\\ Available System Library Integrations: Enabled:
\\
);
if (b.system_library_options.entries.len == 0) {
try out_stream.writeAll(" (none) -\n");
} else {
for (b.system_library_options.keys(), b.system_library_options.values()) |name, v| {
const status = switch (v) {
.declared_enabled => "yes",
.declared_disabled => "no",
.user_enabled, .user_disabled => unreachable, // already emitted error
};
try out_stream.print(" {s:<43} {s}\n", .{ name, status });
}
}
try out_stream.writeAll(
\\
\\Advanced Options:
@ -1161,17 +1147,19 @@ fn usage(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi
);
}
fn usageAndErr(builder: *std.Build, already_ran_build: bool, out_stream: anytype) noreturn {
usage(builder, already_ran_build, out_stream) catch {};
process.exit(1);
}
fn nextArg(args: [][:0]const u8, idx: *usize) ?[:0]const u8 {
if (idx.* >= args.len) return null;
defer idx.* += 1;
return args[idx.*];
}
fn nextArgOrFatal(args: [][:0]const u8, idx: *usize) [:0]const u8 {
return nextArg(args, idx) orelse {
std.debug.print("expected argument after '{s}'\n access the help menu with 'zig build -h'\n", .{args[idx.*]});
process.exit(1);
};
}
fn argsRest(args: [][:0]const u8, idx: usize) ?[][:0]const u8 {
if (idx >= args.len) return null;
return args[idx..];
@ -1202,3 +1190,32 @@ fn renderOptions(ttyconf: std.io.tty.Config) std.zig.ErrorBundle.RenderOptions {
.include_reference_trace = ttyconf != .no_color,
};
}
fn fatalWithHint(comptime f: []const u8, args: anytype) noreturn {
std.debug.print(f ++ "\n access the help menu with 'zig build -h'\n", args);
process.exit(1);
}
fn fatal(comptime f: []const u8, args: anytype) noreturn {
std.debug.print(f ++ "\n", args);
process.exit(1);
}
fn validateSystemLibraryOptions(b: *std.Build) void {
var bad = false;
for (b.system_library_options.keys(), b.system_library_options.values()) |k, v| {
switch (v) {
.user_disabled, .user_enabled => {
// The user tried to enable or disable a system library integration, but
// the build script did not recognize that option.
std.debug.print("system library name not recognized by build script: '{s}'\n", .{k});
bad = true;
},
.declared_disabled, .declared_enabled => {},
}
}
if (bad) {
std.debug.print(" access the help menu with 'zig build -h'\n", .{});
process.exit(1);
}
}

View File

@ -28,6 +28,9 @@ allocator: Allocator,
user_input_options: UserInputOptionsMap,
available_options_map: AvailableOptionsMap,
available_options_list: ArrayList(AvailableOption),
/// All Build instances share this hash map.
system_library_options: *std.StringArrayHashMapUnmanaged(SystemLibraryMode),
system_package_mode: bool,
verbose: bool,
verbose_link: bool,
verbose_cc: bool,
@ -100,6 +103,21 @@ available_deps: AvailableDeps,
const AvailableDeps = []const struct { []const u8, []const u8 };
pub const SystemLibraryMode = enum {
/// User asked for the library to be disabled.
/// The build runner has not confirmed whether the setting is recognized yet.
user_disabled,
/// User asked for the library to be enabled.
/// The build runner has not confirmed whether the setting is recognized yet.
user_enabled,
/// The build runner has confirmed that this setting is recognized.
/// System integration with this library has been resolved to off.
declared_disabled,
/// The build runner has confirmed that this setting is recognized.
/// System integration with this library has been resolved to on.
declared_enabled,
};
const InitializedDepMap = std.HashMap(InitializedDepKey, *Dependency, InitializedDepContext, std.hash_map.default_max_load_percentage);
const InitializedDepKey = struct {
build_root_string: []const u8,
@ -216,6 +234,7 @@ pub fn create(
host: ResolvedTarget,
cache: *Cache,
available_deps: AvailableDeps,
system_library_options: *std.StringArrayHashMapUnmanaged(SystemLibraryMode),
) !*Build {
const env_map = try allocator.create(EnvMap);
env_map.* = try process.getEnvMap(allocator);
@ -278,6 +297,8 @@ pub fn create(
.named_writefiles = std.StringArrayHashMap(*Step.WriteFile).init(allocator),
.initialized_deps = initialized_deps,
.available_deps = available_deps,
.system_library_options = system_library_options,
.system_package_mode = false,
};
try self.top_level_steps.put(allocator, self.install_tls.step.name, &self.install_tls);
try self.top_level_steps.put(allocator, self.uninstall_tls.step.name, &self.uninstall_tls);
@ -297,7 +318,13 @@ fn createChild(
return child;
}
fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Directory, pkg_deps: AvailableDeps, user_input_options: UserInputOptionsMap) !*Build {
fn createChildOnly(
parent: *Build,
dep_name: []const u8,
build_root: Cache.Directory,
pkg_deps: AvailableDeps,
user_input_options: UserInputOptionsMap,
) !*Build {
const allocator = parent.allocator;
const child = try allocator.create(Build);
child.* = .{
@ -366,6 +393,8 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc
.named_writefiles = std.StringArrayHashMap(*Step.WriteFile).init(allocator),
.initialized_deps = parent.initialized_deps,
.available_deps = pkg_deps,
.system_library_options = parent.system_library_options,
.system_package_mode = parent.system_package_mode,
};
try child.top_level_steps.put(allocator, child.install_tls.step.name, &child.install_tls);
try child.top_level_steps.put(allocator, child.uninstall_tls.step.name, &child.uninstall_tls);
@ -1367,7 +1396,7 @@ pub fn addUserInputOption(self: *Build, name_raw: []const u8, value_raw: []const
});
},
.flag => {
log.warn("Option '-D{s}={s}' conflicts with flag '-D{s}'.", .{ name, value, name });
log.warn("option '-D{s}={s}' conflicts with flag '-D{s}'.", .{ name, value, name });
return true;
},
.map => |*map| {
@ -1427,17 +1456,17 @@ fn markInvalidUserInput(self: *Build) void {
self.invalid_user_input = true;
}
pub fn validateUserInputDidItFail(self: *Build) bool {
// make sure all args are used
var it = self.user_input_options.iterator();
pub fn validateUserInputDidItFail(b: *Build) bool {
// Make sure all args are used.
var it = b.user_input_options.iterator();
while (it.next()) |entry| {
if (!entry.value_ptr.used) {
log.err("Invalid option: -D{s}", .{entry.key_ptr.*});
self.markInvalidUserInput();
log.err("invalid option: -D{s}", .{entry.key_ptr.*});
b.markInvalidUserInput();
}
}
return self.invalid_user_input;
return b.invalid_user_input;
}
fn allocPrintCmd(ally: Allocator, opt_cwd: ?[]const u8, argv: []const []const u8) ![]u8 {
@ -2296,6 +2325,31 @@ pub fn wantSharedLibSymLinks(target: Target) bool {
return target.os.tag != .windows;
}
pub fn systemLibraryOption(b: *Build, name: []const u8) bool {
const gop = b.system_library_options.getOrPut(b.allocator, name) catch @panic("OOM");
if (gop.found_existing) switch (gop.value_ptr.*) {
.user_disabled => {
gop.value_ptr.* = .declared_disabled;
return false;
},
.user_enabled => {
gop.value_ptr.* = .declared_enabled;
return true;
},
.declared_disabled => return false,
.declared_enabled => return true,
} else {
gop.key_ptr.* = b.dupe(name);
if (b.system_package_mode) {
gop.value_ptr.* = .declared_enabled;
return true;
} else {
gop.value_ptr.* = .declared_disabled;
return false;
}
}
}
test {
_ = Cache;
_ = Step;