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…
exiv2 + un falso verde del vigía de sonames
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=ON` y es SEGURO acá, a diferencia de `libime`: `swayimg` es un programa en C
76# que enlaza `libexiv2.so`, así que el runtime de C++ vive ENTERO dentro de la biblioteca y ningún
77# objeto de C++ cruza el límite de módulo. El problema de las cuatro copias de libc++ aparece cuando
78# un `std::` creado en un módulo se usa en otro — acá no pasa.
79name = "exiv2"
80version = "0.28.9"
81license = "GPL-2.0-or-later"
82
83[source]
84tarball = "https://github.com/Exiv2/exiv2/archive/refs/tags/v0.28.9/exiv2-0.28.9.tar.gz"
85sha256 = "700b76b97695b2fab4ef8c79619c68ae57d09e0c130724791cafbd39e0eb4aef"
86
87[build]
88compiler = "zig-cc"
89target = "x86_64-linux-musl"
90link = "dynamic"
91flags = []
92
93[build.phases]
94configure = '''
95ZW="$PWD/.zwrap"; mkdir -p "$ZW"
96printf '%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"
97printf '%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"
98chmod +x "$ZW/cc" "$ZW/cxx"
99# `rm -rf build`: un build/ fallido sobrevive en el árbol reutilizado y el caché de cmake hace que
100# el reintento repita el error viejo en dos segundos.
101rm -rf build
102cmake -B build -G Ninja -Wno-dev \
103 -DCMAKE_C_COMPILER="$ZW/cc" -DCMAKE_CXX_COMPILER="$ZW/cxx" \
104 -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr \
105 -DCMAKE_INSTALL_LIBDIR=lib \
106 -DPKG_CONFIG_ARGN=--static \
107 -DCMAKE_C_FLAGS=-Wno-error=date-time -DCMAKE_CXX_FLAGS=-Wno-error=date-time \
108 -DBUILD_SHARED_LIBS=ON \
109 -DEXIV2_ENABLE_XMP=ON -DEXIV2_ENABLE_BROTLI=ON \
110 -DEXIV2_ENABLE_WEBREADY=OFF -DEXIV2_ENABLE_NLS=OFF \
111 -DEXIV2_ENABLE_BMFF=ON \
112 -DEXIV2_ENABLE_INIH=OFF \
113 -DEXPAT_LIBRARY=/usr/lib/libexpat.a -DEXPAT_INCLUDE_DIR=/usr/include \
114 -DZLIB_LIBRARY=/usr/lib/libz.a -DZLIB_INCLUDE_DIR=/usr/include \
115 -DEXIV2_BUILD_EXIV2_COMMAND=ON -DEXIV2_BUILD_SAMPLES=OFF -DEXIV2_BUILD_UNIT_TESTS=OFF
116'''
117compile = "cmake --build build"
118# El guardián comprueba que las PERILLAS se hayan materializado, no sólo que haya ficheros. Una
119# exiv2 construida sin XMP o sin brotli se sella igual, exporta la misma API, y devuelve «no hay
120# metadatos» sobre ficheros que sí los tienen — un fallo que se lee como «la foto no trae EXIF».
121# La prueba es el NEEDED de la compartida, que es donde se ve si la dependencia entró de verdad.
122install = '''
123set -e
124DESTDIR=/out cmake --install build
125so=$(find /out/usr/lib -name 'libexiv2.so*' -type f 2>/dev/null | head -1)
126[ -n "$so" ] || { echo "exiv2: no quedó libexiv2.so — swayimg la enlaza" >&2; find /out -name 'libexiv2*' >&2; exit 1; }
127[ -f /out/usr/include/exiv2/exiv2.hpp ] || { echo "exiv2: falta include/exiv2/exiv2.hpp" >&2; exit 1; }
128pc=$(find /out/usr/lib -name 'exiv2.pc' 2>/dev/null | head -1)
129[ -n "$pc" ] || { echo "exiv2: falta exiv2.pc — swayimg la busca por pkg-config (dependency('exiv2'))" >&2; exit 1; }
130[ -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; }
131
132# XMP necesita expat y brotli necesita libbrotlidec: si las perillas no se materializaron, no
133# aparecen en NEEDED. `.a` del corpus no declararían NEEDED, así que se admiten las dos formas:
134# o el soname en NEEDED, o el símbolo exportado si entró estático.
135# ⚠ **ESTE CONTROL ESTUVO MAL Y DEJÓ PASAR EL ARTEFACTO ROTO.** Mi versión anterior aceptaba
136# «`libexpat` aparece en NEEDED **o** el símbolo `XML_Parse` existe en la tabla», y las dos ramas
137# daban verde sobre el fallo: el NEEDED estaba ahí **y era colgante**, y `readelf -s | grep XML_Parse`
138# casa igual con una entrada **UND** (sin definir) que con una definida.
139#
140# ⇒ Ahora se exige que los símbolos estén **DEFINIDOS** —columna de sección distinta de `UND`— y
141# que NO quede ningún NEEDED hacia una biblioteca que el corpus sólo publica estática. Un símbolo
142# presente no es un símbolo resuelto.
143definido() {
144 readelf -sW "$1" 2>/dev/null | awk -v s="$2" '$8==s && $7!="UND" {found=1} END{exit !found}'
145}
146definido "$so" XML_Parse || {
147 echo "exiv2: XML_Parse no está DEFINIDO en la compartida — expat no entró (o entró como referencia colgante) y el XMP no funcionaría" >&2
148 readelf -sW "$so" 2>/dev/null | awk '/XML_Parse/{print " "$7, $8}' >&2
149 exit 1
150}
151definido "$so" BrotliDecoderDecompress || definido "$so" BrotliDecoderCreateInstance || {
152 echo "exiv2: ningún símbolo BrotliDecoder* DEFINIDO — los metadatos comprimidos no se leerían" >&2
153 exit 1
154}
155# Y el control que faltaba: NINGÚN NEEDED hacia lo que el corpus sólo tiene estático. Si aparece,
156# salió del LAB y la imagen no lo tendrá.
157for mal in libexpat libz.so libbrotli; do
158 readelf -d "$so" 2>/dev/null | grep NEEDED | grep -q "$mal" && {
159 echo "exiv2: NEEDED hacia $mal — el corpus sólo publica esa dep ESTÁTICA, así que esa .so salió del lab y no viajaría en ninguna imagen" >&2
160 readelf -d "$so" 2>/dev/null | grep NEEDED >&2
161 exit 1
162 }
163done
164# Y que la CLI ARRANQUE: es C++ y una ruta mal resuelta la deja muerta aunque el fichero exista.
165# Ejecución y truncado SEPARADOS — el estado de una tubería es el del último comando, y eso ya me
166# selló un fallo hoy (ver `recipes/automake.toml`).
167sal=$(/out/usr/bin/exiv2 --version 2>&1) || { echo "exiv2: la CLI no arranca" >&2; printf '%s\n' "$sal" >&2; exit 1; }
168printf '%s\n' "$sal" | head -1 | grep -qE '^exiv2 0\.28\.9' || { echo "exiv2: --version inesperado: $(printf '%s\n' "$sal" | head -1)" >&2; exit 1; }
169echo "exiv2: $(basename "$so") + CLI + exiv2.pc · xmp y brotli presentes"
170'''
171
172[deps]
173build = ["cmake", "samurai", "pkgconf", "zlib", "brotli", "expat", "binutils"]