# fcitx5-chinese-addons 5.1.13 — el TERCER método de entrada. Con esto se escribe chino. # # ══ LA VERSIÓN ES EL RESULTADO DE UNA MEDICIÓN, NO LA ÚLTIMA ═══════════════════════════════════ # Upstream va por **5.1.15**, y su `CMakeLists.txt` fija `REQUIRED_FCITX_VERSION 5.1.22`. Pero # fcitx5 ≥5.1.22 exige `librsvg-2.0`, que sólo existe en `incoming-gnome` enredado con la pila de # introspección de esa cola, y promoverla cuesta **35 recetas de GNOME rearmando** (medido # promoviendo y revirtiendo de verdad; la nota larga está en `recipes/fcitx5.toml`). # # Entonces se buscó hacia atrás, tag por tag, en vez de dar el techo por bueno: # # addons pide fcitx5 pide libime # 5.1.15 5.1.22 1.1.14 # 5.1.14 5.1.22 1.1.14 # 5.1.13 **5.1.20** 1.1.14 ← acá # 5.1.12 5.1.13 1.1.13 # # Y `librsvg` entró en fcitx5 **5.1.22**, así que `5.1.21` lo cumple sin pedirla. Nuestro `libime` # es 1.1.17 ≥ 1.1.14. **Dos rangos que se solapan en un punto**, y ese punto da el chino sin tocar # ni una receta de GNOME. La lección de método: *un techo de versión se mide en los dos extremos # antes de aceptarlo como coste.* # # ══ ⚠ EL PARCHE DE `pinyinhelper`: DOS DESCARGAS SIN PERILLA ════════════════════════════════════ # `modules/pinyinhelper/CMakeLists.txt` baja dos tarballs con `fcitx5_download`, y el módulo entra # **incondicionalmente** desde `modules/CMakeLists.txt` — no hay `option()` que lo evite. El sandbox # no tiene red, así que eso mata el build. # # Lo que decide la forma del parche es **cuándo** se usa el dato, y se comprobó en la fuente: # `stroke.cpp:47` y `pinyinlookup.cpp:138` lo buscan como `"pinyinhelper/py_stroke.mb"` por la vía # de `StandardPath` — o sea **en EJECUCIÓN, no al compilar**. ⇒ el addon no necesita el fichero para # construirse: le basta encontrarlo en la imagen. # # ⇒ Las descargas, las extracciones y los `install(FILES …)` de esos dos `.mb` se borran del # CMakeLists, y los datos llegan por `recipes/fcitx5-pinyin-stroke.toml` y # `fcitx5-pinyin-table.toml`, declaradas en `[deps] runtime`. Si en vez de eso hubiera dejado el # `install`, el mismo fichero viajaría en DOS artefactos a la misma ruta. # # Cada `sed` se COMPRUEBA antes: si upstream mueve una de esas líneas, el configure falla diciendo # cuál, en vez de volver a intentar una descarga que no puede funcionar. # # ══ LAS PERILLAS, Y QUÉ SE PIERDE CON CADA UNA ═════════════════════════════════════════════════ # ENABLE_OPENCC=On simplificado ↔ tradicional. **Encendida**: `recipes/opencc.toml` es # autocontenida (vendoriza sus 4 deps) así que el tradicional sale por una # sola receta. Apagarla dejaría la mitad del chino escrito afuera. # ENABLE_DATA=On compila `chaizi.txt`, que viene EN el árbol — no baja nada. Es el # diccionario de descomposición de caracteres. # ENABLE_TOOLS=On `scel2org5`: convierte diccionarios de Sogou a texto. Pequeño, sin deps # nuevas, y es cómo alguien importa su propio vocabulario. # ENABLE_GUI=Off la ventana de configuración, que es Qt (`Qt Concurrent` + # `Fcitx5QtWidgetsAddons`). No tenemos `fcitx5-qt` en el corpus; la # configuración se edita por fichero. **Esto es una pérdida real y queda # anotada**, no disimulada: el usuario no tiene diálogo para elegir # disposición de teclado ni diccionarios. # ENABLE_BROWSER=Off necesita `QtWebEngineWidgets`, que es Chromium entero. No. # ENABLE_CLOUDPINYIN=Off **decisión, no falta de dep** (sólo pedía `libcurl`, que está): el # «pinyin en la nube» manda lo que teclea el usuario a un servidor remoto # para mejorar los candidatos. En una distro que se construye entera desde # fuente para ser auditable, eso no se enciende por defecto. # ENABLE_TEST=Off su `option()` viene en On y no se corre la suite acá. # # `Boost 1.61 OPTIONAL_COMPONENTS json`: nuestro boost compila `iostreams` y no `json`. Es # OPCIONAL —`if (TARGET Boost::json)`— así que no falla; lo que se pierde es una ruta de importación # de diccionarios en formato json. name = "fcitx5-chinese-addons" version = "5.1.13" license = "GPL-2.0-or-later" [source] tarball = "https://github.com/fcitx/fcitx5-chinese-addons/archive/refs/tags/5.1.13/fcitx5-chinese-addons-5.1.13.tar.gz" sha256 = "ee056e7ac31f5f9c242cee43f7ad8605e2cc188a7bbcc46f869332a49058244a" [build] compiler = "zig-cc" target = "x86_64-linux-musl" link = "dynamic" flags = [] [build.phases] configure = ''' set -e ZW="$PWD/.zwrap"; mkdir -p "$ZW" printf '#!/bin/sh\nexec /opt/zig/zig cc -mcpu=baseline "$@"\n' > "$ZW/cc" printf '#!/bin/sh\nexec /opt/zig/zig c++ -mcpu=baseline "$@"\n' > "$ZW/cxx" chmod +x "$ZW/cc" "$ZW/cxx" # ── El parche de pinyinhelper. Se comprueba ANTES de aplicar, por fichero y por marcador. PH=modules/pinyinhelper/CMakeLists.txt [ -f "$PH" ] || { echo "fcitx5-chinese-addons: no existe $PH — upstream reorganizó el árbol" >&2; exit 1; } for marca in fcitx5_download fcitx5_extract; do grep -q "$marca" "$PH" || { echo "fcitx5-chinese-addons: $PH ya no usa $marca — el parche de red sobra o cambió de forma; REVISAR antes de seguir" >&2; exit 1; } done # Las llamadas ocupan varias líneas (abren paréntesis y lo cierran más abajo), así que se borran por # RANGO desde la llamada hasta su línea de cierre `)`, no por línea suelta. python3 - "$PH" <<'PYEOF' import sys, re p = sys.argv[1] s = open(p, encoding='utf-8').read() # Fuera download/extract (bajan por red) y los install de los .mb (los entregan las recetas de datos) # ⚠ Los `set(PY_*_URL …)` TAMBIÉN se van, y eso lo descubrió el propio control de salida de esta # receta: quité las llamadas y dejé las URLs, así que el `grep` final denunció que el fichero # «todavía referencia la red tras el parche» y abortó. Hizo exactamente lo que tenía que hacer — # un parche a medias habría sellado un artefacto que no baja nada por casualidad, no por diseño. patrones = [ r'fcitx5_download\([^)]*\)\n', r'fcitx5_extract\([^)]*\)\n', r'set\(PY_(?:STROKE|TABLE)_URL\s*[^)]*\)\n', r'install\(FILES "\$\{PY_(?:STROKE|TABLE)_TGT\}"[^)]*\)\n', ] for pat in patrones: s, n = re.subn(pat, '', s) if n == 0: sys.exit(f"fcitx5-chinese-addons: el patrón {pat!r} no coincidió con nada en {p}") open(p, 'w', encoding='utf-8').write(s) PYEOF # Comprobación de salida: que no quede NI UNA referencia a la red en ese fichero. grep -qE 'fcitx5_download|fcitx5_extract|download\.fcitx-im\.org' "$PH" && { echo "fcitx5-chinese-addons: $PH todavía referencia la red tras el parche" >&2; grep -nE 'fcitx5_download|fcitx5_extract|download' "$PH" >&2; exit 1; } echo "fcitx5-chinese-addons: pinyinhelper sin descargas" # ⚠ **PARCHE 2: una libime ESTÁTICA exige que el consumidor provea su interfaz de enlace.** # `recipes/libime.toml` construye con `BUILD_SHARED_LIBS=Off` (por el segfault de las cuatro copias # de libc++; la nota larga está allá). Consecuencia directa: en una biblioteca estática cmake NO # resuelve las deps, así que **incluso las `PRIVATE` quedan en la interfaz exportada** como # `$` — y los `*Targets.cmake` instalados nombran targets importados que ESTA receta # tiene que definir antes de buscarlos: # # CMake Error at /usr/lib/cmake/LibIMECore/LibIMECoreTargets.cmake:70 (set_target_properties): # The link interface of target "LibIME::kenlm" contains: PkgConfig::ZSTD # but the target was not found. # # libime nombra `PkgConfig::ZSTD` en cuatro targets (`kenlm`, `IMECore`, `IMEPinyin`, `IMETable`) y # `Boost::iostreams` en tres. Con `.so` nada de eso salía al exterior; con `.a` sí. # # ⇒ Se parchea ACÁ y no en libime a propósito: que una estática pida su interfaz es correcto, no un # defecto. Lo que faltaba es que el consumidor la declare. Dos cambios, cada uno comprobado: # · `pkg_check_modules(ZSTD … IMPORTED_TARGET libzstd)` ANTES del `find_package(LibIMEPinyin…)` # · el `find_package(Boost …)` ya existente pasa a pedir el componente `iostreams` CA=CMakeLists.txt grep -q 'find_package(LibIMEPinyin' "$CA" || { echo "fcitx5-chinese-addons: no encuentro find_package(LibIMEPinyin …) donde insertar el target de zstd" >&2; exit 1; } sed -i 's|^find_package(LibIMEPinyin|pkg_check_modules(ZSTD REQUIRED IMPORTED_TARGET "libzstd")\nfind_package(LibIMEPinyin|' "$CA" grep -q 'pkg_check_modules(ZSTD REQUIRED IMPORTED_TARGET' "$CA" || { echo "fcitx5-chinese-addons: el parche del target PkgConfig::ZSTD no se aplicó" >&2; exit 1; } grep -q 'find_package(Boost 1.61 OPTIONAL_COMPONENTS json)' "$CA" || { echo "fcitx5-chinese-addons: el find_package(Boost …) no tiene la forma esperada; REVISAR" >&2; grep -n 'find_package(Boost' "$CA" >&2; exit 1; } sed -i 's|find_package(Boost 1.61 OPTIONAL_COMPONENTS json)|find_package(Boost 1.61 REQUIRED CONFIG COMPONENTS iostreams OPTIONAL_COMPONENTS json)|' "$CA" grep -q 'COMPONENTS iostreams' "$CA" || { echo "fcitx5-chinese-addons: el parche del componente iostreams no se aplicó" >&2; exit 1; } echo "fcitx5-chinese-addons: interfaz de enlace de la libime estática declarada (zstd + boost iostreams)" rm -rf build cmake -B build -G Ninja -Wno-dev \ -DCMAKE_C_COMPILER="$ZW/cc" -DCMAKE_CXX_COMPILER="$ZW/cxx" \ -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr \ -DCMAKE_INSTALL_LIBDIR=lib \ -DPKG_CONFIG_ARGN=--static \ -DBoost_USE_STATIC_LIBS=ON -DBoost_USE_STATIC_RUNTIME=ON \ -DENABLE_OPENCC=On -DENABLE_DATA=On -DENABLE_TOOLS=On \ -DENABLE_GUI=Off -DENABLE_BROWSER=Off -DENABLE_CLOUDPINYIN=Off \ -DENABLE_TEST=Off ''' compile = "cmake --build build" # Guardián por SUBCADENA y no por nombre exacto: la convención de fcitx5 es `lib.so`, y # pedir `pinyin.so` daría un falso negativo que rechaza un build bueno — pasó con `fcitx5-hangul` y # costó una vuelta entera. # # Se exigen los DOS métodos de entrada (`pinyin` y `table`) y sus `.conf`, que es lo que hace que el # daemon sepa que existen. Un addon sin su `.conf` en `share/fcitx5/addon/` está en el disco y es # invisible: el daemon no lo carga nunca y no hay ningún error. install = ''' set -e DESTDIR=/out cmake --install build for m in pinyin table pinyinhelper chttrans punctuation fullwidth; do so=$(find /out/usr/lib/fcitx5 -name "*$m*.so" 2>/dev/null | head -1) [ -n "$so" ] || { echo "fcitx5-chinese-addons: no hay ningún *$m*.so en /usr/lib/fcitx5" >&2; ls -R /out/usr/lib/fcitx5 2>/dev/null | head -30 >&2; exit 1; } [ -f "/out/usr/share/fcitx5/addon/$m.conf" ] || { echo "fcitx5-chinese-addons: falta share/fcitx5/addon/$m.conf — el addon existiría en disco y el daemon no lo cargaría nunca" >&2; ls /out/usr/share/fcitx5/addon 2>/dev/null >&2; exit 1; } done # `chttrans` es el que depende de opencc. Que su `.so` enlace la compartida es lo que separa «el # addon está» de «el addon convierte»: sin ese NEEDED, cargaría y devolvería el texto sin tocar. ch=$(find /out/usr/lib/fcitx5 -name '*chttrans*.so' | head -1) readelf -d "$ch" 2>/dev/null | grep -q 'libopencc' || { echo "fcitx5-chinese-addons: chttrans no enlaza libopencc — el tradicional NO funcionaría y no habría error" >&2; readelf -d "$ch" 2>/dev/null | grep NEEDED >&2; exit 1; } # El diccionario de descomposición que produce ENABLE_DATA. [ -s /out/usr/share/fcitx5/pinyin/chaizi.dict ] || { echo "fcitx5-chinese-addons: falta o está vacío share/fcitx5/pinyin/chaizi.dict — lo produce ENABLE_DATA con libime_pinyindict" >&2; exit 1; } n=$(find /out/usr/lib/fcitx5 -name '*.so' | wc -l) echo "fcitx5-chinese-addons: $n addon(s), chttrans enlazado a opencc, chaizi.dict presente" ''' [deps] build = ["cmake", "samurai", "extra-cmake-modules", "pkgconf", "fcitx5", "libime", "boost", "opencc", "zstd", "gettext-tiny", "libxkbcommon", "wayland", "wayland-protocols", # `python3`: lo usa el PARCHE de pinyinhelper de la fase configure, no el build de upstream. # Sin declararlo la fase muere con exit 127 y el error no nombra a python. "python3", "glib", "pcre2", "libffi", "expat", "zlib", "dbus", "binutils"] # ⚠ Datos que el addon busca EN EJECUCIÓN, no al construir. Van en `runtime` precisamente por eso: # no entran en el hash del build —`runtime` no re-hashea— pero sí en la clausura de la imagen, que # es lo que hace que los ficheros estén cuando el daemon los busque. Sin esto, los addons cargan y # no encuentran sus tablas: ni trazos, ni pronunciación, ni conversión, ni candidatos. runtime = ["libime-lm", "libime-dict", "libime-table", "fcitx5-pinyin-stroke", "fcitx5-pinyin-table", "opencc"]