Files
RedBear-OS/local/scripts/migrate-kf6-seds-to-patches.sh
T
vasilito ffbe098ef8 config: add tlc to redbear-mini and redbear-full; create recipe symlink
TLC (Twilight Commander) was missing from both ISO configs. Added
tlc = {} to [packages] in redbear-mini.toml and redbear-full.toml.
Created missing symlink: recipes/tui/tlc -> ../../local/recipes/tui/tlc.
2026-06-19 11:47:25 +03:00

251 lines
9.9 KiB
Bash
Executable File

#!/usr/bin/env bash
# migrate-kf6-seds-to-patches.sh — C-7 KF6 sed migration
#
# Walks the 56 KDE/Qt recipes in `local/recipes/kde/*` that have
# inline `sed -i` chains in their `[build].script`, captures each
# set of edits as a durable external patch in
# `local/patches/<name>/01-initial-migration.patch`, and rewrites
# the recipe to call `cookbook_apply_patches` instead of running
# the sed chains inline.
#
# Per `local/AGENTS.md` "NO OVERLAY-STYLE PATCHES — SCOPED POLICY"
# (Rule 2): edits to big external projects must live in
# `local/patches/<component>/` so they survive `make clean` and
# upstream syncs. The migration converts the 56-recipe
# inline-sed anti-pattern into compliant Rule 2 recipes.
#
# Usage:
# ./local/scripts/migrate-kf6-seds-to-patches.sh [--dry-run]
# [--recipe=kf6-karchive] [...]
# ./local/scripts/migrate-kf6-seds-to-patches.sh --limit=N
#
# Pre-conditions:
# - All recipe dependencies built (qtbase, qtdeclarative, etc.)
# - Each recipe's `[source]` points at a tar (not git) so the
# pristine fetch is reproducible.
# - Disk space: ~2.8 GB for the unzipped source diffs + patches.
# - `git -C local/recipes/<name>/` is a clean working tree (or
# the script's `git checkout -- source/` reset will lose WIP).
#
# Per-recipe flow (per `bash` recipe):
# 1. Parse `[source].tar` to compute the pristine URL.
# 2. `repo fetch <name>` to get pristine source into `source/`.
# 3. `cp -r source/ source-pristine/` snapshot.
# 4. `repo cook <name>` to apply the inline sed chains.
# 5. `diff -ruN source-pristine/ source/` to capture edits.
# 6. Save diff as `local/patches/<name>/01-initial-migration.patch`.
# 7. Rewrite `recipe.toml` `[build].script` to call
# `cookbook_apply_patches "${REDBEAR_PATCHES_DIR}"` instead.
# 8. `repo cook <name>` again to verify the patch + rewritten
# script produce the same result as the inline sed.
# 9. `rm -rf source-pristine/` and report the patch.
set -euo pipefail
PROJECT_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
# Allow tests to override RECIPES_DIR via env. Production callers
# never set this; it exists so `test_migrate_kf6_seds.py` can
# exercise the candidate discovery on a synthetic tree without
# touching the live project.
RECIPES_DIR="${REDBEAR_MIGRATE_RECIPES_DIR:-$PROJECT_ROOT/local/recipes}"
PATCHES_DIR="${REDBEAR_MIGRATE_PATCHES_DIR:-$PROJECT_ROOT/local/patches}"
LOG_DIR="${MIGRATION_LOG_DIR:-/tmp/kf6-migration-logs}"
mkdir -p "$LOG_DIR"
DRY_RUN=0
LIMIT=""
ONLY_RECIPE=""
while [ $# -gt 0 ]; do
case "$1" in
--dry-run) DRY_RUN=1; shift ;;
--limit=*) LIMIT="${1#--limit=}"; shift ;;
--recipe=*) ONLY_RECIPE="${1#--recipe=}"; shift ;;
-h|--help)
sed -n '2,30p' "$0" | sed 's/^# \?//'
exit 0 ;;
*)
echo "unknown flag: $1" >&2
exit 1 ;;
esac
done
cd "$PROJECT_ROOT"
# The cookbook binary check is only relevant for non-dry-run
# invocations: --dry-run just lists candidates, no fetch/cook.
if [ "$DRY_RUN" != "1" ] && [ ! -x "./target/release/repo" ]; then
echo "./target/release/repo not built. Run: cargo build --release --bin repo" >&2
exit 1
fi
# Discover candidate recipes: anything in local/recipes/kde/ with
# a `sed -i` chain in its [build].script and an upstream tar source
# (Rule 2 candidates).
shopt -s nullglob
recipe_dirs=()
for d in "$RECIPES_DIR"/kde/*/; do
[ -f "$d/recipe.toml" ] || continue
grep -q '^[[:space:]]*sed[[:space:]]*-i' "$d/recipe.toml" || continue
grep -q '^tar[[:space:]]*=' "$d/recipe.toml" || continue
name=$(basename "$d")
if [ -n "$ONLY_RECIPE" ] && [ "$name" != "$ONLY_RECIPE" ]; then
continue
fi
recipe_dirs+=("$d")
done
if [ ${#recipe_dirs[@]} -eq 0 ]; then
echo "No sed-bearing tar-sourced recipes found in $RECIPES_DIR/kde/" >&2
exit 1
fi
# Apply --limit (helps in CI / smoke tests).
if [ -n "$LIMIT" ]; then
recipe_dirs=("${recipe_dirs[@]:0:$LIMIT}")
fi
echo "Found ${#recipe_dirs[@]} candidate recipes."
echo "Patches dir: $PATCHES_DIR"
echo "Log dir: $LOG_DIR"
echo "Dry run: $DRY_RUN"
echo
migrated=0
skipped=0
failed=0
for recipe_dir in "${recipe_dirs[@]}"; do
name=$(basename "$recipe_dir")
log_file="$LOG_DIR/$name.log"
patch_dir="$PATCHES_DIR/$name"
patch_file="$patch_dir/01-initial-migration.patch"
if [ -e "$patch_file" ]; then
echo "=== $name: SKIP — patch already exists at $patch_file ==="
skipped=$((skipped+1))
continue
fi
echo "=== $name ==="
if [ "$DRY_RUN" = "1" ]; then
echo " [dry-run] would fetch, snapshot pristine, cook, diff, save patch"
continue
fi
pristine_dir="$recipe_dir/source-pristine"
rm -rf "$pristine_dir"
mkdir -p "$patch_dir"
# Step 1: fetch pristine source.
# The cookbook's `fetch` re-uses an existing source/ tree if
# it finds one (it does this to avoid re-extracting tars on
# every fetch). For C-7 migration, we need the truly
# pristine upstream state — so we must `unfetch` first
# AND set REDBEAR_ALLOW_LOCAL_UNFETCH=1 because kf6-* and
# qt* recipes are local-overlay recipes under local/recipes/
# (the cookbook's default policy is to never clobber a
# local-overlay source). Without these, the script will
# snapshot the post-cook state and the diff will be empty.
if ! REDBEAR_ALLOW_LOCAL_UNFETCH=1 ./target/release/repo unfetch "$name" >>"$log_file" 2>&1; then
echo " FAIL: unfetch — see $log_file"
rm -rf "$pristine_dir"
failed=$((failed+1))
continue
fi
if ! ./target/release/repo fetch "$name" >>"$log_file" 2>&1; then
echo " FAIL: fetch — see $log_file"
rm -rf "$pristine_dir"
failed=$((failed+1))
continue
fi
# Step 2: snapshot pristine state.
cp -r "$recipe_dir/source" "$pristine_dir"
# Step 3: cook (this runs the inline sed chains + the rest of
# the build script; we don't care if the build itself fails —
# we only need the post-sed source state, which the sed
# commands apply before the actual build step).
#
# A 600-second timeout is applied because some upstream
# recipes (e.g. kf6-kauth, kf6-kconfig, kf6-kwidgetsaddons)
# use autotools and their `autoreconf` step can take 5+
# minutes on a clean cook. The sed chain we care about
# is applied by the recipe's [build].script BEFORE the
# configure step, so a 10-minute window is plenty for the
# sed to apply (and the snapshot was already taken at
# Step 2, so even if the cook is killed by the timeout,
# the post-cook source state is still useful for the diff).
timeout 600 ./target/release/repo cook "$name" >>"$log_file" 2>&1 || true
# Step 4: diff pristine vs post-cook.
#
# Exclude ECM-generated files that the cmake configure step
# creates on every run: `.clang-format` (clang-format style
# config), `.gitignore` (gitignore rules for the build dir),
# and any `target/` build outputs. These are NOT Red Bear
# edits and would pollute the patch with autogenerated noise.
diff_out=$(diff -ruN "$pristine_dir" "$recipe_dir/source" \
--exclude='.git' --exclude='target' \
--exclude='.clang-format' --exclude='.gitignore' \
2>/dev/null || true)
if [ -z "$diff_out" ]; then
echo " NOTE: cook produced no diff (sed chains may have been no-ops)"
rm -rf "$pristine_dir"
skipped=$((skipped+1))
continue
fi
# Step 5: save the diff as a numbered patch with a header.
{
echo "# Initial migration of the inline sed -i chains in"
echo "# $recipe_dir's [build].script to a durable external"
echo "# patch. Captured by local/scripts/migrate-kf6-seds-to-patches.sh"
echo "# on $(date -Iseconds)."
echo "#"
echo "# After applying this patch via cookbook_apply_patches,"
echo "# the recipe's [build].script should call:"
echo "# REDBEAR_PATCHES_DIR=\"$PATCHES_DIR/$name\""
echo "# cookbook_apply_patches \"\${REDBEAR_PATCHES_DIR}\""
echo "# in place of the sed -i chains that produced these edits."
echo
echo "$diff_out"
} >"$patch_file"
line_count=$(wc -l < "$patch_file")
echo " wrote $patch_file ($line_count lines, $(echo "$diff_out" | wc -l) diff lines)"
# Step 6: leave the source tree as-is for now — the user must
# manually rewrite the [build].script to use the patch and
# re-verify the build produces the same package. We do clean
# up the source-pristine snapshot (no longer needed).
rm -rf "$pristine_dir"
# Reset the cooked source so the next run can fetch cleanly.
# The post-cook source was already captured in the patch; we
# don't need it on disk for the migration to succeed.
REDBEAR_ALLOW_LOCAL_UNFETCH=1 ./target/release/repo unfetch "$name" >>"$log_file" 2>&1 || true
migrated=$((migrated+1))
done
echo
echo "=== Migration summary ==="
echo "Migrated (patch written, recipe rewrite pending): $migrated"
echo "Skipped (no diff or patch already exists): $skipped"
echo "Failed (fetch or other error): $failed"
echo
echo "Next steps for each 'Migrated' recipe:"
echo " 1. Open the new patch file under $PATCHES_DIR/<name>/ and"
echo " confirm it captures the right edits (vs the original"
echo " inline sed chain in the recipe)."
echo " 2. Edit the recipe's [build].script to remove the sed"
echo " chains and add:"
echo " REDBEAR_PATCHES_DIR=\"$PATCHES_DIR/<name>\""
echo " cookbook_apply_patches \"\${REDBEAR_PATCHES_DIR}\""
echo " 3. Cook the recipe once more with the patch applied; the"
echo " cookbook's idempotency check will skip the patch if"
echo " the source is already at HEAD."
echo " 4. Re-verify the package builds and is byte-identical to"
echo " the inline-sed version (compare stage.pkgar hashes)."
echo " 5. Run 'git add $PATCHES_DIR/<name>/' and commit."