golang.org/x/tools/gopls@v0.15.3/internal/cache/workspace.go (about) 1 // Copyright 2020 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package cache 6 7 import ( 8 "context" 9 "errors" 10 "fmt" 11 "path/filepath" 12 13 "golang.org/x/mod/modfile" 14 "golang.org/x/tools/gopls/internal/file" 15 "golang.org/x/tools/gopls/internal/protocol" 16 ) 17 18 // isGoWork reports if uri is a go.work file. 19 func isGoWork(uri protocol.DocumentURI) bool { 20 return filepath.Base(uri.Path()) == "go.work" 21 } 22 23 // goWorkModules returns the URIs of go.mod files named by the go.work file. 24 func goWorkModules(ctx context.Context, gowork protocol.DocumentURI, fs file.Source) (map[protocol.DocumentURI]unit, error) { 25 fh, err := fs.ReadFile(ctx, gowork) 26 if err != nil { 27 return nil, err // canceled 28 } 29 content, err := fh.Content() 30 if err != nil { 31 return nil, err 32 } 33 filename := gowork.Path() 34 dir := filepath.Dir(filename) 35 workFile, err := modfile.ParseWork(filename, content, nil) 36 if err != nil { 37 return nil, fmt.Errorf("parsing go.work: %w", err) 38 } 39 var usedDirs []string 40 for _, use := range workFile.Use { 41 usedDirs = append(usedDirs, use.Path) 42 } 43 return localModFiles(dir, usedDirs), nil 44 } 45 46 // localModFiles builds a set of local go.mod files referenced by 47 // goWorkOrModPaths, which is a slice of paths as contained in a go.work 'use' 48 // directive or go.mod 'replace' directive (and which therefore may use either 49 // '/' or '\' as a path separator). 50 func localModFiles(relativeTo string, goWorkOrModPaths []string) map[protocol.DocumentURI]unit { 51 modFiles := make(map[protocol.DocumentURI]unit) 52 for _, path := range goWorkOrModPaths { 53 modDir := filepath.FromSlash(path) 54 if !filepath.IsAbs(modDir) { 55 modDir = filepath.Join(relativeTo, modDir) 56 } 57 modURI := protocol.URIFromPath(filepath.Join(modDir, "go.mod")) 58 modFiles[modURI] = unit{} 59 } 60 return modFiles 61 } 62 63 // isGoMod reports if uri is a go.mod file. 64 func isGoMod(uri protocol.DocumentURI) bool { 65 return filepath.Base(uri.Path()) == "go.mod" 66 } 67 68 // goModModules returns the URIs of "workspace" go.mod files defined by a 69 // go.mod file. This set is defined to be the given go.mod file itself, as well 70 // as the modfiles of any locally replaced modules in the go.mod file. 71 func goModModules(ctx context.Context, gomod protocol.DocumentURI, fs file.Source) (map[protocol.DocumentURI]unit, error) { 72 fh, err := fs.ReadFile(ctx, gomod) 73 if err != nil { 74 return nil, err // canceled 75 } 76 content, err := fh.Content() 77 if err != nil { 78 return nil, err 79 } 80 filename := gomod.Path() 81 dir := filepath.Dir(filename) 82 modFile, err := modfile.Parse(filename, content, nil) 83 if err != nil { 84 return nil, err 85 } 86 var localReplaces []string 87 for _, replace := range modFile.Replace { 88 if modfile.IsDirectoryPath(replace.New.Path) { 89 localReplaces = append(localReplaces, replace.New.Path) 90 } 91 } 92 modFiles := localModFiles(dir, localReplaces) 93 modFiles[gomod] = unit{} 94 return modFiles, nil 95 } 96 97 // fileExists reports whether the file has a Content (which may be empty). 98 // An overlay exists even if it is not reflected in the file system. 99 func fileExists(fh file.Handle) bool { 100 _, err := fh.Content() 101 return err == nil 102 } 103 104 // errExhausted is returned by findModules if the file scan limit is reached. 105 var errExhausted = errors.New("exhausted") 106 107 // Limit go.mod search to 1 million files. As a point of reference, 108 // Kubernetes has 22K files (as of 2020-11-24). 109 // 110 // Note: per golang/go#56496, the previous limit of 1M files was too slow, at 111 // which point this limit was decreased to 100K. 112 const fileLimit = 100_000