Skip to content

Build2 publishers3 min readPublished

Zig 0.17 evaluates build.zig in a separate process from the one that runs the build

Zig 0.17.0 splits zig build into two executables, a configurer that evaluates build.zig and a maker that runs the build graph, according to a dev.to post. Script edits no longer rebuild the maker, while skipping the configure step as well requires build scripts that declare their host dependencies.

The Engineer · Build desk

Illustration accompanying Zig 0.17 evaluates build.zig in a separate process from the one that runs the build
Generated illustration

What happened

  • Probing the host at configure time, such as checking whether scdoc is installed, now has a name: configure cache poisoning.
  • Four new declaration functions and a lazy findProgramLazy lookup let scripts declare file inputs or push tool searches to make time.
  • Passing --listen=- starts a Build Server Protocol on stdin/stdout that lets IDEs inspect the graph and request specific steps.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint A build.zig that probes the host at configure time keeps the configurer running on every invocation, so only scripts written to be pure get the skip.
  • decision Maintainers porting large build scripts have to choose, probe by probe, between configure-time findProgram and findProgramLazy or a declared dependency.
  • capability Editors and other tools can read a project's build graph statically from the serialized configuration, without parsing or running build.zig themselves.

Before 0.17, build.zig ran inside the build system's own process, so editing the script meant rebuilding the build system [2]. Every invocation after an edit started from scratch: recompile the build runner, re-evaluate the script, then execute the graph [3]. According to the dev.to post that walks through the release, that cost is a rounding error for small projects and compounds for large projects with complex build scripts [4]. The post does not include timings.

An edit in 0.17 still costs something. build.zig is Zig source that runs at configure time [5]. The configurer is the process that evaluates it [1], so a changed script still goes through the configurer [1]. What an edit no longer touches is the maker, which is built once and left unmodified when build.zig changes [7]. The maker is also built with optimizations enabled. The post says that matters more now that --watch and --fuzz modes exist [8].

For the large-project claim to transfer to a given tree, two things have to be true. The maker rebuild has to have been a real share of what an edit cost before. And the script has to keep the configure cache clean between runs.

The second condition is the harder one. The configurer can be skipped on identical inputs, but only while the cache stays pure [10]. Some CLI flags bypass configuration entirely [9]. A poisoned cache means the configure logic had side effects or did something the cache could not track [11]. The post's example is a build.zig that checks whether scdoc is installed to decide whether to build man pages. The result depends on host state the cache cannot see, so the cache is poisoned [11]. That is a lot of cache to lose over a man page. Side effects at make time are expected: a Run step that prints output does not poison anything [12].

Porting a large script means finding each configure-time probe and moving it to one of two forms. Four declaration functions cover file contents, file metadata (size, inode, mtime), directory contents and directory metadata, through calls such as std.Build.dependOnFileContents and std.Build.dependOnDirectoryContents [13]. For tool lookups, findProgram still exists for cases that need the answer at configure time. The new findProgramLazy returns a LazyPath that defers the search to make time [14].

I think the serialization boundary is the best engineering in the release. The configurer writes a compact binary serialization of the build graph, and the maker reads it [6]. Third-party tools can consume the same format, and it is the foundation of the new Build Server Protocol [17]. One artifact gives the cache something to key on and gives tools something to read. Passing --listen=- starts the server on stdin/stdout. IDEs can then inspect the graph statically, get notified when steps start and complete, and request specific steps [15]. The post calls the protocol early. Today it exposes available steps, options and dependencies, and module names plus a multiplexed compiler server protocol for type information and refactoring are planned [16].

What to watch

  • Post-edit build timings from a large Zig codebase on the previous release and 0.17 would show whether the maker rebuild was a large share of the old cost.
  • Whether the Build Server Protocol ships the module names and multiplexed compiler server protocol the post lists as planned.
  • How many widely used build.zig files still call findProgram at configure time after porting, keeping the configurer on every run.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories