pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw: []const u8) ?T {
const graph = b.graph;
const arena = graph.arena;
const name = graph.dupeString(name_raw);
const description = graph.dupeString(description_raw);
const type_id = comptime typeToEnum(T);
const available_option: AvailableOption = .{
.type_id = type_id,
.description = description,
.enum_options = if (type_id == .@"enum" or type_id == .enum_list) blk: {
const E = if (type_id == .enum_list) @typeInfo(T).pointer.child else T;
break :blk @typeInfo(E).@"enum".field_names;
} else null,
};
if ((b.available_options_map.fetchPut(arena, name, available_option) catch @panic("OOM")) != null) {
panic("option {q} declared twice", .{name});
}
const user_provided = b.user_input_options.get(name) orelse return null;
switch (type_id) {
.bool => switch (user_provided) {
.flag => return true,
.scalar => |s| {
if (mem.eql(u8, s, "true")) {
return true;
} else if (mem.eql(u8, s, "false")) {
return false;
} else {
log.err("expected -D{s} to be a boolean; received: {s}", .{ name, s });
b.markInvalidUserInput();
return null;
}
},
.list, .map, .lazy_path, .lazy_path_list => {
log.err("expected -D{s} to be a boolean; received: {t}", .{ name, user_provided });
b.markInvalidUserInput();
return null;
},
},
.int => switch (user_provided) {
.flag, .list, .map, .lazy_path, .lazy_path_list => {
log.err("expected -D{s} to be an integer; received: {t}", .{ name, user_provided });
b.markInvalidUserInput();
return null;
},
.scalar => |s| {
const n = std.fmt.parseInt(T, s, 10) catch |err| switch (err) {
error.Overflow => {
log.err("-D{s} value {s} cannot fit into type {s}", .{ name, s, @typeName(T) });
b.markInvalidUserInput();
return null;
},
else => {
log.err("expected -D{s} to be an integer of type {s}", .{ name, @typeName(T) });
b.markInvalidUserInput();
return null;
},
};
return n;
},
},
.float => switch (user_provided) {
.flag, .map, .list, .lazy_path, .lazy_path_list => {
log.err("expected -D{s} to be a float; received: {t}", .{ name, user_provided });
b.markInvalidUserInput();
return null;
},
.scalar => |s| {
const n = std.fmt.parseFloat(T, s) catch {
log.err("expected -D{s} to be a float of type {s}", .{ name, @typeName(T) });
b.markInvalidUserInput();
return null;
};
return n;
},
},
.@"enum" => switch (user_provided) {
.flag, .map, .list, .lazy_path, .lazy_path_list => {
log.err("expected -D{s} to be an enum; received: {t}.", .{ name, user_provided });
b.markInvalidUserInput();
return null;
},
.scalar => |s| {
if (T == std.lang.Optimize) {
if (std.lang.Optimize.fromString(s)) |tag| {
return tag;
}
} else if (std.meta.stringToEnum(T, s)) |tag| {
return tag;
}
log.err("expected -D{s} to be of type {q}", .{ name, @typeName(T) });
b.markInvalidUserInput();
return null;
},
},
.string => switch (user_provided) {
.flag, .list, .map, .lazy_path, .lazy_path_list => {
log.err("expected -D{s} to be a string; received: {t}", .{ name, user_provided });
b.markInvalidUserInput();
return null;
},
.scalar => |s| return s,
},
.build_id => switch (user_provided) {
.flag, .map, .list, .lazy_path, .lazy_path_list => {
log.err("expected -D{s} to be an enum; received: {t}.", .{ name, user_provided });
b.markInvalidUserInput();
return null;
},
.scalar => |s| {
if (std.zig.BuildId.parse(s)) |build_id| {
return build_id;
} else |err| {
log.err("failed to parse option -D{s}: {t}", .{ name, err });
b.markInvalidUserInput();
return null;
}
},
},
.list => switch (user_provided) {
.flag, .map, .lazy_path, .lazy_path_list => {
log.err("expected -D{s} to be a list; received: {t}", .{ name, user_provided });
b.markInvalidUserInput();
return null;
},
.scalar => |s| {
return arena.dupe([]const u8, &[_][]const u8{s}) catch @panic("OOM");
},
.list => |lst| return lst.items,
},
.enum_list => switch (user_provided) {
.flag, .map, .lazy_path, .lazy_path_list => {
log.err("expected -D{s} to be a list; received: {t}", .{ name, user_provided });
b.markInvalidUserInput();
return null;
},
.scalar => |s| {
const Child = @typeInfo(T).pointer.child;
if (Child == std.lang.Optimize) {
if (std.lang.Optimize.fromString(s)) |tag| {
return arena.dupe(Child, &.{tag}) catch @panic("OOM");
}
} else {
if (std.meta.stringToEnum(Child, s)) |tag| {
return arena.dupe(Child, &.{tag}) catch @panic("OOM");
}
}
log.err("expected -D{s} to be of type {q}", .{ name, @typeName(Child) });
b.markInvalidUserInput();
return null;
},
.list => |lst| {
const Child = @typeInfo(T).pointer.child;
const new_list = graph.alloc(Child, lst.items.len);
for (new_list, lst.items) |*new_item, str| {
if (Child == std.lang.Optimize) {
if (std.lang.Optimize.fromString(str)) |tag| {
new_item.* = tag;
continue;
}
}
if (std.meta.stringToEnum(Child, str)) |tag| {
new_item.* = tag;
continue;
}
log.err("expected -D{s} to be of type {q}", .{ name, @typeName(Child) });
b.markInvalidUserInput();
return null;
}
return new_list;
},
},
.lazy_path => switch (user_provided) {
.scalar => |s| return .{ .cwd_relative = s },
.lazy_path => |lp| return lp,
.flag, .map, .list, .lazy_path_list => {
log.err("expected -D{s} to be a path; received: {t}", .{ name, user_provided });
b.markInvalidUserInput();
return null;
},
},
.lazy_path_list => switch (user_provided) {
.scalar => |s| return arena.dupe(LazyPath, &[_]LazyPath{.{ .cwd_relative = s }}) catch @panic("OOM"),
.lazy_path => |lp| return arena.dupe(LazyPath, &[_]LazyPath{lp}) catch @panic("OOM"),
.list => |lst| {
const new_list = graph.alloc(LazyPath, lst.items.len);
for (new_list, lst.items) |*new_item, str| {
new_item.* = .{ .cwd_relative = str };
}
return new_list;
},
.lazy_path_list => |lp_list| return lp_list.items,
.flag, .map => {
log.err("expected -D{s} to be a path; received: {t}", .{ name, user_provided });
b.markInvalidUserInput();
return null;
},
},
}
}
Creates a configuration option to be passed to the build.zig script. When a user directly runs
zig build, they can set these options with-Darguments. When a project depends on a Zig package as a dependency, it programmatically sets these options when calling the dependency's build.zig script as a function.nullis returned when an option is left to default.