ADR 0020: el experimento CORRIDO — 10 de 12 sobreviven a -ec, y el que cae tiene el artefacto BIEN
El diseño es la parte que vale: NO SELLA NADA. Modificar una receta le cambia el ArtifactHash, así que medirlo a lo bruto habría dejado una docena de duplicados basura en un store que se respalda. En vez de eso se inyecta `set -e` en cada fase Y un `exit 77` al final de la última: el build siempre falla, nunca sella, y el veredicto se lee en el código de salida — 77 significa que todas las fases corrieron enteras bajo set -e. Queda en scripts/farm/exp-ec.sh. Muestra de 12 recetas ligeras (los pesos pesados quedan fuera por tiempo, y el sesgo va declarado): 10 sobreviven sin tocar nada, 1 se rompe, 1 inconcluso por timeout. El que se rompe es `e2fsprogs`, y mirarlo de cerca cambia lo que significa: su ./configure aborta con `external uuid library not found` pese a que la receta pasa --disable-libuuid, y hoy eso se traga —comprobado: la misma receta sin set -e y con exit 77 LLEGA al 77—. Pero el artefacto que sella está bien: 149 ficheros con e2fsck, mke2fs, los cuatro fsck.ext* y los mkfs.ext*. O sea que ahí `-ec` no atrapa un bug: rompe una receta que funciona. Ése es el coste, y es el número que no se podía adivinar leyendo: ~8%, unas 8 recetas sobre las 96 expuestas. También se corrige el recuento: 96, no 89. El 89 salía de restar 142−53 dando por hecho que las 53 con `set -e` propio estaban todas dentro de las 142, y no lo estaban. jaula-preparar suma lo que hizo falta para poder correr todo esto desde adentro: rsync/python3/jq/ cargo enlazados del store, y el cargador de musl, sin el cual python3 y cargo no arrancan en una imagen glibc.
This commit is contained in:
Executable
+53
@@ -0,0 +1,53 @@
|
||||
#!/bin/bash
|
||||
# exp-ec.sh — el experimento del ADR 0020: ¿cuántas recetas dejan de construir si las fases
|
||||
# corriesen con `sh -ec` en vez de `sh -c`?
|
||||
#
|
||||
# ── POR QUÉ NO SELLA NADA, Y POR QUÉ ESO IMPORTA ───────────────────────────────────────────────
|
||||
# Modificar una receta le cambia el `ArtifactHash`, así que medir esto a lo bruto dejaría un
|
||||
# duplicado basura en el store POR CADA receta que pase — en un store que se respalda. En vez de eso
|
||||
# se inyecta `set -e` al principio de cada fase Y un `exit 77` al final de la última, de modo que el
|
||||
# build SIEMPRE falla y nunca sella. El veredicto se lee en el código que reporta takana:
|
||||
#
|
||||
# · falló con 77 ⇒ las fases corrieron ENTERAS bajo set -e ⇒ SOBREVIVE a `-ec`
|
||||
# · falló con ≠77 ⇒ una orden intermedia falla y hoy se traga ⇒ SE ROMPE con `-ec`
|
||||
#
|
||||
# Las copias se escriben en `recipes/exp-ec-*.toml` —tienen que vivir ahí porque takana resuelve las
|
||||
# deps RELATIVAS al directorio de la receta— y se barren al terminar.
|
||||
#
|
||||
# ⚠ El lock se toma POR RECETA, no por tanda. La regla 1 de CLAUDE.md dice lo contrario para las
|
||||
# tandas normales, y con razón; acá es al revés a propósito: una tanda de éstas puede durar horas y
|
||||
# retener `work/.farm-build.lock` todo ese tiempo pararía la cosecha del latido.
|
||||
#
|
||||
# Uso: MUESTRA=/ruta/lista.txt scripts/farm/exp-ec.sh (una receta por línea, p.ej. recipes/gzip.toml)
|
||||
cd "$(dirname "$0")/../.."
|
||||
TMP=${TMPDIR:-/tmp}/exp-ec
|
||||
REC=$(cd "$(dirname "$0")/../.." && pwd)/recipes
|
||||
mkdir -p "$TMP"; OUT="$TMP/resultados.tsv"; : > "$OUT"
|
||||
|
||||
while read -r r; do
|
||||
[ -f "$r" ] || continue
|
||||
n=$(basename "$r" .toml)
|
||||
awk '
|
||||
/^(compile|install|configure|check|prepare) = .{3}$/ { print; print "set -e"; inb=1; next }
|
||||
{ print }
|
||||
' "$r" > "$REC/exp-ec-$n.toml"
|
||||
# `exit 77` como última línea de la última fase del fichero
|
||||
last=$(grep -n "^'''$" "$REC/exp-ec-$n.toml" | tail -1 | cut -d: -f1)
|
||||
[ -n "$last" ] && sed -i "${last}i exit 77" "$REC/exp-ec-$n.toml"
|
||||
|
||||
log="$TMP/$n.log"
|
||||
timeout 420 flock -o /work/.farm-build.lock takana build "$REC/exp-ec-$n.toml" > "$log" 2>&1
|
||||
rc=$?
|
||||
if grep -q "exit 77" "$log"; then v="SOBREVIVE"
|
||||
elif [ $rc -eq 124 ]; then v="TIMEOUT"
|
||||
elif grep -qE "build phase falló \(exit [0-9]+\)" "$log"; then v="SE-ROMPE"
|
||||
else v="OTRO"; fi
|
||||
det=$(grep -oE "build phase falló \(exit [0-9]+\): .*" "$log" | head -1 | cut -c1-90)
|
||||
printf '%s\t%s\t%s\n' "$n" "$v" "$det" >> "$OUT"
|
||||
echo "[$v] $n"
|
||||
done < ${MUESTRA:?hace falta MUESTRA=<fichero con una receta por línea>}
|
||||
|
||||
echo "════ RESUMEN ════"
|
||||
for v in SOBREVIVE SE-ROMPE TIMEOUT OTRO; do echo "$v: $(grep -c " $v " "$OUT")"; done
|
||||
rm -f "$REC"/exp-ec-*.toml
|
||||
echo "copias de experimento barridas de recipes/: $(ls "$REC"/exp-ec-*.toml 2>/dev/null | wc -l) quedan"
|
||||
@@ -81,11 +81,29 @@ echo " ✓ known_hosts con $(grep -c . "$K" 2>/dev/null || echo 0) entradas"
|
||||
BIN=/work/$U/bin
|
||||
if [ -d "$BIN" ]; then
|
||||
install -d -o "$U" -g "$U" "$UP/usr/bin"
|
||||
for t in takana patch gh hcloud; do
|
||||
for t in takana patch gh hcloud rsync python3 jq cargo; do
|
||||
[ -x "$BIN/$t" ] || continue
|
||||
ln -sfn "$BIN/$t" "$UP/usr/bin/$t" 2>/dev/null || true
|
||||
done
|
||||
echo " ✓ takana/patch/gh/hcloud enlazados al PATH desde $BIN"
|
||||
ln -sfn "$BIN/python3" "$UP/usr/bin/python" 2>/dev/null || true
|
||||
echo " ✓ herramientas del store enlazadas al PATH desde $BIN"
|
||||
fi
|
||||
|
||||
# ── 5. el cargador de musl, para que los artefactos DINÁMICOS arranquen ─────────────────────────
|
||||
# No todo lo del store es estático: `python3` y `cargo` son musl DINÁMICOS y piden
|
||||
# `/lib/ld-musl-x86_64.so.1`, que una imagen glibc no tiene. Sin esto fallan con un `No such file or
|
||||
# directory` que señala al binario y no al cargador, que es lo que despista.
|
||||
#
|
||||
# ⚠ Y no alcanza con el cargador: `cargo` pide además `libz.so.1` y `libgcc_s.so.1` POR NOMBRE, y en
|
||||
# una imagen glibc esos nombres existen — con otro contenido. Coge las de glibc y muere en
|
||||
# `Error relocating /lib/libz.so.1: __snprintf_chk: symbol not found` (símbolos de fortify que musl
|
||||
# no tiene). Por eso su wrapper exporta LD_LIBRARY_PATH al lab; acá sólo va el cargador.
|
||||
LAB=${LAB:-/work/$U/dev-fs/alpine}
|
||||
if [ -f "$LAB/lib/ld-musl-x86_64.so.1" ]; then
|
||||
install -D -o "$U" -g "$U" -m 755 "$LAB/lib/ld-musl-x86_64.so.1" "$UP/usr/lib/ld-musl-x86_64.so.1"
|
||||
echo " ✓ cargador de musl en /usr/lib (lo alcanza /lib, que es symlink a usr/lib)"
|
||||
else
|
||||
echo " · sin lab en $LAB — el cargador de musl no se pone (python3/cargo del store no arrancarán)"
|
||||
fi
|
||||
|
||||
echo "jaula $INST preparada — lo del manifiesto (paquetes, concesiones) lo pone `takana qorpa provision`"
|
||||
|
||||
Reference in New Issue
Block a user