deb: share the apt download cache across builds

With the chroot now per-run scratch, every build re-downloaded the
packages its dependency install needed — the very cost the chroot reuse
used to hide. Every chroot's /var/cache/apt/archives is now a bind
mount over a shared cache at /var/tmp/pkh/apt-cache/<series>-<arch>/
(the chroot's architecture, like the tarball cache), which one run's
installs fill and the next runs reuse: apt keeps downloaded packages
through a dropped conf (APT::Keep-Downloaded-Packages), and an
apt-get autoclean at the end of each run drops the debs the archive no
longer serves. pkh prune removes caches untouched for a week.

The run holds a host-side flock on the cache for its whole lifetime:
apt's own archives lock is acquired inside the chroot's pid namespace,
where a concurrent build's apt shows the holder as "process 0" — an
invisible process it refuses to wait for — so pkh serializes the
traffic itself; concurrent builds of the same target wait for each
other. Any bind-mount failure falls back to the chroot's own cache
with a warning (correct, just slower), and the temp-chroot teardown now
unmounts everything under the chroot before rm -rf, so the removal can
never reach through the cache bind mount and delete the shared cache
itself.
This commit is contained in:
2026-09-29 14:22:49 +02:00
parent dd2634abe3
commit 0e1e37b2e6
4 changed files with 363 additions and 47 deletions
+17
View File
@@ -410,6 +410,23 @@ pub async fn build(
s.record_phase("binary", None);
}
// Trim the shared download cache before the chroot goes away: drop
// the .debs apt can no longer download (removed from the archive),
// keep the rest for the next run of this target. Best-effort — a
// failure costs cache space, never the build.
let status = cap(
ctx.command("apt-get")
.envs(env.clone())
.arg("-y")
.arg("autoclean"),
&sink,
)
.status();
match status {
Ok(status) if status.success() => {}
outcome => log::warn!("apt-get autoclean failed ({outcome:?})"),
}
Ok(artifacts)
}