From 12066042621ca7920d67d339b54c0a2220d40696 Mon Sep 17 00:00:00 2001 From: Reokodoku <98696261+Reokodoku@users.noreply.github.com> Date: Mon, 18 Mar 2024 03:46:54 +0100 Subject: [PATCH] Implementing structs and better enums in build options --- lib/std/Build/Step/Options.zig | 411 ++++++++++++++++++++++++++------- 1 file changed, 330 insertions(+), 81 deletions(-) diff --git a/lib/std/Build/Step/Options.zig b/lib/std/Build/Step/Options.zig index 8dfe735703..436c7fb8e7 100644 --- a/lib/std/Build/Step/Options.zig +++ b/lib/std/Build/Step/Options.zig @@ -41,123 +41,180 @@ pub fn addOption(self: *Options, comptime T: type, name: []const u8, value: T) v fn addOptionFallible(self: *Options, comptime T: type, name: []const u8, value: T) !void { const out = self.contents.writer(); + try printType(self, out, T, value, 0, name); +} + +fn printType(self: *Options, out: anytype, comptime T: type, value: T, indent: u8, name: ?[]const u8) !void { switch (T) { []const []const u8 => { - try out.print("pub const {}: []const []const u8 = &[_][]const u8{{\n", .{std.zig.fmtId(name)}); + if (name) |payload| { + try out.print("pub const {}: []const []const u8 = ", .{std.zig.fmtId(payload)}); + } + + try out.writeAll("&[_][]const u8{\n"); + for (value) |slice| { + try out.writeByteNTimes(' ', indent); try out.print(" \"{}\",\n", .{std.zig.fmtEscapes(slice)}); } - try out.writeAll("};\n"); - return; - }, - [:0]const u8 => { - try out.print("pub const {}: [:0]const u8 = \"{}\";\n", .{ std.zig.fmtId(name), std.zig.fmtEscapes(value) }); + + if (name != null) { + try out.writeAll("};\n"); + } else { + try out.writeAll("},\n"); + } + return; }, []const u8 => { - try out.print("pub const {}: []const u8 = \"{}\";\n", .{ std.zig.fmtId(name), std.zig.fmtEscapes(value) }); - return; - }, - ?[:0]const u8 => { - try out.print("pub const {}: ?[:0]const u8 = ", .{std.zig.fmtId(name)}); - if (value) |payload| { - try out.print("\"{}\";\n", .{std.zig.fmtEscapes(payload)}); + if (name) |some| { + try out.print("pub const {}: []const u8 = \"{}\";", .{ std.zig.fmtId(some), std.zig.fmtEscapes(value) }); } else { - try out.writeAll("null;\n"); + try out.print("\"{}\",", .{std.zig.fmtEscapes(value)}); + } + return out.writeAll("\n"); + }, + [:0]const u8 => { + if (name) |some| { + try out.print("pub const {}: [:0]const u8 = \"{}\";", .{ std.zig.fmtId(some), std.zig.fmtEscapes(value) }); + } else { + try out.print("\"{}\",", .{std.zig.fmtEscapes(value)}); + } + return out.writeAll("\n"); + }, + ?[]const u8 => { + if (name) |some| { + try out.print("pub const {}: ?[]const u8 = ", .{std.zig.fmtId(some)}); + } + + if (value) |payload| { + try out.print("\"{}\"", .{std.zig.fmtEscapes(payload)}); + } else { + try out.writeAll("null"); + } + + if (name != null) { + try out.writeAll(";\n"); + } else { + try out.writeAll(",\n"); } return; }, - ?[]const u8 => { - try out.print("pub const {}: ?[]const u8 = ", .{std.zig.fmtId(name)}); + ?[:0]const u8 => { + if (name) |some| { + try out.print("pub const {}: ?[:0]const u8 = ", .{std.zig.fmtId(some)}); + } + if (value) |payload| { - try out.print("\"{}\";\n", .{std.zig.fmtEscapes(payload)}); + try out.print("\"{}\"", .{std.zig.fmtEscapes(payload)}); } else { - try out.writeAll("null;\n"); + try out.writeAll("null"); + } + + if (name != null) { + try out.writeAll(";\n"); + } else { + try out.writeAll(",\n"); } return; }, std.SemanticVersion => { - try out.print( - \\pub const {}: @import("std").SemanticVersion = .{{ - \\ .major = {d}, - \\ .minor = {d}, - \\ .patch = {d}, - \\ - , .{ - std.zig.fmtId(name), + if (name) |some| { + try out.print("pub const {}: @import(\"std\").SemanticVersion = ", .{std.zig.fmtId(some)}); + } + + try out.writeAll(".{\n"); + try out.writeByteNTimes(' ', indent); + try out.print(" .major = {d},\n", .{value.major}); + try out.writeByteNTimes(' ', indent); + try out.print(" .minor = {d},\n", .{value.minor}); + try out.writeByteNTimes(' ', indent); + try out.print(" .patch = {d},\n", .{value.patch}); - value.major, - value.minor, - value.patch, - }); if (value.pre) |some| { + try out.writeByteNTimes(' ', indent); try out.print(" .pre = \"{}\",\n", .{std.zig.fmtEscapes(some)}); } if (value.build) |some| { + try out.writeByteNTimes(' ', indent); try out.print(" .build = \"{}\",\n", .{std.zig.fmtEscapes(some)}); } - try out.writeAll("};\n"); - return; - }, - else => {}, - } - switch (@typeInfo(T)) { - .Enum => |enum_info| { - const gop = try self.encountered_types.getOrPut(@typeName(T)); - if (!gop.found_existing) { - try out.print("pub const {} = enum {{\n", .{std.zig.fmtId(@typeName(T))}); - inline for (enum_info.fields) |field| { - try out.print(" {},\n", .{std.zig.fmtId(field.name)}); - } - try out.writeAll("};\n"); - } - try out.print("pub const {}: {s} = .{s};\n", .{ - std.zig.fmtId(name), - std.zig.fmtId(@typeName(T)), - std.zig.fmtId(@tagName(value)), - }); - return; - }, - else => {}, - } - try out.print("pub const {}: {s} = ", .{ std.zig.fmtId(name), @typeName(T) }); - try printLiteral(out, value, 0); - try out.writeAll(";\n"); -} -// TODO: non-recursive? -fn printLiteral(out: anytype, val: anytype, indent: u8) !void { - const T = @TypeOf(val); + if (name != null) { + try out.writeAll("};\n"); + } else { + try out.writeAll("},\n"); + } + return; + }, + else => {}, + } + switch (@typeInfo(T)) { .Array => { + if (name) |some| { + try out.print("pub const {}: {s} = ", .{ std.zig.fmtId(some), @typeName(T) }); + } + try out.print("{s} {{\n", .{@typeName(T)}); - for (val) |item| { + for (value) |item| { try out.writeByteNTimes(' ', indent + 4); - try printLiteral(out, item, indent + 4); - try out.writeAll(",\n"); + try printType(self, out, @TypeOf(item), item, indent + 4, null); } try out.writeByteNTimes(' ', indent); try out.writeAll("}"); + + if (name != null) { + try out.writeAll(";\n"); + } else { + try out.writeAll(",\n"); + } + return; }, .Pointer => |p| { if (p.size != .Slice) { @compileError("Non-slice pointers are not yet supported in build options"); } + + if (name) |some| { + try out.print("pub const {}: {s} = ", .{ std.zig.fmtId(some), @typeName(T) }); + } + try out.print("&[_]{s} {{\n", .{@typeName(p.child)}); - for (val) |item| { + for (value) |item| { try out.writeByteNTimes(' ', indent + 4); - try printLiteral(out, item, indent + 4); - try out.writeAll(",\n"); + try printType(self, out, @TypeOf(item), item, indent + 4, null); } try out.writeByteNTimes(' ', indent); try out.writeAll("}"); + + if (name != null) { + try out.writeAll(";\n"); + } else { + try out.writeAll(",\n"); + } + return; }, .Optional => { - if (val) |inner| { - return printLiteral(out, inner, indent); - } else { - return out.writeAll("null"); + if (name) |some| { + try out.print("pub const {}: {s} = ", .{ std.zig.fmtId(some), @typeName(T) }); } + + if (value) |inner| { + try printType(self, out, @TypeOf(inner), inner, indent + 4, null); + // Pop the '\n' and ',' chars + _ = self.contents.pop(); + _ = self.contents.pop(); + } else { + try out.writeAll("null"); + } + + if (name != null) { + try out.writeAll(";\n"); + } else { + try out.writeAll(",\n"); + } + return; }, .Void, .Bool, @@ -165,11 +222,160 @@ fn printLiteral(out: anytype, val: anytype, indent: u8) !void { .ComptimeInt, .Float, .Null, - => try out.print("{any}", .{val}), + => { + if (name) |some| { + try out.print("pub const {}: {s} = {any};\n", .{ std.zig.fmtId(some), @typeName(T), value }); + } else { + try out.print("{any},\n", .{value}); + } + return; + }, + .Enum => |info| { + try printEnum(self, out, T, info, indent); + + if (name) |some| { + try out.print("pub const {}: {s} = .{s};\n", .{ + std.zig.fmtId(some), + std.zig.fmtId(@typeName(T)), + std.zig.fmtId(@tagName(value)), + }); + } + return; + }, + .Struct => |info| { + try printStruct(self, out, T, info, indent); + + if (name) |some| { + try out.print("pub const {}: {s} = ", .{ + std.zig.fmtId(some), + std.zig.fmtId(@typeName(T)), + }); + try printStructValue(self, out, info, value, indent); + } + return; + }, else => @compileError(std.fmt.comptimePrint("`{s}` are not yet supported as build options", .{@tagName(@typeInfo(T))})), } } +fn printUserDefinedType(self: *Options, out: anytype, comptime T: type, indent: u8) !void { + switch (@typeInfo(T)) { + .Enum => |info| { + return try printEnum(self, out, T, info, indent); + }, + .Struct => |info| { + return try printStruct(self, out, T, info, indent); + }, + else => {}, + } +} + +fn printEnum(self: *Options, out: anytype, comptime T: type, comptime val: std.builtin.Type.Enum, indent: u8) !void { + const gop = try self.encountered_types.getOrPut(@typeName(T)); + if (gop.found_existing) return; + + try out.writeByteNTimes(' ', indent); + try out.print("pub const {} = enum ({s}) {{\n", .{ std.zig.fmtId(@typeName(T)), @typeName(val.tag_type) }); + + inline for (val.fields) |field| { + try out.writeByteNTimes(' ', indent); + try out.print(" {} = {d},\n", .{ std.zig.fmtId(field.name), field.value }); + } + + if (!val.is_exhaustive) { + try out.writeByteNTimes(' ', indent); + try out.writeAll(" _,\n"); + } + + try out.writeByteNTimes(' ', indent); + try out.writeAll("};\n"); +} + +fn printStruct(self: *Options, out: anytype, comptime T: type, comptime val: std.builtin.Type.Struct, indent: u8) !void { + const gop = try self.encountered_types.getOrPut(@typeName(T)); + if (gop.found_existing) return; + + try out.writeByteNTimes(' ', indent); + try out.print("pub const {} = ", .{std.zig.fmtId(@typeName(T))}); + + switch (val.layout) { + .@"extern" => try out.writeAll("extern struct"), + .@"packed" => try out.writeAll("packed struct"), + else => try out.writeAll("struct"), + } + + try out.writeAll(" {\n"); + + inline for (val.fields) |field| { + try out.writeByteNTimes(' ', indent); + + const type_name = @typeName(field.type); + + // If the type name doesn't contains a '.' the type is from zig builtins. + if (std.mem.containsAtLeast(u8, type_name, 1, ".")) { + try out.print(" {}: {}", .{ std.zig.fmtId(field.name), std.zig.fmtId(type_name) }); + } else { + try out.print(" {}: {s}", .{ std.zig.fmtId(field.name), type_name }); + } + + if (field.default_value != null) { + const default_value = @as(*field.type, @ptrCast(@alignCast(@constCast(field.default_value.?)))).*; + + try out.writeAll(" = "); + switch (@typeInfo(@TypeOf(default_value))) { + .Enum => try out.print(".{s},\n", .{@tagName(default_value)}), + .Struct => |info| { + try printStructValue(self, out, info, default_value, indent + 4); + }, + else => try printType(self, out, @TypeOf(default_value), default_value, indent, null), + } + } else { + try out.writeAll(",\n"); + } + } + + // TODO: write declarations + + try out.writeByteNTimes(' ', indent); + try out.writeAll("};\n"); + + inline for (val.fields) |field| { + try printUserDefinedType(self, out, field.type, 0); + } +} + +fn printStructValue(self: *Options, out: anytype, comptime struct_val: std.builtin.Type.Struct, val: anytype, indent: u8) !void { + try out.writeAll(".{\n"); + + if (struct_val.is_tuple) { + inline for (struct_val.fields) |field| { + try out.writeByteNTimes(' ', indent); + try printType(self, out, @TypeOf(@field(val, field.name)), @field(val, field.name), indent, null); + } + } else { + inline for (struct_val.fields) |field| { + try out.writeByteNTimes(' ', indent); + try out.print(" .{} = ", .{std.zig.fmtId(field.name)}); + + const field_name = @field(val, field.name); + switch (@typeInfo(@TypeOf(field_name))) { + .Enum => try out.print(".{s},\n", .{@tagName(field_name)}), + .Struct => |struct_info| { + try printStructValue(self, out, struct_info, field_name, indent + 4); + }, + else => try printType(self, out, @TypeOf(field_name), field_name, indent, null), + } + } + } + + if (indent == 0) { + try out.writeAll("};\n"); + } else { + try out.writeByteNTimes(' ', indent); + try out.writeAll("},\n"); + } +} + /// The value is the path in the cache dir. /// Adds a dependency automatically. pub fn addOptionPath( @@ -341,6 +547,16 @@ test Options { }; const nested_slice: []const []const u16 = &[_][]const u16{ &nested_array[0], &nested_array[1] }; + const NormalStruct = struct { + hello: ?[]const u8, + world: bool = true, + }; + + const NestedStruct = struct { + normal_struct: NormalStruct, + normal_enum: NormalEnum = .foo, + }; + options.addOption(usize, "option1", 1); options.addOption(?usize, "option2", null); options.addOption(?usize, "option3", 3); @@ -351,8 +567,18 @@ test Options { options.addOption([]const []const u16, "nested_slice", nested_slice); options.addOption(KeywordEnum, "keyword_enum", .@"0.8.1"); options.addOption(std.SemanticVersion, "semantic_version", try std.SemanticVersion.parse("0.1.2-foo+bar")); - options.addOption(NormalEnum, "normal1", NormalEnum.foo); - options.addOption(NormalEnum, "normal2", NormalEnum.bar); + options.addOption(NormalEnum, "normal1_enum", NormalEnum.foo); + options.addOption(NormalEnum, "normal2_enum", NormalEnum.bar); + options.addOption(NormalStruct, "normal1_struct", NormalStruct{ + .hello = "foo", + }); + options.addOption(NormalStruct, "normal2_struct", NormalStruct{ + .hello = null, + .world = false, + }); + options.addOption(NestedStruct, "nested_struct", NestedStruct{ + .normal_struct = .{ .hello = "bar" }, + }); try std.testing.expectEqualStrings( \\pub const option1: usize = 1; @@ -381,8 +607,8 @@ test Options { \\ 200, \\ }, \\}; - \\pub const @"Build.Step.Options.decltest.Options.KeywordEnum" = enum { - \\ @"0.8.1", + \\pub const @"Build.Step.Options.decltest.Options.KeywordEnum" = enum (u0) { + \\ @"0.8.1" = 0, \\}; \\pub const keyword_enum: @"Build.Step.Options.decltest.Options.KeywordEnum" = .@"0.8.1"; \\pub const semantic_version: @import("std").SemanticVersion = .{ @@ -392,12 +618,35 @@ test Options { \\ .pre = "foo", \\ .build = "bar", \\}; - \\pub const @"Build.Step.Options.decltest.Options.NormalEnum" = enum { - \\ foo, - \\ bar, + \\pub const @"Build.Step.Options.decltest.Options.NormalEnum" = enum (u1) { + \\ foo = 0, + \\ bar = 1, + \\}; + \\pub const normal1_enum: @"Build.Step.Options.decltest.Options.NormalEnum" = .foo; + \\pub const normal2_enum: @"Build.Step.Options.decltest.Options.NormalEnum" = .bar; + \\pub const @"Build.Step.Options.decltest.Options.NormalStruct" = struct { + \\ hello: ?[]const u8, + \\ world: bool = true, + \\}; + \\pub const normal1_struct: @"Build.Step.Options.decltest.Options.NormalStruct" = .{ + \\ .hello = "foo", + \\ .world = true, + \\}; + \\pub const normal2_struct: @"Build.Step.Options.decltest.Options.NormalStruct" = .{ + \\ .hello = null, + \\ .world = false, + \\}; + \\pub const @"Build.Step.Options.decltest.Options.NestedStruct" = struct { + \\ normal_struct: @"Build.Step.Options.decltest.Options.NormalStruct", + \\ normal_enum: @"Build.Step.Options.decltest.Options.NormalEnum" = .foo, + \\}; + \\pub const nested_struct: @"Build.Step.Options.decltest.Options.NestedStruct" = .{ + \\ .normal_struct = .{ + \\ .hello = "bar", + \\ .world = true, + \\ }, + \\ .normal_enum = .foo, \\}; - \\pub const normal1: @"Build.Step.Options.decltest.Options.NormalEnum" = .foo; - \\pub const normal2: @"Build.Step.Options.decltest.Options.NormalEnum" = .bar; \\ , options.contents.items);