When you initialize project scaffold via zig init-exe, it will generate build.zig with detailed comments to help you understand what it does. The most frequent used commands are:

  • zig build, default step install is invoked
  • zig build test, test step is invoked

Here we introduce the most important concept in Zig’s build system: step. A step describe one task, such as compile binary, run the test.

Step.zig defines MakeFn as step’s main interface:

pub const MakeFn = *const fn (self: *Step, prog_node: *std.Progress.Node) anyerror!void;

All other concrete steps implements it via composition with @fieldParentPtr. If readers don’t know this idioms, refer to this post. The following are most common used steps:

  • CompileStep, used to compile binary/static library/static library
  • RunStep, used to execute one program
  • InstallArtifactStep, used to copy build artifacts to zig-out directory

std.Build provides lots of convenient APIs to define steps and their relation, such as:

  • addExecutable define a CompileStep for application binary
  • addTest define a CompileStep for test binary
  • addRunArtifact define a RunStep for one CompileStep

Steps’ relation is built using Step.dependOn function, and their relation construct a directed acyclic graph(DAG), which is used to drive the whole build process.

Steps DAG

Steps DAG

Figure above show a simple DAG of steps, steps at the top are special, and they can be invoked by zig build ${topLevelStep}, Build.step function creates such top level steps.

After explanation above, readers should have a deeper understand what build.zig does.

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});
    const exe = b.addExecutable(.{
        .name = "demo",
        .root_source_file = .{ .path = "src/main.zig" },
        .target = target,
        .optimize = optimize,
    });
    b.installArtifact(exe);
    const run_cmd = b.addRunArtifact(exe);
    run_cmd.step.dependOn(b.getInstallStep());
    if (b.args) |args| {
        run_cmd.addArgs(args);
    }
    const run_step = b.step("run", "Run the app");
    run_step.dependOn(&run_cmd.step);
    const unit_tests = b.addTest(.{
        .root_source_file = .{ .path = "src/main.zig" },
        .target = target,
        .optimize = optimize,
    });
    const run_unit_tests = b.addRunArtifact(unit_tests);
    const test_step = b.step("test", "Run unit tests");
    test_step.dependOn(&run_unit_tests.step);
}

Read More