Build System

Zig has no Make or CMake: the build is a Zig program (build.zig) that the compiler runs. Build steps form a graph — compile, test, run, install — and every step is declared in ordinary Zig code with full type checking.

From Scaffold to Program

zig init creates a working project with a library, an executable, and a test — instantly green. It is the fastest path to seeing the whole pipeline in motion.

zig init

mkdir hello && cd hello
zig init          # generates build.zig, build.zig.zon, src/
zig build run     # compiles and runs the sample executable
zig build test    # runs the sample unit tests

Project Layout

hello/
├── build.zig        # the build script — a Zig source file
├── build.zig.zon    # package metadata and dependencies
├── src/
│   ├── main.zig     # executable entry
│   └── root.zig     # library exports
└── .zig-cache/      # compiler cache — ephemeral, never commit

Anatomy of build.zig

The core of any build.zig is the build function. It receives a Build handle, reads options, creates artifacts, and wires steps. The code below is for Zig 0.15; older releases used a slightly different module API.

Adding an Executable

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});   // --target flag
    const optimize = b.standardOptimizeOption(.{}); // -Doptimize flag

    const exe = b.addExecutable(.{
        .name = "hello",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });
    b.installArtifact(exe); // makes the binary appear in zig-out/bin

    const run_cmd = b.addRunArtifact(exe);
    if (b.args) |args| run_cmd.addArgs(args);
    const run_step = b.step("run", "Run the app");
    run_step.dependOn(&run_cmd.step);
}

Steps and Dependencies

Every artifact exposes steps (compile, run, test), and steps declare dependencies with dependOn. The example wires a run step after the compile step — the graph runs in order, and caching skips untouched parts.

build step graph

Figure 1 — steps form a dependency graph: test feeds run, install feeds release.

The Workflow

Three commands cover development; all three read the same build.zig, so the paths stay consistent.

Command Reference

CommandEffect
zig buildCompile all installable artifacts into zig-out/
zig build runBuild, then execute the run step
zig build testBuild and execute every test step
zig build run -- argsPass arguments to the running program

Build Options

Options arrive as flags: -Doptimize=ReleaseSafe picks a mode, -Dtarget=x86_64-windows cross-compiles. --help on any build prints the options your build.zig declared — self-documenting builds are the norm.

Modules and Packages

Larger projects split code into modules and dependencies. build.zig.zon declares the package identity and its external dependencies with hashes; zig fetch --save pins them.

build.zig.zon

.{
    .name = .hello,
    .version = "0.1.0",
    .minimum_zig_version = "0.15.0",
    .dependencies = .{},
}

Fetching Dependencies

zig fetch --save https://github.com/foo/bar/archive/refs/tags/v1.0.0.tar.gz
# adds the dependency with its hash to build.zig.zon
Thinking in Zig: your build script is code — reviewable, testable, typed. There is no parallel macro language with its own bugs; the same discipline you apply to source applies to the build, and the caching layer just makes it fast.

Common Pitfalls

Trusting the Cache Directory

.zig-cache and zig-out/ are regenerated. Never commit them, and never ship files from cache — install via the declared steps so dependencies are explicit.

Wrong Root Module Path

A wrong root_source_file path surfaces as a confusing import error. Point it at the file with the pub fn main you intend to run, and keep one module per artifact for clarity.

Next: C Interop — importing the C ecosystem.