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"]