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…

⎇ main recipes/zlib-rs.toml TOML · 118 lines · 7.2 KB History raw

paso 8 ter: lld21 sobre zlib-rs — el caso dinámico destapó que la .so no declaraba SONAME 6b10a805a · Sergio ·

1# zlib-rs 0.6.8 — `libz` en Rust, con la MISMA ABI de C. Variante para reemplazar a `zlib`.
2#
3# ── POR QUÉ ESTÁ EN EL CATÁLOGO Y NO EN NINGÚN PERFIL ────────────────────────────────────────────
4# `zlib` es la librería C **más consumida del corpus**: 122 recetas la declaran (más 83 la variante
5# `-shared`). Eso la vuelve la pieza donde la seguridad de memoria compra más… y también la más cara
6# de cambiar: el swap re-hashea esas 122. Esta receta existe para que la decisión se pueda tomar con
7# el artefacto delante, no en abstracto — el plan lo pedía así (`docs/plan-que-sigue-tras-busybox.md`
8# paso 8: «receta que publique el mismo SONAME, swap en UN consumidor, y comparar»).
9#
10# ── LO MEDIDO (2026-09-22, corpus Silesia de 15,7 MB, el mismo binario de prueba con las dos libs) ─
11#
12#   nivel │ zlib 1.3.1                 │ zlib-rs 0.6.8              │ tamaño   │ tiempo
13#   ──────┼────────────────────────────┼────────────────────────────┼──────────┼─────────
14#     1   │ 7.050.606 B ·  226,6 ms    │ 8.526.731 B ·   95,1 ms    │ +20,9 %  │ 2,4× más rápido
15#     6   │ 6.409.962 B ·  760,4 ms    │ 6.457.822 B ·  335,4 ms    │  +0,7 %  │ 2,3× más rápido
16#     9   │ 6.371.670 B · 1496,3 ms    │ 6.358.939 B ·  634,9 ms    │  −0,2 %  │ 2,4× más rápido
17#
18# Round-trip correcto en los tres niveles. O sea: al nivel POR DEFECTO cuesta 0,7 % de tamaño y
19# devuelve 2,3× de velocidad; al 9 es más chica Y más rápida; al 1 comprime bastante peor.
20#
21# ⚠ **NO es idéntica byte a byte.** El `fnv` de la salida difiere en los tres niveles. Consecuencia
22# que hay que decir antes de que alguien se sorprenda: cualquier artefacto que EMBEBA datos
23# comprimidos (páginas de manual gzipeadas, PNG, `.tar.gz`, un initramfs) **cambia de contenido** al
24# hacer el swap. No es no-determinismo —es determinista y distinto—, pero significa que la campaña de
25# las 122 no es «reconstruir y obtener lo mismo»: produce artefactos DISTINTOS.
26#
27# ⚠ **`gzprintf`/`gzvprintf` NO están.** Medido comparando símbolos contra `libz.a` del corpus: de los
28# 101 públicos faltan esos dos, y su feature en upstream **pide Rust nightly** (`c_variadic`). El
29# resto de la lista que falta es interna de zlib (`_tr_*`, `inflate_fast`, `inflate_table`, `zcalloc`,
30# `z_errmsg`), que ningún consumidor debería usar. Un consumidor que llame `gzprintf` NO enlaza.
31#
32# ⚠ **Quien enlace esto necesita `-lunwind`**, porque es Rust adentro de C: el `staticlib` arrastra el
33# backtrace de std (`addr2line`/`gimli`) y sin el unwinder el enlace muere con `undefined symbol:
34# _Unwind_Resume`. Es la perilla que el corpus ya conoce ([[lab-cargo-vendor-poliglota]]); zig la
35# provee. Por eso esta receta **no** se puede declarar como dep y ya: el consumidor tiene que sumar
36# `LIBS=-lunwind` o su equivalente.
37#
38# ⚠ **LA `.so` NO DECLARA SONAME SOLA, y sin eso es inservible para un consumidor dinámico**
39# (medido 2026-09-22): `readelf -d` sobre la `libz_rs.so` que sale de cargo muestra `NEEDED` y ningún
40# `SONAME`, mientras la `zlib-shared` del corpus declara `libz.so.1`. Un consumidor que enlace contra
41# una `.so` sin soname graba en su `NEEDED` **la ruta con la que se enlazó**, no el nombre de la
42# librería ⇒ el binario sella, reproduce y **no corre** ([[needed-colgante-libstdcxx]]). Se arregla
43# con `-C link-arg=-Wl,-soname,libz.so.1` en el compile, que es donde el hash lo cubre.
44#
45# ⚠ **`zlibVersion()` contesta `1.3.0-zlib-rs-0.6.8`.** El chequeo de versión de `deflateInit_` compara
46# sólo el primer carácter, así que pasa; un `configure` que parsee la cadena entera puede no gustarle.
47#
48# ── EL BUILD, Y POR QUÉ LLEVA UN PARCHE DE DOS LÍNEAS ───────────────────────────────────────────
49# El crate que publica la ABI de C es `libz-rs-sys-cdylib`, y upstream lo tiene **excluido** del
50# workspace. Compilarlo desde su directorio no sirve acá: el vendoreo del lab corre en la RAÍZ, así
51# que un `cargo build --offline` adentro del subdirectorio no encuentra las deps del crate excluido
52# («perhaps a crate was updated and forgotten to be re-vendored?»). Y meterlo al workspace sin más
53# tampoco: el crate aborta a propósito con
54#
55#     compile_error!("panic=\"abort\" is mandatory because unwinding to C is undefined behavior");
56#
57# porque el `[profile.release]` de un MIEMBRO se ignora (gana el de la raíz) — que es exactamente por
58# lo que upstream lo excluyó. El parche hace las dos cosas juntas: sube el crate a `members` y pone
59# `panic = "abort"` en el profile de la RAÍZ. Entra en `hash_inputs` (los parches sí entran), que es
60# donde tiene que estar una decisión de build.
61#
62# ⚠ Y toca **también el `Cargo.lock`**, porque el vendoreo del lab corre con `--locked`: con un miembro
63# nuevo el lock cambia y cargo se niega a actualizarlo («cannot update the lock file … because
64# --locked was passed»). El añadido son las 8 líneas del paquete nuevo; el lock sigue pineado, que es
65# la premisa del ADR 0006.
66#
67# `--features libz-rs-sys-cdylib/gz` exporta la familia `gz*`, que sí usan consumidores de verdad.
68name    = "zlib-rs"
69version = "0.6.8"
70license = "Zlib"
71
72[source]
73repo   = "https://github.com/trifectatechfoundation/zlib-rs"
74# tag v0.6.8. El commit es el identificador inmutable (ADR 0006), no el tag.
75commit = "0254e07204884bfc902d6ebb68c8b703d0ea49cf"
76patches = ["zlib-rs-workspace.patch"]
77
78[build]
79compiler = "zig-cc"
80target   = "x86_64-linux-musl"
81link     = "static"
82flags    = []
83
84[build.phases]
85configure = "true"
86# `RUSTFLAGS` con el soname: acá es seguro porque este árbol NO tiene proc-macros (el problema
87# documentado de `+crt-static` en RUSTFLAGS) y el único artefacto enlazado es la propia cdylib.
88compile   = '''
89RUSTFLAGS="-C link-arg=-Wl,-soname,libz.so.1" \
90  cargo build --release --locked --offline -p libz-rs-sys-cdylib --features libz-rs-sys-cdylib/gz
91'''
92# Se instala con los NOMBRES de zlib (`libz.a`, `libz.so.1`) y con los headers de zlib-rs, para que un
93# consumidor que hoy pide `-lz` y `#include <zlib.h>` no tenga que cambiar una línea. El `.so.1` lleva
94# el enlace `libz.so` porque es el que busca el enlazador al ver `-lz`.
95install = '''
96mkdir -p /out/usr/lib /out/usr/include /out/usr/lib/pkgconfig
97cp target/release/libz_rs.a  /out/usr/lib/libz.a
98cp target/release/libz_rs.so /out/usr/lib/libz.so.1
99ln -sf libz.so.1 /out/usr/lib/libz.so
100cp libz-rs-sys/include/zlib.h  /out/usr/include/zlib.h
101cp libz-rs-sys/include/zconf.h /out/usr/include/zconf.h
102# El .pc con el nombre de zlib: los `configure` del corpus preguntan por `zlib`, no por `zlib-rs`.
103# `Libs.private: -lunwind` es la parte que NO es cosmética: sin el unwinder el enlace estático muere.
104cat > /out/usr/lib/pkgconfig/zlib.pc <<'PC'
105prefix=/usr
106libdir=${prefix}/lib
107includedir=${prefix}/include
108Name: zlib
109Description: zlib compression library (zlib-rs, Rust)
110Version: 1.3.0
111Libs: -L${libdir} -lz
112Libs.private: -lunwind
113Cflags: -I${includedir}
114PC
115'''
116
117[deps]
118build = ["binutils"]