Architecture¶
buckroot is a code generator plus a set of Buck2 rules. It reads what Buildroot already
knows about a configured project, turns each package into a Buck2 target, and lets Buck2
schedule, cache and distribute them. Buildroot's own .mk logic is not rewritten
(for the wrapped variant it is not even bypassed): it runs inside Buck2 actions.
From project.json to a root filesystem¶
flowchart TD
P[project.json] --> S[br2 setup: buck2 + Buildroot tree]
S --> X["br2 extract: make show-info"]
X --> M[golden/model.json]
M --> R[br2 render]
R --> B["BUCK files: one br2_package per package,<br/>br2_config, br2_owners, br2_rootfs"]
B --> K[buck2 build //:rootfs]
K --> C[br2_config: defconfig to .config]
C --> O[br2_owners: which symbol belongs to which directory]
O --> SL[per-package config slice]
SL --> PK["br2_package: make PKG in a view"]
PK --> RF["br2_rootfs: target-finalize + image"]
G[br2 golden: plain make] -.compared with.-> RF
br2 extractrunsmake <defconfig>andmake show-infoin a scratch output directory - no build - and stores the package graph ingolden/model.json: each package's version, direct dependencies, source, hashes, patches, and the stamp directory Buildroot keeps its state in. Only the graph is extracted; build commands and hooks stay in Buildroot.br2 renderturns the model intoBUCKfiles. Every package becomes abr2_packagetarget in the directory where its Buildroot package lives, so the Buck2 target graph mirrors Buildroot's own layout.buck2 build //:rootfsbuilds those targets in dependency order, in parallel.br2_configproduces the.config;br2_ownersscans everyConfig.in*to record which directory declares which symbol; each package gets a slice of the config.- A package action seeds a scratch Buildroot output directory with its config slice
and its dependencies' outputs, then runs Buildroot's own
make <pkg>in a mount namespace that shows it only what it declared. Its output is one tarball: the package'sper-package/<pkg>/{host,target}tree, plus stamps. br2_rootfsunpacks every package's artifact, merges the host and target trees the way Buildroot'shost-finalizeandtarget-finalizedo, and runs the filesystem image step.
The pieces¶
| Piece | Where | What it does |
|---|---|---|
| Rules | toolkit/br2/rules.bzl |
br2_package, br2_rootfs, br2_config, br2_owners, the br2_native_* rules and their providers |
| Actions | toolkit/br2/pkg_action.py |
what an action executes: seed, run make in the namespace, pack the delta |
| Namespace | toolkit/scripts/br2-ns.sh |
fixed-path private mount namespace with allow-list views |
| Slicing | toolkit/br2/slicing.py |
config ownership and the per-package slice |
| Generator | toolkit/scripts/br2buck.py |
extract, fetch, render |
| CLI | toolkit/scripts/br2 |
setup, extract, golden, build, dev, matrix |
| Platform | toolkit/platforms/, toolkit/br2/platform.bzl |
the execution platform with the remote switches |
| Backend | toolkit/infra/buildbarn/ |
cache, scheduler, worker, runner image, portal |
Why a package rebuilds when it does¶
A package action's key is a hash of its command, its declared inputs and its declared environment. Its declared inputs are:
- the infrastructure of the Buildroot tree (everything except other packages' directories),
- the package directories of the package and its dependency closure,
- the config slice, and
- the outputs of its dependencies.
Adding a package therefore changes: the new package's targets, the rootfs, and a few tiny slice actions - not any existing package. Flipping a symbol owned by busybox rebuilds busybox and the rootfs. Flipping a global symbol (optimization level, architecture) rebuilds everything, because global symbols go to every slice - by design, see Views and config slices.
Measured on a 30-package image with four cores: cold build 5 min 16 s; add an upstream package 43 s (that package plus the rootfs); add an external package 40 s; flip a busybox-only symbol 81 s (busybox and the rootfs); flip a global symbol 5 min 2 s (everything).
Where the results live¶
Buck2 decides whether an action reruns from its key, and stores the outputs in its
content-addressed store; with a remote cache configured it also uploads them to a
Buildbarn cache, keyed the same way. Buildroot's own
output tree - the multi-gigabyte scratch space - lives in ~/.cache/br2/<project> and
in per-action scratch directories, never inside the project.