Code refactoring for bpa operator
[icn.git] / cmd / bpa-operator / vendor / golang.org / x / tools / imports / mod.go
1 package imports
2
3 import (
4         "bytes"
5         "encoding/json"
6         "io/ioutil"
7         "log"
8         "os"
9         "path"
10         "path/filepath"
11         "regexp"
12         "sort"
13         "strconv"
14         "strings"
15         "sync"
16         "time"
17
18         "golang.org/x/tools/internal/gopathwalk"
19         "golang.org/x/tools/internal/module"
20 )
21
22 // moduleResolver implements resolver for modules using the go command as little
23 // as feasible.
24 type moduleResolver struct {
25         env *fixEnv
26
27         initialized   bool
28         main          *moduleJSON
29         modsByModPath []*moduleJSON // All modules, ordered by # of path components in module Path...
30         modsByDir     []*moduleJSON // ...or Dir.
31 }
32
33 type moduleJSON struct {
34         Path     string           // module path
35         Version  string           // module version
36         Versions []string         // available module versions (with -versions)
37         Replace  *moduleJSON      // replaced by this module
38         Time     *time.Time       // time version was created
39         Update   *moduleJSON      // available update, if any (with -u)
40         Main     bool             // is this the main module?
41         Indirect bool             // is this module only an indirect dependency of main module?
42         Dir      string           // directory holding files for this module, if any
43         GoMod    string           // path to go.mod file for this module, if any
44         Error    *moduleErrorJSON // error loading module
45 }
46
47 type moduleErrorJSON struct {
48         Err string // the error itself
49 }
50
51 func (r *moduleResolver) init() error {
52         if r.initialized {
53                 return nil
54         }
55         stdout, err := r.env.invokeGo("list", "-m", "-json", "...")
56         if err != nil {
57                 return err
58         }
59         for dec := json.NewDecoder(stdout); dec.More(); {
60                 mod := &moduleJSON{}
61                 if err := dec.Decode(mod); err != nil {
62                         return err
63                 }
64                 if mod.Dir == "" {
65                         if Debug {
66                                 log.Printf("module %v has not been downloaded and will be ignored", mod.Path)
67                         }
68                         // Can't do anything with a module that's not downloaded.
69                         continue
70                 }
71                 r.modsByModPath = append(r.modsByModPath, mod)
72                 r.modsByDir = append(r.modsByDir, mod)
73                 if mod.Main {
74                         r.main = mod
75                 }
76         }
77
78         sort.Slice(r.modsByModPath, func(i, j int) bool {
79                 count := func(x int) int {
80                         return strings.Count(r.modsByModPath[x].Path, "/")
81                 }
82                 return count(j) < count(i) // descending order
83         })
84         sort.Slice(r.modsByDir, func(i, j int) bool {
85                 count := func(x int) int {
86                         return strings.Count(r.modsByDir[x].Dir, "/")
87                 }
88                 return count(j) < count(i) // descending order
89         })
90
91         r.initialized = true
92         return nil
93 }
94
95 // findPackage returns the module and directory that contains the package at
96 // the given import path, or returns nil, "" if no module is in scope.
97 func (r *moduleResolver) findPackage(importPath string) (*moduleJSON, string) {
98         for _, m := range r.modsByModPath {
99                 if !strings.HasPrefix(importPath, m.Path) {
100                         continue
101                 }
102                 pathInModule := importPath[len(m.Path):]
103                 pkgDir := filepath.Join(m.Dir, pathInModule)
104                 if dirIsNestedModule(pkgDir, m) {
105                         continue
106                 }
107
108                 pkgFiles, err := ioutil.ReadDir(pkgDir)
109                 if err != nil {
110                         continue
111                 }
112
113                 // A module only contains a package if it has buildable go
114                 // files in that directory. If not, it could be provided by an
115                 // outer module. See #29736.
116                 for _, fi := range pkgFiles {
117                         if ok, _ := r.env.buildContext().MatchFile(pkgDir, fi.Name()); ok {
118                                 return m, pkgDir
119                         }
120                 }
121         }
122         return nil, ""
123 }
124
125 // findModuleByDir returns the module that contains dir, or nil if no such
126 // module is in scope.
127 func (r *moduleResolver) findModuleByDir(dir string) *moduleJSON {
128         // This is quite tricky and may not be correct. dir could be:
129         // - a package in the main module.
130         // - a replace target underneath the main module's directory.
131         //    - a nested module in the above.
132         // - a replace target somewhere totally random.
133         //    - a nested module in the above.
134         // - in the mod cache.
135         // - in /vendor/ in -mod=vendor mode.
136         //    - nested module? Dunno.
137         // Rumor has it that replace targets cannot contain other replace targets.
138         for _, m := range r.modsByDir {
139                 if !strings.HasPrefix(dir, m.Dir) {
140                         continue
141                 }
142
143                 if dirIsNestedModule(dir, m) {
144                         continue
145                 }
146
147                 return m
148         }
149         return nil
150 }
151
152 // dirIsNestedModule reports if dir is contained in a nested module underneath
153 // mod, not actually in mod.
154 func dirIsNestedModule(dir string, mod *moduleJSON) bool {
155         if !strings.HasPrefix(dir, mod.Dir) {
156                 return false
157         }
158         mf := findModFile(dir)
159         if mf == "" {
160                 return false
161         }
162         return filepath.Dir(mf) != mod.Dir
163 }
164
165 func findModFile(dir string) string {
166         for {
167                 f := filepath.Join(dir, "go.mod")
168                 info, err := os.Stat(f)
169                 if err == nil && !info.IsDir() {
170                         return f
171                 }
172                 d := filepath.Dir(dir)
173                 if len(d) >= len(dir) {
174                         return "" // reached top of file system, no go.mod
175                 }
176                 dir = d
177         }
178 }
179
180 func (r *moduleResolver) loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) {
181         if err := r.init(); err != nil {
182                 return nil, err
183         }
184         names := map[string]string{}
185         for _, path := range importPaths {
186                 _, packageDir := r.findPackage(path)
187                 if packageDir == "" {
188                         continue
189                 }
190                 name, err := packageDirToName(packageDir)
191                 if err != nil {
192                         continue
193                 }
194                 names[path] = name
195         }
196         return names, nil
197 }
198
199 func (r *moduleResolver) scan(_ references) ([]*pkg, error) {
200         if err := r.init(); err != nil {
201                 return nil, err
202         }
203
204         // Walk GOROOT, GOPATH/pkg/mod, and the main module.
205         roots := []gopathwalk.Root{
206                 {filepath.Join(r.env.GOROOT, "/src"), gopathwalk.RootGOROOT},
207         }
208         if r.main != nil {
209                 roots = append(roots, gopathwalk.Root{r.main.Dir, gopathwalk.RootCurrentModule})
210         }
211         for _, p := range filepath.SplitList(r.env.GOPATH) {
212                 roots = append(roots, gopathwalk.Root{filepath.Join(p, "/pkg/mod"), gopathwalk.RootModuleCache})
213         }
214
215         // Walk replace targets, just in case they're not in any of the above.
216         for _, mod := range r.modsByModPath {
217                 if mod.Replace != nil {
218                         roots = append(roots, gopathwalk.Root{mod.Dir, gopathwalk.RootOther})
219                 }
220         }
221
222         var result []*pkg
223         dupCheck := make(map[string]bool)
224         var mu sync.Mutex
225
226         gopathwalk.Walk(roots, func(root gopathwalk.Root, dir string) {
227                 mu.Lock()
228                 defer mu.Unlock()
229
230                 if _, dup := dupCheck[dir]; dup {
231                         return
232                 }
233
234                 dupCheck[dir] = true
235
236                 subdir := ""
237                 if dir != root.Path {
238                         subdir = dir[len(root.Path)+len("/"):]
239                 }
240                 importPath := filepath.ToSlash(subdir)
241                 if strings.HasPrefix(importPath, "vendor/") {
242                         // Ignore vendor dirs. If -mod=vendor is on, then things
243                         // should mostly just work, but when it's not vendor/
244                         // is a mess. There's no easy way to tell if it's on.
245                         // We can still find things in the mod cache and
246                         // map them into /vendor when -mod=vendor is on.
247                         return
248                 }
249                 switch root.Type {
250                 case gopathwalk.RootCurrentModule:
251                         importPath = path.Join(r.main.Path, filepath.ToSlash(subdir))
252                 case gopathwalk.RootModuleCache:
253                         matches := modCacheRegexp.FindStringSubmatch(subdir)
254                         modPath, err := module.DecodePath(filepath.ToSlash(matches[1]))
255                         if err != nil {
256                                 if Debug {
257                                         log.Printf("decoding module cache path %q: %v", subdir, err)
258                                 }
259                                 return
260                         }
261                         importPath = path.Join(modPath, filepath.ToSlash(matches[3]))
262                 case gopathwalk.RootGOROOT:
263                         importPath = subdir
264                 }
265
266                 // Check if the directory is underneath a module that's in scope.
267                 if mod := r.findModuleByDir(dir); mod != nil {
268                         // It is. If dir is the target of a replace directive,
269                         // our guessed import path is wrong. Use the real one.
270                         if mod.Dir == dir {
271                                 importPath = mod.Path
272                         } else {
273                                 dirInMod := dir[len(mod.Dir)+len("/"):]
274                                 importPath = path.Join(mod.Path, filepath.ToSlash(dirInMod))
275                         }
276                 } else {
277                         // The package is in an unknown module. Check that it's
278                         // not obviously impossible to import.
279                         var modFile string
280                         switch root.Type {
281                         case gopathwalk.RootModuleCache:
282                                 matches := modCacheRegexp.FindStringSubmatch(subdir)
283                                 modFile = filepath.Join(matches[1], "@", matches[2], "go.mod")
284                         default:
285                                 modFile = findModFile(dir)
286                         }
287
288                         modBytes, err := ioutil.ReadFile(modFile)
289                         if err == nil && !strings.HasPrefix(importPath, modulePath(modBytes)) {
290                                 // The module's declared path does not match
291                                 // its expected path. It probably needs a
292                                 // replace directive we don't have.
293                                 return
294                         }
295                 }
296                 // We may have discovered a package that has a different version
297                 // in scope already. Canonicalize to that one if possible.
298                 if _, canonicalDir := r.findPackage(importPath); canonicalDir != "" {
299                         dir = canonicalDir
300                 }
301
302                 result = append(result, &pkg{
303                         importPathShort: VendorlessPath(importPath),
304                         dir:             dir,
305                 })
306         }, gopathwalk.Options{Debug: Debug, ModulesEnabled: true})
307         return result, nil
308 }
309
310 // modCacheRegexp splits a path in a module cache into module, module version, and package.
311 var modCacheRegexp = regexp.MustCompile(`(.*)@([^/\\]*)(.*)`)
312
313 var (
314         slashSlash = []byte("//")
315         moduleStr  = []byte("module")
316 )
317
318 // modulePath returns the module path from the gomod file text.
319 // If it cannot find a module path, it returns an empty string.
320 // It is tolerant of unrelated problems in the go.mod file.
321 //
322 // Copied from cmd/go/internal/modfile.
323 func modulePath(mod []byte) string {
324         for len(mod) > 0 {
325                 line := mod
326                 mod = nil
327                 if i := bytes.IndexByte(line, '\n'); i >= 0 {
328                         line, mod = line[:i], line[i+1:]
329                 }
330                 if i := bytes.Index(line, slashSlash); i >= 0 {
331                         line = line[:i]
332                 }
333                 line = bytes.TrimSpace(line)
334                 if !bytes.HasPrefix(line, moduleStr) {
335                         continue
336                 }
337                 line = line[len(moduleStr):]
338                 n := len(line)
339                 line = bytes.TrimSpace(line)
340                 if len(line) == n || len(line) == 0 {
341                         continue
342                 }
343
344                 if line[0] == '"' || line[0] == '`' {
345                         p, err := strconv.Unquote(string(line))
346                         if err != nil {
347                                 return "" // malformed quoted string or multiline module path
348                         }
349                         return p
350                 }
351
352                 return string(line)
353         }
354         return "" // missing module path
355 }