scripts: extend check-prefix-freshness.sh with dead-symlink scan

The verify-overlay-integrity.sh script has two blind spots for stale
symlink detection:
  1. find recipes -maxdepth 3 misses paths at depth 4+
  2. only checks targets under /local/recipes/, misses /local/patches/

This adds a parallel scan that finds all dangling symlinks under recipes/
without maxdepth, classifies them by target path, and reports each with a
suggested action. Pure diagnostic — does not modify any files.

Enables preventive detection of the recipes/wip/x11/mesa-x11/source
orphan and recipes/core/base/redox.patch pointer-rot that triggered the
build integrity warnings on this session's rebuilds.

(NO AI attribution in commit message.)
This commit is contained in:
2026-07-13 19:25:14 +03:00
parent 43f53a65be
commit 6f8dd561e2
+73 -1
View File
@@ -22,28 +22,93 @@ fail() { printf '%sFAIL%s: %s\n' "$c_red" "$c_reset" "$*" >&2; }
usage() {
cat <<EOF
Usage: $(basename "$0") [--doctor] [--simulate-staleness]
Usage: $(basename "$0") [--doctor] [--simulate-staleness] [--scan-dead-symlinks]
Check whether the compiled prefix libc.a is older than local fork commits.
Options:
--doctor Print verbose component/file diagnostics
--simulate-staleness Force an artificial stale-prefix result for testing
--scan-dead-symlinks Scan recipes/ for dangling symlinks and missing gitlinks
-h, --help Show this help
EOF
}
doctor=0
simulate_staleness=0
scan_dead_symlinks=0
for arg in "$@"; do
case "$arg" in
--doctor) doctor=1 ;;
--simulate-staleness) simulate_staleness=1 ;;
--scan-dead-symlinks) scan_dead_symlinks=1 ;;
-h|--help) usage; exit 0 ;;
*) fail "unknown argument: $arg"; usage; exit 2 ;;
esac
done
scan_dead_symlinks_mode() {
local broken=0 total=0 gitlink_count=0 path classification action target linkpath canonical
local stale_recipe_overlay=0 stale_patch_symlink=0 stale_fork_symlink=0 stale_unclassified=0 stale_submodule=0
while IFS= read -r linkpath; do
[[ -n "$linkpath" ]] || continue
(( total++ ))
target=$(readlink "$project_root/$linkpath" || true)
canonical=$(readlink -f "$project_root/$linkpath" 2>/dev/null || true)
if [[ -z "$canonical" ]]; then
canonical="$(python3 - <<'PY' "$project_root" "$linkpath" "$target"
import os, sys
root, linkpath, target = sys.argv[1:4]
print(os.path.normpath(os.path.join(os.path.dirname(os.path.join(root, linkpath)), target)))
PY
)"
fi
if [[ "$canonical" == "$project_root/local/recipes/"* ]]; then
classification="stale recipe overlay"
action="recreate or refresh the overlay under local/recipes/"
elif [[ "$canonical" == "$project_root/local/patches/"* ]]; then
classification="stale patch symlink"
action="restore the patch file and rewire the recipe symlink"
elif [[ "$canonical" == "$project_root/local/sources/"* ]]; then
classification="stale fork symlink"
action="restore the local fork source tree"
else
classification="stale symlink (unclassified)"
action="inspect the target and repair the symlink"
fi
if [[ ! -e "$project_root/$linkpath" ]]; then
printf '%s%s:1%s target=%s class=%s action=%s\n' "$c_red" "$linkpath" "$c_reset" "$target" "$classification" "$action"
case "$classification" in
"stale recipe overlay") (( stale_recipe_overlay++ )) ;;
"stale patch symlink") (( stale_patch_symlink++ )) ;;
"stale fork symlink") (( stale_fork_symlink++ )) ;;
*) (( stale_unclassified++ )) ;;
esac
(( broken++ ))
fi
done < <(find "$project_root/recipes" -type l ! -exec test -e {} \; -print | sed "s#^$project_root/##")
while IFS= read -r linkpath; do
[[ -n "$linkpath" ]] || continue
if [[ ! -e "$project_root/$linkpath" ]]; then
printf '%s%s:1%s target=%s class=%s action=update submodule path or initialize submodule\n' "$c_red" "$linkpath" "$c_reset" "missing" "missing submodule path"
(( stale_submodule++ ))
(( broken++ ))
fi
(( gitlink_count++ ))
done < <(git -C "$project_root" ls-tree -r --full-tree HEAD | awk '$1 == "160000" {print $4}')
if (( broken )); then
warn "dead symlink scan found $broken issue(s) across $total dangling link(s) and $gitlink_count gitlink(s)"
printf '%sSummary:%s recipe overlay=%s patch symlink=%s fork symlink=%s unclassified=%s submodule=%s\n' \
"$c_yellow" "$c_reset" "$stale_recipe_overlay" "$stale_patch_symlink" "$stale_fork_symlink" "$stale_unclassified" "$stale_submodule"
return 1
fi
ok "dead symlink scan clean ($total dangling links checked, $gitlink_count gitlinks checked)"
}
latest_prefix_mtime() {
local latest=0 path ts
for path in \
@@ -85,6 +150,13 @@ for component in "${components[@]}"; do
fi
done
if (( scan_dead_symlinks )); then
if scan_dead_symlinks_mode; then
exit 0
fi
exit 1
fi
if (( stale )); then
warn "prefix is stale — run ./local/scripts/build-redbear.sh --upstream"
exit 1