Every rung pays ~14s to activate envkernel, and that dwarfs the compile
scope: generic · severity: finding · confidence: proven · subsystem: build
CORRECTED 2026-08-29, same evening. The 14.3 s figure below is a COLD reading. envkernel activation is 0.80 s whenever
chroot_native/tmp/envkernel/<toolchain>_setup_doneexists, which is normally; the 14 s is that flag being absent and envkernel re-running its thirty-packageapk add. The conclusion drawn here – that reusing the activation is the next big win – is therefore wrong, and the bind does not survive a build to be reused anyway. The corrected numbers, and what IS worth cutting, are in envkernel-activation-is-cheap-once-the-chroot-is-warm and the-auto-preview-builds-a-package-nobody-reads. The rest of this note – that the compile is not the bottleneck, and that ccache cannot help an incremental build – still holds.
The question — a bring-up session spends its life waiting on porthole build. Where does the time actually go?
The answer — not where it looks. Measured, per invocation:
| step | cost |
|---|---|
source helpers/envkernel.sh |
14.3 s |
no-op make (tree fully built) |
5.4 s |
pmbootstrap -q chroot -- true |
0.6 s cold, ~0.9 s warm |
tkclean (nothing stacked) |
0.004 s |
End to end, porthole build auto is 44 s with nothing to rebuild and 54 s
rebuilding a single module. So compiling one module costs about 10 s and
getting ready to compile costs about 40 s.
What this rules out. The chroot init is not the fixed cost – it is under a
second. And ccache cannot help either number: .output lives in the tree on
the host and survives chroot recreation, so an incremental build never repeats
a compilation, and repeating compilations is the only thing ccache accelerates.
See envkernel-disables-ccache.
The obvious optimisation does not work, and this is the useful part.
envkernel’s 14.3 s is spent binding the tree into the chroot, so reusing an
existing bind should skip it – and make driven from a cached copy of
envkernel’s own alias measured 5.2 s against 5.4 s, i.e. identical. But the
bind does not survive a completed porthole build:
ph-build.shcallstkcleanat the start of every_ph_make, which deliberately unstacks the bind, because stacking them makes pmbootstrap abort on the shadowed.output/Makefileovermount.- Checking for it afterwards from the host is meaningless anyway: the bind
lives in pmbootstrap’s mount namespace, so the host path
<workdir>/chroot_native/mnt/linuxshows nothing even while the chroot sees it. The only honest check ispmbootstrap -q chroot -- test -f /mnt/linux/Makefile, which costs 0.85 s.
An attempt to cache and reuse was written, measured, and reverted: with
tkclean tearing the bind down at the start of each build, the fast path can
never fire, and a dead branch in a device-critical build script is worse than
none. Making it fire means restructuring how the mount is managed so that binds
do not stack – which is a real change to that script, not a shortcut.
One more trap found on the way, worth its own line: .output is created by the
build INSIDE the chroot and is owned by the chroot’s build uid, so the host
user cannot write into it. A cache file placed there fails silently if the
write error is swallowed.
So the way to make a build faster today is to run the rung the change
actually needs – porthole build measures and picks, and one module is ~40 s
against kernel’s ~10 min. The compile is not the problem.
PORTHOLE_LAX_BUILD=1 was recommended here and should not have been: measured
the same day, it saves nothing (14.96 / 15.34 / 15.25 / 14.68 s interleaved on
the kernel package) while still accepting stale build state. See
lax-build-buys-nothing-measurable.
