@@ -53,16 +53,118 @@ func NewPackageLoader(log logutils.Log, cfg *config.Config, args []string, env *
53
53
54
54
// Load loads packages.
55
55
func (l * PackageLoader ) Load (ctx context.Context , linters []* linter.Config ) (pkgs , deduplicatedPkgs []* packages.Package , err error ) {
56
+ // Check for multiple modules and provide helpful error
57
+ if err := l .detectMultipleModules (); err != nil {
58
+ return nil , nil , err
59
+ }
60
+
56
61
loadMode := findLoadMode (linters )
57
62
58
- pkgs , err = l .loadPackages (ctx , loadMode )
59
- if err != nil {
60
- return nil , nil , fmt .Errorf ("failed to load packages: %w" , err )
63
+ pkgs , loadErr : = l .loadPackages (ctx , loadMode )
64
+ if loadErr != nil {
65
+ return nil , nil , fmt .Errorf ("failed to load packages: %w" , loadErr )
61
66
}
62
67
63
68
return pkgs , l .filterDuplicatePackages (pkgs ), nil
64
69
}
65
70
71
+ // detectMultipleModules checks if multiple arguments refer to different modules
72
+ func (l * PackageLoader ) detectMultipleModules () error {
73
+ if len (l .args ) <= 1 {
74
+ return nil
75
+ }
76
+
77
+ var moduleRoots []string
78
+ seenRoots := make (map [string ]bool )
79
+
80
+ for _ , arg := range l .args {
81
+ moduleRoot := l .findModuleRootForArg (arg )
82
+ if moduleRoot != "" && ! seenRoots [moduleRoot ] {
83
+ moduleRoots = append (moduleRoots , moduleRoot )
84
+ seenRoots [moduleRoot ] = true
85
+ }
86
+ }
87
+
88
+ if len (moduleRoots ) > 1 {
89
+ return fmt .Errorf ("multiple Go modules detected: %v\n \n " +
90
+ "Multi-module analysis is not supported. Each module should be analyzed separately:\n " +
91
+ " golangci-lint run %s\n golangci-lint run %s" ,
92
+ moduleRoots , moduleRoots [0 ], moduleRoots [1 ])
93
+ }
94
+
95
+ return nil
96
+ }
97
+
98
+ // findModuleRootForArg finds the module root for a given argument using go env
99
+ func (l * PackageLoader ) findModuleRootForArg (arg string ) string {
100
+ absPath , err := filepath .Abs (arg )
101
+ if err != nil {
102
+ if l .debugf != nil {
103
+ l .debugf ("Failed to get absolute path for %s: %v" , arg , err )
104
+ }
105
+ return ""
106
+ }
107
+
108
+ // Determine the directory to check
109
+ var targetDir string
110
+ if info , statErr := os .Stat (absPath ); statErr == nil && info .IsDir () {
111
+ targetDir = absPath
112
+ } else if statErr == nil {
113
+ targetDir = filepath .Dir (absPath )
114
+ } else {
115
+ return ""
116
+ }
117
+
118
+ // Save current directory
119
+ originalWd , err := os .Getwd ()
120
+ if err != nil {
121
+ if l .debugf != nil {
122
+ l .debugf ("Failed to get current directory: %v" , err )
123
+ }
124
+ return ""
125
+ }
126
+ defer func () {
127
+ if chErr := os .Chdir (originalWd ); chErr != nil && l .debugf != nil {
128
+ l .debugf ("Failed to restore directory %s: %v" , originalWd , chErr )
129
+ }
130
+ }()
131
+
132
+ // Change to target directory and use go env GOMOD
133
+ if chdirErr := os .Chdir (targetDir ); chdirErr != nil {
134
+ if l .debugf != nil {
135
+ l .debugf ("Failed to change to directory %s: %v" , targetDir , chdirErr )
136
+ }
137
+ return ""
138
+ }
139
+
140
+ goModPath , err := goenv .GetOne (context .Background (), goenv .GOMOD )
141
+ if err != nil || goModPath == "" {
142
+ if l .debugf != nil {
143
+ l .debugf ("go env GOMOD failed in %s: err=%v, path=%s" , targetDir , err , goModPath )
144
+ }
145
+ return ""
146
+ }
147
+
148
+ return filepath .Dir (goModPath )
149
+ }
150
+
151
+ // determineWorkingDir determines the working directory for package loading.
152
+ // If the first argument is within a directory tree that has a go.mod file, use that module root.
153
+ // Otherwise, use the current working directory.
154
+ func (l * PackageLoader ) determineWorkingDir () string {
155
+ if len (l .args ) == 0 {
156
+ return ""
157
+ }
158
+
159
+ moduleRoot := l .findModuleRootForArg (l .args [0 ])
160
+ if moduleRoot != "" {
161
+ if l .debugf != nil {
162
+ l .debugf ("Found module root %s, using as working dir" , moduleRoot )
163
+ }
164
+ }
165
+ return moduleRoot
166
+ }
167
+
66
168
func (l * PackageLoader ) loadPackages (ctx context.Context , loadMode packages.LoadMode ) ([]* packages.Package , error ) {
67
169
defer func (startedAt time.Time ) {
68
170
l .log .Infof ("Go packages loading at mode %s took %s" , stringifyLoadMode (loadMode ), time .Since (startedAt ))
@@ -76,10 +178,11 @@ func (l *PackageLoader) loadPackages(ctx context.Context, loadMode packages.Load
76
178
Context : ctx ,
77
179
BuildFlags : l .makeBuildFlags (),
78
180
Logf : l .debugf ,
181
+ Dir : l .determineWorkingDir (),
79
182
// TODO: use fset, parsefile, overlay
80
183
}
81
184
82
- args := buildArgs ( l . args )
185
+ args := l . buildArgs ( )
83
186
84
187
l .debugf ("Built loader args are %s" , args )
85
188
@@ -233,13 +336,23 @@ func (l *PackageLoader) makeBuildFlags() []string {
233
336
return buildFlags
234
337
}
235
338
236
- func buildArgs (args []string ) []string {
237
- if len (args ) == 0 {
339
+ // buildArgs processes the arguments for package loading, handling directory changes appropriately.
340
+ func (l * PackageLoader ) buildArgs () []string {
341
+ if len (l .args ) == 0 {
342
+ return []string {"./..." }
343
+ }
344
+
345
+ workingDir := l .determineWorkingDir ()
346
+
347
+ // If we're using a different working directory, we need to adjust the arguments
348
+ if workingDir != "" {
349
+ // We're switching to the target directory as working dir, so use "./..." to analyze it
238
350
return []string {"./..." }
239
351
}
240
352
353
+ // Use the original buildArgs logic for the current working directory
241
354
var retArgs []string
242
- for _ , arg := range args {
355
+ for _ , arg := range l . args {
243
356
if strings .HasPrefix (arg , "." ) || filepath .IsAbs (arg ) {
244
357
retArgs = append (retArgs , arg )
245
358
} else {
0 commit comments