diff --git a/recipes/firefox.toml b/recipes/firefox.toml index 7e2e0c85..ab0e84af 100644 --- a/recipes/firefox.toml +++ b/recipes/firefox.toml @@ -411,5 +411,9 @@ build = [ # wasi-sdk el `.cfg` de `/etc/clang22`, que hace el sysroot invisible para quien no # pasa el flag. Con `--with-wasi-sysroot` explícito es cinturón Y tirantes: # se declara igual porque es lo que hace Alpine y porque cuesta 28 K. - "wasi-libc", "wasi-compiler-rt", "wasi-sdk", + # wasi-libcxx libc++ y libc++abi para wasm32. NO es opcional: las librerías que RLBox + # enjaula no son todas C (graphite2 es C++) y sin esto el configure muere a + # los 6 segundos con «'cstring' file not found», culpando a los headers de + # WASI, que están perfectos. Es el hueco que este build encontró. + "wasi-libc", "wasi-libcxx", "wasi-compiler-rt", "wasi-sdk", ] diff --git a/recipes/wasi-libcxx.toml b/recipes/wasi-libcxx.toml new file mode 100644 index 00000000..6afc0573 --- /dev/null +++ b/recipes/wasi-libcxx.toml @@ -0,0 +1,184 @@ +# wasi-libcxx — libc++ y libc++abi compilados a wasm32. EL ESLABÓN QUE FALTABA. +# +# POR QUÉ EXISTE ESTA RECETA, y no es una suposición: el build de firefox con RLBox encendido murió +# en el configure, a los SEIS SEGUNDOS, con +# +# /tmp/conftest.cpp:1:10: fatal error: 'cstring' file not found +# ERROR: Cannot find wasi headers or problem with the wasm compiler. +# +# El mensaje culpa a los headers de WASI y los headers de WASI estaban perfectos. Lo que falta es +# otra cosa: `` es un header de C++, y `wasi-libc` sólo trae la C. Las librerías que RLBox +# enjaula NO son todas C —graphite2 es C++— así que el sandbox necesita una biblioteca estándar de +# C++ para wasm32. Es el mismo patrón que la lección de los headers UAPI: el error nombra lo que +# buscó, no lo que falta. +# +# Contrastado contra Alpine, que es nuestro upstream: su `wasi-sdk` declara +# `depends="wasi-libc wasi-libcxx wasi-compiler-rt"` — TRES, y nosotros teníamos dos. El hueco +# estaba en el grafo antes de que el build lo encontrara. +# +# ⚠ LA VERSIÓN SIGUE A LA DEL LAB, igual que `wasi-compiler-rt` y por la misma razón: es el mismo +# tarball de LLVM (22.1.8, mismo sha256) y libc++ es un contrato con el clang que la va a usar. Si +# el lab sube de LLVM, `TOOLCHAIN_PREFIXES` mueve la huella y esto re-hashea solo. +# +# DÓNDE ATERRIZA: `/usr/share/wasi-sysroot`, el MISMO prefijo que `wasi-libc`. No se pisan: las deps +# se apilan como overlay y overlayfs FUSIONA directorios, así que uno aporta `lib/wasm32-wasip1/ +# libc.a` y el otro `libc++.a` en esa misma carpeta. Es exactamente el reparto que hace Alpine con +# dos paquetes sobre un solo sysroot. +name = "wasi-libcxx" +version = "22.1.8" +license = "Apache-2.0 WITH LLVM-exception" + +[source] +tarball = "https://github.com/llvm/llvm-project/releases/download/llvmorg-22.1.8/llvm-project-22.1.8.src.tar.xz" +sha256 = "922f1817a0df7b1489272d18134ee0087a8b068828f87ac63b9861b1a9965888" + +[build] +compiler = "clang" + +[deps] +# `wasi-libc` aporta el sysroot contra el que compila libc++ (headers y `libc.a`). NO va +# `wasi-compiler-rt`: acá sólo se producen archivos estáticos (`LIBCXX_ENABLE_SHARED=OFF`), y sin +# paso de enlace los builtins no hacen falta — es también lo que declara Alpine. +build = ["cmake", "samurai", "python3", "wasi-libc"] + +[build.phases] +configure = ''' +set -e + +# El mismo toolchain file que escribe `wasi-compiler-rt`, transcrito por el mismo motivo: son 38 +# líneas de declaraciones puras repartidas en dos ficheros del tarball de wasi-sdk, y traerlas +# costaría una fuente de red y una receta enteras. Si se toca uno, se toca el otro. +mkdir -p cmake/Platform +echo 'set(WASI 1)' > cmake/Platform/WASI.cmake +cat > wasi-sdk.cmake <<'EOF' +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") +set(CMAKE_SYSTEM_NAME WASI) +set(CMAKE_SYSTEM_VERSION 1) +set(CMAKE_SYSTEM_PROCESSOR wasm32) +set(triple wasm32-wasi) +set(CMAKE_C_COMPILER ${WASI_SDK_PREFIX}/bin/clang) +set(CMAKE_CXX_COMPILER ${WASI_SDK_PREFIX}/bin/clang++) +set(CMAKE_ASM_COMPILER ${WASI_SDK_PREFIX}/bin/clang) +set(CMAKE_AR ${WASI_SDK_PREFIX}/bin/llvm-ar) +set(CMAKE_RANLIB ${WASI_SDK_PREFIX}/bin/llvm-ranlib) +set(CMAKE_C_COMPILER_TARGET ${triple}) +set(CMAKE_CXX_COMPILER_TARGET ${triple}) +set(CMAKE_ASM_COMPILER_TARGET ${triple}) +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) +EOF + +# Los flags del entorno se limpian igual que en `wasi-compiler-rt`: un `-static` o un `-march` +# pensados para el x86_64 nativo son veneno en un enlace a wasm32 y el error no se parece a la causa. +unset CFLAGS CXXFLAGS LDFLAGS +''' +compile = ''' +set -e + +# `-fno-exceptions`: libc++ para wasm se construye SIN excepciones (`LIBCXX_ENABLE_EXCEPTIONS=OFF` +# abajo), y si los flags no acompañan a la opción de cmake el resultado es una librería a medias. +export CFLAGS="-fno-exceptions --sysroot=/usr/share/wasi-sysroot" +export CXXFLAGS="-fno-exceptions --sysroot=/usr/share/wasi-sysroot" + +# Una función y dos llamadas, porque los dos triples se configuran IGUAL salvo por los threads. El +# `-threads` no es opcional: firefox pide el sysroot por el nombre del triple y `wasi-libc` ya +# publica los dos, así que dejar uno sin libc++ sería el hueco de siempre — presente a medias, que +# es peor que ausente. +conf() { + t="$1"; d="$2"; thr=OFF; extra="" + case "$t" in *-threads) thr=ON; extra="-pthread";; esac + cmake -B "$d" -G Ninja -S runtimes -Wno-dev \ + -DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi" \ + -DLLVM_ENABLE_PER_TARGET_RUNTIME_DIR=ON \ + -DCMAKE_BUILD_TYPE=MinSizeRel \ + -DCMAKE_MODULE_PATH=/src/cmake \ + -DCMAKE_TOOLCHAIN_FILE=/src/wasi-sdk.cmake \ + -DCMAKE_C_COMPILER_WORKS=ON \ + -DCMAKE_CXX_COMPILER_WORKS=ON \ + -DCMAKE_C_FLAGS="$CFLAGS $extra --target=$t" \ + -DCMAKE_CXX_FLAGS="$CXXFLAGS $extra --target=$t" \ + -DCMAKE_ASM_COMPILER_TARGET="$t" \ + -DCMAKE_CXX_COMPILER_TARGET="$t" \ + -DCMAKE_C_COMPILER_TARGET="$t" \ + -DCMAKE_AR=/usr/bin/llvm-ar \ + -DCMAKE_RANLIB=/usr/bin/llvm-ranlib \ + -DLLVM_DEFAULT_TARGET_TRIPLE="$t" \ + -DCMAKE_STAGING_PREFIX=/usr/share/wasi-sysroot \ + -DCXX_SUPPORTS_CXX11=ON \ + -DLIBCXX_ABI_VERSION=2 \ + -DLIBCXX_BUILD_EXTERNAL_THREAD_LIBRARY=OFF \ + -DLIBCXX_CXX_ABI=libcxxabi \ + -DLIBCXX_CXX_ABI_INCLUDE_PATHS=libcxxabi/include \ + -DLIBCXX_ENABLE_EXCEPTIONS=OFF \ + -DLIBCXX_ENABLE_EXPERIMENTAL_LIBRARY=OFF \ + -DLIBCXX_ENABLE_FILESYSTEM=OFF \ + -DLIBCXX_ENABLE_SHARED=OFF \ + -DLIBCXX_ENABLE_THREADS=$thr \ + -DLIBCXX_HAS_EXTERNAL_THREAD_API=OFF \ + -DLIBCXX_HAS_MUSL_LIBC=ON \ + -DLIBCXX_HAS_PTHREAD_API=$thr \ + -DLIBCXX_HAS_WIN32_THREAD_API=OFF \ + -DLIBCXX_INCLUDE_TESTS=OFF \ + -DLIBCXXABI_BUILD_EXTERNAL_THREAD_LIBRARY=OFF \ + -DLIBCXXABI_ENABLE_EXCEPTIONS=OFF \ + -DLIBCXXABI_ENABLE_PIC=OFF \ + -DLIBCXXABI_ENABLE_SHARED=OFF \ + -DLIBCXXABI_ENABLE_THREADS=$thr \ + -DLIBCXXABI_HAS_EXTERNAL_THREAD_API=OFF \ + -DLIBCXXABI_HAS_PTHREAD_API=$thr \ + -DLIBCXXABI_HAS_WIN32_THREAD_API=OFF \ + -DLIBCXXABI_INCLUDE_TESTS=OFF \ + -DLIBCXXABI_LIBCXX_INCLUDES=/src/build-libcxx/include/c++/v1 \ + -DLIBCXXABI_LIBCXX_PATH=libcxx \ + -DLIBCXXABI_SILENT_TERMINATE:BOOL=ON \ + -DLIBCXXABI_USE_LLVM_UNWINDER=OFF \ + -DUNIX=ON \ + -DWASI_SDK_PREFIX=/usr \ + -DLIBCXX_INSTALL_INCLUDE_DIR="include/$t/c++/v1" \ + -DLIBCXX_INSTALL_INCLUDE_TARGET_DIR="include/$t/c++/v1" \ + -DLIBCXXABI_INSTALL_INCLUDE_DIR="include/$t/c++/v1" +} + +conf wasm32-wasip1 build +cmake --build build +conf wasm32-wasip1-threads build-threads +cmake --build build-threads +''' +install = ''' +set -e +DESTDIR=/out cmake --install build +DESTDIR=/out cmake --install build-threads + +S=/out/usr/share/wasi-sysroot + +# El triple con el que cmake NOMBRA los directorios es el canónico (`wasm32-unknown-wasip1`) y el +# que clang BUSCA es el corto (`wasm32-wasip1`) — el mismo que ya publica `wasi-libc`. Sin este +# renombre libc++ quedaría instalada al lado del sitio donde se la busca: presente en el artefacto e +# invisible para el compilador, que es el modo de fallo más caro de todos. +cd "$S/lib" +for t in wasm32-unknown-wasip1 wasm32-unknown-wasip1-threads; do + [ -d "$t" ] && mv -v "$t" "${t/unknown-/}" +done +cd /src + +# GUARDIÁN. Se exige exactamente lo que el configure de firefox no encontró: el header de C++ que +# disparó todo esto y la librería que lo respalda, PARA LOS DOS TRIPLES. Un `cmake --install` que no +# instala nada sale con 0, y un sysroot con headers y sin `.a` pasa por bueno y revienta dentro del +# build de firefox, horas después y sin nombrar a esta receta. +for t in wasm32-wasip1 wasm32-wasip1-threads; do + H="$S/include/$t/c++/v1/cstring" + A="$S/lib/$t/libc++.a" + B="$S/lib/$t/libc++abi.a" + for f in "$H" "$A" "$B"; do + test -s "$f" || { + echo "!! falta (o está vacío) $f" >&2 + echo " contenido real del sysroot:" >&2 + find "$S" -maxdepth 3 \( -name 'libc++*.a' -o -name 'cstring' \) 2>/dev/null | head -20 >&2 + exit 1 + } + done + echo "guardián: $t ok — libc++.a $(stat -c%s "$A") bytes, libc++abi.a $(stat -c%s "$B") bytes" +done +''' diff --git a/recipes/wasi-sdk.toml b/recipes/wasi-sdk.toml index 98cb6e9a..0bf7d1a6 100644 --- a/recipes/wasi-sdk.toml +++ b/recipes/wasi-sdk.toml @@ -25,7 +25,11 @@ dir = "wasi-sdk" compiler = "clang" [deps] -build = ["wasi-libc"] +# Las TRES que declara el `wasi-sdk` de Alpine (`depends="wasi-libc wasi-libcxx +# wasi-compiler-rt"`), verificado contra su APKBUILD el 2026-09-06. Acá `wasi-libcxx` no es +# decorativa: sin ella el sysroot al que apunta el .cfg tiene la C y no la C++, y el guardián de +# abajo —que sólo mira `libc.a`— lo daría por bueno. +build = ["wasi-libc", "wasi-libcxx"] [build.phases] configure = "true"