> 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…
fetch: cargo_vendor_dir per-receta — no pisar el vendor/ propio del proyecto
`cargo vendor vendor` borra lo que no reconoce del dir destino; proyectos que
commitean su PROPIO vendor/ (mise → vendor/aqua-registry, 3.1MB que build.rs lee)
lo perdían → build.rs falla 'No such file'. Nuevo campo opcional [source]
cargo_vendor_dir (default 'vendor', sin cambios para las ~200 recetas selladas)
manda el vendoreo cargo a otro dir; el .cargo/config.toml que emite cargo vendor
ya apunta ahí. mise.toml usa '.hammer-cargo-vendor'. Validado en vivo: aqua-registry
sobrevive + compila 591 crates pasando los muros previos (openssl vía rustls + AR).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
diff --git a/crates/hammer-build/src/fetch.rs b/crates/hammer-build/src/fetch.rs
index 2b85694e..d1c17bab 100644
--- a/crates/hammer-build/src/fetch.rs
+++ b/crates/hammer-build/src/fetch.rs
@@ -206,7 +206,7 @@ fn verify_sha256(path: &Path, expected: &str) -> hammer_core::Result<()> {
/// (plan C.2 #5). Ver `docs/runbooks/stage1-vm-boot.md` §8.
///
/// Un crate sin deps de crates.io produce un config vacío y ningún `vendor/`: inocuo.
-pub fn vendor_cargo_deps(src: &Path) -> hammer_core::Result<()> {
+pub fn vendor_cargo_deps(src: &Path, vendor_dir: &str) -> hammer_core::Result<()> {
let locked = src.join("Cargo.lock").is_file();
let mut args: Vec<&str> = vec!["vendor"];
if locked {
@@ -218,7 +218,10 @@ pub fn vendor_cargo_deps(src: &Path) -> hammer_core::Result<()> {
tiempo (deps no pineadas). Committear el lock en la fuente para fijarlo (plan C.2 #5)."
);
}
- args.push("vendor");
+ // Directorio de salida (default `vendor`; override per-receta vía `[source] cargo_vendor_dir`
+ // para no pisar un `vendor/` propio del proyecto). `cargo vendor` emite el `[source.*]` a stdout
+ // apuntando a ESTE dir, así que el `.cargo/config.toml` que persistimos abajo resuelve solo.
+ args.push(vendor_dir);
let output = Command::new("cargo")
.current_dir(src)
.args(&args)
@@ -358,7 +361,7 @@ mod tests {
return;
}
- vendor_cargo_deps(src).unwrap();
+ vendor_cargo_deps(src, "vendor").unwrap();
assert!(src.join(".cargo/config.toml").is_file(), "debe escribir el config de vendoring");
}
@@ -382,7 +385,7 @@ mod tests {
return;
}
// No debe fallar por falta de --locked; genera el lock y escribe el config.
- vendor_cargo_deps(src).unwrap();
+ vendor_cargo_deps(src, "vendor").unwrap();
assert!(src.join(".cargo/config.toml").is_file());
assert!(src.join("Cargo.lock").is_file(), "vendor sin --locked genera el lock");
}
diff --git a/crates/hammer-build/src/lib.rs b/crates/hammer-build/src/lib.rs
index 0b65a8f1..1c9ee52d 100644
--- a/crates/hammer-build/src/lib.rs
+++ b/crates/hammer-build/src/lib.rs
@@ -209,7 +209,10 @@ pub fn build(
// Extractor de workspaces (Etapa G): si el root es virtual, resuelve `-p <pkg>` del bin.
inject_cargo_package_selector(&mut eff_recipe, &src_tree);
tracing::info!("cargo: vendoreando deps para build offline");
- fetch::vendor_cargo_deps(&src_tree)?;
+ fetch::vendor_cargo_deps(
+ &src_tree,
+ eff_recipe.source.cargo_vendor_dir.as_deref().unwrap_or("vendor"),
+ )?;
}
// Recetas Go: análogo a Cargo. Si el árbol trae `go.mod`, vendoreamos los módulos (red en el
diff --git a/crates/hammer-core/src/recipe.rs b/crates/hammer-core/src/recipe.rs
index 0df369d1..d565e54f 100644
--- a/crates/hammer-core/src/recipe.rs
+++ b/crates/hammer-core/src/recipe.rs
@@ -60,6 +60,12 @@ pub struct Source {
#[serde(default = "default_strip")]
pub strip_components: usize,
// --- común ---
+ /// Destino del vendoreo cargo (default `vendor`). Override per-receta para proyectos que
+ /// commitean su PROPIO `vendor/` (p.ej. mise → `vendor/aqua-registry`): `cargo vendor` borra
+ /// lo que no reconoce, así que se vendorea a un subdir aparte para no pisarlo. El fragmento
+ /// `.cargo/config.toml` que emite `cargo vendor` ya apunta al dir que se le pasa.
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub cargo_vendor_dir: Option<String>,
#[serde(default)]
pub patches: Vec<String>,
}
diff --git a/tandas/deferred-openssl-xcb/mise.toml b/tandas/deferred-openssl-xcb/mise.toml
index ec4cbc10..4c928145 100644
--- a/tandas/deferred-openssl-xcb/mise.toml
+++ b/tandas/deferred-openssl-xcb/mise.toml
@@ -8,6 +8,9 @@ version = "2026.7.0"
[source]
repo = "https://github.com/jdx/mise"
commit = "857b73f6a6b39a3bc90c44119a1e86ee11bd7273"
+# mise commitea su PROPIO vendor/aqua-registry (registry.yml 3.1MB que build.rs lee). El
+# `cargo vendor` de hammer, por defecto en `vendor/`, lo borraría. Lo mandamos aparte.
+cargo_vendor_dir = ".hammer-cargo-vendor"
[build]
compiler = "zig-cc"