Zig build system
Contract
| Field | Bound contract |
|---|---|
| Trigger | The user creates or edits build.zig, adds a library or C source to a Zig project, needs a -D build option, wires zig build test, adds a custom step, or manages build.zig.zon dependencies. |
| Authority | Read-only. The skill emits build.zig and build.zig.zon snippets to chat; the user writes them. Rollback is not needed. No remote mutation. |
| Side effect | Chat output. Confirmation runs of zig build happen in a scratch copy, not the project tree. |
| Done | The build.zig shape for the request is reported, it matches the std.Build API of the installed Zig, and a scratch zig build of the same shape succeeds. |
Inputs
- Zig version from
zig version: required. The snippets below were run on Zig 0.14.1; the build API moves between releases, so re-run the scratch build on the installed version. - Project layout: required. Which files are executables, libraries, modules, C sources, and tests.
- Build-time options wanted: optional; name, type, default.
- Dependencies: optional; URL and whether a hash is known.
Procedure
Start from
zig init, which writesbuild.zig,build.zig.zon, andsrc/. Build withzig build, run withzig build run, test withzig build test. Done when: the generated project builds.Write the executable, run, and test steps in the current shape, with a module created first and handed to the compile step:
const std = @import("std"); pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); const exe = b.addExecutable(.{ .name = "myapp", .root_module = b.createModule(.{ .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }), }); b.installArtifact(exe); const run_cmd = b.addRunArtifact(exe); run_cmd.step.dependOn(b.getInstallStep()); if (b.args) |args| run_cmd.addArgs(args); b.step("run", "Run the app").dependOn(&run_cmd.step); const unit_tests = b.addTest(.{ .root_module = exe.root_module }); b.step("test", "Run unit tests").dependOn(&b.addRunArtifact(unit_tests).step); }addExecutablestill accepts.root_source_file,.target, and.optimizedirectly, but Zig 0.14.1 marks those fields deprecated in favor of.root_module. Done when:zig build,zig build run, andzig build testall resolve.Add libraries with
b.addLibrary(.{ .linkage = .static, .name = "mylib", .root_module = <module> }); use.linkage = .dynamicplus.version = .{ .major = 1, .minor = 0, .patch = 0 }for a shared library.addStaticLibraryandaddSharedLibrarystill exist on 0.14.1. Link into an executable withexe.linkLibrary(lib). Done when:zig-out/libholds the artifact.Add C sources:
exe.addCSourceFile(.{ .file = b.path("src/legacy.c"), .flags = &.{ "-std=c11", "-Wall" } })for one file,exe.addCSourceFiles(.{ .files = &.{ "src/a.c", "src/b.c" }, .flags = &.{"-std=c11"} })for several (paths are strings relative to the package, or set.root). Include paths:exe.addIncludePath(b.path("include")). System libraries:exe.linkSystemLibrary("curl"). Alwaysexe.linkLibC()when C code or the C standard library is involved. Done when: the C objects link into the artifact.Expose build options. Declare with
b.option(bool, "logging", "Enable debug logging") orelse false; enums and integers work the same way. Pass them to Zig code throughconst options = b.addOptions(); options.addOption(bool, "enable_logging", enable_logging); exe.root_module.addOptions("build_options", options);and read them with@import("build_options"). Users set them withzig build -Dlogging=true -Dbackend=vulkan. Done when:zig build --helplists the option.Share code through modules:
const utils = b.addModule("utils", .{ .root_source_file = b.path("src/utils.zig") });thenexe.root_module.addImport("utils", utils);and the same on any test module; source imports it with@import("utils"). Done when: both the executable and its tests import the module.Declare dependencies in
build.zig.zon. On 0.14.1 the manifest has.nameas an enum literal (.name = .myapp), a.fingerprint,.version,.minimum_zig_version,.dependencies, and.paths. Each dependency is.{ .url = "<tarball url>", .hash = "<hash>" }; runzig build(orzig fetch <url>) and Zig prints the hash to paste when it is missing. Consume withconst dep = b.dependency("zig_clap", .{ .target = target, .optimize = optimize });andexe.root_module.addImport("clap", dep.module("clap"));. Done when:zig build --fetchcompletes and the import resolves.Add custom steps:
b.addSystemCommand(&.{ "python3", "scripts/gen.py", "--output", "src/generated.zig" })withexe.step.dependOn(&gen.step)for generation;b.addInstallFile(b.path("config/default.toml"), "share/myapp/config.toml")hooked tob.getInstallStep()for extra install files;b.addWriteFilefor generated sources. Inspect the graph withzig build --verboseand change the install root with--prefix. Done when: the step appears inzig build --helpor runs in the graph.Confirm the final
build.zigwith a scratchzig buildandzig build teston the installed Zig before reporting. Done when: both succeed.
Failure and recovery
| Failure class | Behavior |
|---|---|
| Field or function missing on the installed Zig | Read the API in <zig lib dir>/std/Build.zig (zig env prints lib_dir) and adjust; the shape changes between releases. |
duplicate symbol at link |
The same C file was added twice or two artifacts define one symbol; add each source once. |
| Hash mismatch for a dependency | Paste the hash Zig prints; a changed upstream tarball needs the new hash. |
| C library found only through pkg-config or a vendor path | Add the include and library paths explicitly; linkSystemLibrary alone finds only what the system linker sees. |
| Cross-target build in the same file | Multi-target loops over std.Target.Query: use zig-cross. |
Output
A chat report holding the complete build.zig (and build.zig.zon when dependencies are involved) for the request, the zig build commands the user runs, and the line confirming the scratch build passed on the installed Zig version.