~/kettlebuild/uses

Uses

The tools this practice relies on from day to day. None of them is sponsored, and none of them is the answer to every build problem; this is simply what we reach for first, and why.

Build systems and task runners

GNU Make
Still the glue for small projects and for wrapping other tools. It decides what is stale by modification time, which is fine on one machine and a poor basis for a shared cache.
Ninja
Rarely written by hand. We meet it as the fast backend that CMake and Meson generate, and it is usually not the slow part.
Bazel
For large, multi-language codebases that need hermetic, fine-grained builds and remote caching. Powerful and a real investment: rules have to be written and maintained, and the team has to want it.
Gradle
For JVM projects. Its build cache and configuration cache make a large difference, as long as tasks declare their inputs and outputs properly.
Nx and Turborepo
Task graphs and caching for JavaScript and TypeScript monorepos. Both are quick to adopt on top of an existing package manager workspace, which is often the deciding factor.

Caching

ccache
A compiler cache for C and C++. Little effort to set up and often an easy win for both local and CI builds.
sccache
The same idea with Rust support and shared storage backends, so CI machines can reuse each other's results.
BuildKit cache mounts and cache export
For container builds; the details are in Smaller Docker images, warmer layer caches.

Containers

Docker with BuildKit (buildx)
Our default for building images. Cache mounts, secret mounts and parallel stages are most of the reason.
Podman
When a team wants rootless, daemonless containers. It builds from the same Dockerfiles.
dive
For looking inside image layers before anyone argues about image size.

Reproducibility

diffoscope
Recursively unpacks and compares two artifacts and shows exactly where they differ. The single most useful tool for this kind of work.
reprotest
Builds twice under deliberately varied conditions to shake out non-determinism before users find it.
strip-nondeterminism
Normalises timestamps and ordering in zip, JAR and a few other formats when the tool that wrote them can't be fixed.

Measuring

hyperfine
Command-line benchmarking with warm-up runs and statistics, so "it feels faster" becomes a number with a spread.
strace and perf
For finding out, on Linux, what a build step actually reads and where its time actually goes.
Build profiles
Bazel's JSON trace profile, Gradle's --profile report, and the per-step timings every CI system exposes through its API.

Everyday

Git, ripgrep, jq, ShellCheck
Version control, fast search, JSON wrangling, and a linter for the shell scripts that every build accumulates.
Neovim and VS Code
Depending on the day and on what the team we're working with already uses.
CI systems
Whatever the team already runs. In practice that is mostly GitHub Actions and GitLab CI, sometimes Jenkins or Buildkite. The ideas in the articles carry over between them.

Hardware

A Linux workstation with plenty of cores, memory and fast NVMe storage, because build work is mostly waiting on compilers and disks, and a laptop for everything else. The articles describe performance in relative terms on purpose: absolute numbers from one machine rarely transfer to yours.

This site

Hand-written HTML and a single stylesheet, served as static files. No JavaScript, no web fonts, no analytics and no build step. A build-engineering notebook without a build is either a principled choice or a joke at our own expense; possibly both.