#!/bin/sh # Arma el `bin/` de la persona con las herramientas del CORPUS, para que el agente enjaulado tenga # toolchain sin depender del gestor de paquetes de la imagen. # # ── POR QUÉ NO SE INSTALAN CON PACMAN (2026-09-18) ────────────────────────────────────────────── # El agente de adentro reportó que le faltan `cargo rustc make pkg-config python3`, y la salida # obvia —sumarlos a `packages` y `takana qorpa provision`— **hoy no funciona**: con un mapa de un # solo id, `pacman` no puede chownear su directorio de descarga y muere en # # error: failed to chown temporary download directory …: Invalid argument # # que es exactamente la consecuencia que el aviso de qorpa viene anunciando (SDD 28 §6.55). # # Pero no hace falta pacman: **las herramientas ya están selladas en el store**, son musl estáticas y # corren igual dentro de una imagen glibc — comprobado con `takana` y con `rustc`. Usarlas es además # lo coherente con la distro: el corpus es el que provee, no Arch. # # `go` y `zig` no van acá: viven en el lab (`/work/dev-fs/tools`) y el lanzador ya los pone en PATH. # # ── TRES DE LAS DIEZ NO SON ESTÁTICAS, Y ADENTRO NO ARRANCAN (2026-09-18, medido) ─────────────── # `rustc`, `cargo` y `python3` son musl **dinámicas**. Su `PT_INTERP` pide `/lib/ld-musl-x86_64.so.1` # y **la imagen de Arch no lo tiene**, así que adentro las tres mueren con # # …/usr/bin/cargo: cannot execute: required file not found # # `LD_LIBRARY_PATH` no puede taparlo: actúa DESPUÉS de que el cargador cargue, y acá el que falta es # el cargador. Por eso este guion pasó en verde la primera vez: se corrió EN LA CAJA, que es musl y # lo tiene nativo. El control decía la verdad sobre el sitio equivocado. # # La cura es invocar el cargador del lab explícitamente. Y el envoltorio de `rustc` tiene que # llamarse `rustc` **en el PATH**, no sólo existir: cargo lanza a `rustc` POR NOMBRE, y sin eso muere # con `could not execute process … rustc -vV (never executed)`, que no nombra la causa. # # ── Y `cc` TIENE QUE FILTRAR EL TRIPLE QUE LE PASA cc-rs (2026-09-18, medido) ─────────────────── # `zig cc` a secas no sirve de `cc` para cargo. cc-rs le pasa SU triple y zig no lo entiende: # # $ zig cc --target=x86_64-unknown-linux-musl -o h h.c # error: unable to parse target query 'x86_64-unknown-linux-musl': UnknownOperatingSystem # # Eso tumba `blake3`, que cuelga de medio árbol de tawasuyu. Es el MISMO defecto que tiene el `cc` # de `cc-por-zig.sh` en la caja, arreglado allá igual que acá. # # ── Y LAS BANDERAS DE ENLACE VAN EN EL ENVOLTORIO, NO EN /etc/profile.d ───────────────────────── # En la caja las pone `cc-por-zig.sh` en `/etc/profile.d/rust-enlazador.sh`. Acá eso NO serviría: la # jaula no corre shells de login —el lanzador hace `exec` directo— así que el fichero quedaría # presente, con contenido, y sin efecto: justo la avería que este repo persigue. Van adentro del # envoltorio de `rustc`, que es el único sitio por el que pasan TODAS las compilaciones, incluidas # las de los build scripts (a los que `RUSTFLAGS` no alcanza cuando se pasa `--target`). # Van PRIMERO para que lo que ponga la persona en `RUSTFLAGS` gane: en rustc, la última `-C` manda. set -eu U=${1:-sergio} BIN=${BIN:-/work/$U/bin} install -d -o "$U" -g "$U" "$BIN" # ── El lab: de ahí sale el cargador musl que la imagen ajena no tiene ─────────────────────────── LAB=${TAKANA_LAB:-} if [ -z "$LAB" ]; then for d in /work/dev-fs/alpine "/work/$U/dev-fs/alpine"; do [ -x "$d/lib/ld-musl-x86_64.so.1" ] && { LAB=$d; break; } done fi [ -n "$LAB" ] || { echo "no encuentro el alpine del lab (probé /work/dev-fs/alpine y /work/$U/dev-fs/alpine)" >&2; exit 1; } echo "==> lab en $LAB" # El zig del CORPUS, el mismo que usa cc-por-zig.sh en la caja. No se toma el de `/work/dev-fs/tools` # para que el `cc` de adentro y el de afuera sean el mismo binario. ZIG=$(find /store -maxdepth 3 -type f -perm -100 -name zig -path '*seed-zig*' 2>/dev/null | head -1) [ -n "$ZIG" ] || { echo "no encuentro el zig de seed-zig en /store — sin él no hay driver de C" >&2; exit 1; } echo "==> zig $("$ZIG" version) en $ZIG" # Cuerpo común de los envoltorios. $1 = nombre del binario, $2 = nombre de la RECETA, $3 = extra. # # ⚠ El nombre de la receta se compara ENTERO, no por sufijo: el glob `/store/*-make` también casa # con `…-cargo-make` —medido: la primera versión eligió cargo-make y `make --version` moría con un # `exec` que no nombraba la causa—. El directorio es `-`: se le quita el hash y se # compara. Y se elige el MÁS RECIENTE, no un hash cableado, para sobrevivir a un re-sellado. busca_artefacto() { echo "b=\$(ls -dt /store/*-$1 2>/dev/null | while read -r d; do n=\${d##*/}; [ \"\${n#*-}\" = \"$1\" ] && { echo \"\$d\"; break; }; done)" echo "[ -n \"\$b\" ] || { echo '$2: no hay artefacto -$1 en /store' >&2; exit 1; }" } envoltorio() { t=$1; r=$2; pre=$3 { echo '#!/bin/sh' echo "# Envoltorio: $t sale del store (musl estático, corre dentro de la imagen glibc)." busca_artefacto "$r" "$t" echo "$pre" echo "exec \"\$b/usr/bin/$t\" \"\$@\"" } > "$BIN/$t" chmod 755 "$BIN/$t" } # Las dinámicas: por el cargador del lab, y sólo si la caja no tiene el suyo. Así este mismo guion # sirve en la caja (musl, cargador nativo) y en la jaula (Arch, sin cargador musl) sin ramificar. # $1 = binario, $2 = receta, $3 = banderas que el envoltorio antepone a los argumentos. envoltorio_dinamico() { t=$1; r=$2; flags=$3 { echo '#!/bin/sh' echo "# Envoltorio: $t sale del store. Es musl DINÁMICA: sin el cargador no arranca (ver cabecera)." busca_artefacto "$r" "$t" echo "L=\"$LAB/lib:$LAB/usr/lib:\$b/usr/lib\"" echo 'if [ -x /lib/ld-musl-x86_64.so.1 ]; then' echo " LD_LIBRARY_PATH=\"\$L\${LD_LIBRARY_PATH:+:\$LD_LIBRARY_PATH}\" exec \"\$b/usr/bin/$t\" $flags \"\$@\"" echo 'else' echo " exec \"$LAB/lib/ld-musl-x86_64.so.1\" --library-path \"\$L\" \"\$b/usr/bin/$t\" $flags \"\$@\"" echo 'fi' } > "$BIN/$t" chmod 755 "$BIN/$t" } for par in make:make pkgconf:pkgconf jq:jq rsync:rsync git:git curl:curl takana:takana; do envoltorio "${par%%:*}" "${par##*:}" ":" done # `python3` y `cargo` no llevan banderas; `rustc` sí — son las tres que necesita el enlace en este # triple, las mismas que `cc-por-zig.sh` pone en la caja. `link-self-contained=no` porque este # toolchain no trae `rust-lld` (incompatible con el llvm-config externo). envoltorio_dinamico python3 python3 "" envoltorio_dinamico cargo rust "" envoltorio_dinamico rustc rust "-C linker=$BIN/cc -C linker-flavor=gcc -C link-self-contained=no" # ── El driver de C ───────────────────────────────────────────────────────────────────────────── # La caché de zig NO va en el home: son varios G. Misma razón que en cc-por-zig.sh. CACHE=${CACHE:-/work/$U/.cache/zig} install -d -o "$U" -g "$U" "$CACHE" 2>/dev/null || mkdir -p "$CACHE" for sub in cc c++; do nombre=$sub; [ "$sub" = "c++" ] && nombre=c++ cat > "$BIN/$nombre" </dev/null || : # ── CONTROL: que CORRAN, no que existan ───────────────────────────────────────────────────────── # ⚠ Sin tubería: `cmd | head` devuelve el estado de `head`, o sea SIEMPRE 0, y la primera versión de # este control dio por bueno un envoltorio que fallaba al `exec`. Se captura primero, se recorta después. fallos=0 for t in make pkgconf python3 jq rsync git curl takana rustc cargo; do if out=$("$BIN/$t" --version 2>&1); then printf " ✓ %-9s %s\n" "$t" "$(printf '%s' "$out" | head -1 | cut -c1-46)" else printf " ✗ %-9s %s\n" "$t" "$(printf '%s' "$out" | head -1 | cut -c1-60)"; fallos=$((fallos+1)) fi done # ── CONTROL: que COMPILEN, que es otra cosa ──────────────────────────────────────────────────── # `--version` no toca ni el driver de C ni el enlazador, así que no habría visto NINGUNO de los dos # defectos de arriba. Estos tres sí, y ninguno necesita red: # 1. C con el `--target=` de cc-rs → el defecto que tumbaba blake3 # 2. una proc-macro → el enlace de dylibs, que es lo que rompía sin las banderas # 3. un binario estático → que el remedio no sea peor que la enfermedad T=$(mktemp -d); trap 'rm -rf "$T"' EXIT printf '#include \nint main(void){ puts("C"); return 0; }\n' > "$T/h.c" if "$BIN/cc" --target=x86_64-unknown-linux-musl -o "$T/h" "$T/h.c" 2>"$T/e1" && [ "$("$T/h")" = "C" ]; then printf " ✓ %-9s con el --target= de cc-rs\n" "cc" else printf " ✗ %-9s %s\n" "cc" "$(head -1 "$T/e1")"; fallos=$((fallos+1)) fi printf 'extern crate proc_macro;\nuse proc_macro::TokenStream;\n#[proc_macro]\npub fn m(_i: TokenStream) -> TokenStream { "fn v() -> u32 { 42 }".parse().unwrap() }\n' > "$T/pm.rs" if "$BIN/rustc" --edition 2021 --crate-type proc-macro --out-dir "$T" "$T/pm.rs" 2>"$T/e2" && [ -n "$(ls "$T"/*.so 2>/dev/null)" ]; then printf " ✓ %-9s proc-macro (dylib) enlazada\n" "rustc" else printf " ✗ %-9s %s\n" "rustc" "$(grep -m1 error "$T/e2" 2>/dev/null || head -1 "$T/e2")"; fallos=$((fallos+1)) fi printf 'fn main(){ println!("estatico"); }\n' > "$T/s.rs" if "$BIN/rustc" --edition 2021 -o "$T/s" "$T/s.rs" 2>"$T/e3" && [ "$("$T/s")" = "estatico" ]; then printf " ✓ %-9s binario estático\n" "rustc" else printf " ✗ %-9s %s\n" "rustc" "$(grep -m1 error "$T/e3" 2>/dev/null || head -1 "$T/e3")"; fallos=$((fallos+1)) fi [ "$fallos" -eq 0 ] || { echo "==> $fallos comprobación(es) en rojo" >&2; exit 1; } echo "==> $BIN listo"