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>
This commit is contained in:
2026-07-10 03:18:16 -04:00
co-authored by Claude Opus 4.8
parent cee652717e
commit b580c5bfc5
4 changed files with 20 additions and 5 deletions
+7 -4
View File
@@ -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");
}
+4 -1
View File
@@ -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
+6
View File
@@ -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>,
}
+3
View File
@@ -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"