# exiv2 0.28.9 — metadatos de imagen (EXIF, IPTC, XMP). Entra por una razón concreta y visible. # # ══ POR QUÉ NO ES UN ADORNO: SIN ESTO LAS FOTOS SALEN GIRADAS ══════════════════════════════════ # Una cámara de móvil casi nunca rota los píxeles: guarda la imagen como la leyó el sensor y anota # la orientación en una etiqueta EXIF. Un visor que no lee EXIF muestra **todas las fotos verticales # tumbadas**, sin ningún error y sin que el usuario pueda hacer nada. Es la clase de fallo que no # aparece en ninguna métrica del repo y que cualquiera nota en el primer segundo. # # ⇒ Entra como dep de `swayimg` (el visor), cuya opción `exif` la exige por pkg-config. # # ══ LO QUE CUESTA: NADA NUEVO ══════════════════════════════════════════════════════════════════ # Medido sobre `cmake/findDependencies.cmake`, no sobre los paquetes de otra distro: # · `ZLIB` y `Brotli` REQUIRED — los dos están en el corpus # · `EXPAT` REQUIRED **sólo si** XMP está encendido; está en el corpus # · `Filesystem` es `std::filesystem` de C++17, que la libc++ de zig provee # · `CURL` sólo con `EXIV2_ENABLE_WEBREADY`, que se apaga # · `Intl` sólo con NLS, que se apaga # · `Iconv` `find_package` sin REQUIRED ⇒ opcional de verdad (musl trae iconv) # · `inih` ⚠ **ME EQUIVOQUÉ ACÁ**: leí `find_package(inih)` sin REQUIRED y lo conté # como opcional. Su `cmake/Findinih.cmake:28` hace # `message(FATAL_ERROR "inih library not found")` **por su cuenta**, así que # corta igual. Leí la LLAMADA y no el MÓDULO — la misma trampa que este repo # documenta para `find_library` sin REQUIRED, donde la cura es leer el # `else()`. Lo que sí hay es un guard una línea arriba # (`if(EXIV2_ENABLE_INIH)`), y por eso se apaga explícitamente. # ⇒ cero recetas nuevas. # # ══ LAS PERILLAS ═══════════════════════════════════════════════════════════════════════════════ # EXIV2_ENABLE_XMP=ON XMP es lo que escriben Lightroom, darktable y el propio Android; sin # esto se leen EXIF e IPTC y se ignora la mitad de los metadatos reales # EXIV2_ENABLE_BROTLI=ON JPEG XL y algunos contenedores comprimen metadatos con brotli # EXIV2_ENABLE_WEBREADY=OFF leer metadatos por HTTP desde la biblioteca. Una biblioteca de imagen # que abre sockets es superficie de ataque que nadie pidió # EXIV2_ENABLE_NLS=OFF evita la cadena de gettext, cuyo `msgfmt` en este lab tiene huecos # conocidos (ver `recipes/brltty.toml`) # EXIV2_BUILD_EXIV2_COMMAND=ON la CLI `exiv2`: es la única forma de comprobar a mano qué # metadatos lee un fichero, sin abrir un visor # EXIV2_ENABLE_INIH=OFF lo que se pierde es LEER UN `exiv2.ini` con nombres de objetivos # fotográficos personalizados — exiv2 sigue reconociendo los objetivos # que trae en sus tablas internas. Es proporcionado frente a meter dos # recetas (`inih` publica una biblioteca C y un `INIReader` de C++, y su # Find exige las dos) # # ══ ⚠⚠ LAS ESTÁTICAS SE PINEAN A MANO, Y ESTO ES EL HALLAZGO IMPORTANTE DE ESTA RECETA ═════════ # La primera versión selló un artefacto que **no habría cargado**, y el motivo no se ve en ningún # log del build: # # readelf -d libexiv2.so ⇒ NEEDED libexpat.so.1 · NEEDED libz.so.1 # ls /usr/lib ⇒ libexpat.a (¡sólo estática!) # ls /usr/lib ⇒ libz.a (¡sólo estática!) # ls .dev-fs/alpine/usr/lib ⇒ libexpat.so.1 · libz.so.1 ← de acá salieron # # O sea que cmake enlazó contra las compartidas **DEL LAB**. El artefacto sella, reproduce, y en una # imagen el cargador no encontraría ni `libexpat.so.1` ni `libz.so.1`, porque el lab no viaja. # Es la familia del «NEEDED colgante»: sella, reproduce y no corre. # # ⇒ Se pinean las `.a` del corpus por ruta explícita. Se comprobó que **son PIC** (217 y 270 # relocaciones GOT/PLT), que es lo que permite que entren en una `.so`. El repo también tiene # `expat-shared` y `zlib-shared` para este caso; se eligió la estática porque deja el artefacto # **autosuficiente y verificable desde dentro del sandbox**: con la compartida el `NEEDED` sería # legítimo, pero el lab está montado igual y desde dentro **no hay forma de distinguir** si la `.so` # vino del corpus o de la imagen. # # ⚠ Y `brotli` no hizo falta pinearlo: no hay `libbrotli*.so` en el lab, así que su `.a` entró sola. # **Eso es exactamente lo que hace al fallo tan difícil de ver** — la misma receta enlaza bien una # dep y mal otra, según lo que el lab tenga encima. # # ⚠ `-Wno-error=date-time`: `src/version.cpp:431-432` usa `__DATE__`/`__TIME__` y el lab compila # con `-Werror=date-time`, su política de reproducibilidad. Desactivarlo es seguro porque el sandbox # exporta `SOURCE_DATE_EPOCH=1` y con eso clang hace que esos macros rindan la fecha fija en vez de # la del reloj, así que el binario sigue determinista. Mismo razonamiento que `fastfetch`, `cairo`, # `appstream` y `brltty` — **cinco recetas con el mismo parche** ya empieza a parecer un caso para # decidir en el lab y no receta por receta, pero eso es su propia unidad de trabajo. # # ⚠ `BUILD_SHARED_LIBS=ON` y es SEGURO acá, a diferencia de `libime`: `swayimg` es un programa en C # que enlaza `libexiv2.so`, así que el runtime de C++ vive ENTERO dentro de la biblioteca y ningún # objeto de C++ cruza el límite de módulo. El problema de las cuatro copias de libc++ aparece cuando # un `std::` creado en un módulo se usa en otro — acá no pasa. name = "exiv2" version = "0.28.9" license = "GPL-2.0-or-later" [source] tarball = "https://github.com/Exiv2/exiv2/archive/refs/tags/v0.28.9/exiv2-0.28.9.tar.gz" sha256 = "700b76b97695b2fab4ef8c79619c68ae57d09e0c130724791cafbd39e0eb4aef" [build] compiler = "zig-cc" target = "x86_64-linux-musl" link = "dynamic" flags = [] [build.phases] configure = ''' ZW="$PWD/.zwrap"; mkdir -p "$ZW" printf '%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" printf '%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" chmod +x "$ZW/cc" "$ZW/cxx" # `rm -rf build`: un build/ fallido sobrevive en el árbol reutilizado y el caché de cmake hace que # el reintento repita el error viejo en dos segundos. rm -rf build cmake -B build -G Ninja -Wno-dev \ -DCMAKE_C_COMPILER="$ZW/cc" -DCMAKE_CXX_COMPILER="$ZW/cxx" \ -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr \ -DCMAKE_INSTALL_LIBDIR=lib \ -DPKG_CONFIG_ARGN=--static \ -DCMAKE_C_FLAGS=-Wno-error=date-time -DCMAKE_CXX_FLAGS=-Wno-error=date-time \ -DBUILD_SHARED_LIBS=ON \ -DEXIV2_ENABLE_XMP=ON -DEXIV2_ENABLE_BROTLI=ON \ -DEXIV2_ENABLE_WEBREADY=OFF -DEXIV2_ENABLE_NLS=OFF \ -DEXIV2_ENABLE_BMFF=ON \ -DEXIV2_ENABLE_INIH=OFF \ -DEXPAT_LIBRARY=/usr/lib/libexpat.a -DEXPAT_INCLUDE_DIR=/usr/include \ -DZLIB_LIBRARY=/usr/lib/libz.a -DZLIB_INCLUDE_DIR=/usr/include \ -DEXIV2_BUILD_EXIV2_COMMAND=ON -DEXIV2_BUILD_SAMPLES=OFF -DEXIV2_BUILD_UNIT_TESTS=OFF ''' compile = "cmake --build build" # El guardián comprueba que las PERILLAS se hayan materializado, no sólo que haya ficheros. Una # exiv2 construida sin XMP o sin brotli se sella igual, exporta la misma API, y devuelve «no hay # metadatos» sobre ficheros que sí los tienen — un fallo que se lee como «la foto no trae EXIF». # La prueba es el NEEDED de la compartida, que es donde se ve si la dependencia entró de verdad. install = ''' set -e DESTDIR=/out cmake --install build so=$(find /out/usr/lib -name 'libexiv2.so*' -type f 2>/dev/null | head -1) [ -n "$so" ] || { echo "exiv2: no quedó libexiv2.so — swayimg la enlaza" >&2; find /out -name 'libexiv2*' >&2; exit 1; } [ -f /out/usr/include/exiv2/exiv2.hpp ] || { echo "exiv2: falta include/exiv2/exiv2.hpp" >&2; exit 1; } pc=$(find /out/usr/lib -name 'exiv2.pc' 2>/dev/null | head -1) [ -n "$pc" ] || { echo "exiv2: falta exiv2.pc — swayimg la busca por pkg-config (dependency('exiv2'))" >&2; exit 1; } [ -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; } # XMP necesita expat y brotli necesita libbrotlidec: si las perillas no se materializaron, no # aparecen en NEEDED. `.a` del corpus no declararían NEEDED, así que se admiten las dos formas: # o el soname en NEEDED, o el símbolo exportado si entró estático. # ⚠ **ESTE CONTROL ESTUVO MAL Y DEJÓ PASAR EL ARTEFACTO ROTO.** Mi versión anterior aceptaba # «`libexpat` aparece en NEEDED **o** el símbolo `XML_Parse` existe en la tabla», y las dos ramas # daban verde sobre el fallo: el NEEDED estaba ahí **y era colgante**, y `readelf -s | grep XML_Parse` # casa igual con una entrada **UND** (sin definir) que con una definida. # # ⇒ Ahora se exige que los símbolos estén **DEFINIDOS** —columna de sección distinta de `UND`— y # que NO quede ningún NEEDED hacia una biblioteca que el corpus sólo publica estática. Un símbolo # presente no es un símbolo resuelto. definido() { readelf -sW "$1" 2>/dev/null | awk -v s="$2" '$8==s && $7!="UND" {found=1} END{exit !found}' } definido "$so" XML_Parse || { 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 readelf -sW "$so" 2>/dev/null | awk '/XML_Parse/{print " "$7, $8}' >&2 exit 1 } definido "$so" BrotliDecoderDecompress || definido "$so" BrotliDecoderCreateInstance || { echo "exiv2: ningún símbolo BrotliDecoder* DEFINIDO — los metadatos comprimidos no se leerían" >&2 exit 1 } # Y el control que faltaba: NINGÚN NEEDED hacia lo que el corpus sólo tiene estático. Si aparece, # salió del LAB y la imagen no lo tendrá. for mal in libexpat libz.so libbrotli; do readelf -d "$so" 2>/dev/null | grep NEEDED | grep -q "$mal" && { 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 readelf -d "$so" 2>/dev/null | grep NEEDED >&2 exit 1 } done # Y que la CLI ARRANQUE: es C++ y una ruta mal resuelta la deja muerta aunque el fichero exista. # Ejecución y truncado SEPARADOS — el estado de una tubería es el del último comando, y eso ya me # selló un fallo hoy (ver `recipes/automake.toml`). sal=$(/out/usr/bin/exiv2 --version 2>&1) || { echo "exiv2: la CLI no arranca" >&2; printf '%s\n' "$sal" >&2; exit 1; } printf '%s\n' "$sal" | head -1 | grep -qE '^exiv2 0\.28\.9' || { echo "exiv2: --version inesperado: $(printf '%s\n' "$sal" | head -1)" >&2; exit 1; } echo "exiv2: $(basename "$so") + CLI + exiv2.pc · xmp y brotli presentes" ''' [deps] build = ["cmake", "samurai", "pkgconf", "zlib", "brotli", "expat", "binutils"]