diff --git a/crates/hammer-build/src/swm_bridge.rs b/crates/hammer-build/src/swm_bridge.rs index b7e91779..302bd797 100644 --- a/crates/hammer-build/src/swm_bridge.rs +++ b/crates/hammer-build/src/swm_bridge.rs @@ -107,14 +107,18 @@ pub fn recipe_from_source_patch( // Patch bajo un nombre derivado del nombre-de-receta + clave de origen, para no colisionar // entre `{dep}.patch` distintos en el mismo catálogo. let label = name.map(|n| n.to_string()).unwrap_or_else(|| derive_name(target_bin)); - let patch_path = catalog_dir.join(format!("{label}-{source_key}.patch")); + // `source.patches` guarda el NOMBRE relativo a `base_dir` (= catalog_dir); el lab resuelve + // `base_dir.join(nombre)` UNA vez al construir (fetch::apply_patches / Recipe hash). Guardar el + // path completo acá lo haría re-unir a base_dir aguas abajo (doble/triple prefijo). + let filename = format!("{label}-{source_key}.patch"); + let patch_path = catalog_dir.join(&filename); if let Some(text) = patch { std::fs::write(&patch_path, text.as_bytes())?; } else if let Some(url) = patch_url { tracing::info!(%url, "fetch: descargando patch remoto"); crate::download::fetch_url_to_file(url, &patch_path)?; } - patches.push(patch_path.to_string_lossy().into_owned()); + patches.push(filename); } let name = name.map(|n| n.to_string()).unwrap_or_else(|| derive_name(target_bin)); @@ -194,19 +198,11 @@ flags = [] if let Some(n) = strip_components { recipe.source.strip_components = n; } - // Resolución de patches relativos al recipe_dir donde acabamos de escribir los inline. - // Aceptamos rutas absolutas tal cual: las hemos generado nosotros. - recipe.source.patches = patches - .into_iter() - .map(|p| { - let path = Path::new(&p); - if path.is_absolute() { - p - } else { - base_dir.join(&p).to_string_lossy().into_owned() - } - }) - .collect(); + // `patches` ya viene como nombres relativos a `base_dir` (o rutas absolutas que generamos + // nosotros): el lab los resuelve con `base_dir.join(p)` UNA vez al construir. NO los re-unimos + // acá — hacerlo doblaba el prefijo `base_dir` y el join del build lo triplicaba (bug que el + // dogfood de `jq`, primer paquete con patch + build-dep instalado, destapó). + recipe.source.patches = patches; // Deps por NOMBRE (de la receta original): el lab las resolverá como `{dep}.toml` bajo el // `base_dir` — por eso base_dir = el catálogo (no "/"): los patches son absolutos y no lo // necesitan, pero la resolución de build-deps SÍ. `install` puebla el catálogo con los @@ -314,6 +310,37 @@ mod tests { } } + #[test] + fn inline_patch_resuelve_con_un_solo_join_de_base_dir() { + // Regresión: `source.patches` debe guardar el NOMBRE relativo a base_dir, de modo que el + // lab resuelva `base_dir.join(p)` UNA sola vez. El bug doblaba/triplicaba el prefijo + // `swm-recipes/` (lo destapó el dogfood de `jq`, primer paquete con patch instalado). + let d = tempfile::tempdir().unwrap(); + let catalog = d.path().join("swm-recipes"); + let m = Mutation::SourcePatch { + repo: None, + commit: None, + tarball: Some("https://x/jq-1.8.1.tar.gz".into()), + sha256: Some("2be64e7129cecb11d5906290eba10af694fb9e3e7f9fc208a311dc33ca837eb0".into()), + strip_components: None, + patch: Some("--- a\n+++ b\n".into()), + patch_url: None, + build: fake_swm_build(), + target_bin: "/usr/bin/jq".into(), + expected_hash: None, + deps: Default::default(), + }; + let recipe = recipe_from_source_patch(&m, None, &catalog).unwrap(); + assert_eq!(recipe.source.patches.len(), 1); + let p = &recipe.source.patches[0]; + // El nombre NO debe llevar embebido el dir del catálogo (eso causaba el doble prefijo). + assert!(!p.contains("swm-recipes"), "patch debe ser relativo a base_dir, no llevar el catálogo: {p}"); + // Y base_dir.join(p) — el ÚNICO join que hace el lab — debe dar el fichero real escrito. + let resolved = recipe.base_dir.join(p); + assert!(resolved.is_file(), "base_dir.join(patch) debe existir en un solo paso: {}", resolved.display()); + assert_eq!(std::fs::read(&resolved).unwrap(), b"--- a\n+++ b\n"); + } + #[test] fn parse_compiler_and_link() { assert_eq!(parse_compiler("zig-cc").unwrap(), Compiler::ZigCc);