Wrapped and native packages¶
buckroot builds a project in one of two variants. They produce the same package artifacts and the same root filesystem, and you can mix them per package.
| wrapped | native | |
|---|---|---|
| A package's action | Buildroot's own make <pkg> in a view |
Buck2 actions that do the work: untar, compile, link, install |
| Works for | every Buildroot package | packages with a rule in toolkit/br2/native/ or the project's native/ |
| Early cutoff | package granularity | per step: a comment-only change in a C file skips the link and everything after |
| Runs remotely | needs the pinned tool baseline in the worker image | nothing ambient beyond the declared toolchain |
| Cost of a rebuild of one small package | a whole make in a namespace plus the toolchain overlay (about 40 s) |
the compile alone (under a second when nothing changed) |
Switch per package with the native list in project.json; the
generated targets are the same and downstream packages do not change.
Wrapped: Buildroot does the work¶
The action seeds a scratch output tree with the package's config slice and its
dependencies' artifacts, runs make <pkg> in the view, and packs whatever changed -
per-package/<pkg>/{host,target}, the stamps, the .files-list* files - as the package's
artifact. Buck2 owns ordering, parallelism and invalidation; Buildroot owns everything
about how a package is built. That is what makes this variant work for projects
with Mesa, a vendor kernel and Rust host tools without modelling any of it.
The host tools are not in the key¶
The action key hashes the command, the declared inputs and the declared environment - not
the host's gcc, perl, rsync or make. Two machines with different tools can
produce different bytes for the same key, so a cache hit is unsound rather than
loudly failing. For remote execution the answer is to pin the tools: the worker
image (toolkit/infra/buildbarn/runner/Dockerfile) is the tool baseline, and
the action's PATH is fixed (Buck2 sends an empty environment; the runner resolves
executables through the command's own PATH).
Native: Buck2 does the work¶
A native package stands in for a Buildroot package: it has the same label and produces
the same artifact. Because wrapped packages that depend on a native one still need
Buildroot's .mk for it (make must know host-fakedate as a target), a native package
reports the Buildroot package directory it replaces as its own.
The hello package shows the shape: the toolchain tarball is unpacked as a Buck2 artifact
(br2_untar), hello.c is compiled and linked by two separate actions with the real
cross compiler (br2_cc_program), and br2_native_package writes the artifact the
rootfs action expects.
The flags are the contract¶
Buildroot's toolchain wrapper silently adds --sysroot, -mabi, -mcpu,
-fstack-protector-strong, -fPIE -pie, -Wl,-z,relro,now and -Wl,--build-id=none to
every compiler call (visible with BR2_DEBUG_WRAPPER=1). Reproducing exactly those flags
on the unwrapped compiler gives a byte-identical hello.
The artifact format is an interface¶
host-finalizersyncs every package'sper-package/<pkg>/host/, so a native artifact without that (empty) directory fails the whole rootfs with anrsyncerror 23, far from the cause.- Each package kind needs its own stamps for
maketo consider it done (host:.stamp_host_installed; sysroot headers:.stamp_staging_installed; local sites:.stamp_rsynced). rsync -uskips destination files that are newer. That was an accident of the mtime ordering of a sequential build (the toolchain'setc/passwdnever overwrote the skeleton's); native artifacts have fixed mtimes, so the ordering is reproduced by dependency..files-list-host.txtcovers only what an install step adds;.files-list-staging.txtcovers the merged sysroot including dependencies. Only the former is exactly reproducible per package.- Nothing may hard-code the toolchain. A sysroot lives at
host/<tuple>/sysroot, and the tuple depends on the configuration:aarch64-buildroot-linux-gnufor an internal glibc toolchain,aarch64-buildroot-linux-muslfor a Bootlin musl one.br2 extractasks Buildroot forGNU_TARGET_NAMEandbr2 renderwrites it tobr2/generated/target.bzl; native rules loadTARGET_TUPLEfrom there. The first version hard-coded the musl tuple it was developed on. On a glibc project the native skeleton then created itslib64 -> libsymlinks in a sysroot nobody used, glibc installed a realusr/lib64directory, andhost-gcc-finalfailed withld: cannot find crti.oabout fifty minutes into the build. (The sysroot's directory name is data as well: Bottlerocket's SDK calls itsys-root, recorded asSTAGING_SUBDIR, ADR-0012.) The symptom was far from the cause, and a first equivalence check missed it because both sides were the wrapped artifact (see below).
Config-dependent packages declare their config¶
A native package declares the symbols it reads (config = [...]); an action extracts only
their values into a small JSON that the recipe reads. skeleton-init-common reads
BR2_ROOTFS_MERGED_USR, BR2_ARCH_IS_64 and BR2_SYSTEM_DEFAULT_PATH; changing any other
symbol leaves the package's key untouched. That is a stricter contract than the heuristic
slice wrapped packages get.
A recipe raises on any configuration it does not implement (glibc instead of musl, Fortran, host gdb...) instead of quietly producing a wrong artifact.
Equivalence is checked, not assumed¶
scripts/compare-pkg.py wrapped.tar native.tar
compares files (path, mode, sha256), symlinks, stamps and the .files-list* contents.
Building the //buildroot-src/... label of a package always gives the wrapped artifact:
native takes effect through the labels that dependents resolve (br2_dep), so build the
native artifact as //br2/native/<pkg> when comparing.
The two toolchain-sysroot files Buildroot rewrites for every package that depends on the
toolchain (ppd-fixup-paths) are ignored. See Verification.
What ships as native¶
The generic ones (toolkit/br2/native/): host-skeleton, host-fakedate, skeleton,
skeleton-init-sysv, skeleton-init-common, initscripts, urandom-scripts,
musl-compat-headers, and the external-toolchain trio toolchain-external-bootlin,
toolchain-external, toolchain. A project adds its own under native/<pkg>/BUCK.
helloworld builds hello, its whole dependency closure, and two init-script packages this way (12 packages). The
external-toolchain package is the hardest: it unpacks the tarball, compiles Buildroot's
compiler wrapper with the host compiler (a byte-identical result once __FILE__ is
mapped), installs the sysroot, rewrites the libtool .la files, generates gdbinit and
copies the runtime libraries into the target tree following symlink chains. Every one of
those steps is a Buildroot make macro reimplemented in a small recipe.py.
Still wrapped in every project: anything that compiles a large upstream tarball. Native rules for autotools and make projects - not just install-only recipes - are the next frontier; see Status and roadmap.