#!/usr/bin/env bash # Verifies the claims in rpmbuild-ubuntu-create-rpm.html. # # Two classes of check: # EXECUTED - run against a real upstream rpm/rpmbuild on this machine. # (macOS + Homebrew rpm here; the rpm is upstream, the host is not # Ubuntu, and every message below is reproduced verbatim.) # ARCHIVE - read out of the real Ubuntu 26.04 .deb files, downloaded from # archive.ubuntu.com, and out of the Launchpad API. # # No Ubuntu or RHEL host is used. Nothing is asserted that was not produced by # one of the two mechanisms above. Cleans up after itself. set -u FAILED=0 pass() { printf 'PASS %s\n' "$1"; } fail() { printf 'FAIL %s\n' "$1"; FAILED=1; } skip() { printf 'SKIP %s\n' "$1"; } SCRATCH="$(mktemp -d)" trap 'rm -rf "$SCRATCH"' EXIT cd "$SCRATCH" || exit 1 echo "== rpmbuild-ubuntu-create-rpm verification ==" echo "host : $(uname -srm)" echo "date : $(date -u '+%Y-%m-%dT%H:%M:%SZ')" if command -v rpmbuild >/dev/null 2>&1; then echo "rpm : $(rpm --version)" else echo "rpm : NOT INSTALLED" fi echo HAVE_RPM=0 command -v rpmbuild >/dev/null 2>&1 && command -v rpmspec >/dev/null 2>&1 && HAVE_RPM=1 # =========================================================================== # Part 1 - ARCHIVE: what Ubuntu actually ships # =========================================================================== LP='https://api.launchpad.net/1.0/ubuntu/+archive/primary?ws.op=getPublishedSources&source_name=rpm&exact_match=true&status=Published&distro_series=https%3A%2F%2Fapi.launchpad.net%2F1.0%2Fubuntu%2F' lp_version() { curl -s -m 45 "${LP}$1" | python3 -c " import sys,json try: d=json.load(sys.stdin) except Exception: sys.exit(1) e=d.get('entries') or [] print(e[0]['source_package_version'] if e else '')" 2>/dev/null; } res=$(lp_version resolute); nob=$(lp_version noble) if [ -n "$res" ]; then case "$res" in 6.*) pass "A1a Ubuntu 26.04 (resolute) publishes rpm $res -> major version 6" ;; *) fail "A1a Ubuntu 26.04 publishes rpm $res, article claims 6.0.1-1build1" ;; esac else skip "A1a Launchpad unreachable; cannot confirm the resolute rpm version" fi if [ -n "$nob" ]; then case "$nob" in 4.18*) pass "A1b Ubuntu 24.04 (noble) publishes rpm $nob -> still major version 4" ;; *) fail "A1b Ubuntu 24.04 publishes rpm $nob, article claims 4.18.2+dfsg-2.1build2" ;; esac else skip "A1b Launchpad unreachable; cannot confirm the noble rpm version" fi POOL='http://archive.ubuntu.com/ubuntu/pool/universe/r/rpm' got_debs=0 if curl -sS -m 120 -o rpm.deb "$POOL/rpm_6.0.1-1build1_amd64.deb" 2>/dev/null && curl -sS -m 120 -o rpmc.deb "$POOL/rpm-common_6.0.1-1build1_amd64.deb" 2>/dev/null && [ -s rpm.deb ] && [ -s rpmc.deb ]; then mkdir -p a b && (cd a && ar x ../rpm.deb) && (cd b && ar x ../rpmc.deb) && got_debs=1 fi if [ "$got_debs" -eq 1 ]; then # A2: /usr/bin/rpmbuild lives in the `rpm` package if tar -tf a/data.tar.* 2>/dev/null | grep -q '\./usr/bin/rpmbuild$'; then pass "A2 the Ubuntu 'rpm' package ships /usr/bin/rpmbuild ('apt install rpm' is right)" else fail "A2 /usr/bin/rpmbuild not found in the Ubuntu rpm package" fi mkdir -p fs && tar -xf b/data.tar.* -C fs 2>/dev/null M=fs/usr/lib/rpm/macros if [ -f "$M" ]; then # A3: %_topdir default if grep -qE '^%_topdir[[:space:]]+%\{getenv:HOME\}/rpmbuild' "$M"; then pass "A3 Ubuntu's shipped macros already default %_topdir to \$HOME/rpmbuild" grep -nE '^%_topdir' "$M" | sed 's/^/ /' else fail "A3 %_topdir default in Ubuntu's macros is not \$HOME/rpmbuild" fi # A4: %_rpmformat default is 6 if grep -qE '^%_rpmformat[[:space:]]+6' "$M"; then pass "A4a Ubuntu 26.04 rpm defaults to %_rpmformat 6" grep -nE '^%_rpmformat|^%_binary_payload' "$M" | sed 's/^/ /' else fail "A4a %_rpmformat is not 6 in Ubuntu 26.04's macros" fi if grep -q 'zstdio' "$M"; then pass "A4b that format selects a zstd binary payload" else fail "A4b no zstd payload macro found" fi # A5: %dist is NOT defined if grep -qE '^%dist[[:space:]]' "$M"; then fail "A5 %dist IS defined in Ubuntu's macros -- article's claim is wrong" else pass "A5 %dist is not defined in Ubuntu's macros (only commented #%disttag etc.)" grep -nE '^#%dist' "$M" | sed 's/^/ /' fi else skip "A3-A5 rpm-common macros file not found after extraction" fi else skip "A2-A5 Ubuntu archive unreachable; .deb-derived claims not checked" fi echo # =========================================================================== # Part 2 - EXECUTED: build the article's spec with a real rpm # =========================================================================== if [ "$HAVE_RPM" -eq 0 ]; then skip "E1-E7 no rpm/rpmbuild on this machine; build claims not executed" skip " (install upstream rpm to run them: 'brew install rpm' / 'dnf install rpm-build')" else T="$SCRATCH/tree" mkdir -p "$T"/{BUILD,RPMS,SOURCES,SPECS,SRPMS} mkdir -p "$T/SOURCES/helloworld-1.0" cat > "$T/SOURCES/helloworld-1.0/helloworld.c" <<'EOF' #include int main(void){ printf("Hello, World!\n"); return 0; } EOF (cd "$T/SOURCES" && tar -czf helloworld-1.0.tar.gz helloworld-1.0) # The spec EXACTLY as the old article published it (bogus date, %defattr, rm -rf). cat > "$T/SPECS/old.spec" <<'EOF' Name: helloworld Version: 1.0 Release: 1%{?dist} Summary: A simple Hello World program License: GPL URL: https://example.com Source0: helloworld-1.0.tar.gz BuildRequires: gcc %description A basic Hello World program written in C. %prep %setup -q %build gcc -o helloworld helloworld.c %install rm -rf $RPM_BUILD_ROOT install -d $RPM_BUILD_ROOT/usr/local/bin install -m 755 helloworld $RPM_BUILD_ROOT/usr/local/bin %files %defattr(-,root,root,-) /usr/local/bin/helloworld %changelog * Mon Feb 18 2025 MakerPM - 1.0-1 - Initial RPM release EOF D="--define=_topdir $T" # E1: %{?dist} expands to nothing q=$(rpmspec "$D" -q "$T/SPECS/old.spec" 2>/dev/null | tail -1) case "$q" in helloworld-1.0-1.*) pass "E1 Release '1%{?dist}' resolved to plain '1' -- no dist tag: $q" ;; *) fail "E1 unexpected rpmspec -q output: $q" ;; esac # E2: rpm flags the bogus changelog weekday rpmspec "$D" -q "$T/SPECS/old.spec" > /dev/null 2>e2.err if grep -q 'bogus date in %changelog' e2.err; then pass "E2 rpm rejects the old article's changelog weekday" sed 's/^/ /' e2.err | head -2 python3 -c " import datetime print(' 18 Feb 2025 was a', datetime.date(2025,2,18).strftime('%A'), '-- the entry said Mon')" else fail "E2 expected a 'bogus date in %changelog' warning; got: $(cat e2.err)" fi # E3: BuildRequires cannot be satisfied - the rpm database is empty ndb="$SCRATCH/emptydb"; mkdir -p "$ndb" cnt=$(rpm --dbpath "$ndb" -qa 2>/dev/null | wc -l | tr -d ' ') rpmbuild "$D" -ba "$T/SPECS/old.spec" > e3.log 2>&1; rc=$? if [ "$rc" -ne 0 ] && grep -q 'Failed build dependencies' e3.log; then pass "E3 'rpmbuild -ba' fails on BuildRequires against an rpm db holding $cnt packages" grep -A2 'Failed build dependencies' e3.log | sed 's/^/ /' else # A host whose rpm db DOES contain gcc (e.g. Fedora) will not reproduce this. if [ "$cnt" -gt 0 ]; then skip "E3 this host's rpm database is populated ($cnt pkgs); the Debian-host" echo " failure mode cannot be reproduced here" else fail "E3 expected a BuildRequires failure; rpmbuild exit=$rc" fi fi # E4: --nodeps builds rpmbuild "$D" --define="_rpmdir $T/out-v6" --nodeps -bb "$T/SPECS/old.spec" > e4.log 2>&1; rc=$? f6=$(find "$T/out-v6" -name '*.rpm' 2>/dev/null | head -1) if [ "$rc" -eq 0 ] && [ -n "$f6" ]; then pass "E4a --nodeps lets the same spec build to completion" echo " -> $(basename "$f6")" else fail "E4a --nodeps build failed (exit=$rc); tail: $(tail -3 e4.log | tr '\n' ' ')" fi # E4b: BUILDROOT is created automatically inside a per-package build dir if grep -q 'BUILD/helloworld-1.0-build/BUILDROOT' e4.log; then pass "E4b rpm created BUILD/helloworld-1.0-build/BUILDROOT itself (no need to mkdir it)" else skip "E4b per-package BUILDROOT path not seen in this rpm version's log" fi # E5: default format declares rpmlib(PayloadIsZstd) if [ -n "$f6" ]; then r6=$(rpm -qp --requires "$f6" 2>/dev/null | grep rpmlib | sort | tr '\n' ';') p6=$(rpm -qp --qf '%{PAYLOADCOMPRESSOR}' "$f6" 2>/dev/null) if printf '%s' "$r6" | grep -q 'PayloadIsZstd'; then pass "E5 default build (format $(rpm --eval '%_rpmformat')) payload=$p6 and requires rpmlib(PayloadIsZstd)" rpm -qp --requires "$f6" 2>/dev/null | grep rpmlib | sed 's/^/ /' else skip "E5 this rpm's default format does not emit a zstd payload; requires: $r6" fi fi # E6: --define '_rpmformat 4' drops the modern rpmlib requirement rpmbuild "$D" --define="_rpmdir $T/out-v4" --define="_rpmformat 4" --nodeps \ -bb "$T/SPECS/old.spec" > e6.log 2>&1 f4=$(find "$T/out-v4" -name '*.rpm' 2>/dev/null | head -1) if [ -n "$f4" ]; then r4=$(rpm -qp --requires "$f4" 2>/dev/null | grep rpmlib | sort | tr '\n' ';') p4=$(rpm -qp --qf '%{PAYLOADCOMPRESSOR}' "$f4" 2>/dev/null) if printf '%s' "$r4" | grep -q 'PayloadIsZstd'; then fail "E6 _rpmformat 4 still requires rpmlib(PayloadIsZstd): $r4" else pass "E6 --define '_rpmformat 4' -> payload=$p4, no rpmlib(PayloadIsZstd)" rpm -qp --requires "$f4" 2>/dev/null | grep rpmlib | sed 's/^/ /' fi else fail "E6 the _rpmformat 4 build produced no package" fi # E7: %_topdir default at runtime td=$(rpm --eval '%_topdir') case "$td" in */rpmbuild) pass "E7 rpm --eval '%_topdir' = $td (the .rpmmacros step is redundant)" ;; *) fail "E7 unexpected %_topdir: $td" ;; esac fi echo # =========================================================================== # Explicit non-claims # =========================================================================== skip "N1 'an rpm lacking PayloadIsZstd refuses the package' -- rpmlib() deps are" echo " satisfied by the installing rpm binary; the REQUIREMENT is measured above," echo " the refusal is documented rpm behaviour and was NOT reproduced (no old rpm here)." skip "N2 'a binary linked on Ubuntu gets glibc Requires RHEL cannot meet' -- documented" echo " behaviour of rpm's automatic dependency generator; NOT reproduced (no Linux host)." skip "N3 nothing here ran on Ubuntu. The build used upstream rpm on $(uname -s)." echo if [ "$FAILED" -eq 0 ]; then echo "RESULT: no FAILures." exit 0 else echo "RESULT: at least one FAILure above." exit 1 fi