authorgravatar for carl@astholm.seCarl Åstholm <carl@astholm.se> 2025-03-23 22:45:38+01:00
committergravatar for carl@astholm.seCarl Åstholm <carl@astholm.se> 2025-07-20 18:28:36+02:00
log5380e81924dd98e9717eaf09ae05e935952a78ff
tree8891416e384826042d42d6179b8bc85a5407a106
parent00bc72b5ff01c7f8ceb4b58e82614e22a147ccc8

Support passing null to `b.dependency()`

Both null literals and optionals are supported.

2 files changed, 146 insertions(+), 79 deletions(-)

lib/std/Build.zig+101-78
...@@ -408,104 +408,127 @@ fn createChildOnly(...@@ -408,104 +408,127 @@ fn createChildOnly(
408 return child;408 return child;
409}409}
410410
411fn userInputOptionsFromArgs(allocator: Allocator, args: anytype) UserInputOptionsMap {411fn userInputOptionsFromArgs(arena: Allocator, args: anytype) UserInputOptionsMap {
412 var user_input_options = UserInputOptionsMap.init(allocator);412 var map = UserInputOptionsMap.init(arena);
413 inline for (@typeInfo(@TypeOf(args)).@"struct".fields) |field| {413 inline for (@typeInfo(@TypeOf(args)).@"struct".fields) |field| {
414 const v = @field(args, field.name);414 if (field.type == @Type(.null)) continue;
415 const T = @TypeOf(v);415 addUserInputOptionFromArg(arena, &map, field, field.type, @field(args, field.name));
416 switch (T) {416 }
417 Target.Query => {417 return map;
418 user_input_options.put(field.name, .{418}
419 .name = field.name,419
420 .value = .{ .scalar = v.zigTriple(allocator) catch @panic("OOM") },420fn addUserInputOptionFromArg(
421 .used = false,421 arena: Allocator,
422 }) catch @panic("OOM");422 map: *UserInputOptionsMap,
423 user_input_options.put("cpu", .{423 field: std.builtin.Type.StructField,
424 .name = "cpu",424 comptime T: type,
425 .value = .{ .scalar = v.serializeCpuAlloc(allocator) catch @panic("OOM") },425 /// If null, the value won't be added, but `T` will still be type-checked.
426 .used = false,426 maybe_value: ?T,
427 }) catch @panic("OOM");427) void {
428 },428 switch (T) {
429 ResolvedTarget => {429 Target.Query => return if (maybe_value) |v| {
430 user_input_options.put(field.name, .{430 map.put(field.name, .{
431 .name = field.name,431 .name = field.name,
432 .value = .{ .scalar = v.query.zigTriple(allocator) catch @panic("OOM") },432 .value = .{ .scalar = v.zigTriple(arena) catch @panic("OOM") },
433 .used = false,433 .used = false,
434 }) catch @panic("OOM");434 }) catch @panic("OOM");
435 user_input_options.put("cpu", .{435 map.put("cpu", .{
436 .name = "cpu",436 .name = "cpu",
437 .value = .{ .scalar = v.query.serializeCpuAlloc(allocator) catch @panic("OOM") },437 .value = .{ .scalar = v.serializeCpuAlloc(arena) catch @panic("OOM") },
438 .used = false,438 .used = false,
439 }) catch @panic("OOM");439 }) catch @panic("OOM");
440 },440 },
441 LazyPath => {441 ResolvedTarget => return if (maybe_value) |v| {
442 user_input_options.put(field.name, .{442 map.put(field.name, .{
443 .name = field.name,
444 .value = .{ .scalar = v.query.zigTriple(arena) catch @panic("OOM") },
445 .used = false,
446 }) catch @panic("OOM");
447 map.put("cpu", .{
448 .name = "cpu",
449 .value = .{ .scalar = v.query.serializeCpuAlloc(arena) catch @panic("OOM") },
450 .used = false,
451 }) catch @panic("OOM");
452 },
453 LazyPath => return if (maybe_value) |v| {
454 map.put(field.name, .{
455 .name = field.name,
456 .value = .{ .lazy_path = v.dupeInner(arena) },
457 .used = false,
458 }) catch @panic("OOM");
459 },
460 []const LazyPath => return if (maybe_value) |v| {
461 var list = ArrayList(LazyPath).initCapacity(arena, v.len) catch @panic("OOM");
462 for (v) |lp| list.appendAssumeCapacity(lp.dupeInner(arena));
463 map.put(field.name, .{
464 .name = field.name,
465 .value = .{ .lazy_path_list = list },
466 .used = false,
467 }) catch @panic("OOM");
468 },
469 []const u8 => return if (maybe_value) |v| {
470 map.put(field.name, .{
471 .name = field.name,
472 .value = .{ .scalar = v },
473 .used = false,
474 }) catch @panic("OOM");
475 },
476 []const []const u8 => return if (maybe_value) |v| {
477 var list = ArrayList([]const u8).initCapacity(arena, v.len) catch @panic("OOM");
478 list.appendSliceAssumeCapacity(v);
479 map.put(field.name, .{
480 .name = field.name,
481 .value = .{ .list = list },
482 .used = false,
483 }) catch @panic("OOM");
484 },
485 else => switch (@typeInfo(T)) {
486 .bool => return if (maybe_value) |v| {
487 map.put(field.name, .{
443 .name = field.name,488 .name = field.name,
444 .value = .{ .lazy_path = v.dupeInner(allocator) },489 .value = .{ .scalar = if (v) "true" else "false" },
445 .used = false,490 .used = false,
446 }) catch @panic("OOM");491 }) catch @panic("OOM");
447 },492 },
448 []const LazyPath => {493 .@"enum", .enum_literal => return if (maybe_value) |v| {
449 var list = ArrayList(LazyPath).initCapacity(allocator, v.len) catch @panic("OOM");494 map.put(field.name, .{
450 for (v) |lp| list.appendAssumeCapacity(lp.dupeInner(allocator));
451 user_input_options.put(field.name, .{
452 .name = field.name,495 .name = field.name,
453 .value = .{ .lazy_path_list = list },496 .value = .{ .scalar = @tagName(v) },
454 .used = false,497 .used = false,
455 }) catch @panic("OOM");498 }) catch @panic("OOM");
456 },499 },
457 []const u8 => {500 .comptime_int, .int => return if (maybe_value) |v| {
458 user_input_options.put(field.name, .{501 map.put(field.name, .{
459 .name = field.name,502 .name = field.name,
460 .value = .{ .scalar = v },503 .value = .{ .scalar = std.fmt.allocPrint(arena, "{d}", .{v}) catch @panic("OOM") },
461 .used = false,504 .used = false,
462 }) catch @panic("OOM");505 }) catch @panic("OOM");
463 },506 },
464 []const []const u8 => {507 .comptime_float, .float => return if (maybe_value) |v| {
465 var list = ArrayList([]const u8).initCapacity(allocator, v.len) catch @panic("OOM");508 map.put(field.name, .{
466 list.appendSliceAssumeCapacity(v);
467
468 user_input_options.put(field.name, .{
469 .name = field.name,509 .name = field.name,
470 .value = .{ .list = list },510 .value = .{ .scalar = std.fmt.allocPrint(arena, "{e}", .{v}) catch @panic("OOM") },
471 .used = false,511 .used = false,
472 }) catch @panic("OOM");512 }) catch @panic("OOM");
473 },513 },
474 else => switch (@typeInfo(T)) {514 .null => unreachable,
475 .bool => {515 .optional => |info| switch (@typeInfo(info.child)) {
476 user_input_options.put(field.name, .{516 .optional => {},
477 .name = field.name,517 else => {
478 .value = .{ .scalar = if (v) "true" else "false" },518 addUserInputOptionFromArg(
479 .used = false,519 arena,
480 }) catch @panic("OOM");520 map,
481 },521 field,
482 .@"enum", .enum_literal => {522 info.child,
483 user_input_options.put(field.name, .{523 maybe_value orelse null,
484 .name = field.name,524 );
485 .value = .{ .scalar = @tagName(v) },525 return;
486 .used = false,
487 }) catch @panic("OOM");
488 },
489 .comptime_int, .int => {
490 user_input_options.put(field.name, .{
491 .name = field.name,
492 .value = .{ .scalar = std.fmt.allocPrint(allocator, "{d}", .{v}) catch @panic("OOM") },
493 .used = false,
494 }) catch @panic("OOM");
495 },526 },
496 .comptime_float, .float => {
497 user_input_options.put(field.name, .{
498 .name = field.name,
499 .value = .{ .scalar = std.fmt.allocPrint(allocator, "{e}", .{v}) catch @panic("OOM") },
500 .used = false,
501 }) catch @panic("OOM");
502 },
503 else => @compileError("option '" ++ field.name ++ "' has unsupported type: " ++ @typeName(T)),
504 },527 },
505 }528 else => {},
529 },
506 }530 }
507531 @compileError("option '" ++ field.name ++ "' has unsupported type: " ++ @typeName(field.type));
508 return user_input_options;
509}532}
510533
511const OrderedUserValue = union(enum) {534const OrderedUserValue = union(enum) {
test/standalone/dependency_options/build.zig+45-1
...@@ -12,6 +12,29 @@ pub fn build(b: *std.Build) !void {...@@ -12,6 +12,29 @@ pub fn build(b: *std.Build) !void {
12 if (!none_specified_mod.resolved_target.?.query.eql(b.graph.host.query)) return error.TestFailed;12 if (!none_specified_mod.resolved_target.?.query.eql(b.graph.host.query)) return error.TestFailed;
13 if (none_specified_mod.optimize.? != .Debug) return error.TestFailed;13 if (none_specified_mod.optimize.? != .Debug) return error.TestFailed;
1414
15 // Passing null is the same as not specifying the option,
16 // so this should resolve to the same cached dependency instance.
17 const null_specified = b.dependency("other", .{
18 // Null literals
19 .target = null,
20 .optimize = null,
21 .bool = null,
22
23 // Optionals
24 .int = @as(?i64, null),
25 .float = @as(?f64, null),
26
27 // Optionals of the wrong type
28 .string = @as(?usize, null),
29 .@"enum" = @as(?bool, null),
30
31 // Non-defined option names
32 .this_option_does_not_exist = null,
33 .neither_does_this_one = @as(?[]const u8, null),
34 });
35
36 if (null_specified != none_specified) return error.TestFailed;
37
15 const all_specified = b.dependency("other", .{38 const all_specified = b.dependency("other", .{
16 .target = b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .windows, .abi = .gnu }),39 .target = b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .windows, .abi = .gnu }),
17 .optimize = @as(std.builtin.OptimizeMode, .ReleaseSafe),40 .optimize = @as(std.builtin.OptimizeMode, .ReleaseSafe),
...@@ -37,6 +60,27 @@ pub fn build(b: *std.Build) !void {...@@ -37,6 +60,27 @@ pub fn build(b: *std.Build) !void {
37 if (all_specified_mod.resolved_target.?.result.abi != .gnu) return error.TestFailed;60 if (all_specified_mod.resolved_target.?.result.abi != .gnu) return error.TestFailed;
38 if (all_specified_mod.optimize.? != .ReleaseSafe) return error.TestFailed;61 if (all_specified_mod.optimize.? != .ReleaseSafe) return error.TestFailed;
3962
63 const all_specified_optional = b.dependency("other", .{
64 .target = @as(?std.Build.ResolvedTarget, b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .windows, .abi = .gnu })),
65 .optimize = @as(?std.builtin.OptimizeMode, .ReleaseSafe),
66 .bool = @as(?bool, true),
67 .int = @as(?i64, 123),
68 .float = @as(?f64, 0.5),
69 .string = @as(?[]const u8, "abc"),
70 .string_list = @as(?[]const []const u8, &.{ "a", "b", "c" }),
71 .lazy_path = @as(?std.Build.LazyPath, .{ .cwd_relative = "abc.txt" }),
72 .lazy_path_list = @as(?[]const std.Build.LazyPath, &.{
73 .{ .cwd_relative = "a.txt" },
74 .{ .cwd_relative = "b.txt" },
75 .{ .cwd_relative = "c.txt" },
76 }),
77 .@"enum" = @as(?Enum, .alfa),
78 //.enum_list = @as(?[]const Enum, &.{ .alfa, .bravo, .charlie }),
79 //.build_id = @as(?std.zig.BuildId, .uuid),
80 });
81
82 if (all_specified_optional != all_specified) return error.TestFailed;
83
40 // Most supported option types are serialized to a string representation,84 // Most supported option types are serialized to a string representation,
41 // so alternative representations of the same option value should resolve85 // so alternative representations of the same option value should resolve
42 // to the same cached dependency instance.86 // to the same cached dependency instance.
...@@ -59,5 +103,5 @@ pub fn build(b: *std.Build) !void {...@@ -59,5 +103,5 @@ pub fn build(b: *std.Build) !void {
59 //.build_id = @as(std.zig.BuildId, .uuid),103 //.build_id = @as(std.zig.BuildId, .uuid),
60 });104 });
61105
62 if (all_specified != all_specified_alt) return error.TestFailed;106 if (all_specified_alt != all_specified) return error.TestFailed;
63}107}