Misma divergencia que se encontró ayer en GNOME, medida igual: `hydrate-cosmic.sh` nombra 25 raíces escritas a mano dentro del script, el perfil `escritorio-cosmic` declara 43, y **17 paquetes declarados no llegaban al rootfs**: adwaita-cursors · xkeyboard-config · bash · atuq (el navegador) · mpv · swayimg zathura + zathura-pdf-poppler · dejavu-fonts y dejavu-fonts-nerd · libnotify desktop-file-utils · hicolor-icon-theme · y las cinco *-shared de runtime ⚠ EL PRIMERO NO ES UNA COINCIDENCIA: `adwaita-cursors` es EXACTAMENTE el ejemplo con el que la cabecera de `scripts/hydrate-profile.py` advirtió de esto el 2026-09-04 — «se añadió a las raíces de cosmic y sway, y los scripts de hidratación, que no la conocen, habrían seguido armando un rootfs sin cursores». No era hipotético: seguía pasando hoy, cinco días después. Y `xkeyboard-config` es la cicatriz de sway repitiéndose: sin él el compositor arranca sin mapa de teclado. Y AL REVÉS NO SE PIERDE NADA, que es lo que hace el cambio barato y lo separa del caso GNOME (donde primero hubo que mover 11 raíces de runtime al perfil): de las 25 del script, la única que el perfil no declara es `zlib-shared`, y la clausura del perfil la alcanza igual. O sea que el motivo que justificaba la lista a mano —los componentes que `cosmic-session` lanza por PATH, que ningún grafo de build ve— YA ESTÁ RESUELTO en el perfil, que los declara todos como raíces. La lista quedó redundante sin que nadie lo notara. VERIFICADO HIDRATANDO, no leyendo: `hydrate-profile.py escritorio-cosmic` proyecta 162/162 nodos (24166 ficheros, 4,8 G) y ahí están los 17. (Ojo con la sonda: `bash` instala en `/bin/bash`, no en `/usr/bin` — mi primer chequeo lo dio por ausente y era el chequeo el que miraba mal.) ⚠ Y UN DATO QUE MATIZA EL DIAGNÓSTICO Y LO EMPEORA. El rootfs que hay en disco (`escritorios/cosmic-rootfs`, 3,9 G) SÍ tiene cursores, xkb y mpv — o sea que no se armó con la lista por defecto, alguien le pasó raíces a mano. Y aun así le falta `atuq`. El problema real no es que la lista esté mal: es que **lo que llega a la imagen depende de lo que alguien se acuerde de teclear**. Derivarlo del perfil quita esa variable. El script se deja porque su cabecera documenta por qué COSMIC necesita raíces de runtime explícitas —sigue siendo cierto sobre la naturaleza de cosmic-session—; lo que ya no es cierto es que su lista sea la verdad. Los dos armadores de imagen (qemu y metal) ahora apuntan a `hydrate-profile.py`. ── Y de paso, una nota vieja de targets.toml corregida ──────────────────────────────────────────── Decía que el perfil de KDE «son 13 raíces de ARRANQUE EN METAL, no la imagen completa», con konsole/kate/dolphin y otras 19 apps selladas y sin declarar. Ya no: hoy son 48 raíces, con esas apps, las fuentes y las *-shared declaradas — el frente KDE lo reordenó y la nota quedó atrás. De su cola quedan 8 recetas fuera del perfil y son las que corresponde: libICE, libSM, libXtst y los cinco eslabones huérfanos de xwayland desde que se retiró su receta. Lo que a KDE le queda es OTRO problema, también medido hoy: su imagen no se hidrata del perfil sino de un rootfs YA ARMADO (8,4 G) que nadie re-deriva. De las 18 hojas del perfil están 15 — faltan `obs-studio` y `atuq`, justo las dos añadidas después de esa hidratación. Re-hidratarlo es decisión del frente KDE. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LCt3ettR4Z7b6wPCBmbvEV
216 lines
12 KiB
Bash
Executable File
216 lines
12 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# hydrate-cosmic.sh — proyecta al FHS el cierre de runtime de COSMIC desde artefactos SELLADOS del
|
|
# store, sin rebuild ni reproduce (`takana hydrate <hash> --into`).
|
|
#
|
|
# Es el hermano de scripts/gnome/hydrate-gnome.sh y comparte con él lo que importa: el cierre sale
|
|
# del GRAFO REAL de recetas (`[deps].build`, resolución hermano→padre, la misma que usa takana) y el
|
|
# artefacto se elige por `takana hash` —el ArtifactHash de la receta de HOY, sin construir—, no por
|
|
# el más reciente del store. Con dos artefactos del mismo paquete conviviendo, la fecha miente.
|
|
#
|
|
# ── POR QUÉ LAS RAÍCES SE NOMBRAN A MANO ────────────────────────────────────────────────────────
|
|
# Porque el cierre de BUILD no es el cierre de RUNTIME, y en COSMIC la brecha es enorme: cosmic-session
|
|
# lanza a sus componentes por PATH, por nombre, con `start_component("cosmic-panel", …)`. Ninguno es
|
|
# dep de build de ningún otro, así que el grafo no los ve. La lista autoritativa es
|
|
# `cosmic-session/src/main.rs`, no una intuición sobre qué hace falta.
|
|
#
|
|
# ⚠ SUPERADO POR `scripts/hydrate-profile.py` (2026-09-09). Usar:
|
|
#
|
|
# scripts/hydrate-profile.py escritorio-cosmic --into <dir>
|
|
#
|
|
# POR QUÉ, y está MEDIDO. La lista de raíces de abajo es una segunda fuente de verdad frente a
|
|
# `docs/state/targets.toml`, y había divergido: el script nombra 25 raíces, el perfil declara 43, y
|
|
# **17 paquetes declarados NO llegaban al rootfs**. Entre ellos `adwaita-cursors` —que es
|
|
# EXACTAMENTE el ejemplo con el que la cabecera de `hydrate-profile.py` advirtió del problema el
|
|
# 2026-09-04: «habrían seguido armando un rootfs sin cursores»—, `xkeyboard-config` (sin él no hay
|
|
# mapa de teclado, la cicatriz de sway), `bash`, el navegador `atuq`, `mpv`, `swayimg`, `zathura` y
|
|
# su plugin, las dos `dejavu-fonts`, `libnotify`, `desktop-file-utils`, `hicolor-icon-theme` y las
|
|
# cinco `*-shared` de runtime.
|
|
#
|
|
# Y al revés no se pierde NADA, que es lo que hace el cambio barato: de las 25 raíces del script, la
|
|
# única que el perfil no declara es `zlib-shared`, y la clausura del perfil la alcanza igual. O sea
|
|
# que el motivo que justificaba la lista a mano —el párrafo de abajo: los componentes que
|
|
# `cosmic-session` lanza por PATH y el grafo no ve— **ya está resuelto en el perfil**, que los
|
|
# declara a todos como raíces. La lista quedó redundante sin que nadie lo notara.
|
|
#
|
|
# El script se deja porque su cabecera documenta ese porqué, que sigue siendo cierto sobre la
|
|
# NATURALEZA de COSMIC; lo que ya no es cierto es que su lista sea la verdad.
|
|
#
|
|
# Uso: scripts/cosmic/hydrate-cosmic.sh [receta.toml ...]
|
|
# Env: RFS (def work/cosmic-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/cosmic-rootfs}"; STORE="${STORE:-./store}"; LINK="${LINK:-static}"
|
|
HAMMER="${TAKANA:-${HAMMER:-./target/release/takana}}"; KEEP="${KEEP:-0}"
|
|
|
|
if [ $# -gt 0 ]; then
|
|
TARGETS=("$@")
|
|
else
|
|
TARGETS=(
|
|
# El compositor: la única raíz que el grafo sí explica sola (smithay enlaza sus backends).
|
|
recipes/incoming-cosmic/cosmic-comp.toml
|
|
# El gestor de sesión y su `start-cosmic`. Lanza al resto POR NOMBRE ⇒ nada de lo de abajo es
|
|
# alcanzable desde el grafo de deps.
|
|
recipes/incoming-cosmic/cosmic-session.toml
|
|
# Componentes que `cosmic-session/src/main.rs` levanta, en el orden en que los levanta.
|
|
recipes/incoming-cosmic/cosmic-bg.toml
|
|
recipes/incoming-cosmic/cosmic-idle.toml
|
|
recipes/incoming-cosmic/cosmic-panel.toml
|
|
recipes/incoming-cosmic/cosmic-osd.toml
|
|
# Los dos que —con el panel— hacen que `cosmic-session` no panickee. Son TRES los obligatorios;
|
|
# ver la tabla de `.expect` del runbook.
|
|
recipes/incoming-cosmic/cosmic-settings-daemon.toml
|
|
recipes/incoming-cosmic/cosmic-notifications.toml
|
|
# Los que el PANEL spawnea (no cosmic-session): un binario multiplexado en veinte symlinks, más
|
|
# los tres botones. Sin ellos el panel arranca, dice «Done spawning applets» y NO SE DIBUJA —
|
|
# nada que medir, nada que pintar. Invisibles para el grafo por la misma razón que los de arriba:
|
|
# se invocan por AppID.
|
|
recipes/incoming-cosmic/cosmic-applets.toml
|
|
# Los tres que esos botones abren al hacer clic. No matan la sesión si faltan (el botón queda
|
|
# muerto), pero un escritorio sin lanzador ni biblioteca de apps no se puede usar.
|
|
recipes/incoming-cosmic/cosmic-launcher.toml
|
|
# …y su MOTOR, que es otro binario y de otro repo: cosmic-launcher sólo dibuja, `pop-launcher`
|
|
# busca. Lo lanza como proceso hijo por PATH, así que —otra vez— ningún grafo de deps lo ve.
|
|
recipes/incoming-cosmic/pop-launcher.toml
|
|
# …y `qalc`, que es a lo que el plugin `calc` del lanzador le pasa la expresión. Tercer nivel de
|
|
# hijo invisible para el grafo: sesión → lanzador → pop-launcher → qalc, todos por PATH.
|
|
recipes/incoming-cosmic/libqalculate.toml
|
|
# La PRIMERA aplicación con ventana del catálogo. A diferencia de todo lo de arriba, ésta sí es
|
|
# alcanzable «desde el escritorio»: su `.desktop` no es `NoDisplay`, así que aparece en la
|
|
# biblioteca. Sigue siendo raíz a mano porque nadie la declara como dep de nada.
|
|
recipes/incoming-cosmic/cosmic-term.toml
|
|
# …y el gestor de ficheros, la segunda. Su `.desktop` tampoco es `NoDisplay`.
|
|
recipes/incoming-cosmic/cosmic-files.toml
|
|
# …y el panel de control, la tercera. Instala 32 `.desktop`: uno por página (Pantalla, Sonido,
|
|
# Red…), así que la biblioteca lista cada sección como entrada propia.
|
|
recipes/incoming-cosmic/cosmic-settings.toml
|
|
# …y el editor de texto, la cuarta. Su `.desktop` declara `MimeType=text/plain`, así que además
|
|
# de aparecer en la biblioteca es a quien el gestor de ficheros abre un `.txt`.
|
|
recipes/incoming-cosmic/cosmic-edit.toml
|
|
# …y la tienda, la quinta. Con el backend `pkgar` NAVEGA pero no INSTALA: lee el AppStream del
|
|
# sistema, o sea los `.metainfo.xml` que instalan las recetas de acá arriba.
|
|
recipes/incoming-cosmic/cosmic-store.toml
|
|
# …y la captura, la sexta. Se incluye SABIENDO que hoy falla: pide la captura al portal XDG, que
|
|
# la imagen no tiene. Está acá para que el hueco sea visible y medible, no para que funcione.
|
|
recipes/incoming-cosmic/cosmic-screenshot.toml
|
|
# ── LA CADENA DEL PORTAL, y son DOS raíces porque son DOS procesos ───────────────────────────
|
|
# El frontend registra `org.freedesktop.portal.Desktop` (lo que los clientes buscan) y enruta al
|
|
# backend por `org.freedesktop.impl.portal.*`. Ninguno es dep de build del otro: se encuentran
|
|
# por D-Bus en runtime, así que el grafo no los ve — igual que los componentes de la sesión.
|
|
recipes/incoming-cosmic/xdg-desktop-portal.toml
|
|
recipes/incoming-cosmic/xdg-desktop-portal-cosmic.toml
|
|
# …y la SONDA con la que se mide esa cadena. No es parte del escritorio: es el instrumento.
|
|
# Entra a la imagen a propósito —un handshake de portal sólo se puede ejercer DESDE la sesión,
|
|
# con el bus y el compositor vivos— y sale estática, sin un NEEDED, justamente para que nunca
|
|
# falle por algo del sistema que está midiendo.
|
|
recipes/incoming-cosmic/portal-probe.toml
|
|
# El GESTOR DE SESIÓN de pipewire. Ningún `[deps]` lo alcanza —es un cliente del demonio, no una
|
|
# librería de nadie— así que es raíz a mano como todo lo que se lanza por PATH. Trae consigo la
|
|
# glib COMPARTIDA (`glib-shared`), que no es la estática que usa el resto de la cola: wireplumber
|
|
# produce `.so` y la estática del corpus no es PIC.
|
|
recipes/incoming-cosmic/wireplumber.toml
|
|
recipes/incoming-cosmic/cosmic-app-library.toml
|
|
recipes/incoming-cosmic/cosmic-workspaces-epoch.toml
|
|
# Ni binario ni librería: DATOS. El tema de iconos de COSMIC — sin él el escritorio no se ve
|
|
# «sin iconos», se ve roto, y con el cursor por software ni siquiera hay puntero.
|
|
recipes/incoming-cosmic/cosmic-icons.toml
|
|
# …y el tema del que HEREDA (`Inherits=hicolor` en su index.theme). Que un tema herede de otro es
|
|
# una relación declarada en un fichero de datos, invisible para cualquier grafo de deps.
|
|
recipes/incoming-cosmic/hicolor-icon-theme.toml
|
|
|
|
# ── LO QUE PIDE `start-cosmic`, Y NO LO PIDE NINGÚN `[deps]` ────────────────────────────────
|
|
# bash de verdad: el script usa `mapfile`, `[[ ]]` y expansión indirecta `${!var}`, así que con el
|
|
# busybox de la base metal no arranca. Y termina en `exec dbus-run-session -- cosmic-session`,
|
|
# o sea que el bus de SESIÓN es parte del arranque, no un extra.
|
|
recipes/bash.toml
|
|
recipes/dbus.toml
|
|
|
|
# ── DATOS QUE UNA LIBRERÍA DEL CIERRE NECESITA EN RUNTIME ───────────────────────────────────
|
|
# libxkbcommon entra por el grafo (la enlazan comp y clientes) pero sus TABLAS no: sin
|
|
# `/usr/share/X11/xkb` no puede compilar un keymap y el escritorio queda sin teclado. Es el mismo
|
|
# patrón que los gschemas en GNOME — la librería es la interfaz, los datos son otro paquete.
|
|
recipes/incoming-cosmic/xkeyboard-config.toml
|
|
# Idem tipografías: nada del cierre trae una sola fuente, y sin fuentes el panel sale con tofu.
|
|
recipes/incoming-cosmic/dejavu-fonts.toml
|
|
# zlib COMPARTIDA. El corpus trae `zlib` estática, que le sirve a todo el cierre de COSMIC… y no
|
|
# al driver de mesa: `kms_swrast_dri.so` es un .so que se abre con dlopen y NEEDea `libz.so.1`.
|
|
# Sin ella mesa no carga NINGÚN driver de software, GBM falla, y el compositor arranca igual y
|
|
# deja la pantalla NEGRA — el fallo aparece a tres capas de distancia de la causa.
|
|
recipes/incoming-cosmic/zlib-shared.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 libdisplay-info y nasm hubo que copiarlas a incoming-cosmic/."""
|
|
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[@]} raíces: $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)"
|
|
[ "$miss" = 0 ]
|