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 1574Shell 204Rust 111Markdown 993763 Definiciones

Etapa G: frontera go.mod-en-subdir — knob [build] subdir (vendor/detect/compile en src/<subdir>)

Destraba proyectos Go cuyo módulo no está en la raíz (dolt→go, csvtk→subdir): vendor_go_deps corre en
el subdir, detect_build_system/detect_go_main operan ahí, y el compile hace 'cd <subdir>' primero.
Test go_subdir_compiles_in_subdir.

c583c4d679523abe02800398b711708b447d3a0f · por sergio · (hace 3 meses) · padre 49ce5cf05 · ver el árbol en este commit

Delta semántico · 4

crates/hammer-build/src/lib.rs

crates/hammer-core/src/recipe.rs

  • cambió de firma struct Build pub

2 archivos · +46 −5

diff --git a/crates/hammer-build/src/lib.rs b/crates/hammer-build/src/lib.rs
index 2b3524fb..906bb1f9 100644
--- a/crates/hammer-build/src/lib.rs
+++ b/crates/hammer-build/src/lib.rs
@@ -217,9 +217,15 @@ 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() || src_tree.join("go.work").is_file() {
+    // El módulo Go puede estar en un subdir (recipe `build.subdir`, p.ej. dolt→go); vendoreamos ahí.
+    let go_root = if eff_recipe.build.subdir.is_empty() {
+        src_tree.clone()
+    } else {
+        src_tree.join(&eff_recipe.build.subdir)
+    };
+    if go_root.join("go.mod").is_file() || go_root.join("go.work").is_file() {
         tracing::info!("go: vendoreando módulos para build offline");
-        fetch::vendor_go_deps(&src_tree)?;
+        fetch::vendor_go_deps(&go_root)?;
     }
 
     let out_dir = unique_out_dir(&cfg.work_root, &recipe.name, &h)?;
@@ -612,7 +618,14 @@ fn cargo_linker_env_var(triple: &str) -> String {
 /// ganan; el resto lo deriva la heurística según el sistema de build detectado.
 fn resolve_phases(recipe: &Recipe, src: &Path) -> hammer_core::Result<Phases> {
     let mut out = recipe.build.phases.clone();
-    let bs = detect_build_system(src);
+    // Raíz efectiva del módulo Go: `<src>/<subdir>` si la receta lo fija (dolt→go, csvtk→subdir),
+    // si no `src`. detect/detect_go_main operan ahí; el compile hace `cd <subdir>` primero.
+    let go_src = if recipe.build.subdir.is_empty() {
+        src.to_path_buf()
+    } else {
+        src.join(&recipe.build.subdir)
+    };
+    let bs = detect_build_system(&go_src);
     let flags = recipe.build.flags.join(" ");
     let flags_suffix = if flags.is_empty() { String::new() } else { format!(" {flags}") };
 
@@ -746,10 +759,16 @@ fn resolve_phases(recipe: &Recipe, src: &Path) -> hammer_core::Result<Phases> {
                 // el nombre. Offline+reproducible: vendor (vendor_go_deps) + GOPROXY=off + -trimpath
                 // -buildid=. El toolchain `go` llega por deps.build=["go"] (overlay).
                 let pkg = if recipe.build.flags.is_empty() {
-                    detect_go_main(src, &recipe.name)
+                    detect_go_main(&go_src, &recipe.name)
                 } else {
                     flags.clone()
                 };
+                // Si el módulo está en subdir, el compile entra ahí primero (el sandbox arranca en /src).
+                let cd_prefix = if recipe.build.subdir.is_empty() {
+                    String::new()
+                } else {
+                    format!("cd {} && ", recipe.build.subdir)
+                };
                 // GOCACHE/GOTMPDIR en /src (disk-backed, per-build, lo limpia el watchdog), NO en
                 // /tmp: el sandbox monta `--tmpfs /tmp` (~½ RAM); un proyecto Go ENORME (telegraf,
                 // vault) desborda ese tmpfs con sus objetos compilados + el temp del linker al mapear
@@ -770,7 +789,7 @@ fn resolve_phases(recipe: &Recipe, src: &Path) -> hammer_core::Result<Phases> {
                     ("CGO_ENABLED=0", "-ldflags=-buildid=")
                 };
                 Some(format!(
-                    "export GOCACHE=/src/.gocache GOTMPDIR=/src/.gotmp GOPATH=/tmp/gopath \
+                    "{cd_prefix}export GOCACHE=/src/.gocache GOTMPDIR=/src/.gotmp GOPATH=/tmp/gopath \
                      GOTOOLCHAIN=local {cgo_env} GOFLAGS=-mod=vendor GOPROXY=off \
                      GOBIN=/out/usr/bin; mkdir -p /src/.gotmp; \
                      go install -trimpath {ldflags} {pkg}"
@@ -833,6 +852,23 @@ commit = "deadbeef"
         r
     }
 
+    #[test]
+    fn go_subdir_compiles_in_subdir() {
+        // dolt-like: go.mod en subdir `go/`. Con build.subdir="go" el lab detecta Go ahí y el compile
+        // entra al subdir primero.
+        let d = tempfile::tempdir().unwrap();
+        std::fs::create_dir_all(d.path().join("go/cmd/dolt")).unwrap();
+        touch(d.path(), "go/go.mod");
+        std::fs::write(d.path().join("go/cmd/dolt/main.go"), b"package main\nfunc main(){}\n").unwrap();
+        let mut r = recipe(&[]);
+        r.name = "dolt".into();
+        r.build.subdir = "go".into();
+        let p = resolve_phases(&r, d.path()).unwrap();
+        let c = p.compile.unwrap();
+        assert!(c.starts_with("cd go && "), "entra al subdir: {c}");
+        assert!(c.contains("go install -trimpath") && c.trim_end().ends_with("./cmd/dolt"), "{c}");
+    }
+
     #[test]
     fn go_mod_wins_over_configure_and_derives_go_install() {
         // miller-like: go.mod + configure auxiliar ⇒ Go gana, y el compile es `go install`.
diff --git a/crates/hammer-core/src/recipe.rs b/crates/hammer-core/src/recipe.rs
index 9c54c18a..75ee9bea 100644
--- a/crates/hammer-core/src/recipe.rs
+++ b/crates/hammer-core/src/recipe.rs
@@ -111,6 +111,11 @@ pub struct Build {
     /// esas recetas en el sandbox. No-op fuera de BuildSys::Go.
     #[serde(default)]
     pub cgo: bool,
+    /// Subdirectorio donde vive el módulo Go cuando NO está en la raíz del repo (p.ej. dolt → `go`,
+    /// csvtk → su subdir). El lab vendorea, detecta el main y compila DENTRO de `<src>/<subdir>`.
+    /// Vacío = raíz (lo normal). Sólo aplica a BuildSys::Go.
+    #[serde(default)]
+    pub subdir: String,
     /// Overrides explícitos de las fases del build. Lo no especificado se decide por
     /// heurística en el lab (ver `docs/02-build-lab.md` §4).
     #[serde(default)]