Etapa G build-yield: imports Rust de nix BUILDEAN (aislamiento [workspace] genérico + filtro toolchain)
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 <bin>` (el `-p <pname>` 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 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -172,6 +172,9 @@ pub fn to_recipe_toml(pkg: &NixPkg) -> Result<String, String> {
|
||||
// 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 <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 <paquete-real>` — 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]
|
||||
|
||||
Reference in New Issue
Block a user