From 6e4d8da779ed558e4bda8c33e7682156aace58a5 Mon Sep 17 00:00:00 2001 From: sergio Date: Sun, 21 Jun 2026 08:31:56 -0400 Subject: [PATCH] =?UTF-8?q?Etapa=20G=20build-yield:=20imports=20Rust=20de?= =?UTF-8?q?=20nix=20BUILDEAN=20(aislamiento=20[workspace]=20gen=C3=A9rico?= =?UTF-8?q?=20+=20filtro=20toolchain)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Midiendo build-yield real con la capa puesta: hyperfine (nix) construye end-to-end → ELF estático musl que corre. Dos fixes que lo desbloquean genéricamente (sin patch por receta): - nix_import.rs: rustc/cargo/rust se filtran de deps (son el LAB, no paquetes) — sin esto el build abortaba buscando rustc.toml. Default de flags Rust vuelve a `--bin ` (el `-p ` no generaliza: el paquete cargo del bin puede ≠ pname, p.ej. sd→sd-cli). - hammer-build/lib.rs: `ensure_cargo_workspace_isolation` inyecta `[workspace]` vacío al Cargo.toml de la fuente si no lo tiene, ANTES de vendor. Idempotente ⇒ no choca con las recetas del corpus que lo parchean a mano. Resuelve el gotcha "fuente dentro del workspace hammer ⇒ cargo vendor aborta" para CUALQUIER import Rust. BUILD-YIELD medido (real, con la capa): lz4 (C/Alpine, escape gcc) ✓ · hyperfine (Rust/nix) ✓ · sd (Rust) ✗ workspace-virtual con bin en paquete ≠pname (necesita `-p` manual). Texture honesta: los bien-estructurados buildean solos; los con quirks de workspace necesitan toque per-paquete. 31 suites verde. Co-Authored-By: Claude Opus 4.8 --- crates/hammer-build/src/lib.rs | 31 +++++++++++++++++++++++++++++ crates/hammer-cli/src/nix_import.rs | 14 +++++++++---- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/crates/hammer-build/src/lib.rs b/crates/hammer-build/src/lib.rs index b5cea324..d2f3d02c 100644 --- a/crates/hammer-build/src/lib.rs +++ b/crates/hammer-build/src/lib.rs @@ -173,6 +173,13 @@ pub fn build( // Recetas Cargo: vendoreamos las deps (red permitida en el fetch) para que el build sea // hermético `--offline` dentro del sandbox. Tras los patches, por si tocaron Cargo.toml/lock. if matches!(detect_build_system(&src_tree), BuildSys::Cargo) { + // El source se copia DENTRO del repo hammer (que es un workspace Cargo) ⇒ si el Cargo.toml + // de la fuente NO declara su propio `[workspace]`, cargo cree que pertenece al workspace de + // hammer y `cargo vendor` aborta. Inyectamos un `[workspace]` vacío para aislarla. SÓLO si + // no lo tiene ya (las recetas del corpus que lo parchean a mano siguen funcionando sin + // duplicar la tabla). Esto desbloquea genéricamente los imports Rust (Etapa G) sin un patch + // por receta. Ver memoria 'cargo-recipe crate suelto'. + ensure_cargo_workspace_isolation(&src_tree)?; tracing::info!("cargo: vendoreando deps para build offline"); fetch::vendor_cargo_deps(&src_tree)?; } @@ -273,6 +280,30 @@ enum BuildSys { Unknown, } +/// Garantiza que el `Cargo.toml` raíz de la fuente declare un `[workspace]` propio, para que cargo +/// NO la considere parte del workspace del repo hammer (que la contiene en `work/sources/`). Append +/// idempotente: si ya hay `[workspace]` (crate suelto que es su propio workspace, o una receta que +/// lo parchea), no toca nada. Caso virtual-manifest (root ya es `[workspace]`) tampoco se toca. +fn ensure_cargo_workspace_isolation(src: &Path) -> hammer_core::Result<()> { + let manifest = src.join("Cargo.toml"); + let text = std::fs::read_to_string(&manifest)?; + // Heurística simple: ¿hay una tabla `[workspace]` (al inicio de línea)? Cubre `[workspace]` y + // `[workspace.members]`/`[workspace.dependencies]` (un root virtual siempre trae alguna). + let has_workspace = text + .lines() + .any(|l| l.trim_start().starts_with("[workspace]") || l.trim_start().starts_with("[workspace.")); + if !has_workspace { + let mut patched = text; + if !patched.ends_with('\n') { + patched.push('\n'); + } + patched.push_str("\n[workspace]\n"); + std::fs::write(&manifest, patched)?; + tracing::info!("cargo: inyectado [workspace] vacío para aislar la fuente del repo hammer"); + } + Ok(()) +} + fn detect_build_system(src: &Path) -> BuildSys { let has = |p: &str| src.join(p).exists(); // El orden importa: cuando coexisten varios (un tarball release de autotools trae el diff --git a/crates/hammer-cli/src/nix_import.rs b/crates/hammer-cli/src/nix_import.rs index 9d421236..f86cf41e 100644 --- a/crates/hammer-cli/src/nix_import.rs +++ b/crates/hammer-cli/src/nix_import.rs @@ -172,6 +172,9 @@ pub fn to_recipe_toml(pkg: &NixPkg) -> Result { // El bin = meta.mainProgram de nix (ripgrep→rg), o el pname. Esto deja la receta build-ready. let (flags_line, rust_install) = if pkg.is_rust { let bin = if pkg.main_program.is_empty() { name.clone() } else { sanitize_name(&pkg.main_program) }; + // `--bin `: default para el caso común (crate suelto, pname=paquete cargo). Los repos + // con WORKSPACE VIRTUAL en el root (p.ej. sd: el bin vive en el paquete `sd-cli`≠pname) + // necesitan que el humano agregue `-p ` — el import no puede adivinar el nombre. ( format!("flags = [\"--bin\", \"{bin}\"]\n"), format!( @@ -230,6 +233,10 @@ fn is_nix_noise(name: &str) -> bool { || name == "auditable-cargo" || name.starts_with("auditable-cargo-") || name == "version-check" + // El toolchain Rust ES el lab de hammer (cargo vendorea las crates), NO un paquete dep. + || name == "rustc" + || name == "cargo" + || name == "rust" } /// Nombre de receta válido: minúsculas, sólo `[a-z0-9._-]`, sin espacios. nix usa nombres limpios @@ -420,10 +427,9 @@ mod tests { }"#; let pkg: NixPkg = serde_json::from_str(json).unwrap(); let recipe = hammer_core::Recipe::from_toml(&to_recipe_toml(&pkg).unwrap()).unwrap(); - // pcre2 y rustc sobreviven (paquetes reales); los hooks/wrappers/shell-files se filtran. - // Orden = inserción (native primero, luego build): los native son todos ruido ⇒ quedan - // pcre2, rustc en el orden de build_inputs. - assert_eq!(recipe.deps.build, vec!["pcre2", "rustc"]); + // pcre2 sobrevive (paquete real); hooks/wrappers/shell-files y el toolchain rustc/cargo + // se filtran (rustc/cargo son el lab, no deps). + assert_eq!(recipe.deps.build, vec!["pcre2"]); } #[test]