diff --git a/build.zig b/build.zig index 6505f8aec..a08602a10 100644 --- a/build.zig +++ b/build.zig @@ -114,6 +114,12 @@ pub fn build(b: *std.Build) !void { "Build and install the benchmark executables.", ) orelse false; + const emit_helpgen = b.option( + bool, + "emit-helpgen", + "Build and install the helpgen executable.", + ) orelse false; + // On NixOS, the built binary from `zig build` needs to patch the rpath // into the built binary for it to be portable across the NixOS system // it was built for. We default this to true if we can detect we're in @@ -177,6 +183,10 @@ pub fn build(b: *std.Build) !void { // We can use wasmtime to test wasm b.enable_wasmtime = true; + // Help exe. This must be run before any dependent executables because + // otherwise the build will be cached without emit. That's clunky but meh. + if (emit_helpgen) addHelp(b, null); + // Add our benchmarks try benchSteps(b, target, optimize, config, emit_bench); @@ -197,8 +207,6 @@ pub fn build(b: *std.Build) !void { .{version}, )); - createHelp(b); - // Exe if (exe_) |exe| { exe.root_module.addOptions("build_options", exe_options); @@ -477,7 +485,10 @@ pub fn build(b: *std.Build) !void { } // On Mac we can build the embedding library. This only handles the macOS lib. - if (builtin.target.isDarwin() and target.result.os.tag == .macos) { + if (builtin.target.isDarwin() and + target.result.os.tag == .macos and + config.app_runtime == .none) + { // Create the universal macOS lib. const macos_lib_step, const macos_lib_path = try createMacOSLib( b, @@ -1100,42 +1111,43 @@ fn addDeps( } } - addHelp(step); + addHelp(b, step); return static_libs; } -var generate_help_step: *std.Build.Step.Run = undefined; -var help_strings: std.Build.LazyPath = undefined; - /// Generate help files -fn createHelp(b: *std.Build) void { - const generate_help = b.addExecutable(.{ - .name = "generate_help", - .root_source_file = .{ .path = "src/generate_help_strings.zig" }, - .target = b.host, - }); +fn addHelp( + b: *std.Build, + step_: ?*std.Build.Step.Compile, +) void { + // Our static state between runs. We memoize our help strings + // so that we only execute the help generation once. + const HelpState = struct { + var generated: ?std.Build.LazyPath = null; + }; - generate_help_step = b.addRunArtifact(generate_help); - generate_help_step.step.dependOn(&generate_help.step); + const help_output = HelpState.generated orelse strings: { + const help_exe = b.addExecutable(.{ + .name = "helpgen", + .root_source_file = .{ .path = "src/helpgen.zig" }, + .target = b.host, + }); + if (step_ == null) b.installArtifact(help_exe); - help_strings = generate_help_step.addOutputFileArg("help_strings.zig"); + const help_run = b.addRunArtifact(help_exe); + HelpState.generated = help_run.captureStdOut(); + break :strings HelpState.generated.?; + }; - if (builtin.target.isDarwin()) { - const generated = b.option([]const u8, "help_strings", "generated help file") orelse "help_strings"; - const write_file = b.addWriteFiles(); - help_strings = write_file.addCopyFile(help_strings, generated); + if (step_) |step| { + help_output.addStepDependencies(&step.step); + step.root_module.addAnonymousImport("help_strings", .{ + .root_source_file = help_output, + }); } } -/// Add the generated help files to the build. -fn addHelp(step: *std.Build.Step.Compile) void { - step.step.dependOn(&generate_help_step.step); - step.root_module.addAnonymousImport("help_strings", .{ - .root_source_file = help_strings, - }); -} - fn benchSteps( b: *std.Build, target: std.Build.ResolvedTarget, diff --git a/src/cli/action.zig b/src/cli/action.zig index f97a5d2cf..2a7e0c6a9 100644 --- a/src/cli/action.zig +++ b/src/cli/action.zig @@ -69,6 +69,21 @@ pub const Action = enum { .@"list-colors" => try list_colors.run(alloc), }; } + + /// Returns the filename associated with an action. This is a relative + /// path from the root src/ directory. + pub fn file(comptime self: Action) []const u8 { + comptime { + const filename = filename: { + const tag = @tagName(self); + var filename: [tag.len]u8 = undefined; + _ = std.mem.replace(u8, tag, "-", "_", &filename); + break :filename &filename; + }; + + return "cli/" ++ filename ++ ".zig"; + } + } }; test "parse action none" { diff --git a/src/generate_help_strings.zig b/src/generate_help_strings.zig deleted file mode 100644 index 1b4ed35ca..000000000 --- a/src/generate_help_strings.zig +++ /dev/null @@ -1,165 +0,0 @@ -const std = @import("std"); -const ziglyph = @import("ziglyph"); -const Action = @import("cli/action.zig").Action; -const Config = @import("config/Config.zig"); - -pub fn searchConfigAst(alloc: std.mem.Allocator, output: std.fs.File) !void { - var ast = try std.zig.Ast.parse(alloc, @embedFile("config/Config.zig"), .zig); - defer ast.deinit(alloc); - - const config: Config = .{}; - - const tokens = ast.tokens.items(.tag); - - var set = std.StringHashMap(bool).init(alloc); - defer set.deinit(); - - try output.writeAll( - \\//THIS FILE IS AUTO GENERATED - \\//DO NOT MAKE ANY CHANGES TO THIS FILE! - ); - - try output.writeAll("\n\n"); - - inline for (@typeInfo(@TypeOf(config)).Struct.fields) |field| { - if (field.name[0] != '_') try set.put(field.name, false); - } - - var index: u32 = 0; - while (true) : (index += 1) { - if (index >= tokens.len) break; - const token = tokens[index]; - - if (token == .identifier) { - const slice = ast.tokenSlice(index); - // We need this check because the ast grabs the identifier with @"" in case it's used. - const key = if (slice[0] == '@') slice[2 .. slice.len - 1] else slice; - - if (key[0] == '_') continue; - - if (set.get(key)) |value| { - if (value) continue; - if (tokens[index - 1] != .doc_comment) continue; - - const comment = try consumeDocComments(alloc, ast, index - 1, &tokens); - const prop_type = ": " ++ "[:0]const u8 " ++ "= " ++ "\n"; - - try output.writeAll(slice); - try output.writeAll(prop_type); - // const concat = try std.mem.concat(self.alloc, u8, &.{ slice, prop_type }); - // try output.writeAll(concat); - try output.writeAll(comment); - try output.writeAll("\n\n"); - - try set.put(key, true); - } - } - if (token == .eof) break; - } -} - -fn actionPath(comptime action: Action) []const u8 { - return switch (action) { - .version => "cli/version.zig", - .@"list-fonts" => "cli/list_fonts.zig", - .@"list-keybinds" => "cli/list_keybinds.zig", - .@"list-themes" => "cli/list_themes.zig", - .@"list-colors" => "cli/list_colors.zig", - }; -} - -pub fn searchActionsAst(alloc: std.mem.Allocator, output: std.fs.File) !void { - inline for (@typeInfo(Action).Enum.fields) |field| { - const action = comptime std.meta.stringToEnum(Action, field.name).?; - - var ast = try std.zig.Ast.parse(alloc, @embedFile(comptime actionPath(action)), .zig); - const tokens = ast.tokens.items(.tag); - - var index: u32 = 0; - while (true) : (index += 1) { - if (tokens[index] == .keyword_fn) { - if (std.mem.eql(u8, ast.tokenSlice(index + 1), "run")) { - if (tokens[index - 2] != .doc_comment) { - std.debug.print("doc comment must be present on run function of the {s} action!", .{field.name}); - std.process.exit(1); - } - const comment = try consumeDocComments(alloc, ast, index - 2, &tokens); - const prop_type = "@\"+" ++ field.name ++ "\"" ++ ": " ++ "[:0]const u8 " ++ "= " ++ "\n"; - - try output.writeAll(prop_type); - try output.writeAll(comment); - try output.writeAll("\n\n"); - break; - } - } - } - } -} - -fn consumeDocComments(alloc: std.mem.Allocator, ast: std.zig.Ast, index: std.zig.Ast.TokenIndex, toks: anytype) ![]const u8 { - var lines = std.ArrayList([]const u8).init(alloc); - defer lines.deinit(); - - const tokens = toks.*; - var current_idx = index; - - // We iterate backwards because the doc_comment tokens should be on top of each other in case there are any. - while (true) : (current_idx -= 1) { - const token = tokens[current_idx]; - - if (token != .doc_comment) break; - // Insert at 0 so that we don't have the text in reverse. - try lines.insert(0, ast.tokenSlice(current_idx)[3..]); - } - - const prefix = findCommonPrefix(lines); - - var buffer = std.ArrayList(u8).init(alloc); - const writer = buffer.writer(); - - for (lines.items) |line| { - try writer.writeAll(" \\\\"); - try writer.writeAll(line[@min(prefix, line.len)..]); - try writer.writeAll("\n"); - } - try writer.writeAll(",\n"); - - return buffer.toOwnedSlice(); -} - -fn findCommonPrefix(lines: std.ArrayList([]const u8)) usize { - var m: usize = std.math.maxInt(usize); - for (lines.items) |line| { - var n: usize = std.math.maxInt(usize); - for (line, 0..) |c, i| { - if (c != ' ') { - n = i; - break; - } - } - m = @min(m, n); - } - return m; -} - -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - var arena = std.heap.ArenaAllocator.init(gpa.allocator()); - const alloc = arena.allocator(); - - const args = try std.process.argsAlloc(alloc); - defer std.process.argsFree(alloc, args); - - if (args.len != 2) { - std.debug.print("invalid number of arguments provided!", .{}); - std.process.exit(1); - } - - const path = args[1]; - - var output = try std.fs.cwd().createFile(path, .{}); - defer output.close(); - - try searchConfigAst(alloc, output); - try searchActionsAst(alloc, output); -}