diff --git a/build.zig b/build.zig index 0c74262..178772a 100644 --- a/build.zig +++ b/build.zig @@ -113,8 +113,13 @@ fn findOption(name: []const u8) ?spec.Option { return null; } -fn addBuildOptions(b: *std.Build, module_name: []const u8, option_names: []const []const u8) ?*std.Build.Step.Options { - if (option_names.len == 0) return null; +fn addBuildOptions( + b: *std.Build, + module_name: []const u8, + option_names: []const []const u8, + option_values: []const spec.OptionValue, +) ?*std.Build.Step.Options { + if (option_names.len == 0 and option_values.len == 0) return null; const options = b.addOptions(); for (option_names) |name| { @@ -135,6 +140,30 @@ fn addBuildOptions(b: *std.Build, module_name: []const u8, option_names: []const } } + // Injected literal values, for options modules a repo's build.zig would + // normally synthesize (e.g. tigerbeetle's vsr_options). + for (option_values) |ov| { + if (std.mem.eql(u8, ov.kind, "bool")) { + options.addOption(bool, ov.name, ov.bool_value); + } else if (std.mem.eql(u8, ov.kind, "string")) { + options.addOption([]const u8, ov.name, ov.string_value); + } else if (std.mem.eql(u8, ov.kind, "u32")) { + options.addOption(u32, ov.name, ov.u32_value); + } else if (std.mem.eql(u8, ov.kind, "opt_commit")) { + // A ?[40]u8 git commit hash: the 40-char value, or null. + if (ov.commit_value) |commit| { + var buf: [40]u8 = undefined; + if (commit.len != 40) @panic("opt_commit value must be exactly 40 characters"); + @memcpy(&buf, commit[0..40]); + options.addOption(?[40]u8, ov.name, buf); + } else { + options.addOption(?[40]u8, ov.name, null); + } + } else { + @panic("unsupported option_value kind"); + } + } + _ = module_name; return options; } @@ -315,7 +344,7 @@ pub fn build(b: *std.Build) void { .optimize = m.optimize, }); applyNative(b, mod, m.native); - if (addBuildOptions(b, m.name, m.build_options)) |options| { + if (addBuildOptions(b, m.name, m.build_options, m.option_values)) |options| { mod.addOptions(m.build_options_import, options); } modules.put(m.name, mod) catch unreachable; diff --git a/build_spec_test.zig b/build_spec_test.zig index c6be4b9..98025c5 100644 --- a/build_spec_test.zig +++ b/build_spec_test.zig @@ -115,6 +115,18 @@ test "package imports and artifacts name their package surfaces" { } try testing.expect(found); } + for (m.option_values) |ov| { + try testing.expect(ov.name.len > 0); + // kind is one of the four supported tags; opt_commit is null or 40. + const ok = std.mem.eql(u8, ov.kind, "bool") or + std.mem.eql(u8, ov.kind, "string") or + std.mem.eql(u8, ov.kind, "u32") or + std.mem.eql(u8, ov.kind, "opt_commit"); + try testing.expect(ok); + if (std.mem.eql(u8, ov.kind, "opt_commit")) { + if (ov.commit_value) |c| try testing.expect(c.len == 40); + } + } } } diff --git a/gen_build_spec.sh b/gen_build_spec.sh index 0b5f857..25983e1 100755 --- a/gen_build_spec.sh +++ b/gen_build_spec.sh @@ -70,6 +70,15 @@ pub const PackageArtifact = struct { backend: ?[]const u8, }; +pub const OptionValue = struct { + name: []const u8, + kind: []const u8, + bool_value: bool, + string_value: []const u8, + u32_value: u32, + commit_value: ?[]const u8, +}; + pub const Native = struct { c_sources: []const []const u8, include_dirs: []const []const u8, @@ -97,6 +106,7 @@ pub const Module = struct { pkg_imports: []const PackageImport, pkg_artifacts: []const PackageArtifact, build_options: []const []const u8, + option_values: []const OptionValue, build_options_import: []const u8, native: Native, optimize: std.builtin.OptimizeMode, @@ -272,6 +282,25 @@ def zig_pkg_artifacts(items): def zig_bool(v): return 'true' if v else 'false' +def zig_option_values(items): + if not items: + return '&.{}' + rendered = [] + for item in items: + kind = item['kind'] + commit = item.get('commit') + commit_field = 'null' if (kind != 'opt_commit' or not commit) else zig_string(commit) + rendered.append( + '.{ .name = ' + zig_string(item['name']) + + ', .kind = ' + zig_string(kind) + + ', .bool_value = ' + zig_bool(item.get('bool_value', False)) + + ', .string_value = ' + zig_string(item.get('string_value', '')) + + ', .u32_value = ' + str(int(item.get('u32_value', 0))) + + ', .commit_value = ' + commit_field + + ' }' + ) + return '&.{ ' + ', '.join(rendered) + ' }' + def zig_native(item): item = item or {} return '''.{{ @@ -315,6 +344,7 @@ for name, m in mods.items(): pkg_imports_str = zig_pkg_imports(m.get('pkg_imports', [])) pkg_artifacts_str = zig_pkg_artifacts(m.get('pkg_artifacts', [])) build_options_str = zig_string_list(m.get('build_options', [])) + option_values_str = zig_option_values(m.get('option_values', [])) native_str = zig_native(m.get('native', {})) print(f''' .{{ .name = {zig_string(name)}, @@ -330,6 +360,7 @@ for name, m in mods.items(): .pkg_imports = {pkg_imports_str}, .pkg_artifacts = {pkg_artifacts_str}, .build_options = {build_options_str}, + .option_values = {option_values_str}, .build_options_import = {zig_string(m.get('build_options_import', 'build-options'))}, .native = {native_str}, .optimize = {opt}, diff --git a/schema.cue b/schema.cue index c8d030b..2b84996 100644 --- a/schema.cue +++ b/schema.cue @@ -51,6 +51,22 @@ package build backend?: string } +// A literal, typed value injected into a module's build-options module (the +// one imported under build_options_import). Unlike #Option, which surfaces a +// CLI flag, an #OptionValue is a fixed value the build config supplies so a +// module that does @import("") compiles without the project's own +// build.zig. Used to reproduce generated options like tigerbeetle's +// vsr_options. `kind` selects which typed field is emitted. +#OptionValue: { + name: string + kind: "bool" | "string" | "u32" | "opt_commit" + bool_value: bool | *false + string_value: string | *"" + u32_value: uint32 | *0 + // opt_commit emits a ?[40]u8: a 40-char hex string, or "" for null. + commit: string | *"" +} + #Package: { url?: string hash?: string @@ -98,6 +114,7 @@ package build pkg_imports: [...#PackageImport] | *[] pkg_artifacts: [...#PackageArtifact] | *[] build_options: [...string] | *[] + option_values: [...#OptionValue] | *[] build_options_import: string | *"build-options" native: #Native | *{}