From 879af17f5c30503eea45da2c459d8377f7c1810b Mon Sep 17 00:00:00 2001 From: Sergio Date: Thu, 11 Jun 2026 11:24:01 +0000 Subject: [PATCH] =?UTF-8?q?build:=20flags=20est=C3=A1ticos=20v=C3=ADa=20`c?= =?UTF-8?q?argo=20rustc=20--=20=E2=80=A6`=20(no=20RUSTFLAGS)=20para=20no?= =?UTF-8?q?=20romper=20proc-macros?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tercera iteración del fix de la corrida real (runbook §8). +crt-static (y relocation-model=static) en RUSTFLAGS rompen los proc-macros en build nativo: RUSTFLAGS alcanza a las deps, y un proc-macro es un dylib PIC que no puede ser estático ("cannot produce proc-macro for clap_derive ... crate types"). Fix: pasar los flags de codegen del binario por `cargo rustc --release … -- `, que los aplica SÓLO a la crate top-level, dejando proc-macros/deps intactos. Así el binario final es crt-static + relocation-model=static (AUTOCONTENIDO y ET_EXEC sin interpreter, como busybox: sin libc.so, sin loader, sin soname). El linker (zig cc) sigue por RUSTFLAGS. musl queda --disable-shared (no se necesita loader). Validado por unit tests; la corrida real rebuildea hammerd/arje-zero crt-static. Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/hammer-build/src/lib.rs | 35 +++++++++++++++++++--------------- recipes/musl.toml | 4 ++-- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/crates/hammer-build/src/lib.rs b/crates/hammer-build/src/lib.rs index 1a2aabcb..28fb1877 100644 --- a/crates/hammer-build/src/lib.rs +++ b/crates/hammer-build/src/lib.rs @@ -288,23 +288,27 @@ fn resolve_phases(recipe: &Recipe, src: &Path) -> hammer_core::Result { // (`UnknownOperatingSystem`); lo reescribimos a `x86_64-linux-musl`. Se usa como // CC (cc-rs) y como linker (RUSTFLAGS, target-agnóstico — el default sería // cc/gcc, ausente en el sandbox zig-only). - let crt = match recipe.build.link { - // Estático = AUTOCONTENIDO: el rust de Alpine enlaza musl dinámico por - // defecto (deja `DT_NEEDED libc.musl-x86_64.so.1` y rompe el boot — el - // loader no resuelve memcpy/…; ver runbook §8). `+crt-static` mete musl - // dentro; `relocation-model=static` lo hace ET_EXEC sin interpreter (vía de - // oro de hammer, como busybox: sin libc.so, sin loader, sin soname). - LinkMode::Static => " -C target-feature=+crt-static -C relocation-model=static", + // Flags de codegen del BINARIO FINAL, vía `cargo rustc -- …` (NO RUSTFLAGS): en + // build nativo RUSTFLAGS también alcanza a los proc-macros (deps), y + // `+crt-static`/`relocation-model=static` los rompe ("cannot produce + // proc-macro" — un proc-macro es un dylib PIC). `cargo rustc -- …` los aplica + // sólo a la crate top-level, dejando los proc-macros intactos. + let bin_flags = match recipe.build.link { + // Estático AUTOCONTENIDO y no-PIE: `+crt-static` mete musl dentro (sin + // DT_NEEDED — el rust de Alpine enlaza musl dinámico por defecto, lo que deja + // `libc.musl-x86_64.so.1` y rompe el boot, runbook §8); `relocation-model=static` + // ⇒ ET_EXEC sin interpreter (vía de oro: como busybox, sin libc.so/loader/soname). + LinkMode::Static => "-C target-feature=+crt-static -C relocation-model=static", // Dinámico: apaga crt-static para poder dlopen (necesita el loader/musl en el rootfs). - LinkMode::Dynamic => " -C target-feature=-crt-static", + LinkMode::Dynamic => "-C target-feature=-crt-static", }; Some(format!( "printf '%s\\n' '#!/bin/sh' 'for a do' \ 'case \"$a\" in --target=*) a=--target=x86_64-linux-musl ;; esac' \ 'set -- \"$@\" \"$a\"' 'shift' 'done' 'exec zig cc \"$@\"' \ > \"$PWD/.hammer-zig-cc\" && chmod +x \"$PWD/.hammer-zig-cc\" && \ - CC=\"$PWD/.hammer-zig-cc\" RUSTFLAGS=\"-C linker=$PWD/.hammer-zig-cc{crt}\" \ - cargo build --release --locked --offline{flags_suffix}" + CC=\"$PWD/.hammer-zig-cc\" RUSTFLAGS=\"-C linker=$PWD/.hammer-zig-cc\" \ + cargo rustc --release --locked --offline{flags_suffix} -- {bin_flags}" )) } else { // CROSS real (otro target que zig sí entiende): `--target` + linker por env @@ -433,13 +437,13 @@ commit = "deadbeef" let p = resolve_phases(&recipe(&[]), d.path()).unwrap(); assert!(p.configure.is_none(), "cargo no usa configure"); let c = p.compile.unwrap(); - assert!(c.contains("cargo build --release"), "{c}"); + // Nativo: `cargo rustc` (no `cargo build`) para pasar los flags de codegen tras `--` sólo + // a la crate final; sin `--target` (lo cross sí; el único --target es el del wrapper saneador). + assert!(c.contains("cargo rustc --release"), "{c}"); assert!(c.contains("--locked") && c.contains("--offline"), "{c}"); - // Nativo: `cargo build` NO lleva --target (lo cross sí); el único --target del comando es - // el del wrapper saneador del triple alpine. assert!(!c.contains("--offline --target"), "nativo: cargo sin --target ({c})"); assert!(c.contains("a=--target=x86_64-linux-musl"), "wrapper sanea el triple ({c})"); - assert!(c.contains("target-feature=+crt-static"), "estático = autocontenido ({c})"); + assert!(c.contains("-- -C target-feature=+crt-static"), "crt-static scoped tras -- ({c})"); assert!(c.contains("relocation-model=static"), "no-PIE: ET_EXEC sin interpreter ({c})"); assert!(c.contains("CC=\"$PWD/.hammer-zig-cc\""), "CC = wrapper para cc-rs ({c})"); assert!(c.contains("RUSTFLAGS=\"-C linker=$PWD/.hammer-zig-cc"), "linker por RUSTFLAGS ({c})"); @@ -478,7 +482,8 @@ commit = "deadbeef" let d = tempfile::tempdir().unwrap(); touch(d.path(), "Cargo.toml"); let p = resolve_phases(&recipe(&["--features", "gpu"]), d.path()).unwrap(); - assert!(p.compile.unwrap().ends_with("--features gpu")); + // Los flags de la receta van como args de cargo (antes del `--` que separa los de rustc). + assert!(p.compile.unwrap().contains("--features gpu -- ")); } #[test] diff --git a/recipes/musl.toml b/recipes/musl.toml index b3c59692..e46ef5a3 100644 --- a/recipes/musl.toml +++ b/recipes/musl.toml @@ -18,8 +18,8 @@ link = "static" [build.phases] # El sandbox exporta CC="zig cc", DESTDIR=/out y PREFIX=/usr. musl trae su propio `configure`. -# Sólo estático: los componentes Rust se compilan crt-static (autocontenidos, sin libc.so), así que -# el rootfs no necesita el loader/shared musl. Ver runbooks/stage1-vm-boot.md §8. +# Sólo estático: los componentes Rust se compilan crt-static + no-PIE (autocontenidos, ET_EXEC sin +# interpreter), así que el rootfs no necesita el loader/shared de musl. Ver runbooks §8. configure = "./configure --prefix=/usr --disable-shared --enable-static" compile = "make -j\"$(nproc)\"" install = "make install"