diff --git a/lib/build_runner.zig b/lib/build_runner.zig index 54186685d6..4e478e798b 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -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); + } +} diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 59dee7d89d..a9176debb3 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -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;