diff --git a/lib/std/Build.zig b/lib/std/Build.zig index e65a71e12b..e5b9e072f7 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -408,104 +408,127 @@ fn createChildOnly( return child; } -fn userInputOptionsFromArgs(allocator: Allocator, args: anytype) UserInputOptionsMap { - var user_input_options = UserInputOptionsMap.init(allocator); +fn userInputOptionsFromArgs(arena: Allocator, args: anytype) UserInputOptionsMap { + var map = UserInputOptionsMap.init(arena); inline for (@typeInfo(@TypeOf(args)).@"struct".fields) |field| { - const v = @field(args, field.name); - const T = @TypeOf(v); - switch (T) { - Target.Query => { - user_input_options.put(field.name, .{ - .name = field.name, - .value = .{ .scalar = v.zigTriple(allocator) catch @panic("OOM") }, - .used = false, - }) catch @panic("OOM"); - user_input_options.put("cpu", .{ - .name = "cpu", - .value = .{ .scalar = v.serializeCpuAlloc(allocator) catch @panic("OOM") }, - .used = false, - }) catch @panic("OOM"); - }, - ResolvedTarget => { - user_input_options.put(field.name, .{ - .name = field.name, - .value = .{ .scalar = v.query.zigTriple(allocator) catch @panic("OOM") }, - .used = false, - }) catch @panic("OOM"); - user_input_options.put("cpu", .{ - .name = "cpu", - .value = .{ .scalar = v.query.serializeCpuAlloc(allocator) catch @panic("OOM") }, - .used = false, - }) catch @panic("OOM"); - }, - LazyPath => { - user_input_options.put(field.name, .{ - .name = field.name, - .value = .{ .lazy_path = v.dupeInner(allocator) }, - .used = false, - }) catch @panic("OOM"); - }, - []const LazyPath => { - var list = ArrayList(LazyPath).initCapacity(allocator, v.len) catch @panic("OOM"); - for (v) |lp| list.appendAssumeCapacity(lp.dupeInner(allocator)); - user_input_options.put(field.name, .{ - .name = field.name, - .value = .{ .lazy_path_list = list }, - .used = false, - }) catch @panic("OOM"); - }, - []const u8 => { - user_input_options.put(field.name, .{ - .name = field.name, - .value = .{ .scalar = v }, - .used = false, - }) catch @panic("OOM"); - }, - []const []const u8 => { - var list = ArrayList([]const u8).initCapacity(allocator, v.len) catch @panic("OOM"); - list.appendSliceAssumeCapacity(v); - - user_input_options.put(field.name, .{ - .name = field.name, - .value = .{ .list = list }, - .used = false, - }) catch @panic("OOM"); - }, - else => switch (@typeInfo(T)) { - .bool => { - user_input_options.put(field.name, .{ - .name = field.name, - .value = .{ .scalar = if (v) "true" else "false" }, - .used = false, - }) catch @panic("OOM"); - }, - .@"enum", .enum_literal => { - user_input_options.put(field.name, .{ - .name = field.name, - .value = .{ .scalar = @tagName(v) }, - .used = false, - }) catch @panic("OOM"); - }, - .comptime_int, .int => { - user_input_options.put(field.name, .{ - .name = field.name, - .value = .{ .scalar = std.fmt.allocPrint(allocator, "{d}", .{v}) catch @panic("OOM") }, - .used = false, - }) catch @panic("OOM"); - }, - .comptime_float, .float => { - user_input_options.put(field.name, .{ - .name = field.name, - .value = .{ .scalar = std.fmt.allocPrint(allocator, "{e}", .{v}) catch @panic("OOM") }, - .used = false, - }) catch @panic("OOM"); - }, - else => @compileError("option '" ++ field.name ++ "' has unsupported type: " ++ @typeName(T)), - }, - } + if (field.type == @Type(.null)) continue; + addUserInputOptionFromArg(arena, &map, field, field.type, @field(args, field.name)); } + return map; +} - return user_input_options; +fn addUserInputOptionFromArg( + arena: Allocator, + map: *UserInputOptionsMap, + field: std.builtin.Type.StructField, + comptime T: type, + /// If null, the value won't be added, but `T` will still be type-checked. + maybe_value: ?T, +) void { + switch (T) { + Target.Query => return if (maybe_value) |v| { + map.put(field.name, .{ + .name = field.name, + .value = .{ .scalar = v.zigTriple(arena) catch @panic("OOM") }, + .used = false, + }) catch @panic("OOM"); + map.put("cpu", .{ + .name = "cpu", + .value = .{ .scalar = v.serializeCpuAlloc(arena) catch @panic("OOM") }, + .used = false, + }) catch @panic("OOM"); + }, + ResolvedTarget => return if (maybe_value) |v| { + map.put(field.name, .{ + .name = field.name, + .value = .{ .scalar = v.query.zigTriple(arena) catch @panic("OOM") }, + .used = false, + }) catch @panic("OOM"); + map.put("cpu", .{ + .name = "cpu", + .value = .{ .scalar = v.query.serializeCpuAlloc(arena) catch @panic("OOM") }, + .used = false, + }) catch @panic("OOM"); + }, + LazyPath => return if (maybe_value) |v| { + map.put(field.name, .{ + .name = field.name, + .value = .{ .lazy_path = v.dupeInner(arena) }, + .used = false, + }) catch @panic("OOM"); + }, + []const LazyPath => return if (maybe_value) |v| { + var list = ArrayList(LazyPath).initCapacity(arena, v.len) catch @panic("OOM"); + for (v) |lp| list.appendAssumeCapacity(lp.dupeInner(arena)); + map.put(field.name, .{ + .name = field.name, + .value = .{ .lazy_path_list = list }, + .used = false, + }) catch @panic("OOM"); + }, + []const u8 => return if (maybe_value) |v| { + map.put(field.name, .{ + .name = field.name, + .value = .{ .scalar = v }, + .used = false, + }) catch @panic("OOM"); + }, + []const []const u8 => return if (maybe_value) |v| { + var list = ArrayList([]const u8).initCapacity(arena, v.len) catch @panic("OOM"); + list.appendSliceAssumeCapacity(v); + map.put(field.name, .{ + .name = field.name, + .value = .{ .list = list }, + .used = false, + }) catch @panic("OOM"); + }, + else => switch (@typeInfo(T)) { + .bool => return if (maybe_value) |v| { + map.put(field.name, .{ + .name = field.name, + .value = .{ .scalar = if (v) "true" else "false" }, + .used = false, + }) catch @panic("OOM"); + }, + .@"enum", .enum_literal => return if (maybe_value) |v| { + map.put(field.name, .{ + .name = field.name, + .value = .{ .scalar = @tagName(v) }, + .used = false, + }) catch @panic("OOM"); + }, + .comptime_int, .int => return if (maybe_value) |v| { + map.put(field.name, .{ + .name = field.name, + .value = .{ .scalar = std.fmt.allocPrint(arena, "{d}", .{v}) catch @panic("OOM") }, + .used = false, + }) catch @panic("OOM"); + }, + .comptime_float, .float => return if (maybe_value) |v| { + map.put(field.name, .{ + .name = field.name, + .value = .{ .scalar = std.fmt.allocPrint(arena, "{e}", .{v}) catch @panic("OOM") }, + .used = false, + }) catch @panic("OOM"); + }, + .null => unreachable, + .optional => |info| switch (@typeInfo(info.child)) { + .optional => {}, + else => { + addUserInputOptionFromArg( + arena, + map, + field, + info.child, + maybe_value orelse null, + ); + return; + }, + }, + else => {}, + }, + } + @compileError("option '" ++ field.name ++ "' has unsupported type: " ++ @typeName(field.type)); } const OrderedUserValue = union(enum) { diff --git a/test/standalone/dependency_options/build.zig b/test/standalone/dependency_options/build.zig index 8726f61d30..27ce63834d 100644 --- a/test/standalone/dependency_options/build.zig +++ b/test/standalone/dependency_options/build.zig @@ -12,6 +12,29 @@ pub fn build(b: *std.Build) !void { if (!none_specified_mod.resolved_target.?.query.eql(b.graph.host.query)) return error.TestFailed; if (none_specified_mod.optimize.? != .Debug) return error.TestFailed; + // Passing null is the same as not specifying the option, + // so this should resolve to the same cached dependency instance. + const null_specified = b.dependency("other", .{ + // Null literals + .target = null, + .optimize = null, + .bool = null, + + // Optionals + .int = @as(?i64, null), + .float = @as(?f64, null), + + // Optionals of the wrong type + .string = @as(?usize, null), + .@"enum" = @as(?bool, null), + + // Non-defined option names + .this_option_does_not_exist = null, + .neither_does_this_one = @as(?[]const u8, null), + }); + + if (null_specified != none_specified) return error.TestFailed; + const all_specified = b.dependency("other", .{ .target = b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .windows, .abi = .gnu }), .optimize = @as(std.builtin.OptimizeMode, .ReleaseSafe), @@ -37,6 +60,27 @@ pub fn build(b: *std.Build) !void { if (all_specified_mod.resolved_target.?.result.abi != .gnu) return error.TestFailed; if (all_specified_mod.optimize.? != .ReleaseSafe) return error.TestFailed; + const all_specified_optional = b.dependency("other", .{ + .target = @as(?std.Build.ResolvedTarget, b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .windows, .abi = .gnu })), + .optimize = @as(?std.builtin.OptimizeMode, .ReleaseSafe), + .bool = @as(?bool, true), + .int = @as(?i64, 123), + .float = @as(?f64, 0.5), + .string = @as(?[]const u8, "abc"), + .string_list = @as(?[]const []const u8, &.{ "a", "b", "c" }), + .lazy_path = @as(?std.Build.LazyPath, .{ .cwd_relative = "abc.txt" }), + .lazy_path_list = @as(?[]const std.Build.LazyPath, &.{ + .{ .cwd_relative = "a.txt" }, + .{ .cwd_relative = "b.txt" }, + .{ .cwd_relative = "c.txt" }, + }), + .@"enum" = @as(?Enum, .alfa), + //.enum_list = @as(?[]const Enum, &.{ .alfa, .bravo, .charlie }), + //.build_id = @as(?std.zig.BuildId, .uuid), + }); + + if (all_specified_optional != all_specified) return error.TestFailed; + // Most supported option types are serialized to a string representation, // so alternative representations of the same option value should resolve // to the same cached dependency instance. @@ -59,5 +103,5 @@ pub fn build(b: *std.Build) !void { //.build_id = @as(std.zig.BuildId, .uuid), }); - if (all_specified != all_specified_alt) return error.TestFailed; + if (all_specified_alt != all_specified) return error.TestFailed; }