diff --git a/crates/hammer-build/src/lib.rs b/crates/hammer-build/src/lib.rs index 3682913c..4a39d7b8 100644 --- a/crates/hammer-build/src/lib.rs +++ b/crates/hammer-build/src/lib.rs @@ -217,6 +217,15 @@ pub fn build( env.push(("AR".into(), "llvm-ar".into())); } } + // Autotools (Etapa G Fase 3): exporta CBUILD/CHOST con el triple nativo SANEADO del sandbox + // (`x86_64-linux-musl`, el mismo que el wrapper zig-cc emite). Las recetas importadas de Alpine + // traducen `./configure --build=$CBUILD --host=$CHOST` literal del abuild (`translate_abuild` los + // deja intactos a propósito); sin estas envs `$CBUILD/$CHOST` quedan VACÍAS y autotools cae a + // `config.guess`, que detecta `x86_64-alpine-linux-musl` — vendor que zig NO conoce + // (`UnknownOperatingSystem`). Resuelve el residuo autotools que antes era trabajo a mano. Inerte + // para Cargo/CMake/Meson (no leen estas envs). + env.push(("CBUILD".into(), SANDBOX_NATIVE_TARGET.to_string())); + env.push(("CHOST".into(), SANDBOX_NATIVE_TARGET.to_string())); if let Some(cache) = &cfg.cache_root { std::fs::create_dir_all(cache.join("zig"))?; } @@ -450,12 +459,26 @@ fn resolve_phases(recipe: &Recipe, src: &Path) -> hammer_core::Result { let flags = recipe.build.flags.join(" "); let flags_suffix = if flags.is_empty() { String::new() } else { format!(" {flags}") }; + // Autotools (Etapa G Fase 3): fija `--build`/`--host` al triple nativo saneado para que + // `config.guess` no detecte el vendor `alpine` que zig rechaza, e iguala lo que el wrapper zig-cc + // produce. Sólo si la receta no los puso ya en sus flags (juicio per-paquete gana). build==host + // ⇒ NATIVO (no cross): autotools sigue corriendo sus AC_RUN tests; sólo normaliza el triple. + let autotools_triple = if recipe.build.flags.iter().any(|f| { + f.starts_with("--build=") || f.starts_with("--host=") + }) { + String::new() + } else { + format!(" --build={SANDBOX_NATIVE_TARGET} --host={SANDBOX_NATIVE_TARGET}") + }; + if out.configure.is_none() { out.configure = match bs { BuildSys::AutoconfRaw => Some(format!( - "autoreconf -fi && ./configure --prefix=/usr{flags_suffix}" + "autoreconf -fi && ./configure --prefix=/usr{autotools_triple}{flags_suffix}" )), - BuildSys::AutoconfReady => Some(format!("./configure --prefix=/usr{flags_suffix}")), + BuildSys::AutoconfReady => { + Some(format!("./configure --prefix=/usr{autotools_triple}{flags_suffix}")) + } BuildSys::CMake => Some(format!( "cmake -S . -B _build -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release{flags_suffix}" )), @@ -623,6 +646,40 @@ commit = "deadbeef" assert!(c.ends_with("--enable-foo"), "{c}"); } + #[test] + fn autotools_injects_native_triple() { + // Etapa G Fase 3: configure heurístico fija --build/--host al triple saneado. + let d = tempfile::tempdir().unwrap(); + touch(d.path(), "configure.ac"); + touch(d.path(), "configure"); + let c = resolve_phases(&recipe(&[]), d.path()).unwrap().configure.unwrap(); + assert!(c.contains("--build=x86_64-linux-musl"), "{c}"); + assert!(c.contains("--host=x86_64-linux-musl"), "{c}"); + } + + #[test] + fn autotools_triple_yields_to_recipe_flags() { + // Si la receta ya fija el triple (juicio per-paquete), no duplicamos. + let d = tempfile::tempdir().unwrap(); + touch(d.path(), "configure.ac"); + touch(d.path(), "configure"); + let c = resolve_phases(&recipe(&["--host=aarch64-linux-musl"]), d.path()) + .unwrap() + .configure + .unwrap(); + assert!(c.contains("--host=aarch64-linux-musl"), "{c}"); + assert!(!c.contains("x86_64-linux-musl"), "no inyecta si la receta ya lo puso ({c})"); + } + + #[test] + fn cmake_ignores_autotools_triple() { + // El triple es exclusivo de autotools; CMake/Meson/Cargo no lo reciben. + let d = tempfile::tempdir().unwrap(); + touch(d.path(), "CMakeLists.txt"); + let c = resolve_phases(&recipe(&[]), d.path()).unwrap().configure.unwrap(); + assert!(!c.contains("--host="), "{c}"); + } + #[test] fn detect_cmake() { let d = tempfile::tempdir().unwrap(); diff --git a/crates/hammer-cli/src/alpine_import.rs b/crates/hammer-cli/src/alpine_import.rs index 4c53c5d5..2e850925 100644 --- a/crates/hammer-cli/src/alpine_import.rs +++ b/crates/hammer-cli/src/alpine_import.rs @@ -106,7 +106,7 @@ pub fn recipe_from_apkbuild(text: &str) -> Result { let mut s = String::from("\n[build.phases]\n"); if let Some(b) = compile { let t = translate_abuild(&b, &pkgname, &version); - s.push_str(&format!("# de build() de Alpine (traducido; revisá $CBUILD/$CHOST/--shared para estático):\ncompile = {}\n", toml_multiline(&t))); + s.push_str(&format!("# de build() de Alpine (traducido; el lab provee $CBUILD/$CHOST — Etapa G Fase 3; revisá --shared para estático):\ncompile = {}\n", toml_multiline(&t))); } if let Some(p) = install { let t = translate_abuild(&p, &pkgname, &version); @@ -249,8 +249,9 @@ fn func_body(text: &str, name: &str) -> Option { /// Traduce las variables de abuild a su equivalente en el lab de hammer. Substituciones SEGURAS /// (inequívocas): `$pkgdir`→`/out` (el DESTDIR del lab), `$pkgname`→nombre, `$pkgver`→versión. -/// NO toca `$CBUILD/$CHOST/$srcdir/$builddir` ni `--shared` (juicio por-paquete; quedan para el -/// humano, marcados en el comentario de la fase). `make DESTDIR="$pkgdir"` → `make DESTDIR="/out"`. +/// NO toca `$CBUILD/$CHOST/$srcdir/$builddir` ni `--shared`. `$CBUILD/$CHOST` los RESUELVE el lab en +/// runtime (env con el triple nativo saneado, Etapa G Fase 3 — `hammer-build`); `$srcdir/$builddir` y +/// `--shared` quedan para el humano. `make DESTDIR="$pkgdir"` → `make DESTDIR="/out"`. fn translate_abuild(body: &str, name: &str, version: &str) -> String { body.replace("${pkgdir}", "/out") .replace("$pkgdir", "/out")