Files
RedBear-OS/local/scripts/sync-recipe-source.sh
T
vasilito e0590a0b56 kde/kf6-extra-cmake-modules: rebase to 6.28.0 (ECM) + teach version parser set(VERSION)
kf6-kcoreaddons 6.28.0 requires ECM >= 6.28.0 but the vendored ECM source/ was
still 6.10.0: the sync engine skipped it because ECM's version marker is a bare
set(VERSION "X.Y.Z") that the parser didn't recognize (only KF_VERSION /
PROJECT_VERSION / RELEASE_SERVICE_VERSION). Re-lay ECM from its 6.28.0 source.tar
(pure cmake modules, no Redox port) and teach both the sync engine and the
validator to read set(VERSION) so this class is caught, not silently skipped.
2026-08-01 08:07:00 +03:00

279 lines
15 KiB
Bash
Executable File

#!/usr/bin/env bash
# sync-recipe-source.sh — canonical version-propagation engine for tar recipes.
#
# THE MISSING STEP. Bumping a recipe's `tar=`/`blake3=` never propagated into the
# vendored `source/` tree, so the build silently compiled a stale version (Qt
# 6.11.0-vs-6.11.1, the whole KDE stack at 6.10.0 vs recipe 6.28.0). This engine
# makes `source.tar`, the vendored `source/` tree, its version stamp, and the
# recipe's declared version all agree — treating recipe.toml as the source of
# truth (the version to converge ON).
#
# For each recipe it rebases the vendored fork onto the declared upstream:
# source/(Vnew) = pristine(Vnew) + recipe patches + baked local delta
# where the "baked local delta" is any change in the current source/ that is NOT
# produced by a listed recipe patch (e.g. Redox shims committed directly). That
# delta is captured against pristine(Vold) and re-applied onto pristine(Vnew) so
# a re-lay can never silently drop a Redox port change. Injected line-duplication
# corruption (identical consecutive lines) is dropped.
#
# Usage:
# sync-recipe-source.sh [--check] [--commit] [--all|--config=NAME] <recipe>...
# --check dry-run: classify each recipe, apply nothing, write nothing
# --commit git commit each synced recipe individually
# --all every tar recipe under local/recipes (excl. wip)
# <recipe> a recipe dir or bare name (e.g. kde/kf6-kio or kf6-kio)
#
# Exit: 0 all synced/consistent; 2 some need manual rebase (patches/shims reject).
set -uo pipefail
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
CHECK=0; COMMIT=0; ALL=0; declare -a WANT=()
for a in "$@"; do case "$a" in
--check) CHECK=1;; --commit) COMMIT=1;; --all) ALL=1;;
--config=*) : ;; # reserved
--*) echo "unknown opt: $a" >&2; exit 1;;
*) WANT+=("$a");;
esac; done
WORK="$(mktemp -d)"; trap 'rm -rf "$WORK"' EXIT
ver_of(){ grep -oE '[0-9]+\.[0-9]+(\.[0-9]+)*' <<<"$1" | tail -1; }
# resolve a recipe token to its dir. Accepts: a bare name (kf6-kio), a
# recipes-relative path (kde/kf6-kio), a repo-relative path
# (local/recipes/kde/kf6-kio), or an absolute path — with or without a trailing
# slash.
resolve(){ local t="${1%/}" # strip trailing slash
t="${t#"$ROOT"/}" # strip absolute ROOT prefix
t="${t#local/recipes/}" # strip the recipes/ prefix -> group/name or bare name
[ -f "$ROOT/local/recipes/$t/recipe.toml" ] && { echo "$ROOT/local/recipes/$t"; return; }
local hit; hit=$(find "$ROOT/local/recipes" -name recipe.toml -not -path '*/wip/*' -path "*/$t/recipe.toml" 2>/dev/null | head -1)
[ -n "$hit" ] && echo "$(dirname "$hit")"
}
# vendored source/ version marker. ACTUAL code markers (Qt .cmake.conf, KDE
# KF_VERSION) are authoritative and checked FIRST; the generic .redbear-src-version
# stamp is only a fallback for trees with no code marker. Rationale: the stamp is
# a cache the engine writes, and it can go stale (e.g. a code-only revert) — a
# stale stamp must never mask the real version and skip a needed rebase.
src_version(){ local s="$1"
[ -f "$s/.cmake.conf" ] && { local v; v=$(ver_of "$(grep -m1 QT_REPO_MODULE_VERSION "$s/.cmake.conf")"); [ -n "$v" ] && { echo "$v"; return; }; }
if [ -f "$s/CMakeLists.txt" ]; then
# KDE Frameworks (KF_VERSION), Plasma (PROJECT_VERSION), or a literal
# project(Name VERSION X.Y.Z).
local v; v=$(grep -m1 -E 'set\((KF_VERSION|PROJECT_VERSION)|project\([A-Za-z0-9_]+ VERSION [0-9]' "$s/CMakeLists.txt" 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
# Extra CMake Modules and some KF modules use a bare set(VERSION "X.Y.Z").
if [ -z "$v" ]; then
v=$(grep -m1 -E '^set\(VERSION "[0-9]+\.[0-9]+\.[0-9]+"' "$s/CMakeLists.txt" 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
fi
# KDE Gear (konsole etc.): version split across RELEASE_SERVICE_VERSION_*.
if [ -z "$v" ] && grep -q RELEASE_SERVICE_VERSION_MAJOR "$s/CMakeLists.txt" 2>/dev/null; then
local a b c
a=$(grep -m1 RELEASE_SERVICE_VERSION_MAJOR "$s/CMakeLists.txt" | grep -oE '[0-9]+' | head -1)
b=$(grep -m1 RELEASE_SERVICE_VERSION_MINOR "$s/CMakeLists.txt" | grep -oE '[0-9]+' | head -1)
c=$(grep -m1 RELEASE_SERVICE_VERSION_MICRO "$s/CMakeLists.txt" | grep -oE '[0-9]+' | head -1)
[ -n "$a" ] && v="$a.$b.$c"
fi
[ -n "$v" ] && { echo "$v"; return; }
fi
[ -f "$s/.redbear-src-version" ] && { ver_of "$(cat "$s/.redbear-src-version")"; return; }
echo ""
}
# apply a patch trying -p1 then -p0; $1=tree-root $2=patchfile ; echo level or FAIL.
# --fuzz=0: require EXACT context (line offsets are still allowed) so a patch
# whose context no longer matches the new upstream is REJECTED, not silently
# force-fitted with fuzz — that silent force-fit is how a stale/wrong port would
# sneak through.
apply_patch(){ local root="$1" pf="$2" lvl
for lvl in 1 0; do
if ( cd "$root" && patch -p$lvl --fuzz=0 --dry-run -f <"$pf" >/dev/null 2>&1 ); then
( cd "$root" && patch -p$lvl --fuzz=0 -s -f <"$pf" >/dev/null 2>&1 ); echo "$lvl"; return 0
fi
done
echo FAIL; return 1
}
# Drop the line-duplication corruption pattern: a run of >=4 identical
# consecutive NON-BLANK lines collapses to a single line (the observed
# corruption injected 5-13 copies). Runs of <=3 and blank-line runs are kept
# verbatim, so legitimate paired lines (`}`/`}`, `#endif`/`#endif`) survive.
decorrupt(){ awk '
{ a[NR]=$0 }
END {
i=1
while (i<=NR) {
j=i
while (j<NR && a[j+1]==a[i] && a[i]!="") j++
if (j-i+1 >= 4 && a[i]!="") print a[i]
else for (k=i;k<=j;k++) print a[k]
i=j+1
}
}' "$1"; }
# A stalled mirror dies fast via --speed-time (abort if <1KB/s for 45s), while a
# slow-but-progressing download gets up to --max-time 300 — so a slow KDE mirror
# completes instead of a false DL-FAIL, but a truly hung transfer never stalls
# the whole run.
download(){ curl -sLf --retry 2 --connect-timeout 15 --max-time 300 \
--speed-limit 1024 --speed-time 45 "$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
while IFS= read -r r; do grep -qE '^[[:space:]]*tar[[:space:]]*=' "$r" && RECIPES+=("$(dirname "$r")"); done \
< <(find "$ROOT/local/recipes" -name recipe.toml -not -path '*/wip/*')
else
for t in "${WANT[@]}"; do d=$(resolve "$t"); [ -n "$d" ] && RECIPES+=("$d") || echo "!! not found: $t" >&2; done
fi
printf '%-26s %-9s %-9s %-8s %-6s %s\n' RECIPE FROM TO PATCHES BAKED STATUS
RC=0
for dir in "${RECIPES[@]}"; do
rc="$dir/recipe.toml"; name="$(basename "$dir")"
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}')"
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
baked=$(git -C "$ROOT" grep -lI "__redox__\|Q_OS_REDOX\|defined(__redox" HEAD -- "$dir/source" 2>/dev/null | wc -l | tr -d ' ')
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
# (that could clobber a carefully-ported tree like mesa). Report + skip; the
# fix is to add a source/.redbear-src-version stamp once the tree is verified.
if [ "$vold" = "unknown" ]; then
printf '%-26s %-9s %-9s %-8s %-6s %s\n' "$name" "unknown" "$vnew" "${#patches[@]}" "$baked" "no-marker(skip; stamp to enable)"
continue
fi
# --- obtain pristine Vnew ---
base="$WORK/$name-new"; rm -rf "$base"; mkdir -p "$base"
tarball="$dir/source.tar"; tv=""
[ -f "$tarball" ] && tv="$(ver_of "$(tar tf "$tarball" 2>/dev/null | head -1)")"
if [ "$tv" != "$vnew" ]; then
download "$url" "$WORK/$name-new.tar" && tarball="$WORK/$name-new.tar" || { printf '%-26s %-9s %-9s %-8s %-6s %s\n' "$name" "$vold" "$vnew" "${#patches[@]}" "$baked" "DL-FAIL"; RC=2; continue; }
fi
tar xf "$tarball" -C "$base" 2>/dev/null
top="$(tar tf "$tarball" 2>/dev/null | head -1 | cut -d/ -f1)"
mv "$base/$top" "$base/source" 2>/dev/null || { printf '%-26s %-9s %-9s %s\n' "$name" "$vold" "$vnew" "EXTRACT-FAIL"; RC=2; continue; }
# --- apply recipe patches ---
preject=0; for p in "${patches[@]}"; do
pf="$dir/$p"; [ -f "$pf" ] || pf="$ROOT/local/patches/$p"; [ -f "$pf" ] || { preject=1; continue; }
lv=$(apply_patch "$base/source" "$pf"); [ "$lv" = FAIL ] && preject=1
done
# --- preserve RedBear additions + reapply baked Redox deltas ---
# This runs for EVERY vendored rebase (not only marker-bearing ones): a
# RedBear-added file (e.g. a Redox Wayland-crash wrapper .sh, a utmp compat
# header) has no __redox__ token, so a grep-only capture misses it and
# rsync --delete drops it. We compare against pristine(Vold) by PATH to keep
# every RedBear addition, content-agnostic.
breject=0
if [ "$vold" != unknown ]; then
# Old-version URL: replace the full version, then the major.minor dir
# component (KDE mirrors nest under .../6.28/foo-6.28.0 -> 6.10/foo-6.10.0).
oldurl="${url//$vnew/$vold}"
vnm="${vnew%.*}"; vom="${vold%.*}"
[ "$vnm" != "$vnew" ] && [ "$vnm" != "$vom" ] && oldurl="${oldurl//$vnm/$vom}"
if download "$oldurl" "$WORK/$name-old.tar"; then
op="$WORK/$name-old"; rm -rf "$op"; mkdir -p "$op"; tar xf "$WORK/$name-old.tar" -C "$op" 2>/dev/null
otop="$(tar tf "$WORK/$name-old.tar" 2>/dev/null | head -1 | cut -d/ -f1)"; mv "$op/$otop" "$op/source" 2>/dev/null
relroot="${dir#$ROOT/}" # repo-relative recipe dir (git needs this)
gitf="$WORK/gitf"
# (A) Carry forward every committed file absent from pristine(Vold): these
# are RedBear additions upstream never had. Skip the stamp and files
# the new pristine/patches already provide.
while IFS= read -r rel; do
[ "$rel" = ".redbear-src-version" ] && continue
[ -e "$op/source/$rel" ] && continue # exists upstream -> not a RedBear addition
[ -e "$base/source/$rel" ] && continue # already provided by pristine(Vnew) or a patch
if ! git -C "$ROOT" show "HEAD:$relroot/source/$rel" > "$gitf" 2>/dev/null; then breject=1; continue; fi
decorrupt "$gitf" > "$gitf.clean"
mkdir -p "$base/source/$(dirname "$rel")"; cp "$gitf.clean" "$base/source/$rel"
done < <(git -C "$ROOT" ls-tree -r --name-only HEAD -- "$relroot/source" | while IFS= read -r p; do printf '%s\n' "${p#"$relroot"/source/}"; done)
# (B) For marker-bearing files that ALSO exist in Vold (modified upstream
# files), port the Redox delta. Added marker files were handled by (A).
if [ "$baked" -gt 0 ]; then
while IFS= read -r line; do
rel="${line#*/source/}"
[ -f "$op/source/$rel" ] || continue # added file -> already carried by (A)
if ! git -C "$ROOT" show "HEAD:$relroot/source/$rel" > "$gitf" 2>/dev/null || [ ! -s "$gitf" ]; then
breject=1; continue
fi
decorrupt "$gitf" > "$gitf.clean"
if [ ! -f "$base/source/$rel" ]; then breject=1; continue; fi # gone in new upstream -> manual
diff -q "$op/source/$rel" "$gitf.clean" >/dev/null 2>&1 && continue # marker is upstream's own
# Capture delta to a file (never pipe diff|patch: diff exits 1 on any
# difference and pipefail would false-reject every shim).
diff -u "$op/source/$rel" "$gitf.clean" > "$WORK/delta.patch" 2>/dev/null
if ! patch --fuzz=0 -s "$base/source/$rel" < "$WORK/delta.patch" >/dev/null 2>&1; then
breject=1
fi
done < <(git -C "$ROOT" grep -lI "__redox__\|Q_OS_REDOX\|defined(__redox" HEAD -- "$dir/source" 2>/dev/null)
fi
else breject=1; fi
fi
printf '%s\n' "$vnew" > "$base/source/.redbear-src-version"
status="ready"; [ "$preject" = 1 ] && status="PATCH-REJECT(manual)"; [ "$breject" = 1 ] && status="${status};SHIM-REJECT(manual)"
[ "$preject" = 1 ] || [ "$breject" = 1 ] && RC=2
printf '%-26s %-9s %-9s %-8s %-6s %s\n' "$name" "$vold" "$vnew" "${#patches[@]}" "$baked" "$status"
if [ "$CHECK" = 0 ] && [ "$preject" = 0 ] && [ "$breject" = 0 ]; then
# --checksum: compare by content, never by size+mtime. Two files of equal
# 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, 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
fi
fi
done
exit $RC