diff --git a/crates/hammer-build/src/sandbox.rs b/crates/hammer-build/src/sandbox.rs index efabadfa..3bdc9fed 100644 --- a/crates/hammer-build/src/sandbox.rs +++ b/crates/hammer-build/src/sandbox.rs @@ -417,8 +417,11 @@ impl Sandbox { // rompe la reproducibilidad — un no-determinismo que Stage 2 cazó (runbook §8c, SDD 09 // §2). De paso cierra el SIGILL de AVX en qemu64 (runbook §7). Las rutas SIMD que sí // necesita el runtime (blake3/sha2) son asm con dispatch en runtime: siguen presentes. - ("CC", "zig cc -mcpu=baseline"), - ("CXX", "zig c++ -mcpu=baseline"), + // Driver vía wrapper (ensure_layout lo materializa en /usr/local/bin): idéntico a + // `zig cc -mcpu=baseline` salvo que quita `-static` en links `-shared` (extensiones + // Python y demás `.so` dlopen-ables). Ver write_cc_wrappers. + ("CC", CC_WRAPPER), + ("CXX", CXX_WRAPPER), ("AR", "zig ar"), ("LC_ALL", "C"), ("LANG", "C"), @@ -517,6 +520,61 @@ pub fn ensure_layout(rootfs: &Path, zig_dir: &Path) -> hammer_core::Result<()> { zig_dir.display() ))); } + write_cc_wrappers(rootfs)?; + Ok(()) +} + +/// Nombres (en `/usr/local/bin`, que está en el PATH del sandbox) del driver de compilador que +/// usan CC/CXX por defecto. Ver [`write_cc_wrappers`]. +pub const CC_WRAPPER: &str = "hammer-zig-cc"; +pub const CXX_WRAPPER: &str = "hammer-zig-cxx"; + +/// Materializa en el rootfs dos wrappers de linker-driver (`hammer-zig-cc`/`-cxx`) que envuelven a +/// `zig cc`/`zig c++`. Su ÚNICO efecto es QUITAR `-static` cuando el link también lleva `-shared`. +/// +/// El harness inyecta `LDFLAGS=-static` para las recetas `link=static` (binario monolítico que pide +/// la hidratación primaria). Pero meson/autotools aplican ese LDFLAGS a TODOS los targets por igual, +/// incluidas las extensiones Python (`python.extension_module` → `shared_module`): un `.so` que el +/// intérprete del sandbox debe `dlopen`. `-static` + `-shared` es contradictorio ⇒ el `.so` sale +/// ET_EXEC y `dlopen` da `Exec format error` (el muro de gobject-introspection: `_giscanner.so`). +/// +/// Quitar `-static` SÓLO cuando hay `-shared` es correcto por construcción: un objeto compartido +/// nunca debe ser estático. Para todo link normal (ejecutable/`.a`) el wrapper es transparente ⇒ +/// byte-idéntico a `zig cc`. El ArtifactHash se deriva de inputs (receta+deps), no de los bytes ⇒ +/// este cambio NO re-hashea nada sellado; sólo destraba builds nuevos con extensiones dinámicas. +fn write_cc_wrappers(rootfs: &Path) -> hammer_core::Result<()> { + use std::os::unix::fs::PermissionsExt; + let bindir = rootfs.join("usr/local/bin"); + std::fs::create_dir_all(&bindir).map_err(|e| { + hammer_core::Error::Other(anyhow::anyhow!("no pude crear {}: {e}", bindir.display())) + })?; + // `case " $* "` detecta `-shared` como palabra completa; el bucle rota los positional params + // (pop del frente, push al fondo salvo `-static`) ⇒ filtra preservando orden y seguro con espacios. + let body = |driver: &str| { + format!( + "#!/bin/sh\n\ + case \" $* \" in\n\ + \x20 *\" -shared \"*)\n\ + \x20 n=$#\n\ + \x20 while [ \"$n\" -gt 0 ]; do\n\ + \x20 arg=$1; shift\n\ + \x20 [ \"$arg\" = \"-static\" ] || set -- \"$@\" \"$arg\"\n\ + \x20 n=$((n - 1))\n\ + \x20 done\n\ + \x20 ;;\n\ + esac\n\ + exec {driver} -mcpu=baseline \"$@\"\n" + ) + }; + for (name, driver) in [(CC_WRAPPER, "zig cc"), (CXX_WRAPPER, "zig c++")] { + let path = bindir.join(name); + std::fs::write(&path, body(driver)).map_err(|e| { + hammer_core::Error::Other(anyhow::anyhow!("no pude escribir {}: {e}", path.display())) + })?; + std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o755)).map_err(|e| { + hammer_core::Error::Other(anyhow::anyhow!("no pude chmod {}: {e}", path.display())) + })?; + } Ok(()) }