~/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
--profilereport, 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.