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(
408408 return child;
409409}
410410
411fn userInputOptionsFromArgs(allocator: Allocator, args: anytype) UserInputOptionsMap {
412 var user_input_options = UserInputOptionsMap.init(allocator);
411fn userInputOptionsFromArgs(arena: Allocator, args: anytype) UserInputOptionsMap {
412 var map = UserInputOptionsMap.init(arena);
413413 inline for (@typeInfo(@TypeOf(args)).@"struct".fields) |field| {
414 const v = @field(args, field.name);
415 const T = @TypeOf(v);
416 switch (T) {
417 Target.Query => {
418 user_input_options.put(field.name, .{
419 .name = field.name,
420 .value = .{ .scalar = v.zigTriple(allocator) catch @panic("OOM") },
421 .used = false,
422 }) catch @panic("OOM");
423 user_input_options.put("cpu", .{
424 .name = "cpu",
425 .value = .{ .scalar = v.serializeCpuAlloc(allocator) catch @panic("OOM") },
426 .used = false,
427 }) catch @panic("OOM");
428 },
429 ResolvedTarget => {
430 user_input_options.put(field.name, .{
431 .name = field.name,
432 .value = .{ .scalar = v.query.zigTriple(allocator) catch @panic("OOM") },
433 .used = false,
434 }) catch @panic("OOM");
435 user_input_options.put("cpu", .{
436 .name = "cpu",
437 .value = .{ .scalar = v.query.serializeCpuAlloc(allocator) catch @panic("OOM") },
438 .used = false,
439 }) catch @panic("OOM");
440 },
441 LazyPath => {
442 user_input_options.put(field.name, .{
414 if (field.type == @Type(.null)) continue;
415 addUserInputOptionFromArg(arena, &map, field, field.type, @field(args, field.name));
416 }
417 return map;
418}
419
420fn addUserInputOptionFromArg(
421 arena: Allocator,
422 map: *UserInputOptionsMap,
423 field: std.builtin.Type.StructField,
424 comptime T: type,
425 /// If null, the value won't be added, but `T` will still be type-checked.
426 maybe_value: ?T,
427) void {
428 switch (T) {
429 Target.Query => return if (maybe_value) |v| {
430 map.put(field.name, .{
431 .name = field.name,
432 .value = .{ .scalar = v.zigTriple(arena) catch @panic("OOM") },
433 .used = false,
434 }) catch @panic("OOM");
435 map.put("cpu", .{
436 .name = "cpu",
437 .value = .{ .scalar = v.serializeCpuAlloc(arena) catch @panic("OOM") },
438 .used = false,
439 }) catch @panic("OOM");
440 },
441 ResolvedTarget => return if (maybe_value) |v| {
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, .{
443488 .name = field.name,
444 .value = .{ .lazy_path = v.dupeInner(allocator) },
489 .value = .{ .scalar = if (v) "true" else "false" },
445490 .used = false,
446491 }) catch @panic("OOM");
447492 },
448 []const LazyPath => {
449 var list = ArrayList(LazyPath).initCapacity(allocator, v.len) catch @panic("OOM");
450 for (v) |lp| list.appendAssumeCapacity(lp.dupeInner(allocator));
451 user_input_options.put(field.name, .{
493 .@"enum", .enum_literal => return if (maybe_value) |v| {
494 map.put(field.name, .{
452495 .name = field.name,
453 .value = .{ .lazy_path_list = list },
496 .value = .{ .scalar = @tagName(v) },
454497 .used = false,
455498 }) catch @panic("OOM");
456499 },
457 []const u8 => {
458 user_input_options.put(field.name, .{
500 .comptime_int, .int => return if (maybe_value) |v| {
501 map.put(field.name, .{
459502 .name = field.name,
460 .value = .{ .scalar = v },
503 .value = .{ .scalar = std.fmt.allocPrint(arena, "{d}", .{v}) catch @panic("OOM") },
461504 .used = false,
462505 }) catch @panic("OOM");
463506 },
464 []const []const u8 => {
465 var list = ArrayList([]const u8).initCapacity(allocator, v.len) catch @panic("OOM");
466 list.appendSliceAssumeCapacity(v);
467
468 user_input_options.put(field.name, .{
507 .comptime_float, .float => return if (maybe_value) |v| {
508 map.put(field.name, .{
469509 .name = field.name,
470 .value = .{ .list = list },
510 .value = .{ .scalar = std.fmt.allocPrint(arena, "{e}", .{v}) catch @panic("OOM") },
471511 .used = false,
472512 }) catch @panic("OOM");
473513 },
474 else => switch (@typeInfo(T)) {
475 .bool => {
476 user_input_options.put(field.name, .{
477 .name = field.name,
478 .value = .{ .scalar = if (v) "true" else "false" },
479 .used = false,
480 }) catch @panic("OOM");
481 },
482 .@"enum", .enum_literal => {
483 user_input_options.put(field.name, .{
484 .name = field.name,
485 .value = .{ .scalar = @tagName(v) },
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");
514 .null => unreachable,
515 .optional => |info| switch (@typeInfo(info.child)) {
516 .optional => {},
517 else => {
518 addUserInputOptionFromArg(
519 arena,
520 map,
521 field,
522 info.child,
523 maybe_value orelse null,
524 );
525 return;
495526 },
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)),
504527 },
505 }
528 else => {},
529 },
506530 }
507
508 return user_input_options;
531 @compileError("option '" ++ field.name ++ "' has unsupported type: " ++ @typeName(field.type));
509532}
510533
511534const OrderedUserValue = union(enum) {
test/standalone/dependency_options/build.zig+45-1
......@@ -12,6 +12,29 @@ pub fn build(b: *std.Build) !void {
1212 if (!none_specified_mod.resolved_target.?.query.eql(b.graph.host.query)) return error.TestFailed;
1313 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
1538 const all_specified = b.dependency("other", .{
1639 .target = b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .windows, .abi = .gnu }),
1740 .optimize = @as(std.builtin.OptimizeMode, .ReleaseSafe),
......@@ -37,6 +60,27 @@ pub fn build(b: *std.Build) !void {
3760 if (all_specified_mod.resolved_target.?.result.abi != .gnu) return error.TestFailed;
3861 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
4084 // Most supported option types are serialized to a string representation,
4185 // so alternative representations of the same option value should resolve
4286 // to the same cached dependency instance.
......@@ -59,5 +103,5 @@ pub fn build(b: *std.Build) !void {
59103 //.build_id = @as(std.zig.BuildId, .uuid),
60104 });
61105
62 if (all_specified != all_specified_alt) return error.TestFailed;
106 if (all_specified_alt != all_specified) return error.TestFailed;
63107}