1package imports
2
3import (
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.
24type 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
33type 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
47type moduleErrorJSON struct {
48 Err string // the error itself
49}
50
51func (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.
97func (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.
127func (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.
154func 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
165func 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
180func (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
199func (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.
311var modCacheRegexp = regexp.MustCompile(`(.*)@([^/\\]*)(.*)`)
312
313var (
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.
323func 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}