gioser

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 Sutichaykuna

⎇ 534014c3ec69daea789a1d0523e1d00a69c6ed65 recipes/exiv2.toml TOML · 191 siq'ikuna · 13.1 KB Ñawpa willay ch'uya

visor de imágenes: la distro ya puede abrir un JPEG f675db27d · Sergio ·

1# exiv2 0.28.9 — metadatos de imagen (EXIF, IPTC, XMP). Entra por una razón concreta y visible.
2#
3# ══ POR QUÉ NO ES UN ADORNO: SIN ESTO LAS FOTOS SALEN GIRADAS ══════════════════════════════════
4# Una cámara de móvil casi nunca rota los píxeles: guarda la imagen como la leyó el sensor y anota
5# la orientación en una etiqueta EXIF. Un visor que no lee EXIF muestra **todas las fotos verticales
6# tumbadas**, sin ningún error y sin que el usuario pueda hacer nada. Es la clase de fallo que no
7# aparece en ninguna métrica del repo y que cualquiera nota en el primer segundo.
8#
9# ⇒ Entra como dep de `swayimg` (el visor), cuya opción `exif` la exige por pkg-config.
10#
11# ══ LO QUE CUESTA: NADA NUEVO ══════════════════════════════════════════════════════════════════
12# Medido sobre `cmake/findDependencies.cmake`, no sobre los paquetes de otra distro:
13#   · `ZLIB` y `Brotli`   REQUIRED — los dos están en el corpus
14#   · `EXPAT`             REQUIRED **sólo si** XMP está encendido; está en el corpus
15#   · `Filesystem`        es `std::filesystem` de C++17, que la libc++ de zig provee
16#   · `CURL`              sólo con `EXIV2_ENABLE_WEBREADY`, que se apaga
17#   · `Intl`              sólo con NLS, que se apaga
18#   · `Iconv`             `find_package` sin REQUIRED ⇒ opcional de verdad (musl trae iconv)
19#   · `inih`              ⚠ **ME EQUIVOQUÉ ACÁ**: leí `find_package(inih)` sin REQUIRED y lo conté
20#                         como opcional. Su `cmake/Findinih.cmake:28` hace
21#                         `message(FATAL_ERROR "inih library not found")` **por su cuenta**, así que
22#                         corta igual. Leí la LLAMADA y no el MÓDULO — la misma trampa que este repo
23#                         documenta para `find_library` sin REQUIRED, donde la cura es leer el
24#                         `else()`. Lo que sí hay es un guard una línea arriba
25#                         (`if(EXIV2_ENABLE_INIH)`), y por eso se apaga explícitamente.
26# ⇒ cero recetas nuevas.
27#
28# ══ LAS PERILLAS ═══════════════════════════════════════════════════════════════════════════════
29#   EXIV2_ENABLE_XMP=ON       XMP es lo que escriben Lightroom, darktable y el propio Android; sin
30#                             esto se leen EXIF e IPTC y se ignora la mitad de los metadatos reales
31#   EXIV2_ENABLE_BROTLI=ON    JPEG XL y algunos contenedores comprimen metadatos con brotli
32#   EXIV2_ENABLE_WEBREADY=OFF leer metadatos por HTTP desde la biblioteca. Una biblioteca de imagen
33#                             que abre sockets es superficie de ataque que nadie pidió
34#   EXIV2_ENABLE_NLS=OFF      evita la cadena de gettext, cuyo `msgfmt` en este lab tiene huecos
35#                             conocidos (ver `recipes/brltty.toml`)
36#   EXIV2_BUILD_EXIV2_COMMAND=ON  la CLI `exiv2`: es la única forma de comprobar a mano qué
37#                             metadatos lee un fichero, sin abrir un visor
38#   EXIV2_ENABLE_INIH=OFF     lo que se pierde es LEER UN `exiv2.ini` con nombres de objetivos
39#                             fotográficos personalizados — exiv2 sigue reconociendo los objetivos
40#                             que trae en sus tablas internas. Es proporcionado frente a meter dos
41#                             recetas (`inih` publica una biblioteca C y un `INIReader` de C++, y su
42#                             Find exige las dos)
43#
44# ══ ⚠⚠ LAS ESTÁTICAS SE PINEAN A MANO, Y ESTO ES EL HALLAZGO IMPORTANTE DE ESTA RECETA ═════════
45# La primera versión selló un artefacto que **no habría cargado**, y el motivo no se ve en ningún
46# log del build:
47#
48#     readelf -d libexiv2.so  ⇒  NEEDED libexpat.so.1 · NEEDED libz.so.1
49#     ls <artefacto expat>/usr/lib  ⇒  libexpat.a          (¡sólo estática!)
50#     ls <artefacto zlib>/usr/lib   ⇒  libz.a              (¡sólo estática!)
51#     ls .dev-fs/alpine/usr/lib     ⇒  libexpat.so.1 · libz.so.1      ← de acá salieron
52#
53# O sea que cmake enlazó contra las compartidas **DEL LAB**. El artefacto sella, reproduce, y en una
54# imagen el cargador no encontraría ni `libexpat.so.1` ni `libz.so.1`, porque el lab no viaja.
55# Es la familia del «NEEDED colgante»: sella, reproduce y no corre.
56#
57# ⇒ Se pinean las `.a` del corpus por ruta explícita. Se comprobó que **son PIC** (217 y 270
58# relocaciones GOT/PLT), que es lo que permite que entren en una `.so`. El repo también tiene
59# `expat-shared` y `zlib-shared` para este caso; se eligió la estática porque deja el artefacto
60# **autosuficiente y verificable desde dentro del sandbox**: con la compartida el `NEEDED` sería
61# legítimo, pero el lab está montado igual y desde dentro **no hay forma de distinguir** si la `.so`
62# vino del corpus o de la imagen.
63#
64# ⚠ Y `brotli` no hizo falta pinearlo: no hay `libbrotli*.so` en el lab, así que su `.a` entró sola.
65# **Eso es exactamente lo que hace al fallo tan difícil de ver** — la misma receta enlaza bien una
66# dep y mal otra, según lo que el lab tenga encima.
67#
68# ⚠ `-Wno-error=date-time`: `src/version.cpp:431-432` usa `__DATE__`/`__TIME__` y el lab compila
69# con `-Werror=date-time`, su política de reproducibilidad. Desactivarlo es seguro porque el sandbox
70# exporta `SOURCE_DATE_EPOCH=1` y con eso clang hace que esos macros rindan la fecha fija en vez de
71# la del reloj, así que el binario sigue determinista. Mismo razonamiento que `fastfetch`, `cairo`,
72# `appstream` y `brltty` — **cinco recetas con el mismo parche** ya empieza a parecer un caso para
73# decidir en el lab y no receta por receta, pero eso es su propia unidad de trabajo.
74#
75# ══ ⚠ `BUILD_SHARED_LIBS=OFF`, Y ME CORRIJO: ESCRIBÍ LO CONTRARIO SOBRE UNA PREMISA FALSA ══════
76# Puse `ON` argumentando que era seguro «porque `swayimg` es un programa en C, así que el runtime de
77# C++ vive entero dentro de la biblioteca y ningún objeto de C++ cruza el límite». **swayimg 5.6 es
78# C++**, no C (compila `.cpp`), así que la premisa era falsa. Y el cruce no es teórico — está en
79# `src/imageformat.cpp:124-150`:
80#
81#     Exiv2::Image::UniquePtr exiv2 = Exiv2::ImageFactory::open(...);   // creado en la .so…
82#     }                                                                 // …y DESTRUIDO en swayimg
83#     catch (Exiv2::Error&) {                                           // lanzada en la .so, capturada acá
84#
85# Un `std::unique_ptr` construido en un módulo y destruido en otro libera con el `operator delete`
86# del consumidor memoria que asignó el `new` de la biblioteca, y la excepción cruza dos copias
87# ESTÁTICAS de libc++abi. Es la misma figura que segfaulteó `libime_pinyindict`
88# ([[cuatro-libcxx-en-un-proceso]]), con el agravante de que **swayimg no se puede probar en el
89# build** —necesita un display Wayland—, así que «sella» no diría nada.
90#
91# ⇒ Estática: el código de exiv2 entra en el binario de swayimg y hay **un solo** runtime de C++ en
92# el proceso. `CMAKE_POSITION_INDEPENDENT_CODE=ON` para que la `.a` sirva también si algún día la
93# necesita una compartida.
94name    = "exiv2"
95version = "0.28.9"
96license = "GPL-2.0-or-later"
97
98[source]
99tarball = "https://github.com/Exiv2/exiv2/archive/refs/tags/v0.28.9/exiv2-0.28.9.tar.gz"
100sha256  = "700b76b97695b2fab4ef8c79619c68ae57d09e0c130724791cafbd39e0eb4aef"
101
102[build]
103compiler = "zig-cc"
104target   = "x86_64-linux-musl"
105link     = "dynamic"
106flags    = []
107
108[build.phases]
109configure = '''
110ZW="$PWD/.zwrap"; mkdir -p "$ZW"
111printf '%s\n' '#!/bin/sh' 'for a do' 'case "$a" in --target=*) a=--target=x86_64-linux-musl ;; esac' 'set -- "$@" "$a"' 'shift' 'done' 'exec /opt/zig/zig cc -mcpu=baseline "$@"' > "$ZW/cc"
112printf '%s\n' '#!/bin/sh' 'for a do' 'case "$a" in --target=*) a=--target=x86_64-linux-musl ;; esac' 'set -- "$@" "$a"' 'shift' 'done' 'exec /opt/zig/zig c++ -mcpu=baseline "$@"' > "$ZW/cxx"
113chmod +x "$ZW/cc" "$ZW/cxx"
114# `rm -rf build`: un build/ fallido sobrevive en el árbol reutilizado y el caché de cmake hace que
115# el reintento repita el error viejo en dos segundos.
116rm -rf build
117cmake -B build -G Ninja -Wno-dev \
118  -DCMAKE_C_COMPILER="$ZW/cc" -DCMAKE_CXX_COMPILER="$ZW/cxx" \
119  -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr \
120  -DCMAKE_INSTALL_LIBDIR=lib \
121  -DPKG_CONFIG_ARGN=--static \
122  -DCMAKE_C_FLAGS=-Wno-error=date-time -DCMAKE_CXX_FLAGS=-Wno-error=date-time \
123  -DBUILD_SHARED_LIBS=OFF -DCMAKE_POSITION_INDEPENDENT_CODE=ON \
124  -DEXIV2_ENABLE_XMP=ON -DEXIV2_ENABLE_BROTLI=ON \
125  -DEXIV2_ENABLE_WEBREADY=OFF -DEXIV2_ENABLE_NLS=OFF \
126  -DEXIV2_ENABLE_BMFF=ON \
127  -DEXIV2_ENABLE_INIH=OFF \
128  -DEXPAT_LIBRARY=/usr/lib/libexpat.a -DEXPAT_INCLUDE_DIR=/usr/include \
129  -DZLIB_LIBRARY=/usr/lib/libz.a -DZLIB_INCLUDE_DIR=/usr/include \
130  -DEXIV2_BUILD_EXIV2_COMMAND=ON -DEXIV2_BUILD_SAMPLES=OFF -DEXIV2_BUILD_UNIT_TESTS=OFF
131'''
132compile = "cmake --build build"
133# El guardián comprueba que las PERILLAS se hayan materializado, no sólo que haya ficheros. Una
134# exiv2 construida sin XMP o sin brotli se sella igual, exporta la misma API, y devuelve «no hay
135# metadatos» sobre ficheros que sí los tienen — un fallo que se lee como «la foto no trae EXIF».
136# La prueba es el NEEDED de la compartida, que es donde se ve si la dependencia entró de verdad.
137install = '''
138set -e
139DESTDIR=/out cmake --install build
140so=$(find /out/usr/lib -name 'libexiv2.a' 2>/dev/null | head -1)
141[ -n "$so" ] || { echo "exiv2: no quedó libexiv2.a — NO sello" >&2; find /out -name 'libexiv2*' >&2; exit 1; }
142# PIC, que es lo que permitiría meterla en una compartida el día que haga falta.
143readelf -r "$so" 2>/dev/null | grep -qE 'R_X86_64_(GOTPCREL|REX_GOTP|PLT32)' || { echo "exiv2: libexiv2.a no parece PIC" >&2; exit 1; }
144[ -f /out/usr/include/exiv2/exiv2.hpp ] || { echo "exiv2: falta include/exiv2/exiv2.hpp" >&2; exit 1; }
145pc=$(find /out/usr/lib -name 'exiv2.pc' 2>/dev/null | head -1)
146[ -n "$pc" ] || { echo "exiv2: falta exiv2.pc — swayimg la busca por pkg-config (dependency('exiv2'))" >&2; exit 1; }
147[ -x /out/usr/bin/exiv2 ] || { echo "exiv2: falta la CLI /usr/bin/exiv2 — es la única forma de inspeccionar metadatos a mano" >&2; exit 1; }
148
149# XMP necesita expat y brotli necesita libbrotlidec: si las perillas no se materializaron, no
150# aparecen en NEEDED. `.a` del corpus no declararían NEEDED, así que se admiten las dos formas:
151# o el soname en NEEDED, o el símbolo exportado si entró estático.
152# ⚠⚠ **ESTE CONTROL FALLÓ DOS VECES, DE LAS DOS FORMAS OPUESTAS. Vale anotar las dos.**
153#
154# 1. Con la biblioteca COMPARTIDA aceptaba «`libexpat` en NEEDED **o** el símbolo `XML_Parse`
155#    existe», y las dos ramas daban verde sobre un fallo real: el NEEDED estaba ahí **y era
156#    colgante** (apuntaba a la `libexpat.so` del LAB, que no viaja), y
157#    `readelf -s | grep XML_Parse` casa igual con una entrada **UND** que con una definida.
158#    *Un símbolo presente no es un símbolo resuelto.*
159#
160# 2. Al pasar a ESTÁTICA lo endurecí exigiendo que `XML_Parse` estuviera **definido dentro** de la
161#    biblioteca… y eso rechaza un build correcto: **una `.a` no contiene a sus deps.** Es una
162#    colección de objetos propios; quien enlaza expat es el CONSUMIDOR. El guardián pedía algo que
163#    no debe estar.
164#
165# ⇒ El contrato de una estática no son sus símbolos ajenos: es **lo que su `.pc` le dice al
166# consumidor que tiene que enlazar**. Si `Requires.private` no nombra expat/brotli/zlib, el
167# consumidor no lo sabrá, y el fallo saldrá en ÉL con once `undefined symbol: XML_*` que no mencionan
168# a exiv2 — pasó exactamente así con `libfontconfig.a` y costó dos vueltas de diagnóstico.
169#
170# Y la prueba de que las perillas se materializaron pasa a ser la CLI: se enlaza en este mismo build
171# con expat y brotli de verdad, así que si no entraran, no habría binario que arrancara.
172for req in expat brotli zlib; do
173  grep -E '^Requires\.private' "$pc" | grep -q "$req" || {
174    echo "exiv2: exiv2.pc no declara '$req' en Requires.private — el consumidor no sabría que debe enlazarlo, y su fallo serían símbolos sin resolver que NO mencionan a exiv2" >&2
175    grep -E '^Requires|^Libs' "$pc" >&2
176    exit 1
177  }
178done
179# Y los símbolos PROPIOS de exiv2 sí tienen que estar en la `.a`: es lo único que ella aporta.
180readelf -sW "$so" 2>/dev/null | grep -q 'ImageFactory' || { echo "exiv2: libexiv2.a no trae ningún símbolo de Exiv2::ImageFactory — es la API que usa el consumidor" >&2; exit 1; }
181
182# Y que la CLI ARRANQUE: es C++ y una ruta mal resuelta la deja muerta aunque el fichero exista.
183# Ejecución y truncado SEPARADOS — el estado de una tubería es el del último comando, y eso ya me
184# selló un fallo hoy (ver `recipes/automake.toml`).
185sal=$(/out/usr/bin/exiv2 --version 2>&1) || { echo "exiv2: la CLI no arranca" >&2; printf '%s\n' "$sal" >&2; exit 1; }
186printf '%s\n' "$sal" | head -1 | grep -qE '^exiv2 0\.28\.9' || { echo "exiv2: --version inesperado: $(printf '%s\n' "$sal" | head -1)" >&2; exit 1; }
187echo "exiv2: $(basename "$so") + CLI + exiv2.pc · xmp y brotli presentes"
188'''
189
190[deps]
191build = ["cmake", "samurai", "pkgconf", "zlib", "brotli", "expat", "binutils"]