tawasuyu

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 1572Shell 203Rust 109Markdown 993728 Definiciones

build: memoizar artifact_hash (DAG-diamante) — plasma-workspace (~90 deps) colgaba en walk exponencial de caminos

El grafo de build de KF6/Qt converge en qtbase/kcoreaddons por decenas de rutas; artifact_hash_rec
recorría cada camino sin caché ⇒ O(nº caminos) exponencial. Memo por path canónico de receta.
El hash resultante es idéntico (sólo cachea) ⇒ no invalida artefactos sellados.

0f3ec4feac7ff82b9d780b9c381fb4d72bddf33c · por sergio · (hace 3 meses) · padre a86b49122 · ver el árbol en este commit

Delta semántico · 3

crates/hammer-build/src/lib.rs

1 archivos · +25 −4

diff --git a/crates/hammer-build/src/lib.rs b/crates/hammer-build/src/lib.rs
index 1c9ee52d..f40b4d0f 100644
--- a/crates/hammer-build/src/lib.rs
+++ b/crates/hammer-build/src/lib.rs
@@ -1,6 +1,7 @@
 //! El laboratorio: compila una receta de forma hermética y la sella en el store; e hidrata
 //! artefactos al FHS. Ver `docs/02-build-lab.md` y `docs/03-hydration.md`.
 
+use std::collections::HashMap;
 use std::path::{Path, PathBuf};
 
 use hammer_core::{ArtifactHash, Compiler, LinkMode, Phases, Recipe, Store};
@@ -30,10 +31,21 @@ pub use swm_bridge::build_source_patch;
 /// fallan con un mensaje que nombra la cadena.
 pub fn artifact_hash(recipe: &Recipe, _store: &Store) -> hammer_core::Result<ArtifactHash> {
     let mut stack: Vec<String> = Vec::new();
-    artifact_hash_rec(recipe, &mut stack)
+    // Memo por path canónico de receta: el grafo de build es un DAG-diamante (p.ej. TODO KF6/Qt
+    // converge en qtbase/kcoreaddons, alcanzables por decenas de rutas). Sin memoizar, el walk
+    // recursivo es exponencial en NÚMERO DE CAMINOS y cuelga en paquetes grandes (plasma-workspace,
+    // ~90 deps). El hash de una receta es función pura de su subárbol ⇒ computar una vez por nodo.
+    // Clave = path resuelto (no `name`): dos variantes homónimas (incoming-kde/libxml2 vs canónica)
+    // no deben colisionar. Ver granja-promote-colisiones.
+    let mut memo: HashMap<PathBuf, ArtifactHash> = HashMap::new();
+    artifact_hash_rec(recipe, &mut stack, &mut memo)
 }
 
-fn artifact_hash_rec(recipe: &Recipe, stack: &mut Vec<String>) -> hammer_core::Result<ArtifactHash> {
+fn artifact_hash_rec(
+    recipe: &Recipe,
+    stack: &mut Vec<String>,
+    memo: &mut HashMap<PathBuf, ArtifactHash>,
+) -> hammer_core::Result<ArtifactHash> {
     if stack.iter().any(|n| n == &recipe.name) {
         stack.push(recipe.name.clone());
         return Err(hammer_core::Error::Recipe(format!(
@@ -42,7 +54,7 @@ fn artifact_hash_rec(recipe: &Recipe, stack: &mut Vec<String>) -> hammer_core::R
         )));
     }
 
-    let dep_hashes = resolve_build_dep_hashes(recipe, stack)?;
+    let dep_hashes = resolve_build_dep_hashes(recipe, stack, memo)?;
     let inputs = recipe.hash_inputs(&dep_hashes)?;
     let refs: Vec<&[u8]> = inputs.iter().map(|v| v.as_slice()).collect();
     Ok(ArtifactHash::of_inputs(&refs))
@@ -73,6 +85,7 @@ fn resolve_dep_path(base_dir: &Path, dep: &str) -> PathBuf {
 fn resolve_build_dep_hashes(
     recipe: &Recipe,
     stack: &mut Vec<String>,
+    memo: &mut HashMap<PathBuf, ArtifactHash>,
 ) -> hammer_core::Result<Vec<ArtifactHash>> {
     if recipe.deps.build.is_empty() {
         return Ok(Vec::new());
@@ -89,6 +102,12 @@ fn resolve_build_dep_hashes(
     let mut hashes = Vec::with_capacity(recipe.deps.build.len());
     for dep in &recipe.deps.build {
         let dep_path = resolve_dep_path(&recipe.base_dir, dep);
+        // Clave del memo: path canónico (colapsa `./a/../b` y symlinks a una identidad estable).
+        let key = dep_path.canonicalize().unwrap_or_else(|_| dep_path.clone());
+        if let Some(h) = memo.get(&key) {
+            hashes.push(h.clone());
+            continue;
+        }
         let dep_recipe = Recipe::load_from_path(&dep_path).map_err(|e| {
             hammer_core::Error::Recipe(format!(
                 "no pude cargar la dep de build '{dep}' de '{}' ({}): {e}",
@@ -96,7 +115,9 @@ fn resolve_build_dep_hashes(
                 dep_path.display()
             ))
         })?;
-        hashes.push(artifact_hash_rec(&dep_recipe, stack)?);
+        let h = artifact_hash_rec(&dep_recipe, stack, memo)?;
+        memo.insert(key, h.clone());
+        hashes.push(h);
     }
     stack.pop();
     Ok(hashes)