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).
Saving the modified source entries with save_legacy rewrote every entry
into /etc/apt/sources.list in legacy format, destroying deb-src entries
and signed-by/trusted options, duplicating every distro entry that came
from a deb822 file (which stayed in place), and hardcoding the Ubuntu
keyring on cross builds. Entries now remember the file and format they
were loaded from and are written back there; new entries (PPAs, ports)
go to a pkh-owned /etc/apt/sources.list.d/pkh-added.list, and a one-time
<path>.pkh-backup copy is made before overwriting an existing file.
Also fixes: 'Types: deb deb-src' stanzas are split instead of being
treated as binary-only, commented-out legacy entries are kept disabled
instead of deleted, debian.sources is actually read on Debian (the old
else-if never fired), and the double blank lines save_legacy emitted.
The shared world-writable /tmp/pkh-keyrings directory, combined with the
skip-if-exists logic, let any local user pre-plant keyrings that pkh
then trusts into the chroot's trusted.gpg.d. Use a per-uid 0700
directory instead, refuse to reuse a pre-existing directory that is not
owned by the current user or is group/other-writable, and drop the now
unnecessary world-accessibility chmods (mmdebstrap in unshare mode runs
with the same real uid).
Entry::unpack performs no path sanitization, so a malicious or malformed
tarball (PPA, flat repository) could write files outside the package
directory via '..' components or absolute entry paths. Refuse such
entries with an error naming the offending path.
Without --host-architecture, the second build-dep pass re-resolves the
whole Build-Depends field for the native architecture: apt swaps
host-arch -dev packages for native ones (e.g. libcurl4-gnutls-dev,
whose arch-differing curl-config makes dpkg refuse the co-install) and
breaks the cross build environment.
Per dpkg-checkbuilddeps, both Build-Depends and Build-Depends-Indep
resolve for the host architecture in cross mode, so pass
--host-architecture to the second pass as well. Skip the pass entirely
when the source declares no Build-Depends-Indep.
Add an end-to-end regression test building a package that declares
libdb-dev in both fields and links a host-arch binary against it: the
test only passes if the arch-indep pass did not swap the arm64 -dev
packages for native ones.
Add a --repository flag taking the full suite URL of an external flat
repository (e.g. https://pkg.noctalia.dev/deb/resolute/), i.e. one with
no dists/ hierarchy, like apt's exact-path suites ('Suites: resolute/').
The suite name is read from the root Release file (Codename/Suite), the
sources index is fetched from the repository root as Sources.xz/gz/plain,
and package files are resolved against the URL root, ignoring the stanza
Directory field like apt does. As with PPAs, the stanza Vcs-Git is never
used for external repositories, so the source always comes from the
repository itself.
Also make the sources index parser detect compression by magic bytes
(gz/xz/plain) instead of assuming gzip, and fix extraction of archives
with './'-prefixed entries, which previously aborted and are now
extracted in place instead of being relocated.
build_binary_package_impl copied the whole parent directory into the
build root (ensure_available) and then retrieved every *.deb / *.changes
/ *.buildinfo it found there. That surfaced stale files already sitting
next to the package tree in the "Built in Ns:" summary.
Now local::build returns the exact set of artifacts produced by this
build — the binary packages registered in debian/files plus the
generated .buildinfo/.changes from generate_binary_metadata — and
build_binary_package_impl retrieves that list instead of globbing the
build root. Only files genuinely produced by the current build are
printed.
By default the number of parallel jobs is detected with nproc inside
the build context. Add a -j/--jobs option so an explicit count can be
honored instead, threading it through build_binary_package and
local::build into DEB_BUILD_OPTIONS=parallel=N.
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'.
Extend the metadata writers to binary-only uploads and wire them into
the 'pkh deb' flow:
- build/binary.rs generates <pkg>_<ver>_<arch>.buildinfo/.changes
through any Context: debian/files consumption, encounter-order
Architecture accumulation (sorted in .buildinfo like dpkg-genbuildinfo),
sorted Binary lists, dpkg-formatted Description lines with udeb
suffixes, Installed-Build-Depends closure over the context status DB,
and binNMU handling (Source: pkg (prev), Binary-Only-Changes, previous
.dsc redistribution);
- artifact digests are computed inside the context via coreutils
(md5sum/sha1sum/sha256sum/stat) so chrooted/remote trees work;
- deb/local.rs runs the generation after 'rules binary', exports
SOURCE_DATE_EPOCH from the changelog (reproducibility), and resolves
vendor/profiles inside the context; deb/mod.rs retrieves the new
artifacts alongside the debs;
- reusable helpers added: FilesList::parse/render,
parse_changelog_entry_from_str, parse_previous_version_from_str,
installed_build_depends_from_content.
Differential gate: same tree built with real 'dpkg-buildpackage -b' and
with the pkh flow; .changes/.buildinfo compared field-by-field modulo
machine-dependent fields, artifact checksums included.
Replace dpkg-checkbuilddeps with a native implementation:
- full dependency grammar: comma clauses, | alternatives, << <= = >= >>
relations, :arch qualifiers (any/native/specific), [arch lists] and
<profile restriction> formulas per alternative;
- restriction reduction against active build profiles and the host arch
at parse time (reduce_restrictions semantics);
- evaluation against a parsed dpkg status database with Multi-Arch
semantics (foreign/allowed) and versioned Provides rules (unversioned
provides never satisfy versioned deps; versioned ones must satisfy the
relation);
- clause simplification with implication-based deduplication, rendering
dpkg-compatible 'unmet build dependencies/conflicts' diagnostics.
check_build_depends() consumes debian/control + CheckOpts (-A/-B/-I
equivalents). run_source_build performs the check when forced (-D
parity); source-only builds skip it entirely like dpkg-buildpackage,
and unsatisfied deps propagate as UnmetBuildDependencies -> exit 3.
Unit tests port the Dpkg_Deps.t reduction matrices; differential gate
runs 24 scenarios (alternatives, versions, arch/profile restrictions,
Multi-Arch, Provides, conflicts, -A/-B flags) against real
dpkg-checkbuilddeps comparing exit status and diagnostics.
Implement the documented dpkg ordering algorithm (Debian Policy 5.6.1):
numeric epoch, then upstream/revision compared as alternating non-digit
and digit chunks, with '~' ordering before anything including the empty
chunk and letters before non-letters in non-digit chunks.
Adds Ord/PartialOrd for DebianVersion, a free compare() and a
later_than() convenience.
Unit tests port all vectors from dpkg's scripts/t/Dpkg_Version.t plus
Ubuntu-flavored cases (security uploads, ~ppa1 backports). Differential
gate: every vector cross-checked against real 'dpkg --compare-versions'
for <<, <=, =, >= and >>.
Embed dpkg's factual cputable/ostable/tupletable/abitable data and
implement tuple/triplet/multiarch lookups, wildcard matching, arch
restriction evaluation and the full DEB_BUILD_*/DEB_HOST_*/DEB_TARGET_*
environment dump natively.
build/env.rs::arch_env now delegates to the native implementation
instead of shelling out to 'dpkg-architecture -f'.
Differential gate (build/mod.rs): arch_env(Some(a)) must equal real
'dpkg-architecture -f -a a' key-for-key for every architecture listed by
'dpkg-architecture -L', plus the native case. Data tables carry upstream
attribution comments; no dpkg code was transliterated.
Add an automated differential test harness in build/mod.rs that builds
the same source tree twice - once with real 'dpkg-buildpackage -S
-I -i -nc -d --no-sign', once with the native pipeline - and compares
all produced artifacts:
- .dsc payload byte-for-byte,
- .changes field-by-field (checksum lines of the .buildinfo itself
excluded, as its content legitimately differs on machine-dependent
fields),
- .buildinfo structure (Installed-Build-Depends, Environment,
Build-Date and Build-Tainted-By excluded).
Move the generic components out of src/build/ into a new src/debian/
module so they can be reused independently of the build pipeline:
deb822 control parsing (plus the debian/control model), file checksum
registry, debian/files registry, Debian version handling and changelog
entry parsing.
Merge OpenPGP clearsigning into utils/gpg.rs next to the existing key
discovery helper, making signing available outside of builds.
Delegate changelog.rs header/footer parsing to the new
debian::changelog parser, removing the duplicate regex implementation.
src/build/ keeps only build-specific logic: the pipeline driver,
build types, environment setup and the .buildinfo/.changes writers.
Replace the 'dpkg-buildpackage -S' wrapper with a native pipeline in
src/build/:
- deb822 control parser/writer with dpkg-compatible multiline rendering
(control.rs)
- md5/sha1/sha256 checksum registry, insertion-ordered like dpkg's
artifact accumulation (checksums.rs)
- Debian version splitting/validation and full changelog entry parsing,
including binNMU binary-only entries (metadata.rs)
- build-type bitflags and rules-target/artifact-suffix mapping
(buildtype.rs)
- environment setup: SOURCE_DATE_EPOCH, DEB_BUILD_OPTIONS,
dpkg-architecture env dump, vendor default profiles and the sanitized
Environment field recorded in .buildinfo (env.rs)
- debian/files registry with atomic saves (files.rs)
- native .buildinfo writer, including the Installed-Build-Depends
closure computed over the dpkg status database (buildinfo.rs)
- native .changes writer emitting dpkg's canonical field order with
legacy Files + Checksums-Sha1/Sha256 (changes.rs)
- gpgme clearsigning with the transitive checksum cascade
(dsc -> buildinfo -> changes), key discovery from the changelog
maintainer and UNRELEASED no-sign handling (sign.rs)
dpkg-source (-b/--before-build/--after-build) intentionally remains a
subprocess; debian/rules execution is unchanged.
Validated differentially against real dpkg-buildpackage -S -I -i -nc -d
on native and 3.0 (quilt) fixture packages: .dsc byte-identical, .changes
payload matches modulo machine-dependent Installed-Build-Depends and
Environment content, all signatures verify with gpg, artifact ordering
and UNRELEASED no-sign behavior match dpkg.
The test suite is too heavy for the current CI infra (CPU/RAM
exhaustion), so it must not run alongside lint/build feedback.
- add a dedicated test job (needs: build) running runtime deps
install, subuid/subgid setup and 'cargo test'
- share the compiled target/ dir and cargo caches between both jobs
via actions/cache keyed on the commit sha, so the test job only
compiles the test binaries
- disable the test job with 'if: false' until infra can handle it;
re-enable by removing that line
- snap: bump upload-artifact v3 -> v4 (v3 is decommissioned) and
checkout to v6
- update images to ubuntu:26.04
'proposed' pockets are marked 'NotAutomatic' in their Release file,
which gives them an apt priority of 1: apt would ignore them during
build-dependency resolution even when the suite is enabled.
Write an apt preferences entry pinning '{series}-proposed' at
priority 600 so build-dependencies are actually resolved from the
requested pocket.
Apt preferences are global: a single 'release' pin matches the pinned
suite on every repository carrying it (archive, security and ports),
for all architectures, so this also covers cross-builds pulling
dependencies from 'ports.ubuntu.com'.
- pull: select the pocket to download the source package from
- deb: build against dependencies from a specific pocket:
- local mode: enable '{series}-{pocket}' suite on archive sources
- cross mode: include the pocket suite in required repositories
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.
The Ubuntu development series (stonking) Sources.gz no longer ships a
Checksums-Sha256 field, only Checksums-Sha512 and the legacy Files
(MD5) field. The package_info parser only read Checksums-Sha256, so
the file list ended up empty and fetch_orig_tarball panicked on
Option::unwrap() when looking for the orig tarball.
- Add a ChecksumAlgo enum (Md5/Sha256/Sha512) to FileEntry, replacing
the hardcoded sha256 field, and parse the strongest available
checksum field (Sha256 > Sha512 > MD5).
- Make download_file_checksum verify against the correct algorithm
instead of always using SHA-256.
- Replace the unwrap() on the orig tarball search with a proper error
listing the available files, so future regressions fail clearly
instead of panicking.
- Add md-5 dependency for MD5 verification.