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.
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user