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 Definiciones

⎇ 534014c3ec69daea789a1d0523e1d00a69c6ed65 recipes/fcitx5.toml TOML · 290 líneas · 21.0 KB Historia crudo

fcitx5: el quinto requisito del salto no salía de ningún CMakeLists 019761cad · Sergio ·

1# fcitx5 — el marco de métodos de entrada. Lo que falta para ESCRIBIR en chino, japonés y coreano.
2#
3# ══ POR QUÉ ENTRA ══════════════════════════════════════════════════════════════════════════════
4# El 2026-10-19 entraron las fuentes Noto (`noto-fonts`, `noto-fonts-cjk`, `noto-fonts-emoji`), así
5# que la distro pasó de mostrar cuadraditos a LEER esos alfabetos. Escribirlos es otra cosa: un
6# teclado tiene cien teclas y el chino tiene decenas de miles de caracteres, así que hace falta un
7# motor que convierta lo que se teclea en lo que se escribe. `censo-distro.py` marcaba la categoría
8# «escribir en esos idiomas» 5 de 6 ausente.
9#
10# ══ LO QUE SE MIDIÓ ANTES DE ESCRIBIR ESTO ═════════════════════════════════════════════════════
11#
12# **1. NO hay muro estructural, y era la primera duda.** El camino de un IME en Wayland son dos
13# protocolos que el COMPOSITOR tiene que implementar; si no están, ningún paquete sirve y esto
14# habría sido trabajo tirado (como `kitty`, que no entra por falta de GL de escritorio).
15# `mirada-compositor` los registra los dos — `setup.rs:600-602`:
16#     smithay::wayland::text_input::TextInputManagerState   ⇒ zwp_text_input_v3
17#     smithay::wayland::input_method::InputMethodManagerState ⇒ zwp_input_method_v2
18#
19# **2. Las dos deps que faltaban eran de COLA, no inexistentes.** `extra-cmake-modules` vivía en
20# `incoming-kde` y `iso-codes` en `incoming-gnome`, y una receta del corpus resuelve sibling-first y
21# después el catálogo padre — **nunca una cola hermana**. Se promovieron al corpus en el commit
22# anterior, verificando que es gratis: los 109 dependientes de ecm en incoming-kde conservan su hash
23# uno por uno.
24#
25# **3. Cinco deps se caen con dos perillas.** El `CMakeLists.txt` de upstream mete XCB y
26# `XCBImdkit` como REQUIRED **sólo dentro de `if(ENABLE_X11)`** (líneas 138-139), y `enchant2` sólo
27# con `ENABLE_ENCHANT`. Con las dos en Off desaparecen `xcb-imdkit`, `xcb-util-keysyms`,
28# `xcb-util-wm`, `libxkbfile` y `enchant2`. Y apagar X11 no es un recorte: **esta distro es
29# Wayland-only**, el X11 de fcitx5 sería código muerto.
30#
31# ══ LAS PERILLAS, UNA POR UNA ══════════════════════════════════════════════════════════════════
32#   ENABLE_X11=Off         Wayland-only por decisión de distro; se lleva cuatro deps
33#   ENABLE_ENCHANT=Off     predicción de palabras por diccionario: no la pide nadie y trae enchant2
34#   USE_SYSTEMD=Off        acá el init es `arje-zero`. Upstream dice que cae a libuv/libdbus, que SÍ
35#                          están — dejarlo en Auto haría que el build busque algo que no existe
36#   ENABLE_TEST=Off        el test suite de fcitx5 no se corre acá.
37#   ENABLE_TESTING_ADDONS=On  ⚠ **ESTABA EN Off Y ROMPÍA A SUS CONSUMIDORES — corregido 2026-10-20.**
38#                          Su `option()` dice «Build testing frontend, im and ui addons», que se lee
39#                          como «cosas de test, fuera», y así lo puse. Pero además de los addons
40#                          instala el MÓDULO DE CMAKE que los consumidores piden: `fcitx5-hangul`
41#                          hace `find_package(Fcitx5Module REQUIRED COMPONENTS TestFrontend)` en su
42#                          CMakeLists:15, **sin condición**, y murió con
43#                            Could not find … "Fcitx5ModuleTestFrontend" …
44#                            Fcitx5ModuleTestFrontendConfig.cmake
45#                          O sea que no es opcional desde fuera: todo motor de fcitx5 lo exige, y
46#                          apagarlo deja un marco que no puede tener motores. La lección es más
47#                          general que esta receta: **una perilla que se llama «testing» puede
48#                          publicar interfaz, y la descripción del `option()` no lo dice.** Medir
49#                          qué deja de instalarse, no leer cómo se llama.
50#                          Coste: re-sellar fcitx5, radio 1 (sólo fcitx5-hangul cuelga de él).
51#   ENABLE_DOC=Off         doxygen no está en el corpus
52#   BUILD_SPELL_DICT=Off   construye `en_dict.fscd` para el corrector inglés — depende de enchant
53#   ENABLE_LIBUUID=Off     ⚠ ÉSTA ES UNA DECISIÓN DE ARQUITECTURA DEL CORPUS, no una perilla más.
54#                          `libuuid.a` (de util-linux) no es PIC, así que `libFcitx5Utils.so` no
55#                          puede enlazarlo:
56#                            ld.lld: relocation R_X86_64_32 … recompile with -fPIC
57#                            >>> libuuid_la-randutils.o … in archive /usr/lib/libuuid.a
58#                          Arreglar la librería —como se hizo con `libuv`, radio 1— acá NO se paga:
59#                          `util-linux` tiene **radio 79 y afecta a `escritorio-kde`**, y re-sellarlo
60#                          tira a deuda lo que cuelga de él. Es el precedente de `libpng` del
61#                          2026-08-21, donde el usuario decidió no PIC-ificar el canónico porque «es
62#                          estático por diseño; eso es arquitectura del corpus». ⇒ se arregla el
63#                          CONSUMIDOR.
64#                          Y el fallback es bueno en Linux, no un parche: `uuid_p.h:103` usa
65#                          `generateUUIDFallback("/proc/sys/kernel/random/uuid")`, o sea el UUID lo
66#                          genera el KERNEL. (El aviso del comentario de upstream sobre un «dummy
67#                          self increment» es para el camino NO-Linux, que acá no se toma.)
68#                          Apagarlo también desactiva el frontend de ibus, que no usamos: el camino
69#                          de esta distro es el de Wayland.
70#   PKG_CONFIG_ARGN=--static  ⚠ **LA PERILLA QUE UN CORPUS ESTÁTICO NECESITA, y la descubrí tarde.**
71#                          Con el trío de fuentes ya PIC, el enlace de `libclassicui.so` avanzó y
72#                          murió en otra cosa:
73#                            ld.lld: error: undefined symbol: XML_ParserCreate
74#                            >>> fcxml.o:(FcConfigParseAndLoad…) in archive /usr/lib/libfontconfig.a
75#                          `XML_ParserCreate` es de expat, y expat ESTABA declarado en `[deps]`: lo
76#                          que faltaba no era el paquete sino el `-lexpat` en la línea de enlace.
77#                          Medido en el `.pc`:
78#                            fontconfig.pc →  Requires.private: expat
79#                          y pkg-config entrega los `Requires.private`/`Libs.private` **sólo en modo
80#                          estático**. Sin `--static`, cmake pide las libs como si fueran compartidas
81#                          —donde la `.so` ya trae resueltas sus privadas— y para un `.a` eso es
82#                          justamente lo que falta. `PKG_CONFIG_ARGN` es la variable que el módulo
83#                          `FindPkgConfig` de cmake pasa a pkg-config, y estaba vacía en el cache.
84#                          ⇒ Es la cura de raíz para este corpus, no un `-lexpat` a mano: toda dep
85#                          estática con privadas se resuelve por el mismo camino.
86#
87# ⚠ `link = "dynamic"` **y no es opcional**: fcitx5 es un daemon cuyos métodos de entrada son
88# addons que carga con `dlopen` en tiempo de ejecución (`libexec/` + `lib/fcitx5/`). Un binario
89# `crt-static` no puede dlopen, así que estático sellaría un daemon incapaz de cargar un solo motor
90# — sellado, con contenido, reproducible e inerte.
91#
92# ══ ⚠ ESTO ES EL MARCO, NO UN MÉTODO DE ENTRADA ════════════════════════════════════════════════
93# fcitx5 solo no escribe chino: es la plomería (el daemon, el protocolo, la UI de la barra de
94# candidatos). Cada idioma es un addon APARTE y son recetas que todavía no existen:
95#     fcitx5-chinese-addons  pinyin        (arrastra libime y libpinyin)
96#     fcitx5-anthy           japonés       (arrastra anthy)
97#     fcitx5-hangul          coreano       (arrastra libhangul)
98#     fcitx5-rime            los tres      (arrastra librime, que es grande)
99# Declarar sólo esto en un perfil daría una barra de IME que no convierte nada. **Por eso NO se
100# declara en ningún perfil en este commit**: primero el marco construye, después un motor, y recién
101# entonces el par se declara junto. La alternativa —declarar el marco ya— es exactamente la figura
102# de `crun` sin podman y de `cups` sin filtros que este catálogo tiene documentada tres veces.
103# ⚠ **SUBE A 5.1.21, Y ESA VERSIÓN EXACTA ES EL RESULTADO DE UNA MEDICIÓN — 2026-10-20.**
104# Upstream va por 5.1.23. El techo que bloqueaba el chino era `librsvg-2.0` (REQUIRED dentro de
105# `if (ENABLE_X11 OR ENABLE_WAYLAND)`, o sea inevitable acá), y la pregunta que no me había hecho
106# era **en qué versión entró**. Medido tag a tag:
107#
108#     fcitx5    librsvg   yoga   nlohmann
109#     5.1.20      no       sí      sí
110#     5.1.21      no       sí      sí      ← acá
111#     5.1.22      SÍ       sí      sí
112#     5.1.23      SÍ       sí      sí
113#
114# `libime` pide `Fcitx5Utils ≥5.1.20` y `librsvg` entró en **5.1.22** ⇒ **5.1.21 es la única versión
115# que destraba el motor de pinyin sin tocar nada de GNOME.** Y `fcitx5-chinese-addons` encaja en la
116# misma ventana: su 5.1.15 exige el marco ≥5.1.22, pero su **5.1.13** pide ≥5.1.20 (y libime
117# ≥1.1.14, que nuestro 1.1.17 cumple). Dos rangos que se solapan en un punto, y ese punto es éste.
118#
119# ⚠ **LA RUTA POR GNOME QUE ESTO EVITA, CON SU COSTE MEDIDO** — promoví y revertí de verdad, no lo
120# estimé: las 7 recetas de la raíz de introspección cambiaban **5 de sus 7 hashes** y los
121# dependientes de GNOME **los 35**. La causa no es «faltaba promover unas recetas»: la cola de GNOME
122# es un **mundo paralelo dinámico+introspectado**. Su `glib` es `default_library=both` contra la
123# `static` del corpus —misma versión 2.88.1, otro hash—, y `glib-introspected` es una **SEGUNDA
124# pasada** de glib que depende de la primera Y de `gobject-introspection`, con **31 recetas de la
125# cola colgando de ella**. Promover esa raíz la haría construir contra la glib ESTÁTICA del corpus.
126# Esa decisión sigue viva, pero **ya sólo para `orca`** (`at-spi2-core` + `py3-gobject`), no para el
127# chino.
128#
129# ══ LOS CUATRO REQUISITOS NUEVOS DEL SALTO 5.1.14 → 5.1.21 ═════════════════════════════════════
130# Comparando los `find_package`/`pkg_check_modules` de los dos CMakeLists, no de los makedepends de
131# otra distro — que es el error que cometí inventando una dep `fmt` para esta misma receta:
132#   · `nlohmann_json`                ya en el corpus (3.12.0) — y **reemplaza a `JsonC`**, que sale
133#                                    de las deps porque 5.1.21 ya no lo busca
134#   · `WaylandProtocols ≥1.46`       ya cubierto (tenemos 1.48)
135#   · `PlasmaWaylandProtocols ≥1.20` **se apaga**. Sólo existe en `incoming-kde`, pero no hace falta
136#                                    traerlo: con `USE_SYSTEM_PLASMA_WAYLAND_PROTOCOLS=Off` la rama
137#                                    `else()` usa su XML **vendorizado en el árbol** (20 KB,
138#                                    comprobado presente en el tarball). Leer el `else()` antes de
139#                                    escribir una receta: tercera vez que esa costumbre ahorra una.
140#   · `yoga`                         **receta nueva** — es un SUBMÓDULO git y el tarball lo trae
141#                                    vacío (el único directorio vacío del tarball), así que
142#                                    `USE_SYSTEM_YOGA=On` no es una preferencia: sin él
143#                                    `add_subdirectory(third_party/yoga/yoga)` falla al CONFIGURAR.
144
145# ══ ⚠ Y UN QUINTO REQUISITO QUE NO SALE DE NINGÚN CMakeLists: `std::osyncstream` ════════════════
146# Los cuatro de arriba se encuentran comparando los dos CMakeLists. **Éste no se ve ahí y mató el
147# build**: `src/lib/fcitx-utils/log.cpp` usa `std::osyncstream` (C++20, salida sincronizada), y el
148# compilador cortó con
149#
150#     log.cpp:38:30: error: no type named 'osyncstream' in namespace 'std'
151#
152# Entró en **5.1.20** —o sea en TODAS las versiones de la ventana, no hay un tag que lo evite— y no
153# estaba en 5.1.14, que es por lo que esta receta nunca lo había pisado.
154#
155# ⚠ **Lo primero que supuse era falso: no es que falte el header.** `<syncstream>` está en la libc++
156# de zig. Lo que pasa es que la clase está VALLADA (`__config:54`):
157#
158#     #if defined(_LIBCPP_ENABLE_EXPERIMENTAL) || defined(_LIBCPP_BUILDING_LIBRARY)
159#     #  define _LIBCPP_HAS_EXPERIMENTAL_LIBRARY 1
160#     …
161#     #define _LIBCPP_HAS_EXPERIMENTAL_SYNCSTREAM _LIBCPP_HAS_EXPERIMENTAL_LIBRARY
162#
163# y `<syncstream>` sólo declara `basic_osyncstream` si ese macro está en 1. ⇒ la cura es **un
164# define**, no un parche a la fuente ni una biblioteca extra: comprobado que el header no trae
165# ninguna declaración fuera de línea (`_LIBCPP_EXPORTED_FROM_ABI`, `extern template`), así que es
166# header-only y **no hace falta `-lc++experimental`**.
167#
168# Es la familia de [[cxx-runtime-debe-coincidir]]: zig trae libc++, y libc++ marca `<syncstream>`
169# como experimental por estabilidad de ABI en su dylib — una preocupación que acá no aplica, porque
170# cada artefacto enlaza su runtime y no compartimos una libc++ de sistema.
171#
172# ✅ **Y el define va SÓLO acá, que es el dato que evita cinco recetas tocadas**: se comprobó que
173# `osyncstream` aparece **únicamente** en `log.cpp` y en NINGUNA cabecera pública (`log.h` incluida),
174# así que `fcitx5-hangul`, `fcitx5-anthy`, `libime` y los addons compilan contra las cabeceras
175# instaladas sin enterarse. Si algún día upstream lo subiera a un header, el fallo saldría en los
176# consumidores y no acá — y esta nota es lo que haría reconocerlo.
177#
178# Lo que este salto arrastra: `fcitx5-hangul` y `fcitx5-anthy` rearman, porque dependen del marco.
179# Son dos recetas del corpus y el coreano y el japonés ya estaban sellados — se reconstruyen y se
180# vuelve a comprobar que sigan dando su addon.
181name    = "fcitx5"
182version = "5.1.21"
183license = "LGPL-2.1-or-later"
184
185[source]
186tarball = "https://github.com/fcitx/fcitx5/archive/refs/tags/5.1.21/fcitx5-5.1.21.tar.gz"
187sha256  = "8211fe5996db22254e5df9617cbd45873ae7fab82e7e0c42bde5a197299d1276"
188
189[build]
190compiler = "zig-cc"
191target   = "x86_64-linux-musl"
192link     = "dynamic"
193flags    = []
194
195[build.phases]
196configure = '''
197ZW="$PWD/.zwrap"; mkdir -p "$ZW"
198printf '#!/bin/sh\nexec /opt/zig/zig cc -mcpu=baseline "$@"\n'  > "$ZW/cc"
199printf '#!/bin/sh\nexec /opt/zig/zig c++ -mcpu=baseline "$@"\n' > "$ZW/cxx"
200chmod +x "$ZW/cc" "$ZW/cxx"
201# ⚠ **BORRAR `build/` ANTES DE CONFIGURAR, Y NO ES HIGIENE: ES CORRECCIÓN.** El árbol de fuentes
202# (`work/sources/<receta>-<sha>`) se REUSA entre builds — es caché por diseño—, así que un `build/`
203# de un intento anterior sobrevive con su `CMakeCache.txt`. Y `pkg_check_modules` cachea su
204# resultado: en la segunda corrida no vuelve a preguntar a pkg-config y **no recrea el target
205# importado**, así que `PkgConfig::Cairo` sigue ausente aunque el `.pc` ya resuelva.
206#
207# Medido el 2026-10-19 y cuesta un viaje entero: tras arreglar las deps que faltaban de verdad, el
208# build volvió a morir con EL MISMO error en **2 segundos** —«Generating done (0.0s)»— reusando el
209# cache del fallo anterior. Un primer fallo envenena todos los reintentos y el mensaje no lo dice:
210# es la figura de [[cache-congela-regresiones]] dentro de cmake.
211#
212# ⚠ Y esto NO es particular de fcitx5: toda receta de este repo que haga `cmake -B build` sin
213# limpiar tiene el mismo defecto latente. No se nota mientras el primer intento salga bien.
214rm -rf build
215cmake -B build -G Ninja -Wno-dev \
216  -DCMAKE_C_COMPILER="$ZW/cc" -DCMAKE_CXX_COMPILER="$ZW/cxx" -DCMAKE_ASM_COMPILER="$ZW/cc" \
217  -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr \
218  -DENABLE_X11=Off -DENABLE_ENCHANT=Off -DUSE_SYSTEMD=Off \
219  -DENABLE_TEST=Off -DENABLE_TESTING_ADDONS=On -DENABLE_DOC=Off \
220  -DBUILD_SPELL_DICT=Off -DENABLE_LIBUUID=Off \
221  -DUSE_SYSTEM_YOGA=On -DUSE_SYSTEM_PLASMA_WAYLAND_PROTOCOLS=Off \
222  -DCMAKE_CXX_FLAGS=-D_LIBCPP_ENABLE_EXPERIMENTAL \
223  -DPKG_CONFIG_ARGN=--static
224'''
225compile = "cmake --build build"
226# El guardián de presencia, por la regla 3: lo que tiene que quedar es el DAEMON y su directorio de
227# addons. Un `cmake --install` que deje las cabeceras y los `.cmake` y ningún binario sellaría un
228# artefacto con contenido y sin función — y eso llega hasta el final diciendo que todo fue bien.
229install = '''
230set -e
231DESTDIR=/out cmake --install build
232[ -x /out/usr/bin/fcitx5 ] || { echo "fcitx5: no quedó /usr/bin/fcitx5 — NO sello un marco sin daemon" >&2; exit 1; }
233n=$(find /out/usr/lib/fcitx5 -name '*.so' 2>/dev/null | wc -l)
234echo "fcitx5: daemon + $n addon(s) de fábrica"
235[ "$n" -gt 0 ] || { echo "fcitx5: cero addons en /usr/lib/fcitx5 — el daemon no podría cargar ni su propia UI" >&2; exit 1; }
236'''
237
238[deps]
239# `iso-codes` y `extra-cmake-modules` son las dos promovidas al corpus para esto.
240# `util-linux` por `libuuid` (`find_package(LibUUID REQUIRED)`), `gettext-tiny` por `LibIntl`.
241#
242# ⚠ **EL CIERRE DE `.pc` NO ES EL DE LAS BIBLIOTECAS, Y MI PRIMERA LISTA LO IGNORÓ.** Declaré
243# `cairo`, `pango` y `glib`… bueno, `glib` ni eso, y el build murió al GENERAR —no al configurar—
244# con `Target "classicui" links to PkgConfig::Cairo but the target was not found`.
245#
246# La causa tiene dos mitades y las dos valen la pena:
247#
248#   1. `pkg_check_modules(Cairo IMPORTED_TARGET cairo)` va **SIN `REQUIRED`** (CMakeLists:162) y lo
249#      mismo `Gio` (:173). Sin REQUIRED, pkg-config que no encuentra el módulo **no falla**: no crea
250#      el target, el configure dice «Configuring done» y el error sale después, en `Generating`,
251#      nombrando un target ausente en vez del `.pc` que falta. Dos errores distintos (Cairo y Gio)
252#      con UNA sola causa, y ninguno nombra la causa.
253#   2. Un `.pc` falla si sus `Requires:` TRANSITIVOS no están. Medido en el artefacto:
254#        cairo.pc      Requires: zlib, libpng, fontconfig, freetype2, pixman-1
255#        pangocairo.pc Requires: … fribidi, harfbuzz, fontconfig, freetype2, cairo
256#        gio-2.0.pc    Requires: glib-2.0, gobject-2.0, gmodule-no-export-2.0, zlib
257#      Yo tenía `cairo` y `zlib`, y me faltaban los otros cuatro de cairo. Declarar la biblioteca
258#      que uno usa no alcanza: hay que declarar el cierre de su `.pc`.
259#
260# ⚠ **Y EL CIERRE TIENE MÁS DE UN NIVEL: `glib-2.0.pc` exige `libpcre2-8`.** El build volvió a
261# morir con el MISMO mensaje de target ausente, y el motivo real estaba cuatro líneas más arriba en
262# el log del configure, sin llamar la atención:
263#     -- Checking for module 'cairo'            → Found cairo, version 1.18.4
264#     -- Checking for modules 'pango;pangocairo' → Package 'libpcre2-8', required by 'glib-2.0', not found
265# O sea: cairo SÍ resolvía, y lo que faltaba era `pcre2`, dos saltos por debajo. `pkg_check_modules`
266# sin REQUIRED no grita, así que el único síntoma es el target que no existe.
267#
268# ⚠⚠ **Y MI PRIMERA COMPROBACIÓN DIO UN FALSO VERDE, por dos errores de método a la vez:**
269#   1. Usé `pkgconf --exists`, que NO valida los `Requires:` transitivos. Lo que hace cmake es
270#      `--cflags --libs`, que sí. Hay que preguntar lo mismo que pregunta el que falla.
271#   2. Exporté sólo `PKG_CONFIG_PATH` y no `PKG_CONFIG_LIBDIR`. Sin LIBDIR, pkgconf **además**
272#      busca en el camino del SISTEMA — y esta jaula es una imagen de Arch que SÍ trae pcre2. La
273#      comprobación estaba leyendo el anfitrión. Es exactamente el apagón que `jaula-sysroot.sh`
274#      documenta («con el sysroot a medias eso "andaba" porque lld caía al /usr/lib de Arch»).
275#
276# ⇒ Entran `glib`, `libpng`, `fontconfig`, `freetype`, `pixman`, `fribidi`, `harfbuzz`, `pcre2`,
277# `libffi`. Verificado con el chequeo FUERTE —`PKG_CONFIG_LIBDIR` puesto y `--cflags --libs`— sobre
278# los doce módulos que este build pide: `cairo`, `gio-2.0`, `gio-unix-2.0`, `pango`, `pangocairo`,
279# `gdk-pixbuf-2.0`, `wayland-client`, `wayland-cursor`, `xkbcommon`, `dbus-1`, `libuv`, `json-c`.
280# Los doce resuelven, cero fallan.
281#
282# ⚠ Esa lista de doce es de la **5.1.14** y se deja como el registro de lo que se midió entonces. En
283# **5.1.21 son once**: `json-c` desaparece, reemplazado por `nlohmann_json`, que es *headers-only* y
284# se encuentra con `find_package` y no con pkg-config — o sea que no entra en esa comprobación.
285build = ["cmake", "samurai", "extra-cmake-modules", "pkgconf", "iso-codes", "xkeyboard-config",
286         "libxkbcommon", "wayland", "wayland-protocols", "cairo", "pango", "gdk-pixbuf",
287         "glib", "libpng", "fontconfig", "freetype", "pixman", "fribidi", "harfbuzz",
288         "pcre2", "libffi",
289         "dbus", "libuv", "nlohmann-json", "expat", "zlib", "gettext-tiny", "util-linux",
290         "libxml2", "yoga", "python3"]