@@ -53,16 +53,118 @@ func NewPackageLoader(log logutils.Log, cfg *config.Config, args []string, env *
5353
5454// Load loads packages.
5555func (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+
5661 loadMode := findLoadMode (linters )
5762
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 )
6166 }
6267
6368 return pkgs , l .filterDuplicatePackages (pkgs ), nil
6469}
6570
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+
66168func (l * PackageLoader ) loadPackages (ctx context.Context , loadMode packages.LoadMode ) ([]* packages.Package , error ) {
67169 defer func (startedAt time.Time ) {
68170 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
76178 Context : ctx ,
77179 BuildFlags : l .makeBuildFlags (),
78180 Logf : l .debugf ,
181+ Dir : l .determineWorkingDir (),
79182 // TODO: use fset, parsefile, overlay
80183 }
81184
82- args := buildArgs ( l . args )
185+ args := l . buildArgs ( )
83186
84187 l .debugf ("Built loader args are %s" , args )
85188
@@ -233,13 +336,23 @@ func (l *PackageLoader) makeBuildFlags() []string {
233336 return buildFlags
234337}
235338
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
238350 return []string {"./..." }
239351 }
240352
353+ // Use the original buildArgs logic for the current working directory
241354 var retArgs []string
242- for _ , arg := range args {
355+ for _ , arg := range l . args {
243356 if strings .HasPrefix (arg , "." ) || filepath .IsAbs (arg ) {
244357 retArgs = append (retArgs , arg )
245358 } else {
0 commit comments