#!/usr/bin/env bash # audit-needed.sh — ¿hay artefactos que piden una librería que NADIE del corpus provee? # # ── POR QUÉ EXISTE ───────────────────────────────────────────────────────────────────────────── # Encontrado el 2026-09-06 corriendo el navegador, no leyendo: `atuq` moría con decenas de # «Error relocating: _ZNKSt5ctypeIcE13_M_widen_initEv: symbol not found». El binario sellado de # firefox declara # # NEEDED libstdc++.so.6 NEEDED libgcc_s.so.1 NEEDED libc.so # # y las dos primeras NO EXISTEN en el store ni en ningún cierre: sólo en `.dev-fs/alpine`, o sea en # el LAB. Es exactamente la lección de `quitar-dep-no-apaga-funcion`: el proyecto la encuentra en el # sysroot del lab y el artefacto sella con un NEEDED que nadie provee. # # LO QUE LO HACE CARO ES QUE NINGUNA MÉTRICA LO VE. `build-state.json` dice `sealed`, # `verificar-repro.sh` dice que REPRODUCE bit a bit, los guardianes de la receta pasan — y el # binario no arranca en la distro. El verde responde «¿construye?», nunca «¿corre?». # # ── QUÉ MIDE, EXACTAMENTE ────────────────────────────────────────────────────────────────────── # Índice de sonames PROVISTOS = todo `*.so*` que haya en el store. Para cada ELF ejecutable del # store, sus `NEEDED` menos lo provisto. Lo que sobra es una dep de ejecución colgante. # # NO mira el lab a propósito: si mirara `.dev-fs/alpine` diría que todo está bien, que es justo la # ilusión que causó el problema. El lab es donde se CONSTRUYE, no donde se corre. # # Uso: scripts/audit-needed.sh [--store DIR] (sale 1 si hay colgantes) set -uo pipefail STORE="${STORE:-./store}" [ "${1:-}" = "--store" ] && STORE="$2" [ -d "$STORE" ] || { echo "no existe $STORE" >&2; exit 2; } tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT # 1. Qué provee el corpus. Sólo el basename: es lo que el loader busca. find "$STORE" -maxdepth 5 -name '*.so' -o -maxdepth 5 -name '*.so.*' 2>/dev/null \ | sed 's|.*/||' | sort -u > "$tmp/provisto" echo "== sonames provistos por el store: $(wc -l < "$tmp/provisto")" # 2. Qué pide cada artefacto. colgantes=0 for d in "$STORE"/*/; do d=${d%/}; nombre=$(basename "$d"); receta=${nombre#*-} falta="" while IFS= read -r f; do n=$(readelf -d "$f" 2>/dev/null | sed -n 's/.*Shared library: \[\(.*\)\]/\1/p') for so in $n; do grep -qxF "$so" "$tmp/provisto" && continue case "$so" in libc.so|ld-musl*) continue ;; esac # la libc la pone el sistema base case " $falta " in *" $so "*) ;; *) falta="$falta $so" ;; esac done done < <(find "$d" -maxdepth 4 -type f -perm -u+x 2>/dev/null | head -12) [ -n "$falta" ] && { printf ' ✗ %-24s pide y nadie provee:%s\n' "$receta" "$falta"; colgantes=$((colgantes+1)); } done echo "════════════════════════════════════════════════════════════" if [ "$colgantes" -eq 0 ]; then echo " ✅ sin deps de ejecución colgantes" else echo " ⚠ $colgantes artefacto(s) con deps de ejecución COLGANTES" echo " no arrancan fuera del lab, por más que sellen y reproduzcan" fi exit $([ "$colgantes" -eq 0 ] && echo 0 || echo 1)