SDD 31: el compilador de Rust — el escalón 2 construyendo, y los cinco muros del camino
rustc desde fuente está compilando el stage1 en el worker. El documento recoge el frente entero: el hueco medido (12 crates y 42 555 líneas de Rust, 44 recetas `cargo-*`, CERO de rustc, y el `rustc` que construye todo eso saliendo del LAB — que ENTRA en el ArtifactHash), los tres escalones, y lo que costó cada uno. Los cinco muros del escalón 2, todos con un mensaje que no nombra la causa: 1. `llvm18` no sirve: `bad LLVM version, need >=21`. Mínimos medidos en el propio bootstrap (1.87→18 · 1.90→19 · 1.93/1.95→20 · 1.97→21). Bajar de rustc no es salida porque el stage0 de N es N-1 o N. De ahí `llvm21` — y `llvm18` se queda, que lo usa mesa. 2. **El toolchain musl oficial no puede producir proc-macros** (crt-static=true ⇒ sin dylibs): muere compilando el propio bootstrap de rustc, que usa `clap` con derive. Sirve para compilar programas, no para arrancar el build de rustc. El stage0 es el del lab. 3. x.py DESCARGA el stage0 si no le nombras el local: en un sandbox sin red, `RuntimeError: failed verification` en `download_toolchain()`, que no menciona ni la red ni el stage0. 4. El triple de Alpine no es el canónico: sin fijarlo, la sección `[target.…-unknown-…]` no aplica a nada y x.py se pone a construir LLVM solo; forzando el canónico, el stage0 no tiene su std. 5. cmake + zig = «compiler broken» en la prueba de ABI. CC=gcc, igual que llvm21 y cmake. Y del escalón 1, lo que no estaba en el grafo: el prebuilt no ejecuta sin `musl-shared` (cargador) y sin `gcc-libs` (`libgcc_s.so.1`) — dos paquetes sellados y en el perfil equivocado. Más una propiedad que vale: no hace falta un `cc`, el toolchain trae su propio lld. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Tf9T4vGzsMoT7eS8YzMFn
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
# SDD 31 — El compilador de Rust: de «lo presta el lab» a «es nuestro»
|
||||
|
||||
> «sin compilador de rust, takana no tiene sentido» — el usuario, 2026-09-14.
|
||||
>
|
||||
> Tiene razón por una vía que no es de gusto, y conviene escribirla entera antes de discutir cómo.
|
||||
|
||||
## 1. El hueco, medido
|
||||
|
||||
| pregunta | respuesta (2026-09-14) |
|
||||
|---|---|
|
||||
| ¿cuánto de takana es Rust? | **12 crates, 42 555 líneas** — todo el instrumental |
|
||||
| recetas `cargo-*` en el corpus | **44** |
|
||||
| recetas de `rustc` o `cargo` | **0** |
|
||||
| ¿de dónde sale el `rustc` que construye todo eso? | del **LAB** (`.dev-fs/alpine`), un rootfs ajeno |
|
||||
|
||||
Y el lab **entra en el `ArtifactHash`**. O sea: la distro depende de bytes de otro para poder
|
||||
reconstruir su propio instrumental. No se ve porque funciona — es la misma forma de
|
||||
`subcomando-sin-driver`, a escala de toolchain: 44 subcomandos de `cargo` y ningún `cargo`.
|
||||
|
||||
## 2. Los tres escalones
|
||||
|
||||
1. **`rust-toolchain-bin`** — el tarball oficial de rust-lang, verificado por su sha256 y sellado
|
||||
tal cual con `foreign = true` (bytes ajenos: clase `ajeno`, fuera del recuento del corpus).
|
||||
**Hecho y probado en la caja**: `rustc 1.97.0`, `cargo 1.97.0`, y un binario compilado que corre.
|
||||
2. **`rust` desde fuente** — con `llvm21` del corpus y el stage0 del lab. *En curso.*
|
||||
3. **La cadena `mrustc → 1.90 → 1.91.0 → 1.91.1`** del selfhost (`docs/10-roadmap.md`,
|
||||
`docs/11-bootstrap.md`), que quita hasta el escalón 1. Pendiente.
|
||||
|
||||
## 3. Lo que costó el escalón 1, y no estaba en el grafo
|
||||
|
||||
El artefacto sella (799 M) y **no ejecuta** hasta tener dos cosas que la distro publicaba por
|
||||
separado — las dos `sealed`, las dos en el perfil equivocado:
|
||||
|
||||
- **El cargador musl.** `rustc` es PIE dinámico (`NEEDED librustc_driver-*.so`, `NEEDED libc.so`).
|
||||
En el worker ni arranca (`cannot execute: required file not found`, que es como se ve la falta del
|
||||
intérprete); en la caja sí, porque ahí está `musl-shared`.
|
||||
- **`libgcc_s.so.1`** (`_Unwind_Resume: symbol not found`). Lo publica `gcc-libs`, que estaba
|
||||
declarado **sólo en los cuatro perfiles de escritorio**.
|
||||
|
||||
**Y no hace falta un `cc`.** El primer `rustc hola.rs` falla con `linker \`cc\` not found`, y la
|
||||
respuesta no es traer un compilador de C: el toolchain **trae su propio `lld`**
|
||||
(`rustlib/<target>/bin/rust-lld`). Con `-C linker-flavor=ld.lld -C link-self-contained=yes` enlaza y
|
||||
el binario corre. Una distro sin compilador de C puede compilar Rust igual.
|
||||
|
||||
## 4. Lo que costó el escalón 2 — cinco muros, todos con su mensaje engañoso
|
||||
|
||||
1. **`llvm18` no sirve.** El bootstrap de rustc lo dice en una línea:
|
||||
`panic!("bad LLVM version: {version}, need >=21")`. Mínimos medidos leyendo
|
||||
`src/bootstrap/src/core/build_steps/llvm.rs`: **1.87 → ≥18 · 1.90 → ≥19 · 1.93/1.95 → ≥20 ·
|
||||
1.97 → ≥21**. Y bajar de rustc no es salida: el stage0 de rustc *N* es *N-1* o *N*, y lo que
|
||||
tenemos es 1.97. Por eso existe `llvm21` — y `llvm18` se queda, porque lo usa `mesa-llvmpipe` y
|
||||
mesa 24.0.9 no soporta 21. Dos consumidores con rangos incompatibles ⇒ dos artefactos.
|
||||
2. **El toolchain musl oficial NO puede producir proc-macros.** Está compilado con
|
||||
`crt-static = true`, y los proc-macros son dylibs:
|
||||
|
||||
error: cannot produce proc-macro for `clap_derive` 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
|
||||
sirve para compilar programas 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 el rustc que salga de acá lleva
|
||||
`crt-static = false`: con `true` heredaría el mismo defecto y no podría compilar ninguna de las
|
||||
44 recetas `cargo-*` que usan `derive`.)
|
||||
3. **x.py DESCARGA el stage0 si no le nombras el local.** Sin `build.rustc`/`build.cargo`, en un
|
||||
sandbox sin red muere con `RuntimeError: failed verification` en `download_toolchain()` — un
|
||||
mensaje que no menciona ni la red ni el stage0.
|
||||
4. **El triple de Alpine no es el canónico.** Sin fijar `build`, x.py detecta
|
||||
`x86_64-alpine-linux-musl` y entonces una sección `[target.x86_64-unknown-linux-musl]` **no
|
||||
aplica a nada**: ignora el `llvm-config` externo y se pone a construir LLVM él solo. Y al forzar
|
||||
el canónico, el stage0 del lab **no tiene la std de ese triple** (`can't find crate for std`).
|
||||
Se usa el de Alpine en todo, que además es con el que ya se compila el corpus entero.
|
||||
5. **cmake + zig = «compiler broken».** Los build scripts nativos (jemalloc, compiler-rt) usan
|
||||
cmake, y con el wrapper de zig la prueba de ABI falla. `CC=gcc CXX=g++`, igual que en `llvm21`
|
||||
y `cmake`.
|
||||
|
||||
## 5. Lo que falta decidir
|
||||
|
||||
- **Dónde vive el toolchain.** Hoy `rust-toolchain-bin` está en `perfil.servidor` (799 M; un
|
||||
escritorio no compila nada). El de fuente irá donde se decida — y probablemente sustituya al
|
||||
prebuilt en ese perfil en vez de sumarse.
|
||||
- **La config del sitio**: que las flags del linker (`-C linker-flavor=ld.lld`) sean el default en
|
||||
un `/etc/cargo/config.toml`, para que `cargo build` funcione sin recordarlas.
|
||||
- **El escalón 3**: cuándo se conecta la cadena del selfhost al corpus. Es lo único que quita la
|
||||
dependencia de bytes ajenos, y es el trabajo más caro de los tres.
|
||||
+41
-7
@@ -36,7 +36,20 @@ flags = []
|
||||
# `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…).
|
||||
build = ["rust-toolchain-bin", "llvm21", "cmake", "samurai", "python3", "pkgconf", "curl"]
|
||||
# ⚠ 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"]
|
||||
|
||||
[build.phases]
|
||||
# El fichero de configuración de x.py se llama `bootstrap.toml` desde 1.8x; se escribe también
|
||||
@@ -48,14 +61,29 @@ 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-unknown-linux-musl"]
|
||||
host = ["x86_64-unknown-linux-musl"]
|
||||
target = ["x86_64-alpine-linux-musl"]
|
||||
host = ["x86_64-alpine-linux-musl"]
|
||||
vendor = true
|
||||
|
||||
[llvm]
|
||||
@@ -68,9 +96,12 @@ codegen-tests = false
|
||||
deny-warnings = false
|
||||
musl-root = "/usr"
|
||||
|
||||
[target.x86_64-unknown-linux-musl]
|
||||
[target.x86_64-alpine-linux-musl]
|
||||
llvm-config = "/usr/bin/llvm-config"
|
||||
crt-static = true
|
||||
# `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
|
||||
@@ -79,5 +110,8 @@ cp bootstrap.toml config.toml
|
||||
rustc --version | grep -q '1[.]97[.]0' || { echo "stage0 NO es 1.97.0: $(rustc --version)" >&2; exit 1; }
|
||||
llvm-config --version
|
||||
'''
|
||||
compile = "python3 x.py build --stage 2"
|
||||
install = "DESTDIR=/out python3 x.py install --stage 2"
|
||||
# `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.
|
||||
compile = "export CC=gcc CXX=g++ AR=ar RANLIB=ranlib && python3 x.py build --stage 2"
|
||||
install = "export CC=gcc CXX=g++ AR=ar RANLIB=ranlib && DESTDIR=/out python3 x.py install --stage 2"
|
||||
|
||||
Reference in New Issue
Block a user