Etapa G frente Go: vendoring integrado en fetch — amfora construye, repro

Cierra el muro del frente Go (vendoring) replicando el patron de Rust:
vendorear in-situ en el fetch (host, con red), no tarball-al-mirror.

- fetch.rs: vendor_go_deps() analoga a vendor_cargo_deps — corre go mod vendor
  en el host si hay go.mod; el sandbox compila offline con -mod=vendor.
- lib.rs: engancha vendor_go_deps tras los patches cuando el arbol trae go.mod
  (sin BuildSys::Go; el compile lo fija la receta en build.phases).
- bootstrap-devfs.sh: instala el toolchain go del host (1.26.4, estatico) en
  .dev-fs/tools/go + symlink en ~/.cargo/bin (PATH del worker via .cargo/env).
- amfora: PRIMERA receta Go del corpus. Build end-to-end validado (repo+commit
  -> fetch vendorea -> sandbox offline -> binario estatico que corre, Amfora
  v1.11.0). REPRODUCIBLE bit-a-bit: dos stores independientes dan of_tree
  b3:9ec1d5fe (trimpath + buildid= en el compile). Promovida al repo (282).

Quedan 12 recetas Go en tandas/staged-2026-06-26/ (cheat/dnsx/croc/dyff/csvtk/
git-town/miller/jump/gron/qrcp/walk/gtrash): mecanico, copiar el patron de
amfora ajustando el nombre del binario.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-26 04:34:56 -04:00
co-authored by Claude Opus 4.8
parent 1670ebf933
commit 4291b2adeb
5 changed files with 120 additions and 10 deletions
+35
View File
@@ -234,6 +234,41 @@ pub fn vendor_cargo_deps(src: &Path) -> hammer_core::Result<()> {
Ok(())
}
/// Vendoriza los módulos de un proyecto Go en `<src>/vendor` para que `go build -mod=vendor`
/// resuelva todo offline dentro del sandbox. La red se usa AQUÍ (fuera del sandbox, como
/// `vendor_cargo_deps`): `go mod vendor` baja los módulos de `go.mod`/`go.sum` al árbol.
///
/// **Reproducible por `go.sum`:** a diferencia de Cargo (lock opcional), Go exige `go.sum`
/// committeado con el hash de cada módulo; el mismo commit ⇒ el mismo `go.sum` ⇒ el mismo
/// `vendor/` (ya hasheado dentro del commit que fija la receta). `GOTOOLCHAIN=local` impide que
/// `go.mod` fuerce la auto-descarga de otra versión del toolchain (el del lab manda y queda fijo);
/// `GOFLAGS=-mod=mod` permite poblar el `vendor/` aunque el árbol no lo traiga.
///
/// Un proyecto sin deps externas produce un `vendor/` vacío (o ninguno): inocuo.
pub fn vendor_go_deps(src: &Path) -> hammer_core::Result<()> {
let output = Command::new("go")
.current_dir(src)
.args(["mod", "vendor"])
.env("GOFLAGS", "-mod=mod")
.env("GOTOOLCHAIN", "local")
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.output()
.map_err(|e| {
hammer_core::Error::Other(anyhow::anyhow!(
"spawn go mod vendor: {e} (¿está `go` en el PATH del host? lo provee el lab, \
cf. scripts/bootstrap-devfs.sh / vps-setup.sh)"
))
})?;
if !output.status.success() {
return Err(hammer_core::Error::Other(anyhow::anyhow!(
"go mod vendor falló (exit {:?})",
output.status.code()
)));
}
Ok(())
}
/// Aplica los patches de la receta (relativos a `recipe.base_dir`) sobre `tree`.
pub fn apply_patches(recipe: &Recipe, tree: &Path) -> hammer_core::Result<()> {
for p in &recipe.source.patches {
+10
View File
@@ -212,6 +212,16 @@ pub fn build(
fetch::vendor_cargo_deps(&src_tree)?;
}
// Recetas Go: análogo a Cargo. Si el árbol trae `go.mod`, vendoreamos los módulos (red en el
// fetch) para que `go build -mod=vendor` resuelva offline en el sandbox. El toolchain `go` lo
// aporta `deps.build = ["go"]` (recipes/go.toml) DENTRO del sandbox; el vendoring usa el `go`
// del host (lo provee el lab). No hay BuildSys::Go: las recetas Go fijan el compile en
// `[build.phases]` (ver tandas/staged-2026-06-26/amfora.toml como plantilla).
if src_tree.join("go.mod").is_file() {
tracing::info!("go: vendoreando módulos para build offline");
fetch::vendor_go_deps(&src_tree)?;
}
let out_dir = unique_out_dir(&cfg.work_root, &recipe.name, &h)?;
let mut env: Vec<(String, String)> = Vec::new();
if matches!(recipe.build.link, LinkMode::Static) {