Etapa G: detect_go_main ve el main bajo header de comentario de bloque
cmd/helm/helm.go arranca con un header Apache /* ... */ cuyas líneas internas no llevan '*';
el filtro por-línea ingenuo de is_main_go cortaba en 'Copyright ...' y daba 'no es main' ⇒
detect_go_main no hallaba cmd/helm y caía a '.' (raíz sin .go → 'no Go files in /src'). Ahora
rastrea el estado dentro/fuera de bloque /* */. Afecta a cualquier .go con header de bloque.
Test: detect_go_main_sees_main_under_block_comment_header.
3b23463ac1c42864a7bf2a869409c211047b5744 · por sergio · 2026-06-27T03:38:47Z (hace 3 meses) · padre 06ba41733 · ver el árbol en este commit
Delta semántico · 3
crates/hammer-build/src/lib.rs
diff --git a/crates/hammer-build/src/lib.rs b/crates/hammer-build/src/lib.rs
index 011155e3..19323680 100644
--- a/crates/hammer-build/src/lib.rs
+++ b/crates/hammer-build/src/lib.rs
@@ -488,11 +488,45 @@ fn detect_build_system(src: &Path) -> BuildSys {
fn detect_go_main(src: &Path) -> String {
fn is_main_go(path: &Path) -> bool {
let Ok(text) = std::fs::read_to_string(path) else { return false };
+ // El primer token significativo de un .go es la cláusula `package` (tras build-constraints
+ // `//go:build`, comentarios de línea y el header de licencia). Hay que saltar comentarios de
+ // BLOQUE `/* ... */` correctamente: muchos headers Apache son `/*\nCopyright ...\n*/` y las
+ // líneas internas NO arrancan con `*` ⇒ un filtro por-línea ingenuo cortaría ahí y fallaría
+ // (p.ej. cmd/helm/helm.go). Escaneamos rastreando el estado dentro/fuera de bloque.
+ let mut in_block = false;
for line in text.lines() {
- let l = line.trim();
- if l.is_empty() || l.starts_with("//") || l.starts_with("/*") || l.starts_with('*') {
+ let mut l = line.trim();
+ if in_block {
+ match l.find("*/") {
+ Some(i) => {
+ l = l[i + 2..].trim();
+ in_block = false;
+ }
+ None => continue,
+ }
+ }
+ if l.is_empty() || l.starts_with("//") {
continue;
}
+ if let Some(i) = l.find("/*") {
+ let before = l[..i].trim();
+ if !before.is_empty() {
+ return before.starts_with("package main");
+ }
+ match l[i + 2..].find("*/") {
+ Some(j) => {
+ let after = l[i + 2 + j + 2..].trim();
+ if after.is_empty() {
+ continue;
+ }
+ return after.starts_with("package main");
+ }
+ None => {
+ in_block = true;
+ continue;
+ }
+ }
+ }
return l.starts_with("package main");
}
false
@@ -800,6 +834,20 @@ commit = "deadbeef"
assert_eq!(detect_go_main(d.path()), "./cmd/mlr");
}
+ #[test]
+ fn detect_go_main_sees_main_under_block_comment_header() {
+ // header de licencia `/* ... */` con líneas internas SIN `*` (estilo Apache de helm):
+ // el main en cmd/helm debe detectarse igual (regresión del corte ingenuo por-línea).
+ let d = tempfile::tempdir().unwrap();
+ std::fs::create_dir_all(d.path().join("cmd/helm")).unwrap();
+ std::fs::write(
+ d.path().join("cmd/helm/helm.go"),
+ b"/*\nCopyright The Helm Authors.\nLicensed under the Apache License 2.0\n*/\n\npackage main\n\nfunc main() {}\n",
+ )
+ .unwrap();
+ assert_eq!(detect_go_main(d.path()), "./cmd/helm");
+ }
+
#[test]
fn detect_autoconf_raw_when_only_configure_ac() {
let d = tempfile::tempdir().unwrap();