Files
pkh/tests/e2e
vhaudiquet abc89c4544 test: drop the coreutils xfail, rootless builds clear it
The e2e canary against the rootless build records coreutils as
xpass: with the rules running as the build user, its configure no
longer refuses the build.
2026-10-02 12:43:32 +02:00
..
2026-10-01 22:24:07 +02:00
2026-10-01 22:24:07 +02:00
2026-10-01 22:24:07 +02:00

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, then target/debug/pkh), falls back to pkh on PATH; override with --pkh PATH or build first with --build-pkh.
  • The pkh runtime toolchain: mmdebstrap, sudo (passwordless, for the unshare chroot builds), dpkg-dev, distro-info data, 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.