Exploring the zig build system
Friday, 5 Feb 2021
Zig’s build system (aka Make for zig) is written in … zig. That may seem a bit odd for a compiled language (how does that even work) but it yields a few benefits:
- there’s only 1 thing to learn
- no macros or preprocessor required
- as the dependency graph is directly known by the compiler, it should be able to do all sorts of clever optimisations, and be screamingly fast
- you can do pretty much anything
Getting Started
We’ll start with a simple program that reads a file, and prints the
contents to stdout, and call it kat
- much of this is taken from the
ziglearn chapter 2 page.
zig provides generators via zig init-exe
- let’s see what it spits
out.
// src/main.zig
const std = @import("std");
pub fn main() anyerror!void {
std.log.info("All your codebase are belong to us.", .{});
}
main.zig
looks pretty similar to what we did above for Hello World,
apart from the anyerror!void
thing.
// build.zig
const Builder = @import("std").build.Builder;
pub fn build(b: *Builder) void {
// Standard target options allows the person running `zig build` to choose
// what target to build for. Here we do not override the defaults, which
// means any target is allowed, and the default is native. Other options
// for restricting supported target set are available.
const target = b.standardTargetOptions(.{});
// Standard release options allow the person running `zig build` to select
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall.
const mode = b.standardReleaseOptions();
const exe = b.addExecutable("kat", "src/main.zig");
exe.setTarget(target);
exe.setBuildMode(mode);
exe.install();
const run_cmd = exe.run();
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);
}
This looks like a bunch of boilerplate, adding an executable target, and a run step, with some dependencies between them. Maybe we shall just run this, aye?
$ zig run src/main.zig
info: All your codebase are belong to us.
$ l
total 10
-rw-r--r-- 1 dch staff 976B Feb 12 14:32 build.zig
drwxr-xr-x 2 dch staff 3B Feb 12 14:32 src/
$ zig build --verbose-cimport --verbose-cc
Code Generation [1965/2246] std.fmt.formatInt...
$ l
total 11
-rw-r--r-- 1 dch staff 976B Feb 12 14:32 build.zig
drwxr-xr-x 2 dch staff 3B Feb 12 14:32 src/
drwxr-xr-x 5 dch staff 5B Feb 12 14:42 zig-cache/
$ ./zig-cache/bin/kat
info: All your codebase are belong to us.
So far, so good. After the build process, we have a runnable target,
defined in the build.zig
file, and we can also build an executable
directly, and run it, using the info defined in the build file too.