deps.runtime: que ALGUIEN las lea — de 29 huecos de soname a 6
Tres arreglos que son el mismo: un campo que nadie lee es un campo que miente. 1. yupana._deps() leía SÓLO `deps.build`. Como yupana es la base de todas las herramientas de grafo, una dep de EJECUCIÓN no existía para ninguna: ni el vigía de sonames, ni la membresía de perfiles, ni el rootfs hidratado. Medido: firefox declaraba `runtime = ["gcc-libs"]` y el cierre de las cuatro imágenes seguía sin libstdc++.so.6, así que el navegador no arrancaba y el vigía lo seguía reportando como hueco DESPUÉS de haberlo arreglado. `deps.runtime` está en el esquema de hammer desde siempre. La unión es la definición de cierre: para CORRER hacen falta las dos. 2. build-state.py, lo mismo y por lo mismo. 3. El vigía entra en el LATIDO y deja docs/state/sonames.txt. Existía desde antes y contesta la pregunta que el grafo no contesta —no «¿está sellado?» sino «¿arranca?»— pero NADIE LO CORRÍA: no estaba en cosecha-cron y no dejaba fichero de estado. Por eso libstdc++.so.6, que rompía el navegador en los CUATRO perfiles, estuvo en su salida sin que nadie lo leyera, y se redescubrió arrancando atuq a mano. Un vigía que hay que acordarse de invocar no se distingue de no tenerlo. Y se BORRA scripts/audit-needed.sh, que escribí ayer sin ver que vigia-sonames.py ya hacía exactamente esto, con la misma frase en la cabecera. Dos herramientas que miden lo mismo divergen y la que nadie mira es la que miente; la que se queda es la que ya existía, que además reporta POR PERFIL y encontró más cosas. python3 declara sus cuatro deps de ejecución (readline/sqlite/lzma/bz2): son módulos de la stdlib que se cargan por dlopen, así que no rompen el arranque sino un `import` — un fallo que aparece lejos y no menciona a python. Resultado, con todo aplicado: 29 huecos -> 6, y los que quedan son otra clase. `libperl.so` es empaquetado de la receta perl; `libc.so.6` lo pide el `go` prebuilt y es un soname de GLIBC en una distro musl, que es un síntoma distinto. Los dos quedan anotados en docs/state/sonames.txt, que ahora se regenera solo.
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
== escritorio-mirada 41 nodos · 136 sonames provistos · 1 sin proveedor
|
||||||
|
FALTA libperl.so ← lo piden: perl
|
||||||
|
== escritorio-kde 306 nodos · 1387 sonames provistos · 1 sin proveedor
|
||||||
|
FALTA libperl.so ← lo piden: perl
|
||||||
|
== escritorio-gnome 190 nodos · 517 sonames provistos · 1 sin proveedor
|
||||||
|
FALTA libperl.so ← lo piden: perl
|
||||||
|
== escritorio-cosmic 161 nodos · 390 sonames provistos · 1 sin proveedor
|
||||||
|
FALTA libperl.so ← lo piden: perl
|
||||||
|
== escritorio-sway 201 nodos · 402 sonames provistos · 2 sin proveedor
|
||||||
|
FALTA libc.so.6 ← lo piden: go
|
||||||
|
FALTA libperl.so ← lo piden: perl
|
||||||
@@ -44,4 +44,12 @@ compile = 'make -j"$(nproc)"'
|
|||||||
install = "make install DESTDIR=/out"
|
install = "make install DESTDIR=/out"
|
||||||
|
|
||||||
[deps]
|
[deps]
|
||||||
|
# ✅ DECIDIDA 2026-09-06, y la decisión cambió porque cambió el cálculo. La verruga de arriba dejaba
|
||||||
|
# dos salidas sin elegir, y el argumento contra la primera era «la imagen carga una librería más sólo
|
||||||
|
# para que ande un comando». Ya no es cierto: `python3` sale con el MISMO `NEEDED libreadline.so.8`
|
||||||
|
# y es raíz de los cuatro perfiles, así que `readline-shared` entra en la clausura igual. El coste
|
||||||
|
# marginal de arreglar además el `sqlite3` de la CLI es CERO.
|
||||||
|
# `runtime` no entra en el ArtifactHash ⇒ no re-hashea, y los 6 dependientes que menciona la nota de
|
||||||
|
# arriba no se tocan. Esa era la otra mitad del precio y tampoco se paga.
|
||||||
|
runtime = ["readline-shared"]
|
||||||
build = ["make", "pkgconf"]
|
build = ["make", "pkgconf"]
|
||||||
|
|||||||
@@ -37,6 +37,16 @@ link = "dynamic"
|
|||||||
zig_version = "0.13.0"
|
zig_version = "0.13.0"
|
||||||
|
|
||||||
[deps]
|
[deps]
|
||||||
|
# ⚠ DEP DE EJECUCIÓN (2026-09-06). Este artefacto sale con `NEEDED libreadline.so.8` y hasta hoy
|
||||||
|
# ningún artefacto del cierre publicaba ese soname: la `readline` del catálogo es `--disable-shared`.
|
||||||
|
# En el rootfs hidratado se resolvía contra el sysroot Alpine DEL LAB — que en la máquina de
|
||||||
|
# desarrollo está y en la distro no. Lo venía reportando `scripts/vigia-sonames.py` en los cuatro
|
||||||
|
# perfiles. `runtime` NO entra en el ArtifactHash, así que esto NO re-hashea: cambia la CLAUSURA.
|
||||||
|
# Las CUATRO que `python3` pide por soname y que el cierre no publicaba (lo reporta
|
||||||
|
# `scripts/vigia-sonames.py`): readline para el REPL, y sqlite/lzma/bz2 para los módulos `sqlite3`,
|
||||||
|
# `lzma` y `bz2` de la stdlib, que se cargan por `dlopen` y por eso no rompen el arranque sino un
|
||||||
|
# `import` — un fallo que aparece lejos y no menciona a python.
|
||||||
|
runtime = ["readline-shared", "sqlite-shared", "xz-shared", "bzip2-shared"]
|
||||||
# `libffi` NO es opcional aunque el build no falle sin él: sin libffi el `configure` de CPython
|
# `libffi` NO es opcional aunque el build no falle sin él: sin libffi el `configure` de CPython
|
||||||
# construye un intérprete SIN el módulo `_ctypes`, y lo hace en SILENCIO — sella un python3 que
|
# construye un intérprete SIN el módulo `_ctypes`, y lo hace en SILENCIO — sella un python3 que
|
||||||
# arranca perfectamente y revienta más tarde, en otra receta, con
|
# arranca perfectamente y revienta más tarde, en otra receta, con
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
#!/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.
|
|
||||||
#
|
|
||||||
# ⚠ DOS CORRECCIONES SOBRE LA PRIMERA VERSIÓN, las dos por sub-reportar (2026-09-06):
|
|
||||||
# · Miraba sólo ejecutables y `head -12` por artefacto. El `NEEDED` que faltaba de verdad estaba
|
|
||||||
# en una LIBRERÍA (`libmozsandbox.so` pedía `libnspr4.so`), así que el audit daba «6 recetas»
|
|
||||||
# cuando el artefacto de firefox solo declara 30 sonames. Ahora mira TODOS los ELF.
|
|
||||||
# · No contaba lo que el propio artefacto TRAE. Firefox bundlea su nspr/nss y las encuentra por el
|
|
||||||
# `LD_LIBRARY_PATH` que fija su lanzador; contarlas como colgantes sería un falso positivo.
|
|
||||||
colgantes=0
|
|
||||||
for d in "$STORE"/*/; do
|
|
||||||
d=${d%/}; nombre=$(basename "$d"); receta=${nombre#*-}
|
|
||||||
# lo que este artefacto se provee a sí mismo
|
|
||||||
find "$d" \( -name '*.so' -o -name '*.so.*' \) 2>/dev/null | sed 's|.*/||' | sort -u > "$tmp/propio"
|
|
||||||
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
|
|
||||||
grep -qxF "$so" "$tmp/propio" && 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" -type f \( -name '*.so' -o -name '*.so.*' -o -perm -u+x \) 2>/dev/null)
|
|
||||||
[ -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)
|
|
||||||
@@ -124,7 +124,15 @@ def load_recipes():
|
|||||||
version=d.get("version"),
|
version=d.get("version"),
|
||||||
link=b.get("link"),
|
link=b.get("link"),
|
||||||
compiler=b.get("compiler"),
|
compiler=b.get("compiler"),
|
||||||
deps=d.get("deps", {}).get("build", []),
|
# ⚠ BUILD **+ RUNTIME** (2026-09-06). Antes sólo `build`, y por eso una dep de
|
||||||
|
# EJECUCIÓN declarada en la receta no entraba en la clausura ni en el rootfs
|
||||||
|
# hidratado: `firefox` declaraba `runtime = ["gcc-libs"]` y el grafo seguía sin
|
||||||
|
# conocerla, así que la imagen se armaba sin `libstdc++.so.6` y el navegador no
|
||||||
|
# arrancaba. `deps.runtime` existe en el esquema de hammer desde siempre; lo que
|
||||||
|
# faltaba era que ESTE contador lo mirara. Un campo que nadie lee es un campo que
|
||||||
|
# miente. La unión es la definición de clausura: para CORRER hacen falta las dos.
|
||||||
|
deps=sorted(set(d.get("deps", {}).get("build", []))
|
||||||
|
| set(d.get("deps", {}).get("runtime", []))),
|
||||||
cls=classify(n, d),
|
cls=classify(n, d),
|
||||||
queue=queue,
|
queue=queue,
|
||||||
# `foreign = true`: la receta SELLA bytes ajenos ya compilados, no construye desde
|
# `foreign = true`: la receta SELLA bytes ajenos ya compilados, no construye desde
|
||||||
|
|||||||
@@ -277,6 +277,20 @@ if [ -x "$HAMMER" ]; then
|
|||||||
# la granja entera. `nice -n 19` porque la prioridad la tiene construir, no auditar.
|
# la granja entera. `nice -n 19` porque la prioridad la tiene construir, no auditar.
|
||||||
AUDIT_SELLO="work/.static-audit.sello"
|
AUDIT_SELLO="work/.static-audit.sello"
|
||||||
if [ ! -f "$AUDIT_SELLO" ] || [ -n "$(find "$AUDIT_SELLO" -mtime +0 2>/dev/null)" ]; then
|
if [ ! -f "$AUDIT_SELLO" ] || [ -n "$(find "$AUDIT_SELLO" -mtime +0 2>/dev/null)" ]; then
|
||||||
|
# ── VIGÍA DE SONAMES (2026-09-06) ────────────────────────────────────────────────────────────
|
||||||
|
# `vigia-sonames.py` existía desde antes y contesta la pregunta que el grafo NO contesta: no
|
||||||
|
# «¿está sellado?» sino «¿arranca?». Encuentra los `NEEDED` que ningún artefacto del cierre
|
||||||
|
# publica y que el rootfs hidratado resolvía contra el sysroot del LAB.
|
||||||
|
#
|
||||||
|
# NADIE LO CORRÍA. Por eso `libstdc++.so.6` —que rompía el navegador en los CUATRO perfiles—
|
||||||
|
# estuvo en su salida sin que nadie lo leyera, y se redescubrió el 2026-09-06 arrancando `atuq`
|
||||||
|
# a mano. Un vigía que hay que acordarse de invocar no se distingue de no tenerlo: es el mismo
|
||||||
|
# argumento que `scripts/test-atuq-politica.py` hace sobre los guardianes.
|
||||||
|
# Deja fichero de estado para que la salida quede en el repo y se vea si empeora entre cosechas.
|
||||||
|
python3 scripts/vigia-sonames.py > docs/state/sonames.txt 2>&1 \
|
||||||
|
&& echo " sonames.txt ✓ $(grep -c 'FALTA' docs/state/sonames.txt) hueco(s) sin proveedor" \
|
||||||
|
|| echo " ⚠ vigia-sonames.py falló"
|
||||||
|
|
||||||
echo " static-audit: puerta diaria abierta, barriendo el store (~8 min, nice 19)…"
|
echo " static-audit: puerta diaria abierta, barriendo el store (~8 min, nice 19)…"
|
||||||
nice -n 19 scripts/static-audit.sh > work/.static-audit.salida 2>&1
|
nice -n 19 scripts/static-audit.sh > work/.static-audit.salida 2>&1
|
||||||
audit_rc=$?
|
audit_rc=$?
|
||||||
@@ -308,7 +322,7 @@ fi
|
|||||||
# 4. Commitear SÓLO el estado firme (el avance que el humano sigue). Recetas nuevas que YO autoré se
|
# 4. Commitear SÓLO el estado firme (el avance que el humano sigue). Recetas nuevas que YO autoré se
|
||||||
# commitean aparte, a mano, para no meter autoría a medias en un commit de cron.
|
# commitean aparte, a mano, para no meter autoría a medias en un commit de cron.
|
||||||
if [ "${NO_COMMIT:-}" != "1" ]; then
|
if [ "${NO_COMMIT:-}" != "1" ]; then
|
||||||
git add docs/state/build-state.json docs/state/build-state-kde.json docs/state/build-state-gnome.json docs/state/build-state-cosmic.json docs/state/build-state-wlr.json docs/state/drenaje.json docs/state/fuentes-vigia.json docs/state/kernel-contract.txt docs/state/static-audit.txt 2>/dev/null || true
|
git add docs/state/sonames.txt docs/state/build-state.json docs/state/build-state-kde.json docs/state/build-state-gnome.json docs/state/build-state-cosmic.json docs/state/build-state-wlr.json docs/state/drenaje.json docs/state/fuentes-vigia.json docs/state/kernel-contract.txt docs/state/static-audit.txt 2>/dev/null || true
|
||||||
if ! git diff --cached --quiet 2>/dev/null; then
|
if ! git diff --cached --quiet 2>/dev/null; then
|
||||||
git commit -q -m "estado: cosecha granja $(ts) — avance del árbol KDE" 2>/dev/null \
|
git commit -q -m "estado: cosecha granja $(ts) — avance del árbol KDE" 2>/dev/null \
|
||||||
&& { git push -q origin main 2>/dev/null && echo "==> estado commiteado+pusheado" \
|
&& { git push -q origin main 2>/dev/null && echo "==> estado commiteado+pusheado" \
|
||||||
|
|||||||
+10
-1
@@ -90,7 +90,16 @@ def cargar_colas():
|
|||||||
|
|
||||||
def _deps(f):
|
def _deps(f):
|
||||||
try:
|
try:
|
||||||
return tomllib.load(open(f, "rb")).get("deps", {}).get("build", []) or []
|
d = tomllib.load(open(f, "rb")).get("deps", {})
|
||||||
|
# ⚠ BUILD **+ RUNTIME** (2026-09-06). Leía sólo `build`, y por eso una dep de EJECUCIÓN no
|
||||||
|
# existía para NINGUNA herramienta que use yupana —el vigía de sonames, la membresía de
|
||||||
|
# perfiles, el grafo de estado—. Medido: `firefox` declaraba `runtime = ["gcc-libs"]` y el
|
||||||
|
# cierre de las cuatro imágenes seguía sin `libstdc++.so.6`, así que el navegador no
|
||||||
|
# arrancaba y el vigía lo seguía reportando como hueco después de haberlo arreglado.
|
||||||
|
# `deps.runtime` está en el esquema de hammer desde siempre; lo que faltaba era que ALGUIEN
|
||||||
|
# lo leyera. Un campo que nadie lee es un campo que miente.
|
||||||
|
# La unión es la definición de cierre: para CORRER hacen falta las dos.
|
||||||
|
return sorted(set(d.get("build", []) or []) | set(d.get("runtime", []) or []))
|
||||||
except Exception:
|
except Exception:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user