tawasuyu

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1411Shell 141Markdown 85Rust 842918 Definiciones

zsh sella estático, con sus módulos y con `=~` — y dos teorías mías que eran falsas

El primer build confirmó la predicción que la propia receta traía escrita: `link = "static"` salió
DINÁMICO, contra la libncursesw del lab, y moría con `Error relocating ... symbol not found` —o sea,
habría arrancado en el sandbox de Alpine y no dentro de una imagen—. La causa de fondo es que zsh
carga sus módulos con dlopen; por eso va `--disable-dynamic`, que los mete en el binario.

Pero `--disable-dynamic` solo dejaba un zsh sin `=~`:

    zsh:1: failed to load module: zsh/regex
    zsh:1: -regex-match not available for regex

Dos explicaciones mías fueron falsas antes de la buena, y quedan escritas en la receta porque las dos
son plausibles y costaron un build cada una: (1) «quedan en link=dynamic, hay que sedearlos a static»
—el sed no cambió nada, los disponibles ya salían static—; (2) «el .mdd evalúa $ac_cv_func_regcomp y
config.status no tiene esas variables, hay que exportarlas» —el configure contesta yes a las cuatro y
el módulo seguía fuera—.

Lo zanjó `configure.ac`, que no opina: con dynamic apagado, un módulo que su .mdd declara `dynamic`
se DESCARTA (link=no) y sólo los que declaran `either` pasan a estático. regex.mdd dice `dynamic` y
complete.mdd dice `either`: por eso había completado y no había `=~`. El arreglo va sobre los .mdd,
antes del configure, y sobrevive a que make regenere config.modules.

Además `--enable-pcre` era una etiqueta, no un hecho: zsh 5.9 sólo habla PCRE1 y el corpus tiene
pcre2; el configure lo decía tres veces. Se quitan la bandera y la dep, que mentían sobre el cierre.

Y el control va EN LA RECETA: la instalación corta si el ELF tiene INTERP o si falta alguno de
{regex, complete, zle, parameter} dentro del binario. Ese guardián es quien atajó los dos intentos
fallidos en vez de sellarlos. Medido sobre el artefacto: statically linked, 16 módulos cargan,
`=~` contesta, 1663 completadores, 14 M.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

c8744ace1819e93823730308b2bb5e5e89b77e8f · por Sergio · (hace 1 mes) · padre 5d16b55e1 · ver el árbol en este commit

Delta semántico

Ninguna definición cambió: el cambio es de texto, documentación o datos.

1 archivos · +86 −11

diff --git a/recipes/zsh.toml b/recipes/zsh.toml
index 2fe0cdac..b1b866e5 100644
--- a/recipes/zsh.toml
+++ b/recipes/zsh.toml
@@ -19,13 +19,69 @@
 # nuevo de Alpine, punteros incompatibles de gcc14 y el cacheo de `man -w`.
 #
 # ══ RIESGO CONOCIDO PARA EL PRIMER BUILD (heredado de bash.toml) ═══════════════════════════════
-# `link = "static"` puede salir DINÁMICO igual, como le pasó a bash: si el `configure` mete
-# `-rdynamic` en la línea de enlace, el wrapper de cc del lab (`sandbox.rs:597`) tira el `-static`
-# inyectado y el binario sale contra `libncursesw.so.6` — que **ningún artefacto del corpus
-# provee**, así que arrancaría en el sandbox de Alpine y no dentro de una imagen de takana. El
-# síntoma exacto a buscar es `Error relocating ... symbol not found`. Si pasa, el arreglo de bash
-# fue `make LOCAL_LDFLAGS=` y vale acá igual.
-# Verificar el primer artefacto con `file`: debe decir `statically linked`.
+# ✅ **PASÓ, Y LA PREDICCIÓN ERA CORRECTA** (primer build, 2026-09-17): el artefacto salió
+# `dynamically linked, interpreter /lib/ld-musl-x86_64.so.1` y murió con el síntoma exacto anotado
+# acá arriba:
+#
+#     Error relocating /lib/libncursesw.so.6: __vfprintf_chk: symbol not found
+#
+# —o sea, se enlazó contra la ncurses del LAB de Alpine, que ninguna imagen de takana tiene—.
+#
+# La causa de fondo es más honda que el `-rdynamic` de bash: **zsh construye sus módulos como `.so`
+# y los carga con `dlopen`**, y un binario que hace `dlopen` no puede ser estático. Por eso el
+# arreglo no es sólo tirar la bandera, es `--disable-dynamic`: los módulos quedan ENLAZADOS DENTRO
+# del binario. Se pierde `zmodload` de un `.so` de fuera —que en una distro de artefactos sellados
+# no tiene sentido— y se gana un zsh que arranca. `make LOCAL_LDFLAGS=` queda igual, por si el
+# `configure` mete `-rdynamic` de todas formas.
+#
+# Y el control **va en la receta**, no en la cabeza de quien la corra: la fase de instalación
+# pregunta por el `INTERP` del ELF con el `readelf` de binutils (que ya es dep) y CORTA si aparece.
+# Un zsh dinámico sella, reproduce y no arranca: es [[needed-colgante-libstdcxx]] otra vez, y sin el
+# guardián no se descubre hasta que alguien abre una terminal dentro de la imagen.
+#
+# ══ Y `--disable-dynamic` NO LINKEA LOS MÓDULOS: LOS APAGA ═════════════════════════════════════
+# Segundo build, y el binario ya era estático — pero `zmodload -L` decía **`zsh/main` y nada más**:
+#
+#     $ zsh -c '[[ "takana-2026" =~ "^takana-[0-9]+$" ]]'
+#     zsh:1: failed to load module: zsh/regex
+#     zsh:1: -regex-match not available for regex
+#
+# O sea un zsh sin `=~`, que es de las cosas que más se escriben en un script. Los que sí estaban
+# —`zsh/complete`, `zsh/zle`— confunden más, porque el completado funciona y parece que está todo.
+#
+# ⚠ **Dos explicaciones mías fueron FALSAS antes de la buena. Las dejo escritas porque las dos son
+# plausibles y cuestan un build cada una.**
+#
+#   1ª) «`--disable-dynamic` deja los módulos en `link=dynamic` y hay que pasarlos a `static` con un
+#       `sed` sobre `config.modules`». El `sed` no cambió nada: los disponibles YA salían `static`.
+#   2ª) «el `link=` del `.mdd` evalúa `$ac_cv_func_regcomp` y `config.status` no tiene esas variables,
+#       así que hay que EXPORTARLAS». Tampoco: el configure contesta `yes` a las cuatro y el módulo
+#       seguía fuera.
+#
+# Lo que zanjó el asunto fue leer `configure.ac`, que no opina — decide:
+#
+#     case "$link" in
+#       dynamic) if test x$dynamic != xno; then … else  link=no   fi ;;   ← ACÁ
+#       either)  if test x$dynamic != xno; then … else  link=static fi ;;
+#
+# O sea: con `--disable-dynamic`, un módulo que su `.mdd` declara **`dynamic` se DESCARTA**, y sólo
+# los que declaran **`either`** pasan a estático. `Src/Modules/regex.mdd` dice `echo dynamic`, y
+# `Src/Zle/complete.mdd` dice `either`: por eso había completado y no había `=~`. No era el entorno
+# ni el orden: era el vocabulario del `.mdd`.
+#
+# Por eso el arreglo va sobre los `.mdd`, ANTES del configure —`dynamic` → `either`—, que además
+# sobrevive a que `make` regenere `config.modules` (lo regenera: corre `config.status` de nuevo).
+# A los que les falta su librería el `.mdd` les hace decir `no`, y esos siguen fuera, que es correcto.
+#
+# La lección general: `checking for regcomp... yes` en el log **no** implica que el módulo que lo
+# necesita se construya. Lo que manda es `config.modules`, y ahí el estado era `link=no`.
+#
+# ══ `--enable-pcre` ERA UNA ETIQUETA, NO UN HECHO ══════════════════════════════════════════════
+# zsh 5.9 sólo habla **PCRE1** (busca `pcre-config` y `pcre.h`); el corpus tiene **pcre2**. El
+# configure lo dijo tres veces —`checking for pcre-config... no`, `pcre.h... no`,
+# `pcre_compile... no`— y la receta seguía declarando la bandera y la dep. Se quitan las dos: una dep
+# que no se usa miente sobre el cierre y una bandera que no engancha miente sobre la capacidad.
+# `=~` no la necesita: lo resuelve `zsh/regex` sobre la regex de libc.
 name    = "zsh"
 version = "5.9"
 license = "MIT"
@@ -63,36 +119,55 @@ flags    = []
 # Encenderlo pediría una receta más para una capacidad que nadie pidió.
 compile = '''
 _abuild_phase() {
+	# Ver la nota de arriba: con `--disable-dynamic`, lo que el `.mdd` declara `dynamic` se DESCARTA
+	# y sólo lo que declara `either` queda estático. Esto reescribe ese vocabulario antes de que el
+	# configure lo lea; los `.mdd` que dicen `no` por falta de librería siguen diciendo `no`.
+	sed -i "s/echo dynamic/echo either/; s/^link=dynamic$/link=either/" Src/*/*.mdd Src/*/*/*.mdd
 	./configure \
 		--build=$CBUILD \
 		--host=$CHOST \
 		--prefix=/usr \
 		--bindir=/bin \
 		--enable-etcdir=/etc/zsh \
-		--enable-pcre \
 		--enable-cap \
 		--enable-multibyte \
 		--enable-function-subdirs \
 		--enable-fndir=/usr/share/zsh/functions \
 		--enable-zsh-secure-free \
 		--disable-gdbm \
+		--disable-dynamic \
 		--sysconfdir=/etc \
 		--mandir=/usr/share/man \
 		--infodir=/usr/share/info \
 		--with-tcsetpgrp
-	make
+	# Los módulos ADENTRO del binario: `--disable-dynamic` por sí solo los deja fuera.
+	make LOCAL_LDFLAGS=
 }
 _abuild_phase
 '''
 # de package() de Alpine (traducido $pkgdir→/out). Sin locales: el corpus no los hidrata y pesan.
 install = '''
 _abuild_phase() {
-	make DESTDIR="/out" install install.info
+	make LOCAL_LDFLAGS= DESTDIR="/out" install install.info
 	rm -rf "/out"/usr/share/locale
 	# `/etc/shells` no se escribe acá: es estado del sistema instalado, no del artefacto.
+	# GUARDIÁN: un zsh dinámico sella, reproduce y NO ARRANCA dentro de una imagen (pide
+	# libncursesw.so.6, que nadie provee). Se comprueba el hecho en el ELF, no la intención.
+	if readelf -l /out/bin/zsh | grep -q INTERP; then
+		echo "zsh: el binario salió DINÁMICO (tiene INTERP) — pediría libncursesw.so.6 dentro de la imagen y moriría con 'Error relocating'. Mirar si el configure volvió a meter -rdynamic"
+		exit 1
+	fi
+	# Y que los módulos estén DENTRO: un zsh sin `zsh/regex` no tiene `=~`, y eso no se nota
+	# hasta que un script lo usa. Se pregunta al binario, que es quien sabe.
+	for m in zsh/regex zsh/complete zsh/zle zsh/parameter; do
+		if ! /out/bin/zsh -fc "zmodload $m" 2>/dev/null; then
+			echo "zsh: falta el módulo $m dentro del binario — mirar config.modules tras el configure (¿quedó link=dynamic?)"
+			exit 1
+		fi
+	done
 }
 _abuild_phase
 '''
 
 [deps]
-build = ["binutils", "ncurses", "pcre2", "libcap", "linux-headers", "make"]
+build = ["binutils", "ncurses", "libcap", "linux-headers", "make"]