github.com/april1989/origin-go-tools@v0.0.32/internal/imports/mod.go (about)

     1  package imports
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"os"
    10  	"path"
    11  	"path/filepath"
    12  	"regexp"
    13  	"sort"
    14  	"strconv"
    15  	"strings"
    16  
    17  	"golang.org/x/mod/module"
    18  	"github.com/april1989/origin-go-tools/internal/gocommand"
    19  	"github.com/april1989/origin-go-tools/internal/gopathwalk"
    20  )
    21  
    22  // ModuleResolver implements resolver for modules using the go command as little
    23  // as feasible.
    24  type ModuleResolver struct {
    25  	env            *ProcessEnv
    26  	moduleCacheDir string
    27  	dummyVendorMod *gocommand.ModuleJSON // If vendoring is enabled, the pseudo-module that represents the /vendor directory.
    28  	roots          []gopathwalk.Root
    29  	scanSema       chan struct{} // scanSema prevents concurrent scans and guards scannedRoots.
    30  	scannedRoots   map[gopathwalk.Root]bool
    31  
    32  	initialized   bool
    33  	main          *gocommand.ModuleJSON
    34  	modsByModPath []*gocommand.ModuleJSON // All modules, ordered by # of path components in module Path...
    35  	modsByDir     []*gocommand.ModuleJSON // ...or Dir.
    36  
    37  	// moduleCacheCache stores information about the module cache.
    38  	moduleCacheCache *dirInfoCache
    39  	otherCache       *dirInfoCache
    40  }
    41  
    42  func newModuleResolver(e *ProcessEnv) *ModuleResolver {
    43  	r := &ModuleResolver{
    44  		env:      e,
    45  		scanSema: make(chan struct{}, 1),
    46  	}
    47  	r.scanSema <- struct{}{}
    48  	return r
    49  }
    50  
    51  func (r *ModuleResolver) init() error {
    52  	if r.initialized {
    53  		return nil
    54  	}
    55  
    56  	goenv, err := r.env.goEnv()
    57  	if err != nil {
    58  		return err
    59  	}
    60  	inv := gocommand.Invocation{
    61  		BuildFlags: r.env.BuildFlags,
    62  		Env:        r.env.env(),
    63  		Logf:       r.env.Logf,
    64  		WorkingDir: r.env.WorkingDir,
    65  	}
    66  	mainMod, vendorEnabled, err := gocommand.VendorEnabled(context.TODO(), inv, r.env.GocmdRunner)
    67  	if err != nil {
    68  		return err
    69  	}
    70  
    71  	if mainMod != nil && vendorEnabled {
    72  		// Vendor mode is on, so all the non-Main modules are irrelevant,
    73  		// and we need to search /vendor for everything.
    74  		r.main = mainMod
    75  		r.dummyVendorMod = &gocommand.ModuleJSON{
    76  			Path: "",
    77  			Dir:  filepath.Join(mainMod.Dir, "vendor"),
    78  		}
    79  		r.modsByModPath = []*gocommand.ModuleJSON{mainMod, r.dummyVendorMod}
    80  		r.modsByDir = []*gocommand.ModuleJSON{mainMod, r.dummyVendorMod}
    81  	} else {
    82  		// Vendor mode is off, so run go list -m ... to find everything.
    83  		r.initAllMods()
    84  	}
    85  
    86  	if gmc := r.env.Env["GOMODCACHE"]; gmc != "" {
    87  		r.moduleCacheDir = gmc
    88  	} else {
    89  		r.moduleCacheDir = filepath.Join(filepath.SplitList(goenv["GOPATH"])[0], "/pkg/mod")
    90  	}
    91  
    92  	sort.Slice(r.modsByModPath, func(i, j int) bool {
    93  		count := func(x int) int {
    94  			return strings.Count(r.modsByModPath[x].Path, "/")
    95  		}
    96  		return count(j) < count(i) // descending order
    97  	})
    98  	sort.Slice(r.modsByDir, func(i, j int) bool {
    99  		count := func(x int) int {
   100  			return strings.Count(r.modsByDir[x].Dir, "/")
   101  		}
   102  		return count(j) < count(i) // descending order
   103  	})
   104  
   105  	r.roots = []gopathwalk.Root{
   106  		{filepath.Join(goenv["GOROOT"], "/src"), gopathwalk.RootGOROOT},
   107  	}
   108  	if r.main != nil {
   109  		r.roots = append(r.roots, gopathwalk.Root{r.main.Dir, gopathwalk.RootCurrentModule})
   110  	}
   111  	if vendorEnabled {
   112  		r.roots = append(r.roots, gopathwalk.Root{r.dummyVendorMod.Dir, gopathwalk.RootOther})
   113  	} else {
   114  		addDep := func(mod *gocommand.ModuleJSON) {
   115  			if mod.Replace == nil {
   116  				// This is redundant with the cache, but we'll skip it cheaply enough.
   117  				r.roots = append(r.roots, gopathwalk.Root{mod.Dir, gopathwalk.RootModuleCache})
   118  			} else {
   119  				r.roots = append(r.roots, gopathwalk.Root{mod.Dir, gopathwalk.RootOther})
   120  			}
   121  		}
   122  		// Walk dependent modules before scanning the full mod cache, direct deps first.
   123  		for _, mod := range r.modsByModPath {
   124  			if !mod.Indirect && !mod.Main {
   125  				addDep(mod)
   126  			}
   127  		}
   128  		for _, mod := range r.modsByModPath {
   129  			if mod.Indirect && !mod.Main {
   130  				addDep(mod)
   131  			}
   132  		}
   133  		r.roots = append(r.roots, gopathwalk.Root{r.moduleCacheDir, gopathwalk.RootModuleCache})
   134  	}
   135  
   136  	r.scannedRoots = map[gopathwalk.Root]bool{}
   137  	if r.moduleCacheCache == nil {
   138  		r.moduleCacheCache = &dirInfoCache{
   139  			dirs:      map[string]*directoryPackageInfo{},
   140  			listeners: map[*int]cacheListener{},
   141  		}
   142  	}
   143  	if r.otherCache == nil {
   144  		r.otherCache = &dirInfoCache{
   145  			dirs:      map[string]*directoryPackageInfo{},
   146  			listeners: map[*int]cacheListener{},
   147  		}
   148  	}
   149  	r.initialized = true
   150  	return nil
   151  }
   152  
   153  func (r *ModuleResolver) initAllMods() error {
   154  	stdout, err := r.env.invokeGo(context.TODO(), "list", "-m", "-json", "...")
   155  	if err != nil {
   156  		return err
   157  	}
   158  	for dec := json.NewDecoder(stdout); dec.More(); {
   159  		mod := &gocommand.ModuleJSON{}
   160  		if err := dec.Decode(mod); err != nil {
   161  			return err
   162  		}
   163  		if mod.Dir == "" {
   164  			if r.env.Logf != nil {
   165  				r.env.Logf("module %v has not been downloaded and will be ignored", mod.Path)
   166  			}
   167  			// Can't do anything with a module that's not downloaded.
   168  			continue
   169  		}
   170  		// golang/go#36193: the go command doesn't always clean paths.
   171  		mod.Dir = filepath.Clean(mod.Dir)
   172  		r.modsByModPath = append(r.modsByModPath, mod)
   173  		r.modsByDir = append(r.modsByDir, mod)
   174  		if mod.Main {
   175  			r.main = mod
   176  		}
   177  	}
   178  	return nil
   179  }
   180  
   181  func (r *ModuleResolver) ClearForNewScan() {
   182  	<-r.scanSema
   183  	r.scannedRoots = map[gopathwalk.Root]bool{}
   184  	r.otherCache = &dirInfoCache{
   185  		dirs:      map[string]*directoryPackageInfo{},
   186  		listeners: map[*int]cacheListener{},
   187  	}
   188  	r.scanSema <- struct{}{}
   189  }
   190  
   191  func (r *ModuleResolver) ClearForNewMod() {
   192  	<-r.scanSema
   193  	*r = ModuleResolver{
   194  		env:              r.env,
   195  		moduleCacheCache: r.moduleCacheCache,
   196  		otherCache:       r.otherCache,
   197  		scanSema:         r.scanSema,
   198  	}
   199  	r.init()
   200  	r.scanSema <- struct{}{}
   201  }
   202  
   203  // findPackage returns the module and directory that contains the package at
   204  // the given import path, or returns nil, "" if no module is in scope.
   205  func (r *ModuleResolver) findPackage(importPath string) (*gocommand.ModuleJSON, string) {
   206  	// This can't find packages in the stdlib, but that's harmless for all
   207  	// the existing code paths.
   208  	for _, m := range r.modsByModPath {
   209  		if !strings.HasPrefix(importPath, m.Path) {
   210  			continue
   211  		}
   212  		pathInModule := importPath[len(m.Path):]
   213  		pkgDir := filepath.Join(m.Dir, pathInModule)
   214  		if r.dirIsNestedModule(pkgDir, m) {
   215  			continue
   216  		}
   217  
   218  		if info, ok := r.cacheLoad(pkgDir); ok {
   219  			if loaded, err := info.reachedStatus(nameLoaded); loaded {
   220  				if err != nil {
   221  					continue // No package in this dir.
   222  				}
   223  				return m, pkgDir
   224  			}
   225  			if scanned, err := info.reachedStatus(directoryScanned); scanned && err != nil {
   226  				continue // Dir is unreadable, etc.
   227  			}
   228  			// This is slightly wrong: a directory doesn't have to have an
   229  			// importable package to count as a package for package-to-module
   230  			// resolution. package main or _test files should count but
   231  			// don't.
   232  			// TODO(heschi): fix this.
   233  			if _, err := r.cachePackageName(info); err == nil {
   234  				return m, pkgDir
   235  			}
   236  		}
   237  
   238  		// Not cached. Read the filesystem.
   239  		pkgFiles, err := ioutil.ReadDir(pkgDir)
   240  		if err != nil {
   241  			continue
   242  		}
   243  		// A module only contains a package if it has buildable go
   244  		// files in that directory. If not, it could be provided by an
   245  		// outer module. See #29736.
   246  		for _, fi := range pkgFiles {
   247  			if ok, _ := r.env.matchFile(pkgDir, fi.Name()); ok {
   248  				return m, pkgDir
   249  			}
   250  		}
   251  	}
   252  	return nil, ""
   253  }
   254  
   255  func (r *ModuleResolver) cacheLoad(dir string) (directoryPackageInfo, bool) {
   256  	if info, ok := r.moduleCacheCache.Load(dir); ok {
   257  		return info, ok
   258  	}
   259  	return r.otherCache.Load(dir)
   260  }
   261  
   262  func (r *ModuleResolver) cacheStore(info directoryPackageInfo) {
   263  	if info.rootType == gopathwalk.RootModuleCache {
   264  		r.moduleCacheCache.Store(info.dir, info)
   265  	} else {
   266  		r.otherCache.Store(info.dir, info)
   267  	}
   268  }
   269  
   270  func (r *ModuleResolver) cacheKeys() []string {
   271  	return append(r.moduleCacheCache.Keys(), r.otherCache.Keys()...)
   272  }
   273  
   274  // cachePackageName caches the package name for a dir already in the cache.
   275  func (r *ModuleResolver) cachePackageName(info directoryPackageInfo) (string, error) {
   276  	if info.rootType == gopathwalk.RootModuleCache {
   277  		return r.moduleCacheCache.CachePackageName(info)
   278  	}
   279  	return r.otherCache.CachePackageName(info)
   280  }
   281  
   282  func (r *ModuleResolver) cacheExports(ctx context.Context, env *ProcessEnv, info directoryPackageInfo) (string, []string, error) {
   283  	if info.rootType == gopathwalk.RootModuleCache {
   284  		return r.moduleCacheCache.CacheExports(ctx, env, info)
   285  	}
   286  	return r.otherCache.CacheExports(ctx, env, info)
   287  }
   288  
   289  // findModuleByDir returns the module that contains dir, or nil if no such
   290  // module is in scope.
   291  func (r *ModuleResolver) findModuleByDir(dir string) *gocommand.ModuleJSON {
   292  	// This is quite tricky and may not be correct. dir could be:
   293  	// - a package in the main module.
   294  	// - a replace target underneath the main module's directory.
   295  	//    - a nested module in the above.
   296  	// - a replace target somewhere totally random.
   297  	//    - a nested module in the above.
   298  	// - in the mod cache.
   299  	// - in /vendor/ in -mod=vendor mode.
   300  	//    - nested module? Dunno.
   301  	// Rumor has it that replace targets cannot contain other replace targets.
   302  	for _, m := range r.modsByDir {
   303  		if !strings.HasPrefix(dir, m.Dir) {
   304  			continue
   305  		}
   306  
   307  		if r.dirIsNestedModule(dir, m) {
   308  			continue
   309  		}
   310  
   311  		return m
   312  	}
   313  	return nil
   314  }
   315  
   316  // dirIsNestedModule reports if dir is contained in a nested module underneath
   317  // mod, not actually in mod.
   318  func (r *ModuleResolver) dirIsNestedModule(dir string, mod *gocommand.ModuleJSON) bool {
   319  	if !strings.HasPrefix(dir, mod.Dir) {
   320  		return false
   321  	}
   322  	if r.dirInModuleCache(dir) {
   323  		// Nested modules in the module cache are pruned,
   324  		// so it cannot be a nested module.
   325  		return false
   326  	}
   327  	if mod != nil && mod == r.dummyVendorMod {
   328  		// The /vendor pseudomodule is flattened and doesn't actually count.
   329  		return false
   330  	}
   331  	modDir, _ := r.modInfo(dir)
   332  	if modDir == "" {
   333  		return false
   334  	}
   335  	return modDir != mod.Dir
   336  }
   337  
   338  func (r *ModuleResolver) modInfo(dir string) (modDir string, modName string) {
   339  	readModName := func(modFile string) string {
   340  		modBytes, err := ioutil.ReadFile(modFile)
   341  		if err != nil {
   342  			return ""
   343  		}
   344  		return modulePath(modBytes)
   345  	}
   346  
   347  	if r.dirInModuleCache(dir) {
   348  		matches := modCacheRegexp.FindStringSubmatch(dir)
   349  		index := strings.Index(dir, matches[1]+"@"+matches[2])
   350  		modDir := filepath.Join(dir[:index], matches[1]+"@"+matches[2])
   351  		return modDir, readModName(filepath.Join(modDir, "go.mod"))
   352  	}
   353  	for {
   354  		if info, ok := r.cacheLoad(dir); ok {
   355  			return info.moduleDir, info.moduleName
   356  		}
   357  		f := filepath.Join(dir, "go.mod")
   358  		info, err := os.Stat(f)
   359  		if err == nil && !info.IsDir() {
   360  			return dir, readModName(f)
   361  		}
   362  
   363  		d := filepath.Dir(dir)
   364  		if len(d) >= len(dir) {
   365  			return "", "" // reached top of file system, no go.mod
   366  		}
   367  		dir = d
   368  	}
   369  }
   370  
   371  func (r *ModuleResolver) dirInModuleCache(dir string) bool {
   372  	if r.moduleCacheDir == "" {
   373  		return false
   374  	}
   375  	return strings.HasPrefix(dir, r.moduleCacheDir)
   376  }
   377  
   378  func (r *ModuleResolver) loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) {
   379  	if err := r.init(); err != nil {
   380  		return nil, err
   381  	}
   382  	names := map[string]string{}
   383  	for _, path := range importPaths {
   384  		_, packageDir := r.findPackage(path)
   385  		if packageDir == "" {
   386  			continue
   387  		}
   388  		name, err := packageDirToName(packageDir)
   389  		if err != nil {
   390  			continue
   391  		}
   392  		names[path] = name
   393  	}
   394  	return names, nil
   395  }
   396  
   397  func (r *ModuleResolver) scan(ctx context.Context, callback *scanCallback) error {
   398  	if err := r.init(); err != nil {
   399  		return err
   400  	}
   401  
   402  	processDir := func(info directoryPackageInfo) {
   403  		// Skip this directory if we were not able to get the package information successfully.
   404  		if scanned, err := info.reachedStatus(directoryScanned); !scanned || err != nil {
   405  			return
   406  		}
   407  		pkg, err := r.canonicalize(info)
   408  		if err != nil {
   409  			return
   410  		}
   411  
   412  		if !callback.dirFound(pkg) {
   413  			return
   414  		}
   415  		pkg.packageName, err = r.cachePackageName(info)
   416  		if err != nil {
   417  			return
   418  		}
   419  
   420  		if !callback.packageNameLoaded(pkg) {
   421  			return
   422  		}
   423  		_, exports, err := r.loadExports(ctx, pkg, false)
   424  		if err != nil {
   425  			return
   426  		}
   427  		callback.exportsLoaded(pkg, exports)
   428  	}
   429  
   430  	// Start processing everything in the cache, and listen for the new stuff
   431  	// we discover in the walk below.
   432  	stop1 := r.moduleCacheCache.ScanAndListen(ctx, processDir)
   433  	defer stop1()
   434  	stop2 := r.otherCache.ScanAndListen(ctx, processDir)
   435  	defer stop2()
   436  
   437  	// We assume cached directories are fully cached, including all their
   438  	// children, and have not changed. We can skip them.
   439  	skip := func(root gopathwalk.Root, dir string) bool {
   440  		info, ok := r.cacheLoad(dir)
   441  		if !ok {
   442  			return false
   443  		}
   444  		// This directory can be skipped as long as we have already scanned it.
   445  		// Packages with errors will continue to have errors, so there is no need
   446  		// to rescan them.
   447  		packageScanned, _ := info.reachedStatus(directoryScanned)
   448  		return packageScanned
   449  	}
   450  
   451  	// Add anything new to the cache, and process it if we're still listening.
   452  	add := func(root gopathwalk.Root, dir string) {
   453  		r.cacheStore(r.scanDirForPackage(root, dir))
   454  	}
   455  
   456  	// r.roots and the callback are not necessarily safe to use in the
   457  	// goroutine below. Process them eagerly.
   458  	roots := filterRoots(r.roots, callback.rootFound)
   459  	// We can't cancel walks, because we need them to finish to have a usable
   460  	// cache. Instead, run them in a separate goroutine and detach.
   461  	scanDone := make(chan struct{})
   462  	go func() {
   463  		select {
   464  		case <-ctx.Done():
   465  			return
   466  		case <-r.scanSema:
   467  		}
   468  		defer func() { r.scanSema <- struct{}{} }()
   469  		// We have the lock on r.scannedRoots, and no other scans can run.
   470  		for _, root := range roots {
   471  			if ctx.Err() != nil {
   472  				return
   473  			}
   474  
   475  			if r.scannedRoots[root] {
   476  				continue
   477  			}
   478  			gopathwalk.WalkSkip([]gopathwalk.Root{root}, add, skip, gopathwalk.Options{Logf: r.env.Logf, ModulesEnabled: true})
   479  			r.scannedRoots[root] = true
   480  		}
   481  		close(scanDone)
   482  	}()
   483  	select {
   484  	case <-ctx.Done():
   485  	case <-scanDone:
   486  	}
   487  	return nil
   488  }
   489  
   490  func (r *ModuleResolver) scoreImportPath(ctx context.Context, path string) int {
   491  	if _, ok := stdlib[path]; ok {
   492  		return MaxRelevance
   493  	}
   494  	mod, _ := r.findPackage(path)
   495  	return modRelevance(mod)
   496  }
   497  
   498  func modRelevance(mod *gocommand.ModuleJSON) int {
   499  	switch {
   500  	case mod == nil: // out of scope
   501  		return MaxRelevance - 4
   502  	case mod.Indirect:
   503  		return MaxRelevance - 3
   504  	case !mod.Main:
   505  		return MaxRelevance - 2
   506  	default:
   507  		return MaxRelevance - 1 // main module ties with stdlib
   508  	}
   509  }
   510  
   511  // canonicalize gets the result of canonicalizing the packages using the results
   512  // of initializing the resolver from 'go list -m'.
   513  func (r *ModuleResolver) canonicalize(info directoryPackageInfo) (*pkg, error) {
   514  	// Packages in GOROOT are already canonical, regardless of the std/cmd modules.
   515  	if info.rootType == gopathwalk.RootGOROOT {
   516  		return &pkg{
   517  			importPathShort: info.nonCanonicalImportPath,
   518  			dir:             info.dir,
   519  			packageName:     path.Base(info.nonCanonicalImportPath),
   520  			relevance:       MaxRelevance,
   521  		}, nil
   522  	}
   523  
   524  	importPath := info.nonCanonicalImportPath
   525  	mod := r.findModuleByDir(info.dir)
   526  	// Check if the directory is underneath a module that's in scope.
   527  	if mod != nil {
   528  		// It is. If dir is the target of a replace directive,
   529  		// our guessed import path is wrong. Use the real one.
   530  		if mod.Dir == info.dir {
   531  			importPath = mod.Path
   532  		} else {
   533  			dirInMod := info.dir[len(mod.Dir)+len("/"):]
   534  			importPath = path.Join(mod.Path, filepath.ToSlash(dirInMod))
   535  		}
   536  	} else if !strings.HasPrefix(importPath, info.moduleName) {
   537  		// The module's name doesn't match the package's import path. It
   538  		// probably needs a replace directive we don't have.
   539  		return nil, fmt.Errorf("package in %q is not valid without a replace statement", info.dir)
   540  	}
   541  
   542  	res := &pkg{
   543  		importPathShort: importPath,
   544  		dir:             info.dir,
   545  		relevance:       modRelevance(mod),
   546  	}
   547  	// We may have discovered a package that has a different version
   548  	// in scope already. Canonicalize to that one if possible.
   549  	if _, canonicalDir := r.findPackage(importPath); canonicalDir != "" {
   550  		res.dir = canonicalDir
   551  	}
   552  	return res, nil
   553  }
   554  
   555  func (r *ModuleResolver) loadExports(ctx context.Context, pkg *pkg, includeTest bool) (string, []string, error) {
   556  	if err := r.init(); err != nil {
   557  		return "", nil, err
   558  	}
   559  	if info, ok := r.cacheLoad(pkg.dir); ok && !includeTest {
   560  		return r.cacheExports(ctx, r.env, info)
   561  	}
   562  	return loadExportsFromFiles(ctx, r.env, pkg.dir, includeTest)
   563  }
   564  
   565  func (r *ModuleResolver) scanDirForPackage(root gopathwalk.Root, dir string) directoryPackageInfo {
   566  	subdir := ""
   567  	if dir != root.Path {
   568  		subdir = dir[len(root.Path)+len("/"):]
   569  	}
   570  	importPath := filepath.ToSlash(subdir)
   571  	if strings.HasPrefix(importPath, "vendor/") {
   572  		// Only enter vendor directories if they're explicitly requested as a root.
   573  		return directoryPackageInfo{
   574  			status: directoryScanned,
   575  			err:    fmt.Errorf("unwanted vendor directory"),
   576  		}
   577  	}
   578  	switch root.Type {
   579  	case gopathwalk.RootCurrentModule:
   580  		importPath = path.Join(r.main.Path, filepath.ToSlash(subdir))
   581  	case gopathwalk.RootModuleCache:
   582  		matches := modCacheRegexp.FindStringSubmatch(subdir)
   583  		if len(matches) == 0 {
   584  			return directoryPackageInfo{
   585  				status: directoryScanned,
   586  				err:    fmt.Errorf("invalid module cache path: %v", subdir),
   587  			}
   588  		}
   589  		modPath, err := module.UnescapePath(filepath.ToSlash(matches[1]))
   590  		if err != nil {
   591  			if r.env.Logf != nil {
   592  				r.env.Logf("decoding module cache path %q: %v", subdir, err)
   593  			}
   594  			return directoryPackageInfo{
   595  				status: directoryScanned,
   596  				err:    fmt.Errorf("decoding module cache path %q: %v", subdir, err),
   597  			}
   598  		}
   599  		importPath = path.Join(modPath, filepath.ToSlash(matches[3]))
   600  	}
   601  
   602  	modDir, modName := r.modInfo(dir)
   603  	result := directoryPackageInfo{
   604  		status:                 directoryScanned,
   605  		dir:                    dir,
   606  		rootType:               root.Type,
   607  		nonCanonicalImportPath: importPath,
   608  		moduleDir:              modDir,
   609  		moduleName:             modName,
   610  	}
   611  	if root.Type == gopathwalk.RootGOROOT {
   612  		// stdlib packages are always in scope, despite the confusing go.mod
   613  		return result
   614  	}
   615  	return result
   616  }
   617  
   618  // modCacheRegexp splits a path in a module cache into module, module version, and package.
   619  var modCacheRegexp = regexp.MustCompile(`(.*)@([^/\\]*)(.*)`)
   620  
   621  var (
   622  	slashSlash = []byte("//")
   623  	moduleStr  = []byte("module")
   624  )
   625  
   626  // modulePath returns the module path from the gomod file text.
   627  // If it cannot find a module path, it returns an empty string.
   628  // It is tolerant of unrelated problems in the go.mod file.
   629  //
   630  // Copied from cmd/go/internal/modfile.
   631  func modulePath(mod []byte) string {
   632  	for len(mod) > 0 {
   633  		line := mod
   634  		mod = nil
   635  		if i := bytes.IndexByte(line, '\n'); i >= 0 {
   636  			line, mod = line[:i], line[i+1:]
   637  		}
   638  		if i := bytes.Index(line, slashSlash); i >= 0 {
   639  			line = line[:i]
   640  		}
   641  		line = bytes.TrimSpace(line)
   642  		if !bytes.HasPrefix(line, moduleStr) {
   643  			continue
   644  		}
   645  		line = line[len(moduleStr):]
   646  		n := len(line)
   647  		line = bytes.TrimSpace(line)
   648  		if len(line) == n || len(line) == 0 {
   649  			continue
   650  		}
   651  
   652  		if line[0] == '"' || line[0] == '`' {
   653  			p, err := strconv.Unquote(string(line))
   654  			if err != nil {
   655  				return "" // malformed quoted string or multiline module path
   656  			}
   657  			return p
   658  		}
   659  
   660  		return string(line)
   661  	}
   662  	return "" // missing module path
   663  }