Files
takana/scripts/gnome/hydrate-gnome.sh
T
sergioandClaude Opus 5 51c6830f58 audio: wireplumber (b3:39a9a06c) + lua (b3:e6c35f98) — y el Devices vacío es del KERNEL
Cierra el gestor de sesión de PipeWire, que es lo que separa «PipeWire acepta clientes»
de «PipeWire tiene dispositivos». Su política está escrita en Lua, así que arrastró una
receta de Lua que hubo que autorar entera.

LO QUE LA RECETA DE LUA TIENE QUE INVENTAR: el Makefile de upstream sólo produce
liblua.a y los binarios — **no hay regla de .so ni fichero pkg-config**, los agrega cada
distro. Acá la compartida se enlaza a mano desde la .a con --whole-archive (una estática
sólo aporta lo referenciado y hay que llevarse todo) y el .pc se escribe con los TRES
nombres que se usan por ahí, porque wireplumber prueba lua-5.4, lua5.4 y lua54 en orden.
`-fPIC` no es opcional: libwplua es un objeto compartido.

Dos símbolos más en libelogind (tawasuyu 087230054 → re-pineado 749edfe41):
sd_uid_get_seats y sd_uid_get_state, que module-logind.c de wireplumber usa para saber si
el usuario está en un asiento antes de tomar los dispositivos. Van 28 símbolos sd-*.

**EL `Devices:` VACÍO DE wpctl NO ES UN FALLO DE wireplumber: el kernel no tiene ALSA.**
recipes/linux-metal.toml:111 lo apaga explícito (`-d SOUND -d SND`), así que no hay
/dev/snd que enumerar. Y esto NO se dio por supuesto: se agregó una ich9-intel-hda
emulada a QEMU (AUDIO=1, ahora el default de run-qemu-desktop.sh) justo porque un
«Devices: vacío» se lee IGUAL si wireplumber funciona sobre una VM sin hardware que si no
funciona. Con tarjeta y sin ALSA en el kernel, el resultado no cambia — la ambigüedad
queda resuelta. Encender el sonido en la distro es una decisión con costo (re-sellar el
kernel, rehacer la imagen metal) y queda a la vista en vez de escondida en un default.

Bug propio destapado en el camino: el bloque de wireplumber quedó DUPLICADO en
gnome-start y había DOS wireplumber peleándose el grafo. El síntoma no era un error sino
una lista de clientes que se lee normal si no se cuenta: dos «WirePlumber» con pids
distintos en wpctl status.

Y una nota de ADR 0012 que costó un rato: la cascada de libelogind cortó a mitad y dejó
el `output/` de mutter a medio hacer; el reintento moría con «error opening
'...c.o.d': No such file or directory». Un `rm -rf output` en el configure NO alcanzó
—el árbol tenía estado viejo más allá del build dir—; lo que lo arregló fue BORRAR
work/sources/mutter-* para que el fetch lo re-extraiga limpio.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 20:40:12 -04:00

133 lines
6.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# hydrate-gnome.sh — proyecta al FHS el cierre de runtime de una receta GNOME, desde artefactos
# SELLADOS del store, sin rebuild ni reproduce (`hammer hydrate <hash> --into`).
#
# DIFERENCIA CON scripts/kde/hydrate-from-store.sh, y por qué importa: aquél resuelve el cierre desde
# un `index.json` de repo y elige el artefacto por MTIME con un CUTOFF ("el más nuevo anterior a hoy").
# Eso es una heurística: si dos artefactos del mismo paquete conviven en el store, la fecha no dice
# cuál corresponde a la receta VIGENTE. Acá el cierre sale del GRAFO REAL de recetas (`[deps].build`,
# resolución hermano→padre, la misma que usa hammer) y el artefacto se elige por `hammer hash`, que
# calcula el ArtifactHash de la receta de HOY sin construir. O sea: cero ambigüedad, y si falta algo
# el reporte dice exactamente qué receta y con qué hash lo buscaba.
#
# Uso: scripts/gnome/hydrate-gnome.sh [receta.toml ...]
# Env: RFS (def work/gnome-rootfs) destino
# STORE (def ./store)
# LINK (def static) static=hardlink | dynamic=patchelf
# KEEP (def 0) 1 = no borrar el RFS previo
set -uo pipefail
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"; cd "$ROOT"
RFS="${RFS:-work/gnome-rootfs}"; STORE="${STORE:-./store}"; LINK="${LINK:-static}"
HAMMER="${HAMMER:-./target/release/hammer}"; KEEP="${KEEP:-0}"
# Por defecto se hidratan DOS raíces, no una. accountsservice no es dep de build de gnome-shell —no
# aparece en su meson.build ni en su cierre— sino dep de RUNTIME: la capa JS del shell hace
# `imports.gi.AccountsService` y gjs resuelve el typelib al arrancar. Todo lo que el shell carga por
# `imports.gi.*` es invisible al grafo de deps, así que hay que nombrarlo acá o no entra al rootfs.
# El cierre de BUILD no es el cierre de RUNTIME; ésta es la lista de las raíces que faltan.
# (no `${@:-...}`: con dos valores por defecto y comillas, bash los pasaría como UN solo argumento)
if [ $# -gt 0 ]; then
TARGETS=("$@")
else
TARGETS=(
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
# 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
# Y ésta NO sale de la lista del shell: la pide el propio gjs desde su JS embebido
# (`imports.gi.versions.GIRepository = '2.0'`). Dep de runtime del INTÉRPRETE.
recipes/incoming-gnome/gi-girepository-typelib.toml
# Ni typelib ni librería: los GSCHEMAS de gnome-settings-daemon. El demonio está aparcado
# (GTK3/X11) pero el shell lee sus esquemas al construir los quick settings, y si faltan la
# excepción ocurre DENTRO de Main.start() ⇒ el panel no se arma y la pantalla queda negra.
recipes/incoming-gnome/gsd-schemas.toml
# Ni typelib ni esquema: DATOS. `adwaita-icon-theme` trae los iconos y —lo que importa para que
# el escritorio sea usable— los CURSORES. Con el cursor por software (obligatorio en virtio-gpu)
# y sin tema, el puntero se mueve invisible. Arrastra hicolor-icon-theme por `Inherits=hicolor`.
recipes/incoming-gnome/adwaita-icon-theme.toml
# El SERVIDOR de audio (decisión del usuario: PipeWire, no PulseAudio). No es dep de build de
# nadie: el shell habla libpulse y `pipewire-pulse` le contesta por el otro lado.
recipes/incoming-gnome/pipewire.toml
# Y su gestor de sesión, que es lo que hace que PipeWire tenga DISPOSITIVOS y no sólo un socket.
recipes/incoming-gnome/wireplumber.toml
)
fi
# ── cierre transitivo por el grafo de recetas, en orden topológico ──────────────────────────────
PLAN=$(python3 - "${TARGETS[@]}" <<'PY'
import sys, tomllib
from pathlib import Path
def resolve(base_dir: Path, dep: str) -> Path | None:
"""Misma regla que hammer_build::resolve_dep_path: HERMANO primero, luego el catálogo PADRE.
Nunca de reojo a otra cola — por eso icu4c hubo que copiarla a incoming-gnome/."""
sib = base_dir / f"{dep}.toml"
if sib.exists():
return sib
par = base_dir.parent / f"{dep}.toml"
return par if par.exists() else None
seen, order, missing = set(), [], []
def visit(path: Path):
p = path.resolve()
if p in seen:
return
seen.add(p)
try:
r = tomllib.loads(p.read_text())
except Exception as e:
print(f"!PARSE\t{p}\t{e}", file=sys.stderr)
return
for d in r.get("deps", {}).get("build", []):
q = resolve(p.parent, d)
if q is None:
missing.append((p.name, d))
else:
visit(q)
order.append((p, r.get("name", p.stem)))
for t in sys.argv[1:]:
visit(Path(t))
for recipe, dep in missing:
print(f"!NORECIPE\t{recipe}\t{dep}", file=sys.stderr)
for p, name in order:
print(f"{p}\t{name}")
PY
)
[ "$KEEP" = 1 ] || rm -rf "$RFS"
mkdir -p "$RFS"
total=$(printf '%s\n' "$PLAN" | grep -c . || true)
echo "==> cierre de ${TARGETS[*]}: $total recetas → $RFS (link=$LINK)"
ok=0; miss=0; MISSING=""
while IFS=$'\t' read -r path name; do
[ -n "$path" ] || continue
h=$("$HAMMER" --store "$STORE" hash "$path" 2>/dev/null | tail -1)
if [ -z "$h" ]; then
printf " ✗ %-28s (hash falló)\n" "$name"; miss=$((miss+1)); MISSING="$MISSING $name"; continue
fi
hex="${h#b3:}"
if [ ! -d "$STORE/$hex-$name" ]; then
printf " ✗ %-28s %s NO-SELLADO\n" "$name" "${hex:0:12}"; miss=$((miss+1)); MISSING="$MISSING $name"; continue
fi
if "$HAMMER" --store "$STORE" hydrate "$hex" --into "$RFS" --link "$LINK" >/dev/null 2>&1; then
ok=$((ok+1))
else
printf " ✗ %-28s %s (hydrate falló)\n" "$name" "${hex:0:12}"; miss=$((miss+1)); MISSING="$MISSING $name"
fi
done <<< "$PLAN"
echo "==> $ok proyectados · $miss faltantes"
[ -n "$MISSING" ] && echo " faltan:$MISSING"
echo " binarios: $(find "$RFS" -type f -perm -u+x 2>/dev/null | wc -l) · .so: $(find "$RFS" -name '*.so*' 2>/dev/null | wc -l) · typelibs: $(find "$RFS" -name '*.typelib' 2>/dev/null | wc -l)"
[ "$miss" = 0 ]