gioser

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1184Shell 70Rust 62Markdown 501503 Definiciones

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>

6e4d8da779ed558e4bda8c33e7682156aace58a5 · por sergio · (hace 4 meses) · padre c3f890c7d · ver el árbol en este commit

Delta semántico · 4

crates/hammer-build/src/lib.rs

crates/hammer-cli/src/nix_import.rs

2 archivos · +41 −4

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<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]