From d6944b2cd9e4a87fce046ead1369d02325e94c9c Mon Sep 17 00:00:00 2001 From: sergio Date: Mon, 13 Jul 2026 23:26:26 -0400 Subject: [PATCH] =?UTF-8?q?kde/Plasma:=20hidrataci=C3=B3n=20del=20ESCRITOR?= =?UTF-8?q?IO=20COMPLETO=20store-directa=20(rebuild-free),=20cierre=20runt?= =?UTF-8?q?ime=20verificado?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - hydrate-from-store.sh: proyecta artefactos SELLADOS del store al FHS vía hammer hydrate, sin reproduce (install-reproduce rebuildеa qtbase por consumidor — hashing frágil bajo régimen dinámico). Selección por-pkg = artefacto más reciente con mtime --- docs/adr/0011-escritorio-kde-plasma.md | 29 +++++++++++- scripts/kde/hydrate-fhs.sh | 25 ++++++---- scripts/kde/hydrate-from-store.sh | 64 ++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 10 deletions(-) create mode 100644 scripts/kde/hydrate-from-store.sh diff --git a/docs/adr/0011-escritorio-kde-plasma.md b/docs/adr/0011-escritorio-kde-plasma.md index 992365fc..9e1a6ff7 100644 --- a/docs/adr/0011-escritorio-kde-plasma.md +++ b/docs/adr/0011-escritorio-kde-plasma.md @@ -243,8 +243,35 @@ probada, en tres piezas nuevas: - **Fuentes**: Qt ya no trae fuentes (`QFontDatabase: Cannot find font directory`). Cosmético para H-QML (no bloquea la instanciación), pero desplegar dejavu/fontconfig antes de apps reales. +### Hidratación del ESCRITORIO COMPLETO — store-directa, rebuild-free (2026-07-14) +`install`-reproduce NO escala al escritorio completo: el hashing determinista es frágil bajo el régimen +dinámico — cada consumidor qt (qtwayland/kwin) recomputa el cierre de build y puede obtener un hash de +qtbase distinto del sellado → **rebuild** (se observó qtbase rebuildeándose al instalar qtwayland). La +vía correcta es **`hammer hydrate --into` = proyectar el artefacto YA SELLADO del store al FHS, +sin reproduce**. `scripts/kde/hydrate-from-store.sh`: +- computa el cierre (nombres reales qt6-*) y, por paquete, elige el artefacto `store/-` MÁS + RECIENTE con mtime **anterior al inicio de hoy** (CUTOFF) = la cosecha FINAL de la granja (set + coherente, todo contra el mismo qtbase del cascade; evita los rebuilds ad-hoc de la sesión). +- **Resultado: 137/137 proyectados, 0 faltantes, 1257 `.so`, 241 binarios, 5.0G** en `work/kde-store- + rootfs`. Los hashes elegidos coinciden EXACTO con los sellados de la campaña (kwin `3a674cb6`, + plasma-workspace `284c862f`, libplasma `963403fe`, kscreenlocker `55120487`, breeze `df1d1e68`, + kio `44376420`). Binarios presentes: `plasmashell kwin_wayland startplasma-wayland krunner + plasma_session kscreenlocker_greet` (ksmserver no existe en Plasma 6 Wayland → plasma_session). +- **CIERRE RUNTIME VERIFICADO COMPLETO**: todos los `NEEDED` de plasmashell (30), kwin_wayland (24), + startplasma-wayland (11), krunner (19) y libs core (libPlasma/libPlasmaQuick/libKF6XmlGui/libQt6Quick) + resuelven dentro del rootfs+sysroot musl. **Cero dependencias colgantes** — el objetivo "capturar el + cierre runtime" queda cumplido para el escritorio entero. + +`hydrate-fhs.sh` (install-reproduce) queda para validar UN closure chico con verificación de firma +end-to-end (como H-QML); `hydrate-from-store.sh` es la vía para el rootfs de PRODUCTO completo. + ### Próximo paso concreto -Con H-QML cerrado, **Capa 2 (KDE Frameworks 6)**: `extra-cmake-modules` ya está; atacar tier 1 +Con H-QML cerrado y el escritorio hidratado con cierre runtime completo, lo que falta para "escritorio +VIVO" es: (1) **arrancar `startplasma-wayland` sobre `kwin_wayland` en metal/VM con GPU** (el worker es +headless glibc — necesita KMS/DRM real; coordinar con [[arranque-grafo-mirada]] / QEMU-GPU); (2) +desplegar fuentes (dejavu/fontconfig, hoy warning); (3) empaquetar `work/kde-store-rootfs` como imagen +de disco (reusar `scripts/disk-image.sh`/`install-image-efi.sh`). En paralelo, **Capa 2 (KDE Frameworks +6)**: `extra-cmake-modules` ya está; atacar tier 1 (`kcoreaddons`, `kconfig`, `ki18n`, `kirigami`…) hacia el hito **H-KF6** (una app KDE de ejemplo corre). En paralelo saldar la deuda Capa 0 (sqlite/openssl/glib shared-PIC) para promover un qtbase "real" a canónico/firmado. Mantener la hidratación FHS como el patrón de validación por capa. diff --git a/scripts/kde/hydrate-fhs.sh b/scripts/kde/hydrate-fhs.sh index 40caf449..6823cf16 100644 --- a/scripts/kde/hydrate-fhs.sh +++ b/scripts/kde/hydrate-fhs.sh @@ -13,21 +13,28 @@ set -uo pipefail ROOT="$(cd "$(dirname "$0")/../.." && pwd)"; cd "$ROOT" REPO="${REPO:-work/repo-kde}"; TRUST="${TRUST:-dist/keys}"; STORE="${STORE:-./store}" -RFS="${RFS:-work/kde-rootfs}"; TARGET="${TARGET:-qt6-qtdeclarative}" +RFS="${RFS:-work/kde-rootfs}" +# TARGETS = uno o varios paquetes tope (unión de cierres). TARGET (singular) sigue soportado. +TARGETS="${TARGETS:-${TARGET:-qt6-qtdeclarative}}" HAMMER="${HAMMER:-./target/release/hammer}" DB="$RFS/var/lib/hammer/installed.json" -# cierre transitivo del TARGET, leído del índice (deps + aliases ya resuelven). Excluimos herramientas -# puramente de build (no aportan runtime) para acelerar; si algo falta lo delata ldd. -CLOSURE=$(python3 - "$REPO/index.json" "$TARGET" <<'PY' +# cierre transitivo de TODOS los targets, leído del índice (deps + aliases ya resuelven). Excluimos +# herramientas puramente de build (no aportan runtime) para acelerar; si algo falta lo delata ldd. +CLOSURE=$(python3 - "$REPO/index.json" $TARGETS <<'PY' import json,sys -idx=json.load(open(sys.argv[1])); tgt=sys.argv[2] +idx=json.load(open(sys.argv[1])); targets=sys.argv[2:] by={p['name']:p for p in idx['packages']} BUILD_ONLY={'cmake','samurai','meson','ninja','bison','flex','gperf','util-macros','xorgproto', 'xcb-proto','wayland-protocols','pkgconf','mako','markupsafe','packaging','python3','extra-cmake-modules', - 'qttools','qt6-qttools'} -# alias qt-corto -> qt6-* + 'qttools','qt6-qttools','intltool','perl','perl-xml-parser','gettext-tiny','m4','autoconf','automake', + 'libtool','util-macros'} +# PREFERIR el nombre real qt6-* sobre el alias qt-corto: install nombra el artefacto por la clave del +# índice, y sólo `-qt6-qtdeclarative` está sellado (no `-qtdeclarative`) ⇒ instalar el alias +# fuerza cache-miss+rebuild. El alias existe sólo para resolver deps DENTRO del .swm, no para instalar. def canon(n): + if n.startswith('qt') and not n.startswith('qt6-') and ('qt6-'+n) in by: + return 'qt6-'+n return n if n in by else ('qt6-'+n if 'qt6-'+n in by else n) seen=set(); order=[] def visit(n): @@ -36,14 +43,14 @@ def visit(n): seen.add(n) for d in by[n].get('deps',[]): visit(d) order.append(n) -visit(tgt) +for t in targets: visit(t) for n in order: if n not in BUILD_ONLY: print(n) PY ) mkdir -p "$(dirname "$DB")" -echo "==> hidratando cierre runtime de $TARGET → $RFS ($(echo "$CLOSURE"|wc -w) pkgs)" +echo "==> hidratando cierre runtime de [$TARGETS] → $RFS ($(echo "$CLOSURE"|wc -w) pkgs)" ok=0; fail=0 for pkg in $CLOSURE; do before=$(find "$RFS" -type f 2>/dev/null | wc -l) diff --git a/scripts/kde/hydrate-from-store.sh b/scripts/kde/hydrate-from-store.sh new file mode 100644 index 00000000..8e4a8fba --- /dev/null +++ b/scripts/kde/hydrate-from-store.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# hydrate-from-store.sh — hidrata un FHS runtime KDE PROYECTANDO artefactos SELLADOS del store, SIN +# reproduce ni rebuild (`hammer hydrate --into`). Es la vía correcta para el escritorio completo: +# `install` reproduce-desde-fuente y el hashing determinista es frágil bajo el régimen dinámico (cada +# consumidor puede recomputar un hash distinto de qtbase → rebuild). Aquí se toma la salida ya sellada +# por la granja. +# +# Selección de hash por paquete: el artefacto store/- MÁS RECIENTE con mtime ANTERIOR al +# CUTOFF (default: hoy) — i.e. la cosecha FINAL de la granja (set coherente, todo construido contra el +# mismo qtbase del cascade final), evitando los rebuilds ad-hoc de la sesión de hoy. +# +# Uso: TARGETS="plasma-workspace kwin ..." ./hydrate-from-store.sh +# Env: RFS (def work/kde-store-rootfs), REPO (def work/repo-kde), CUTOFF (epoch; def inicio de hoy), +# LINK (static|dynamic, def static), STORE (def ./store) +set -uo pipefail +ROOT="$(cd "$(dirname "$0")/../.." && pwd)"; cd "$ROOT" +REPO="${REPO:-work/repo-kde}"; STORE="${STORE:-./store}"; LINK="${LINK:-static}" +RFS="${RFS:-work/kde-store-rootfs}"; HAMMER="${HAMMER:-./target/release/hammer}" +TARGETS="${TARGETS:-${TARGET:-qt6-qtdeclarative}}" +# CUTOFF: por defecto medianoche de hoy (excluye rebuilds de la sesión). Pasá 9999999999 para no filtrar. +CUTOFF="${CUTOFF:-$(date -d 'today 00:00' +%s 2>/dev/null || echo 9999999999)}" + +# cierre transitivo (nombres REALES qt6-*, no aliases: los artefactos del store se sellan por nombre real) +CLOSURE=$(python3 - "$REPO/index.json" $TARGETS <<'PY' +import json,sys +idx=json.load(open(sys.argv[1])); targets=sys.argv[2:] +by={p['name']:p for p in idx['packages']} +BUILD_ONLY={'cmake','samurai','meson','ninja','bison','flex','gperf','util-macros','xorgproto', + 'xcb-proto','wayland-protocols','pkgconf','mako','markupsafe','packaging','python3','extra-cmake-modules', + 'qttools','qt6-qttools','intltool','perl','perl-xml-parser','gettext-tiny','m4','autoconf','automake','libtool'} +def canon(n): + if n.startswith('qt') and not n.startswith('qt6-') and ('qt6-'+n) in by: return 'qt6-'+n + return n if n in by else ('qt6-'+n if 'qt6-'+n in by else n) +seen=set(); order=[] +def visit(n): + n=canon(n) + if n in seen or n not in by: return + seen.add(n) + for d in by[n].get('deps',[]): visit(d) + order.append(n) +for t in targets: visit(t) +for n in order: + if n not in BUILD_ONLY: print(n) +PY +) + +rm -rf "$RFS"; mkdir -p "$RFS" +echo "==> hidratando desde el store SELLADO (cutoff<$(date -d @$CUTOFF 2>/dev/null||echo $CUTOFF)) → $RFS ($(echo "$CLOSURE"|wc -w) pkgs)" +ok=0; miss=0 +for pkg in $CLOSURE; do + # artefacto - más reciente con mtime < CUTOFF (nombre EXACTO, no substring) + hash=$(find "$STORE" -maxdepth 1 -type d -name "*-$pkg" -printf '%T@ %f\n' 2>/dev/null \ + | awk -v c="$CUTOFF" '$1/dev/null \ + | sort -rn | head -1 | awk '{print $2}' | sed "s/-$pkg\$//") # fallback: el más nuevo aunque sea de hoy + if [ -z "$hash" ]; then printf " ✗ %-24s (sin artefacto en el store)\n" "$pkg"; miss=$((miss+1)); continue; fi + if "$HAMMER" --store "$STORE" hydrate "$hash" --into "$RFS" --link "$LINK" >"$RFS/.hs-$pkg.log" 2>&1; then + printf " ✓ %-24s %s\n" "$pkg" "${hash:0:12}"; ok=$((ok+1)) + else + printf " ✗ %-24s %s (ver .hs-%s.log)\n" "$pkg" "${hash:0:12}" "$pkg"; miss=$((miss+1)) + fi +done +echo "==> $ok proyectados, $miss faltantes; .so totales: $(find "$RFS" -name '*.so*'|wc -l)" +rm -f "$RFS"/.hs-*.log 2>/dev/null || true