tawasuyu

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 1572Shell 203Rust 109Markdown 993728 Definiciones

Etapa G: soporte go.work multimódulo (vendor_go_deps usa 'go work vendor')

Destraba la clase workspace (go-mockery/k3d/git-town/csvtk): con go.work, GOWORK=off + 'go mod
vendor' sólo vendoreaba el módulo raíz → 'inconsistent vendoring' en el build. Ahora si hay go.work
se usa 'go work vendor' (go 1.22+, vendor consistente de todo el workspace); el trigger y
detect_build_system también reconocen go.work (workspace sin go.mod raíz). Test detect_go_when_only_go_work.

f5880331ed7c1e05c8f8c2b6a36da9b0af04eae2 · por sergio · (hace 3 meses) · padre 388bf9b89 · ver el árbol en este commit

Delta semántico · 4

crates/hammer-build/src/fetch.rs

crates/hammer-build/src/lib.rs

2 archivos · +37 −14

diff --git a/crates/hammer-build/src/fetch.rs b/crates/hammer-build/src/fetch.rs
index a7ab8dd2..873efa77 100644
--- a/crates/hammer-build/src/fetch.rs
+++ b/crates/hammer-build/src/fetch.rs
@@ -246,26 +246,38 @@ pub fn vendor_cargo_deps(src: &Path) -> hammer_core::Result<()> {
 ///
 /// 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"])
+    // Proyecto WORKSPACE (multimódulo): si el árbol trae `go.work`, `go mod vendor` con GOWORK=off
+    // vendorea SÓLO el módulo raíz → el build (que ve todo el workspace) se queja de "inconsistent
+    // vendoring". `go work vendor` (go 1.22+) produce un `vendor/` consistente para TODO el workspace.
+    // Destraba la clase go.work (go-mockery, k3d, git-town, csvtk…). Sin go.work, el camino clásico
+    // `go mod vendor` con GOWORK=off (ignora cualquier go.work espurio y vendorea el raíz).
+    let has_work = src.join("go.work").is_file();
+    let (verb, label): ([&str; 2], &str) = if has_work {
+        (["work", "vendor"], "go work vendor")
+    } else {
+        (["mod", "vendor"], "go mod vendor")
+    };
+    let mut cmd = Command::new("go");
+    cmd.current_dir(src)
+        .args(verb)
         .env("GOFLAGS", "-mod=mod")
-        .env("GOTOOLCHAIN", "local")
-        // `go mod vendor` aborta en modo workspace (si el árbol trae go.work). Lo apagamos para
-        // vendorear contra el go.mod del módulo raíz (que es lo que el build compila).
-        .env("GOWORK", "off")
+        .env("GOTOOLCHAIN", "local");
+    if !has_work {
+        cmd.env("GOWORK", "off");
+    }
+    let output = cmd
         .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, \
+                "spawn {label}: {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 {:?})",
+            "{label} falló (exit {:?})",
             output.status.code()
         )));
     }
diff --git a/crates/hammer-build/src/lib.rs b/crates/hammer-build/src/lib.rs
index a34d3f36..f7ea30c7 100644
--- a/crates/hammer-build/src/lib.rs
+++ b/crates/hammer-build/src/lib.rs
@@ -217,7 +217,7 @@ pub fn build(
     // 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() {
+    if src_tree.join("go.mod").is_file() || src_tree.join("go.work").is_file() {
         tracing::info!("go: vendoreando módulos para build offline");
         fetch::vendor_go_deps(&src_tree)?;
     }
@@ -443,10 +443,11 @@ fn ensure_cargo_workspace_isolation(src: &Path) -> hammer_core::Result<()> {
 
 fn detect_build_system(src: &Path) -> BuildSys {
     let has = |p: &str| src.join(p).exists();
-    // `go.mod` PRIMERO: un módulo Go suele traer también un `configure`/`Makefile` AUXILIAR (para
-    // man pages, completions, packaging), pero el binario se hace con `go build`. Ningún paquete
-    // C/Rust del corpus trae go.mod, así que esto sólo captura proyectos Go (miller, etc.).
-    if has("go.mod") {
+    // `go.mod`/`go.work` PRIMERO: un módulo Go suele traer también un `configure`/`Makefile` AUXILIAR
+    // (man pages, completions, packaging), pero el binario se hace con `go build`. Ningún paquete
+    // C/Rust del corpus trae go.mod. `go.work` cubre los WORKSPACE multimódulo cuya RAÍZ no tiene
+    // go.mod (sólo go.work + submódulos) — sin esto caían a Makefile/Unknown.
+    if has("go.mod") || has("go.work") {
         return BuildSys::Go;
     }
     // El orden importa: cuando coexisten varios (un tarball release de autotools trae el
@@ -836,6 +837,16 @@ commit = "deadbeef"
         assert_eq!(p.install.as_deref(), Some("true"));
     }
 
+    #[test]
+    fn detect_go_when_only_go_work() {
+        // workspace multimódulo sin go.mod en la raíz (sólo go.work) ⇒ sigue siendo Go (antes caía a
+        // Makefile/Unknown). El vendoring usará `go work vendor`.
+        let d = tempfile::tempdir().unwrap();
+        touch(d.path(), "go.work");
+        touch(d.path(), "Makefile"); // un Makefile auxiliar no debe ganarle a Go
+        assert_eq!(detect_build_system(d.path()), BuildSys::Go);
+    }
+
     #[test]
     fn detect_go_main_picks_cmd_over_docs() {
         // main real en cmd/mlr + main de ejemplo roto en docs/ ⇒ elige cmd/mlr, ignora docs.