@@ -3,12 +3,13 @@ package interp
33import (
44 "fmt"
55 "io/fs"
6- "os"
76 "path/filepath"
87 "strings"
8+
9+ "golang.org/x/tools/go/packages"
910)
1011
11- // importSrc calls gta on the source code for the package identified by
12+ // importSrc calls global tag analysis on the source code for the package identified by
1213// importPath. rPath is the relative path to the directory containing the source
1314// code for the package. It can also be "main" as a special value.
1415func (interp * Interpreter ) importSrc (rPath , importPath string , skipTest bool ) (string , error ) {
@@ -23,24 +24,14 @@ func (interp *Interpreter) importSrc(rPath, importPath string, skipTest bool) (s
2324 return name , nil
2425 }
2526
26- // For relative import paths in the form "./xxx" or "../xxx", the initial
27- // base path is the directory of the interpreter input file, or "." if no file
28- // was provided.
29- // In all other cases, absolute import paths are resolved from the GOPATH
30- // and the nested "vendor" directories.
27+ // resolve relative and absolute import paths
3128 if isPathRelative (importPath ) {
3229 if rPath == mainID {
3330 rPath = "."
3431 }
3532 dir = filepath .Join (filepath .Dir (interp .name ), rPath , importPath )
36- } else if dir , rPath , err = interp .pkgDir (interp .context .GOPATH , rPath , importPath ); err != nil {
37- // Try again, assuming a root dir at the source location.
38- if rPath , err = interp .rootFromSourceLocation (); err != nil {
39- return "" , err
40- }
41- if dir , rPath , err = interp .pkgDir (interp .context .GOPATH , rPath , importPath ); err != nil {
42- return "" , err
43- }
33+ } else if dir , err = interp .getPackageDir (importPath ); err != nil {
34+ return "" , err
4435 }
4536
4637 if interp .rdir [importPath ] {
@@ -171,119 +162,87 @@ func (interp *Interpreter) importSrc(rPath, importPath string, skipTest bool) (s
171162 return pkgName , nil
172163}
173164
174- // rootFromSourceLocation returns the path to the directory containing the input
175- // Go file given to the interpreter, relative to $GOPATH/src.
176- // It is meant to be called in the case when the initial input is a main package.
177- func (interp * Interpreter ) rootFromSourceLocation () (string , error ) {
178- sourceFile := interp .name
179- if sourceFile == DefaultSourceName {
180- return "" , nil
181- }
182- wd , err := os .Getwd ()
165+ // getPackageDir uses the GOPATH to find the absolute path of an import path.
166+ func (interp * Interpreter ) getPackageDir (importPath string ) (string , error ) {
167+ // search the standard library and Go modules.
168+ config := packages.Config {}
169+ config .Env = append (config .Env , "GOPATH=" + interp .context .GOPATH , "GOCACHE=" + interp .opt .env ["goCache" ], "GOTOOLDIR=" + interp .opt .env ["goToolDir" ])
170+ pkgs , err := packages .Load (& config , importPath )
183171 if err != nil {
184- return "" , err
172+ return "" , fmt . Errorf ( "an error occurred retrieving a package from the GOPATH: %v \n %v \n If Access is denied, run in administrator" , importPath , err )
185173 }
186- pkgDir := filepath .Join (wd , filepath .Dir (sourceFile ))
187- root := strings .TrimPrefix (pkgDir , filepath .Join (interp .context .GOPATH , "src" )+ "/" )
188- if root == wd {
189- return "" , fmt .Errorf ("package location %s not in GOPATH" , pkgDir )
174+
175+ // confirm the import path is found.
176+ for _ , pkg := range pkgs {
177+ for _ , goFile := range pkg .GoFiles {
178+ if strings .Contains (filepath .Dir (goFile ), pkg .Name ) {
179+ return filepath .Dir (goFile ), nil
180+ }
181+ }
190182 }
191- return root , nil
192- }
193183
194- // pkgDir returns the absolute path in filesystem for a package given its import path
195- // and the root of the subtree dependencies.
196- func (interp * Interpreter ) pkgDir (goPath string , root , importPath string ) (string , string , error ) {
197- rPath := filepath .Join (root , "vendor" )
198- dir := filepath .Join (goPath , "src" , rPath , importPath )
184+ // check for certain go tools located in GOTOOLDIR.
185+ if interp .opt .env ["goToolDir" ] != "" {
186+ // search for the go directory before searching for packages.
187+ // this approach prevents the computer from searching the entire filesystem.
188+ godir , err := searchUpDirPath (interp .opt .env ["goToolDir" ], "go" , false )
189+ if err != nil {
190+ return "" , fmt .Errorf ("an import source could not be found: %q\n The current GOPATH=%v, GOCACHE=%v, GOTOOLDIR=%v\n %v" , importPath , interp .context .GOPATH , interp .opt .env ["goCache" ], interp .opt .env ["goToolDir" ], err )
191+ }
199192
200- if _ , err := fs .Stat (interp .opt .filesystem , dir ); err == nil {
201- return dir , rPath , nil // found!
193+ absimportpath , err := searchDirs (godir , importPath )
194+ if err != nil {
195+ return "" , fmt .Errorf ("an import source could not be found: %q\n The current GOPATH=%v, GOCACHE=%v, GOTOOLDIR=%v\n %v" , importPath , interp .context .GOPATH , interp .opt .env ["goCache" ], interp .opt .env ["goToolDir" ], err )
196+ }
197+ return absimportpath , nil
202198 }
203199
204- dir = filepath .Join (goPath , "src" , effectivePkg (root , importPath ))
200+ return "" , fmt .Errorf ("an import source could not be found: %q. Set the GOPATH and/or GOTOOLDIR environment variable from Interpreter.Options" , importPath )
201+ }
205202
206- if _ , err := fs .Stat (interp .opt .filesystem , dir ); err == nil {
207- return dir , root , nil // found!
203+ // searchUpDirPath searches up a directory path in order to find a target directory.
204+ func searchUpDirPath (initial string , target string , isCaseSensitive bool ) (string , error ) {
205+ // strings.Split always returns [:0] as filepath.Dir returns "." or the last directory.
206+ splitdir := strings .Split (filepath .Clean (initial ), string (filepath .Separator ))
207+ if len (splitdir ) == 1 {
208+ return "" , fmt .Errorf ("the target directory %q is not within the path %q" , target , initial )
208209 }
209210
210- if len (root ) == 0 {
211- if interp .context .GOPATH == "" {
212- return "" , "" , fmt .Errorf ("unable to find source related to: %q. Either the GOPATH environment variable, or the Interpreter.Options.GoPath needs to be set" , importPath )
213- }
214- return "" , "" , fmt .Errorf ("unable to find source related to: %q" , importPath )
211+ updir := splitdir [len (splitdir )- 1 ]
212+ if ! isCaseSensitive {
213+ updir = strings .ToLower (updir )
215214 }
216-
217- rootPath := filepath .Join (goPath , "src" , root )
218- prevRoot , err := previousRoot (interp .opt .filesystem , rootPath , root )
219- if err != nil {
220- return "" , "" , err
215+ if updir == target {
216+ return initial , nil
221217 }
222-
223- return interp .pkgDir (goPath , prevRoot , importPath )
218+ return searchUpDirPath (filepath .Dir (initial ), target , isCaseSensitive )
224219}
225220
226- const vendor = "vendor"
227-
228- // Find the previous source root (vendor > vendor > ... > GOPATH).
229- func previousRoot (filesystem fs.FS , rootPath , root string ) (string , error ) {
230- rootPath = filepath .Clean (rootPath )
231- parent , final := filepath .Split (rootPath )
232- parent = filepath .Clean (parent )
233-
234- // TODO(mpl): maybe it works for the special case main, but can't be bothered for now.
235- if root != mainID && final != vendor {
236- root = strings .TrimSuffix (root , string (filepath .Separator ))
237- prefix := strings .TrimSuffix (strings .TrimSuffix (rootPath , root ), string (filepath .Separator ))
238-
239- // look for the closest vendor in one of our direct ancestors, as it takes priority.
240- var vendored string
241- for {
242- fi , err := fs .Stat (filesystem , filepath .Join (parent , vendor ))
243- if err == nil && fi .IsDir () {
244- vendored = strings .TrimPrefix (strings .TrimPrefix (parent , prefix ), string (filepath .Separator ))
245- break
246- }
247- if ! os .IsNotExist (err ) {
248- return "" , err
249- }
250-
251- // stop when we reach GOPATH/src/blah
252- parent = filepath .Dir (parent )
253- if parent == prefix {
254- break
255- }
221+ // searchDirs searches within a directory (and its subdirectories) in an attempt to find a filepath.
222+ func searchDirs (initial string , target string ) (string , error ) {
223+ absfilepath , err := filepath .Abs (initial )
224+ if err != nil {
225+ return "" , err
226+ }
256227
257- // just an additional failsafe, stop if we reach the filesystem root, or dot (if
258- // we are dealing with relative paths).
259- // TODO(mpl): It should probably be a critical error actually,
260- // as we shouldn't have gone that high up in the tree.
261- if parent == string ( filepath . Separator ) || parent == "." {
262- break
228+ // find the go directory.
229+ var foundpath string
230+ filter := func ( path string , d fs. DirEntry , err error ) error {
231+ if d . IsDir () {
232+ if d . Name () == target {
233+ foundpath = path
263234 }
264235 }
265-
266- if vendored != "" {
267- return vendored , nil
268- }
236+ return nil
269237 }
270-
271- // TODO(mpl): the algorithm below might be redundant with the one above,
272- // but keeping it for now. Investigate/simplify/remove later.
273- splitRoot := strings .Split (root , string (filepath .Separator ))
274- var index int
275- for i := len (splitRoot ) - 1 ; i >= 0 ; i -- {
276- if splitRoot [i ] == "vendor" {
277- index = i
278- break
279- }
238+ if err = filepath .WalkDir (absfilepath , filter ); err != nil {
239+ return "" , fmt .Errorf ("An error occurred searching for a directory.\n %v" , err )
280240 }
281241
282- if index == 0 {
283- return "" , nil
242+ if foundpath != "" {
243+ return foundpath , nil
284244 }
285-
286- return filepath .Join (splitRoot [:index ]... ), nil
245+ return "" , fmt .Errorf ("The target filepath %q is not within the path %q" , target , initial )
287246}
288247
289248func effectivePkg (root , path string ) string {
0 commit comments