diff --git a/.github/workflows/redbear-ci.yml b/.github/workflows/redbear-ci.yml index d407709685..c57a318c20 100644 --- a/.github/workflows/redbear-ci.yml +++ b/.github/workflows/redbear-ci.yml @@ -2,7 +2,10 @@ name: redbear-ci on: push: - branches: [0.3.1, master] + # Glob so a release-branch increment (0.3.1 -> 0.3.2 -> ...) keeps triggering + # CI — including the versioning-machinery checks below — without editing this + # file on every bump. + branches: ["0.3.*", master] pull_request: jobs: @@ -25,6 +28,19 @@ jobs: - name: Check sync-versions compliance run: ./local/scripts/sync-versions.sh --check + - name: Install b3sum (versioning machinery + validator) + run: sudo apt-get update && sudo apt-get install -y b3sum + + - name: Versioning machinery unit tests + run: ./local/scripts/test-versioning-machinery.sh + + - name: External recipe source-version consistency + # Fails if any tar recipe's vendored source/ (or tracked source.tar) + # diverges from its declared version — the Qt/KDE class of bug. Mirrors + # build-preflight.sh Phase 1.0C so divergence is caught on every push/PR, + # not only at build time. + run: ./local/scripts/verify-external-source-versions.sh + - name: cargo fmt check (redbear-* crates) run: | set -e diff --git a/local/scripts/sync-recipe-source.sh b/local/scripts/sync-recipe-source.sh index bb03264c78..3b440af257 100755 --- a/local/scripts/sync-recipe-source.sh +++ b/local/scripts/sync-recipe-source.sh @@ -86,6 +86,22 @@ decorrupt(){ awk ' download(){ curl -sLf --retry 3 --connect-timeout 20 --max-time 600 "$1" -o "$2" 2>/dev/null; } +# Reconcile recipe blake3 to the authoritative source.tar. A vendored bump often +# left recipe blake3 stale (URL bumped, hash not), so recipe.toml's declared hash +# no longer matched its own tarball. Only reconciles when source.tar exists AND +# its top-dir version equals the recipe URL version (so we never stamp the hash +# of a wrong-version tarball). No-op under --check. +reconcile_blake3(){ # $1=dir $2=recipe.toml $3=vnew + [ "$CHECK" = 1 ] && return 0 + command -v b3sum >/dev/null 2>&1 || return 0 + [ -f "$1/source.tar" ] || return 0 + local tvv; tvv="$(ver_of "$(tar tf "$1/source.tar" 2>/dev/null | head -1)")" + [ "$tvv" = "$3" ] || return 0 + grep -qE '^[[:space:]]*blake3[[:space:]]*=' "$2" || return 0 + local h; h="$(b3sum "$1/source.tar" | awk '{print $1}')" + sed -i -E "s|^([[:space:]]*blake3[[:space:]]*=[[:space:]]*)\"[0-9a-f]*\"|\1\"$h\"|" "$2" +} + # build the recipe list declare -a RECIPES=() if [ "$ALL" = 1 ]; then @@ -102,7 +118,24 @@ for dir in "${RECIPES[@]}"; do url="$(grep -m1 -E '^[[:space:]]*tar[[:space:]]*=' "$rc" | sed -E 's/^[^"]*"([^"]+)".*/\1/')"; [ -n "$url" ] || continue vnew="$(ver_of "$(basename "$url")")"; [ -n "$vnew" ] || continue blake="$(grep -m1 -E '^[[:space:]]*blake3[[:space:]]*=' "$rc" | grep -oE '[0-9a-f]{64}')" - [ -d "$dir/source" ] || { printf '%-26s %-9s %-9s %-8s %-6s %s\n' "$name" "-" "$vnew" "-" "-" "transient(skip)"; continue; } + if [ ! -d "$dir/source" ]; then + # Transient recipe: cookbook builds from source.tar/URL, so a stale cached + # source.tar is a stale build. Ensure it matches the declared version. + tst=""; [ -f "$dir/source.tar" ] && tst="$(ver_of "$(tar tf "$dir/source.tar" 2>/dev/null | head -1)")" + if [ -z "$tst" ] || [ "$tst" = "$vnew" ]; then + # no cached tar (cookbook fetches fresh) or already correct: just reconcile blake3 + reconcile_blake3 "$dir" "$rc" "$vnew" + printf '%-26s %-9s %-9s %-8s %-6s %s\n' "$name" "${tst:-none}" "$vnew" "-" "-" "transient(ok)" + elif [ "$CHECK" = 1 ]; then + printf '%-26s %-9s %-9s %-8s %-6s %s\n' "$name" "$tst" "$vnew" "-" "-" "transient(stale-tar)" + elif download "$url" "$WORK/$name-tr.tar"; then + cp "$WORK/$name-tr.tar" "$dir/source.tar"; reconcile_blake3 "$dir" "$rc" "$vnew" + printf '%-26s %-9s %-9s %-8s %-6s %s\n' "$name" "$tst" "$vnew" "-" "-" "transient(tar-bumped)" + else + printf '%-26s %-9s %-9s %-8s %-6s %s\n' "$name" "$tst" "$vnew" "-" "-" "transient(DL-FAIL)"; RC=2 + fi + continue + fi vold="$(src_version "$dir/source")"; vold="${vold:-unknown}" mapfile -t patches < <(awk '/^[[:space:]]*patches[[:space:]]*=/{p=1} p{print} p&&/\]/{exit}' "$rc" | grep -oE '"[^"]+\.patch"' | tr -d '"') # baked marker files (Redox) not covered by patches @@ -110,6 +143,7 @@ for dir in "${RECIPES[@]}"; do if [ "$vold" = "$vnew" ]; then printf '%-26s %-9s %-9s %-8s %-6s %s\n' "$name" "$vold" "$vnew" "${#patches[@]}" "$baked" "consistent" [ "$CHECK" = 1 ] && continue + reconcile_blake3 "$dir" "$rc" "$vnew" printf '%s\n' "$vnew" > "$dir/source/.redbear-src-version"; continue fi # Version marker unreadable: we cannot prove divergence, so we must NOT rebase @@ -166,8 +200,9 @@ for dir in "${RECIPES[@]}"; do # size with a coincidental mtime (e.g. "v1\n" vs "v2\n") would otherwise be # skipped by rsync's quick-check, leaving stale content in source/. rsync -a --delete --checksum "$base/source/" "$dir/source/" - # keep source.tar authoritative for Vnew + # keep source.tar authoritative for Vnew, and reconcile recipe blake3 to it [ -f "$WORK/$name-new.tar" ] && cp "$WORK/$name-new.tar" "$dir/source.tar" + reconcile_blake3 "$dir" "$rc" "$vnew" if [ "$COMMIT" = 1 ]; then git -C "$ROOT" add "$dir/source" "$dir/source.tar" >/dev/null 2>&1 git -C "$ROOT" -c commit.gpgsign=false commit -q -m "$name: propagate vendored source/ bump $vold -> $vnew (sync-recipe-source)" -- "$dir" >/dev/null 2>&1 diff --git a/local/scripts/test-versioning-machinery.sh b/local/scripts/test-versioning-machinery.sh index 88e88692c8..cd0af91ffa 100755 --- a/local/scripts/test-versioning-machinery.sh +++ b/local/scripts/test-versioning-machinery.sh @@ -156,6 +156,40 @@ out="$($SYNC eng_dlfail 2>&1)"; rc=$? assert_contains "E12 bad url -> DL-FAIL" "$out" "DL-FAIL" assert_eq "E12 source/ untouched on DL-FAIL" "$(cat "$d/source/f.c")" "keep"; rm -rf "$d" +echo "=== blake3 reconcile ===" +STALE=deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef +# E13 rebase reconciles recipe blake3 to the fetched source.tar +newtar="$FIX/tarballs/e13-2.0.0.tar"; mk_tar "$newtar" "app-2.0.0" ".redbear-src-version:2.0.0\n" "m.c:v2\n" +d="$(mk_recipe eng_b3 "file://$newtar" "$STALE")" +mkdir -p "$d/source"; printf '1.0.0\n' >"$d/source/.redbear-src-version"; printf 'v1\n' >"$d/source/m.c" +$SYNC eng_b3 >/dev/null 2>&1 +rb="$(grep -m1 blake3 "$d/recipe.toml" | grep -oE '[0-9a-f]{64}')"; tb="$(b3 "$d/source.tar")" +assert_eq "E13 recipe blake3 reconciled to source.tar on rebase" "$rb" "$tb" +rm -rf "$d" +# E14 consistent module with stale blake3 gets reconciled +tarf="$FIX/tarballs/e14.tar"; mk_tar "$tarf" "app-2.0.0" "m.c:x" +d="$(mk_recipe eng_b3c "file://x/app-2.0.0.tar.xz" "$STALE")"; cp "$tarf" "$d/source.tar" +mkdir -p "$d/source"; printf '2.0.0\n' >"$d/source/.redbear-src-version" +$SYNC eng_b3c >/dev/null 2>&1 +rb="$(grep -m1 blake3 "$d/recipe.toml" | grep -oE '[0-9a-f]{64}')" +assert_eq "E14 consistent+stale-blake3 reconciled" "$rb" "$(b3 "$d/source.tar")" +# E15 --check must NOT modify blake3 +printf 'blake3 = "%s"\n' "$STALE" > /dev/null # marker +sed -i "s|blake3 = .*|blake3 = \"$STALE\"|" "$d/recipe.toml"; printf '9.9.9\n' >"$d/source/.redbear-src-version" +$SYNC --check eng_b3c >/dev/null 2>&1 +rb="$(grep -m1 blake3 "$d/recipe.toml" | grep -oE '[0-9a-f]{64}')" +assert_eq "E15 --check leaves blake3 untouched" "$rb" "$STALE" +rm -rf "$d" + +echo "=== E16 transient stale source.tar gets bumped ===" +newtar="$FIX/tarballs/e16-2.0.0.tar"; mk_tar "$newtar" "app-2.0.0" "m.c:v2\n" +oldtar="$FIX/tarballs/e16-1.0.0.tar"; mk_tar "$oldtar" "app-1.0.0" "m.c:v1\n" +d="$(mk_recipe eng_tr "file://$newtar" "$STALE")"; cp "$oldtar" "$d/source.tar" # stale, no source/ +out="$($SYNC eng_tr 2>&1)"; assert_contains "E16 transient stale-tar bumped" "$out" "tar-bumped" +assert_eq "E16 source.tar now v2.0.0" "$(tar tf "$d/source.tar" | head -1)" "app-2.0.0/" +assert_eq "E16 transient blake3 reconciled" "$(grep -m1 blake3 "$d/recipe.toml" | grep -oE '[0-9a-f]{64}')" "$(b3 "$d/source.tar")" +rm -rf "$d" + echo "" echo "================ RESULT ================" echo " PASS: $PASS FAIL: $FAIL"