name: Packaging Verification
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
paths:
- '**/*.rs'
- '**/Cargo.toml'
- 'Cargo.lock'
- 'flake.nix'
- 'flake.lock'
- 'packaging/**'
- '.github/workflows/packaging.yml'
workflow_dispatch:
permissions:
contents: read
jobs:
nixpkgs:
runs-on: ubuntu-latest
if: false steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- name: Install Nix
uses: cachix/install-nix-action@v31
with:
nix_path: nixpkgs=channel:nixos-unstable
- name: Build Nixpkgs Derivation
run: nix-build packaging/nixpkgs/test.nix
- name: Build Nix Flake
run: nix build .#default
aur:
runs-on: ubuntu-latest
container: archlinux:latest
steps:
- name: Install system dependencies
run: |
# mandown is deliberately NOT installed: the PKGBUILD no longer regenerates the man
# page (it installs the committed docs/retch.1), so mandown is not in makedepends
# either. Pre-installing it here would mask a future build() that calls it without
# declaring it — `makepkg -s` installs what makedepends asks for, and nothing else.
# `cargo` is still pre-installed, so that one makedepend remains masked.
pacman -Syu --noconfirm git base-devel cargo curl sudo
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- name: Verify declared source checksum
shell: bash
run: |
set -euo pipefail
cd packaging/aur
# A PKGBUILD is a bash script of assignments; sourcing defines the functions without
# running them.
# shellcheck disable=SC1091
source ./PKGBUILD
url_field="${source[0]#*::}"
declared="${sha256sums[0]}"
echo "pkgver: $pkgver"
echo "url: $url_field"
echo "declared: $declared"
if [ "$declared" = "SKIP" ]; then
echo "::error::sha256sums is SKIP in the committed PKGBUILD; it must carry a real checksum"
exit 1
fi
# The reference copy tracks the last RELEASED version, so its tag normally exists. On
# a branch that has bumped it ahead of the last release it will not yet — that is a
# legitimate state, not a failure, so skip rather than go red.
if ! curl -fsSL --retry 3 -o upstream.tar.gz "$url_field"; then
echo "::notice::tag not published yet ($url_field) — checksum not verifiable at this commit"
exit 0
fi
actual=$(sha256sum upstream.tar.gz | cut -d' ' -f1)
echo "actual: $actual"
if [ "$actual" != "$declared" ]; then
echo "::error::sha256 mismatch for $url_field — declared $declared, actual $actual"
exit 1
fi
echo "checksum OK"
rm -f upstream.tar.gz
- name: Build AUR PKGBUILD
run: |
# Create builduser
useradd -m builduser
echo "builduser ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
cd packaging/aur
# Package local source to avoid downloading tag that doesn't exist yet
tar --exclude=packaging/aur/cargo-home --exclude=target --exclude=.git -czf /tmp/retch.tar.gz -C ../.. .
mv /tmp/retch.tar.gz retch.tar.gz
# Patch PKGBUILD for local build
sed -i 's|source=.*|source=("retch.tar.gz")|' PKGBUILD
sed -i 's|sha256sums=.*|sha256sums=("SKIP")|' PKGBUILD
sed -i 's|"$pkgname-\$pkgver"|"$srcdir"|g' PKGBUILD
chown -R builduser:builduser .
# Run makepkg (unsetting any runner-inherited cargo linker configs)
sudo -u builduser env -u CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER makepkg -s --noconfirm
- name: Verify packaged man page
shell: bash
run: |
set -euo pipefail
cd packaging/aur
# NOTE: every inspection below reads a materialised FILE, never `producer | grep -q`.
# Under `set -o pipefail` that idiom is a check that fails for the wrong reason:
# `grep -q` (and `head -1`, and `grep -m1`) exit on the first match, the producer
# takes SIGPIPE and exits 141, and pipefail turns the whole pipeline nonzero — so the
# check reports "not found" precisely WHEN IT MATCHES. The first version of this step
# did exactly that and failed CI while the package was perfectly correct.
pkg=$(find . -maxdepth 1 -name 'retch-*.pkg.tar.*' ! -name '*-debug-*' -print -quit)
if [ -z "$pkg" ]; then
echo "::error::no built package found in packaging/aur"
ls -1
exit 1
fi
echo "inspecting $pkg"
bsdtar -tf "$pkg" > pkg-listing.txt
# makepkg's `zipman` option is ON by default, so the packaged page is
# `retch.1.gz`, not `retch.1` — match either, and any other compressor a
# makepkg.conf might select, rather than assuming one name.
man_entry=$(grep -m1 -E '^usr/share/man/man1/retch\.1(\.gz|\.zst|\.xz|\.bz2)?$' pkg-listing.txt || true)
if [ -z "$man_entry" ]; then
echo "::error::package contains no usr/share/man/man1/retch.1"
echo "--- what it did install under usr/share:"
grep -E '^usr/share' pkg-listing.txt || echo "(nothing)"
exit 1
fi
echo "man page entry: $man_entry"
bsdtar -xOf "$pkg" "$man_entry" > packaged-man.raw
case "$man_entry" in
*.gz) gzip -dc < packaged-man.raw > packaged-retch.1 ;;
*.zst) zstd -dc < packaged-man.raw > packaged-retch.1 ;;
*.xz) xz -dc < packaged-man.raw > packaged-retch.1 ;;
*.bz2) bzip2 -dc < packaged-man.raw > packaged-retch.1 ;;
*) cp packaged-man.raw packaged-retch.1 ;;
esac
th=$(grep -m1 '^\.TH' packaged-retch.1)
echo "TH line: $th"
# Defect 1: an unexpanded shell variable in the footer.
case "$th" in
*'$'*)
echo "::error::.TH line contains a literal \$ — unexpanded variable in the footer"
exit 1
;;
esac
# The footer must name the program and a real date. Deliberately NOT asserted against
# $pkgver: this job builds from the local tree, whose committed page carries the
# in-development version while pkgver tracks the last release. Coupling them here
# would fail for a reason that has nothing to do with the packaging.
case "$th" in
*'"retch '[0-9]*) : ;;
*)
echo "::error::.TH line does not carry a 'retch <version>' footer"
exit 1
;;
esac
# Defect 2: doubled font escapes the dead sed claimed to strip.
if grep -q '\\fB\\fB\|\\fP\\fP' packaged-retch.1; then
echo "::error::packaged man page contains doubled font escapes (\\fB\\fB / \\fP\\fP)"
exit 1
fi
rm -f pkg-listing.txt packaged-retch.1 packaged-man.raw
echo "packaged man page OK"