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 1572Shell 203Rust 109Markdown 993728 Definiciones

⎇ b0085e7cf6978a7f9e27aa863f756a463fc1407e recipes/gcc-libs.toml TOML · 204 líneas · 11.4 KB Historia crudo

gcc-libs enviaba un ld script que pedía una librería que nadie tenía e49545e0a · Sergio ·

1# gcc-libs — `libstdc++.so.6` y `libgcc_s.so.1`. LAS LIBRERÍAS DE EJECUCIÓN QUE FALTABAN.
2#
3# ══ POR QUÉ EXISTE, Y CÓMO SE DESCUBRIÓ ════════════════════════════════════════════════════════
4# No salió de leer el grafo sino de CORRER el navegador. El 2026-09-06, `atuq` bajo un sway headless
5# murió con decenas de `Error relocating: _ZNKSt5ctypeIcE13_M_widen_initEv: symbol not found`. El
6# binario SELLADO de firefox declara:
7#
8#     NEEDED libstdc++.so.6      NEEDED libgcc_s.so.1      NEEDED libc.so
9#
10# y las dos primeras no existían en el store ni en ningún cierre: sólo en `.dev-fs/alpine`, o sea en
11# el LAB. `scripts/vigia-sonames.py` —que ya existía y nadie corría— medía el alcance: 6 recetas colgantes —`firefox`, `atuq`,
12# `waterfox`, `librsvg`, `spidermonkey`, `mesa-llvmpipe`— y SÓLO ESTOS DOS sonames faltando en todo
13# el corpus. Como `firefox` y `atuq` son raíces de los CUATRO perfiles de escritorio, las cuatro
14# imágenes llevaban un navegador que no podía arrancar.
15#
16# Lo que lo hizo caro es que ninguna métrica lo veía: `build-state.json` decía `sealed`,
17# `verificar-repro.sh` decía que REPRODUCE bit a bit y los tres guardianes de la receta pasaban.
18# **El verde responde «¿construye?», nunca «¿corre?».**
19#
20# ══ POR QUÉ SE CONSTRUYE GCC ENTERO PARA DOS `.so` ═════════════════════════════════════════════
21# Porque no hay otra fuente: `libstdc++` no tiene tarball propio, es parte del árbol de GCC (Alpine
22# la publica como subpaquete de su aport `gcc`). Se construye SÓLO lo necesario —los target libs—
23# con `--disable-bootstrap`, que evita las tres etapas y es la diferencia entre una hora y cinco.
24#
25# ⚠ LA VERSIÓN NO ES LIBRE: 15.2.0 PORQUE ES LA DEL LAB. `firefox` se enlaza con el `clang++` del
26# lab, que usa la `libstdc++` COMPARTIDA del gcc del lab — 15.2.0, `libstdc++.so.6.0.34`. Si acá se
27# construyera otra versión, el `.so` existiría y los símbolos podrían no cuadrar: el modo de fallo
28# volvería, disfrazado de otra cosa. Un bump del lab obliga a bumpear esto.
29#
30# ⚠ `--disable-symvers` NO ES OPCIONAL Y NO ES COPIA CIEGA. Alpine lo pasa, y el versionado de
31# símbolos cambia la tabla de símbolos de la librería. Nuestros binarios se enlazaron contra la
32# libstdc++ SIN versionar del lab; una con versionado no los satisface. Verificado en el APKBUILD de
33# `main/gcc` de Alpine el 2026-09-06, junto con el resto de los flags de ABI de abajo.
34#
35# Verificado: el sha512 del tarball coincide byte a byte con el del APKBUILD de Alpine.
36name    = "gcc-libs"
37version = "15.2.0"
38license = "GPL-3.0-or-later WITH GCC-exception-3.1"
39
40[source]
41tarball = "https://ftp.gnu.org/gnu/gcc/gcc-15.2.0/gcc-15.2.0.tar.xz"
42sha256  = "438fd996826b0c82485a29da03a72d71d6e3541a83ec702df4271f6fe025d24e"
43
44[build]
45# Se construye GCC con el gcc del lab: es el camino que upstream prueba, y con zig-cc el configure
46# de GCC pelea por las mismas tres razones que ya documenta `firefox.toml`.
47compiler = "gcc"
48# ⚠ `dynamic` ES EL PUNTO DE LA RECETA. takana inyecta `LDFLAGS=-static` en modo estático, y acá el
49# producto ES una librería compartida: con el default no saldría ni un `.so`. Es la misma perilla
50# que apagaba los threads de openssl, vista desde el otro lado.
51link = "dynamic"
52
53[deps]
54# gmp/mpfr/mpc son las tres que el configure de GCC exige aunque sólo se quieran los target libs:
55# el top-level configura también el compilador. Estaban ya en `recipes/incoming-*`; se promovieron
56# al catálogo canónico para poder declararlas desde acá (la resolución es sibling-first y una receta
57# de `recipes/` no ve las de una cola).
58build = ["make", "m4", "gmp", "mpfr", "mpc"]
59
60[build.phases]
61configure = '''
62set -e
63# GCC EXIGE construir fuera del árbol de fuentes; si no, el configure aborta.
64mkdir -p build && cd build
65
66# Los flags de ABI son los de Alpine (`main/gcc`, verificado el 2026-09-06). No son preferencias:
67# esta librería tiene que ser intercambiable con la que el lab le dio a firefox al enlazar.
68../configure \
69  --prefix=/usr \
70  --build=x86_64-alpine-linux-musl \
71  --host=x86_64-alpine-linux-musl \
72  --target=x86_64-alpine-linux-musl \
73  --enable-languages=c,c++ \
74  --disable-bootstrap \
75  --disable-symvers \
76  --disable-libstdcxx-pch \
77  --disable-multilib \
78  --disable-nls \
79  --disable-werror \
80  --disable-cet \
81  --disable-fixed-point \
82  --disable-libssp \
83  --disable-libsanitizer \
84  --disable-libgomp \
85  --disable-libitm \
86  --disable-libquadmath \
87  --enable-__cxa_atexit \
88  --enable-default-pie \
89  --enable-default-ssp \
90  --enable-shared \
91  --enable-threads \
92  --enable-tls \
93  --enable-checking=release \
94  --with-linker-hash-style=gnu
95'''
96compile = '''
97set -e
98cd build
99# SÓLO los target libs. `all` construiría el compilador entero, que no queremos y que multiplica el
100# tiempo. Make resuelve solo que para llegar acá hace falta un xgcc de etapa 1.
101make -j"$(nproc)" all-target-libgcc all-target-libstdc++-v3
102'''
103install = '''
104set -e
105cd build
106make install-target-libgcc install-target-libstdc++-v3 DESTDIR=/out
107
108# ⚠ GCC INSTALA EN `lib64`, Y `--disable-multilib` NO LO APAGA. En x86_64 el `t-linux64` de GCC fija
109# `MULTILIB_OSDIRNAMES = ../lib64`, así que el install aterriza en `/usr/lib/../lib64/` — o sea
110# `/usr/lib64/`— por más que se haya desactivado el multilib. Esta distro es `lib`-only, como el
111# Alpine del que sale el lab, así que se normaliza acá.
112#
113# No es cosmético: el enlazador dinámico busca en `/usr/lib`, y un `.so` perfecto en `/usr/lib64`
114# es exactamente el fallo que esta receta viene a arreglar, sólo que un directorio más allá. Lo
115# cazó el guardián 1 en la primera corrida (`!! falta libstdc++.so.6`), que es para lo que está.
116if [ -d /out/usr/lib64 ]; then
117  mkdir -p /out/usr/lib
118  cp -a /out/usr/lib64/. /out/usr/lib/     # `-a` preserva los symlinks .so.6 -> .so.6.0.34
119  rm -rf /out/usr/lib64
120fi
121
122# ── libgcc.a SE SALVA DE LA PODA, Y ES UNA CORRECCIÓN (2026-09-17) ───────────────────────────
123# `/usr/lib/libgcc_s.so` que esta receta instala **no es una librería: es un ld script**
124#
125#     GROUP ( libgcc_s.so.1 -lgcc )
126#
127# y ese `-lgcc` es `libgcc.a`, que NINGUNA receta del corpus proveía —ésta la borraba con el
128# `find -name '*.a' -delete` de acá abajo, y de paso `rm -rf /usr/lib/gcc` se llevaba el original—.
129# Consecuencia medida en la caja de producción con el `rust` recién sellado: los binarios ESTÁTICOS
130# compilan y corren, pero **cualquier dylib —y eso incluye TODAS las proc-macros— muere**:
131#
132#     ld.lld: error: unable to find library -lgcc
133#
134# O sea `cargo build` con `derive` (serde, clap…) imposible sobre takana, con el compilador perfecto.
135# Es la misma familia que el hueco que originó esta receta: todo presente, todo reproducible, y el
136# camino que hace falta no existe. Y no alcanza con dar `-L /usr/lib`: con eso se resuelven `-lc` y
137# `-lgcc_s`, y entonces aparece justo este `-lgcc`.
138#
139# Va a `/usr/lib` —no al directorio privado de gcc— porque es donde el `-L` del enlazador ya mira, y
140# porque el gcc completo no está en esta distro: acá la librería tiene que valerse sola.
141A=$(find /out -name libgcc.a | head -1)
142test -n "$A" || { echo "!! el install no dejó ninguna libgcc.a — sin ella el ld script de libgcc_s miente" >&2; exit 1; }
143cp "$A" /out/usr/lib/libgcc.a
144
145# Se PODA todo lo que no sea runtime: headers, el resto de los `.a`, `.la`, libsupc++, los `.py` del
146# pretty-printer. Esta receta existe para que un binario ARRANQUE —y ahora también para que el
147# `libgcc_s.so` que envía sea ENLAZABLE—, no para compilar C++ contra ella; dejar los headers
148# invitaría a que otra receta se enlace contra esto en vez de contra el lab, que es una decisión
149# distinta y no la que se está tomando acá.
150find /out -name '*.la' -delete
151rm -rf /out/usr/include /out/usr/share /out/usr/lib/gcc
152find /out -name '*.a' ! -name 'libgcc.a' -delete
153find /out -name '*.py' -delete
154
155L=/out/usr/lib
156test -d "$L" || { echo "!! no se instaló nada en $L" >&2; find /out -maxdepth 3 >&2; exit 1; }
157
158# ── GUARDIÁN 1: los dos sonames, y que sean los que el loader busca ──────────────────────────
159# No alcanza con que exista un fichero llamado así: lo que el enlazador dinámico usa es el SONAME
160# de dentro. Un `.so` bien nombrado con otro soname no lo encuentra nadie.
161for so in libstdc++.so.6 libgcc_s.so.1; do
162  f=$(find "$L" -name "$so*" -o -name "$so" | head -1)
163  test -n "$f" || { echo "!! falta $so" >&2; ls -la "$L" >&2; exit 1; }
164  real=$(readelf -d "$f" 2>/dev/null | sed -n 's/.*SONAME.*\[\(.*\)\].*/\1/p')
165  test "$real" = "$so" || { echo "!! $f tiene SONAME '$real' y esperaba '$so'" >&2; exit 1; }
166  echo "guardián: $so ok (SONAME correcto, $(stat -Lc%s "$f") bytes)"
167done
168
169# ── GUARDIÁN 2: LA PRUEBA DEL CONSUMIDOR ─────────────────────────────────────────────────────
170# «Existe» no es «sirve». Se exige que estén DEFINIDOS los símbolos exactos que firefox no
171# encontraba — copiados del fallo real de `atuq`, no inventados. Si esta receta sella sin ellos,
172# el navegador vuelve a morir igual y el artefacto habría dicho que todo bien.
173# ⚠ EL SÍMBOLO PUEDE VENIR VERSIONADO, Y LA PRIMERA VERSIÓN DE ESTE GUARDIÁN NO LO CONTEMPLABA.
174# `--disable-symvers` desactiva el versionado de **libstdc++**, pero `libgcc` tiene su PROPIO
175# `libgcc.map` y sigue versionando pase lo que pase. Así que `nm` los imprime como
176# `_Unwind_Backtrace@@GCC_3.3`, y un grep anclado al fin de línea NO los encuentra: el guardián
177# reportaba «no definen los símbolos» sobre una librería que los definía perfectamente.
178# Comprobado contra el `libgcc_s.so.1` DEL LAB —el que hoy satisface a firefox— y coinciden hasta
179# en la etiqueta de versión: `_Unwind_Backtrace@@GCC_3.3`, `__udivti3@@GCC_3.0`.
180falta=""
181for s in _ZNKSt5ctypeIcE13_M_widen_initEv _ZNSt13runtime_errorC1EPKc _ZNKSt9bad_alloc4whatEv; do
182  nm -D --defined-only "$L"/libstdc++.so.6* 2>/dev/null | grep -qE " $s(@|\$)" || falta="$falta $s"
183done
184for s in _Unwind_Backtrace _Unwind_GetIP __udivti3; do
185  nm -D --defined-only "$L"/libgcc_s.so.1* 2>/dev/null | grep -qE " $s(@|\$)" || falta="$falta $s"
186done
187test -z "$falta" || {
188  echo "!! los .so están pero NO definen los símbolos que firefox pide:$falta" >&2
189  echo "   (son los del fallo real de atuq; si cambiaron, revisá --disable-symvers)" >&2
190  exit 1
191}
192echo "guardián: los 6 símbolos que firefox no encontraba están DEFINIDOS"
193
194# ── GUARDIÁN 3: EL LD SCRIPT NO PUEDE MENTIR ─────────────────────────────────────────────────
195# `libgcc_s.so` es un ld script que manda buscar `-lgcc`. Si esa librería no viaja con él, el
196# artefacto sella, reproduce, y la primera dylib que alguien enlace en una caja takana muere. Se
197# comprueba lo que el script PIDE contra lo que el artefacto TRAE, no la intención.
198if grep -q "GROUP" "$L/libgcc_s.so" 2>/dev/null; then
199  grep -q -- "-lgcc" "$L/libgcc_s.so" && test -f "$L/libgcc.a" || {
200    echo "!! $L/libgcc_s.so es un ld script que pide -lgcc y no hay libgcc.a al lado" >&2
201    cat "$L/libgcc_s.so" >&2; exit 1; }
202  echo "guardián: el ld script pide -lgcc y libgcc.a viaja con él ($(stat -Lc%s "$L/libgcc.a") bytes)"
203fi
204'''