Skip to content

`zig build`

Large programs are rarely built with a single compiler command.

zig build

Large programs are rarely built with a single compiler command.

A program may contain many source files. It may need tests, examples, generated files, libraries, or platform-specific options. Typing long command lines by hand quickly becomes tedious.

Zig solves this with a build system written in Zig itself.

Most Zig projects contain a file named build.zig. The zig build command reads this file and executes the build graph described inside it.

A small project might look like this:

project/
├── build.zig
├── build.zig.zon
└── src/
    └── main.zig

The simplest useful build.zig file is short:

const std = @import("std");

pub fn build(b: *std.Build) void {
    const exe = b.addExecutable(.{
        .name = "hello",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = b.standardTargetOptions(.{}),
            .optimize = b.standardOptimizeOption(.{}),
        }),
    });

    b.installArtifact(exe);
}

Build the program:

zig build

Run the executable:

./zig-out/bin/hello

The command zig build does not directly compile source files itself. Instead, it executes the build description in build.zig.

The function:

pub fn build(b: *std.Build) void

is the entry point of the build system.

The parameter b is the build context. It provides functions for creating executables, libraries, tests, install steps, and custom commands.

This line creates an executable build step:

const exe = b.addExecutable(...)

The executable is named:

.name = "hello",

The source file is:

.root_source_file = b.path("src/main.zig"),

The compiler target comes from:

.target = b.standardTargetOptions(.{}),

This allows the user to select targets from the command line:

zig build -Dtarget=x86_64-linux

Optimization mode comes from:

.optimize = b.standardOptimizeOption(.{}),

This enables commands such as:

zig build -Doptimize=ReleaseFast

The final line:

b.installArtifact(exe);

adds an install step. By convention, installed files are written into zig-out/.

The build system is incremental. If a source file has not changed, Zig avoids rebuilding unnecessary parts of the project.

A build script is ordinary Zig code. Loops, conditions, helper functions, and compile-time computation can all be used naturally.

For example:

if (some_condition) {
    // add extra library
}

or:

for (targets) |target| {
    // build multiple executables
}

This is one of the major design choices of Zig’s tooling. There is no separate build language. The language used to build Zig programs is Zig itself.

A project often defines additional build steps. For example:

const run_cmd = b.addRunArtifact(exe);

const run_step = b.step("run", "Run the program");
run_step.dependOn(&run_cmd.step);

Now the program can be run with:

zig build run

Tests are commonly integrated the same way:

const tests = b.addTest(.{
    .root_module = b.createModule(.{
        .root_source_file = b.path("src/main.zig"),
    }),
});

and executed with:

zig build test

The build system is still evolving in Zig 0.16, but its overall direction is stable:

  • Build descriptions are code.
  • Cross compilation is built in.
  • Dependencies are explicit.
  • Build graphs are incremental.
  • The standard toolchain handles compilation, linking, testing, formatting, and packaging together.

Exercise 15-1. Create a project with build.zig and one source file.

Exercise 15-2. Add a run step.

Exercise 15-3. Build the same program for another operating system.

Exercise 15-4. Add a test step and run it with zig build test.