Files
RedBear-OS/local/scripts/migrate-kf6-seds-direct.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

244 lines
9.0 KiB
Bash
Executable File

#!/usr/bin/env bash
# Red Bear OS — C-7 direct-sed migration
#
# For KF6 recipes whose [build].script uses inline `sed -i`
# chains, this script:
# 1. Restores the pristine source from `source-pristine/`
# 2. Applies the same sed chain manually via `bash -c`
# 3. Diffs pristine vs post-sed source
# 4. Saves the diff as `local/patches/<name>/01-initial-migration.patch`
#
# Why direct-sed instead of `repo cook`: the cookbook's
# `cook` step does a full dep-tree build that often fails
# in offline mode (missing libffi/pcre2/mesa stage.pkgar
# etc.). The sed chain we care about is the FIRST thing
# in the recipe's [build].script and runs BEFORE cmake
# configure. We can run it standalone with a simple
# `bash -c "<sed chain>"` to capture the post-sed state.
#
# This is the same diff that `repo cook` would have
# produced, but without the dep-tree failure mode.
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")/../.."
PATCHES_DIR="${REDBEAR_MIGRATE_PATCHES_DIR:-/home/kellito/Builds/RedBear-OS/local/patches}"
# All 17 KF6 recipes that previously had inline `sed -i`
# chains (all those whose upstream 6.26.0 still has the
# ecm_install_po_files_as_qm call). The 24 NO-OP recipes
# were cleaned separately by cleanup-kf6-noop-seds.sh.
recipe_dirs=(
"local/recipes/kde/kf6-karchive"
"local/recipes/kde/kf6-kauth"
"local/recipes/kde/kf6-kbookmarks"
"local/recipes/kde/kf6-kcodecs"
"local/recipes/kde/kf6-kcompletion"
"local/recipes/kde/kf6-kconfig"
"local/recipes/kde/kf6-kcoreaddons"
"local/recipes/kde/kf6-kdbusaddons"
"local/recipes/kde/kf6-kglobalaccel"
"local/recipes/kde/kf6-kitemviews"
"local/recipes/kde/kf6-kjobwidgets"
"local/recipes/kde/kf6-knotifications"
"local/recipes/kde/kf6-kwidgetsaddons"
"local/recipes/kde/kf6-kwindowsystem"
"local/recipes/kde/kf6-solid"
"local/recipes/kde/kf6-sonnet"
"local/recipes/kde/kf6-syntaxhighlighting"
"local/recipes/kde/sddm"
# 7 unclassified (git-sourced or kde/plasma). Their
# pristine state was snapshotted after a successful
# `repo fetch`. If the pristine doesn't exist, the
# script reports NO-OP and moves on.
"local/recipes/kde/breeze"
"local/recipes/kde/kde-cli-tools"
"local/recipes/kde/kdecoration"
"local/recipes/kde/kf6-kcmutils"
"local/recipes/kde/kf6-kdeclarative"
"local/recipes/kde/kf6-kded6"
"local/recipes/kde/kf6-kwayland"
"local/recipes/kde/kf6-notifyconfig"
"local/recipes/kde/kglobalacceld"
"local/recipes/kde/kirigami"
"local/recipes/kde/konsole"
"local/recipes/kde/kwin"
"local/recipes/kde/plasma-desktop"
"local/recipes/kde/plasma-workspace"
)
migrated=0
skipped=0
failed=0
no_op=0
for recipe_dir in "${recipe_dirs[@]}"; do
name=$(basename "$recipe_dir")
recipe="$recipe_dir/recipe.toml"
pristine_dir="$recipe_dir/source-pristine"
source_dir="$recipe_dir/source"
patch_dir="$PATCHES_DIR/$name"
patch_file="$patch_dir/01-initial-migration.patch"
if [ -e "$patch_file" ]; then
echo "SKIP: $name (patch already exists)"
skipped=$((skipped+1))
continue
fi
if [ ! -e "$pristine_dir" ] || [ ! -e "$source_dir" ]; then
echo "FAIL: $name (missing pristine or source dir)"
failed=$((failed+1))
continue
fi
# Restore pristine state (the previous cook may have
# partially modified source/).
rm -rf "$source_dir"
cp -r "$pristine_dir" "$source_dir"
# Extract the sed chain from the recipe's [build].script.
# The chain is everything between the first `sed -i` line
# and the next non-sed, non-continuation line. Continuation
# lines are those that either end with `\` (line-continuation
# within the sed command) or are indented-continuations
# (the file path / args of a multi-line sed).
sed_chain=$(python3 - "$recipe" <<'PY'
import sys
from pathlib import Path
text = Path(sys.argv[1]).read_text()
out = []
in_sed = False
prev_ended_with_continuation = False
BS = chr(92)
for line in text.splitlines():
if "sed -i" in line and not in_sed:
in_sed = True
out.append(line)
prev_ended_with_continuation = line.rstrip().endswith(BS)
elif in_sed:
ends_with_bs = line.rstrip().endswith(BS)
is_indented = line.startswith(" ") or line.startswith("\t")
if "sed -i" in line:
out.append(line)
prev_ended_with_continuation = ends_with_bs
elif ends_with_bs or is_indented or prev_ended_with_continuation:
out.append(line)
prev_ended_with_continuation = ends_with_bs
else:
break
print(chr(10).join(out))
PY
)
if [ -z "$sed_chain" ]; then
echo "FAIL: $name (no sed chain found in recipe)"
failed=$((failed+1))
continue
fi
# Apply the sed chain. Bash's `$(...)` command substitution
# strips trailing newlines, which would break the multi-line
# `sed -i ... \` continuation. Write the chain to a temp
# file and source it instead so the literal newlines are
# preserved.
sed_script=$(mktemp /tmp/sed-chain.XXXXXX.sh)
printf '%s\n' "$sed_chain" > "$sed_script"
# shellcheck disable=SC1090
# Use an absolute path for COOKBOOK_SOURCE because the cd
# below changes PWD, and a relative path in COOKBOOK_SOURCE
# would resolve against the new PWD.
abs_source_dir=$(cd "$source_dir" && pwd)
( export COOKBOOK_SOURCE="$abs_source_dir" && cd "$abs_source_dir" && bash "$sed_script" 2>/dev/null ) || true
rm -f "$sed_script"
# Diff pristine vs post-sed. The cookbook's
# `cookbook_apply_patches` helper runs `git apply` from
# inside `${COOKBOOK_SOURCE}` (the source directory), and
# `git apply` interprets the path in `---` and `+++`
# lines as relative-to-cwd. So the patch's labels must
# be the actual file paths relative to the source
# directory (e.g. `CMakeLists.txt` or `src/CMakeLists.txt`).
#
# Build a per-file diff so each file gets the right
# label. Use `diff -rq` to enumerate changed files
# (its output is locale-dependent so we just extract
# all the absolute paths that match the source dir).
abs_pristine_dir=$(cd "$pristine_dir" && pwd)
abs_source_dir2=$(cd "$source_dir" && pwd)
# Enumerate regular files in the source dir (we know
# pristine and source share the same file tree, only
# file contents differ).
diff_out=""
while IFS= read -r -d '' rel_file; do
# Skip ECM-generated noise.
case "$rel_file" in
.clang-format|.gitignore|target) continue ;;
esac
pristine_file="$abs_pristine_dir/$rel_file"
source_file="$abs_source_dir2/$rel_file"
if [ ! -f "$pristine_file" ] || [ ! -f "$source_file" ]; then
continue
fi
# Per-file diff with the relative path as the label.
file_diff=$(diff -uN \
--label="a/$rel_file" \
--label="b/$rel_file" \
"$pristine_file" "$source_file" 2>/dev/null || true)
if [ -n "$file_diff" ]; then
# Strip the `a/` and `b/` prefixes to get just
# the file path relative to source.
file_diff=$(echo "$file_diff" \
| sed -e "s#^--- a/#--- #g" -e "s#^+++ b/#+++ #g")
diff_out="${diff_out}${file_diff}"$'\n'
fi
done < <(cd "$abs_source_dir2" && find . -type f -not -path './.git/*' -not -path './target/*' -not -name '.clang-format' -not -name '.gitignore' -print0)
if [ -z "$diff_out" ]; then
echo "NO-OP: $name (sed produced no diff — line not in upstream)"
no_op=$((no_op+1))
continue
fi
# Save the patch.
mkdir -p "$patch_dir"
{
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-direct.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"
# Verify the patch applies cleanly to pristine using
# `git apply --check` (the same tool the cookbook's
# `cookbook_apply_patches` helper uses). `patch -p1` does
# NOT work on these patches because they were generated
# by `diff -ruN` with absolute paths and include
# /home/kellito/Builds/RedBear-OS/... prefixes that
# `patch -p1` cannot strip.
if (cd "$pristine_dir" && git apply --check "$patch_file" >/dev/null 2>&1); then
echo "MIGRATED: $name"
migrated=$((migrated+1))
else
echo "FAIL: $name (patch does not apply cleanly)"
rm -f "$patch_file"
failed=$((failed+1))
fi
done
echo
echo "=== Summary ==="
echo "Migrated: $migrated"
echo "No-op: $no_op"
echo "Skipped: $skipped"
echo "Failed: $failed"