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 workfor multi-module repos. - You ship a Python application → switch to
uvif 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.