gioser

sergio/takana

> 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…

TOML 1389Shell 137Markdown 80Rust 802602 Definitions

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>

4291b2adeb51ee8abbdc8287b487d16b12fed450 · by sergio · (3 months ago) · parent 1670ebf93 · browse the tree at this commit

Semantic delta · 2

crates/hammer-build/src/fetch.rs

crates/hammer-build/src/lib.rs

  • body changed fn build pub

5 files · +120 −10

diff --git a/crates/hammer-build/src/fetch.rs b/crates/hammer-build/src/fetch.rs
index 76b381e8..5c6b7341 100644
--- a/crates/hammer-build/src/fetch.rs
+++ b/crates/hammer-build/src/fetch.rs
@@ -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 {
diff --git a/crates/hammer-build/src/lib.rs b/crates/hammer-build/src/lib.rs
index 7f71baef..002c41ef 100644
--- a/crates/hammer-build/src/lib.rs
+++ b/crates/hammer-build/src/lib.rs
@@ -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) {
diff --git a/recipes/amfora.toml b/recipes/amfora.toml
new file mode 100644
index 00000000..af4126e2
--- /dev/null
+++ b/recipes/amfora.toml
@@ -0,0 +1,33 @@
+# amfora — PLANTILLA DE REFERENCIA del patrón Go (Etapa G, frente Go). DIFERIDA.
+#
+# Estado del frente (2026-06-26):
+#   ✅ toolchain: recipes/go.toml (1.26.4, binario estático, corre en el sandbox musl,
+#      se materializa como overlay vía deps.build=["go"] → /usr/bin/go visible).
+#   ✅ vendoring INTEGRADO en fetch.rs (`vendor_go_deps`, análogo a vendor_cargo_deps):
+#      si el árbol trae go.mod, hammer corre `go mod vendor` en el host (red) y el
+#      sandbox compila offline con -mod=vendor. El `go` del host lo provee el lab
+#      (.dev-fs/tools/go). NO hace falta tarball-al-mirror: se vendorea in-situ como Rust.
+#   ✅ VALIDADO end-to-end: esta receta (repo+commit) → binario ESTÁTICO que corre
+#      (Amfora v1.11.0), sellado b3:0a3f63bc…
+#
+# Patrón compile para cualquier tool Go (-trimpath -buildid= ⇒ reproducible bit-a-bit
+# para la cosecha trust-free de la granja):
+name    = "amfora"
+version = "1.11.0"
+
+[source]
+repo   = "https://github.com/makew0rld/amfora"
+commit = "4d9a5c56c88f7bec2938968182c88130b923fbba"
+
+[build]
+compiler = "zig-cc"   # inerte (Go no usa zig); el toolchain real llega por deps.build
+target   = "x86_64-linux-musl"
+link     = "static"
+
+[deps]
+build = ["go"]
+
+[build.phases]
+configure = "true"
+compile   = "export GOCACHE=/tmp/gocache GOPATH=/tmp/gopath GOTOOLCHAIN=local CGO_ENABLED=0 GOFLAGS=-mod=vendor GOPROXY=off; go build -trimpath -ldflags=-buildid= -o /out/usr/bin/amfora ."
+install   = "true"
diff --git a/scripts/bootstrap-devfs.sh b/scripts/bootstrap-devfs.sh
index b8d44fdb..fd8ecad4 100755
--- a/scripts/bootstrap-devfs.sh
+++ b/scripts/bootstrap-devfs.sh
@@ -28,6 +28,9 @@ ALPINE_VER="${ALPINE_VER:-3.23.4}"
 ALPINE_SHA256="${ALPINE_SHA256:-85498865362aa7ebececa0d725a2f2e4db7ac4e4b2850b8df21645afa0d03ee3}"
 ZIG_VER="${ZIG_VER:-0.16.0}"
 ZIG_SHA256="${ZIG_SHA256:-70e49664a74374b48b51e6f3fdfbf437f6395d42509050588bd49abe52ba3d00}"
+# Toolchain Go del HOST (para `go mod vendor` en el fetch; cf. recipes/go.toml para el sandbox).
+GO_VER="${GO_VER:-1.26.4}"
+GO_SHA256="${GO_SHA256:-1153d3d50e0ac764b447adfe05c2bcf08e889d42a02e0fe0259bd47f6733ad7f}"
 
 SKIP_APK=0
 FORCE_ZIG=0
@@ -100,6 +103,36 @@ else
     ok "zig $ZIG_VER instalado, symlink $ZIG_LINK"
 fi
 
+# --- 2b. Toolchain Go del host (frente Go, Etapa G) ----------------------------
+# El vendoring de módulos Go (`go mod vendor`) corre en el HOST durante el fetch (red
+# permitida), igual que `cargo vendor`. El binario `go` oficial es estático ⇒ corre en
+# cualquier host. Lo dejamos en .dev-fs/tools/go (fuente de verdad versionada) y lo
+# enlazamos a ~/.cargo/bin (que el worker pone en PATH vía `. ~/.cargo/env`).
+GO_DIR="$TOOLS/go-$GO_VER"
+GO_LINK="$TOOLS/go"
+if [[ -x "$GO_LINK/bin/go" ]] && "$GO_LINK/bin/go" version 2>/dev/null | grep -q "go$GO_VER"; then
+    ok "go $GO_VER ya presente en $GO_LINK"
+else
+    log "go: descargando $GO_VER"
+    tar="$TOOLS/go.tar.gz"
+    url="https://go.dev/dl/go$GO_VER.linux-amd64.tar.gz"
+    curl -fsSL "$url" -o "$tar"
+    echo "$GO_SHA256  $tar" | sha256sum -c -
+    rm -rf "$GO_DIR"; mkdir -p "$GO_DIR"
+    tar -xzf "$tar" -C "$GO_DIR" --strip-components=1
+    rm -f "$tar"
+    ln -sfn "go-$GO_VER" "$GO_LINK"
+    ok "go $GO_VER instalado en $GO_LINK"
+fi
+# Symlink en un dir del PATH del host (junto a cargo) para que el fetch encuentre `go`.
+if [[ -d "$HOME/.cargo/bin" ]]; then
+    ln -sfn "$GO_LINK/bin/go" "$HOME/.cargo/bin/go"
+    ln -sfn "$GO_LINK/bin/gofmt" "$HOME/.cargo/bin/gofmt"
+    ok "go enlazado en ~/.cargo/bin (PATH del host/worker)"
+else
+    log "go: ~/.cargo/bin no existe; agregá $GO_LINK/bin al PATH del host a mano"
+fi
+
 # --- 3. Build tools en el rootfs (apk add) ------------------------------------
 if [[ $SKIP_APK -eq 1 ]]; then
     log "apk: --skip-apk ⇒ saltando build tools"
diff --git a/tandas/staged-2026-06-26/amfora.toml b/tandas/staged-2026-06-26/amfora.toml
index be0acf2a..af4126e2 100644
--- a/tandas/staged-2026-06-26/amfora.toml
+++ b/tandas/staged-2026-06-26/amfora.toml
@@ -3,16 +3,15 @@
 # Estado del frente (2026-06-26):
 #   ✅ toolchain: recipes/go.toml (1.26.4, binario estático, corre en el sandbox musl,
 #      se materializa como overlay vía deps.build=["go"] → /usr/bin/go visible).
-#   ✅ build offline DE-RISKED end-to-end: `go mod vendor` (con red) + `go build
-#      -mod=vendor GOPROXY=off` produce un binario ESTÁTICO que corre (Amfora v1.11.0).
-#   ⬜ FALTA: el source de hammer es repo+commit (checkout pelado, sin vendor/). El
-#      sandbox de build es OFFLINE (reproducibilidad) ⇒ `go build` no puede bajar
-#      módulos de proxy.golang.org. Igual que Rust vendorea crates, el frente Go
-#      necesita que el IMPORTADOR (con red) corra `go mod vendor` y empaquete el
-#      source-vendoreado como tarball content-addressed (source = tarball+sha en vez
-#      de repo+commit). Ahí esta receta construye con las phases de abajo tal cual.
+#   ✅ vendoring INTEGRADO en fetch.rs (`vendor_go_deps`, análogo a vendor_cargo_deps):
+#      si el árbol trae go.mod, hammer corre `go mod vendor` en el host (red) y el
+#      sandbox compila offline con -mod=vendor. El `go` del host lo provee el lab
+#      (.dev-fs/tools/go). NO hace falta tarball-al-mirror: se vendorea in-situ como Rust.
+#   ✅ VALIDADO end-to-end: esta receta (repo+commit) → binario ESTÁTICO que corre
+#      (Amfora v1.11.0), sellado b3:0a3f63bc…
 #
-# Cuando el vendor esté integrado, este es el patrón compile para cualquier tool Go:
+# Patrón compile para cualquier tool Go (-trimpath -buildid= ⇒ reproducible bit-a-bit
+# para la cosecha trust-free de la granja):
 name    = "amfora"
 version = "1.11.0"
 
@@ -30,5 +29,5 @@ build = ["go"]
 
 [build.phases]
 configure = "true"
-compile   = "export GOCACHE=/tmp/gocache GOPATH=/tmp/gopath GOTOOLCHAIN=local CGO_ENABLED=0 GOFLAGS=-mod=vendor GOPROXY=off; go build -o /out/usr/bin/amfora ."
+compile   = "export GOCACHE=/tmp/gocache GOPATH=/tmp/gopath GOTOOLCHAIN=local CGO_ENABLED=0 GOFLAGS=-mod=vendor GOPROXY=off; go build -trimpath -ldflags=-buildid= -o /out/usr/bin/amfora ."
 install   = "true"