diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4b64690..6e48346 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -46,4 +46,5 @@ jobs: ./bin/omarchy-pkgs self-test ./bin/omarchy-release self-test ./tests/partial-release.sh + ./tests/published-build-plan.sh ' diff --git a/bin/check-versions b/bin/check-versions index 925570f..6f0558f 100755 --- a/bin/check-versions +++ b/bin/check-versions @@ -172,8 +172,8 @@ check_package() { # it because that exact filename is already published with different bytes. # If the artifact for this version already exists in the channel, there is # nothing to build regardless of which direction the versions differ. - if compgen -G "$REPO_ROOT/$mirror/$ARCH/${pkg}-${pkgbuild_version}-*.pkg.tar."[!s]* >/dev/null 2>&1; then - print_warning "$pkg $pkgbuild_version is already published — this checkout is behind the channel; not queueing" + if package_version_is_published "$REPO_ROOT/$mirror/$ARCH" "$pkg" "$pkgbuild_version" "$ARCH"; then + print_warning "$pkg $pkgbuild_version is already published; not queueing" return 1 fi diff --git a/build/build.sh b/build/build.sh index 96500e7..4c39147 100755 --- a/build/build.sh +++ b/build/build.sh @@ -531,9 +531,17 @@ check_needs_build() { if [[ "$local_version" == "$pkgbuild_version" ]]; then return 1 # Already up to date - else - return 0 # Needs building fi + + # Match check-versions: a retained archive is already published even when + # the DB now indexes a newer release (for example, 4.0.4rc1 vs 4.0.3). + # Rebuilding it would produce different bytes under an immutable filename. + if package_version_is_published "$FINAL_OUTPUT_DIR" "$pkg" "$pkgbuild_version" "$ARCH"; then + echo " + $pkg $pkgbuild_version - archive already published; skipping rebuild" + return 1 + fi + + return 0 # Needs building } # Collect packages that should be built for the selected mirror diff --git a/helpers/package-metadata.sh b/helpers/package-metadata.sh index a3ad047..ce46e92 100644 --- a/helpers/package-metadata.sh +++ b/helpers/package-metadata.sh @@ -191,6 +191,18 @@ package_supports_arch() { esac } +# The channel DB indexes only its newest version, but older published archives +# remain immutable. Both the scheduler and build planner must skip an existing +# filename even when the checkout differs from the version currently indexed. +package_version_is_published() { + local repo_dir="$1" package="$2" version="$3" target="$4" path + for path in "$repo_dir/$package-$version-$target.pkg.tar."* \ + "$repo_dir/$package-$version-any.pkg.tar."*; do + [[ -f "$path" && "$path" != *.sig ]] && return 0 + done + return 1 +} + # Channel membership: where a package may be published. Packages without a # `channels` key are members of every channel (they flow edge -> rc -> stable). package_has_channels() { diff --git a/tests/published-build-plan.sh b/tests/published-build-plan.sh new file mode 100755 index 0000000..538eff7 --- /dev/null +++ b/tests/published-build-plan.sh @@ -0,0 +1,71 @@ +#!/bin/bash +# The timer and builder must agree when an older archive remains published. +set -euo pipefail +BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..") +TEST_ROOT=$(mktemp -d) +trap 'rm -rf "$TEST_ROOT"' EXIT +mkdir -p "$TEST_ROOT/bin" "$TEST_ROOT/pkgbuilds" "$TEST_ROOT/state" +cp "$BUILD_ROOT/bin/check-versions" "$TEST_ROOT/bin/" +cp -r "$BUILD_ROOT/helpers" "$BUILD_ROOT/build" "$TEST_ROOT/" +export OMARCHY_STATE_DIR="$TEST_ROOT/state" +unset OMARCHY_REPO_ROOT OMARCHY_RC_PINS OMARCHY_DEFER_RUNTIME_DEPS + +fixture() { + local name=$1 version=$2 release=$3 + mkdir -p "$TEST_ROOT/pkgbuilds/$name/.omarchy" + printf '{"source":"local","channels":["edge"]}\n' > "$TEST_ROOT/pkgbuilds/$name/.omarchy/package.json" + cat > "$TEST_ROOT/pkgbuilds/$name/PKGBUILD" < "$TEST_ROOT/db/$name-$version/desc" +} +db_entry omarchy 4.0.4rc1-1 +db_entry omarchy-settings 4.0.4rc1-1 +for package in current update rebuild; do db_entry "$package" 1-1; done +printf '%s\n' new-package other-arch rebuild signature-only update | sort > "$TEST_ROOT/expected" + +for arch in x86_64 aarch64; do + repo="$TEST_ROOT/pkgs.omarchy.org/edge/$arch" + mkdir -p "$repo" + tar --zstd -cf "$repo/omarchy.db.tar.zst" -C "$TEST_ROOT/db" . + touch "$repo/omarchy-4.0.3-1-$arch.pkg.tar.zst" + touch "$repo/omarchy-settings-4.0.3-1-any.pkg.tar.xz" + touch "$repo/unindexed-1-1-$arch.pkg.tar.zst" + touch "$repo/rebuild-1-1-$arch.pkg.tar.zst" + touch "$repo/signature-only-1-1-$arch.pkg.tar.zst.sig" + other=x86_64 + [[ "$arch" == x86_64 ]] && other=aarch64 + touch "$repo/other-arch-1-1-$other.pkg.tar.zst" + + "$TEST_ROOT/bin/check-versions" --arch "$arch" > "$TEST_ROOT/check.log" 2>&1 + sort "$TEST_ROOT/state/.sync-needed-edge-$arch" > "$TEST_ROOT/queue" + diff -u "$TEST_ROOT/expected" "$TEST_ROOT/queue" + + for selection in '' 'omarchy omarchy-settings current update rebuild unindexed signature-only other-arch new-package'; do + ARCH="$arch" MIRROR=edge DRY_RUN=true PACKAGES="$selection" \ + PKGBUILDS_DIR="$TEST_ROOT/pkgbuilds" HELPERS_DIR="$TEST_ROOT/helpers" \ + FINAL_OUTPUT_DIR="$repo" BUILD_PLAN_DIR="$TEST_ROOT/plan" \ + "$TEST_ROOT/build/build.sh" > "$TEST_ROOT/plan.log" 2>&1 + sort "$TEST_ROOT/plan/packages" > "$TEST_ROOT/planned" + diff -u "$TEST_ROOT/queue" "$TEST_ROOT/planned" + grep -q 'omarchy 4.0.3-1 - archive already published' "$TEST_ROOT/plan.log" + done + printf 'PASS: %s scheduler and explicit/unscoped plans skip retained archives, allow new versions and releases\n' "$arch" +done