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
|
/// 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`.
|
/// `<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
|
/// La red se usa AQUÍ (fuera del sandbox, como el resto del fetch).
|
||||||
/// 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
|
/// **`--locked` condicional:** si la fuente trae un `Cargo.lock` committeado, se vendorea con
|
||||||
/// la receta). Un crate sin deps de crates.io produce un config vacío y ningún `vendor/`: inocuo.
|
/// `--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<()> {
|
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")
|
let output = Command::new("cargo")
|
||||||
.current_dir(src)
|
.current_dir(src)
|
||||||
.args(["vendor", "--locked", "vendor"])
|
.args(&args)
|
||||||
.stderr(Stdio::inherit())
|
.stderr(Stdio::inherit())
|
||||||
.output()
|
.output()
|
||||||
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("spawn cargo vendor: {e}")))?;
|
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("spawn cargo vendor: {e}")))?;
|
||||||
@@ -286,6 +305,31 @@ mod tests {
|
|||||||
vendor_cargo_deps(src).unwrap();
|
vendor_cargo_deps(src).unwrap();
|
||||||
assert!(src.join(".cargo/config.toml").is_file(), "debe escribir el config de vendoring");
|
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<()> {
|
fn run_git(args: &[&str], cwd: Option<&Path>) -> hammer_core::Result<()> {
|
||||||
|
|||||||
Reference in New Issue
Block a user