I finally got some time to sit down and work on this. Your solution worked exactly like I was hoping!
I did have to do some contrived stuff to get my Config structs to the Generator using Build Options though. I’m hoping this is temporary until Build Options are able to support Structs, Enums, Unions, etc. That said, I figured I’d post what I did here for posterity and in case anyone knows of a better way.
Cova’s build.zig
function.
/// Add Cova's Meta Doc Generation Step to a project's `build.zig`.
pub fn addCovaDocGenStep(
b: *std.Build,
/// The Cova Dependency of the project's `build.zig`.
cova_dep: *std.Build.Dependency,
/// The Program Module where the Command Type and Setup Command can be found.
program_mod: *std.Build.Module,
/// The Config for Meta Doc Generation.
doc_gen_config: generate.MetaDocConfig,
) *std.Build.Step.Run {
const cova_gen_exe = cova_dep.artifact("cova_generator");
cova_gen_exe.root_module.addImport("cova", cova_dep.module("cova"));
cova_gen_exe.root_module.addImport("program", program_mod);
const md_conf_opts = b.addOptions();
var sub_conf_map = std.StringHashMap(?*std.Build.Step.Options).init(b.allocator);
sub_conf_map.put("manpages_config", null) catch @panic("OOM");
sub_conf_map.put("tab_complete_config", null) catch @panic("OOM");
inline for (@typeInfo(generate.MetaDocConfig).Struct.fields) |field| {
switch(@typeInfo(field.type)) {
.Struct, .Enum => continue,
.Optional => |optl| {
switch (@typeInfo(optl.child)) {
.Struct => |struct_info| {
const maybe_conf = @field(doc_gen_config, field.name);
if (maybe_conf) |conf| {
const doc_conf_opts = b.addOptions();
inline for (struct_info.fields) |s_field| {
if (@typeInfo(s_field.type) == .Enum) {
doc_conf_opts.addOption(usize, s_field.name, @intFromEnum(@field(conf, s_field.name)));
continue;
}
doc_conf_opts.addOption(s_field.type, s_field.name, @field(conf, s_field.name));
}
doc_conf_opts.addOption(bool, "provided", true);
sub_conf_map.put(field.name, doc_conf_opts) catch @panic("OOM");
}
continue;
},
else => {},
}
},
.Pointer => |ptr| {
if (ptr.child == generate.MetaDocConfig.MetaDocKind) {
var kinds_list = std.ArrayList(usize).init(b.allocator);
for (@field(doc_gen_config, field.name)) |kind|
kinds_list.append(@intFromEnum(kind)) catch @panic("There was an issue with the Meta Doc Config.");
md_conf_opts.addOption(
[]const usize,
field.name,
kinds_list.toOwnedSlice() catch @panic("There was an issue with the Meta Doc Config."),
);
continue;
}
},
else => {},
}
md_conf_opts.addOption(
field.type,
field.name,
@field(doc_gen_config, field.name),
);
}
cova_gen_exe.root_module.addOptions("md_config_opts", md_conf_opts);
var sub_conf_map_iter = sub_conf_map.iterator();
while (sub_conf_map_iter.next()) |conf| {
cova_gen_exe.root_module.addOptions(
conf.key_ptr.*,
if (conf.value_ptr.*) |conf_opts| conf_opts
else confOpts: {
const conf_opts = b.addOptions();
conf_opts.addOption(bool, "provided", false);
break :confOpts conf_opts;
}
);
}
return b.addRunArtifact(cova_gen_exe);
}
Relevant code from generator.zig
:
const cova = @import("cova");
const generate = cova.generate;
/// This is a reference module for the program being built. Typically this is the main `.zig` file
/// in a project that has both the `main()` function and `setup_cmd` Command.
const program = @import("program");
/// This is a reference to the Meta Doc Build Options passed in from `build.zig`.
const md_config = @import("md_config_opts");
/// Manpages Config
const manpages_config = optsToConf(generate.ManpageConfig, @import("manpages_config"));
/// Tab Completion Config
const tab_complete_config = optsToConf(generate.TabCompletionConfig, @import("tab_complete_config"));
/// Translate Build Options to Meta Doc Generation Configs.
///TODO Refactor this once Build Options support Types.
fn optsToConf(comptime ConfigT: type, comptime conf_opts: anytype) ?ConfigT {
if (!conf_opts.provided) return null;
var conf = ConfigT{};
for (@typeInfo(ConfigT).Struct.fields) |field| {
if (std.mem.eql(u8, field.name, "provided")) continue;
@field(conf, field.name) = @field(conf_opts, field.name);
}
return conf;
}
Relevant code from the project’s build.zig
:
// Docs
// - Meta
const cova_gen = @import("cova").addCovaDocGenStep(b, cova_dep, &zing_exe.root_module, .{
.kinds = &.{ .manpages, .bash },
.manpages_config = .{
.local_filepath = "meta",
.version = "0.1.0",
.ver_date = "04 FEB 2024",
.man_name = "User's Manual",
.author = "00JCIV00",
.copyright = "Copyright info here",
},
.tab_complete_config = .{
.local_filepath = "meta",
.include_opts = true,
}
});
const meta_doc_gen = b.step("gen-meta", "Generate Meta Docs using Cova");
meta_doc_gen.dependOn(&cova_gen.step);