a29fe71666
build and publish packages / distcc-avahi-aarch64 (push) Successful in 3s
build and publish packages / mesa-panvk-bifrost-aarch64 (push) Successful in 4s
build and publish packages / lmcp-any (push) Successful in 3s
build and publish packages / lmcp-debian (push) Successful in 3s
build and publish packages / claude-his-any (push) Successful in 3s
build and publish packages / ffmpeg-v4l2-request-aarch64 (push) Successful in 3s
build and publish packages / claude-his-debian (push) Successful in 3s
build and publish packages / ffmpeg-v4l2-request-debian (push) Successful in 3s
build and publish packages / libva-v4l2-request-fourier-aarch64 (push) Successful in 3s
build and publish packages / daedalus-v4l2-debian (push) Successful in 3s
build and publish packages / mpv-fourier-aarch64 (push) Successful in 3s
build and publish packages / libva-v4l2-request-fourier-debian (push) Successful in 3s
build and publish packages / daedalus-v4l2-dkms-debian (push) Successful in 3s
build and publish packages / mpv-fourier-debian (push) Successful in 4s
Closes task #134 work. PR #44 showed the cross-distro ABI hazard for `libva-v4l2-request-fourier-debian`: building on Arch (libva 2.23) produced `__vaDriverInit_1_23`, which trixies libva 2.22 runtime cant bind. Same hazard applies to other fourier-debian jobs that link against debian-native libs. **Moved from runs-on: arch-aarch64 → debian-aarch64:** - ffmpeg-v4l2-request-debian - mpv-fourier-debian - daedalus-v4l2-debian - daedalus-v4l2-dkms-debian **Left alone (arch=all, no native compile against debian libs):** - lmcp-debian - claude-his-debian Depends on PR #46 (label vs name fix) being merged so `debian-aarch64` actually routes to bohr. Reviewed-on: #47
231 lines
8.1 KiB
Bash
Executable File
231 lines
8.1 KiB
Bash
Executable File
#!/bin/bash
|
|
# check-already-published.sh <recipe-dir>
|
|
#
|
|
# Decide whether a given recipe (arch/<name> or debian/<name>) is already
|
|
# present in https://packages.reauktion.de/. Emits exactly one line to
|
|
# stdout:
|
|
#
|
|
# skip=1 — package with this version-pkgrel-arch tuple already lives in
|
|
# the pool; CI should short-circuit.
|
|
# skip=0 — file is missing or HEAD failed; CI should build + publish.
|
|
#
|
|
# Design notes:
|
|
# * For Arch recipes we source the PKGBUILD in a clean subshell so
|
|
# shell expansions (epoch=, ${_pkgver/-/}, pkgname=() arrays) resolve
|
|
# naturally. Only the first element of pkgname[] is checked — split
|
|
# packages share one source tarball / one build, so any-one-missing
|
|
# forces the full rebuild anyway.
|
|
# * For Debian recipes we extract the bare top-level PKGVER= /
|
|
# PKGREL= assignments (plus any other top-level VAR=value lines they
|
|
# reference) via grep and re-evaluate them in an isolated subshell —
|
|
# sourcing the entire build-deb.sh would run curl/tar/dpkg-deb
|
|
# against a tempdir we don't want to materialise here.
|
|
# * Epoch handling differs by ecosystem: Arch keeps `<epoch>:` in the
|
|
# pool filename, Debian/reprepro strips it.
|
|
# * curl --head with -f maps non-2xx to non-zero exit, which is what we
|
|
# want — 404 means "build it". -L follows mirrors. --max-time caps
|
|
# the worst-case latency per HEAD.
|
|
set -euo pipefail
|
|
|
|
REPO_BASE="${REPO_BASE:-https://packages.reauktion.de}"
|
|
HEAD_TIMEOUT="${HEAD_TIMEOUT:-15}"
|
|
|
|
RECIPE_DIR="${1:?usage: $0 <recipe-dir> (e.g. arch/distcc-avahi or debian/lmcp)}"
|
|
|
|
# Resolve relative to repo root if a leading path is passed; allow
|
|
# both `arch/foo` and absolute paths.
|
|
if [ ! -d "$RECIPE_DIR" ]; then
|
|
echo "error: recipe dir not found: $RECIPE_DIR" >&2
|
|
exit 2
|
|
fi
|
|
|
|
ecosystem="${RECIPE_DIR%%/*}"
|
|
|
|
http_head() {
|
|
local url="$1"
|
|
curl -sS -L --max-time "$HEAD_TIMEOUT" -o /dev/null \
|
|
-w '%{http_code}' --head "$url" || echo "000"
|
|
}
|
|
|
|
emit() {
|
|
# one-line GITHUB_OUTPUT-compatible kv
|
|
echo "skip=$1"
|
|
exit 0
|
|
}
|
|
|
|
case "$ecosystem" in
|
|
arch)
|
|
pkgbuild="$RECIPE_DIR/PKGBUILD"
|
|
[ -f "$pkgbuild" ] || { echo "error: $pkgbuild missing" >&2; exit 2; }
|
|
|
|
# Source in a fresh bash to capture variables. Some PKGBUILDs run
|
|
# functions or call commands at top level — keep this fast by
|
|
# restricting PATH and trapping side effects.
|
|
eval "$(
|
|
bash --noprofile --norc -c "
|
|
set +e
|
|
# Stub out anything that might shell out; we only need variable
|
|
# assignments to land.
|
|
cd '$RECIPE_DIR'
|
|
source ./PKGBUILD >/dev/null 2>&1 || true
|
|
# pkgname may be array; print first element.
|
|
if declare -p pkgname 2>/dev/null | grep -q 'declare -a'; then
|
|
first_name=\"\${pkgname[0]}\"
|
|
else
|
|
first_name=\"\$pkgname\"
|
|
fi
|
|
if declare -p arch 2>/dev/null | grep -q 'declare -a'; then
|
|
first_arch=\"\${arch[0]}\"
|
|
else
|
|
first_arch=\"\$arch\"
|
|
fi
|
|
printf 'PB_NAME=%q\n' \"\$first_name\"
|
|
printf 'PB_VER=%q\n' \"\$pkgver\"
|
|
printf 'PB_REL=%q\n' \"\$pkgrel\"
|
|
printf 'PB_EPOCH=%q\n' \"\${epoch:-}\"
|
|
printf 'PB_ARCH=%q\n' \"\$first_arch\"
|
|
"
|
|
)"
|
|
|
|
if [ -z "${PB_NAME:-}" ] || [ -z "${PB_VER:-}" ] || [ -z "${PB_REL:-}" ]; then
|
|
echo "error: failed to parse PKGBUILD ($RECIPE_DIR)" >&2
|
|
emit 0
|
|
fi
|
|
|
|
# Pool arch:
|
|
# arch=('any') → any
|
|
# arch=('aarch64' 'x86_64') → aarch64 (we publish for both, but the
|
|
# aarch64 artifact is the canonical CI build)
|
|
# arch=('aarch64') → aarch64
|
|
case "$PB_ARCH" in
|
|
any) pool_arch=any ;;
|
|
*) pool_arch=aarch64 ;;
|
|
esac
|
|
|
|
# Version string with optional epoch (epoch:pkgver-pkgrel).
|
|
if [ -n "${PB_EPOCH:-}" ]; then
|
|
ver_full="${PB_EPOCH}:${PB_VER}-${PB_REL}"
|
|
else
|
|
ver_full="${PB_VER}-${PB_REL}"
|
|
fi
|
|
|
|
# Pool URL path (arch keeps any/aarch64 split; 'any' lands in the
|
|
# aarch64 dir per current marfrit layout — both arches share the
|
|
# blob via the publish-to-both-arches step in build.yml).
|
|
pool_dir="arch/aarch64"
|
|
|
|
base_url="${REPO_BASE}/${pool_dir}/${PB_NAME}-${ver_full}-${pool_arch}.pkg.tar"
|
|
for ext in zst xz gz; do
|
|
code=$(http_head "${base_url}.${ext}")
|
|
if [ "$code" = "200" ]; then
|
|
emit 1
|
|
fi
|
|
done
|
|
emit 0
|
|
;;
|
|
|
|
debian)
|
|
bd="$RECIPE_DIR/build-deb.sh"
|
|
ctrl="$RECIPE_DIR/control"
|
|
[ -f "$bd" ] || { echo "error: $bd missing" >&2; exit 2; }
|
|
|
|
# Pull top-level `VAR=value` lines until we've passed PKGREL, and
|
|
# only those whose RHS is safe to re-evaluate (no command
|
|
# substitution `$(...)`, no escaped `\$`, no embedded commands like
|
|
# `DESTDIR=... meson ...`). This deliberately undershoots: we just
|
|
# need PKGVER/PKGREL plus any version vars they reference. Anything
|
|
# else (HERE=$(readlink ...), KERNELVER=\$(uname -r) inside a
|
|
# HEREDOC, etc.) gets dropped.
|
|
assigns=$(awk '
|
|
/^[A-Z_][A-Z0-9_]*=/ {
|
|
# split into LHS and RHS
|
|
eq = index($0, "=")
|
|
lhs = substr($0, 1, eq - 1)
|
|
rhs = substr($0, eq + 1)
|
|
# strip inline `# comment`
|
|
hash = index(rhs, "#")
|
|
if (hash > 1 && substr(rhs, hash-1, 1) == " ") rhs = substr(rhs, 1, hash - 2)
|
|
# reject lines with command-subst or escaped-dollar or naked commands
|
|
if (rhs ~ /\$\(/) next
|
|
if (rhs ~ /\\\$/) next
|
|
if (rhs ~ / [a-z]/) next # e.g. `DESTDIR="$ROOT" meson ...`
|
|
print lhs "=" rhs
|
|
if (lhs == "PKGREL") exit
|
|
}
|
|
' "$bd")
|
|
|
|
eval "$(
|
|
bash --noprofile --norc -c "
|
|
set +e
|
|
$assigns
|
|
printf 'PKGVER=%q\n' \"\${PKGVER:-}\"
|
|
printf 'PKGREL=%q\n' \"\${PKGREL:-}\"
|
|
"
|
|
)"
|
|
|
|
if [ -z "${PKGVER:-}" ] || [ -z "${PKGREL:-}" ]; then
|
|
echo "error: failed to parse PKGVER/PKGREL from $bd" >&2
|
|
emit 0
|
|
fi
|
|
|
|
# Strip epoch (`N:` prefix) — debian pool filenames omit it.
|
|
ver_no_epoch="${PKGVER#*:}"
|
|
# If PKGVER had no colon, ${PKGVER#*:} returns PKGVER unchanged (bash quirk:
|
|
# the pattern must match for the prefix to be stripped). Guard explicitly.
|
|
case "$PKGVER" in
|
|
*:*) : ;;
|
|
*) ver_no_epoch="$PKGVER" ;;
|
|
esac
|
|
|
|
ver_full="${ver_no_epoch}-${PKGREL}"
|
|
|
|
# Architecture: parse control's `Architecture:` field.
|
|
if [ ! -f "$ctrl" ]; then
|
|
# Some recipes ship debian/control instead of ./control
|
|
ctrl="$RECIPE_DIR/debian/control"
|
|
fi
|
|
ctrl_arch=$(grep -m1 '^Architecture:' "$ctrl" 2>/dev/null | awk '{print $2}')
|
|
case "$ctrl_arch" in
|
|
all) file_arch=all ;;
|
|
arm64|any) file_arch=arm64 ;;
|
|
amd64) file_arch=amd64 ;;
|
|
*) file_arch=arm64 ;; # conservative default
|
|
esac
|
|
|
|
pkg_name=$(basename "$RECIPE_DIR")
|
|
|
|
# Compare against the canonical Packages index (what apt actually
|
|
# consults). reprepro refuses lower-version uploads, so checking
|
|
# only an exact source-pkgrel URL produces an endless-rebuild trap
|
|
# whenever source PKGREL has rolled back below pool head. We skip
|
|
# if pools published version >= source version-tuple.
|
|
source_full="${ver_full}"
|
|
if [ -n "${PKGVER#*:}" ] && [ "${PKGVER}" != "${PKGVER#*:}" ]; then
|
|
# PKGVER had an epoch — keep it for dpkg --compare-versions.
|
|
source_full="${PKGVER}-${PKGREL}"
|
|
fi
|
|
|
|
# Determine suite: most recipes publish to both bookworm and trixie;
|
|
# checking trixie is sufficient (changelogs share Distribution).
|
|
suite="trixie"
|
|
pkg_arch_label="$file_arch"
|
|
[ "$file_arch" = "all" ] && pkg_arch_label="all"
|
|
packages_url="${REPO_BASE}/debian/dists/${suite}/main/binary-arm64/Packages"
|
|
[ "$file_arch" = "amd64" ] && packages_url="${REPO_BASE}/debian/dists/${suite}/main/binary-amd64/Packages"
|
|
|
|
pool_ver=$(set +o pipefail; curl -sS --max-time "$HEAD_TIMEOUT" "$packages_url" 2>/dev/null | awk -v p="$pkg_name" '$1=="Package:" && $2==p {found=1; next} found && $1=="Version:" {print $2; exit}')
|
|
|
|
if [ -n "$pool_ver" ] && command -v dpkg >/dev/null && dpkg --compare-versions "$pool_ver" ge "$source_full"; then
|
|
echo "pool has $pool_ver >= source $source_full" >&2
|
|
emit 1
|
|
fi
|
|
echo "pool has $pool_ver, source wants $source_full — build" >&2
|
|
emit 0
|
|
;;
|
|
|
|
*)
|
|
echo "error: unsupported ecosystem '$ecosystem' (recipe-dir=$RECIPE_DIR)" >&2
|
|
emit 0
|
|
;;
|
|
esac
|