From 2ec30b0e69e61105860752ad6b5f213e586e35b1 Mon Sep 17 00:00:00 2001 From: Kevin Burke Date: Fri, 5 Sep 2025 10:48:14 -0700 Subject: [PATCH 1/2] pkg/goanalysis: expose underlying errors If compilation or building of a Go package fails, export data will not be available. Previously, we would mask the underlying failure and display only a message that "no export data" was available. This has led to lots of confusion among golangci-lint users (and users at my company, which is how I discovered this problem). golangci/golangci-lint#3996 golangci/golangci-lint#4630 golangci/golangci-lint#4912 golangci/golangci-lint#4964 golangci/golangci-lint#5037 golangci/golangci-lint#5051 golangci/golangci-lint#5184 golangci/golangci-lint#5428 golangci/golangci-lint#5437 In addition to numerous discussions. The only common thread between the responses to these discussions, is that the "no export data" error message is obscuring whatever the underlying problem is, and making it more difficult to troubleshoot. https://github.com/golangci/golangci-lint/discussions/2489 https://github.com/golangci/golangci-lint/discussions/2191 https://github.com/golangci/golangci-lint/discussions/2751 https://github.com/golangci/golangci-lint/discussions/3363 https://github.com/golangci/golangci-lint/discussions/4829 https://github.com/golangci/golangci-lint/discussions/4880 This fix simply walks the import tree and appends any found error messages to the "no export data" error message. This will make it much clearer for end users to discover the problem that is causing the linter to fail. Example output: ERRO Running error: can't run linter goanalysis_metalinter inspect: failed to load package errs: could not load export data: no export data for "github.com/segmentio/ctlstore/pkg/errs" because of error in imported package(s): error in package "github.com/segmentio/stats/v5": # github.com/segmentio/stats/v5 vendor/github.com/segmentio/stats/v5/tag.go:38:35: in call to slices.IsSortedFunc, type func(a Tag, b Tag) int of tagCompare does not match inferred type func(a Tag, b Tag) bool for func(a E, b E) bool Fixes #6056. --- pkg/goanalysis/runner_loadingpackage.go | 27 ++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/pkg/goanalysis/runner_loadingpackage.go b/pkg/goanalysis/runner_loadingpackage.go index 29a27089c1c2..098550a61328 100644 --- a/pkg/goanalysis/runner_loadingpackage.go +++ b/pkg/goanalysis/runner_loadingpackage.go @@ -240,7 +240,32 @@ func (lp *loadingPackage) loadFromExportData() error { } } if pkg.ExportFile == "" { - return fmt.Errorf("no export data for %q", pkg.ID) + seen := make(map[string]struct{}) + + count := 0 + var sb strings.Builder + packages.Visit([]*packages.Package{pkg}, func(p *packages.Package) bool { + if _, ok := seen[p.ID]; ok { + return false + } + if count >= 3 { + return false + } + seen[p.ID] = struct{}{} + // Optionally skip test variants: + if p.ForTest == "" && !strings.HasSuffix(p.PkgPath, ".test") { + for _, e := range p.Errors { + fmt.Fprintf(&sb, "error in package %q: %v\n", p.ID, e.Msg) + count++ + } + } + return true // keep walking into p.Imports + }, nil) + err := fmt.Sprintf("no export data for %q", pkg.ID) + if sb.Len() > 0 { + err += fmt.Sprintf(" because of error in imported package(s): %v", sb.String()) + } + return errors.New(err) } f, err := os.Open(pkg.ExportFile) if err != nil { From e1fc2a7300d1feae34ed95de276b59e951055f83 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Fri, 5 Sep 2025 23:06:27 +0200 Subject: [PATCH 2/2] review --- pkg/goanalysis/runner_loadingpackage.go | 66 +++++++++++++++---------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/pkg/goanalysis/runner_loadingpackage.go b/pkg/goanalysis/runner_loadingpackage.go index 098550a61328..ad45377e4dbb 100644 --- a/pkg/goanalysis/runner_loadingpackage.go +++ b/pkg/goanalysis/runner_loadingpackage.go @@ -239,34 +239,11 @@ func (lp *loadingPackage) loadFromExportData() error { return fmt.Errorf("dependency %q hasn't been loaded yet", path) } } - if pkg.ExportFile == "" { - seen := make(map[string]struct{}) - count := 0 - var sb strings.Builder - packages.Visit([]*packages.Package{pkg}, func(p *packages.Package) bool { - if _, ok := seen[p.ID]; ok { - return false - } - if count >= 3 { - return false - } - seen[p.ID] = struct{}{} - // Optionally skip test variants: - if p.ForTest == "" && !strings.HasSuffix(p.PkgPath, ".test") { - for _, e := range p.Errors { - fmt.Fprintf(&sb, "error in package %q: %v\n", p.ID, e.Msg) - count++ - } - } - return true // keep walking into p.Imports - }, nil) - err := fmt.Sprintf("no export data for %q", pkg.ID) - if sb.Len() > 0 { - err += fmt.Sprintf(" because of error in imported package(s): %v", sb.String()) - } - return errors.New(err) + if pkg.ExportFile == "" { + return noExportDataError(pkg) } + f, err := os.Open(pkg.ExportFile) if err != nil { return err @@ -357,6 +334,7 @@ func (lp *loadingPackage) loadImportedPackageWithFacts(loadMode LoadMode) error if srcErr := lp.loadFromSource(loadMode); srcErr != nil { return srcErr } + // Make sure this package can't be imported successfully pkg.Errors = append(pkg.Errors, packages.Error{ Pos: "-", @@ -578,3 +556,39 @@ func sizeOfReflectValueTreeBytes(rv reflect.Value, visitedPtrs map[uintptr]struc panic("unknown rv of type " + rv.String()) } } + +func noExportDataError(pkg *packages.Package) error { + seen := make(map[string]struct{}) + + const limit = 3 + + var count int + + var sb strings.Builder + + packages.Visit([]*packages.Package{pkg}, func(p *packages.Package) bool { + if count >= limit { + return false + } + + if _, ok := seen[p.ID]; ok { + return false + } + + seen[p.ID] = struct{}{} + + for _, e := range p.Errors { + fmt.Fprintf(&sb, "\terror in package %q: %s\n", p.ID, e.Msg) + count++ + } + + return true + }, nil) + + msg := fmt.Sprintf("no export data for %q", pkg.ID) + if sb.Len() > 0 { + msg += fmt.Sprintf(", maybe because of error(s) in imported package(s):\n%s", sb.String()) + } + + return errors.New(msg) +}