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.
This commit is contained in:
@@ -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`.
|
||||
|
||||
@@ -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)]
|
||||
|
||||
Reference in New Issue
Block a user