You've already forked .autoupdate
Some checks failed
Build Autoupdater Image / build (push) Failing after 6s
All six nightly jobs ran the same pacman transaction before doing any work. The toolchain now lives in an image published to the Gitea registry as git.bjphoster.com/aur/autoupdate:latest, rebuilt monthly since Arch is rolling and a month-old image is a month behind. The build job itself runs on plain ubuntu-latest -- it only drives docker. Only the image is Arch. Based on archlinux:base rather than base-devel: the autoupdater parses PKGBUILDs and never compiles, so the toolchain is dead weight, 822 MB against 1.48 GB. nodejs is included because actions/checkout is a JavaScript action the runner executes with node from inside the image. Slimming to base initially dropped diffutils, and apply_pkgrel_rule used diff -q. A missing binary makes the `if` fail, which reads as "changed", so pkgrel ratcheted upward on every run -- the same silent-wrong-default class as the empty .SRCINFO this rewrite was about, and invisible to the host test run. The comparison is now a shell string equality with no external dependency, and is verified against an image with no diff at all. require_tools() additionally fails on startup for any missing binary rather than midway through. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
214 lines
7.6 KiB
Bash
Executable File
214 lines
7.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Shared helpers for the AUR autoupdater.
|
|
# Sourced by autoupdate.sh and the standalone scripts; never executed directly.
|
|
|
|
set -euo pipefail
|
|
|
|
# Endpoints. Overridable so the test suite can point at local bare repos
|
|
# instead of the real AUR; defaults are the production values.
|
|
AUR_SSH_BASE="${AUR_SSH_BASE:-ssh://aur@aur.archlinux.org}"
|
|
AUR_HTTPS_BASE="${AUR_HTTPS_BASE:-https://aur.archlinux.org}"
|
|
|
|
log() { printf '==> %s\n' "$*"; }
|
|
info() { printf ' -> %s\n' "$*"; }
|
|
warn() { printf 'WARNING: %s\n' "$*" >&2; }
|
|
die() { printf 'ERROR: %s\n' "$*" >&2; exit 1; }
|
|
|
|
# The old push.sh fell back to an interactive `read` when no version was passed.
|
|
# In CI that reads EOF and yields an empty version, which is how pman-helper
|
|
# committed pkgver="" and the sha256 of a zero-byte download.
|
|
# Fail up front on a missing tool rather than midway through, where an absent
|
|
# binary tends to be read as "condition false" and silently takes the wrong
|
|
# branch. `diff` going missing in a slimmer base image did exactly that: it made
|
|
# every run look like a packaging change and ratcheted pkgrel upward.
|
|
require_tools() {
|
|
local t missing=()
|
|
for t in git curl jq sha256sum awk sed grep makepkg; do
|
|
command -v "$t" >/dev/null 2>&1 || missing+=("$t")
|
|
done
|
|
[ "${#missing[@]}" -eq 0 ] || die "missing required tool(s): ${missing[*]}"
|
|
return 0
|
|
}
|
|
|
|
require_noninteractive() {
|
|
[ -t 0 ] && die "refusing to prompt: this script must never depend on interactive input"
|
|
return 0
|
|
}
|
|
|
|
# A version we are willing to write into a PKGBUILD.
|
|
VERSION_RE='^[0-9][0-9a-zA-Z._+]*$'
|
|
|
|
validate_version() {
|
|
local ver="${1-}" what="${2-version}"
|
|
[ -n "$ver" ] || die "${what} is empty"
|
|
[[ "$ver" =~ $VERSION_RE ]] || die "${what} '${ver}' does not look like a version"
|
|
return 0
|
|
}
|
|
|
|
# Read a scalar assignment out of a PKGBUILD without sourcing it.
|
|
# Handles pkgver="1.2.3", pkgver='1.2.3' and bare pkgver=1.2.3.
|
|
pkgbuild_get() {
|
|
local var="$1" file="${2:-PKGBUILD}"
|
|
sed -n -E "s/^${var}=[\"']?([^\"']*)[\"']?[[:space:]]*(#.*)?$/\1/p" "$file" | head -n1
|
|
}
|
|
|
|
# Rewrite a scalar assignment in place, preserving double-quoted style.
|
|
pkgbuild_set() {
|
|
local var="$1" val="$2" file="${3:-PKGBUILD}"
|
|
grep -qE "^${var}=" "$file" || die "no ${var}= assignment in ${file}"
|
|
sed -i -E "s|^${var}=.*|${var}=\"${val}\"|" "$file"
|
|
}
|
|
|
|
# Expand a PKGBUILD's arrays/vars by sourcing it in a subshell and printing the
|
|
# result. Safe enough: these are our own package definitions. Used instead of
|
|
# grep/sed so that ${pkgver} / ${_srcname} substitutions actually resolve.
|
|
#
|
|
# Usage: pkgbuild_expand <file> <bash snippet printing what you want>
|
|
pkgbuild_expand() {
|
|
local file="$1" snippet="$2"
|
|
(
|
|
set +euo pipefail
|
|
# Neutralise anything that would run during a real build.
|
|
# shellcheck disable=SC1090
|
|
source "$file" >/dev/null 2>&1 || true
|
|
eval "$snippet"
|
|
)
|
|
}
|
|
|
|
# Print the elements of an array variable, one per line, NUL-safe enough for
|
|
# source entries (which never contain newlines).
|
|
pkgbuild_array() {
|
|
local file="$1" name="$2"
|
|
pkgbuild_expand "$file" "printf '%s\n' \"\${${name}[@]}\" 2>/dev/null"
|
|
}
|
|
|
|
# True if the named array exists in the PKGBUILD (even if empty).
|
|
pkgbuild_has_array() {
|
|
local file="$1" name="$2"
|
|
[ "$(pkgbuild_expand "$file" "declare -p ${name} >/dev/null 2>&1 && echo yes")" = "yes" ]
|
|
}
|
|
|
|
# Replace `name=( ... )` in a PKGBUILD with the given values.
|
|
#
|
|
# This exists to replace the line-counting sed in the old freetube push.sh
|
|
# (`sednewline="n;"` accumulating `n;n;n;`), which silently corrupts the file
|
|
# whenever a source array's length changes. Matches `^name=(` through its
|
|
# closing `)`, so it handles both one-line and multi-line blocks, and appends
|
|
# the array if it is missing entirely.
|
|
replace_array() {
|
|
local file="$1" name="$2"; shift 2
|
|
local block v
|
|
if [ "$#" -eq 1 ]; then
|
|
block="${name}=(\"$1\")"
|
|
else
|
|
block="${name}=("
|
|
for v in "$@"; do block+=$'\n'" \"${v}\""; done
|
|
block+=$'\n'")"
|
|
fi
|
|
|
|
if ! grep -qE "^${name}=\(" "$file"; then
|
|
printf '%s\n' "$block" >> "$file"
|
|
return 0
|
|
fi
|
|
|
|
awk -v name="$name" -v block="$block" '
|
|
BEGIN { skip = 0 }
|
|
skip {
|
|
# Consume until the line that closes the array.
|
|
if ($0 ~ /\)[[:space:]]*$/) skip = 0
|
|
next
|
|
}
|
|
$0 ~ "^" name "=\\(" {
|
|
print block
|
|
# A one-line `name=("x")` closes on the same line; a block does not.
|
|
if ($0 !~ /\)[[:space:]]*$/) skip = 1
|
|
next
|
|
}
|
|
{ print }
|
|
' "$file" > "${file}.tmp"
|
|
|
|
mv "${file}.tmp" "$file"
|
|
}
|
|
|
|
# Delete a whole `name=( ... )` block (used to retire sha1sums/md5sums).
|
|
remove_array() {
|
|
local file="$1" name="$2"
|
|
grep -qE "^${name}=\(" "$file" || return 0
|
|
awk -v name="$name" '
|
|
BEGIN { skip = 0 }
|
|
skip { if ($0 ~ /\)[[:space:]]*$/) skip = 0; next }
|
|
$0 ~ "^" name "=\\(" { if ($0 !~ /\)[[:space:]]*$/) skip = 1; next }
|
|
{ print }
|
|
' "$file" > "${file}.tmp"
|
|
mv "${file}.tmp" "$file"
|
|
}
|
|
|
|
# Split a source entry into its rename target and its URL/path.
|
|
# "foo.AppImage::https://x/y.AppImage" -> name=foo.AppImage loc=https://x/y.AppImage
|
|
# "https://x/y.tar.gz" -> name=y.tar.gz loc=https://x/y.tar.gz
|
|
# "reflex.desktop" -> name=reflex.desktop loc=reflex.desktop
|
|
source_name() {
|
|
local e="$1"
|
|
if [[ "$e" == *"::"* ]]; then printf '%s' "${e%%::*}"; else printf '%s' "${e##*/}"; fi
|
|
}
|
|
source_loc() { local e="$1"; printf '%s' "${e##*::}"; }
|
|
|
|
is_remote() { [[ "$(source_loc "$1")" =~ ^(https?|ftp):// ]]; }
|
|
|
|
# VCS sources (git+https://..., svn+..., etc) are not fixed artefacts and
|
|
# makepkg expects a literal SKIP for them rather than a digest.
|
|
is_vcs() { [[ "$(source_loc "$1")" =~ ^(git|svn|hg|bzr)(\+|:) ]]; }
|
|
|
|
# Download a URL once per run and hand back the cached path.
|
|
#
|
|
# Downloads go to a content-addressed cache path, never to the source entry's
|
|
# `::` rename target. That matters for multi-arch: freetube's x86_64 and
|
|
# aarch64 entries both rename to freetube.AppImage, so writing to the rename
|
|
# target makes the second arch find the first arch's file and silently hash
|
|
# the wrong binary. This is the same collision that makes `makepkg -g`
|
|
# unusable for multi-arch PKGBUILDs.
|
|
download_cached() {
|
|
local url="$1" key path
|
|
[ -n "${DL_CACHE:-}" ] || die "download_cached: DL_CACHE is not set"
|
|
mkdir -p "$DL_CACHE"
|
|
key="$(printf '%s' "$url" | sha256sum | cut -c1-32)"
|
|
path="${DL_CACHE}/${key}"
|
|
|
|
if [ ! -s "$path" ]; then
|
|
info "fetching ${url}" >&2
|
|
curl --silent --show-error --location --fail --retry 3 --retry-delay 2 \
|
|
--max-time 900 -o "${path}.part" "$url" \
|
|
|| die "download failed: ${url}"
|
|
mv "${path}.part" "$path"
|
|
fi
|
|
printf '%s' "$path"
|
|
}
|
|
|
|
# Regenerate .SRCINFO and refuse to accept a broken one.
|
|
#
|
|
# This is the guard that would have prevented every observed breakage: an empty
|
|
# or inconsistent .SRCINFO is exactly what the AUR rejects, and the old script
|
|
# committed and pushed it anyway because it had no `set -e`.
|
|
regen_srcinfo() {
|
|
local dir="${1:-.}"
|
|
local out rc=0
|
|
out="$(cd "$dir" && makepkg --printsrcinfo 2>&1)" || rc=$?
|
|
|
|
if [ "$rc" -ne 0 ] || [ -z "${out//[[:space:]]/}" ]; then
|
|
printf '%s\n' "$out" >&2
|
|
die "makepkg --printsrcinfo produced no usable output (exit ${rc}).
|
|
Is this running under a real Arch makepkg? The CI job must use the
|
|
archlinux container; Ubuntu's build environment silently yields an
|
|
empty .SRCINFO, which the AUR then rejects."
|
|
fi
|
|
|
|
printf '%s\n' "$out" > "${dir}/.SRCINFO"
|
|
|
|
local pb_ver sr_ver
|
|
pb_ver="$(pkgbuild_get pkgver "${dir}/PKGBUILD")"
|
|
sr_ver="$(sed -n -E 's/^[[:space:]]*pkgver = (.*)$/\1/p' "${dir}/.SRCINFO" | head -n1)"
|
|
[ -n "$sr_ver" ] || die ".SRCINFO has no pkgver"
|
|
[ "$pb_ver" = "$sr_ver" ] \
|
|
|| die ".SRCINFO pkgver '${sr_ver}' disagrees with PKGBUILD pkgver '${pb_ver}'"
|
|
}
|