mod.go

  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}