# code generation generating zig source at build time - for help text, unicode tables, bindings, anything derived from data. ## the pattern build an executable, run it, capture output, import as zig source: ```zig // 1. build the generator (always for host, not target) const gen_exe = b.addExecutable(.{ .name = "helpgen", .root_module = b.createModule(.{ .root_source_file = b.path("src/helpgen.zig"), .target = b.graph.host, // runs on build machine }), }); // 2. run it and capture stdout const gen_run = b.addRunArtifact(gen_exe); const gen_output = gen_run.captureStdOut(); // 3. make it available as an import step.root_module.addAnonymousImport("help_strings", .{ .root_source_file = gen_output, }); ``` now your code can `@import("help_strings")` and get the generated content. ## why `.target = b.graph.host` the generator runs during the build, on your machine. even if you're cross-compiling to arm64-linux, the generator needs to run on your x86-macos (or whatever you're building from). `b.graph.host` gives you the host target - the machine running the build. ## writing to files instead if you need the output as a file (not just an import): ```zig const wf = b.addWriteFiles(); const output_path = wf.addCopyFile( gen_run.captureStdOut(), "generated.zig", ); // output_path is a LazyPath you can use elsewhere ``` ## custom build steps for external tools wrap non-zig tools (metal shader compiler, lipo, etc.) as build steps: ```zig pub const MetallibStep = struct { step: std.Build.Step, output: std.Build.LazyPath, pub fn create(b: *std.Build, shader_source: []const u8) *MetallibStep { const run = b.addSystemCommand(&.{ "/usr/bin/xcrun", "-sdk", "macosx", "metal", "-c", "-o", }); const ir_output = run.addOutputFileArg("shader.ir"); run.addFileArg(b.path(shader_source)); // chain another command for metallib... const self = b.allocator.create(MetallibStep) catch @panic("OOM"); self.* = .{ .step = std.Build.Step.init(.{ ... }), .output = ir_output, }; return self; } }; ``` key points: - `addOutputFileArg()` creates a LazyPath for the output - `addFileArg()` adds a dependency on an input file - proper dependency tracking means the step reruns when inputs change ## conditional embedding bun embeds javascript runtime code in release builds but loads from disk in debug: ```zig pub fn shouldEmbedCode(opts: *const BuildOptions) bool { return opts.optimize != .Debug or opts.force_embed; } ``` debug builds iterate faster (no recompile to change js). release builds are self-contained. sources: - [ghostty/src/build/HelpStrings.zig](https://github.com/ghostty-org/ghostty/blob/main/src/build/HelpStrings.zig) - [ghostty/src/build/MetallibStep.zig](https://github.com/ghostty-org/ghostty/blob/main/src/build/MetallibStep.zig)