Buildroot vs. Buck2¶
Why moving a Buildroot project to Buck2 is not a syntax translation, and how each Buildroot concept maps onto Buck2. This is the background to the design decisions; Architecture is how buckroot puts it together.
Buildroot's model¶
Makefile-driven, package-centric, operationally coarse. package/*/*.mk files are the
build logic per package; the steps run roughly source -> depends -> extract -> patch ->
configure -> build -> install. output/host holds host tools plus the target sysroot;
output/staging is a compatibility symlink to that sysroot; output/target is the
near-final root filesystem (not a deployable chroot); output/images holds the
deliverables. Patches come from package-local directories or BR2_GLOBAL_PATCH_DIR.
- Top-level parallel build is still an experimental mode with sharp edges.
- Incremental rebuilds are package-scoped and coarse: a dependency's configuration change does not reliably rebuild its dependents.
- Reproducibility leans on hashes, pinned sources and caches (
DL_DIR, ccache), but the model underneath is still "mutable build trees plus convention".
Buck2's model¶
Graph-first. BUCK files declare targets; Buck2 analyzes them into a dependency graph,
turns them into actions, and executes only what is needed, locally or remotely. A rule
transforms declared inputs into declared outputs, package boundaries are explicit,
the graph is acyclic, and toolchains and platforms are explicit rather than ambient host
state. Configuration is part of a configured target's identity, so variants coexist by
construction. The design leans on hermeticity, action digests, content-addressed storage
and action-cache hits; remote execution is first-class.
The core mismatch¶
Buildroot lets a package rely, implicitly, on whatever earlier steps left in host/,
staging/ or the target tree. Buck2 makes every dependency explicit - good for
correctness, but it exposes a great deal of hidden coupling that Buildroot tolerated
silently. For each package the migration question is: does this become a target, a rule,
an action, a toolchain, a configuration, or a generated artifact?
Concept mapping¶
| Buildroot | Buck2 | The problem | Solution shape |
|---|---|---|---|
.mk package file |
BUCK target + rule |
metadata, steps, side effects and conditionals in one file | generate one target per package; keep the .mk for the wrapped variant |
Config.in |
.buckconfig, platforms, constraints |
menu-driven global config vs. graph-scoped, variant-aware config | config slices per package |
*_DEPENDENCIES |
deps |
often means "must exist before I run", not "is an input" | dependencies become artifacts a package's action consumes |
| source -> patch -> configure -> build -> install | actions | Buck2 does not want a long imperative pipeline as one node | one action per package (wrapped), or one per step (native) |
DL_DIR |
fetched artifacts with hashes | filesystem-based, package-level caching | vendored sources with recorded digests |
| package patches | patch action / pre-patched source | search-path and ordering conventions | patch directories are declared inputs; verified by br2 preflight |
host-* packages |
exec tools / toolchain deps | host tools and target artifacts share a tree but differ in role | separate host and target trees in each package's artifact |
output/host + sysroot |
toolchain outputs | a shared mutable directory | per-package directories, merged only in the rootfs action |
output/staging |
none | a compatibility alias | dropped |
output/target |
rootfs action inputs | a partially finalized filesystem | explicit br2_rootfs action |
output/images |
final artifacts | maps cleanly | top-level deliverable |
| parallel build | native graph parallelism | Buildroot's is partial | let the graph drive it; weights for heavy packages |
| ccache, download cache | action cache + CAS | best effort | deterministic action keys, content addressing |
| hidden coupling through staging | explicit input edges | works because something else happened earlier | enforced views: fail loudly |
Semantic differences that matter¶
- Package-oriented vs. action-oriented. One Buildroot package can become several Buck2 targets or actions.
- Dependency declarations do not guarantee rebuilds in Buildroot. In Buck2, if an action's inputs change, its key changes and it reruns. A distributed Buildroot cache needs exactly this discipline; Buck2 puts it in the graph instead of an external policy.
- Shared directories vs. artifacts. Buck2 does not care what is currently in a directory; the declared inputs and outputs are the contract.
- Convention-based vs. modeled patching. The question is where patch application lives in the graph and what its identity is.
- Global vs. variant-driven configuration. Buildroot asks "what is the system configuration"; Buck2 asks "what configuration produces this target variant".
Migration problems and patterns¶
- Dependency modelling - separate build-time tools from runtime libraries, generated inputs and patch-time dependencies; make hidden coupling visible.
- Package conversion - thousands of packages cannot be converted one by one; classify
them first (trivial, generator-heavy, patch-heavy, host-only, custom build logic,
ambient-state-dependent) and migrate by class. buckroot's
wrappedvariant is the "convert everything at once, refine later" answer. - External sources - explicit hashed artifacts; no unpinned mutable VCS state.
- Cross-compilation - separate execution tools from target toolchains.
- Reproducibility and caching - every action deterministic; any ambient filesystem dependency is a bug. Buildroot bakes absolute build paths into the rootfs, hence the fixed-path namespace.
- Remote-execution readiness - hermetic, serializable actions; eliminate local paths and undeclared host tools.
- Validation - equivalence at several levels: package artifact, rootfs contents, image hashes, boot behaviour. See Verification.
- Rollout - both systems in parallel during the transition; the golden build stays the reference.
- Debugging - be able to inspect the exact command, inputs and outputs of every action. See Debugging.