build: relink only static initfs binaries on boot-ABI change, not the desktop

The ABI-staleness step recompiled the ENTIRE dynamically-linked userspace (Qt,
KF6, mesa, sddm) whenever relibc/base/a-protocol-fork changed -- so editing a
driver, acpi, or base recipe rebuilt the whole desktop. That is wrong: relibc is
a shared libc.so.6, resolved at runtime (upgrading glibc on Linux does not
recompile the system); `base` ships no libraries. Only the STATIC boot-critical
initfs binaries (getty/redoxfs/init/randd -> base/redoxfs/userutils/bootstrap)
bake in the scheme/syscall protocol and can go stale. Invalidate only those.
Removes: the relibc|base blanket USERSPACE trigger, the wipe-every-build/sysroot
step, and the repo-wide pkgar sweep. Genuine relibc C-ABI break -> --no-cache.
This commit is contained in:
2026-07-25 07:37:12 +09:00
parent 9964a0d63b
commit 38932fe1fd
+38 -80
View File
@@ -628,7 +628,9 @@ if [ "$NO_CACHE" != "1" ]; then
find "$PROJECT_ROOT/recipes" -path "*/$src/target" -type d -exec rm -rf {} + 2>/dev/null || true
STALE_DETECTED=1
[ "$src" = "relibc" ] && RELIBC_INVALIDATED=1
case "$src" in relibc|base) USERSPACE_RUNTIME_STALE=1 ;; esac
# NB: base ships NO libraries (daemons/drivers/init) and relibc is
# a shared libc.so.6 — neither forces a userspace-wide relink. Only
# the static initfs-critical binaries are handled, selectively, below.
fi
fi
done
@@ -661,13 +663,11 @@ if [ "$NO_CACHE" != "1" ]; then
done
if [ "$USERSPACE_RUNTIME_STALE" = "1" ] && [ "${REDBEAR_SKIP_ABI_STALENESS:-0}" != "1" ]; then
echo ">>> Cleaning stale build/sysroot dirs (userspace-linked runtime changed)..."
find "$PROJECT_ROOT/recipes" "$PROJECT_ROOT/local/recipes" \
\( -path "*/target/x86_64-unknown-redox/build" \
-o -path "*/target/x86_64-unknown-redox/sysroot" \) \
-type d -exec rm -rf {} + 2>/dev/null || true
fi
# (The blanket "wipe every recipe's build/sysroot when a runtime fork changed"
# step used to live here. It was the bug: it recompiled the entire dynamically
# -linked desktop — Qt, KF6, mesa, sddm — on any relibc/base/syscall change,
# which is exactly what a shared-libc system never needs. Replaced by the
# selective, static-consumer-only invalidation below.)
# Downstream ABI-staleness prevention (relibc consumers).
#
@@ -739,78 +739,36 @@ if [ "$NO_CACHE" != "1" ]; then
*) return 1 ;;
esac
}
# ── Config-scoped ABI invalidation ──────────────────────────────────
# repo/ is shared across configs, so a `redbear-mini` build must NOT nuke
# packages that only `redbear-full` builds (Qt, mesa, sddm, ...) just because
# their pkgars sit in the repo older than relibc.pkgar. Resolve the closure
# of the config being built and only ever invalidate packages inside it.
# No env flag needed — you named the config, so we know it's mini not full.
declare -A _config_pkg_set
_config_scoped=0
_cfg_file="$PROJECT_ROOT/config/$CONFIG.toml"
if [ -f "$_cfg_file" ]; then
while read -r _p; do
[ -n "$_p" ] && _config_pkg_set["$_p"]=1
done < <(timeout 120 "$PROJECT_ROOT/target/release/repo" push-tree \
--filesystem="$_cfg_file" 2>/dev/null \
| sed -E 's/^[^A-Za-z0-9_]+//' | awk '{print $1}' | grep -E '^[A-Za-z0-9]')
[ "${#_config_pkg_set[@]}" -gt 0 ] && _config_scoped=1
fi
if [ "$_config_scoped" = "1" ]; then
echo ">>> ABI-staleness scoped to $CONFIG (${#_config_pkg_set[@]} pkgs); other configs' repo packages untouched."
else
echo ">>> NOTE: could not resolve $CONFIG package closure; ABI sweep is repo-wide (fallback)."
fi
_pkg_in_config() {
# true unless we have a config closure and pkg is outside it
[ "$_config_scoped" != "1" ] && return 0
[ -n "${_config_pkg_set[$1]:-}" ]
}
invalidate_consumer() {
local pkg="$1"
_pkg_in_config "$pkg" || return 0
rm -f "$REPO_DIR/$pkg".*
find "$PROJECT_ROOT/recipes" "$PROJECT_ROOT/local/recipes" \
-path "*/$pkg/target" -type d -exec rm -rf {} + 2>/dev/null || true
}
if [ "${REDBEAR_SKIP_ABI_STALENESS:-0}" = "1" ]; then
# Fast-iteration escape hatch: skip the entire relibc-consumer
# invalidation. Safe ONLY when relibc's ABI is unchanged (e.g. iterating
# on a single driver/app recipe). The cookbook's own BLAKE3 dep-hash
# cache still rebuilds recipes whose sources changed, so your edited
# package re-cooks while everything else stays cached — no 30-min churn
# to re-reach it. Unset to restore the stale-binary safety net (which is
# required whenever relibc/base actually changed).
echo ">>> REDBEAR_SKIP_ABI_STALENESS=1 — skipping relibc-consumer invalidation"
echo ">>> (fast iteration; assumes relibc ABI unchanged). Unset to restore it."
elif [ "$relibc_rebuilding" = "1" ]; then
echo ">>> relibc is rebuilding — it is statically linked into every"
echo ">>> userspace binary, so ALL packages are invalidated to force a"
echo ">>> clean relink (correctness over speed; prevents stale-binary skew)."
for _pkgar in "$REPO_DIR"/*.pkgar; do
[ -f "$_pkgar" ] || continue
_pkg="$(basename "$_pkgar" .pkgar)"
_pkg_in_config "$_pkg" || continue
if _rb_preserve_toolchain "$_pkg"; then
echo ">>> preserving ABI-inert toolchain pkg '$_pkg' (consumer relinks;" \
"set REDBEAR_FORCE_TOOLCHAIN_RECOOK=1 to force a full re-cook)."
continue
fi
invalidate_consumer "$_pkg"
done
elif [ -f "$RELIBC_PKGAR" ]; then
relibc_ts=$(stat -c %Y "$RELIBC_PKGAR" 2>/dev/null || echo "0")
for _pkgar in "$REPO_DIR"/*.pkgar; do
[ -f "$_pkgar" ] || continue
_pkg="$(basename "$_pkgar" .pkgar)"
[ "$_pkg" = "relibc" ] && continue
_pkg_in_config "$_pkg" || continue
_rb_preserve_toolchain "$_pkg" && continue
_pkg_ts=$(stat -c %Y "$_pkgar" 2>/dev/null || echo "0")
if [ "$relibc_ts" != "0" ] && [ "$relibc_ts" -gt "$_pkg_ts" ]; then
echo ">>> ABI-stale $_pkg (older than relibc.pkgar — interrupted prior build); invalidating..."
invalidate_consumer "$_pkg"
fi
# ── Selective, static-consumer-only ABI invalidation ───────────────
# relibc is a SHARED library (libc.so.6): dynamically-linked userspace — Qt,
# KF6, mesa, sddm, the whole desktop — resolves it at RUNTIME, so a relibc
# rebuild needs NO recompile there. That is exactly why upgrading glibc on
# Linux does not recompile the system. `base` ships no libraries at all
# (daemons/drivers/init). The ONLY binaries that can genuinely go stale are
# the STATIC, boot-critical initfs binaries that bake in the Redox
# scheme/syscall protocol before libc.so is available — the getty / redoxfs /
# init / randd class. Those come from a small fixed set of forks; when the
# boot ABI changes (relibc rebuilds, or a syscall/libredox/redox-scheme
# protocol crate changed -> USERSPACE_RUNTIME_STALE), relink ONLY them.
#
# This restores the selective rule the old "nuke every pkgar + wipe every
# build/sysroot" version had replaced (which recompiled the entire desktop on
# any driver/acpi/base edit). For a genuine relibc *C-ABI* break — a libc
# struct/inline baked into a compiled consumer, which is rare — force a full
# clean rebuild with `--no-cache`.
ABI_CRITICAL_PKGS="base redoxfs userutils bootstrap"
_boot_abi_changed=0
[ "$relibc_rebuilding" = "1" ] && _boot_abi_changed=1
[ "$USERSPACE_RUNTIME_STALE" = "1" ] && _boot_abi_changed=1
if [ "$_boot_abi_changed" = "1" ]; then
echo ">>> Boot ABI touched (relibc / syscall protocol fork) — relinking ONLY the"
echo ">>> static initfs-critical binaries: $ABI_CRITICAL_PKGS."
echo ">>> Dynamically-linked userspace (Qt/KF6/mesa/sddm/desktop) is untouched —"
echo ">>> shared libc.so resolved at runtime (the Linux glibc model)."
for _pkg in $ABI_CRITICAL_PKGS; do
rm -f "$REPO_DIR/$_pkg".*
find "$PROJECT_ROOT/recipes" "$PROJECT_ROOT/local/recipes" \
-path "*/$_pkg/target" -type d -exec rm -rf {} + 2>/dev/null || true
done
fi
fi