On a resumed session whose overlay mount fails (no overlayfs, mount
error, non-overlay driver), the fallback used to copy the host tree
into the staged tree of the previous attempt: a merge that keeps the
old build stamps and resurrects files deleted on the host — a
silently half-stale build, exactly what resume must never produce.
The driver now reports the failure instead of copying, and the
caller wipes the staged build root and re-stages a fresh copy
(environment resumable, build artifacts discarded).
Resumable build sessions need to re-mount the staged tree over the
same overlay upperdir on every attempt: the build artifacts written
inside the chroot (object files) live there, while the host tree
stays visible as the live lowerdir. Add an optional
ensure_available_with_overlay staging path (OverlayStaging reports
whether the persistent upperdir was actually mounted or a fresh copy
was staged instead) with a default implementation rejecting the
request, so drivers without overlay support degrade cleanly.
The overlay mount itself is factored out of try_overlay_mount into
mount_overlay, taking optional pinned upper/work directories; a
failed mount only cleans up the directories it created, never the
caller-pinned ones.
list_files returns context-relative paths (rooted inside the chroot
for an unshare context, on the remote for ssh): whether an entry is
a directory can only be decided through the context, never with a
host-side stat. Give every driver a proper is_dir rather than
approximating it with exists, so callers can keep directories and
files apart — the deb package-directory search lists directories
only.
create_temp_dir probed for a free pkh-<seconds> name and then created
the directory, so two contexts arriving together could both observe a
free name and unpack into the same directory — observed as two e2e
tests started within the same second failing on 'File exists when
hard linking' during the chroot tarball unpack.
Name with sub-second precision and create atomically: a losing race
gets AlreadyExists and falls through to the next attempt, which
removes the probe window instead of narrowing it. The schroot and
ssh drivers already use mktemp -d and need no change.
Subprocesses inherit the session environment, so a translated host
locale leaked into builds: perl-based packaging tools
(dpkg-architecture, dpkg-parsechangelog, quilt, ...) warned about
missing locale settings, change their output with the environment,
and dpkg-buildpackage treats some of that output as data.
Default every command to LANG=C and LC_ALL=C; a caller can still
override explicitly through envs().
The chlog target-series resolution becomes changelog::series_candidates
(UNRELEASED pinning, development-series default and fallbacks modeled
by SeriesCandidates), PPA references get package_info::split_ppa
(shared by pull and deb, now also rejecting empty parts), and SSH
endpoints get context::ContextConfig::from_endpoint — so a library
consumer can resolve series, validate PPAs and build context
configurations without reimplementing the CLI's rules. All three carry
unit tests; main.rs keeps only parsing of flags and error handling.
ssh2::File's Drop discards the close-handshake error ('too late to
recover'), so a quota or server-side abort surfacing in the final ACKs
was recorded as a successful upload of a truncated file. Close upload
handles explicitly and propagate the error; also applies to the ssh
context driver's write_file and upload_recursive, which had the same
silent-drop issue.
cargo test used to be unreadable: subprocesses inherited the terminal, so
dpkg-buildpackage, apt and configure output interleaved with the harness
summary, and env_logger lines from parallel tests crossed each other.
New test_support module, compiled into test binaries only (inert stubs
otherwise) and initialized before main via .init_array:
- all log output goes to target/pkh-test-logs/<test>.log, one file per
test thread, so concurrent tests never interleave
- context-launched commands are captured line by line into the same file
(driver-level wrapper); test-code spawns use run_logged()
- a panic hook records failures and an atexit callback prints a matrix
(test name, panic location, message, log path) after the libtest
summary; tests panicking on purpose can opt out with a guard
Also fixes two test bugs found on the way:
- diff_checkbuilddeps_matrix compared dpkg-checkbuilddeps diagnostics
against English messages without pinning the locale
- run_source_build in differential tests now captures output like the
live-UI path does
set_current held the config RwLock for writing across make_context,
which for a context with a parent re-entered the same lock through
Context::new's global-manager lookup, deadlocking 'pkh context use'.
Context building is now lock-free by construction: make_context
resolves parent chains against a snapshot map (also rejecting parent
cycles), and neither set_current nor remove_context holds a guard
while building a Context.
A corrupt contexts.json no longer aborts every command at manager
init: load falls back to the default local-only config, backs the
corrupt file up to contexts.json.bak so a later save cannot silently
destroy it, and a dangling current/parent context falls back to local
with an error log instead of panicking.
build_binary_package installed its ephemeral chroot context into the
process-global manager and read it back with context::current(),
ignoring its ctx parameter: two concurrent builds would re-point each
other's global and each drop would clean up whichever chroot was
current at the time. The guard now keeps the Arc of the context it
created (parented directly on the base context, not on a config-name
lookup), exposes it via context(), and Drop cleans up exactly that
context and restores the exact handle that was current at creation,
so overlapping builds no longer cross-destroy each other.
All three non-local drivers assembled remote/chroot command strings by
raw concatenation: ssh pushed args verbatim (TODO: escape), schroot
interpolated env values raw so DEB_BUILD_OPTIONS='parallel=4 nocheck'
made sh treat 'nocheck' as the command, and unshare wrapped args in
unescaped double quotes letting quotes break out and $/backticks
expand. Add a shared POSIX shell_quote helper and use it for every
component interpolated into a shell string, including ssh copy_path
(which used Rust's {:?}, not shell quoting) and schroot write_file
(which also switches echo -ne to printf %s so backslash sequences in
content are no longer interpreted).
Copying a git checkout verbatim into /tmp/pkh-build-*/ ships '.git',
flipping autotools' building-from-VCS detection (GNU hello's
BUILD_FROM_GIT): the shipped man page gets cleaned and regenerated
via help2man, which is correctly not in Build-Depends. Skip VCS
dirs (.git/.hg/.svn/.bzr/CVS) in local/unshare/ssh copies, and
prune them after an unshare overlayfs mount.
Route 'pkh build' through the DebUi capture machinery 'pkh deb'
already uses instead of letting dpkg-source inherit the terminal:
- pin LANG=C and LC_ALL=C so dpkg-source emits deterministic English
diagnostics regardless of the session locale;
- new DpkgSourceClassifier rewrites info:/warning:/error: lines into
colored pane entries, telling benign tar warnings from failures;
- DebUi generalizes for reuse (arbitrary phase labels, build-specific
log naming); run_source_build() drives phases and pipes subprocess
output through the sink when a UI is present;
- glyph-free house-style summaries: 'Built in Ns:' plus artifact
paths relative to cwd; failures print captured errors + log path;
- drop/capitalize pipeline chatter, add 'pkh build --verbose' to
bypass the view like 'pkh deb --verbose'.
std::fs::copy resets the destination mtime to "now", which breaks
timestamp-based build systems (autotools/gnulib). Packages like
'hello' from Debian sid ship pre-generated files alongside their
prerequisites; when the copy flattens all mtimes, make considers the
generated targets out-of-date and tries to regenerate them with tools
(e.g. gperf) that are not declared build-dependencies, failing the
build.
Restore the source modification and access times after every file copy
in pull::copy_dir_all, pull::fetch_archive_sources merge step, and
context::unshare copy_dir_recursive/ensure_available.
Multiple changes:
- New contexts (schroot, unshare)
- Cross-building quirks, with ephemeral contexts and repositories management
- Contexts with parents, global context manager, better lifetime handling
- Local building of binary packages
- Pull: pulling dsc files by default
- Many small bugfixes and changes
Co-authored-by: Valentin Haudiquet <valentin.haudiquet@canonical.com>
Co-committed-by: Valentin Haudiquet <valentin.haudiquet@canonical.com>