Exercises pkh the way it is used in anger: 63 real source packages (from hello and micro to dracut, grub2 and the kernel) run through pull -> chlog --rebuild -> build -> deb, each stage timed, logged and checked for its artifacts, with the catalog alternating between debian and ubuntu within every category. run.py keeps runs self-contained (per-package logs, progressive results.json, a markdown summary), pulls in parallel while builds run sequentially to keep pkh's shared chroot/apt/ccache caches warm, resumes interrupted runs reusing the stages that already succeeded, and marks known-broken entries as xfail with a reason. The first catalog run caught four pkh defects within an hour - the unstable/sid suite alias, the bz2 origs, the hard link extraction and the wrapper unwrapping - which is exactly what it is for.
4.6 KiB
pkh end-to-end package harness
Exercises pkh the way it is used in anger: real packages, the whole
workflow. For every package in catalog.yml it runs
pkh pull <name> --archive # sources from the Debian/Ubuntu archive
pkh chlog --rebuild # no-change rebuild entry (1.0-1 -> 1.0-1build1)
pkh build # .dsc + _source.changes next to the tree
pkh deb # binary packages in an ephemeral chroot
The catalog spans ~60 source packages: smoke-test smalls (hello,
micro, jq...), core utilities, server software, databases, desktop
stacks, toolchains and the weird corners — dracut, opensbi, grub2,
a cross u-boot, the kbuild-based kbuild, and (opt-in, hours each)
gcc-14, glibc, rustc, libreoffice, firefox, the Ubuntu linux
kernel...
Prerequisites
- A pkh binary: the harness prefers the repo build
(
target/release/pkh, thentarget/debug/pkh), falls back topkhon PATH; override with--pkh PATHor build first with--build-pkh. - The pkh runtime toolchain:
mmdebstrap,sudo(passwordless, for the unshare chroot builds),dpkg-dev,distro-infodata, and network access to the Debian and Ubuntu archives. - Python 3 only (stdlib; PyYAML is used when installed, with a built-in fallback parser for the catalog's YAML subset).
- Disk: tens of GB under
runs/for trees and artifacts, plus pkh's own caches (/var/tmp/pkh); expect a full default-tier run to take many hours — the tiers exist so you never have to.
Usage
./run.py --list # what would run, with tiers and notes
./run.py --dry-run # the exact commands, nothing executed
./run.py # smoke + standard tiers
./run.py --tier long # the multi-hour builds
./run.py --tier all # everything
./run.py --only hello,dracut # exact names
./run.py --category database # or server, desktop, boot-firmware...
./run.py --tag cross --arch arm64 # tag/arch sweeps
./run.py --stages pull # catalog sanity: can we pull everything?
./run.py --resume runs/<id>/results.json # continue an interrupted run
Useful global pass-throughs: --dist, --series, --arch, --cross,
--pocket, --ppa user/ppa (deb dependencies), --jobs N,
--no-ccache, --git (pull through the VCS instead of the archive).
Exit codes: 0 nothing unexpected failed, 1 at least one package
failed (or xpassed under --strict), 2 usage/catalog error, 130
interrupted.
Selection: tiers, categories, tags
Entries carry a tier: smoke (minutes), standard (the default,
up to ~1-2h each), long (hours: compilers, browsers, the kernel —
only run when asked). Default selection is smoke + standard. On top of
that, filters compose: --category, --tag, --only, --skip.
An entry can be marked xfail: <reason> when it is known-broken; it
then reports xfail on failure and xpass on unexpected success
(xpass fails the run only under --strict).
What a run produces
runs/<id>/
logs/<pkg>.log # full output of every stage of that package
<pkg>/<pkg>/ # the pulled source tree
<pkg>/*.dsc ... # build artifacts, next to the tree as pkh puts them
results.json # machine-readable, written progressively
results.md # the summary table
The run directory is per invocation; --resume <id>/results.json
reuses it, keeps the recorded outcomes and only reruns packages that
did not finish. Already-pulled trees are kept (pkh pull refuses
existing directories anyway — delete runs/<id>/<pkg>/ to force a
re-pull), and stages that already succeeded are reused rather than
re-run, so a resumed chlog does not bump the version twice; change
--dist/--series/--arch/--cross and everything re-runs.
Pulls run a few at a time (--pull-jobs), the build pipeline is
sequential by design: one heavy build at a time keeps pkh's shared
chroot tarball, apt and ccache caches warm, so repeat runs get
dramatically faster.
Each stage gets the package's timeout budget (tier default, overridable
per entry with timeout:); on timeout the whole build process group is
killed and the package is marked timeout.
Adding packages
Edit catalog.yml. Fields are documented at the top of the file;
keep source names as they exist in the archive — not binary names
(make-dfsg and rust-ripgrep, not make and ripgrep;
xz-utils, not xz; postgresql-17, not postgresql). The file
stays within a small YAML subset so the stdlib fallback parser can read
it: flat mappings, inline lists, no colons in values.