gioser

sergio/takana

> 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…

TOML 1572Shell 203Rust 109Markdown 993728 Sutichaykuna

⎇ main docs/adr/0011-escritorio-kde-plasma.md Markdown · 277 siq'ikuna · 19.5 KB Ñawpa willay ch'uya

takana etapa 5b: los 59 docs de diseño, runbooks y ADR 97ceb7241 · Sergio ·

1# 0011 — Etapa Escritorio: campaña KDE Plasma 6
2
3Estado: aceptado · **gate H-Qt PASADO (2026-07-11)** · **hito H-QML PASADO (2026-07-14)**
4Fecha: 2026-07-11
5Relacionado: [[etapa-g-importar-nix]], [[etapa-g-gui-chain-boundary]], [[granja-efimera-hcloud]],
60003-zig-cc-builder, 0008-bootstrap-stages, 0010-arranque-grafo-mirada
7
8## Contexto
9
10El catálogo firmado tiene ~748 paquetes y ~760 recetas canónicas. En la práctica **es userland
11CLI**: cadenas Rust/Go self-contained que la granja muele fácil, más un cimiento gráfico GTK
12(`gtk4`/`libadwaita`/`cairo`/`pango`/`harfbuzz`/`freetype`/`fontconfig`/`mesa`/`wayland`) capaz de
13correr *apps* GTK sueltas — no un escritorio.
14
15El objetivo de esta etapa es un **escritorio completo, booteable y usable: KDE Plasma 6**. Se eligió
16KDE primero (sobre GNOME/XFCE) a pesar de ser el más caro (~600–800 recetas nuevas, C++ pesado)
17porque define el techo de dificultad: si la máquina de build sobrevive a Qt+Plasma, GNOME y XFCE
18son cuesta abajo y comparten casi todo el sustrato de bajo nivel.
19
20Este ADR **no** importa ni construye nada. Fija la política, el orden topológico, los hitos y la
21estrategia de granja. La primera tanda real es un paso posterior.
22
23## El eje del problema: static-musl/zig-cc **vs.** el mundo Qt/KDE
24
25Todo el lab se construyó sobre una disciplina (ADR 0003): **link estático, musl, `zig-cc`, cierre de
26deps explícito**. Eso hizo reproducibles 748 paquetes. Pero KDE es exactamente el terreno donde esa
27disciplina deja de aplicar sin traducción:
28
291. **Qt carga plugins `.so` en runtime.** La plataforma (QPA: `libqwayland*.so`, `libqxcb.so`),
30   los formatos de imagen, los estilos, los backends de multimedia — todos son bibliotecas
31   compartidas cargadas por `dlopen`. Un Qt "todo estático" **no arranca un escritorio**: sin el
32   plugin QPA no hay ventana. Esto no es una opción a desactivar como en `gtk4` (`-Dx11-backend=false`);
33   es la arquitectura.
342. **Plasma es una constelación de procesos + QML.** `kwin`, `plasmashell`, `krunner`,
35   `systemsettings`, servicios D-Bus. Comparten bibliotecas KF6/Qt6 a nivel de `.so`; estatizarlas
36   multiplicaría el binario por N procesos y rompería los plugins igual.
373. **C++ a escala Qt es territorio no ejercitado.** El corpus es C/Rust/Go. `zig-cc` compila C++,
38   pero Qt6 + KF6 es el stress-test más grande que va a ver; el zig-skew ya rompió `cairo`
39   (`ctime_r`) en el laptop ([[etapa-g-gui-chain-boundary]]).
40
41### Decisión de política
42
43**La capa gráfica de KDE adopta linking DINÁMICO.** Concretamente:
44
45- **Bibliotecas compartidas (`.so`) para Qt6, KF6 y Plasma**, con `SONAME` y `rpath`/`ld.so.conf`
46  soberanos. Se abandona el `-static` para esta capa; se conserva para el userland CLI ya sellado
47  (no se re-hashea nada existente).
48- **musl dinámico como objetivo por defecto.** Si un componente pelea con musl de forma
49  irreducible (glibc-ismos duros), se aísla y se documenta como deuda, igual que la "deuda GL de
50  Alpine" que ya arrastramos ([[mirada-usb-nvidia]]). No se introduce glibc a la ligera.
51- **`compiler` por componente.** `zig-cc` donde funcione; caída a `gcc`-de-gueto (el patrón ya usado
52  para kernel/cmake) donde el C++ de Qt lo exija. Se prueba zig primero, se documenta cada caída.
53- **CMake es el sistema de build dominante** (ECM lo es todo en KDE). Ya existe `recipes/cmake.toml`.
54
55Esta es la decisión arquitectónica que este ADR **registra**: la etapa escritorio parte el mundo en
56dos regímenes de linking, y eso es deliberado, no un accidente.
57
58## Lo que SÍ reusa el cimiento actual
59
60El preview del planeamiento dijo "reusa casi nada"; es pesimista. Qt≠GTK a nivel toolkit, pero **todo
61el sustrato de bajo nivel es compartido** y ya está sellado:
62
63`freetype`, `fontconfig`, `harfbuzz`, `fribidi`, `pixman`, `libpng`, `libjpeg-turbo`, `libtiff`,
64`expat`, `zlib`, `libffi`, `pcre2`, `libxml2`, `libxkbcommon`, `wayland`, `wayland-protocols`,
65`libdrm`, `mesa`, `dbus`, `glib` (Qt puede integrarse con glib main loop).
66
67Es decir: la **capa 0** (abajo) ya existe en buena parte. Lo nuevo empieza en Xorg/servicios y sube.
68
69## Orden topológico (capas de la campaña)
70
71De abajo hacia arriba; cada capa es puerta de la siguiente. Números = recetas *nuevas* estimadas.
72
73### Capa 0 — Sustrato gráfico y de sistema (~60–120)
74Lo que falta bajo Qt, mayormente C/Meson-CMake, dentro de la disciplina actual (dinámico ahora):
75- **Mesa completo** (no `mesa-swrast`): DRI, Gallium, Vulkan (KWin quiere Vulkan). Hoy tenemos base.
76- **Pila X11 para XWayland/xcb**: `libxcb`, `xcb-util*`, `libX11`, `libXext`, `libXi`, `libXcursor`,
77  `libXrandr`, `libXfixes`, `xkeyboard-config`, `xcb-util-cursor`. (Qt xcb + apps legacy vía XWayland.)
78- **XWayland** + **Xorg server** (para XWayland embebido).
79- **Seat/sesión**: `seatd` (ya en cola del otro agente) o **elogind** (logind sin systemd, clave en
80  musl), `pam`/`linux-pam` o `pam_rundir`, `polkit`.
81- **Audio**: `pipewire` + `wireplumber` (Plasma 6 asume PipeWire).
82- **Media** (Qt Multimedia / phonon): `ffmpeg` o `gstreamer`.
83- **Cripto/red base para servicios**: `openssl` (tenemos), `NetworkManager` o `connman`, `BlueZ`.
84- **Imagen/color**: `libepoxy` (tenemos), `lcm2`/`little-cms`, `libglvnd`.
85
86### Capa 1 — Qt 6 (~15–25 recetas, pero enormes)
87El corazón. Módulos, en orden:
88- `qtbase` (el mastodonte: gui, widgets, network, dbus, sql, el plugin QPA wayland+xcb).
89- `qtshadertools`, `qtdeclarative` (**QML — Plasma es QML**), `qtwayland`, `qtsvg`, `qt5compat`,
90  `qttools`, `qtmultimedia`, `qtsensors`, `qtvirtualkeyboard`, `qtimageformats`, `qtquick3d`(opc),
91  `qtwebengine`(**diferible** — Chromium embebido, la receta más cara de todo el proyecto).
92- Extras KDE: `qtkeychain`.
93
94### Capa 2 — KDE Frameworks 6 (~80 recetas, por tiers ECM)
95`extra-cmake-modules` (ECM) primero — es el build-system de todo KDE. Luego por tiers de dependencia:
96- **Tier 1** (sin deps entre frameworks): `kcoreaddons`, `kconfig`, `kguiaddons`, `ki18n`,
97  `kwidgetsaddons`, `kwindowsystem`, `kdbusaddons`, `karchive`, `kcodecs`, `kitemviews`, `solid`,
98  `sonnet`, `threadweaver`, `kholidays`, `prison`, `syntax-highlighting`, `breeze-icons`, `kirigami`…
99- **Tier 2**: `kauth`, `kcompletion`, `kcrash`, `kdoctools`, `kjobwidgets`, `knotifications`,
100  `kpackage`, `kunitconversion`, `kservice`…
101- **Tier 3**: `kconfigwidgets`, `kiconthemes`, `kio`, `kglobalaccel`, `ktextwidgets`, `kxmlgui`,
102  `kbookmarks`, `kwallet`, `knewstuff`, `kparts`, `kdeclarative`, `kcmutils`, `kdesu`,
103  `krunner`, `plasma-framework`/`libplasma`, `frameworkintegration`…
104- **Integración**: `kwayland`, `kdnssd`, `kpeople`, `modemmanager-qt`, `networkmanager-qt`,
105  `bluez-qt`, `kactivities`, `baloo` (búsqueda), `purpose`.
106
107### Capa 3 — Plasma 6 workspace (~40–60)
108- Base: `libkscreen`, `libksysguard`, `kscreenlocker`, `kglobalacceld`, `kdecoration`,
109  `layer-shell-qt`, `plasma-activities`, `kpipewire`.
110- Compositor: **`kwin`** (Wayland; requiere Vulkan/GL, XWayland).
111- Shell: `plasma-workspace` (`plasmashell`, `krunner`, `ksmserver`), `plasma-desktop`,
112  `plasma-pa` (audio), `plasma-nm` (red), `powerdevil`, `systemsettings`, `kmenuedit`,
113  `plasma-systemmonitor`, `kactivitymanagerd`, `milou`, `kscreen`, `sddm`(display manager) o
114  integración con el arranque-grafo-mirada (ver ADR 0010), `breeze` (tema/estilo Qt).
115
116### Capa 4 — Apps núcleo (opcional para "booteable", ~20–60)
117`dolphin` (archivos), `konsole` (terminal), `kate`/`kwrite`, `ark`, `gwenview`, `okular`,
118`spectacle`, `kcalc`, `kwalletmanager`… Se prioriza el mínimo para un escritorio *usable*, el resto
119por demanda.
120
121## Hitos (definición de "hecho" por etapa, verificable)
122
1231. **H-Qt**: `qtbase` construye + un binario Qt trivial (`QApplication` + `QLabel`) **abre una
124   ventana** sobre el compositor de referencia (mirada/QEMU). Prueba que el régimen dinámico + QPA
125   wayland funciona end-to-end. *Sin esto, nada de KDE tiene sentido — es el gate real.*
1262. **H-QML**: una app `qtdeclarative` (QML) renderiza. Plasma es QML; este hito de-riskea la capa 3.
1273. **H-KF6**: ECM + tier 1 + `kio` construyen; una app KDE de ejemplo corre.
1284. **H-kwin**: `kwin_wayland` levanta como compositor y pinta un fondo.
1295. **H-shell**: `plasmashell` arranca sobre `kwin`, con panel y lanzador. **← "escritorio prende".**
1306. **H-sesión**: login (SDD/mirada) → sesión Plasma completa, con audio (PipeWire), red (plasma-nm),
131   `konsole` + `dolphin` corriendo. **← distro gráfica booteable-usable.**
132
133## Estrategia de granja
134
135- La granja **efímera** ([[granja-efimera-hcloud]]) es la herramienta de volumen: `farm-run N`.
136- **PERO**: el escritorio no es "importar 700 y `farm-run`". El build-yield de C++/CMake/Qt es bajo;
137  cada receta pide de-Alpinización manual, patches y orden estricto. La granja **muele lo que ya está
138  bien importado**; el cuello es producir recetas de calidad, denso y asistido.
139- **Aislamiento de cola**: el otro agente comparte `recipes/incoming/` (stack tawasuyu). Esta campaña
140  usa cola propia **`recipes/incoming-kde/`** y commits con rutas explícitas (nunca `git add -A`),
141  mismo patrón que `incoming-go` ([[etapa-g-frente-go]]).
142- **No rebuildear en el laptop** el stack que el worker selle (zig-skew, [[etapa-g-gui-chain-boundary]]).
143- Cadencia por capa: importar+pinnear una capa → `farm-run` → cosechar+firmar → subir a la siguiente.
144  No fan-out ciego entre capas (violaría el orden topológico).
145- `qtwebengine` (Chromium) se **difiere**: es la receta más cara del proyecto y no es necesaria para
146  H-shell. Se ataca sólo si una app núcleo lo exige.
147
148## Riesgos y deudas conocidas
149
150- **musl vs. Qt/KDE**: partes pueden exigir glibc-ismos. Mitigación: aislar + documentar deuda, no
151  glibc global.
152- **zig-cc en C++ a escala**: puede haber caídas masivas a gcc-gueto. Aceptado; se documenta por comp.
153- **Vulkan/GL soberano**: KWin quiere aceleración; hoy arrastramos "deuda GL de Alpine". Puede forzar
154  atacar Mesa/driver a fondo antes de H-kwin.
155- **Tamaño**: install de escritorio KDE ronda miles de paquetes contando cierre transitivo; esta es
156  una **campaña larga**, no una tanda. Los ~700 son las recetas *nombradas*; el árbol es mayor.
157- **Reproducibilidad**: el régimen dinámico + plugins dlopen complica el hashing determinista que dio
158  748 reproducibles. Hay que extender la disciplina de sellado a `.so` con SONAME estable.
159
160## Alternativas consideradas
161
162- **XFCE/GNOME primero** (más baratos): descartado por decisión de producto — KDE fija el techo.
163- **Mantener todo estático**: inviable, Qt/Plasma requiere dlopen de plugins.
164- **Adoptar binarios Qt/KDE de Alpine sin rebuild**: viola soberanía del bootstrap; se acepta sólo
165  como deuda puntual (ej. GL), no como base del escritorio.
166
167## Resultado H-Qt (2026-07-11) — GATE PASADO ✅
168
169`qtbase 6.11.1` **construye y sella** en la granja (`b3:7c7c747d4d4b609f0d9eddbab5b4bc73e7816ce71c03e0fc53cfebf7ba35cebd`),
170receta `recipes/incoming-kde/qtbase.toml` (de-Alpinizada del APKBUILD, conserva sus parches de musl).
171Produce `libQt6Core/Gui/Widgets/OpenGL.so` **dinámicos** + el plugin QPA `libqwayland.so`. La app de
172prueba `scripts/kde/h-qt-test.cpp` (QApplication+QLabel) compila y linkea contra él, y **corre a rc=0**
173con `QT_QPA_PLATFORM=offscreen` en el rootfs musl del worker: crea la ventana y el event loop sale
174limpio. **El régimen dinámico + zig-cc + musl del ADR queda probado end-to-end en el componente más
175difícil de KDE** — el riesgo #1 (¿sobrevive el toolchain a Qt?) está respondido: sí.
176
177**Snapshot `takana-kde-qtbase-2026-07-11` (img 407438750)**: worker con qtbase + su cierre de deps ya
178sellados. Arrancar futuras `farm-up` de la campaña KDE desde ESTA imagen (no la golden limpia) evita
179re-pagar ~45 min de build. El worker efímero se destruyó (baseline €0).
180
181### Lecciones durables (patrón de-Alpinización Qt/KDE)
1821. **zig-cc + CMake**: CMake canonicaliza el symlink `cc`→`/opt/zig/zig`, perdiendo el despacho por
183   `argv[0]`; ASM y custom-commands invocan `zig -std=…` (subcomando inválido). Fix: scripts wrapper
184   reales (`$PWD/.zwrap/{cc,cxx}` = `exec zig cc/c++ -mcpu=baseline`), en el ÁRBOL DE FUENTE (no /tmp,
185   que se vacía entre fases), y `-DCMAKE_{C,CXX,ASM}_COMPILER` apuntándolos. Un script no se canonicaliza.
1862. **Tensión estático↔dinámico (la deuda central de Capa 0)**: la capa GUI arma `.so`, pero muchas
187   libs C canónicas están selladas como **archive estático sin -fPIC** (openssl `libcrypto.a`, sqlite)
188   o **incompletas bajo `-Wl,--no-undefined`** (glib) o **sin arrastrar deps transitivas** (fontconfig
189   →expat). `libQt6Core.so` SÍ linkea con glib/pcre2 (PIC-compat) y `zlib` entra como `libz.so`. Para
190   el gate se apagó/bundleó lo problemático (dbus/sql/openssl/glib/fontconfig OFF; freetype/harfbuzz/
191   libpng/libjpeg bundled). **DEUDA para KDE real: reconstruir el closure C shared-PIC + `.pc` con
192   `Requires.private` completo** (SSL/SQL/glib/fontconfig los va a exigir Plasma).
1933. **Hidratación runtime**: correr un binario musl del store exige el rootfs musl (`.dev-fs/alpine`, el
194   host del worker es glibc) + el cierre de `.so` en `LD_LIBRARY_PATH` (mesa/libdrm/…). Varios libs son
195   static-only en el store: se puede fabricar la `.so` desde el `.a` si es PIC
196   (`zig cc -shared -Wl,--whole-archive lib.a`). Falta una hidratación FHS que capture el cierre runtime.
197
198## Próximo paso concreto
199
200Con H-Qt cerrado, la **Capa 1 (Qt6)** sigue con **qtdeclarative (QML)** — Plasma es QML, es el
201siguiente de-risk (hito H-QML) — más `qtwayland` como módulo completo (ya tenemos su QPA en qtbase,
202pero KDE usa `qtwayland` extendido). En paralelo, **saldar la deuda de Capa 0**: sqlite+openssl
203shared-PIC y glib `.a` completo, para reactivar SSL/SQL/glib en un qtbase "real" (hoy es el qtbase del
204gate, reducido; se rebuildea con features plenas antes de promover a canónico/firmado). Arrancar las
205`farm-up` desde el snapshot `takana-kde-qtbase-2026-07-11`.
206
207## Resultado H-QML (2026-07-14) — HITO PASADO ✅
208
209`qtdeclarative` (QML/QtQuick) cosechado en el repo firmado del escritorio (`work/repo-kde`, 161 pkgs
210KDE). El test `scripts/kde/h-qml-test.cpp` (`QQmlApplicationEngine` que carga QML inline `Window` +
211`Text` vía `import QtQuick`) **compila** con zig-cc/musl contra el FHS hidratado y **corre a rc=0** con
212`QT_QPA_PLATFORM=offscreen`: el engine instancia el árbol de objetos QtQuick y el event loop sale
213limpio. **El motor QML — el corazón de Plasma — carga e instancia una escena bajo el régimen
214dinámico/musl/zig-cc.** Reproducible con `scripts/kde/h-qml-run.sh` en el worker.
215
216### La HIDRATACIÓN FHS (salda la deuda de la lección 3 del gate H-Qt)
217El gate dejó pendiente "una hidratación FHS que capture el cierre runtime". Ahora existe y está
218probada, en tres piezas nuevas:
2191. **`scripts/kde/repo-selfcontain.py`** — vuelve `work/repo-kde` AUTO-CONTENIDO para `install`:
220   `install` lee los `depends` del recipe embebido en cada `.swm` y resuelve cada nombre como CLAVE
221   del índice. El repo cosechado no cerraba por dos causas: (a) deps a nombres de receta-archivo
222   qt-cortos (`qtbase`) cuando el paquete es `qt6-qtbase` (fuga de de-Alpinización, 79 recetas), y
223   (b) faltaban libs base del cierre. El script trae de `dist/repo` (repo base firmado, cierre
224   completo) todo paquete ausente (colisión → gana la versión KDE) y agrega entradas ALIAS
225   qt-corto→qt6-* (mismo `.swm`, otra clave). Índice resultante 884 entradas, re-firmado con
226   `takana repo sign` → `repo verify` trusted.
2272. **`scripts/kde/hydrate-fhs.sh`** — hidrata el cierre runtime instalando **cada** paquete del
228   cierre en el MISMO prefix (clave: `install <pkg> --prefix P` sólo baja los ficheros de `<pkg>`, NO
229   de su cierre, igual que `product-userland-from-repo.sh`). Con el store del worker caliente los
230   32 pkgs del cierre de `qt6-qtdeclarative` cache-hitean; produce `work/kde-rootfs` con 455 `.so`
231   (libQt6Core/Gui/Qml/Quick + el QPA `libqoffscreen.so` + mesa/EGL/xcb/wayland/openssl/...).
2323. **`scripts/kde/h-qml-run.sh`** — compila+corre el reproducer H-QML contra ese FHS.
233
234### Deudas/lecciones nuevas (para KDE real)
235- **`target_bin` espurio en recetas de LIBS**: las recetas KDE declaran `target_bin=/usr/bin/<pkg>`
236  que no existe (son libs). `install` hidrata los ficheros y LUEGO falla la aserción con exit 1;
237  `hydrate-fhs.sh` lo tolera (cuenta éxito si dejó ficheros). **Fix pendiente**: quitar/corregir
238  `target_bin` en las recetas de librería (requiere re-pack del `.swm`).
239- **Cache-miss al re-resolver deps**: agregar libpng/openssl/etc. al cierre cambió el input-hash de
240  qtbase → `install` rebuildeó un qtbase MÁS COMPLETO (con módulo Network/SSL, antes apagado en el
241  gate). Es avance hacia el "qtbase real", pero rompe la reutilización del sellado `7c7c747d`. Para
242  reproducibilidad estricta hay que fijar el cierre de build-deps exacto del sellado.
243- **Fuentes**: Qt ya no trae fuentes (`QFontDatabase: Cannot find font directory`). Cosmético para
244  H-QML (no bloquea la instanciación), pero desplegar dejavu/fontconfig antes de apps reales.
245
246### Hidratación del ESCRITORIO COMPLETO — store-directa, rebuild-free (2026-07-14)
247`install`-reproduce NO escala al escritorio completo: el hashing determinista es frágil bajo el régimen
248dinámico — cada consumidor qt (qtwayland/kwin) recomputa el cierre de build y puede obtener un hash de
249qtbase distinto del sellado → **rebuild** (se observó qtbase rebuildeándose al instalar qtwayland). La
250vía correcta es **`takana hydrate <hash> --into` = proyectar el artefacto YA SELLADO del store al FHS,
251sin reproduce**. `scripts/kde/hydrate-from-store.sh`:
252- computa el cierre (nombres reales qt6-*) y, por paquete, elige el artefacto `store/<hash>-<name>` MÁS
253  RECIENTE con mtime **anterior al inicio de hoy** (CUTOFF) = la cosecha FINAL de la granja (set
254  coherente, todo contra el mismo qtbase del cascade; evita los rebuilds ad-hoc de la sesión).
255- **Resultado: 137/137 proyectados, 0 faltantes, 1257 `.so`, 241 binarios, 5.0G** en `work/kde-store-
256  rootfs`. Los hashes elegidos coinciden EXACTO con los sellados de la campaña (kwin `3a674cb6`,
257  plasma-workspace `284c862f`, libplasma `963403fe`, kscreenlocker `55120487`, breeze `df1d1e68`,
258  kio `44376420`). Binarios presentes: `plasmashell kwin_wayland startplasma-wayland krunner
259  plasma_session kscreenlocker_greet` (ksmserver no existe en Plasma 6 Wayland → plasma_session).
260- **CIERRE RUNTIME VERIFICADO COMPLETO**: todos los `NEEDED` de plasmashell (30), kwin_wayland (24),
261  startplasma-wayland (11), krunner (19) y libs core (libPlasma/libPlasmaQuick/libKF6XmlGui/libQt6Quick)
262  resuelven dentro del rootfs+sysroot musl. **Cero dependencias colgantes** — el objetivo "capturar el
263  cierre runtime" queda cumplido para el escritorio entero.
264
265`hydrate-fhs.sh` (install-reproduce) queda para validar UN closure chico con verificación de firma
266end-to-end (como H-QML); `hydrate-from-store.sh` es la vía para el rootfs de PRODUCTO completo.
267
268### Próximo paso concreto
269Con H-QML cerrado y el escritorio hidratado con cierre runtime completo, lo que falta para "escritorio
270VIVO" es: (1) **arrancar `startplasma-wayland` sobre `kwin_wayland` en metal/VM con GPU** (el worker es
271headless glibc — necesita KMS/DRM real; coordinar con [[arranque-grafo-mirada]] / QEMU-GPU); (2)
272desplegar fuentes (dejavu/fontconfig, hoy warning); (3) empaquetar `work/kde-store-rootfs` como imagen
273de disco (reusar `scripts/disk-image.sh`/`install-image-efi.sh`). En paralelo, **Capa 2 (KDE Frameworks
2746)**: `extra-cmake-modules` ya está; atacar tier 1
275(`kcoreaddons`, `kconfig`, `ki18n`, `kirigami`…) hacia el hito **H-KF6** (una app KDE de ejemplo
276corre). En paralelo saldar la deuda Capa 0 (sqlite/openssl/glib shared-PIC) para promover un qtbase
277"real" a canónico/firmado. Mantener la hidratación FHS como el patrón de validación por capa.

0011-escritorio-kde-plasma.md

0011 — Etapa Escritorio: campaña KDE Plasma 6

Estado: aceptado · gate H-Qt PASADO (2026-07-11) · hito H-QML PASADO (2026-07-14) Fecha: 2026-07-11 Relacionado: [[etapa-g-importar-nix]], [[etapa-g-gui-chain-boundary]], [[granja-efimera-hcloud]], 0003-zig-cc-builder, 0008-bootstrap-stages, 0010-arranque-grafo-mirada

Contexto

El catálogo firmado tiene ~748 paquetes y ~760 recetas canónicas. En la práctica es userland CLI: cadenas Rust/Go self-contained que la granja muele fácil, más un cimiento gráfico GTK (gtk4/libadwaita/cairo/pango/harfbuzz/freetype/fontconfig/mesa/wayland) capaz de correr apps GTK sueltas — no un escritorio.

El objetivo de esta etapa es un escritorio completo, booteable y usable: KDE Plasma 6. Se eligió KDE primero (sobre GNOME/XFCE) a pesar de ser el más caro (~600–800 recetas nuevas, C++ pesado) porque define el techo de dificultad: si la máquina de build sobrevive a Qt+Plasma, GNOME y XFCE son cuesta abajo y comparten casi todo el sustrato de bajo nivel.

Este ADR no importa ni construye nada. Fija la política, el orden topológico, los hitos y la estrategia de granja. La primera tanda real es un paso posterior.

El eje del problema: static-musl/zig-cc vs. el mundo Qt/KDE

Todo el lab se construyó sobre una disciplina (ADR 0003): link estático, musl, zig-cc, cierre de deps explícito. Eso hizo reproducibles 748 paquetes. Pero KDE es exactamente el terreno donde esa disciplina deja de aplicar sin traducción:

  1. Qt carga plugins .so en runtime. La plataforma (QPA: libqwayland*.so, libqxcb.so), los formatos de imagen, los estilos, los backends de multimedia — todos son bibliotecas compartidas cargadas por dlopen. Un Qt "todo estático" no arranca un escritorio: sin el plugin QPA no hay ventana. Esto no es una opción a desactivar como en gtk4 (-Dx11-backend=false); es la arquitectura.
  2. Plasma es una constelación de procesos + QML. kwin, plasmashell, krunner, systemsettings, servicios D-Bus. Comparten bibliotecas KF6/Qt6 a nivel de .so; estatizarlas multiplicaría el binario por N procesos y rompería los plugins igual.
  3. C++ a escala Qt es territorio no ejercitado. El corpus es C/Rust/Go. zig-cc compila C++, pero Qt6 + KF6 es el stress-test más grande que va a ver; el zig-skew ya rompió cairo (ctime_r) en el laptop ([[etapa-g-gui-chain-boundary]]).

Decisión de política

La capa gráfica de KDE adopta linking DINÁMICO. Concretamente:

Esta es la decisión arquitectónica que este ADR registra: la etapa escritorio parte el mundo en dos regímenes de linking, y eso es deliberado, no un accidente.

Lo que SÍ reusa el cimiento actual

El preview del planeamiento dijo "reusa casi nada"; es pesimista. Qt≠GTK a nivel toolkit, pero todo el sustrato de bajo nivel es compartido y ya está sellado:

freetype, fontconfig, harfbuzz, fribidi, pixman, libpng, libjpeg-turbo, libtiff, expat, zlib, libffi, pcre2, libxml2, libxkbcommon, wayland, wayland-protocols, libdrm, mesa, dbus, glib (Qt puede integrarse con glib main loop).

Es decir: la capa 0 (abajo) ya existe en buena parte. Lo nuevo empieza en Xorg/servicios y sube.

Orden topológico (capas de la campaña)

De abajo hacia arriba; cada capa es puerta de la siguiente. Números = recetas nuevas estimadas.

Capa 0 — Sustrato gráfico y de sistema (~60–120)

Lo que falta bajo Qt, mayormente C/Meson-CMake, dentro de la disciplina actual (dinámico ahora):

Capa 1 — Qt 6 (~15–25 recetas, pero enormes)

El corazón. Módulos, en orden:

Capa 2 — KDE Frameworks 6 (~80 recetas, por tiers ECM)

extra-cmake-modules (ECM) primero — es el build-system de todo KDE. Luego por tiers de dependencia:

Capa 3 — Plasma 6 workspace (~40–60)

Capa 4 — Apps núcleo (opcional para "booteable", ~20–60)

dolphin (archivos), konsole (terminal), kate/kwrite, ark, gwenview, okular, spectacle, kcalc, kwalletmanager… Se prioriza el mínimo para un escritorio usable, el resto por demanda.

Hitos (definición de "hecho" por etapa, verificable)

  1. H-Qt: qtbase construye + un binario Qt trivial (QApplication + QLabel) abre una ventana sobre el compositor de referencia (mirada/QEMU). Prueba que el régimen dinámico + QPA wayland funciona end-to-end. Sin esto, nada de KDE tiene sentido — es el gate real.
  2. H-QML: una app qtdeclarative (QML) renderiza. Plasma es QML; este hito de-riskea la capa 3.
  3. H-KF6: ECM + tier 1 + kio construyen; una app KDE de ejemplo corre.
  4. H-kwin: kwin_wayland levanta como compositor y pinta un fondo.
  5. H-shell: plasmashell arranca sobre kwin, con panel y lanzador. ← "escritorio prende".
  6. H-sesión: login (SDD/mirada) → sesión Plasma completa, con audio (PipeWire), red (plasma-nm), konsole + dolphin corriendo. ← distro gráfica booteable-usable.

Estrategia de granja

Riesgos y deudas conocidas

Alternativas consideradas

Resultado H-Qt (2026-07-11) — GATE PASADO ✅

qtbase 6.11.1 construye y sella en la granja (b3:7c7c747d4d4b609f0d9eddbab5b4bc73e7816ce71c03e0fc53cfebf7ba35cebd), receta recipes/incoming-kde/qtbase.toml (de-Alpinizada del APKBUILD, conserva sus parches de musl). Produce libQt6Core/Gui/Widgets/OpenGL.so dinámicos + el plugin QPA libqwayland.so. La app de prueba scripts/kde/h-qt-test.cpp (QApplication+QLabel) compila y linkea contra él, y corre a rc=0 con QT_QPA_PLATFORM=offscreen en el rootfs musl del worker: crea la ventana y el event loop sale limpio. El régimen dinámico + zig-cc + musl del ADR queda probado end-to-end en el componente más difícil de KDE — el riesgo #1 (¿sobrevive el toolchain a Qt?) está respondido: sí.

Snapshot takana-kde-qtbase-2026-07-11 (img 407438750): worker con qtbase + su cierre de deps ya sellados. Arrancar futuras farm-up de la campaña KDE desde ESTA imagen (no la golden limpia) evita re-pagar ~45 min de build. El worker efímero se destruyó (baseline €0).

Lecciones durables (patrón de-Alpinización Qt/KDE)

  1. zig-cc + CMake: CMake canonicaliza el symlink cc→/opt/zig/zig, perdiendo el despacho por argv[0]; ASM y custom-commands invocan zig -std=… (subcomando inválido). Fix: scripts wrapper reales ($PWD/.zwrap/{cc,cxx} = exec zig cc/c++ -mcpu=baseline), en el ÁRBOL DE FUENTE (no /tmp, que se vacía entre fases), y -DCMAKE_{C,CXX,ASM}_COMPILER apuntándolos. Un script no se canonicaliza.
  2. Tensión estático↔dinámico (la deuda central de Capa 0): la capa GUI arma .so, pero muchas libs C canónicas están selladas como archive estático sin -fPIC (openssl libcrypto.a, sqlite) o incompletas bajo -Wl,--no-undefined (glib) o sin arrastrar deps transitivas (fontconfig →expat). libQt6Core.so SÍ linkea con glib/pcre2 (PIC-compat) y zlib entra como libz.so. Para el gate se apagó/bundleó lo problemático (dbus/sql/openssl/glib/fontconfig OFF; freetype/harfbuzz/ libpng/libjpeg bundled). DEUDA para KDE real: reconstruir el closure C shared-PIC + .pc con Requires.private completo (SSL/SQL/glib/fontconfig los va a exigir Plasma).
  3. Hidratación runtime: correr un binario musl del store exige el rootfs musl (.dev-fs/alpine, el host del worker es glibc) + el cierre de .so en LD_LIBRARY_PATH (mesa/libdrm/…). Varios libs son static-only en el store: se puede fabricar la .so desde el .a si es PIC (zig cc -shared -Wl,--whole-archive lib.a). Falta una hidratación FHS que capture el cierre runtime.

Próximo paso concreto

Con H-Qt cerrado, la Capa 1 (Qt6) sigue con qtdeclarative (QML) — Plasma es QML, es el siguiente de-risk (hito H-QML) — más qtwayland como módulo completo (ya tenemos su QPA en qtbase, pero KDE usa qtwayland extendido). En paralelo, saldar la deuda de Capa 0: sqlite+openssl shared-PIC y glib .a completo, para reactivar SSL/SQL/glib en un qtbase "real" (hoy es el qtbase del gate, reducido; se rebuildea con features plenas antes de promover a canónico/firmado). Arrancar las farm-up desde el snapshot takana-kde-qtbase-2026-07-11.

Resultado H-QML (2026-07-14) — HITO PASADO ✅

qtdeclarative (QML/QtQuick) cosechado en el repo firmado del escritorio (work/repo-kde, 161 pkgs KDE). El test scripts/kde/h-qml-test.cpp (QQmlApplicationEngine que carga QML inline Window + Text vía import QtQuick) compila con zig-cc/musl contra el FHS hidratado y corre a rc=0 con QT_QPA_PLATFORM=offscreen: el engine instancia el árbol de objetos QtQuick y el event loop sale limpio. El motor QML — el corazón de Plasma — carga e instancia una escena bajo el régimen dinámico/musl/zig-cc. Reproducible con scripts/kde/h-qml-run.sh en el worker.

La HIDRATACIÓN FHS (salda la deuda de la lección 3 del gate H-Qt)

El gate dejó pendiente "una hidratación FHS que capture el cierre runtime". Ahora existe y está probada, en tres piezas nuevas:

  1. scripts/kde/repo-selfcontain.py — vuelve work/repo-kde AUTO-CONTENIDO para install: install lee los depends del recipe embebido en cada .swm y resuelve cada nombre como CLAVE del índice. El repo cosechado no cerraba por dos causas: (a) deps a nombres de receta-archivo qt-cortos (qtbase) cuando el paquete es qt6-qtbase (fuga de de-Alpinización, 79 recetas), y (b) faltaban libs base del cierre. El script trae de dist/repo (repo base firmado, cierre completo) todo paquete ausente (colisión → gana la versión KDE) y agrega entradas ALIAS qt-corto→qt6-* (mismo .swm, otra clave). Índice resultante 884 entradas, re-firmado con takana repo sign → repo verify trusted.
  2. scripts/kde/hydrate-fhs.sh — hidrata el cierre runtime instalando cada paquete del cierre en el MISMO prefix (clave: install <pkg> --prefix P sólo baja los ficheros de <pkg>, NO de su cierre, igual que product-userland-from-repo.sh). Con el store del worker caliente los 32 pkgs del cierre de qt6-qtdeclarative cache-hitean; produce work/kde-rootfs con 455 .so (libQt6Core/Gui/Qml/Quick + el QPA libqoffscreen.so + mesa/EGL/xcb/wayland/openssl/...).
  3. scripts/kde/h-qml-run.sh — compila+corre el reproducer H-QML contra ese FHS.

Deudas/lecciones nuevas (para KDE real)

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 takana hydrate <hash> --into = proyectar el artefacto YA SELLADO del store al FHS, sin reproduce. scripts/kde/hydrate-from-store.sh:

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 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.