Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

65 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Azazel

CI Zig

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.

What It Looks Like

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.

Quick Start

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              # run

setup.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.sh

Doing 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

How It Works

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.

Module Fields

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"
}]

Examples

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/gateway

Documentation

docs/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.

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.

Editor support

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.

Install

Azazel ships a build.zig.zon, so it is fetchable with the Zig package manager:

zig fetch --save git+https://github.com/godofecht/azazel

Most 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.

Part of the Zaza Ecosystem

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.

License

MIT

About

CUE + Zig deterministic build system. No JSON runtime, no flags, no ceremony.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages