tawasuyu

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…

⎇ main recipes/soundcraft.toml TOML · 150 lines · 10.9 KB History raw

docs: la suite ArtCraft entra entera — 12/12 selladas y 12/12 pintando, y los tres huecos de la IMAGEN que destaparon 107091e5f · Sergio ·

1# soundcraft — la estación de audio multipista — reimplementación limpia de Pro Tools
2#
3# ── DE DÓNDE SALE ESTA FAMILIA ─────────────────────────────────────────────────────────────────
4# ArtCraft publica doce aplicaciones de escritorio en **Rust puro sobre egui + wgpu**, Apache-2.0 o
5# MIT, cada una en su propio repo de `github.com/storytold`. Para esta casa son interesantes por una
6# razón concreta y medible: **no tienen ni una línea de GTK ni de Qt**, que es el muro que deja a
7# `libreoffice` en «59 recetas y tres muros ⇒ no es un ticket, es un año»
8# (`docs/plan-apps-usuario-final.md:48`). Acá cada app es UNA receta `cargo_vendor`.
9#
10# La primera que se midió fue `gridcraft`, y la medición está en su receta: sella en 12 min, abre
11# ventana en el perfil `escritorio-sway` y **pinta** — `wgpu` toma el backend **GL** sobre EGL/GLES
12# con llvmpipe (GL 4.5), sin Vulkan y sin ninguna variable de entorno, porque el default de
13# `egui-wgpu` es `Backends::from_env().unwrap_or(PRIMARY | GL)`.
14#
15# ⚠ **Son 0.x y se mueven solas.** Portar esto es comprar su cinta de correr (SDD 26 §2). Se pinea
16# el **sha del tag**, no `main`, y por eso la versión de arriba es una release y no «lo de hoy».
17#
18# ── X11: NO SE PARCHEA, Y ESO ES UNA DECISIÓN MEDIDA ──────────────────────────────────────────
19# Las doce declaran `eframe` con los features `wayland` **y** `x11`, y la primera versión de esto
20# arrancaba el feature `x11` con un `sed`. Se quitó, por tres cosas que se midieron al hacerlo:
21#   1. **No hay muro de build**: lo que `x11` arrastra es `x11rb` (protocolo X en Rust puro) y
22#      `x11-dl` (que hace `dlopen`, no enlaza). Ninguna receta nueva, ni un `.so` de X en el link.
23#   2. **No hay muro de runtime**: winit elige Wayland cuando `WAYLAND_DISPLAY` está puesto, y en
24#      esta distro `libX11` directamente no existe ⇒ el camino X es inerte, no roto.
25#   3. **Quitarlo SÍ cuesta**: cambia el grafo ⇒ el `Cargo.lock` deja de cerrar y hay que soltar el
26#      `--locked` del compile (medido: `error: cannot update the lock file … because --locked`).
27#      Y varias de estas apps declaran `winit` con `x11` por su cuenta y lo USAN en el código
28#      (`photocraft` abre por Xwayland si el usuario lo elige en Preferencias), así que un `sed`
29#      ciego rompe el build en unas y es un no-op en otras.
30# ⇒ Se deja el feature y se conserva el `--locked`, que es el pin fuerte. La distro sigue siendo
31# Wayland-only por lo que NO lleva en la imagen, no por lo que esta receta le recorta al upstream.
32#
33# ── LA FORMA ───────────────────────────────────────────────────────────────────────────────────
34# `link = "dynamic"` + `-crt-static` como `boveda`/`shuma-pregunta`/`mirada-greeter`: winit y wgpu
35# cargan `libwayland-client`, `libEGL`/`libvulkan` por `dlopen`, y un binario `crt-static` no puede.
36# El lanzador, el MIME y los iconos son de UPSTREAM — lo contrario de `boveda.toml`, donde hay que
37# autorar el `.desktop` — y están enhebrados: `Icon=`/`StartupWMClass=` del `.desktop`, el nombre de
38# los iconos hicolor y el `with_app_id()` de la app coinciden en `ai.storyteller.soundcraft`, así que
39# el emparejamiento ventana↔lanzador funciona en Wayland sin parchear nada.
40#
41# ⚠ El audio de esta casa es **PipeWire**, no ALSA, y esta app habla ALSA por `cpal`. No es
42# contradicción: PipeWire sirve el dispositivo ALSA por su capa de compatibilidad (`pipewire-alsa`,
43# que las tres recetas de pipewire ya llevan encendida). Lo que NO hay es cliente nativo: si
44# hiciera falta latencia baja de verdad, eso es su propia unidad de trabajo.
45#
46#
47# ── ⚠ DOS COSAS QUE ESTA FAMILIA DESMINTIÓ AL CONSTRUIRSE (2026-10-13) ────────────────────────
48# 1. **El panic de `zbus-lockstep-macros` NO se dispara en estas versiones.** `vectorcraft` compiló
49#    `atspi-common` 0.13.0 + `zbus-lockstep-macros` 0.5.2 **sin** el `LOCKSTEP_XML_PATH` del compile
50#    y salió `rc=0`, con el subdirectorio `xml/schemas/` presente en el crate (comprobado en el
51#    registro: `…/atspi-common-0.13.0/xml/schemas/`). O sea que lo de `boveda`/`mirada-greeter` era
52#    de otra combinación, no de la mera presencia del subdir. El bloque se deja en las demás como
53#    seguro barato —cuesta un `cp`— pero **no hay que leerlo como «sin esto no compila»**.
54# 2. **Los features de `eframe` del manifiesto de la app NO son los del build.** El `apps/<app>/
55#    Cargo.toml` de `vectorcraft` pide `eframe` sin `wgpu` y sin `accesskit`, y aun así el binario
56#    corre sobre `egui_wgpu` y compila la torre de `accesskit`+`atspi`: los features son ADITIVOS y
57#    se los enciende otro miembro del workspace (`<app>-ui-egui`). Leer sólo el manifiesto de la app
58#    para decidir qué camino gráfico toma una de éstas da la respuesta equivocada; lo que manda es
59#    lo que aparece en el log del build y en el del binario corriendo.
60# ⚠ **Medido al correrla (2026-10-13): esta app se queda SIN PORTAPAPELES en esta distro.**
61#     egui_winit::clipboard: Failed to initialize arboard clipboard: … X11 server connection timed out
62# Su `arboard` no enciende `wayland-data-control` —`gridcraft`, `photocraft`, `vectorcraft` y
63# `wordcraft` sí— así que intenta X11, que acá no existe, y expira. Copiar y pegar queda apagado y
64# nada falla: la misma figura que el diálogo de ficheros sin portal. Son siete de las doce; el
65# arreglo es un feature, y mejor aguas arriba que parcheado acá (ver la adenda del 2026-10-13 en
66# `docs/plan-apps-usuario-final.md`).
67name    = "soundcraft"
68version = "0.6.1"
69license = "MIT OR Apache-2.0"
70
71[source]
72# `v0.6.1`, el tag vigente al 2026-10-12. Se pinea el SHA: un tag se mueve.
73repo   = "https://github.com/storytold/soundcraft"
74commit = "6cf896e76c0ec82a11bf2494f209cfe7c8507f74"
75# ⚠ `vendor_cargo_deps` **sobrescribe** `<src>/.cargo/config.toml` con `std::fs::write`
76# (`crates/takana-build/src/fetch.rs:852`), y estos repos traen uno propio. Hoy es inocuo —dentro
77# sólo hay `[alias] xtask`, que estas recetas no usan— pero si un bump mete ahí un `rustflags` o un
78# `linker`, desaparece sin un solo aviso y el fallo no va a nombrar al config.
79# El `Cargo.lock` está committeado ⇒ el vendoreo va con `--locked` y queda pineado en el tiempo.
80
81[build]
82compiler = "zig-cc"
83target   = "x86_64-linux-musl"
84link     = "dynamic"
85flags    = ["-p", "soundcraft", "--bin", "soundcraft"]
86
87[build.phases]
88compile = '''
89set -e
90# LOCKSTEP_XML_PATH — la causa entera está en `mirada-greeter.toml` y repetida en `boveda.toml`:
91# `accesskit` → `atspi` → `atspi-common`, que trae `zbus-lockstep-macros` como dep NORMAL y cuyo
92# `#[validate]` recorre el dir `xml/` llamando `.extension().expect()` en CADA entrada ⇒ PANICA al
93# toparse con el subdirectorio `xml/schemas/`. Apuntando la variable a un dir con sólo los `.xml` se
94# evita. La comprobación es a gritos: un `cp` mudo dejaría la variable apuntando a un dir VACÍO y el
95# panic volvería con el error lejos de la causa.
96XMLDIR="$PWD/.atspi-lockstep-xml"
97mkdir -p "$XMLDIR"
98ls vendor/atspi-common/xml/*.xml >/dev/null 2>&1 || { echo "no hay vendor/atspi-common/xml/*.xml — ¿cambió el vendoreo o la versión de atspi? sin esto zbus-lockstep-macros panica en xml/schemas/" >&2; exit 1; }
99cp vendor/atspi-common/xml/*.xml "$XMLDIR"/
100export LOCKSTEP_XML_PATH="$XMLDIR"
101
102# El envoltorio de zig-cc, igual que en las otras winit+wgpu del corpus.
103printf '%s\n' '#!/bin/sh' 'for a do' 'case "$a" in --target=*) a=--target=x86_64-linux-musl ;; esac' 'set -- "$@" "$a"' 'shift' 'done' 'exec zig cc -mcpu=baseline "$@"' > "$PWD/.hammer-zig-cc"
104chmod +x "$PWD/.hammer-zig-cc"
105printf '%s\n' '#!/bin/sh' 'exec zig ar "$@"' > "$PWD/.hammer-zig-ar"
106chmod +x "$PWD/.hammer-zig-ar"
107RF="-C linker=$PWD/.hammer-zig-cc"
108rustc -vV | grep -q 'host: .*-alpine-' || RF="$RF -C link-self-contained=no"
109CC="$PWD/.hammer-zig-cc" AR="$PWD/.hammer-zig-ar" RUSTFLAGS="$RF" cargo rustc --release --locked --offline -p soundcraft --bin soundcraft -- -C target-feature=-crt-static
110'''
111install = '''
112set -e
113test -x target/release/soundcraft || { echo "no hay target/release/soundcraft — ¿cambió el nombre del bin?" >&2; exit 1; }
114install -Dm755 target/release/soundcraft /out/usr/bin/soundcraft
115
116install -Dm644 packaging/linux/ai.storyteller.soundcraft.desktop /out/usr/share/applications/ai.storyteller.soundcraft.desktop
117install -Dm644 packaging/linux/ai.storyteller.soundcraft.mime.xml /out/usr/share/mime/packages/ai.storyteller.soundcraft.xml
118# Los iconos se recorren con un glob y NO con una lista de tamaños: `effectcraft` y `filmcraft`
119# traen el doble de entradas que las demás, y una lista fija los habría instalado a medias en
120# silencio. Se cuenta lo instalado y se falla si es cero.
121n=0
122for p in assets/app-icon/hicolor/*/apps/ai.storyteller.soundcraft.*; do
123  [ -f "$p" ] || continue
124  install -Dm644 "$p" "/out/usr/share/icons/hicolor/${p#assets/app-icon/hicolor/}"
125  n=$((n+1))
126done
127[ "$n" -gt 0 ] || { echo "ningún icono instalado — ¿cambió assets/app-icon/hicolor/?" >&2; exit 1; }
128install -Dm644 LICENSE-MIT /out/usr/share/licenses/soundcraft/LICENSE-MIT
129install -Dm644 LICENSE-APACHE /out/usr/share/licenses/soundcraft/LICENSE-APACHE
130
131# Un `.desktop` vacío o un binario de 0 bytes son la forma de fallo de la casa: artefacto presente,
132# función ausente (CLAUDE.md §3).
133test -s /out/usr/share/applications/ai.storyteller.soundcraft.desktop
134test -s /out/usr/bin/soundcraft
135'''
136
137[deps]
138# Igual que las demás de la familia MÁS `alsa-lib`, y ésta no es decorativa: la app trae `cpal`
139# **sin condicionar por plataforma**, que en Linux es `alsa-sys` y pregunta por `alsa.pc` con
140# pkg-config ⇒ sin esta dep el build muere en el `build.rs` de `alsa-sys`, no en el link.
141# ⚠ **Y acá hay una cicatriz que conviene dejar escrita, porque me mordió a mí.** Escribí que «el
142# corpus no publica alsa» porque `scripts/provee.py --desde corpus alsa` contestaba «NADIE LO PUBLICA
143# en ninguna cola». **Era un falso negativo mío, no del catálogo**: `provee.py` toma el store de
144# `HAMMER_STORE` y su default es `./store`, que en el clon de la jaula está VACÍO — el bueno es
145# `/store`. Con `HAMMER_STORE=/store` la respuesta es `alsa ✓ corpus/alsa-lib ⇒ desde corpus: SÍ`, y
146# el artefacto estaba sellado en las dos máquinas (`b3:73fb200a`, con `alsa.pc` y `libasound.so.2`).
147# El build lo confirmó sin ayuda: `caché: artefacto ya en el store … name=alsa-lib`.
148# ⇒ **Una herramienta que contesta «nadie» apuntando a un store vacío se ve igual que un hueco real.**
149# Antes de declarar que falta algo, mirar a qué store se le está preguntando.
150build = ["mesa", "wayland", "wayland-protocols", "libdrm", "expat", "zlib", "zstd", "libffi", "pkgconf", "alsa-lib"]