# rust 1.97.0 DESDE FUENTE — el paso 2 de los tres, y el que hace que el compilador sea nuestro. # # ══ LA CADENA, Y POR QUÉ ESTE ES EL SEGUNDO ESCALÓN ════════════════════════════════════════════ # 1. `rust-toolchain-bin` — el tarball oficial, `foreign`, sellado tal cual. Desbloqueó que una # caja takana compile Rust (probado: `rustc 1.97.0` y un binario que corre). Es el escalón. # 2. **esto** — rustc construido acá, con `llvm21` del corpus y con el (1) como **stage0**. Un # prebuilt ajeno sirve exactamente para esto: ser el escalón que permite dejar de necesitarlo. # 3. La cadena `mrustc → 1.90 → 1.91.0 → 1.91.1` del selfhost, que quita hasta ese escalón. # # ⚠ **`local-rebuild = true` NO es un detalle.** El bootstrap de rustc espera que el stage0 sea el # compilador ANTERIOR (o el beta de esta versión); acá el stage0 es **1.97.0 construyendo 1.97.0**, y # sin esa opción x.py aborta por «stage0 compiler version mismatch». Es el modo que usan las distros # para reconstruir la misma versión con la misma versión. # # ⚠ **El LLVM es EXTERNO y tiene que ser ≥21**, medido en el propio bootstrap: # `panic!("bad LLVM version: {version}, need >=21")`. Por eso existe `llvm21` y por eso `llvm18` —que # ya estaba sellado— no servía: lo usa mesa, que no soporta 21. Ver `recipes/llvm21.toml`. # # ⚠ Sin red: el tarball `-src` trae `vendor/` con todas las deps de cargo. Es la única forma de que # esto construya dentro del sandbox, que no tiene red después del fetch. name = "rust" version = "1.97.0" license = "MIT OR Apache-2.0" [source] tarball = "https://static.rust-lang.org/dist/rustc-1.97.0-src.tar.xz" sha256 = "de002ee301c1b7422b0a7b09d7c4cb4924cd3224e6cfb24f065dad786dd3ed12" # El triple de Alpine, DENTRO del compilador: sin esto el stage 2 muere con «could not find # specification for target x86_64-alpine-linux-musl» — el nombre que el stage0 necesita es uno que # upstream no conoce. Es lo que hace Alpine en su APKBUILD. Ver SDD 31 §4.bis y la cabecera del # propio parche. patches = ["rust-alpine-target.patch"] [build] compiler = "zig-cc" target = "x86_64-linux-musl" link = "dynamic" flags = [] [deps] # `rust-toolchain-bin` es el STAGE0: el compilador con el que arranca el build. `llvm21` aporta el # `llvm-config` y las librerías. `cmake`/`samurai`/`pkgconf` los pide el propio bootstrap aunque el # LLVM venga hecho (compila algunas cosas nativas: `compiler-rt` para los sanitizers, jemalloc…). # ⚠ EL STAGE0 **NO** ES `rust-toolchain-bin`, Y ESO SE APRENDIÓ CHOCANDO. El toolchain musl oficial # de rust-lang está compilado con `crt-static = true`, y un rustc así **no puede producir # proc-macros** — que son dylibs. El primer intento murió en el primer minuto: # # error: cannot produce proc-macro for `clap_derive v4.5.18` as the target # `x86_64-unknown-linux-musl` does not support these crate types # # …compilando el PROPIO bootstrap de rustc, que usa `clap` con derive. O sea: el prebuilt oficial # sirve para compilar programas (probado: un binario que corre) y **no** para arrancar el build de # rustc. El stage0 es el `rustc` del LAB, que Alpine construye con soporte de dylib. # # Y por eso abajo va `crt-static = false` para el host: sin eso, el rustc que salga de acá heredaría # el mismo defecto y no podría compilar ni una receta del corpus que use `derive`. build = ["llvm21", "cmake", "samurai", "python3", "pkgconf", "curl", "openssl-threads"] # ⚠⚠ `lld = true` EN EL `[rust]` DE ARRIBA NO SE PUEDE, y lo dice el propio bootstrap en una línea: # # Cannot enable LLD with `rust.lld = true` when using external llvm-config. # # O sea: que el toolchain se construya su propio `rust-lld` está CERRADO mientras usemos el `llvm21` # del corpus — y usar el nuestro es justamente lo que queremos. Leí el paso `Lld` del bootstrap (que # compila desde el `src/llvm-project/lld` del tarball y reusa el `llvm-config` externo) y me faltó # mirar la VALIDACIÓN de config, que rechaza la combinación antes de llegar a ese paso. La guarda # tiene sentido: LLD y LLVM comparten ABI de C++ y con un llvm-config externo no se puede garantizar # que casen. # # ⇒ El enlazador entra por el otro lado: `recipes/lld21.toml` construye el MISMO `lld`, del mismo # `llvm-project`, contra el `llvm21` ya sellado. Se usa con `-C linker-flavor=ld.lld`. # # ⚠ Y el comentario va ACÁ y no adentro del heredoc de `bootstrap.toml` a propósito: ese texto es # parte de la fase, o sea **entrada de hash**. Explicar algo ahí adentro re-sella el compilador # entero — hora y media de granja por un párrafo. [build.phases] # El fichero de configuración de x.py se llama `bootstrap.toml` desde 1.8x; se escribe también # `config.toml` porque el nombre viejo sigue siendo el que busca media documentación, y un fichero # de más no cuesta nada mientras el de verdad esté. configure = ''' cat > bootstrap.toml <<'CFG' profile = "dist" change-id = "ignore" [build] # ⚠⚠ EL TRIPLE ES EL DE ALPINE, `x86_64-alpine-linux-musl`, Y NO EL CANÓNICO. Cuesta dos fallos # seguidos entenderlo: # · Sin decir `build`, x.py detecta el del `cc` del sandbox (el de Alpine) y entonces una sección # `[target.x86_64-alpine-linux-musl]` **no aplica a nada**: ignora el `llvm-config` externo y se # pone a construir LLVM él solo — se vio porque entró en `/src/build/x86_64-alpine-linux-musl/`. # · Y al forzar el canónico, el stage0 del LAB **no tiene la std de ese triple**: # `error[E0463]: can't find crate for std`. Alpine RENOMBRA el triple, y su rustc sólo trae la # std del suyo. # Así que se usa el de Alpine en todas partes, que además es el triple con el que ya se compila todo # el corpus. Es el mismo camino que toma el APKBUILD de rust de Alpine. build = "x86_64-alpine-linux-musl" docs = false extended = true tools = ["cargo"] local-rebuild = true # Hay que NOMBRAR el stage0 aunque ya esté en el PATH: sin estas dos líneas, x.py se salta el # compilador local y va a DESCARGAR el toolchain de `static.rust-lang.org` — que en un sandbox sin # red muere con un `RuntimeError: failed verification` en `download_toolchain()`, un mensaje que no # menciona ni la red ni el stage0. Estas rutas son las del LAB, no las del prebuilt (ver arriba). rustc = "/usr/bin/rustc" cargo = "/usr/bin/cargo" target = ["x86_64-alpine-linux-musl"] host = ["x86_64-alpine-linux-musl"] vendor = true [llvm] download-ci-llvm = false link-shared = false [rust] channel = "stable" codegen-tests = false deny-warnings = false musl-root = "/usr" # ⚠ `rpath = false` es la pareja de haber puesto `ld.lld` como enlazador en el spec del triple. # Con el enlazador INVOCADO DIRECTO (sin driver de C), los `-Wl,…` que el bootstrap añade para el # rpath de sus propios binarios llegan tal cual a lld, que no los entiende: # # ld.lld: error: unknown argument '-Wl,-z,origin' # ld.lld: error: unknown argument '-Wl,-rpath,$ORIGIN/../lib' # # …y muere compilando `std`. El rpath sobra igual: con `prefix = "/usr"` las librerías quedan en # `/usr/lib`, que YA está en el camino del cargador. Es lo que hacen las distros que empaquetan rust. rpath = false [install] # ⚠ SIN ESTO EL TOOLCHAIN QUEDA FUERA DEL PATH, y es la trampa de «existe ≠ se encuentra»: el # default de x.py es `/usr/local`, y el PATH de una caja takana es `/bin:/usr/bin:/sbin:/usr/sbin` # — medido en la caja de producción. O sea que el perfil proyectaría un `rustc` perfecto que nadie # puede invocar. El resto del corpus instala en `/usr`; esto se alinea. prefix = "/usr" [target.x86_64-alpine-linux-musl] llvm-config = "/usr/bin/llvm-config" # `false` a propósito: habilita dylibs, y sin dylibs no hay PROC-MACROS. Un rustc con # `crt-static = true` no puede compilar `serde_derive`, `clap_derive` ni ninguna de las 44 recetas # `cargo-*` del corpus que usan `derive`. crt-static = false CFG cp bootstrap.toml config.toml # La versión del stage0 se COMPRUEBA, no se asume: `local-rebuild` sólo es correcto si el compilador # de arranque es exactamente esta versión, y si alguien mueve el pin de `rust-toolchain-bin` esto # tiene que fallar acá y no tres horas después. rustc --version | grep -q '1[.]97[.]0' || { echo "stage0 NO es 1.97.0: $(rustc --version)" >&2; exit 1; } llvm-config --version ''' # `CC`/`CXX` de GCC para los build scripts nativos (jemalloc, compiler-rt): con el wrapper de zig, # cmake declara el compilador «broken» en su prueba de ABI. Mismo motivo por el que `llvm21` y # `cmake` usan g++ en vez de zig. # ── EL OPENSSL DE CARGO, Y POR QUÉ ES LA VARIANTE CON THREADS ────────────────────────────────── # `tools = ["cargo"]` arrastra `openssl-sys`, que sin openssl en el lab corta con «The system library # `openssl` required by crate `openssl-sys` was not found» — y eso pasa DESPUÉS de compilar las 400 # crates del compilador, o sea a la hora y media de build. # # La dep es `openssl-threads` y NO la canónica, aunque las dos publiquen sólo `.a`: el `Configure` de # openssl **apaga los threads en cuanto ve `-static` en LDFLAGS**, y la canónica se construye así. Un # cargo —que es masivamente multihilo— enlazado contra un openssl sin soporte de hilos es la clase de # fallo que no se ve al construir ni al arrancar. La variante existe por lo mismo para `python3`. # # `OPENSSL_STATIC`/`OPENSSL_DIR`: el lab publica `.a` y ningún `.so`, y sin decírselo `openssl-sys` # busca la dinámica y falla con un mensaje que habla de pkg-config y no de eso. compile = "export CC=gcc CXX=g++ AR=ar RANLIB=ranlib OPENSSL_STATIC=1 OPENSSL_DIR=/usr && python3 x.py build --stage 2" install = "export CC=gcc CXX=g++ AR=ar RANLIB=ranlib OPENSSL_STATIC=1 OPENSSL_DIR=/usr && DESTDIR=/out python3 x.py install --stage 2"