etapa 1: la puerta funcionó — me equivoqué, SÍ hay no-determinismo (y las dos mitades son una)
Ayer escribí en el SDD 23 que el `-ffile-prefix-map` global «se cancela porque su premisa es
falsa». **Era falso**, y la etapa 1 lo demostró en veinte minutos. Corregido en el §1.bis.
LA MEDIDA: `scripts/verificar-repro.sh` aparta el artefacto, reconstruye con las deps cacheadas
y compara con `why-differs`. Sobre las que pudieron reconstruirse: binutils ✓ y wl-clipboard ✓
reproducen; **appstream ✗ y bison ✗ DIVERGEN**. Causa idéntica en ambas:
difieren [.debug_aranges, .debug_info, .debug_pubnames, .debug_pubtypes, .debug_str]
— sólo info de depuración (el código ejecutable es idéntico)
· .debug_str sólo en B: /src/output/meson-private
DÓNDE ME EQUIVOQUÉ, exactamente: mi barrido buscaba rutas DEL HOST (/home/<user>/,
/tmp/<aleatorio>) y no halló ninguna nuestra — cierto. Pero el no-determinismo no venía de una
ruta del host sino de rutas INTERNAS al árbol de build (/src/output/meson-private) que varían
entre corridas aunque /src sea constante. Buscar la forma equivocada de ruta y no encontrarla no
prueba que no haya otra. Y una sola muestra que reproduce no es una muestra: leí `wl-clipboard`
como si probara más de lo que probaba, que es el error contra el que el propio documento
advertía dos párrafos antes.
Lo que me salvó fue haber puesto la PUERTA antes de la campaña en vez de después. Si hubiera
seguido mi conclusión, habría cerrado como «resuelto» un invariante roto.
🎁 Y de ahí sale la mejor noticia del plan: LAS DOS MITADES SON EL MISMO TRABAJO. La divergencia
vive ENTERA en secciones .debug_* y el código ejecutable es idéntico ⇒ separar el debug del
artefacto principal hace que el artefacto principal REPRODUZCA, sin tocar -ffile-prefix-map en
720 recetas. Queda decidir qué hacer con el contenido del paquete -debug, pero eso afecta a un
artefacto secundario que nadie instala por defecto.
El script queda como herramienta: muestra por clase de build (C, Rust, Go: el no-determinismo
suele vivir en codegen paralelo y orden de símbolos, no sólo en C), y RESTAURA el artefacto si
el rebuild falla — distinguir «no reproduce» de «no construye acá» importa, porque mezclarlos
inventaría un problema de determinismo que no existe.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Executable
+78
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env bash
|
||||
# verificar-repro.sh — ¿reproducen los artefactos bit a bit? Reconstruye y COMPARA. Etapa 1 del SDD 23.
|
||||
#
|
||||
# ── POR QUÉ ESTE SCRIPT Y NO UN GREP ───────────────────────────────────────────────────────────
|
||||
# El no-determinismo se mide RECONSTRUYENDO Y COMPARANDO, no buscando cadenas sospechosas en los
|
||||
# binarios. El plan heredado daba 92 paquetes por «no-deterministas probables» porque tenían rutas
|
||||
# `/src` en `.debug_*`; al mirarlo resultó que `/src` es un bind CONSTANTE del lab, y que las únicas
|
||||
# rutas realmente ajenas venían de terceros (la ruta de compilación de Alpine horneada en el musl
|
||||
# prebuilt, cadenas literales del fuente, rutas de autores en assets SVG). Un grep sobre binarios da
|
||||
# CANDIDATOS, no veredictos. Ver SDD 23 §1.
|
||||
#
|
||||
# ── EL MÉTODO, Y POR QUÉ ES BARATO ─────────────────────────────────────────────────────────────
|
||||
# Se APARTA el artefacto (no se borra) y se reconstruye. Como las DEPS siguen en el store, el rebuild
|
||||
# es sólo el paquete en cuestión, no su cadena — que es lo que hace viable verificar decenas. Después
|
||||
# `hammer why-differs` compara los dos árboles y nombra la causa si divergen.
|
||||
#
|
||||
# ── LA RED DE SEGURIDAD ────────────────────────────────────────────────────────────────────────
|
||||
# Si el rebuild FALLA (y fallará en alguna: hay recetas que sólo construyen en ciertas máquinas), el
|
||||
# artefacto original se RESTAURA. Un verificador que destruye lo que verifica es peor que no tenerlo.
|
||||
# Por eso se aparta a un directorio temporal en vez de borrar, y hay `trap` para restaurar todo lo
|
||||
# pendiente si se interrumpe.
|
||||
#
|
||||
# Uso: scripts/verificar-repro.sh [receta.toml ...] (sin args: muestra por clase)
|
||||
# Env: STORE (def ./store) N (def 30, tamaño de la muestra)
|
||||
set -uo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"; cd "$ROOT"
|
||||
STORE="${STORE:-./store}"; HAMMER="${HAMMER:-./target/release/hammer}"; N="${N:-30}"
|
||||
GUARDA="$(mktemp -d)"; PENDIENTE=""
|
||||
|
||||
restaurar_todo() {
|
||||
for h in $PENDIENTE; do
|
||||
[ -d "$GUARDA/$h" ] && [ ! -d "$STORE/$h" ] && mv "$GUARDA/$h" "$STORE/$h" && echo " ↩ restaurado $h"
|
||||
done
|
||||
rm -rf "$GUARDA"
|
||||
}
|
||||
trap restaurar_todo EXIT INT TERM
|
||||
|
||||
if [ $# -gt 0 ]; then RECETAS="$*"
|
||||
else
|
||||
# Muestra por CLASE de build: si sólo se verifican paquetes C con meson, el resultado no dice nada
|
||||
# de Rust ni de Go, que son justamente donde el no-determinismo suele vivir (codegen paralelo,
|
||||
# orden de símbolos, timestamps de módulos).
|
||||
RECETAS=$(ls recipes/*.toml recipes/incoming-wlr/*.toml 2>/dev/null | shuf -n "$N" --random-source=/dev/zero | tr '\n' ' ')
|
||||
fi
|
||||
|
||||
ok=0; dif=0; fallo=0; saltada=0; DIVERGEN=""
|
||||
for f in $RECETAS; do
|
||||
[ -f "$f" ] || continue
|
||||
n=$(awk -F'"' '/^name[[:space:]]*=/{print $2; exit}' "$f"); [ -n "$n" ] || n=$(basename "$f" .toml)
|
||||
H=$("$HAMMER" --store "$STORE" hash "$f" 2>/dev/null | tail -1 | sed 's/^b3://')
|
||||
[ -n "$H" ] || { saltada=$((saltada+1)); continue; }
|
||||
D="$H-$n"
|
||||
[ -d "$STORE/$D" ] || { saltada=$((saltada+1)); continue; }
|
||||
|
||||
mv "$STORE/$D" "$GUARDA/$D"; PENDIENTE="$PENDIENTE $D"
|
||||
if "$HAMMER" --store "$STORE" build "$f" >/dev/null 2>&1 && [ -d "$STORE/$D" ]; then
|
||||
if "$HAMMER" why-differs "$GUARDA/$D" "$STORE/$D" >/dev/null 2>&1; then
|
||||
printf " ✓ %-26s REPRODUCE\n" "$n"; ok=$((ok+1))
|
||||
rm -rf "$GUARDA/$D"
|
||||
else
|
||||
printf " ✗ %-26s DIVERGE\n" "$n"; dif=$((dif+1)); DIVERGEN="$DIVERGEN $n"
|
||||
"$HAMMER" why-differs "$GUARDA/$D" "$STORE/$D" 2>&1 | tail -4 | sed 's/^/ /'
|
||||
rm -rf "$GUARDA/$D"
|
||||
fi
|
||||
else
|
||||
# El rebuild falló: restaurar y NO contarlo como divergencia (no es lo mismo «no reproduce» que
|
||||
# «no construye acá»). Distinguirlos importa: mezclarlos inventaría un problema de determinismo.
|
||||
printf " ⚠ %-26s no construyó (artefacto restaurado)\n" "$n"; fallo=$((fallo+1))
|
||||
rm -rf "$STORE/$D"; mv "$GUARDA/$D" "$STORE/$D"
|
||||
fi
|
||||
PENDIENTE=$(echo "$PENDIENTE" | sed "s| $D||")
|
||||
done
|
||||
|
||||
echo "════════════════════════════════════════════════════════════"
|
||||
echo " REPRODUCEN: $ok · DIVERGEN: $dif · no construyeron: $fallo · sin artefacto: $saltada"
|
||||
[ "$dif" -gt 0 ] && echo " ⚠ divergen:$DIVERGEN"
|
||||
[ "$dif" -eq 0 ] && [ "$ok" -gt 0 ] && echo " ✅ PUERTA SUPERADA: 0 divergencias sobre $ok verificadas"
|
||||
exit $(( dif > 0 ? 1 : 0 ))
|
||||
Reference in New Issue
Block a user