From 0768b4dc842c02d7f513030c2b49a3bcdd1a9ebe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Balv=C3=ADn?= Date: Tue, 6 Oct 2026 20:24:39 +0200 Subject: [PATCH] fix(cmd): locate GOROOT when the build is trimmed Assisted-by: GLM 5.3 Flash --- cmd/gasm/asmhdr.go | 25 +++++++++++++++++++++++++ cmd/gasm/asmhdr_test.go | 18 ++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/cmd/gasm/asmhdr.go b/cmd/gasm/asmhdr.go index e7b778d..d766b4a 100644 --- a/cmd/gasm/asmhdr.go +++ b/cmd/gasm/asmhdr.go @@ -13,6 +13,7 @@ import ( "go/token" "go/types" "os" + "os/exec" "path/filepath" "regexp" "strings" @@ -217,6 +218,9 @@ type sourceImporter struct { func newSourceImporter(goos, goarch string) *sourceImporter { ctxt := new(build.Context) *ctxt = build.Default + if root := resolveGOROOT(build.Default.GOROOT); root != "" { + ctxt.GOROOT = root + } ctxt.GOOS = goos ctxt.GOARCH = goarch ctxt.CgoEnabled = false @@ -228,6 +232,27 @@ func newSourceImporter(goos, goarch string) *sourceImporter { } } +// resolveGOROOT returns the root the type-checker reads standard packages +// from, preferring a baked root that is a real directory. A binary built +// with -trimpath carries no root: build.Default.GOROOT comes back empty and +// every standard import fails with "GOROOT not set", so the toolchain is +// asked, the way the corpus audit locates its own GOROOT. An empty result +// leaves the context untouched and the failure stays the honest one. +func resolveGOROOT(baked string) string { + if st, err := os.Stat(baked); err == nil && st.IsDir() { + return baked + } + out, err := exec.Command("go", "env", "GOROOT").Output() + if err != nil { + return "" + } + root := strings.TrimSpace(string(out)) + if st, err := os.Stat(root); err != nil || !st.IsDir() { + return "" + } + return root +} + // Import type-checks one imported package and memoises it. "unsafe" must // resolve to go/types' own package, never to the source in GOROOT/src/unsafe: // the source declares Sizeof and Offsetof as ordinary functions over diff --git a/cmd/gasm/asmhdr_test.go b/cmd/gasm/asmhdr_test.go index fbfa653..6873921 100644 --- a/cmd/gasm/asmhdr_test.go +++ b/cmd/gasm/asmhdr_test.go @@ -285,6 +285,24 @@ func TestGoAsmHeaderResolved(t *testing.T) { } } +// TestResolveGOROOT holds the two resolutions the header generator leans +// on: a baked root that is a real directory passes through, and a missing +// one falls back to what go env GOROOT names, which is what a -trimpath +// build needs. +func TestResolveGOROOT(t *testing.T) { + out, err := exec.Command("go", "env", "GOROOT").Output() + if err != nil { + t.Skip("go env GOROOT unavailable:", err) + } + root := strings.TrimSpace(string(out)) + if got := resolveGOROOT(root); got != root { + t.Fatalf("a real baked root must pass through, got %q", got) + } + if got := resolveGOROOT(filepath.Join(root, "nonexistent")); got != root { + t.Fatalf("a missing baked root must resolve to %q, got %q", root, got) + } +} + func TestOtherGOOSFile(t *testing.T) { for path, want := range map[string]bool{ "/x/sys_windows_amd64.s": true,