autotools al corpus: no para TENERLO, para usarlo de forma REPRODUCIBLE
El hueco se pisó cuatro veces el mismo día: cada vez que un paquete publica
sólo configure.ac, el lab no puede construirlo. pcaudiolib, speech-dispatcher y
brltty se salvaron usando el asset del release en vez del archive del tag;
dotconf NO se salvó —upstream no publica releases, el asset da 404— y hubo que
compilar sus dos .c a mano.
⚠ Y EL MOTIVO REAL NO ES EL QUE ESCRIBÍ PRIMERO. El lab (.dev-fs/alpine) SÍ
trae autoconf, automake-1.18 y sus macros (medido). Las notas del repo dicen
«no están en el corpus», que es cierto, y yo lo conté como «no están en el lab»,
que es falso. Lo que sí es cierto, y es lo que importa: bin/autoconf resuelve
sus datos por rutas absolutas —
my $pkgdatadir = $ENV{'autom4te_perllibdir'} || '/usr/share/autoconf';
my $autom4te = $ENV{'AUTOM4TE'} || '/usr/bin/autom4te';
— así que una receta que corra autoreconf sin fijarlas usa las del LAB, y el lab
no entra en hash_inputs: dos labs distintos sellarían bytes distintos y los dos
builds parecerían correctos. Con las recetas en el corpus se puede declarar y
fijar.
Su sitio es [solo-build], por el mismo argumento que cmake: que se puedan
instalar desde el repo es correcto; horneados en el rootfs, no. Mi plan inicial
era declararlas en perfiles y era equivocado — lo corrigió leer la política.
No manda rehacer nada: usar el configure que upstream publicó sigue siendo MÁS
reproducible que regenerarlo, porque el nuestro saldría de nuestra versión de
autoconf, un input más.
⚠ LOS GUARDIANES EJECUTAN LA HERRAMIENTA, Y LAS DOS PRIMERAS VERSIONES ESTABAN
MAL — las dos lecciones valen más que las recetas:
1. automake SELLÓ UN FALLO. Dijo «Can't locate Automake/Config.pm in @INC» y el
build salió rc=0, por dos errores encadenados: el estado de $(cmd | head -1)
es el de `head`, que siempre sale 0, así que el || nunca se dispara —
EXACTAMENTE el error que había documentado horas antes leyendo $? tras un
pipe—; y el respaldo buscaba «1.17» cuando el propio mensaje de error
contiene /usr/share/automake-1.17, así que daba verde sobre el texto del
fallo. Ahora: ejecución y truncado separados, y la versión anclada a ^.
2. La prueba de autoconf era INVÁLIDA: exportaba sólo AUTOCONF y PATH, así que
el script leía /usr/share/autoconf DEL LAB. El configure de 2434 líneas
habría salido igual con /out sin una sola macro propia. Ahora las cuatro
rutas apuntan a /out, y con eso sí verifica autosuficiencia — y pasa.
Corolario general: para probar funcionalmente un paquete cuyos scripts hornean
rutas absolutas hay que redirigir TODAS sus variables al staging (PERL5LIB,
autom4te_perllibdir, AC_MACRODIR…). Si no, se prueba la imagen, no el artefacto.
Verificado: autoconf genera un configure de 2434 líneas con sus propias macros;
automake y aclocal arrancan y dicen 1.17.
Delta semántico
Ninguna definición cambió: el cambio es de texto, documentación o datos.
3 archivos · +218 −0
- docs/state/targets.toml +20 −0
- recipes/autoconf.toml +110 −0
- recipes/automake.toml +88 −0
diff --git a/docs/state/targets.toml b/docs/state/targets.toml
index 675275f2..3e9e19b2 100644
--- a/docs/state/targets.toml
+++ b/docs/state/targets.toml
@@ -74,6 +74,26 @@ paquetes = [
"rust",
# Generadores y parsers del build. `samurai` y `meson` son los que arrastran a `python3`.
"make", "cmake", "meson", "samurai", "pkgconf", "bison", "flex", "m4", "gperf", "cbindgen",
+ # `autoconf` y `automake` (2026-10-20): entran al corpus y **su sitio es esta lista**, por el
+ # mismo argumento que `cmake` — que se puedan instalar desde el repo es correcto; horneados en el
+ # rootfs, no.
+ #
+ # ⚠ El motivo de que existan NO es «tener autoconf»: es poder usarlo de forma REPRODUCIBLE. El
+ # lab (`.dev-fs/alpine`) ya trae `autoconf` y `automake-1.18` con sus macros —medido—, y una
+ # receta que corra `autoreconf` sin fijar `autom4te_perllibdir`/`AC_MACRODIR`/`AUTOM4TE` usa esos,
+ # que **no entran en `hash_inputs`**: dos labs distintos sellarían bytes distintos y los dos
+ # builds parecerían correctos. Con las recetas en el corpus, un build puede declararlas y fijar
+ # las rutas.
+ #
+ # Lo que destraban, concreto: un paquete que publique sólo `configure.ac` y ningún tarball de
+ # release. Ese hueco se pisó cuatro veces el mismo día (`pcaudiolib`, `speech-dispatcher`,
+ # `brltty` se salvaron usando el asset del release; `dotconf` no, y hubo que compilar sus dos
+ # `.c` a mano).
+ #
+ # ⚠ **No manda rehacer nada**: usar el `configure` que upstream publicó sigue siendo MÁS
+ # reproducible que regenerarlo, porque el nuestro saldría de nuestra versión de autoconf — un
+ # input más. Esto abre una puerta, no cambia las recetas que ya andan.
+ "autoconf", "automake",
# `nodejs`: el JS del build de Gecko. Llega a las 4 imágenes de escritorio por `atuq → firefox`.
"nodejs",
# `go`: el toolchain, 222 MiB. 366 recetas lo declaran build-dep y ninguna imagen lo necesita
diff --git a/recipes/autoconf.toml b/recipes/autoconf.toml
new file mode 100644
index 00000000..2779417d
--- /dev/null
+++ b/recipes/autoconf.toml
@@ -0,0 +1,110 @@
+# autoconf 2.72 — el generador de `configure`. Entra porque su AUSENCIA ya costó trabajo real.
+#
+# ══ POR QUÉ ENTRA AHORA, Y NO «algún día» ══════════════════════════════════════════════════════
+# El censo lo marca en «construcción C clásica», pero el argumento no es la fila del censo: es que
+# **este hueco se pisó tres veces en una sola tarde** (2026-10-20). Cada vez que un paquete publica
+# sólo `configure.ac` y no un tarball de release, el lab no puede construirlo:
+#
+# · `pcaudiolib` se salvó usando el asset del release en vez del archive del tag
+# · `speech-dispatcher` lo mismo
+# · `brltty` lo mismo
+# · `dotconf` **NO se salvó**: upstream no publica releases (el asset da 404), así que
+# hubo que compilar sus dos `.c` a mano. Funciona, pero es una receta que
+# no se parece a ninguna otra del repo
+#
+# O sea que la regla «mirá si publica un tarball de release antes de escribir el autoconf» es una
+# mitigación, no una solución: el día que haga falta un paquete que sólo tenga `configure.ac` y sea
+# más grande que dos ficheros, no hay salida. ⇒ con autoconf en el corpus, `autoreconf -fi` pasa a
+# ser una opción normal.
+#
+# ⚠ **Lo que NO cambia:** las recetas existentes que usan el tarball del release **se quedan como
+# están**. Usar el `configure` que upstream publicó es MÁS reproducible que regenerarlo —el nuestro
+# saldría de nuestra versión de autoconf/automake, que es un input más—, así que esto abre una
+# puerta, no manda rehacer nada.
+#
+# ══ ES BARATO PORQUE `perl` Y `m4` YA ESTÁN ════════════════════════════════════════════════════
+# autoconf es un puñado de scripts de perl más una biblioteca de macros m4; no compila nada. Los dos
+# ya están en el corpus, así que el coste es esta receta y nada más.
+#
+# ⚠ **`m4` y `perl` van también en `[deps] runtime`, y eso NO es redundancia.** autoconf los invoca
+# **al ejecutarse**, no al construirse: sin ellos en la imagen es un script que no puede trabajar —
+# la figura del «subcomando sin driver» que este repo tiene documentada. Un `[deps] build` solo los
+# pondría en el sandbox y los dejaría fuera de la clausura.
+name = "autoconf"
+version = "2.72"
+license = "GPL-3.0-or-later"
+
+[source]
+tarball = "https://ftp.gnu.org/gnu/autoconf/autoconf-2.72.tar.xz"
+sha256 = "ba885c1319578d6c94d46e9b0dceb4014caafe2490e437a0dbca3f270a223f5a"
+
+[build]
+compiler = "zig-cc"
+target = "x86_64-linux-musl"
+link = "static"
+flags = []
+
+[build.phases]
+configure = './configure --build=$CBUILD --host=$CHOST --prefix=/usr --sysconfdir=/etc'
+compile = 'make -j"$(nproc)"'
+# ⚠ EL GUARDIÁN CORRE LA HERRAMIENTA DE VERDAD, y es el punto de esta receta.
+#
+# autoconf no es un binario: son scripts de perl con rutas —a `perl`, a `m4`, a su propio
+# directorio de macros— **horneadas al configurar**. Un artefacto puede quedar completo, sellado y
+# reproducible, y ser un script que muere en la primera línea porque su intérprete no está donde
+# dice. Comprobar que los ficheros existen no distingue esos dos casos; ejecutarlo, sí.
+#
+# ⇒ Se genera un `configure` desde un `configure.ac` mínimo y se comprueba que SALGA y que sea un
+# script ejecutable de verdad. Es la prueba de extremo a extremo más corta posible.
+install = '''
+set -e
+make install DESTDIR=/out
+for b in autoconf autoreconf autoheader autom4te autoscan autoupdate ifnames; do
+ [ -x "/out/usr/bin/$b" ] || { echo "autoconf: falta /usr/bin/$b" >&2; ls /out/usr/bin 2>/dev/null >&2; exit 1; }
+done
+n=$(find /out/usr/share/autoconf -name '*.m4*' 2>/dev/null | wc -l)
+[ "$n" -gt 20 ] || { echo "autoconf: sólo $n fichero(s) de macros m4 — sin su biblioteca no puede expandir nada" >&2; exit 1; }
+
+# PRUEBA FUNCIONAL. `AC_INIT`+`AC_OUTPUT` es el `configure.ac` más corto que produce algo.
+#
+# ⚠⚠ **Y LA PRIMERA VERSIÓN DE ESTA PRUEBA ERA INVÁLIDA: usaba los datos DEL LAB.**
+# `bin/autoconf` resuelve su directorio de macros así (líneas 32 y 47-48):
+#
+# my $pkgdatadir = $ENV{'autom4te_perllibdir'} || '/usr/share/autoconf';
+# my $autom4te = $ENV{'AUTOM4TE'} || '/usr/bin/autom4te';
+# my $trailer_m4 = $ENV{'trailer_m4'} || '/usr/share/autoconf/autoconf/trailer.m4';
+#
+# o sea rutas ABSOLUTAS al destino final. Yo sólo exportaba `AUTOCONF` y `PATH`, así que el script
+# leía `/usr/share/autoconf`… **que EXISTE en el lab**: la imagen base trae `autoconf`,
+# `automake-1.18` y sus macros (medido en `.dev-fs/alpine`). El `configure` que salió era correcto y
+# no probaba nada de NUESTRO artefacto — habría salido igual con `/out` vacío de macros.
+#
+# ⇒ Ahora cada ruta se apunta a `/out` explícitamente. Si el artefacto no es autosuficiente, falla.
+#
+# ⚠ **Y el corolario importa más que esta receta:** cualquier receta que invoque `autoreconf` o
+# `autoconf` SIN fijar estas variables usará **el autoconf del lab**, y el lab **no entra en
+# `hash_inputs`** — o sea que dos labs distintos sellarían bytes distintos y el build parecería
+# correcto en los dos. Es exactamente la razón por la que estas dos recetas entran al corpus: no
+# para «tener autoconf», sino para poder usarlo de forma reproducible.
+t=$(mktemp -d)
+printf 'AC_INIT([prueba],[1])\nAC_OUTPUT\n' > "$t/configure.ac"
+( cd "$t" && env \
+ PATH="/out/usr/bin:$PATH" \
+ AUTOCONF="/out/usr/bin/autoconf" \
+ AUTOM4TE="/out/usr/bin/autom4te" \
+ autom4te_perllibdir="/out/usr/share/autoconf" \
+ AC_MACRODIR="/out/usr/share/autoconf" \
+ trailer_m4="/out/usr/share/autoconf/autoconf/trailer.m4" \
+ /out/usr/bin/autoconf ) || {
+ echo "autoconf: NO PUDO generar un configure usando SÓLO sus propios datos — el artefacto está completo y no es autosuficiente" >&2
+ exit 1
+}
+[ -s "$t/configure" ] || { echo "autoconf: corrió sin error y no dejó ningún ./configure" >&2; ls -la "$t" >&2; exit 1; }
+head -1 "$t/configure" | grep -q '^#!' || { echo "autoconf: el configure generado no empieza por un shebang — no es un script ejecutable" >&2; head -3 "$t/configure" >&2; exit 1; }
+echo "autoconf: 7 herramientas + $n macro(s) + genera un configure de $(wc -l < "$t/configure") líneas"
+'''
+
+[deps]
+build = ["make", "perl", "m4"]
+# Ver la nota de arriba: autoconf los invoca al EJECUTARSE.
+runtime = ["perl", "m4"]
diff --git a/recipes/automake.toml b/recipes/automake.toml
new file mode 100644
index 00000000..78692cb9
--- /dev/null
+++ b/recipes/automake.toml
@@ -0,0 +1,88 @@
+# automake 1.17 — el generador de `Makefile.in`. La otra mitad de `autoreconf`.
+#
+# Con `autoconf` solo se puede regenerar el `configure` de un proyecto, pero no sus `Makefile.in`:
+# un árbol que trae `Makefile.am` necesita automake, y `autoreconf -fi` llama a los dos. Ver la nota
+# larga de `recipes/autoconf.toml` sobre por qué este hueco entra ahora.
+#
+# ⚠ **`aclocal` es la pieza que hace que esto sirva de verdad**, y es la que más fácil queda a
+# medias: recoge las macros de `autoconf`, de `libtool` y de `pkgconf` y las junta en el
+# `aclocal.m4` del proyecto. Si no encuentra los directorios de macros de sus vecinos, `autoreconf`
+# falla con un `possibly undefined macro` que no nombra la causa — el error más común y más
+# desconcertante de la cadena autotools.
+#
+# ⇒ Por eso `libtool` y `pkgconf` entran en `runtime` además de `perl`: no son deps de construir
+# automake, son de que `aclocal` tenga qué recoger cuando alguien lo use en la imagen.
+name = "automake"
+version = "1.17"
+license = "GPL-2.0-or-later"
+
+[source]
+tarball = "https://ftp.gnu.org/gnu/automake/automake-1.17.tar.xz"
+sha256 = "8920c1fc411e13b90bf704ef9db6f29d540e76d232cb3b2c9f4dc4cc599bd990"
+
+[build]
+compiler = "zig-cc"
+target = "x86_64-linux-musl"
+link = "static"
+flags = []
+
+[build.phases]
+configure = './configure --build=$CBUILD --host=$CHOST --prefix=/usr --sysconfdir=/etc'
+compile = 'make -j"$(nproc)"'
+# Mismo criterio que autoconf: se EJECUTA, porque un script de perl con una ruta mal horneada se
+# sella igual de bien que uno que funciona.
+install = '''
+set -e
+make install DESTDIR=/out
+for b in automake aclocal; do
+ [ -x "/out/usr/bin/$b" ] || { echo "automake: falta /usr/bin/$b" >&2; ls /out/usr/bin 2>/dev/null >&2; exit 1; }
+done
+# Sus propios ficheros de apoyo (`install-sh`, `missing`, `compile`…): sin ellos, un
+# `automake --add-missing` no tiene qué copiar y el proyecto no construye.
+for f in install-sh missing compile depcomp; do
+ [ -n "$(find /out/usr/share -name "$f" 2>/dev/null | head -1)" ] || { echo "automake: falta el fichero de apoyo '$f' — un --add-missing no tendría qué copiar" >&2; exit 1; }
+done
+# ⚠ PRUEBA FUNCIONAL — Y ESTA VERSIÓN EXISTE PORQUE LA PRIMERA **SELLÓ UN FALLO**.
+#
+# Mi primer intento fue `v=$(… --version 2>&1 | head -1) || exit 1`, y no podía funcionar por dos
+# razones encadenadas, las dos mías:
+#
+# 1. **El estado de una tubería es el del ÚLTIMO comando.** `$(cmd | head -1)` devuelve el estado de
+# `head`, que siempre sale 0, así que el `||` NUNCA se dispara. Es EXACTAMENTE el error que
+# cometí horas antes leyendo `$?` después de un pipe y que ya está anotado en la memoria del
+# repo — repetido el mismo día, en una receta distinta.
+# 2. **El respaldo buscaba `1.17` y el MENSAJE DE ERROR contiene `1.17`**: el fallo era
+# `Can't locate Automake/Config.pm in @INC (@INC entries checked:
+# /usr/share/automake-1.17 …)`, así que el `grep` daba verde sobre el texto del error.
+#
+# Resultado: `automake` se selló mientras su propia prueba decía que no arrancaba.
+#
+# ⚠ Y la prueba además era INVÁLIDA, que es lo que el fallo escondía: el script hornea su ruta
+# absoluta (`unshift(@INC, '/usr/share/automake-1.17')`, línea 31) y durante el `install` los
+# ficheros todavía están en `/out/usr/share/…`, así que el módulo no resuelve **aunque el artefacto
+# esté perfecto**. Comprobado: `Config.pm` está donde corresponde en el artefacto. ⇒ para probarlo
+# desde el staging hay que apuntar `PERL5LIB` al directorio de `/out`.
+#
+# Las tres cosas arregladas: ejecución y truncado SEPARADOS, `PERL5LIB` puesto, y la versión
+# comprobada ANCLADA al principio de línea para que no coincida con una ruta dentro de un error.
+L="/out/usr/share/automake-1.17"
+[ -f "$L/Automake/Config.pm" ] || { echo "automake: falta $L/Automake/Config.pm — es el módulo que el script añade a @INC" >&2; find /out -name 'Config.pm' >&2; exit 1; }
+sal=$(PERL5LIB="$L" PATH="/out/usr/bin:$PATH" /out/usr/bin/automake --version 2>&1) || {
+ echo "automake: NO ARRANCA — el artefacto está completo y no funciona" >&2
+ printf '%s\n' "$sal" >&2; exit 1
+}
+v=$(printf '%s\n' "$sal" | head -1)
+printf '%s\n' "$v" | grep -qE '^automake \(GNU automake\) 1\.17' || { echo "automake: --version no empieza por 'automake (GNU automake) 1.17': $v" >&2; exit 1; }
+sal=$(PERL5LIB="$L" PATH="/out/usr/bin:$PATH" /out/usr/bin/aclocal --version 2>&1) || {
+ echo "automake: aclocal NO ARRANCA — es la pieza que junta las macros de autoconf, libtool y pkgconf" >&2
+ printf '%s\n' "$sal" >&2; exit 1
+}
+a=$(printf '%s\n' "$sal" | head -1)
+printf '%s\n' "$a" | grep -qE '^aclocal \(GNU automake\) 1\.17' || { echo "automake: aclocal --version inesperado: $a" >&2; exit 1; }
+echo "automake: $v · $a"
+'''
+
+[deps]
+build = ["make", "perl", "m4", "autoconf"]
+# `aclocal` recoge macros de estos al EJECUTARSE, no al construirse. Ver la nota de arriba.
+runtime = ["perl", "m4", "autoconf", "libtool", "pkgconf"]