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:
@@ -234,6 +234,41 @@ pub fn vendor_cargo_deps(src: &Path) -> hammer_core::Result<()> {
|
|||||||
Ok(())
|
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`.
|
/// Aplica los patches de la receta (relativos a `recipe.base_dir`) sobre `tree`.
|
||||||
pub fn apply_patches(recipe: &Recipe, tree: &Path) -> hammer_core::Result<()> {
|
pub fn apply_patches(recipe: &Recipe, tree: &Path) -> hammer_core::Result<()> {
|
||||||
for p in &recipe.source.patches {
|
for p in &recipe.source.patches {
|
||||||
|
|||||||
@@ -212,6 +212,16 @@ pub fn build(
|
|||||||
fetch::vendor_cargo_deps(&src_tree)?;
|
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 out_dir = unique_out_dir(&cfg.work_root, &recipe.name, &h)?;
|
||||||
let mut env: Vec<(String, String)> = Vec::new();
|
let mut env: Vec<(String, String)> = Vec::new();
|
||||||
if matches!(recipe.build.link, LinkMode::Static) {
|
if matches!(recipe.build.link, LinkMode::Static) {
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -28,6 +28,9 @@ ALPINE_VER="${ALPINE_VER:-3.23.4}"
|
|||||||
ALPINE_SHA256="${ALPINE_SHA256:-85498865362aa7ebececa0d725a2f2e4db7ac4e4b2850b8df21645afa0d03ee3}"
|
ALPINE_SHA256="${ALPINE_SHA256:-85498865362aa7ebececa0d725a2f2e4db7ac4e4b2850b8df21645afa0d03ee3}"
|
||||||
ZIG_VER="${ZIG_VER:-0.16.0}"
|
ZIG_VER="${ZIG_VER:-0.16.0}"
|
||||||
ZIG_SHA256="${ZIG_SHA256:-70e49664a74374b48b51e6f3fdfbf437f6395d42509050588bd49abe52ba3d00}"
|
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
|
SKIP_APK=0
|
||||||
FORCE_ZIG=0
|
FORCE_ZIG=0
|
||||||
@@ -100,6 +103,36 @@ else
|
|||||||
ok "zig $ZIG_VER instalado, symlink $ZIG_LINK"
|
ok "zig $ZIG_VER instalado, symlink $ZIG_LINK"
|
||||||
fi
|
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) ------------------------------------
|
# --- 3. Build tools en el rootfs (apk add) ------------------------------------
|
||||||
if [[ $SKIP_APK -eq 1 ]]; then
|
if [[ $SKIP_APK -eq 1 ]]; then
|
||||||
log "apk: --skip-apk ⇒ saltando build tools"
|
log "apk: --skip-apk ⇒ saltando build tools"
|
||||||
|
|||||||
@@ -3,16 +3,15 @@
|
|||||||
# Estado del frente (2026-06-26):
|
# Estado del frente (2026-06-26):
|
||||||
# ✅ toolchain: recipes/go.toml (1.26.4, binario estático, corre en el sandbox musl,
|
# ✅ 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).
|
# 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
|
# ✅ vendoring INTEGRADO en fetch.rs (`vendor_go_deps`, análogo a vendor_cargo_deps):
|
||||||
# -mod=vendor GOPROXY=off` produce un binario ESTÁTICO que corre (Amfora v1.11.0).
|
# si el árbol trae go.mod, hammer corre `go mod vendor` en el host (red) y el
|
||||||
# ⬜ FALTA: el source de hammer es repo+commit (checkout pelado, sin vendor/). El
|
# sandbox compila offline con -mod=vendor. El `go` del host lo provee el lab
|
||||||
# sandbox de build es OFFLINE (reproducibilidad) ⇒ `go build` no puede bajar
|
# (.dev-fs/tools/go). NO hace falta tarball-al-mirror: se vendorea in-situ como Rust.
|
||||||
# módulos de proxy.golang.org. Igual que Rust vendorea crates, el frente Go
|
# ✅ VALIDADO end-to-end: esta receta (repo+commit) → binario ESTÁTICO que corre
|
||||||
# necesita que el IMPORTADOR (con red) corra `go mod vendor` y empaquete el
|
# (Amfora v1.11.0), sellado b3:0a3f63bc…
|
||||||
# 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.
|
|
||||||
#
|
#
|
||||||
# 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"
|
name = "amfora"
|
||||||
version = "1.11.0"
|
version = "1.11.0"
|
||||||
|
|
||||||
@@ -30,5 +29,5 @@ build = ["go"]
|
|||||||
|
|
||||||
[build.phases]
|
[build.phases]
|
||||||
configure = "true"
|
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"
|
install = "true"
|
||||||
|
|||||||
Reference in New Issue
Block a user