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…

accesibilidad: el SERVIDOR de voz, no sólo el sintetizador

espeak-ng es un sintetizador: si cada app lo invoca sola, dos que hablen a la vez
se pisan, nadie puede interrumpir a nadie, y cambiar voz o velocidad es cosa de
cada app. speech-dispatcher es la capa que falta — daemon con cola y
prioridades, API estable en C (libspeechd) y en Python (speechd), y los
sintetizadores detrás como módulos. Es lo que orca usará para hablar y lo que
hace que `spd-say "hola"` funcione desde cualquier script.

Medido sobre su configure.ac y no sobre los paquetes de otra distro: de sus once
PKG_CHECK_MODULES los obligatorios son glib/gthread/gmodule, sndfile y
dotconf >= 1.3. libsystemd trae su propio fallback ([:]), que es lo que permite
construirlo sin systemd — no hubo que parchear nada.

dotconf era la única que faltaba, y se compila A MANO: el archive del tag no
trae configure generado, el lab no tiene autoconf ni automake, y upstream NO
publica releases (el asset da 404), así que el truco que salvó a pcaudiolib no
existe acá. La biblioteca son dos .c de 41 KB — un autoconf para eso es más
código que la biblioteca y mete una dep de build grande para siempre. El .pc se
sustituye de su plantilla, y el guardián usa el chequeo FUERTE
(PKG_CONFIG_LIBDIR + --cflags --libs, no --exists) más la cota >= 1.3 exacta que
pide el consumidor.

--disable-ltdl aunque su default (check) habría funcionado: así usa el dlopen de
libc fijo en vez de decidir su mecanismo según lo que hubiera en el sysroot. Un
build que elige según el entorno no es reproducible por construcción.

⚠ DOS ERRORES MÍOS, corregidos, y las lecciones valen más que el arreglo:

1. Sin --sysconfdir=/etc, autotools deriva /usr/etc EN SILENCIO y ahí fue a
   parar speechd.conf. El daemon hornea esa ruta, así que el artefacto queda
   auto-consistente y nadie se entera — salvo que la config queda fuera de donde
   está la del resto de la distro. 31 recetas del repo ya lo pasan: la omisión
   era la anomalía.

2. Mi control del audio buscaba «pipewire» en speechd.conf, y el .conf NO es el
   contrato: upstream deja la línea comentada a propósito, como documentación
   del default, y el valor real lo hornea el configure. Medido en el binario: de
   los cinco métodos el daemon sólo contiene «pipewire». Ahora se comprueba el
   BINARIO y FALLA en vez de avisar — un aviso blando que nadie lee no es un
   control.

Y binutils declarado por el guardián (aporta `strings`), no por el build: sin
eso el control fallaría por falta de herramienta y acusaría a la receta de algo
que no hizo. Misma nota que lleva espeak-ng.

Apagados y dicho por qué: libao/oss/nas no están en el corpus; flite tampoco; y
ibmtts, voxin, ivona, pico, baratinoo y kali son propietarios o de pago. Con
espeak-ng detrás el servidor habla 100+ idiomas sin ninguno.

Verificado: dotconf sella a la primera; speech-dispatcher con daemon, spd-say,
7 módulos, libspeechd.so y el módulo python.

a709d76c23a1c8b5a8689a07cc4a145f070d6787 · por Sergio · (hace 1 d) · padre dbe4fc662 · ver el árbol en este commit

Delta semántico

Ninguna definición cambió: el cambio es de texto, documentación o datos.

2 archivos · +241 −0

diff --git a/recipes/dotconf.toml b/recipes/dotconf.toml
new file mode 100644
index 00000000..bbf249f7
--- /dev/null
+++ b/recipes/dotconf.toml
@@ -0,0 +1,102 @@
+# dotconf 1.4.1 — el parser de ficheros de configuración de speech-dispatcher.
+#
+# ══ POR QUÉ ENTRA ══════════════════════════════════════════════════════════════════════════════
+# Es la ÚNICA dep obligatoria de `speech-dispatcher` que el corpus no tenía. Medido sobre su
+# `configure.ac`, no sobre los paquetes de otra distro: de sus once `PKG_CHECK_MODULES`, los
+# obligatorios son `glib-2.0`/`gthread-2.0`/`gmodule-2.0`, `sndfile` y **`dotconf >= 1.3`**; el
+# resto va detrás de un `--with-*` (espeak-ng, pulse, libao, pipewire, alsa) y `libsystemd` trae su
+# propio fallback (`[:]`), que es lo que permite construirlo en una distro sin systemd.
+#
+# ══ ⚠ SE COMPILA A MANO, Y NO ES CAPRICHO ══════════════════════════════════════════════════════
+# El archive del tag **no trae `configure` generado** (sólo `configure.ac`), y en este lab **no hay
+# autoconf ni automake** — `libtool` y `m4` sí, pero no alcanzan. Tampoco hay salida por el lado de
+# upstream: el proyecto **no publica releases**, sólo tags (comprobado: el asset
+# `releases/download/v1.4.1/…` da 404), así que el truco que salvó a `pcaudiolib` —usar el tarball
+# del release, que es un `make dist` con el `configure` ya hecho— acá no existe.
+#
+# ⇒ Las opciones eran escribir el autoconf o compilar los dos ficheros a mano. Se eligió lo
+# segundo **porque la biblioteca son dos `.c`**: `dotconf.c` (36 KB) y `readdir.c` (4,7 KB), los dos
+# fuentes incondicionales de su `Makefile.am`, y una sola cabecera instalable (`dotconf.h`). Un
+# autoconf para eso es más código que la biblioteca, y además mete una dep de build grande en el
+# corpus para siempre.
+#
+# `-fPIC` explícito: `speech-dispatcher` carga sus módulos de salida como `.so` con gmodule, y una
+# `.a` no-PIC no puede entrar en una compartida — el muro del 19-oct.
+#
+# ══ EL `.pc` SE SUSTITUYE, PORQUE ES LO QUE EL CONSUMIDOR BUSCA ═════════════════════════════════
+# `speech-dispatcher` la encuentra con `PKG_CHECK_MODULES([DOTCONF], [dotconf >= 1.3])`, o sea
+# **por pkg-config y con una cota de versión**: sin `dotconf.pc` no hay forma de que la vea, y con
+# un `.pc` sin `Version:` la cota falla. Upstream trae la plantilla `dotconf.pc.in`; acá se
+# sustituyen sus cinco `@marcadores@` a mano, igual que en `libhangul`.
+#
+# El guardián comprueba las tres cosas por separado, porque fallan distinto: que no quede ningún
+# `@marcador@` sin sustituir, que la cabecera esté donde el `.pc` promete, y que **pkgconf lo
+# resuelva de verdad** con `PKG_CONFIG_LIBDIR` puesto — el chequeo fuerte, porque `pkgconf --exists`
+# sin eso lee el anfitrión y da verde en falso (pasado dos veces en este repo).
+name    = "dotconf"
+version = "1.4.1"
+license = "LGPL-2.1-or-later"
+
+[source]
+tarball = "https://github.com/williamh/dotconf/archive/refs/tags/v1.4.1/dotconf-1.4.1.tar.gz"
+sha256  = "5922c46cacf99b2ecc4853d28a2bda4a489292e73276e604bd9cba29dfca892d"
+
+[build]
+compiler = "zig-cc"
+target   = "x86_64-linux-musl"
+link     = "static"
+flags    = []
+
+[build.phases]
+configure = '''
+set -e
+for f in src/dotconf.c src/readdir.c src/dotconf.h dotconf.pc.in; do
+  [ -f "$f" ] || { echo "dotconf: falta $f — upstream reorganizó el árbol" >&2; ls -R . 2>/dev/null | head -20 >&2; exit 1; }
+done
+echo "dotconf: los 2 .c y la plantilla del .pc presentes"
+'''
+compile = '''
+set -e
+/opt/zig/zig cc -mcpu=baseline -target x86_64-linux-musl -O2 -fPIC -Isrc -c src/dotconf.c -o dotconf.o
+/opt/zig/zig cc -mcpu=baseline -target x86_64-linux-musl -O2 -fPIC -Isrc -c src/readdir.c -o readdir.o
+/opt/zig/zig ar rcs libdotconf.a dotconf.o readdir.o
+'''
+install = '''
+set -e
+mkdir -p /out/usr/lib/pkgconfig /out/usr/include
+cp libdotconf.a /out/usr/lib/
+cp src/dotconf.h /out/usr/include/
+# Sustitución de la plantilla de upstream. `sed` y no `configure`, por lo de arriba.
+sed -e 's|@prefix@|/usr|g' \
+    -e 's|@exec_prefix@|/usr|g' \
+    -e 's|@libdir@|/usr/lib|g' \
+    -e 's|@includedir@|/usr/include|g' \
+    -e 's|@VERSION@|1.4.1|g' \
+    dotconf.pc.in > /out/usr/lib/pkgconfig/dotconf.pc
+
+[ -f /out/usr/lib/libdotconf.a ] || { echo "dotconf: no quedó la .a — NO sello" >&2; exit 1; }
+[ -f /out/usr/include/dotconf.h ] || { echo "dotconf: falta include/dotconf.h" >&2; exit 1; }
+# Ningún marcador sin sustituir: un `.pc` con un `@libdir@` crudo se instala igual y el consumidor
+# recibe un `-L@libdir@` que el enlazador no entiende. Es la lección de libhangul.
+if grep -q '@[A-Za-z_]*@' /out/usr/lib/pkgconfig/dotconf.pc; then
+  echo "dotconf: quedaron marcadores sin sustituir en dotconf.pc" >&2
+  grep -n '@[A-Za-z_]*@' /out/usr/lib/pkgconfig/dotconf.pc >&2
+  exit 1
+fi
+# CHEQUEO FUERTE: con PKG_CONFIG_LIBDIR puesto, para no leer el anfitrión, y pidiendo cflags+libs
+# de verdad en vez de `--exists`, que no valida nada transitivo.
+PKG_CONFIG_LIBDIR=/out/usr/lib/pkgconfig pkgconf --cflags --libs dotconf >/dev/null 2>&1 || {
+  echo "dotconf: pkgconf no resuelve 'dotconf' — el PKG_CHECK_MODULES de speech-dispatcher fallaría" >&2
+  PKG_CONFIG_LIBDIR=/out/usr/lib/pkgconfig pkgconf --cflags --libs dotconf >&2
+  exit 1
+}
+# Y la cota de versión que el consumidor pide literalmente.
+PKG_CONFIG_LIBDIR=/out/usr/lib/pkgconfig pkgconf --atleast-version=1.3 dotconf || {
+  echo "dotconf: el .pc no satisface 'dotconf >= 1.3', que es la cota exacta de speech-dispatcher" >&2
+  exit 1
+}
+echo "dotconf: libdotconf.a (PIC) + dotconf.h + dotconf.pc $(PKG_CONFIG_LIBDIR=/out/usr/lib/pkgconfig pkgconf --modversion dotconf)"
+'''
+
+[deps]
+build = ["pkgconf"]
diff --git a/recipes/speech-dispatcher.toml b/recipes/speech-dispatcher.toml
new file mode 100644
index 00000000..15eb1f1a
--- /dev/null
+++ b/recipes/speech-dispatcher.toml
@@ -0,0 +1,139 @@
+# speech-dispatcher 0.12.1 — el SERVIDOR de voz. Es lo que convierte «la distro habla» en
+# «las aplicaciones de la distro pueden hablar».
+#
+# ══ QUÉ AÑADE SOBRE espeak-ng, QUE YA ESTÁ ═════════════════════════════════════════════════════
+# `espeak-ng` es un sintetizador: un proceso que recibe texto y hace ruido. Si cada aplicación lo
+# invoca por su cuenta, dos que hablen a la vez se pisan, nadie puede interrumpir a nadie, y cambiar
+# de voz o de velocidad es cosa de cada app. `speech-dispatcher` es la capa que falta: un daemon con
+# COLA y PRIORIDADES, una API estable (`libspeechd` en C y el módulo `speechd` en Python), y los
+# sintetizadores detrás como módulos intercambiables.
+#
+# ⇒ Es la dep que `orca` —el lector de pantalla— usa para hablar, y también lo que hace que
+# `spd-say "hola"` funcione desde cualquier script. Sin esto, el hueco de accesibilidad del censo
+# sigue abierto aunque la máquina ya tenga voz.
+#
+# ══ LO QUE PIDE, MEDIDO EN SU `configure.ac` ═══════════════════════════════════════════════════
+# De sus once `PKG_CHECK_MODULES`, **obligatorios**: `glib-2.0`, `gthread-2.0`, `gmodule-2.0`
+# (los tres los da `glib`), `sndfile` y **`dotconf >= 1.3`**. El resto va detrás de un `--with-*`.
+# `libsystemd` trae su propio fallback (`[:]` en la línea 139), que es exactamente lo que permite
+# construir esto en una distro sin systemd — no hubo que parchear nada.
+#
+# `dotconf` era la ÚNICA que el corpus no tenía, y se midió sobre el `configure.ac` y no sobre los
+# paquetes de otra distro: ver `recipes/dotconf.toml`, que se compila a mano porque upstream no
+# publica releases y el lab no tiene autoconf.
+#
+# ══ LAS PERILLAS, Y QUÉ GANA O PIERDE CADA UNA ═════════════════════════════════════════════════
+#   --disable-ltdl              **evita libltdl entero.** Su default es `check`: si encuentra
+#                               `lt_dlopen` lo usa, y si no cae a `dlopen`. Apagándolo
+#                               explícitamente usa el `dlopen` de libc, que musl trae, y el
+#                               `AC_DEFINE(USE_DLOPEN)` queda fijo en vez de depender de qué había
+#                               en el sysroot — un build que decide su propio mecanismo según el
+#                               entorno no es reproducible por construcción.
+#   --with-espeak-ng            el sintetizador que tenemos
+#   --without-espeak            el espeak VIEJO, distinto paquete; no está y no hace falta
+#   --with-pulse --with-pipewire --with-alsa
+#                               las tres salidas de audio que el corpus tiene. **El servidor de
+#                               audio de esta distro es PipeWire**, que además habla el protocolo de
+#                               PulseAudio; ALSA queda como camino directo sin servidor (un
+#                               instalador, una consola de rescate)
+#   --with-default-audio-method=pipewire
+#                               sin esto el default es «el primero que se descubra», o sea que el
+#                               orden de detección decide por nosotros
+#   --enable-python             el módulo `speechd`, que es **cómo orca habla con el daemon**.
+#                               Comprobado que no necesita setuptools ni pip: lo instala automake
+#                               plano (`python_PYTHON`), así que sólo pide el intérprete
+#   --disable-doc               la documentación en texinfo
+#
+# ⚠ **APAGADOS Y SE DICE POR QUÉ**: `--without-libao --without-oss --without-nas` (no están en el
+# corpus; `oss` y `nas` además son caminos históricos) y `--without-flite --without-ibmtts
+# --without-voxin --without-ivona --without-pico --without-baratinoo --without-kali`, que son otros
+# sintetizadores: `flite` no está en el corpus y **los otros seis son propietarios o de pago**. Con
+# espeak-ng detrás, el servidor habla 100+ idiomas sin ninguno de ellos.
+#
+# `gettext-tiny` basta acá: se comprobó que `po/` **no trae `ar.po`**, que es el fichero con el que
+# su `msgfmt` muere por los plurales del árabe (documentado en este repo).
+name    = "speech-dispatcher"
+version = "0.12.1"
+license = "GPL-2.0-or-later"
+
+[source]
+# ⚠ El asset del RELEASE, no el archive del tag: trae `configure` y `Makefile.in` ya generados, y
+# el lab no tiene autoconf ni automake. Es la misma lección que `pcaudiolib` — cuando un proyecto
+# autotools no construye por falta de autoconf, mirar si publica un tarball de release ANTES de
+# escribir el autoconf.
+tarball = "https://github.com/brailcom/speechd/releases/download/0.12.1/speech-dispatcher-0.12.1.tar.gz"
+sha256  = "b14a5238d287d2dcce4dd42bbd66ca65fa228e7e683708267f7b34036f7ba4b4"
+
+[build]
+compiler = "zig-cc"
+target   = "x86_64-linux-musl"
+link     = "dynamic"
+flags    = []
+
+[build.phases]
+configure = '''
+set -e
+[ -f ./configure ] || { echo "speech-dispatcher: el tarball no trae ./configure — ¿se cambió por el archive del tag?" >&2; exit 1; }
+# ⚠ `--sysconfdir=/etc` NO es opcional: con `--prefix=/usr` a secas, autotools lo deriva como
+# `/usr/etc` **en silencio**, y ahí fue a parar `speechd.conf` en la primera pasada. El daemon
+# hornea esa ruta, así que el artefacto es auto-consistente y nadie se entera — salvo que la
+# configuración de este paquete queda fuera de donde está la del resto de la distro, y nadie la
+# encuentra para editarla. **31 recetas de este repo ya lo pasan**: la omisión era la anomalía, no
+# una decisión.
+./configure --build=$CBUILD --host=$CHOST --prefix=/usr --sysconfdir=/etc \
+  --disable-ltdl --disable-doc --enable-python \
+  --with-espeak-ng --without-espeak \
+  --without-flite --without-ibmtts --without-voxin --without-ivona \
+  --without-pico --without-baratinoo --without-kali \
+  --with-pulse --with-pipewire --with-alsa \
+  --without-libao --without-oss --without-nas \
+  --with-default-audio-method=pipewire
+'''
+compile = 'make -j"$(nproc)"'
+# Guardián escrito contra lo que este paquete PROMETE, que es «las apps pueden hablar», y cada
+# control cubre una forma distinta de sellar algo que no cumple:
+#   · sin el daemon          no hay servidor: la API conecta a nada
+#   · sin módulo de salida   el daemon arranca, acepta texto y no suena
+#   · sin módulo espeak-ng   hay servidor y hay audio, y ninguna voz que sintetice
+#   · sin el módulo python   el daemon anda y **orca no puede usarlo**, que es el consumidor por el
+#                            que esta receta existe
+install = '''
+set -e
+make install DESTDIR=/out
+[ -x /out/usr/bin/speech-dispatcher ] || { echo "speech-dispatcher: no quedó el daemon — NO sello" >&2; exit 1; }
+[ -x /out/usr/bin/spd-say ] || { echo "speech-dispatcher: falta spd-say — es la forma de comprobar a mano que la máquina habla" >&2; exit 1; }
+# Los módulos de SÍNTESIS viven en libexec/speech-dispatcher-modules.
+md=$(find /out/usr -type d -name 'speech-dispatcher-modules' 2>/dev/null | head -1)
+[ -n "$md" ] || { echo "speech-dispatcher: no hay directorio de módulos — el daemon no tendría con qué sintetizar" >&2; find /out/usr -maxdepth 3 -type d >&2; exit 1; }
+[ -x "$md/sd_espeak-ng" ] || { echo "speech-dispatcher: falta el módulo sd_espeak-ng — habría servidor y audio, y ninguna voz" >&2; ls "$md" >&2; exit 1; }
+# La API en C, que es lo que enlaza cualquier app no-Python.
+so=$(find /out/usr/lib -name 'libspeechd.so*' -type f 2>/dev/null | head -1)
+[ -n "$so" ] || { echo "speech-dispatcher: no quedó libspeechd.so — las apps en C no podrían hablar" >&2; exit 1; }
+# Y el módulo de Python, por el que orca existe.
+py=$(find /out/usr/lib -type d -name speechd 2>/dev/null | head -1)
+[ -n "$py" ] || { echo "speech-dispatcher: no se instaló el módulo python 'speechd' — orca no podría hablar con el daemon" >&2; find /out/usr/lib -maxdepth 4 -type d -name 'python*' >&2; exit 1; }
+# ⚠ El audio por defecto, comprobado donde VIVE el contrato y no donde yo creía.
+#
+# Mi primera versión miraba `speechd.conf` buscando `pipewire`, y eso estaba mal por dos razones a
+# la vez: la config caía en `/usr/etc` (ver la nota del `--sysconfdir` arriba), y **el `.conf` no es
+# el contrato** — upstream deja la línea COMENTADA a propósito, como documentación del default, y
+# el valor real lo hornea el `configure`. Medido en el binario: de los cinco métodos posibles
+# (`pipewire pulse alsa oss nas`) el daemon sólo contiene la cadena `pipewire`.
+#
+# ⇒ Se comprueba el BINARIO, que es quien decide, y además que la config esté en /etc, que es lo
+# que permite editarla. Un aviso blando que nadie lee no es un control; esto falla.
+[ -f /out/etc/speech-dispatcher/speechd.conf ] || { echo "speech-dispatcher: speechd.conf no está en /etc — autotools lo derivó a otro sitio y nadie la encontraría" >&2; find /out -name 'speechd.conf' >&2; exit 1; }
+strings /out/usr/bin/speech-dispatcher 2>/dev/null | grep -qx 'pipewire' || { echo "speech-dispatcher: el daemon no trae 'pipewire' como método de audio — el default sería «el primero que se descubra»" >&2; strings /out/usr/bin/speech-dispatcher 2>/dev/null | grep -xE 'pulse|alsa|oss|nas' >&2; exit 1; }
+n=$(ls "$md" 2>/dev/null | wc -l)
+echo "speech-dispatcher: daemon + spd-say + $n módulo(s) + libspeechd.so + módulo python"
+'''
+
+[deps]
+build = ["make", "pkgconf", "dotconf", "glib", "libsndfile", "espeak-ng",
+         "pulseaudio", "pipewire", "alsa-lib", "python3",
+         "pcre2", "libffi", "zlib", "gettext-tiny", "libtool",
+         # ⚠ `binutils` está por el GUARDIÁN y no por el build: aporta `strings`, que es con lo que
+         # se comprueba el método de audio horneado en el daemon. Sin declararlo el control fallaría
+         # por falta de herramienta en vez de por el defecto que busca — y acusaría a la receta de
+         # algo que no hizo. Es la misma nota que lleva `espeak-ng`.
+         "binutils"]