build: vendoring con --locked condicional al Cargo.lock committeado (Opción 2)
Desbloquea recetas Cargo cuya fuente no committea Cargo.lock (p.ej. el monorepo tawasuyu, que lo gitignora) sin tocar la política de ese repo. vendor_cargo_deps: si la fuente trae Cargo.lock → `cargo vendor --locked` (reproducible, deps pineadas por el lock del commit). Si no → vendoreo sin --locked (genera el lock al vuelo) + warn! explícito de "build no pineado en el tiempo". El end-state reproducible sigue siendo committear el lock (plan C.2 #5). +1 test del camino sin-lock. Aplica a arje-zero (tawasuyu); hammerd (hammer, que sí committea el lock) sigue por el camino --locked. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
4895c1c884
commit
8d98a6053a
@@ -189,14 +189,33 @@ fn verify_sha256(path: &Path, expected: &str) -> hammer_core::Result<()> {
|
||||
/// Vendoriza las dependencias de un proyecto Cargo en `<src>/vendor` y escribe
|
||||
/// `<src>/.cargo/config.toml` para que el build dentro del sandbox resuelva todo `--offline`.
|
||||
///
|
||||
/// La red se usa AQUÍ (fuera del sandbox, como el resto del fetch). `--locked` fija las versiones
|
||||
/// por el `Cargo.lock` del source: mismo commit ⇒ mismo lock ⇒ mismas deps vendoreadas, así que
|
||||
/// el vendoring no introduce no-determinismo (el `Cargo.lock` ya está dentro del commit que hashea
|
||||
/// la receta). Un crate sin deps de crates.io produce un config vacío y ningún `vendor/`: inocuo.
|
||||
/// La red se usa AQUÍ (fuera del sandbox, como el resto del fetch).
|
||||
///
|
||||
/// **`--locked` condicional:** si la fuente trae un `Cargo.lock` committeado, se vendorea con
|
||||
/// `--locked` y el build es reproducible (mismo commit ⇒ mismo lock ⇒ mismas deps; el lock ya está
|
||||
/// dentro del commit que hashea la receta). Si la fuente **no** lo trae (p.ej. un monorepo que lo
|
||||
/// gitignora, como tawasuyu), se genera el lock al vuelo **sin** `--locked` y se emite un `warn!`:
|
||||
/// el build queda internamente consistente pero **no pineado en el tiempo** (las deps pueden
|
||||
/// derivar entre corridas). El end-state reproducible es committear el `Cargo.lock` en la fuente
|
||||
/// (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<()> {
|
||||
let locked = src.join("Cargo.lock").is_file();
|
||||
let mut args: Vec<&str> = vec!["vendor"];
|
||||
if locked {
|
||||
args.push("--locked");
|
||||
} else {
|
||||
tracing::warn!(
|
||||
src = %src.display(),
|
||||
"sin Cargo.lock committeado: vendoreo sin --locked; el build NO es reproducible en el \
|
||||
tiempo (deps no pineadas). Committear el lock en la fuente para fijarlo (plan C.2 #5)."
|
||||
);
|
||||
}
|
||||
args.push("vendor");
|
||||
let output = Command::new("cargo")
|
||||
.current_dir(src)
|
||||
.args(["vendor", "--locked", "vendor"])
|
||||
.args(&args)
|
||||
.stderr(Stdio::inherit())
|
||||
.output()
|
||||
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("spawn cargo vendor: {e}")))?;
|
||||
@@ -286,6 +305,31 @@ mod tests {
|
||||
vendor_cargo_deps(src).unwrap();
|
||||
assert!(src.join(".cargo/config.toml").is_file(), "debe escribir el config de vendoring");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vendor_cargo_deps_without_committed_lock() {
|
||||
// Fuente SIN Cargo.lock (como un monorepo que lo gitignora): vendoreamos sin --locked,
|
||||
// generando el lock al vuelo. Para un crate depless es offline.
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let src = tmp.path();
|
||||
std::fs::write(
|
||||
src.join("Cargo.toml"),
|
||||
"[package]\nname = \"vt\"\nversion = \"0.0.0\"\nedition = \"2021\"\n\n[dependencies]\n",
|
||||
)
|
||||
.unwrap();
|
||||
std::fs::create_dir_all(src.join("src")).unwrap();
|
||||
std::fs::write(src.join("src/main.rs"), "fn main() {}").unwrap();
|
||||
assert!(!src.join("Cargo.lock").is_file(), "precondición: sin lock");
|
||||
|
||||
if Command::new("cargo").arg("--version").status().map(|s| !s.success()).unwrap_or(true) {
|
||||
eprintln!("SKIP: cargo no disponible");
|
||||
return;
|
||||
}
|
||||
// No debe fallar por falta de --locked; genera el lock y escribe el config.
|
||||
vendor_cargo_deps(src).unwrap();
|
||||
assert!(src.join(".cargo/config.toml").is_file());
|
||||
assert!(src.join("Cargo.lock").is_file(), "vendor sin --locked genera el lock");
|
||||
}
|
||||
}
|
||||
|
||||
fn run_git(args: &[&str], cwd: Option<&Path>) -> hammer_core::Result<()> {
|
||||
|
||||
Reference in New Issue
Block a user