tawasuyu

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 Definitions

⎇ 8b1673d689b25a7854e74067da850d4a8f8a09ea recipes/wasi-libcxx.toml TOML · 231 lines · 10.8 KB History raw

wasi-libcxx: el directorio VACÍO que hace visible a libc++ (y el guardián que lo prueba) cddae4620 · Sergio ·

1# wasi-libcxx — libc++ y libc++abi compilados a wasm32. EL ESLABÓN QUE FALTABA.
2#
3# POR QUÉ EXISTE ESTA RECETA, y no es una suposición: el build de firefox con RLBox encendido murió
4# en el configure, a los SEIS SEGUNDOS, con
5#
6#     /tmp/conftest.cpp:1:10: fatal error: 'cstring' file not found
7#     ERROR: Cannot find wasi headers or problem with the wasm compiler.
8#
9# El mensaje culpa a los headers de WASI y los headers de WASI estaban perfectos. Lo que falta es
10# otra cosa: `<cstring>` es un header de C++, y `wasi-libc` sólo trae la C. Las librerías que RLBox
11# enjaula NO son todas C —graphite2 es C++— así que el sandbox necesita una biblioteca estándar de
12# C++ para wasm32. Es el mismo patrón que la lección de los headers UAPI: el error nombra lo que
13# buscó, no lo que falta.
14#
15# Contrastado contra Alpine, que es nuestro upstream: su `wasi-sdk` declara
16# `depends="wasi-libc wasi-libcxx wasi-compiler-rt"` — TRES, y nosotros teníamos dos. El hueco
17# estaba en el grafo antes de que el build lo encontrara.
18#
19# ⚠ LA VERSIÓN SIGUE A LA DEL LAB, igual que `wasi-compiler-rt` y por la misma razón: es el mismo
20# tarball de LLVM (22.1.8, mismo sha256) y libc++ es un contrato con el clang que la va a usar. Si
21# el lab sube de LLVM, `TOOLCHAIN_PREFIXES` mueve la huella y esto re-hashea solo.
22#
23# DÓNDE ATERRIZA: `/usr/share/wasi-sysroot`, el MISMO prefijo que `wasi-libc`. No se pisan: las deps
24# se apilan como overlay y overlayfs FUSIONA directorios, así que uno aporta `lib/wasm32-wasip1/
25# libc.a` y el otro `libc++.a` en esa misma carpeta. Es exactamente el reparto que hace Alpine con
26# dos paquetes sobre un solo sysroot.
27name    = "wasi-libcxx"
28version = "22.1.8"
29license = "Apache-2.0 WITH LLVM-exception"
30
31[source]
32tarball = "https://github.com/llvm/llvm-project/releases/download/llvmorg-22.1.8/llvm-project-22.1.8.src.tar.xz"
33sha256  = "922f1817a0df7b1489272d18134ee0087a8b068828f87ac63b9861b1a9965888"
34
35[build]
36compiler = "clang"
37
38[deps]
39# `wasi-libc` aporta el sysroot contra el que compila libc++ (headers y `libc.a`). NO va
40# `wasi-compiler-rt`: acá sólo se producen archivos estáticos (`LIBCXX_ENABLE_SHARED=OFF`), y sin
41# paso de enlace los builtins no hacen falta — es también lo que declara Alpine.
42build = ["cmake", "samurai", "python3", "wasi-libc"]
43
44[build.phases]
45configure = '''
46set -e
47
48# El mismo toolchain file que escribe `wasi-compiler-rt`, transcrito por el mismo motivo: son 38
49# líneas de declaraciones puras repartidas en dos ficheros del tarball de wasi-sdk, y traerlas
50# costaría una fuente de red y una receta enteras. Si se toca uno, se toca el otro.
51mkdir -p cmake/Platform
52echo 'set(WASI 1)' > cmake/Platform/WASI.cmake
53cat > wasi-sdk.cmake <<'EOF'
54list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
55set(CMAKE_SYSTEM_NAME WASI)
56set(CMAKE_SYSTEM_VERSION 1)
57set(CMAKE_SYSTEM_PROCESSOR wasm32)
58set(triple wasm32-wasi)
59set(CMAKE_C_COMPILER   ${WASI_SDK_PREFIX}/bin/clang)
60set(CMAKE_CXX_COMPILER ${WASI_SDK_PREFIX}/bin/clang++)
61set(CMAKE_ASM_COMPILER ${WASI_SDK_PREFIX}/bin/clang)
62set(CMAKE_AR      ${WASI_SDK_PREFIX}/bin/llvm-ar)
63set(CMAKE_RANLIB  ${WASI_SDK_PREFIX}/bin/llvm-ranlib)
64set(CMAKE_C_COMPILER_TARGET   ${triple})
65set(CMAKE_CXX_COMPILER_TARGET ${triple})
66set(CMAKE_ASM_COMPILER_TARGET ${triple})
67set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
68set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
69set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
70set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
71EOF
72
73# Los flags del entorno se limpian igual que en `wasi-compiler-rt`: un `-static` o un `-march`
74# pensados para el x86_64 nativo son veneno en un enlace a wasm32 y el error no se parece a la causa.
75unset CFLAGS CXXFLAGS LDFLAGS
76'''
77compile = '''
78set -e
79
80# `-fno-exceptions`: libc++ para wasm se construye SIN excepciones (`LIBCXX_ENABLE_EXCEPTIONS=OFF`
81# abajo), y si los flags no acompañan a la opción de cmake el resultado es una librería a medias.
82export CFLAGS="-fno-exceptions --sysroot=/usr/share/wasi-sysroot"
83export CXXFLAGS="-fno-exceptions --sysroot=/usr/share/wasi-sysroot"
84
85# Una función y dos llamadas, porque los dos triples se configuran IGUAL salvo por los threads. El
86# `-threads` no es opcional: firefox pide el sysroot por el nombre del triple y `wasi-libc` ya
87# publica los dos, así que dejar uno sin libc++ sería el hueco de siempre — presente a medias, que
88# es peor que ausente.
89conf() {
90  t="$1"; d="$2"; thr=OFF; extra=""
91  case "$t" in *-threads) thr=ON; extra="-pthread";; esac
92  cmake -B "$d" -G Ninja -S runtimes -Wno-dev \
93    -DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi" \
94    -DLLVM_ENABLE_PER_TARGET_RUNTIME_DIR=ON \
95    -DCMAKE_BUILD_TYPE=MinSizeRel \
96    -DCMAKE_MODULE_PATH=/src/cmake \
97    -DCMAKE_TOOLCHAIN_FILE=/src/wasi-sdk.cmake \
98    -DCMAKE_C_COMPILER_WORKS=ON \
99    -DCMAKE_CXX_COMPILER_WORKS=ON \
100    -DCMAKE_C_FLAGS="$CFLAGS $extra --target=$t" \
101    -DCMAKE_CXX_FLAGS="$CXXFLAGS $extra --target=$t" \
102    -DCMAKE_ASM_COMPILER_TARGET="$t" \
103    -DCMAKE_CXX_COMPILER_TARGET="$t" \
104    -DCMAKE_C_COMPILER_TARGET="$t" \
105    -DCMAKE_AR=/usr/bin/llvm-ar \
106    -DCMAKE_RANLIB=/usr/bin/llvm-ranlib \
107    -DLLVM_DEFAULT_TARGET_TRIPLE="$t" \
108    -DCMAKE_STAGING_PREFIX=/usr/share/wasi-sysroot \
109    -DCXX_SUPPORTS_CXX11=ON \
110    -DLIBCXX_ABI_VERSION=2 \
111    -DLIBCXX_BUILD_EXTERNAL_THREAD_LIBRARY=OFF \
112    -DLIBCXX_CXX_ABI=libcxxabi \
113    -DLIBCXX_CXX_ABI_INCLUDE_PATHS=libcxxabi/include \
114    -DLIBCXX_ENABLE_EXCEPTIONS=OFF \
115    -DLIBCXX_ENABLE_EXPERIMENTAL_LIBRARY=OFF \
116    -DLIBCXX_ENABLE_FILESYSTEM=OFF \
117    -DLIBCXX_ENABLE_SHARED=OFF \
118    -DLIBCXX_ENABLE_THREADS=$thr \
119    -DLIBCXX_HAS_EXTERNAL_THREAD_API=OFF \
120    -DLIBCXX_HAS_MUSL_LIBC=ON \
121    -DLIBCXX_HAS_PTHREAD_API=$thr \
122    -DLIBCXX_HAS_WIN32_THREAD_API=OFF \
123    -DLIBCXX_INCLUDE_TESTS=OFF \
124    -DLIBCXXABI_BUILD_EXTERNAL_THREAD_LIBRARY=OFF \
125    -DLIBCXXABI_ENABLE_EXCEPTIONS=OFF \
126    -DLIBCXXABI_ENABLE_PIC=OFF \
127    -DLIBCXXABI_ENABLE_SHARED=OFF \
128    -DLIBCXXABI_ENABLE_THREADS=$thr \
129    -DLIBCXXABI_HAS_EXTERNAL_THREAD_API=OFF \
130    -DLIBCXXABI_HAS_PTHREAD_API=$thr \
131    -DLIBCXXABI_HAS_WIN32_THREAD_API=OFF \
132    -DLIBCXXABI_INCLUDE_TESTS=OFF \
133    -DLIBCXXABI_LIBCXX_INCLUDES=/src/build-libcxx/include/c++/v1 \
134    -DLIBCXXABI_LIBCXX_PATH=libcxx \
135    -DLIBCXXABI_SILENT_TERMINATE:BOOL=ON \
136    -DLIBCXXABI_USE_LLVM_UNWINDER=OFF \
137    -DUNIX=ON \
138    -DWASI_SDK_PREFIX=/usr \
139    -DLIBCXX_INSTALL_INCLUDE_DIR="include/$t/c++/v1" \
140    -DLIBCXX_INSTALL_INCLUDE_TARGET_DIR="include/$t/c++/v1" \
141    -DLIBCXXABI_INSTALL_INCLUDE_DIR="include/$t/c++/v1"
142}
143
144conf wasm32-wasip1         build
145cmake --build build
146conf wasm32-wasip1-threads build-threads
147cmake --build build-threads
148'''
149install = '''
150set -e
151DESTDIR=/out cmake --install build
152DESTDIR=/out cmake --install build-threads
153
154S=/out/usr/share/wasi-sysroot
155
156# El triple con el que cmake NOMBRA los directorios es el canónico (`wasm32-unknown-wasip1`) y el
157# que clang BUSCA es el corto (`wasm32-wasip1`) — el mismo que ya publica `wasi-libc`. Sin este
158# renombre libc++ quedaría instalada al lado del sitio donde se la busca: presente en el artefacto e
159# invisible para el compilador, que es el modo de fallo más caro de todos.
160cd "$S/lib"
161for t in wasm32-unknown-wasip1 wasm32-unknown-wasip1-threads; do
162  [ -d "$t" ] && mv -v "$t" "${t/unknown-/}"
163done
164cd /src
165
166# ⚠ UN DIRECTORIO VACÍO QUE SÍ IMPORTA — y por eso está en su propia línea con su propio párrafo.
167#
168# El APKBUILD de Alpine hace un `mkdir -p .../include/c++/v1` que parece ruido de empaquetado. NO
169# lo es: es la SONDA por la que el driver de clang decide que este sysroot tiene layout de libc++.
170# Sin ese directorio, clang NO añade `include/<triple>/c++/v1` a la lista de búsqueda, y los
171# headers quedan instalados en el artefacto y a la vez invisibles para el compilador.
172#
173# MEDIDO, no deducido. Con el sysroot fusionado (wasi-libc + esta receta) y la invocación EXACTA
174# que hace el configure de firefox:
175#
176#     clang++ -std=gnu++20 --target=wasm32-wasip1 conftest.cpp --sysroot=$S -c
177#
178#   sin include/c++/v1  ->  fatal error: 'cstring' file not found   (y `cstring` ESTABA ahí)
179#   con include/c++/v1  ->  exit 0
180#
181# Es el reverso exacto de la regla 3 del CLAUDE.md: allá un directorio vacío es un artefacto
182# mentiroso; acá un directorio vacío es una declaración dirigida al compilador. Borrarlo por
183# "limpieza" reintroduce un fallo que aparece a 6 segundos de un build de 4 horas, culpando a los
184# headers de WASI, que están perfectos.
185mkdir -p "$S/include/c++/v1"
186
187# GUARDIÁN. Se exige exactamente lo que el configure de firefox no encontró: el header de C++ que
188# disparó todo esto y la librería que lo respalda, PARA LOS DOS TRIPLES. Un `cmake --install` que no
189# instala nada sale con 0, y un sysroot con headers y sin `.a` pasa por bueno y revienta dentro del
190# build de firefox, horas después y sin nombrar a esta receta.
191for t in wasm32-wasip1 wasm32-wasip1-threads; do
192  H="$S/include/$t/c++/v1/cstring"
193  A="$S/lib/$t/libc++.a"
194  B="$S/lib/$t/libc++abi.a"
195  for f in "$H" "$A" "$B"; do
196    test -s "$f" || {
197      echo "!! falta (o está vacío) $f" >&2
198      echo "   contenido real del sysroot:" >&2
199      find "$S" -maxdepth 3 \( -name 'libc++*.a' -o -name 'cstring' \) 2>/dev/null | head -20 >&2
200      exit 1
201    }
202  done
203  echo "guardián: $t ok — libc++.a $(stat -c%s "$A") bytes, libc++abi.a $(stat -c%s "$B") bytes"
204done
205
206# ── GUARDIÁN DE VERDAD: CORRER LA MISMA PRUEBA QUE HACE EL CONFIGURE DE FIREFOX ───────────────
207# Los tests de arriba comprueban que los ficheros existen, y "existe" no es "se encuentra": el
208# fallo que motivó esta receta era exactamente eso —`cstring` presente en el disco e invisible
209# para clang—, así que un guardián de presencia lo habría dado por bueno.
210#
211# Se reproduce la invocación literal del configure de Mozilla contra un sysroot FUSIONADO (el de
212# la dep `wasi-libc` más lo que acaba de instalar esta receta), que es la forma en que se va a usar
213# de verdad. Si esto pasa acá, no puede fallar allá; y si falla, falla en el segundo 20 de ESTA
214# receta y no en el segundo 6 de un build de cuatro horas que además culpa a otro.
215P=/tmp/sysroot-fusionado
216mkdir -p "$P"
217cp -a /usr/share/wasi-sysroot/. "$P"/     # lo que aporta la dep wasi-libc
218cp -a "$S"/. "$P"/                        # lo que aporta esta receta
219echo '#include <cstring>' > /tmp/probe.cpp
220for t in wasm32-wasip1 wasm32-wasip1-threads; do
221  clang++ -std=gnu++20 --target=$t /tmp/probe.cpp --sysroot="$P" -c -o /tmp/probe.o || {
222    echo "!! libc++ instalada pero INVISIBLE para clang en $t." >&2
223    echo "   Los headers están en el artefacto y el compilador no los encuentra." >&2
224    echo "   Sospechá del directorio-sonda include/c++/v1 antes que de la instalación." >&2
225    clang++ -std=gnu++20 --target=$t /tmp/probe.cpp --sysroot="$P" -c -o /tmp/probe.o -v 2>&1 \
226      | sed -n '/search starts/,/End of search/p' >&2
227    exit 1
228  }
229  echo "guardián: clang++ encuentra <cstring> para $t (la prueba exacta del configure de firefox)"
230done
231'''