# 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 '''