Declarative std.Build graphs generated from build.zig.zon at comptime.
zbuild reads @import("build.zig.zon") as a typed value and turns it into normal std.Build calls. There is no runtime parser, and the graph is generated directly inside the build, not by an external codegen phase. It is a library that sits on top of Zig's native build graph.
- Want to try it immediately: use the quickstart below.
- Want the mental model first: read Conceptual Model.
- Want exact field-by-field details: read Schema Reference.
- Want the rationale and tradeoffs: read Why zbuild?.
zig fetch --save=zbuild <zbuild-url-or-path>That writes the .dependencies.zbuild entry for you.
const zbuild = @import("zbuild");
const std = @import("std");
pub fn build(b: *std.Build) !void {
_ = try zbuild.configureBuild(b, @import("build.zig.zon"), .{});
}.executables = .{
.myapp = .{
.root_module = .{
.root_source_file = "src/main.zig",
},
},
},Assume the rest of build.zig.zon is the normal Zig package metadata from your project or zig init.
zig build
zig build run:myapp
zig build helpOnce the first executable works, add modules, tests, runs, fmts, options modules, or libraries as needed. The simple example shows the minimal shape. The full example shows most of the library in one place.
modulesfor reusable Zig modules with imports, include paths, and dependency librariesexecutables,libraries, andobjectstestswith per-test steps and an aggregateteststepfmtswith per-target steps and an aggregatefmtsteprunsfor arbitrary system commandsaliasesfor named aggregate steps such ascheck,ci, orreleaseoptions_modulesthat become importable Zig config modules and-Dmodule.optionCLI flagspresetsfor named bundles ofoptions_modulesdefaults, selected with-Dpreset=<name>- comptime dependency args forwarded to
b.dependency(...) - a built-in help step (
helpby default, configurable viaOptions.help_step) - two-phase validation so local graph mistakes fail early
zbuild becomes easy to use once you keep three rules in your head:
-
build.zig.zondeclares graph nodes.modules,executables,libraries,tests,runs,fmts, andaliaseseach map to a different kind ofstd.Buildnode or step. -
Ownership is encoded in syntax. Enum literals like
.core,.config, and.myappmean "this belongs to the zbuild-owned graph". Bare strings like"shared"and"gen:prep"mean "this is manualbuild.zigstate registered beforeconfigureBuild". -
Validation happens in two phases. Local manifest structure and manifest-owned refs fail at comptime. Manual refs and dependency exports fail during configure, after zbuild can actually inspect them.
If you want the full model, including namespace rules and why those syntax splits exist, read docs/concepts.md.
If you have a recurring bundle of option values, keep the types in options_modules and add a named preset on top:
.options_modules = .{
.app = .{
.log_level = .{
.type = .@"enum",
.values = .{ .debug, .info, .warn },
.default = .info,
},
.asset_dir = .{
.type = .string,
},
},
},
.presets = .{
.dev = .{
.app = .{
.log_level = .debug,
},
},
.prod = .{
.app = .{
.log_level = .warn,
.asset_dir = "dist/prod",
},
},
},Then select one explicitly:
zig build run:myapp -Dpreset=prod
zig build run:myapp -Dpreset=prod -Dapp.log_level=debugPresets only override options_modules. They do not create a second config API, and per-option -Dmodule.option=... flags still win.
zbuild does not replace build.zig. It owns the declarative 90%, and you keep Zig for the rest.
Register manual modules or steps before configureBuild:
const zbuild = @import("zbuild");
const std = @import("std");
pub fn build(b: *std.Build) !void {
_ = b.addModule("shared", .{
.root_source_file = b.path("src/shared.zig"),
.target = b.resolveTargetQuery(.{}),
.optimize = .Debug,
});
_ = b.step("gen:prep", "manual prep step");
_ = try zbuild.configureBuild(b, @import("build.zig.zon"), .{});
}Then reference those manual nodes from the manifest with bare strings:
.executables = .{
.app = .{
.root_module = "shared",
},
},
.runs = .{
.demo = .{
.cmd = .{ "echo", "ok" },
.depends_on = .{ "gen:prep" },
},
},- Conceptual Model: the bottom-up explanation of how the graph, namespaces, and validation fit together
- Schema Reference: exact field types, syntax, and generated step names
- Why zbuild?: the problem it solves and the design constraints it follows
- Simple Example: the smallest useful project
- Full Example: modules, tests, runs, fmts, and options modules together
Zig 0.16.0+
MIT