> 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…
gnome: los 7 typelibs CERRADOS — librsvg y ibus, y dos deudas viejas pagadas de paso
Rsvg-2.0 librsvg b3:351f4658
IBus-1.0 ibus b3:d4d3750a
**1. PERILLA NUEVA DEL LAB: `[source] cargo_vendor`.** `detect_build_system` asume UN
sistema de build por árbol, y librsvg genuinamente tiene dos: `configure.ac` gana por
precedencia pero su Makefile llama a `cargo build`, que dentro del sandbox hermético
no tiene red. La perilla fuerza el vendoreo (que ocurre en el fetch, donde sí hay red)
sin tocar la detección. **No entra al ArtifactHash** —decide de dónde salen las deps,
no cuáles: eso lo fija el Cargo.lock, ya bajo el sha256 de la fuente— así que se puede
prender en una receta ya sellada sin re-hashear nada. Con test, y verificado en vivo:
libelogind no movió su hash tras el cambio.
**2. LA DEUDA DEL UNWINDER, PAGADA.** librsvg moría en
`undefined reference: _Unwind_DeleteException`. No es de librsvg: es de la `std` de
rustc, que trae landing pads y espera el runtime que en glibc vive en libgcc_s. Es la
deuda que el corpus arrastra desde matar-gcc —«las 12 recetas Rust son UN problema, no
12»— y el remedio estaba a mano: **zig empaqueta la libunwind de LLVM** y exporta los
`_Unwind_*` (verificado con nm). Alcanza `LIBS=-lunwind`. Va por LIBS y no por LDFLAGS
porque autotools pone LIBS al FINAL de la línea de enlace, que es donde tiene que ir
una librería que resuelve símbolos indefinidos.
librsvg va en 2.58.5 y no 2.59+: en 2.59 cambió a meson + cargo-c, y cargo-cbuild
enlaza el crate `cargo` entero (libgit2, libssh2, libcurl, openssl) — una campaña
propia por un binario que sólo corre en el constructor. 2.58.5 produce el mismo
Rsvg-2.0. Mismo criterio que gnome-desktop 44.5.
**3. `x11-compose-data`, y es una tensión que vale la pena tener escrita.** ibus COMPILA
la tabla de teclas muertas de X11 dentro de libibus (Makefile.am:310, incondicional, sin
`--disable-`). Sin datos no construye. Los datos viven en el tarball de libX11 por
historia, no por necesidad técnica: son 5192 líneas de reglas. La receta extrae SÓLO los
ficheros de locale reproduciendo la regla de upstream (`cpprules.in`: cpp crudo +
CPP_SED_MAGIC literal) y no compila una línea de X11. Un escritorio Wayland-only sigue
necesitando la tabla de composición del mundo Unix.
Gotchas de ibus, los tres medidos: su ayuda MIENTE (`--enable-gtk4`/`--enable-wayland`
sugieren default apagado; el default es `yes`); `--disable-emoji-dict` NO apaga
`--disable-unicode-dict`; y con `--disable-wayland` el build muere igual porque
`tools/main.c` llama wl_display_* sin `#ifdef` mientras WAYLAND_LIBS sólo se agrega si
la opción está prendida — bug de upstream en su propia configuración sin Wayland.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
diff --git a/crates/hammer-build/src/lib.rs b/crates/hammer-build/src/lib.rs
index 6e615ca8..995ef5ea 100644
--- a/crates/hammer-build/src/lib.rs
+++ b/crates/hammer-build/src/lib.rs
@@ -220,7 +220,13 @@ pub fn build(
// Recetas Cargo: vendoreamos las deps (red permitida en el fetch) para que el build sea
// hermético `--offline` dentro del sandbox. Tras los patches, por si tocaron Cargo.toml/lock.
- if matches!(detect_build_system(&src_tree), BuildSys::Cargo) {
+ // `cargo_vendor = true` fuerza esta rama en árboles POLÍGLOTAS, donde `detect_build_system`
+ // —que asume un sistema por árbol— eligió el de C. librsvg es el arquetipo: `configure.ac` gana
+ // por precedencia, pero su Makefile llama a `cargo build`, y sin vendoreo ese cargo sale a la
+ // red dentro del sandbox hermético. Ver `Source::cargo_vendor`.
+ if matches!(detect_build_system(&src_tree), BuildSys::Cargo)
+ || eff_recipe.source.cargo_vendor == Some(true)
+ {
// El source se copia DENTRO del repo hammer (que es un workspace Cargo) ⇒ si el Cargo.toml
// de la fuente NO declara su propio `[workspace]`, cargo cree que pertenece al workspace de
// hammer y `cargo vendor` aborta. Inyectamos un `[workspace]` vacío para aislarla. SÓLO si
@@ -229,7 +235,13 @@ pub fn build(
// por receta. Ver memoria 'cargo-recipe crate suelto'.
ensure_cargo_workspace_isolation(&src_tree)?;
// Extractor de workspaces (Etapa G): si el root es virtual, resuelve `-p <pkg>` del bin.
- inject_cargo_package_selector(&mut eff_recipe, &src_tree);
+ // SÓLO cuando el árbol ES una receta Cargo. En un políglota (`cargo_vendor = true`) el
+ // producto lo arma el sistema de build de C —que llama a cargo con sus propios argumentos—
+ // y meterle un `-p` a los flags de la receta sería inyectar una opción de Cargo en un
+ // `./configure`. Acá el forzado pide vendoreo, nada más.
+ if matches!(detect_build_system(&src_tree), BuildSys::Cargo) {
+ inject_cargo_package_selector(&mut eff_recipe, &src_tree);
+ }
tracing::info!("cargo: vendoreando deps para build offline");
fetch::vendor_cargo_deps(
&src_tree,
diff --git a/crates/hammer-core/src/recipe.rs b/crates/hammer-core/src/recipe.rs
index d565e54f..b21c8957 100644
--- a/crates/hammer-core/src/recipe.rs
+++ b/crates/hammer-core/src/recipe.rs
@@ -66,6 +66,19 @@ pub struct Source {
/// `.cargo/config.toml` que emite `cargo vendor` ya apunta al dir que se le pasa.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cargo_vendor_dir: Option<String>,
+ /// Fuerza el vendoreo cargo aunque el árbol NO se detecte como `BuildSys::Cargo`.
+ ///
+ /// Existe porque `detect_build_system` asume **un** sistema de build por árbol, y hay proyectos
+ /// que genuinamente tienen dos: C por fuera, Rust por dentro. El arquetipo es librsvg —trae
+ /// `configure.ac` Y `Cargo.toml`, y autotools gana por precedencia—, pero su `Makefile` invoca
+ /// `cargo build`. Sin vendoreo ese cargo intenta salir a la red DENTRO del sandbox hermético y
+ /// el build muere. Es un patrón, no una excepción: cada vez hay más GNOME así.
+ ///
+ /// No cambia qué se construye, sólo de dónde salen las deps —el `Cargo.lock` del source es el
+ /// que fija las versiones— así que **no entra al ArtifactHash**, igual que `cargo_vendor_dir`.
+ /// Es lo que permite prenderla en una receta ya sellada sin re-hashearla.
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub cargo_vendor: Option<bool>,
#[serde(default)]
pub patches: Vec<String>,
}
@@ -732,6 +745,34 @@ expected_output = "b3:deadbeef"
);
}
+ #[test]
+ fn cargo_vendor_roundtripea_y_no_afecta_el_hash() {
+ // `cargo_vendor` decide DE DÓNDE salen las deps de Cargo (registro vs. `vendor/` local), no
+ // CUÁLES: eso lo fija el `Cargo.lock` del source, que ya está bajo el hash de la fuente. Por
+ // eso no entra a `hash_inputs`, igual que `cargo_vendor_dir` — y por eso se puede prender en
+ // una receta YA SELLADA para destrabar su build offline sin re-hashear nada aguas abajo.
+ let sin = Recipe::from_toml(SAMPLE).unwrap();
+ let mut con = sin.clone();
+ con.source.cargo_vendor = Some(true);
+ assert_eq!(
+ sin.hash_inputs(&[]).unwrap(),
+ con.hash_inputs(&[]).unwrap(),
+ "cargo_vendor NO debe entrar en hash_inputs"
+ );
+ // Y roundtripea por TOML: si se perdiera al serializar, el forzado se apagaría solo en
+ // cualquier flujo que reescriba la receta.
+ let r = Recipe::from_toml(&con.to_toml().unwrap()).unwrap();
+ assert_eq!(r.source.cargo_vendor, Some(true));
+ // Ausente sigue siendo ausente (no `Some(false)`), que es lo que mantiene el TOML limpio.
+ assert_eq!(
+ Recipe::from_toml(&sin.to_toml().unwrap())
+ .unwrap()
+ .source
+ .cargo_vendor,
+ None
+ );
+ }
+
#[test]
fn slots_round_trip_y_no_afectan_el_hash() {
// H4b: el bloque `slots` declara TOPOLOGÍA (qué toca / de qué depende), no identidad ⇒
diff --git a/recipes/incoming-gnome/ibus.toml b/recipes/incoming-gnome/ibus.toml
new file mode 100644
index 00000000..6b26ef4b
--- /dev/null
+++ b/recipes/incoming-gnome/ibus.toml
@@ -0,0 +1,107 @@
+# ibus 1.5.34 — el bus de métodos de entrada (teclados de chino/japonés/coreano, emoji, Unicode).
+# De acá sale el typelib `IBus-1.0`.
+#
+# POR QUÉ ENTRA, y es el caso más claro de la tanda: gnome-shell lo importa desde JavaScript **sin
+# condicional** (`js/misc/dependencies.js`: `import 'gi://IBus?version=1.0'`), aunque el usuario no
+# use ningún método de entrada. El panel de teclado del shell habla con ibus siempre. Dep de RUNTIME
+# invisible al grafo de build — el mismo patrón que AccountsService, Gdm, Geoclue y GWeather.
+#
+# LO QUE SE CONSTRUYE ES LA LIBRERÍA Y EL BUS, NO EL ESCRITORIO DE ibus. Casi todos los
+# `--disable-*` de abajo apagan piezas que dependen de cosas que esta distro no tiene y que el shell
+# no consulta:
+# --disable-gtk2/3/4 los módulos de método de entrada para GTK. GTK3 no está en el
+# corpus (decisión de X11-al-tacho) y el de GTK4 no hace falta:
+# GTK4 habla `text-input` de Wayland directo con el compositor.
+# ⚠ OJO CON EL TEXTO DE AYUDA DE UPSTREAM: dice `--enable-gtk4`
+# y `--enable-wayland`, lo que sugiere que están APAGADAS por
+# defecto. No lo están: `enable_gtk4=yes` y `enable_wayland=yes`
+# son los defaults reales (configure.ac:277 y :299). Sin
+# `--disable-gtk4` explícito el configure corta pidiendo gtk4.
+# La ayuda miente; el default manda.
+#
+# ⚠ `--enable-wayland` VA PRENDIDA, y no por gusto. Empezó apagada —el módulo Wayland es para que el
+# demonio de ibus hable con un compositor, y el shell implementa `text-input` por su cuenta— pero con
+# `--disable-wayland` el build MUERE igual: `tools/main.c` incluye `<wayland-client.h>` y llama
+# `wl_display_connect`/`wl_display_disconnect` **sin ningún `#ifdef`** (:1260), mientras que el
+# `WAYLAND_LIBS` que resolvería esos símbolos sólo se agrega si la opción está encendida. Es un bug
+# de upstream en su propia configuración «sin Wayland». Prenderla es más honesto que parchear el
+# `tools/`: esta distro ES Wayland. Arrastra `libxkbcommon`, que su PKG_CHECK pide junto a
+# `wayland-client`.
+# --disable-xim el servidor XIM. Es X11 puro.
+# --disable-setup `ibus-setup`, una GUI en Python+GTK3.
+# --disable-python2 / --disable-python-library / --disable-dbus-python-check
+# bindings de Python 2. No hay Python 2 y no va a haber.
+# --disable-dconf --enable-memconf ibus guarda su configuración en dconf por defecto; dconf no
+# está en el corpus (ya lo esquivó gdm por la misma razón).
+# `memconf` es el backend EN MEMORIA que trae upstream para
+# exactamente este caso: la config no persiste entre sesiones,
+# que para un escritorio sin métodos de entrada configurados es
+# la diferencia entre nada y nada.
+# --disable-systemd-services acá PID1 es arje-zero.
+# --disable-appindicator pide dbusmenu-glib/dbusmenu-gtk3 (bandeja legacy de Ubuntu).
+# --disable-libnotify sirve para que el DEMONIO avise «se cambió el método de
+# entrada» por notificación de escritorio. libnotify no está en
+# el corpus, y las notificaciones del escritorio las sirve el
+# propio gnome-shell.
+# --disable-engine el motor `simple` (mapas de teclado de teclas muertas). Va
+# junto con emoji-dict: son datos y lógica de método de entrada,
+# no la librería.
+# --disable-emoji-dict el diccionario de emoji se compila desde los XML de CLDR y
+# `/usr/share/unicode/emoji`, datos que no están en el corpus.
+# Se pierde el selector de emoji de ibus, no el typelib.
+# --disable-unicode-dict su hermano: el índice de nombres de caracteres se compila desde
+# los ficheros UCD (`NamesList.txt`, `Blocks.txt` de
+# `/usr/share/unicode/ucd`). Apagar emoji-dict NO lo apaga —son
+# dos opciones distintas, y el configure aborta por la segunda
+# después de pasar la primera.
+# --disable-tests --disable-gtk-doc --disable-vala
+#
+# Las comprobaciones de X11/XFixes y de libevdev/libudev son BLANDAS en el configure.ac
+# (:778 y :785 no abortan, sólo definen un condicional), así que no hace falta apagarlas ni
+# satisfacerlas: se resuelven en "no" solas.
+#
+# ══ LA DEP QUE NO SE VE VENIR: `x11-compose-data` ══════════════════════════════════════════════
+# ibus COMPILA la tabla de teclas muertas de X11 dentro de libibus. `gen-internal-compose-table`
+# (src/gencomposetable.c:70) lee un fichero `Compose` en tiempo de build y lo convierte en un
+# GResource que entra en `ibusresources.c`; ese paso es INCONDICIONAL en su Makefile.am:310 y no hay
+# `--disable-` que lo evite. Sin datos el build muere con
+# `Failed to open file "/X11/locale/en/Compose"`.
+#
+# Los datos viven en el tarball de libX11 —por historia, no por necesidad técnica— así que hay una
+# receta aparte, `x11-compose-data`, que extrae SÓLO los ficheros de locale y no compila una línea
+# de X11. Es la tensión de un escritorio Wayland-only con un mundo Unix cuya tabla de composición
+# sigue publicándose dentro de X11, y queda escrita ahí.
+#
+# También pide `iso-codes` (nombres de idioma para el selector de método de entrada), que ya estaba.
+#
+# ISLA DINÁMICA: el typelib nombra `libibus-1.0.so.5` y gjs la dlopea.
+name = "ibus"
+version = "1.5.34"
+
+[source]
+tarball = "https://github.com/ibus/ibus/releases/download/1.5.34/ibus-1.5.34.tar.gz"
+sha256 = "12a72210ce5250f8a66df562a75e034e67b2bce74c4df2983dc86d1154894943"
+
+[build]
+compiler = "zig-cc"
+target = "x86_64-linux-musl"
+link = "dynamic"
+
+[build.phases]
+configure = '''
+set -e
+PKG_CONFIG_PATH=/usr/lib/pkgconfig ./configure --prefix=/usr \
+ --enable-introspection --enable-memconf \
+ --disable-gtk2 --disable-gtk3 --disable-gtk4 --enable-wayland \
+ --disable-xim --disable-dconf \
+ --disable-setup --disable-python2 --disable-python-library \
+ --disable-dbus-python-check --disable-systemd-services \
+ --disable-appindicator --disable-libnotify --disable-engine \
+ --disable-emoji-dict --disable-unicode-dict --disable-tests \
+ --disable-gtk-doc --disable-vala --disable-nls
+'''
+compile = "make"
+install = "DESTDIR=/out make install"
+
+[deps]
+build = ["make", "pkgconf", "python3", "py3-setuptools", "gobject-introspection", "gi-foreign-girs", "glib", "glib-introspected", "iso-codes", "x11-compose-data", "wayland", "wayland-protocols", "libxkbcommon", "dbus-shared", "libffi", "pcre2", "zlib-shared", "expat"]
diff --git a/recipes/incoming-gnome/librsvg.toml b/recipes/incoming-gnome/librsvg.toml
new file mode 100644
index 00000000..ca75168f
--- /dev/null
+++ b/recipes/incoming-gnome/librsvg.toml
@@ -0,0 +1,78 @@
+# librsvg 2.58.5 — el renderizador de SVG de GNOME, escrito en Rust. De acá sale el typelib `Rsvg-2.0`.
+#
+# POR QUÉ ENTRA: gnome-shell lo importa desde JavaScript sin condicional
+# (`js/misc/dependencies.js`: `import 'gi://Rsvg?version=2.0'`). Es lo que dibuja los iconos
+# simbólicos del panel y los fondos vectoriales. Dep de RUNTIME del shell, invisible a su grafo de
+# build.
+#
+# ══ POR QUÉ 2.58 Y NO 2.59+, que es lo que shippea GNOME 48 ═════════════════════════════════════
+# En 2.59 librsvg cambió a **meson + cargo-c**, y `cargo-cbuild` (el binario de lu-zero/cargo-c) no
+# está en el corpus. Empaquetarlo no es una receta más: cargo-c ENLAZA el crate `cargo`, que arrastra
+# libgit2, libssh2, libcurl y openssl como `*-sys`. Es una campaña propia, y por un binario que sólo
+# corre en el constructor.
+#
+# 2.58.5 es la última de la serie autotools + `cargo build` plano, y produce **el mismo `Rsvg-2.0`**
+# —la ABI del typelib no se movió—, así que el shell no nota la diferencia. Es el mismo criterio que
+# ya se aplicó a gnome-desktop 44.5 en esta cola: la versión correcta es la que cierra la cadena, no
+# la más nueva. Cuando cargo-c exista, subir es cambiar dos líneas.
+#
+# ══ LA PERILLA NUEVA DEL LAB: `cargo_vendor = true` ═════════════════════════════════════════════
+# librsvg es POLÍGLOTA — `configure.ac` Y `Cargo.toml` en la misma raíz— y `detect_build_system`
+# asume un sistema por árbol: autotools gana por precedencia. Pero el `Makefile` invoca
+# `cargo build`, y dentro del sandbox hermético ese cargo no tiene red ⇒ el build moría intentando
+# bajar el registro. La perilla fuerza el vendoreo (que ocurre en el fetch, donde SÍ hay red) sin
+# tocar la detección. No entra al ArtifactHash: decide de dónde salen las deps, no cuáles —eso lo
+# fija el `Cargo.lock` del tarball, que ya está bajo el sha256 de la fuente.
+#
+# `CARGO_NET_OFFLINE=true` cierra el círculo: sin él cargo intentaría igual la red antes de mirar el
+# `.cargo/config.toml` vendoreado en algunos caminos.
+#
+# ══ `LIBS=-lunwind`: LA DEUDA DEL UNWINDER, PAGADA ═════════════════════════════════════════════
+# Sin esto el build muere al linkear el binario temporal de la introspección:
+#
+# ld.lld: error: undefined reference: _Unwind_DeleteException
+# >>> referenced by ./.libs/librsvg-2.so (disallowed by --no-allow-shlib-undefined)
+#
+# **No es de librsvg: es de la `std` de rustc**, que trae landing pads de unwinding y espera el
+# runtime que en glibc vive en `libgcc_s`. Es exactamente la deuda que el corpus arrastra desde hace
+# tiempo —«las 12 recetas Rust de la lista de matar-gcc son UN problema, no 12: falta un runtime de
+# unwinding»— y el remedio resulta estar a mano: **zig empaqueta la libunwind de LLVM**, y exporta
+# los `_Unwind_*` que hacen falta (verificado con `nm` sobre el `libunwind.a` de su caché). O sea que
+# no hay que traer libgcc: alcanza con pedirle a zig el suyo.
+#
+# Va por `LIBS` y no por `LDFLAGS` a propósito: autotools pone `LIBS` al FINAL de la línea de enlace,
+# que es donde tiene que ir una librería que resuelve símbolos indefinidos. En `LDFLAGS` iría antes
+# de los objetos y el linker la descartaría por no tener nada pendiente todavía.
+#
+# ══ ALCANCE ════════════════════════════════════════════════════════════════════════════════════
+# `--disable-pixbuf-loader`: el loader SVG de gdk-pixbuf necesita correr
+# `gdk-pixbuf-query-loaders` y reescribir el `loaders.cache` del sistema — un efecto sobre OTRO
+# paquete, que en un store direccionable por contenido no se puede hacer desde acá. El shell no lo
+# usa: carga SVG por `imports.gi.Rsvg`, no por GdkPixbuf.
+# `--disable-gtk-doc`: gtk-doc no está en el corpus.
+#
+# ISLA DINÁMICA: el typelib nombra `librsvg-2.so.2` y gjs la dlopea.
+name = "librsvg"
+version = "2.58.5"
+
+[source]
+tarball = "https://download.gnome.org/sources/librsvg/2.58/librsvg-2.58.5.tar.xz"
+sha256 = "224233a0e347d38c415f15a49f0e0885313e3ecc18f3192055f9304dd2f3a27a"
+cargo_vendor = true
+
+[build]
+compiler = "zig-cc"
+target = "x86_64-linux-musl"
+link = "dynamic"
+
+[build.phases]
+configure = '''
+set -e
+PKG_CONFIG_PATH=/usr/lib/pkgconfig LIBS=-lunwind ./configure --prefix=/usr \
+ --disable-gtk-doc --disable-pixbuf-loader --enable-introspection --disable-vala
+'''
+compile = "CARGO_NET_OFFLINE=true make"
+install = "CARGO_NET_OFFLINE=true DESTDIR=/out make install"
+
+[deps]
+build = ["make", "pkgconf", "python3", "py3-setuptools", "gobject-introspection", "gi-foreign-girs", "glib", "glib-introspected", "gdk-pixbuf", "cairo-shared", "pango", "harfbuzz", "fribidi", "freetype-shared", "fontconfig-shared", "libxml2-shared", "libpng-shared", "pixman", "expat", "libffi", "pcre2", "zlib-shared"]
diff --git a/recipes/incoming-gnome/x11-compose-data.toml b/recipes/incoming-gnome/x11-compose-data.toml
new file mode 100644
index 00000000..a7e92628
--- /dev/null
+++ b/recipes/incoming-gnome/x11-compose-data.toml
@@ -0,0 +1,76 @@
+# x11-compose-data — la base de secuencias Compose de X11, y NADA de X11.
+#
+# QUÉ ES: los ficheros `/usr/share/X11/locale/…` que definen las teclas muertas y las combinaciones
+# de `Multi_key` (`<Compose> a '` → `á`). Son **datos**, no código: 5192 líneas de reglas para
+# `en_US.UTF-8`. Viven en el tarball de libX11 porque históricamente X11 fue quien las mantuvo, no
+# porque necesiten X11 para existir.
+#
+# POR QUÉ LAS NECESITA UNA DISTRO SIN X11, y es una tensión real que conviene dejar escrita: **ibus
+# las compila DENTRO de libibus**. Su `gen-internal-compose-table` (src/gencomposetable.c:70) lee un
+# fichero `Compose` en tiempo de build y lo convierte en `compose/sequences-<endian>-endian`, que
+# entra como GResource en `ibusresources.c`. Ese paso NO es opcional en su Makefile.am:310 — no hay
+# `--disable-` que lo evite. Sin datos, el build de ibus muere con
+# `Failed to open file "/X11/locale/en/Compose"`.
+#
+# O sea: el escritorio es Wayland-only por decisión, pero la tabla de teclas muertas del mundo Unix
+# sigue publicándose dentro de libX11. Empaquetar esos datos NO reintroduce X11: no hay una sola
+# línea de código, ni un `.so`, ni una dependencia. Es el mismo criterio que `iso-codes`.
+#
+# CÓMO SE GENERAN: los ficheros del tarball son `.pre` y hay que pasarlos por el preprocesador de C.
+# La receta REPRODUCE la regla de upstream (`cpprules.in`) tal cual, en vez de improvisar:
+# · `cpp -undef -traditional -DWCHAR32=1` — `RAWCPP $(RAWCPPFLAGS) $(CPP_FILES_FLAGS)`.
+# · el `CPP_SED_MAGIC`, copiado literal. Traduce `XCOMM` a `#` con sed y no con `-DXCOMM=XCOMM`
+# —el comentario de upstream explica que ese truco no funciona en todos los preprocesadores—,
+# borra las marcas de línea de cpp, sustituye `X11_LOCALEDATADIR` por la ruta real y colapsa los
+# `@@` finales en barras invertidas.
+# Verificado: la salida son 5192 líneas con 2337 secuencias de teclas muertas, sintaxis intacta.
+#
+# SÓLO en_US.UTF-8. Es la que ibus busca (`sys_langs[] = { "en_US.UTF-8", "en_US", "en" }`) y la que
+# todas las demás locales incluyen por `include "%L"`. Traer las ~180 locales sería traer el árbol
+# de i18n de X11 entero para que nadie lo lea.
+#
+# `locale.dir` no es decorativo: el configure de ibus decide `X11_LOCALEDATADIR` comprobando que
+# EXISTA (configure.ac:394). Sin él cae a un `$(datadir)` sin expandir y el define queda en
+# `"/X11/locale"`, que es exactamente el error de arriba.
+name = "x11-compose-data"
+version = "1.8.13"
+
+[source]
+tarball = "https://gitlab.freedesktop.org/xorg/lib/libx11/-/archive/libX11-1.8.13/libx11-libX11-1.8.13.tar.gz"
+sha256 = "d210291f5cd974e5029cce4bde0fc1b8bfc0ce88b8530ea6ba4b1fcf141506ab"
+
+[build]
+compiler = "zig-cc"
+target = "x86_64-linux-musl"
+link = "dynamic"
+
+[build.phases]
+configure = "true"
+compile = "true"
+install = '''
+set -e
+LOCALEDIR=/usr/share/X11/locale
+mkdir -p "/out$LOCALEDIR/en_US.UTF-8"
+# La regla de upstream, literal (cpprules.in): cpp crudo + el CPP_SED_MAGIC.
+gen() {
+ cpp -undef -traditional -DWCHAR32=1 < "$1" | LC_CTYPE=C sed \
+ -e '/^\# *[0-9][0-9]* *.*$/d' \
+ -e '/^\#line *[0-9][0-9]* *.*$/d' \
+ -e '/^[ ]*XCOMM$/s/XCOMM/\#/' \
+ -e '/^[ ]*XCOMM[^a-zA-Z0-9_]/s/XCOMM/\#/' \
+ -e '/^[ ]*XHASH/s/XHASH/\#/' \
+ -e "s,X11_LOCALEDATADIR,$LOCALEDIR,g" \
+ -e '/\@\@$/s/\@\@$/\\/' \
+ -e '/^$/d' > "$2"
+}
+gen nls/en_US.UTF-8/Compose.pre "/out$LOCALEDIR/en_US.UTF-8/Compose"
+gen nls/en_US.UTF-8/XLC_LOCALE.pre "/out$LOCALEDIR/en_US.UTF-8/XLC_LOCALE"
+gen nls/locale.dir.pre "/out$LOCALEDIR/locale.dir"
+gen nls/compose.dir.pre "/out$LOCALEDIR/compose.dir"
+gen nls/locale.alias.pre "/out$LOCALEDIR/locale.alias"
+cp nls/en_US.UTF-8/XI18N_OBJS "/out$LOCALEDIR/en_US.UTF-8/XI18N_OBJS"
+wc -l "/out$LOCALEDIR/en_US.UTF-8/Compose"
+'''
+
+[deps]
+build = ["make", "pkgconf"]
diff --git a/scripts/gnome/hydrate-gnome.sh b/scripts/gnome/hydrate-gnome.sh
index f7f821b5..7d2e3dbd 100755
--- a/scripts/gnome/hydrate-gnome.sh
+++ b/scripts/gnome/hydrate-gnome.sh
@@ -32,7 +32,14 @@ else
recipes/incoming-gnome/gnome-shell.toml
recipes/incoming-gnome/accountsservice.toml # imports.gi.AccountsService
recipes/incoming-gnome/gi-foreign-typelibs.toml # DBus-1.0/cairo-1.0, que Gdk y Atspi incluyen
- recipes/incoming-gnome/libgdm.toml # imports.gi.Gdm (js/misc/dependencies.js)
+ # El resto sale de `js/misc/dependencies.js` del shell — la lista donde DECLARA qué typelibs
+ # exige al arrancar. Cerrarlas de una evita el bucle de «arrancar, ver qué falta, rehacer imagen».
+ recipes/incoming-gnome/libgdm.toml # imports.gi.Gdm
+ recipes/incoming-gnome/geoclue.toml # imports.gi.Geoclue
+ recipes/incoming-gnome/libgweather.toml # imports.gi.GWeather
+ recipes/incoming-gnome/upower.toml # imports.gi.UPowerGlib
+ recipes/incoming-gnome/librsvg.toml # imports.gi.Rsvg
+ recipes/incoming-gnome/ibus.toml # imports.gi.IBus
)
fi