Skip to content

Build systems & toolchains

Why it matters

The build system is the part of a language you fight every day until you stop fighting it. Each path's build tool encodes the language's bets: about reproducibility (Cargo, Bazel, Nix), about familiarity (Maven, pip), about speed (Gradle's daemon, Go's go build cache), about dependencies (lockfiles vs. transitive resolution vs. vendored). Pick any path and the build's design tells you what its community values.

This page is a side-by-side of how each path's build works and where the leverage lives.


The lens, per path

Rust - Cargo

Month 1 - Foundations, week on Cargo. Cargo.toml manifest, Cargo.lock lockfile, cargo build / test / bench / check / doc / publish. Workspaces for multi-crate projects.

What's unique here: the strongest "batteries included" build of any mainstream language. Cargo is the package manager, the test runner, the documentation generator, the benchmark harness, and the release publisher in one binary. No separate pip+pytest+build+twine zoo.

The trap

Slow CI builds. Cargo recompiles a lot more than people expect. Use sccache, set up cargo chef for Docker-layer caching, and consider cranelift as a debug-build backend.

Java - Maven and Gradle

Month 1 - Language & Toolchain, week 2.

  • Maven - XML POM, lifecycle phases (compile → test → package → install → deploy), plugin model, central repository. Predictable, verbose, hard to make bad.
  • Gradle - Groovy or Kotlin DSL, task graph, configuration cache, build cache. Faster, more flexible, easier to make subtly wrong.

What's unique here: the dependency-management depth. BOMs (dependencyManagement), version catalogs (libs.versions.toml), dependencyManagement inheritance, transitive resolution that you can audit with mvn dependency:tree or gradle dependencies.

The trap

Gradle's configuration cache and build cache are off-by-default in older projects and produce dramatic speedups when enabled. Audit any Gradle project for org.gradle.configuration-cache=true.

Go - go build

Month 1 - Runtime Foundations, toolchain week. go.mod for module + dependencies (Go modules since 1.11). go build / test / vet / fmt / mod tidy all in one binary.

What's unique here: minimal-ceremony. No DSL, no Turing-complete config. The build's "knobs" are essentially -tags, -ldflags, and -trimpath. The Go team's design ethic - "if it isn't in the language, it isn't your problem" - applies to the build too.

The trap

cgo makes builds 10× slower and breaks CGO_ENABLED=0 static-binary expectations. Audit any Go project that imports C and decide whether the import is paying its way.

Python - pip, uv, Poetry, hatch, build, twine

Month 1 - Foundations, packaging week. The ecosystem is layered:

  • pip - the installer. Resolves dependencies, downloads wheels, installs.
  • uv (2024) - the modern Rust-implemented combined installer + resolver + virtualenv manager. ~10–100× faster than pip. The 2026 default for new projects.
  • pyproject.toml - the modern manifest (PEP 621). Tool config lives here too (ruff, mypy, pytest).
  • build + twine - produce wheels/sdists, upload to PyPI.
  • Poetry / Hatch / PDM - workflow tools wrapping the above with lockfiles + virtualenv management.

What's unique here: the longest packaging legacy. Every modern tool is an attempt to fix what came before. uv is the cleanest current answer; Poetry is the most-deployed.

The trap

Building a wheel for a package with C extensions (NumPy, PyTorch, anything with setup.py invoking a C compiler) needs cibuildwheel or auditwheel to produce manylinux-compatible binaries. Without it, you ship wheels that only work on your laptop.

Linux kernel - kbuild + Kconfig

Month 1 - Kernel Foundations, kbuild week. make defconfig / menuconfig / oldconfig to produce .config. Make-based build with Kconfig as the configuration language. Cross-compilation via ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-.

What's unique here: the configuration-as-build-input model. The kernel is not one binary - it's millions of possible binaries parameterized by .config. Every Kconfig symbol is a feature toggle.

The trap

make oldconfig after a kernel upgrade asks about every new symbol - defaults are usually right but read each one. make olddefconfig accepts every default silently (faster but you miss new features).

Containers - Dockerfile, BuildKit, Buildah, Buildpacks

Month 2 - Filesystems & Builds. docker build is the famous one; underneath, BuildKit is the modern frontend (graph-based, parallel, cache-aware). Alternatives: Buildah (daemonless), Kaniko (in-cluster), Buildpacks (opinionated, layered, no Dockerfile).

What's unique here: the layer cache as the dominant performance lever. Order layers so the most-changing operations come last. The single most-impactful container-build optimization.

The trap

COPY . . before RUN pip install invalidates the dependency cache every time any source file changes. Always copy dep manifests first, install, then copy source.

AI Systems - building PyTorch, JAX, vLLM from source

Month 3 - Framework Internals. Building PyTorch from source is its own discipline - CUDA versions, NCCL versions, glibc compatibility, Triton versions. Most teams use pre-built wheels until they need to patch the framework.

What's unique here: wheel compatibility is a multidimensional matrix (Python version × CUDA version × CPU arch × glibc), and the maintainers ship most combinations. When you need to patch, expect a 2-hour build.


The contrasts that teach

Aspect Cargo Maven/Gradle go build pip/uv kbuild Docker/BuildKit
Manifest Cargo.toml pom.xml / build.gradle go.mod pyproject.toml Kconfig + .config Dockerfile
Lockfile Cargo.lock optional (gradle.lockfile) go.sum uv.lock / poetry.lock .config is the lock (no - image digest IS the lock)
Build cache local + sccache Gradle build cache yes (auto, in $GOCACHE) wheel cache ccache BuildKit cache mounts
Reproducible builds strong (lockfile + sysroot) strong (BOM + checksums) strong (-trimpath) weak (timestamps in wheels) strong (KBUILD_BUILD_TIMESTAMP=0) strong (digest pinning)
Cross-compile excellent (--target) platform-portable (JVM) excellent (GOOS GOARCH) painful (cibuildwheel) excellent (ARCH=) excellent (--platform)
Daemonless / single-shot single-shot Gradle has daemon single-shot single-shot single-shot Docker daemon vs Buildah

The most clarifying read across these: Cargo + go build + uv. Three modern takes on "one tool, batteries included, minimal ceremony." The contrast with Maven/pip is generational; the contrast with kbuild is domain-driven.


What to read first

  • You ship a Rust binary → Rust Month 1's toolchain week, then Cargo's [profile.release] and link-time-optimization docs.
  • You ship a JVM service → Java Month 1's build week. Pick one of Maven or Gradle and commit; do not write a polyglot build.
  • You ship a Go service → Go Month 1's toolchain week. Then read about go work for multi-module repos.
  • You ship a Python application → switch to uv if you haven't. The speed delta makes everything else easier.
  • You ship anything in a container → Containers Month 2. Master BuildKit cache mounts.
  • You build the kernel → Linux Month 1's kbuild week.