Bump unless every trigger is recorded and matches
A review at xhigh found several ways this command could report success while delivering nothing, which is the exact failure it exists to prevent. A trigger named in rebuild_on but missing from rebuilt_against was never examined, because the comparison walked the record rather than the declared list. Adding a dependency to a package already opted in left that dependency untracked forever. The comparison now walks the declared triggers, so a name the record does not carry reads as changed. That also retires the separate baseline path. Recording a package's triggers without bumping pkgrel certified a build nobody had checked: a package already broken by a release that moved before it opted in would be recorded as current and never rebuilt. Opting in now costs one rebuild, which is much the cheaper mistake. A bumped version was only checked against the checked-in one. The floor is what users already have, so a checkout that had fallen behind the repository could be bumped to a version pacman orders below the package it means to replace, with the record advancing regardless. The published database is now the floor, and an unreadable one warns rather than blocks. Metadata that did not parse dropped its package out of an unscoped run without a word, an unreadable rebuild_on being indistinguishable from an absent one. It is now reported and fails the run. The workflow reads versions from mirror.omarchy.org, the mirror the x86_64 builder itself uses, rather than whichever mirror the container defaulted to. A mirror running ahead of the builder would record a version the build never linked against, and nothing re-fires once the record matches. aarch64 stays uncovered and is documented as such: those builds resolve from Arch Linux ARM, one record cannot describe two architectures, and only x86_64 is published today. bin/sync-rebuilds --self-test covers each of these against a throwaway repository root with pacman and curl stubbed. 🤖 Generated by Opus 5 in Claude Code. Reviewed by Codex XHigh. Co-Authored-By: Codex XHigh <codex@openai.com>
This commit is contained in:
@@ -24,9 +24,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
# Runs in an Arch container because the question being asked is what the
|
# Runs in an Arch container against the mirror the x86_64 builder itself
|
||||||
# build container will link against, and pacman's own view of core/extra is
|
# uses, because the question being asked is what that builder will link
|
||||||
# the only answer that matches.
|
# against and a different mirror can be hours ahead of it. Recording a
|
||||||
|
# version the build never saw is the one failure this command must not
|
||||||
|
# have: nothing re-fires once the record matches.
|
||||||
- name: Bump pkgrel for packages whose dependencies moved
|
- name: Bump pkgrel for packages whose dependencies moved
|
||||||
run: |
|
run: |
|
||||||
docker run --rm \
|
docker run --rm \
|
||||||
@@ -40,6 +42,7 @@ jobs:
|
|||||||
archlinux:base-devel bash -lc '
|
archlinux:base-devel bash -lc '
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
printf "Server = https://mirror.omarchy.org/\$repo/os/\$arch\n" > /etc/pacman.d/mirrorlist
|
||||||
pacman -Syu --noconfirm jq
|
pacman -Syu --noconfirm jq
|
||||||
|
|
||||||
groupadd -g "$HOST_GID" runner
|
groupadd -g "$HOST_GID" runner
|
||||||
|
|||||||
@@ -288,6 +288,7 @@ it describes.
|
|||||||
```bash
|
```bash
|
||||||
bin/sync-rebuilds # Bump every package whose dependencies moved
|
bin/sync-rebuilds # Bump every package whose dependencies moved
|
||||||
bin/sync-rebuilds quickshell-git # Update specific packages
|
bin/sync-rebuilds quickshell-git # Update specific packages
|
||||||
|
bin/sync-rebuilds --self-test # Run the regression tests
|
||||||
```
|
```
|
||||||
|
|
||||||
Some packages have to be rebuilt when something they link against changes, even though nothing in their own source moved. A Qt private-API consumer is the usual case: `Qt_6_PRIVATE_API` symbols are not covered by the soname, so a qt6-base point release can leave an installed binary unable to resolve a symbol at startup, and pacman upgrades Qt out from under it because the dependency is unversioned. The package still builds from the same git commit, so nothing in the normal version check notices.
|
Some packages have to be rebuilt when something they link against changes, even though nothing in their own source moved. A Qt private-API consumer is the usual case: `Qt_6_PRIVATE_API` symbols are not covered by the soname, so a qt6-base point release can leave an installed binary unable to resolve a symbol at startup, and pacman upgrades Qt out from under it because the dependency is unversioned. The package still builds from the same git commit, so nothing in the normal version check notices.
|
||||||
@@ -298,13 +299,17 @@ A package names those dependencies in `.omarchy/package.json`:
|
|||||||
{ "source": "aur", "sync": false, "rebuild_on": ["qt6-base", "qt6-declarative", "qt6-wayland"] }
|
{ "source": "aur", "sync": false, "rebuild_on": ["qt6-base", "qt6-declarative", "qt6-wayland"] }
|
||||||
```
|
```
|
||||||
|
|
||||||
`bin/sync-rebuilds` reads each named package's version from the official repositories and compares it to `rebuilt_against`, the record of what the checked-in pkgrel was last bumped for. When they differ it bumps pkgrel and rewrites the record. A package with no record yet is only recorded, never bumped: what its published build linked against is not knowable from here, so the first run establishes the baseline and the next change acts on it.
|
`bin/sync-rebuilds` reads each named package's version from the official repositories and compares it to `rebuilt_against`, the record of what the checked-in pkgrel was last bumped for. pkgrel is bumped unless every name in `rebuild_on` is recorded and still matches, so a name the record does not carry reads as changed rather than going unexamined forever. Opting a package in therefore buys one rebuild: what its published build actually linked against is not knowable from here, and a record written without a rebuild would certify a build nobody checked.
|
||||||
|
|
||||||
The bump is the point of the command, and it has to land in git rather than in the builder. A rebuild that reuses the published version string produces a package pacman will never offer anyone, so merely unlocking the build gate would ship nothing. Bumping pkgrel needs no other change: `bin/check-versions` and the builder both already rebuild when pkgrel moves.
|
The bump is the point of the command, and it has to land in git rather than in the builder. A rebuild that reuses the published version string produces a package pacman will never offer anyone, so merely unlocking the build gate would ship nothing. Bumping pkgrel needs no other change: `bin/check-versions` and the builder both already rebuild when pkgrel moves.
|
||||||
|
|
||||||
For an AUR-synced package the bump is expressed as the dotted Omarchy pkgrel suffix in the metadata as well as in the PKGBUILD, because the next AUR sync replaces the PKGBUILD wholesale and would otherwise drop it.
|
For an AUR-synced package the bump is expressed as the dotted Omarchy pkgrel suffix in the metadata as well as in the PKGBUILD, because the next AUR sync replaces the PKGBUILD wholesale and would otherwise drop it.
|
||||||
|
|
||||||
Versions are read from the local pacman database, so this runs on Arch or in an Arch container against a synced database. Only `core`, `extra` and `multilib` count: a Qt release sitting in testing or kde-unstable is not what the builder will link against, and rebuilding for it would ship a package built against the wrong ABI.
|
The bumped version is checked against the published one as well as the checked-in one, and refused when pacman would not order it higher. The checked-in version is not the floor; what a user already has is, and a checkout that has fallen behind the repository can otherwise be bumped to something that loses to the package it means to replace. That check is skipped with a warning when the published database cannot be read.
|
||||||
|
|
||||||
|
Versions are read from the local pacman database, so this runs on Arch or in an Arch container against a synced database. Only `core`, `extra` and `multilib` count: a Qt release sitting in testing or kde-unstable is not what the builder will link against, and rebuilding for it would ship a package built against the wrong ABI. The workflow points that database at `mirror.omarchy.org`, the mirror the x86_64 builder itself uses, because a mirror running ahead of the builder would record a version the build never linked against and nothing re-fires once the record matches.
|
||||||
|
|
||||||
|
aarch64 is not covered. Those builds resolve Qt from Arch Linux ARM, which can lag Arch, so one record cannot describe both architectures. Only x86_64 is published today, so nothing currently ships from the untracked side; if ARM publishing starts, `rebuilt_against` has to become per-architecture before this can be trusted there.
|
||||||
|
|
||||||
### Other
|
### Other
|
||||||
|
|
||||||
|
|||||||
+343
-45
@@ -6,13 +6,23 @@ source "$BUILD_ROOT/helpers/message-helpers.sh"
|
|||||||
source "$BUILD_ROOT/helpers/paths.sh"
|
source "$BUILD_ROOT/helpers/paths.sh"
|
||||||
source "$BUILD_ROOT/helpers/package-metadata.sh"
|
source "$BUILD_ROOT/helpers/package-metadata.sh"
|
||||||
|
|
||||||
|
TEMP_DIR=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$TEMP_DIR"' EXIT
|
||||||
|
|
||||||
SPECIFIC_PACKAGES=()
|
SPECIFIC_PACKAGES=()
|
||||||
|
SELF_TEST=false
|
||||||
|
|
||||||
# Only the repositories a user actually installs from. A Qt release sitting in
|
# Only the repositories a user actually installs from. A Qt release sitting in
|
||||||
# testing or kde-unstable is not what the build container will link against, and
|
# testing or kde-unstable is not what the build container will link against, and
|
||||||
# rebuilding for it would ship a package built against the wrong ABI.
|
# rebuilding for it would ship a package built against the wrong ABI.
|
||||||
OFFICIAL_REPOS=" core extra multilib core-debug extra-debug "
|
OFFICIAL_REPOS=" core extra multilib core-debug extra-debug "
|
||||||
|
|
||||||
|
# The published repository, used as the floor a bumped pkgrel has to clear.
|
||||||
|
# Only x86_64 is published today; aarch64 has no repository to compare against.
|
||||||
|
PUBLISHED_BASE_URL="${OMARCHY_PUBLISHED_BASE_URL:-https://pkgs.omarchy.org}"
|
||||||
|
PUBLISHED_MIRRORS=(edge stable)
|
||||||
|
PUBLISHED_ARCH=x86_64
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
Usage: $0 [PACKAGE...]
|
Usage: $0 [PACKAGE...]
|
||||||
@@ -24,23 +34,28 @@ A package opts in by naming those dependencies in .omarchy/package.json:
|
|||||||
|
|
||||||
{ "source": "aur", "sync": false, "rebuild_on": ["qt6-base"] }
|
{ "source": "aur", "sync": false, "rebuild_on": ["qt6-base"] }
|
||||||
|
|
||||||
The versions the current pkgrel was last bumped for are recorded alongside, in
|
The versions the current pkgrel was bumped for are recorded alongside, in
|
||||||
rebuilt_against, and written by this command:
|
rebuilt_against, and written by this command:
|
||||||
|
|
||||||
{ "rebuild_on": ["qt6-base"], "rebuilt_against": { "qt6-base": "6.11.2-2" } }
|
{ "rebuild_on": ["qt6-base"], "rebuilt_against": { "qt6-base": "6.11.2-2" } }
|
||||||
|
|
||||||
When a named package in the official repositories no longer matches what is
|
pkgrel is bumped unless every package named in rebuild_on is recorded and still
|
||||||
recorded, pkgrel is bumped and the record is rewritten. A package with no record
|
matches. A name that is missing from the record counts as changed, so opting a
|
||||||
yet is only recorded, never bumped: the versions its published build was linked
|
package in, or adding a dependency to one already opted in, buys one rebuild
|
||||||
against are not knowable from here, so the first run establishes the baseline
|
rather than a record that certifies a build nobody checked.
|
||||||
and the next real change acts on it.
|
|
||||||
|
|
||||||
The bump has to happen in git rather than in the builder, because a rebuild that
|
The bump has to happen in git rather than in the builder, because a rebuild that
|
||||||
reuses the published version string is a package pacman will never offer anyone.
|
reuses the published version string is a package pacman will never offer anyone.
|
||||||
|
For the same reason the bumped version is checked against the published one and
|
||||||
|
refused if it would not be an upgrade.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
PACKAGE One or more package names to update (optional)
|
PACKAGE One or more package names to update (optional)
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--self-test Run the built-in regression tests and exit
|
||||||
|
-h, --help Show this help
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
$0 # Update every package that declares rebuild_on
|
$0 # Update every package that declares rebuild_on
|
||||||
$0 quickshell-git # Update specific packages
|
$0 quickshell-git # Update specific packages
|
||||||
@@ -56,6 +71,10 @@ while [[ $# -gt 0 ]]; do
|
|||||||
usage
|
usage
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
|
--self-test)
|
||||||
|
SELF_TEST=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
--*)
|
--*)
|
||||||
print_error "Unknown option: $1"
|
print_error "Unknown option: $1"
|
||||||
exit 1
|
exit 1
|
||||||
@@ -67,15 +86,6 @@ while [[ $# -gt 0 ]]; do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
for tool in pacman vercmp jq; do
|
|
||||||
if ! command -v "$tool" >/dev/null 2>&1; then
|
|
||||||
print_error "$tool not found: reading trigger versions and ordering pkgrels both need pacman"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
print_header "Rebuild Trigger Sync"
|
|
||||||
|
|
||||||
UPDATED=0
|
UPDATED=0
|
||||||
SKIPPED=0
|
SKIPPED=0
|
||||||
FAILED=0
|
FAILED=0
|
||||||
@@ -98,6 +108,73 @@ repo_version() {
|
|||||||
' <<<"$info"
|
' <<<"$info"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare -A PUBLISHED_VERSION=()
|
||||||
|
PUBLISHED_LOADED=false
|
||||||
|
PUBLISHED_AVAILABLE=true
|
||||||
|
|
||||||
|
remember_published() {
|
||||||
|
local name="$1"
|
||||||
|
local version="$2"
|
||||||
|
local known="${PUBLISHED_VERSION[$name]:-}"
|
||||||
|
|
||||||
|
# A package can be in both mirrors at different revisions. The floor is the
|
||||||
|
# highest of them, because that is what a user could already have installed.
|
||||||
|
if [[ -z "$known" || "$(vercmp "$version" "$known")" -gt 0 ]]; then
|
||||||
|
PUBLISHED_VERSION["$name"]="$version"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Versions currently published, read from the repository databases. Split
|
||||||
|
# packages are stored under their own pkgname, so both %NAME% and %BASE% are
|
||||||
|
# recorded to make a pkgbase findable.
|
||||||
|
load_published_versions() {
|
||||||
|
[[ "$PUBLISHED_LOADED" == true ]] && return 0
|
||||||
|
PUBLISHED_LOADED=true
|
||||||
|
|
||||||
|
local mirror db name base version
|
||||||
|
|
||||||
|
for mirror in "${PUBLISHED_MIRRORS[@]}"; do
|
||||||
|
db="$TEMP_DIR/published-$mirror.db.tar.zst"
|
||||||
|
|
||||||
|
if ! curl -fsSL --max-time 120 -o "$db" \
|
||||||
|
"$PUBLISHED_BASE_URL/$mirror/$PUBLISHED_ARCH/omarchy.db.tar.zst" 2>/dev/null; then
|
||||||
|
print_warning "Could not read the published $mirror database; bumps are not checked against it this run"
|
||||||
|
PUBLISHED_AVAILABLE=false
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
while IFS=$'\t' read -r name base version; do
|
||||||
|
[[ -n "$name" && -n "$version" ]] && remember_published "$name" "$version"
|
||||||
|
[[ -n "$base" && -n "$version" ]] && remember_published "$base" "$version"
|
||||||
|
done < <(
|
||||||
|
tar -xOf "$db" --wildcards '*/desc' 2>/dev/null | awk '
|
||||||
|
function emit() {
|
||||||
|
if (name != "" && version != "") print name "\t" base "\t" version
|
||||||
|
name=""; base=""; version=""
|
||||||
|
}
|
||||||
|
$0 == "%FILENAME%" { emit(); next }
|
||||||
|
$0 == "%NAME%" { if (name != "" && version != "") emit(); getline; name=$0; next }
|
||||||
|
$0 == "%BASE%" { getline; base=$0; next }
|
||||||
|
$0 == "%VERSION%" { getline; version=$0; next }
|
||||||
|
END { emit() }
|
||||||
|
'
|
||||||
|
)
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
published_version() {
|
||||||
|
local package="$1"
|
||||||
|
|
||||||
|
load_published_versions
|
||||||
|
echo "${PUBLISHED_VERSION[$package]:-}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# The pkgver of a full version string, with any epoch and pkgrel removed.
|
||||||
|
version_pkgver() {
|
||||||
|
local version="${1#*:}"
|
||||||
|
echo "${version%-*}"
|
||||||
|
}
|
||||||
|
|
||||||
pkgbuild_field() {
|
pkgbuild_field() {
|
||||||
local package_dir="$1"
|
local package_dir="$1"
|
||||||
local field="$2"
|
local field="$2"
|
||||||
@@ -178,6 +255,23 @@ record_triggers() {
|
|||||||
write_metadata "$package_dir" '.rebuilt_against = $current' --argjson current "$current"
|
write_metadata "$package_dir" '.rebuilt_against = $current' --argjson current "$current"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Metadata that does not parse would otherwise drop its package out of the run
|
||||||
|
# without a word, because every query of it reports nothing and an absent
|
||||||
|
# rebuild_on is indistinguishable from an unreadable one.
|
||||||
|
check_metadata_readable() {
|
||||||
|
local package_dir="$1"
|
||||||
|
local metadata
|
||||||
|
metadata=$(metadata_file_for_dir "$package_dir")
|
||||||
|
|
||||||
|
[[ -f "$metadata" ]] || return 0
|
||||||
|
|
||||||
|
if ! jq empty "$metadata" 2>/dev/null; then
|
||||||
|
print_error "Unreadable metadata, skipping $(basename "$package_dir"): $metadata"
|
||||||
|
((++FAILED))
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
sync_package() {
|
sync_package() {
|
||||||
local package="$1"
|
local package="$1"
|
||||||
local package_dir="$PKGBUILDS_DIR/$package"
|
local package_dir="$PKGBUILDS_DIR/$package"
|
||||||
@@ -188,6 +282,8 @@ sync_package() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
check_metadata_readable "$package_dir" || return 0
|
||||||
|
|
||||||
local triggers=()
|
local triggers=()
|
||||||
mapfile -t triggers < <(package_rebuild_triggers "$package_dir")
|
mapfile -t triggers < <(package_rebuild_triggers "$package_dir")
|
||||||
|
|
||||||
@@ -225,24 +321,17 @@ sync_package() {
|
|||||||
((++FAILED))
|
((++FAILED))
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
[[ -n "$recorded" && "$recorded" != "null" ]] || recorded="{}"
|
||||||
|
|
||||||
if [[ -z "$recorded" || "$recorded" == "null" ]]; then
|
# Walk the declared triggers rather than the record, so a name the record does
|
||||||
if ! record_triggers "$package_dir" "$current"; then
|
# not carry reads as changed instead of going unexamined forever.
|
||||||
print_error " Failed to record trigger versions for $package"
|
|
||||||
((++FAILED))
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
print_success " Recorded baseline: $(jq -r 'to_entries | map("\(.key) \(.value)") | join(", ")' <<<"$current")"
|
|
||||||
((++UPDATED))
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
local moved
|
local moved
|
||||||
if ! moved=$(jq -r --argjson current "$current" '
|
if ! moved=$(jq -r --argjson recorded "$recorded" '
|
||||||
to_entries
|
to_entries
|
||||||
| map(select($current[.key] != .value) | "\(.key) \(.value) -> \($current[.key])")
|
| map(select($recorded[.key] != .value)
|
||||||
|
| "\(.key) \($recorded[.key] // "unrecorded") -> \(.value)")
|
||||||
| join(", ")
|
| join(", ")
|
||||||
' <<<"$recorded"); then
|
' <<<"$current"); then
|
||||||
print_error " Could not compare recorded trigger versions for $package"
|
print_error " Could not compare recorded trigger versions for $package"
|
||||||
((++FAILED))
|
((++FAILED))
|
||||||
return 0
|
return 0
|
||||||
@@ -291,6 +380,19 @@ sync_package() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# The checked-in version is not the floor. What a user already has is, and a
|
||||||
|
# checkout that has fallen behind the repository can otherwise be bumped to
|
||||||
|
# something pacman orders below what it would replace.
|
||||||
|
local floor
|
||||||
|
floor=$(published_version "$package")
|
||||||
|
if [[ -n "$floor" && "$(version_pkgver "$floor")" == "$pkgver" ]]; then
|
||||||
|
if [[ "$(vercmp "$new_version" "$floor")" -le 0 ]]; then
|
||||||
|
print_error " $new_version would not be an upgrade over the published $floor; leaving $package alone"
|
||||||
|
((++FAILED))
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
if ! write_pkgrel "$package_dir" "$next_pkgrel"; then
|
if ! write_pkgrel "$package_dir" "$next_pkgrel"; then
|
||||||
print_error " Failed to bump pkgrel for $package"
|
print_error " Failed to bump pkgrel for $package"
|
||||||
((++FAILED))
|
((++FAILED))
|
||||||
@@ -313,28 +415,224 @@ sync_package() {
|
|||||||
((++UPDATED))
|
((++UPDATED))
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ ${#SPECIFIC_PACKAGES[@]} -gt 0 ]]; then
|
run_sync() {
|
||||||
|
if [[ ${#SPECIFIC_PACKAGES[@]} -gt 0 ]]; then
|
||||||
SPECIFIC_MODE=true
|
SPECIFIC_MODE=true
|
||||||
for package in "${SPECIFIC_PACKAGES[@]}"; do
|
for package in "${SPECIFIC_PACKAGES[@]}"; do
|
||||||
sync_package "$package"
|
sync_package "$package"
|
||||||
done
|
done
|
||||||
else
|
else
|
||||||
while IFS= read -r package; do
|
# Enumerated up front rather than streamed, so a producer that dies partway
|
||||||
sync_package "$package"
|
# cannot quietly shorten the list of packages considered.
|
||||||
done < <(packages_for_rebuild_sync)
|
local packages=()
|
||||||
fi
|
mapfile -t packages < <(package_dirs)
|
||||||
|
|
||||||
echo ""
|
local package_dir
|
||||||
if [[ $FAILED -gt 0 ]]; then
|
for package_dir in "${packages[@]}"; do
|
||||||
|
check_metadata_readable "$package_dir" || continue
|
||||||
|
if package_has_rebuild_triggers "$package_dir"; then
|
||||||
|
sync_package "$(basename "$package_dir")"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
if [[ $FAILED -gt 0 ]]; then
|
||||||
print_error "Rebuild trigger sync completed with failures"
|
print_error "Rebuild trigger sync completed with failures"
|
||||||
else
|
else
|
||||||
print_success "Rebuild trigger sync complete!"
|
print_success "Rebuild trigger sync complete!"
|
||||||
fi
|
fi
|
||||||
echo " Target: $PKGBUILDS_DIR"
|
echo " Target: $PKGBUILDS_DIR"
|
||||||
echo " Updated: $UPDATED"
|
echo " Updated: $UPDATED"
|
||||||
echo " Skipped: $SKIPPED"
|
echo " Skipped: $SKIPPED"
|
||||||
echo " Failed: $FAILED"
|
echo " Failed: $FAILED"
|
||||||
|
|
||||||
if [[ $FAILED -gt 0 ]]; then
|
[[ $FAILED -eq 0 ]]
|
||||||
exit 1
|
}
|
||||||
|
|
||||||
|
# --- self-test ---------------------------------------------------------------
|
||||||
|
|
||||||
|
# Each case runs the real script against a throwaway repository root, with
|
||||||
|
# pacman and curl replaced by stubs, so what is exercised is the decision path
|
||||||
|
# itself rather than a restatement of it.
|
||||||
|
selftest_root() {
|
||||||
|
local name="$1"
|
||||||
|
local root="$TEMP_DIR/case-$name"
|
||||||
|
|
||||||
|
mkdir -p "$root/bin" "$root/helpers" "$root/pkgbuilds" "$root/stub"
|
||||||
|
cp "$BUILD_ROOT/bin/sync-rebuilds" "$root/bin/"
|
||||||
|
cp "$BUILD_ROOT"/helpers/*.sh "$root/helpers/"
|
||||||
|
echo "$root"
|
||||||
|
}
|
||||||
|
|
||||||
|
selftest_package() {
|
||||||
|
local root="$1" name="$2" pkgrel="$3" metadata="$4" pkgver="${5:-1.0}"
|
||||||
|
local dir="$root/pkgbuilds/$name"
|
||||||
|
|
||||||
|
mkdir -p "$dir/.omarchy"
|
||||||
|
printf 'pkgname=%s\npkgver=%s\npkgrel=%s\narch=(x86_64)\n' "$name" "$pkgver" "$pkgrel" > "$dir/PKGBUILD"
|
||||||
|
printf '%s\n' "$metadata" > "$dir/.omarchy/package.json"
|
||||||
|
}
|
||||||
|
|
||||||
|
selftest_pacman() {
|
||||||
|
local root="$1"
|
||||||
|
shift
|
||||||
|
printf '%s\n' "$@" > "$root/stub/versions"
|
||||||
|
|
||||||
|
cat > "$root/stub/pacman" <<'STUB'
|
||||||
|
#!/bin/bash
|
||||||
|
[[ "$1" == "-Si" ]] || exit 1
|
||||||
|
version=$(awk -F= -v p="$2" '$1 == p { print $2; exit }' "$(dirname "$0")/versions")
|
||||||
|
[[ -n "$version" ]] || exit 1
|
||||||
|
printf 'Repository : extra\nName : %s\nVersion : %s\n\n' "$2" "$version"
|
||||||
|
STUB
|
||||||
|
chmod +x "$root/stub/pacman"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Serves a repository database assembled by hand from name=version pairs. With
|
||||||
|
# none given the stub fails, which is how the unreachable-repository path is
|
||||||
|
# exercised.
|
||||||
|
selftest_published() {
|
||||||
|
local root="$1"
|
||||||
|
shift
|
||||||
|
local staging="$root/stub/db"
|
||||||
|
local entry name version
|
||||||
|
|
||||||
|
if [[ $# -gt 0 ]]; then
|
||||||
|
rm -rf "$staging"
|
||||||
|
mkdir -p "$staging"
|
||||||
|
for entry in "$@"; do
|
||||||
|
name="${entry%=*}"
|
||||||
|
version="${entry#*=}"
|
||||||
|
mkdir -p "$staging/$name-$version"
|
||||||
|
printf '%%FILENAME%%\n%s-%s-x86_64.pkg.tar.zst\n\n%%NAME%%\n%s\n\n%%BASE%%\n%s\n\n%%VERSION%%\n%s\n' \
|
||||||
|
"$name" "$version" "$name" "$name" "$version" > "$staging/$name-$version/desc"
|
||||||
|
done
|
||||||
|
tar --zstd -cf "$root/stub/omarchy.db.tar.zst" -C "$staging" .
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat > "$root/stub/curl" <<'STUB'
|
||||||
|
#!/bin/bash
|
||||||
|
out=""
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
-o) out="$2"; shift 2 ;;
|
||||||
|
*) shift ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
db="$(dirname "$0")/omarchy.db.tar.zst"
|
||||||
|
[[ -f "$db" && -n "$out" ]] || exit 22
|
||||||
|
cp "$db" "$out"
|
||||||
|
STUB
|
||||||
|
chmod +x "$root/stub/curl"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_self_test() {
|
||||||
|
local failures=0
|
||||||
|
local root
|
||||||
|
|
||||||
|
check() {
|
||||||
|
local label="$1" expected="$2" got="$3"
|
||||||
|
if [[ "$got" == "$expected" ]]; then
|
||||||
|
echo " ok: $label"
|
||||||
|
else
|
||||||
|
echo " FAIL: $label -> $got (expected $expected)"
|
||||||
|
failures=$((failures + 1))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run_case() {
|
||||||
|
local root="$1"
|
||||||
|
shift
|
||||||
|
local status=0
|
||||||
|
PATH="$root/stub:$PATH" "$root/bin/sync-rebuilds" "$@" > "$root/output" 2>&1 || status=$?
|
||||||
|
echo "$status"
|
||||||
|
}
|
||||||
|
|
||||||
|
pkgrel_of() {
|
||||||
|
grep -m1 '^pkgrel=' "$1/PKGBUILD" | cut -d= -f2
|
||||||
|
}
|
||||||
|
|
||||||
|
print_header "sync-rebuilds self-test"
|
||||||
|
|
||||||
|
echo "A trigger missing from the record counts as changed:"
|
||||||
|
root=$(selftest_root partial)
|
||||||
|
selftest_package "$root" t-partial 1 '{"source":"local","rebuild_on":["dep-a","dep-b"],"rebuilt_against":{"dep-a":"1-1"}}'
|
||||||
|
selftest_pacman "$root" dep-a=1-1 dep-b=2-2
|
||||||
|
selftest_published "$root"
|
||||||
|
check "run succeeds" 0 "$(run_case "$root")"
|
||||||
|
check "pkgrel bumped" 2 "$(pkgrel_of "$root/pkgbuilds/t-partial")"
|
||||||
|
check "unrecorded trigger now recorded" "2-2" \
|
||||||
|
"$(jq -r '.rebuilt_against["dep-b"]' "$root/pkgbuilds/t-partial/.omarchy/package.json")"
|
||||||
|
|
||||||
|
echo "Opting a package in buys a rebuild rather than a bare record:"
|
||||||
|
root=$(selftest_root fresh)
|
||||||
|
selftest_package "$root" t-fresh 1 '{"source":"local","rebuild_on":["dep-a"]}'
|
||||||
|
selftest_pacman "$root" dep-a=1-1
|
||||||
|
selftest_published "$root"
|
||||||
|
check "run succeeds" 0 "$(run_case "$root")"
|
||||||
|
check "pkgrel bumped" 2 "$(pkgrel_of "$root/pkgbuilds/t-fresh")"
|
||||||
|
check "trigger recorded" "1-1" \
|
||||||
|
"$(jq -r '.rebuilt_against["dep-a"]' "$root/pkgbuilds/t-fresh/.omarchy/package.json")"
|
||||||
|
|
||||||
|
echo "An unchanged package is left alone:"
|
||||||
|
root=$(selftest_root current)
|
||||||
|
selftest_package "$root" t-current 1 '{"source":"local","rebuild_on":["dep-a"],"rebuilt_against":{"dep-a":"1-1"}}'
|
||||||
|
selftest_pacman "$root" dep-a=1-1
|
||||||
|
selftest_published "$root"
|
||||||
|
check "run succeeds" 0 "$(run_case "$root")"
|
||||||
|
check "pkgrel untouched" 1 "$(pkgrel_of "$root/pkgbuilds/t-current")"
|
||||||
|
|
||||||
|
echo "Metadata that does not parse fails the run instead of vanishing from it:"
|
||||||
|
root=$(selftest_root unreadable)
|
||||||
|
selftest_package "$root" t-broken 1 '{"source":"local","rebuild_on":["dep-a"'
|
||||||
|
selftest_package "$root" t-good 1 '{"source":"local","rebuild_on":["dep-a"]}'
|
||||||
|
selftest_pacman "$root" dep-a=1-1
|
||||||
|
selftest_published "$root"
|
||||||
|
check "run fails" 1 "$(run_case "$root")"
|
||||||
|
check "the readable package is still processed" 2 "$(pkgrel_of "$root/pkgbuilds/t-good")"
|
||||||
|
|
||||||
|
echo "A bump that pacman would not order above the published package is refused:"
|
||||||
|
root=$(selftest_root floor)
|
||||||
|
selftest_package "$root" t-floor 1 '{"source":"local","rebuild_on":["dep-a"],"rebuilt_against":{"dep-a":"0-0"}}'
|
||||||
|
selftest_pacman "$root" dep-a=1-1
|
||||||
|
selftest_published "$root" t-floor=1.0-5
|
||||||
|
check "run fails" 1 "$(run_case "$root")"
|
||||||
|
check "pkgrel untouched" 1 "$(pkgrel_of "$root/pkgbuilds/t-floor")"
|
||||||
|
check "record untouched" "0-0" \
|
||||||
|
"$(jq -r '.rebuilt_against["dep-a"]' "$root/pkgbuilds/t-floor/.omarchy/package.json")"
|
||||||
|
|
||||||
|
echo "An AUR-synced package is bumped as a dotted suffix that sync-aur reapplies:"
|
||||||
|
root=$(selftest_root aur)
|
||||||
|
selftest_package "$root" t-aur 3 '{"source":"aur","rebuild_on":["dep-a"],"rebuilt_against":{"dep-a":"0-0"}}'
|
||||||
|
selftest_pacman "$root" dep-a=1-1
|
||||||
|
selftest_published "$root"
|
||||||
|
check "run succeeds" 0 "$(run_case "$root")"
|
||||||
|
check "pkgrel suffixed" "3.1" "$(pkgrel_of "$root/pkgbuilds/t-aur")"
|
||||||
|
check "suffix recorded for the next AUR sync" 1 \
|
||||||
|
"$(jq -r '.pkgrel.suffix' "$root/pkgbuilds/t-aur/.omarchy/package.json")"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
if [[ "$failures" -eq 0 ]]; then
|
||||||
|
print_success "Self-test passed"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
print_error "$failures self-test failure(s)"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "$SELF_TEST" == true ]]; then
|
||||||
|
cmd_self_test
|
||||||
|
exit $?
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
for tool in pacman vercmp jq curl; do
|
||||||
|
if ! command -v "$tool" >/dev/null 2>&1; then
|
||||||
|
print_error "$tool not found: reading trigger versions and ordering pkgrels both need pacman"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
print_header "Rebuild Trigger Sync"
|
||||||
|
|
||||||
|
run_sync
|
||||||
|
|||||||
Reference in New Issue
Block a user