A deterministic build configuration layer powered by CUE for constraint validation and Zig for execution. The configuration frontend for Zaza.
Who this is for: Zig developers who'd rather declare their build than hand-maintain std.Build. You still write your own Zig — Azazel replaces the imperative build.zig boilerplate (and the churn of tracking std.Build across Zig releases), not the language.
project.cue → CUE validates → build_spec.zig → zig build → binary
(human) (schema.cue) (generated) (engine)
No JSON runtime. No flags. No ceremony.
package build
core: #Module & {
kind: "module"
root: "src/core.zig"
}
app: #Module & {
kind: "exe"
root: "src/main.zig"
deps: ["core"]
profile: "release"
}That's the core project configuration. Toolchain lanes, import-mode linking, and post-build commands are opt-in when a project needs them.
brew install cue zig # prerequisites (also needs python3)
git clone https://github.com/godofecht/azazel.git
cd azazel
./setup.sh # check tools, generate, build, test
./zig-out/bin/app # runsetup.sh reports the versions of zig, cue and python3, prints install
hints for anything missing, then runs the full pipeline. It uses a
Zig-version-specific cache directory by default, so switching between the
0.14/0.15/0.16 lanes does not reuse a stale build runner. It is safe to run
repeatedly and exits non-zero on the first failure.
./setup.sh --check-only # just report tool versions
./setup.sh --examples # also build and test everything in examples/
ZIG=/path/to/zig ./setup.sh # use a specific Zig binary
ZIG_CACHE_DIR=/tmp/azazel-cache ./setup.shDoing it by hand is three commands:
./gen_build_spec.sh # CUE validates → generates build_spec.zig
zig build # compile
zig build test --summary all # 56 tests| Layer | Tool | File | Purpose |
|---|---|---|---|
| Human | You | project.cue |
Declare modules, deps, profiles |
| Constraint | CUE | schema.cue |
Type-check and resolve defaults |
| Codegen | Shell | gen_build_spec.sh |
Emit typed Zig source (not JSON) |
| Execution | Zig | build.zig |
Compile and link from spec |
CUE generates Zig source code, not JSON. The build system never parses anything at runtime. The module array is a compile-time constant.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
kind |
"exe" | "static" | "shared" | "module" |
Yes | — | Output type |
root |
string |
Yes | — | Root source file |
artifact_name |
string |
No | module key | Name of the produced artifact when it should differ from the module key |
deps |
[...string] |
No | [] |
Module dependencies |
profile |
"debug" | "release" |
No | "debug" |
Optimization level |
link |
"abi" | "import" |
No | "abi" |
How dependents consume this module |
pre |
[{ argv: [...] }] |
No | [] |
Commands to run before compiling this module |
post |
[{ argv: [...] }] |
No | [] |
Commands to run after installing this module |
pkg_imports |
package import list | No | [] |
Imports from build.zig.zon dependencies |
pkg_library_paths |
package library path list | No | [] |
Adds library search paths from lazy package dependencies |
build_options |
[...string] |
No | [] |
Typed options to expose through an options module |
native |
native metadata | No | {} |
C sources, include dirs, system libs, frameworks |
project.cue can also declare the supported Zig toolchain lanes:
toolchain: zig: {
lanes: ["0.14", "0.15", "0.16"]
preferred: "0.15"
}Azazel intentionally tracks Zig by minor-version lanes because std.Build
changes between Zig releases. The generated build_spec.zig records those
lanes and build.zig rejects unsupported lanes before doing any real work.
Two things that catch people out. Every module also has to be listed in
export.cue's _modules map, or it is silently not built. And by default deps
is a linker edge, so symbols cross it as pub export fn / extern fn rather
than as a Zig @import. Both are covered in
docs/WIKI.md.
Set link: "import" on a dependency to consume it as a Zig module instead. It
merges into its dependents (@import("name")) as one compilation, with no
separate artifact and no link step. That rebuilds much faster on pure
Zig-to-Zig graphs. Keep the default "abi" for shared libraries and C or C++
interop. See link and
examples/05-import-mode.
For large Zig projects, prefer kind: "module" for named modules that should
never produce an artifact. A module target is always consumed by import.
Large projects can also declare package imports and native link metadata:
options: [{
name: "enable_tracy"
type: "bool"
description: "Enable Tracy instrumentation"
default: false
}]
app: #Module & {
kind: "exe"
root: "src/main.zig"
build_options: ["enable_tracy"]
pkg_imports: [{
alias: "known-folders"
package: "known_folders"
module: "known-folders"
}]
native: {
link_libc: true
system_libs: ["sqlite3"]
pkg_config_libs: ["libinput"]
frameworks: ["CoreFoundation"]
}
}Package imports and artifacts can also pass a package backend enum option
when the dependency declares one:
pkg_imports: [{
alias: "zgui"
package: "zgui"
module: "root"
backend: "glfw_wgpu"
}]
pkg_artifacts: [{
package: "zgui"
artifact: "imgui"
backend: "glfw_wgpu"
}]Four runnable projects, each self-contained with its own README.
| Directory | Demonstrates |
|---|---|
examples/01-hello |
The minimum module. Both schema defaults. |
examples/02-lib-and-app |
deps, profile, static linkage, the C-ABI boundary. |
examples/03-services |
All three kinds, a shared library, multiple deps, mixed profiles. |
examples/04-validation |
Every rejection the schema performs, with real cue output. |
examples/05-import-mode |
link: "import", a dependency merged as a Zig module instead of linked. |
examples/06-clusters |
Clusters: import graphs behind abi boundaries, the shape for large projects. |
cd examples/03-services
./gen_build_spec.sh && zig build && ./zig-out/bin/gatewaydocs/WIKI.md is the complete reference: the pipeline, every
schema field with a worked example, how build_spec.zig maps onto
build.zig, and troubleshooting for the common failures.
Published at godofecht.github.io/azazel, regenerated from docs/WIKI.md on every change.
- Huge Zig Project Corpus
- The Pipeline
- Installation
- Quickstart
- Schema Reference
- Code Generation
- Examples
- Troubleshooting
The corpus runner also has an executable parity lane:
tools/huge_corpus.py --executable-parity regenerates Azazel's build spec in a
repo-local .azazel/parity-work/ workspace and runs modeled target slices
against upstream source. The modeled slices cover the libxev module probe,
libvaxis package-backed module probe, and zig-gamedev shared vectormath
module plus pinned zmath/zopengl/zglfw/zmesh/znoise package probe.
zig-gamedev also links exported native package artifacts from zglfw,
zmesh, and znoise through the generated parity workspace and verifies
staged sdl2_demo_content assets under an explicit install prefix. libvaxis
resolves local zigimg and uucode path dependencies through the generated
parity workspace, and strict install-path parity exposes its remaining
generated Unicode config gap.
Use tools/huge_corpus.py --plan --expect-count 10 before a full batch run to
write corpus-plan.json and verify that all ten tracked forks are selected.
Use tools/huge_corpus.py --roadmap --expect-count 10 to generate
corpus-roadmap.md plus issue-ready markdown files under corpus-issues/ from
the same manifest data.
Future entries can stay marked unverified until their real baseline command
has been run and classified.
ide/ has a VS Code extension for authoring project.cue: syntax
highlighting for the #Module fields, inline diagnostics that run
cue export -e build on save, graph warnings for missing dependency targets and
unexported modules, completion for fields and enum values, hover help, and
go-to-definition from deps strings to module declarations. It also adds an
"Azazel: Generate build_spec" command. Open ide/vscode in VS Code and press F5
to try it; see ide/vscode/README.md.
A dependency-free stdio language server with the same diagnostics, completion,
hover, and definition behavior lives in ide/server; see
ide/DESIGN.md.
Azazel ships a build.zig.zon, so it is fetchable with the Zig package manager:
zig fetch --save git+https://github.com/godofecht/azazelMost projects use it as a starting point rather than a linked dependency: copy a
directory from examples/ (or the repo root) and edit project.cue. The
maintained Zig lanes are 0.14.x, 0.15.x, and 0.16.x; project configs can narrow
that list with toolchain.zig.lanes.
Azazel is the declarative configuration frontend for Zaza, a Zig-driven build system for C, C++, Zig, CMake-interop, and WebAssembly. Azazel can also be used standalone with any Zig project.
MIT