wasi-libcxx: el eslabón que faltaba en la cadena wasm (lo encontró el build)

El firefox con RLBox murió en el configure a los SEIS SEGUNDOS:

    /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.
`<cstring>` 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. Mismo patrón que la lección de los
headers UAPI: el error nombra lo que buscó, no lo que falta.

El hueco estaba en el GRAFO antes de que el build lo encontrara, y se podía ver
sin construir nada: el `wasi-sdk` de Alpine declara
`depends="wasi-libc wasi-libcxx wasi-compiler-rt"` —TRES— y nosotros teníamos
dos. Queda anotado como método: cuando se porta una cadena ajena, la lista de
depends del paquete equivalente es una comprobación de completitud gratis.

La receta es el mismo tarball de LLVM 22.1.8 que `wasi-compiler-rt` (mismo
sha256) y repite su transcripción del toolchain file de wasi-sdk, por el mismo
motivo: 38 líneas de declaraciones puras no justifican una fuente de red y una
receta. Si se toca una, se toca la otra.

Dos detalles que son trampas y no adorno:

- Se construyen los DOS triples (wasip1 y wasip1-threads). `wasi-libc` ya publica
  los dos y firefox pide el sysroot por nombre de triple, así que dejar uno sin
  libc++ sería presente-a-medias, que es peor que ausente.
- cmake instala en el triple canónico (`wasm32-unknown-wasip1`) y clang busca en
  el corto (`wasm32-wasip1`). Sin el renombre del install, libc++ queda al lado
  del sitio donde se la busca: en el artefacto e invisible para el compilador.

El guardián exige exactamente lo que el configure no encontró —`cstring`,
`libc++.a` y `libc++abi.a`— para los dos triples.

Re-hashea: wasi-sdk b3:c8a587fa -> b3:f13634c6, firefox b3:98d6899a ->
b3:2880e28a.
This commit is contained in:
Sergio
2026-09-06 01:42:34 +00:00
parent 65ee20e437
commit 553fc1fb7d
3 changed files with 194 additions and 2 deletions
+5 -1
View File
@@ -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",
]
+184
View File
@@ -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: `<cstring>` 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
'''
+5 -1
View File
@@ -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"