Source file src/cmd/go/internal/modload/search.go

     1  // Copyright 2018 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 modload
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  	"io/fs"
    12  	"os"
    13  	"path"
    14  	"path/filepath"
    15  	"runtime"
    16  	"sort"
    17  	"strings"
    18  	"sync"
    19  
    20  	"cmd/go/internal/cfg"
    21  	"cmd/go/internal/fsys"
    22  	"cmd/go/internal/gover"
    23  	"cmd/go/internal/imports"
    24  	"cmd/go/internal/modindex"
    25  	"cmd/go/internal/par"
    26  	"cmd/go/internal/search"
    27  	"cmd/go/internal/str"
    28  	"cmd/go/internal/trace"
    29  	"cmd/internal/pkgpattern"
    30  
    31  	"golang.org/x/mod/module"
    32  )
    33  
    34  type stdFilter int8
    35  
    36  const (
    37  	omitStd = stdFilter(iota)
    38  	includeStd
    39  )
    40  
    41  // matchPackages is like m.MatchPackages, but uses a local variable (rather than
    42  // a global) for tags, can include or exclude packages in the standard library,
    43  // and is restricted to the given list of modules.
    44  func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, filter stdFilter, modules []module.Version) {
    45  	ctx, span := trace.StartSpan(ctx, "modload.matchPackages")
    46  	defer span.Done()
    47  
    48  	m.Pkgs = []string{}
    49  
    50  	isMatch := func(string) bool { return true }
    51  	treeCanMatch := func(string) bool { return true }
    52  	if !m.IsMeta() {
    53  		isMatch = pkgpattern.MatchPattern(m.Pattern())
    54  		treeCanMatch = pkgpattern.TreeCanMatchPattern(m.Pattern())
    55  	}
    56  
    57  	var mu sync.Mutex
    58  	have := map[string]bool{
    59  		"builtin": true, // ignore pseudo-package that exists only for documentation
    60  	}
    61  	addPkg := func(p string) {
    62  		mu.Lock()
    63  		m.Pkgs = append(m.Pkgs, p)
    64  		mu.Unlock()
    65  	}
    66  	if !cfg.BuildContext.CgoEnabled {
    67  		have["runtime/cgo"] = true // ignore during walk
    68  	}
    69  
    70  	type pruning int8
    71  	const (
    72  		pruneVendor = pruning(1 << iota)
    73  		pruneGoMod
    74  	)
    75  
    76  	q := par.NewQueue(runtime.GOMAXPROCS(0))
    77  
    78  	walkPkgs := func(root, importPathRoot string, prune pruning) {
    79  		_, span := trace.StartSpan(ctx, "walkPkgs "+root)
    80  		defer span.Done()
    81  
    82  		// If the root itself is a symlink to a directory,
    83  		// we want to follow it (see https://go.dev/issue/50807).
    84  		// Add a trailing separator to force that to happen.
    85  		root = str.WithFilePathSeparator(filepath.Clean(root))
    86  		err := fsys.Walk(root, func(pkgDir string, fi fs.FileInfo, err error) error {
    87  			if err != nil {
    88  				m.AddError(err)
    89  				return nil
    90  			}
    91  
    92  			want := true
    93  			elem := ""
    94  
    95  			// Don't use GOROOT/src but do walk down into it.
    96  			if pkgDir == root {
    97  				if importPathRoot == "" {
    98  					return nil
    99  				}
   100  			} else {
   101  				// Avoid .foo, _foo, and testdata subdirectory trees.
   102  				_, elem = filepath.Split(pkgDir)
   103  				if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
   104  					want = false
   105  				}
   106  			}
   107  
   108  			name := path.Join(importPathRoot, filepath.ToSlash(pkgDir[len(root):]))
   109  			if !treeCanMatch(name) {
   110  				want = false
   111  			}
   112  
   113  			if !fi.IsDir() {
   114  				if fi.Mode()&fs.ModeSymlink != 0 && want && strings.Contains(m.Pattern(), "...") {
   115  					if target, err := fsys.Stat(pkgDir); err == nil && target.IsDir() {
   116  						fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", pkgDir)
   117  					}
   118  				}
   119  				return nil
   120  			}
   121  
   122  			if !want {
   123  				return filepath.SkipDir
   124  			}
   125  			// Stop at module boundaries.
   126  			if (prune&pruneGoMod != 0) && pkgDir != root {
   127  				if fi, err := os.Stat(filepath.Join(pkgDir, "go.mod")); err == nil && !fi.IsDir() {
   128  					return filepath.SkipDir
   129  				}
   130  			}
   131  
   132  			if !have[name] {
   133  				have[name] = true
   134  				if isMatch(name) {
   135  					q.Add(func() {
   136  						if _, _, err := scanDir(root, pkgDir, tags); err != imports.ErrNoGo {
   137  							addPkg(name)
   138  						}
   139  					})
   140  				}
   141  			}
   142  
   143  			if elem == "vendor" && (prune&pruneVendor != 0) {
   144  				return filepath.SkipDir
   145  			}
   146  			return nil
   147  		})
   148  		if err != nil {
   149  			m.AddError(err)
   150  		}
   151  	}
   152  
   153  	// Wait for all in-flight operations to complete before returning.
   154  	defer func() {
   155  		<-q.Idle()
   156  		sort.Strings(m.Pkgs) // sort everything we added for determinism
   157  	}()
   158  
   159  	if filter == includeStd {
   160  		walkPkgs(cfg.GOROOTsrc, "", pruneGoMod)
   161  		if treeCanMatch("cmd") {
   162  			walkPkgs(filepath.Join(cfg.GOROOTsrc, "cmd"), "cmd", pruneGoMod)
   163  		}
   164  	}
   165  
   166  	if cfg.BuildMod == "vendor" {
   167  		for _, mod := range MainModules.Versions() {
   168  			if modRoot := MainModules.ModRoot(mod); modRoot != "" {
   169  				walkPkgs(modRoot, MainModules.PathPrefix(mod), pruneGoMod|pruneVendor)
   170  			}
   171  		}
   172  		if HasModRoot() {
   173  			walkPkgs(VendorDir(), "", pruneVendor)
   174  		}
   175  		return
   176  	}
   177  
   178  	for _, mod := range modules {
   179  		if gover.IsToolchain(mod.Path) || !treeCanMatch(mod.Path) {
   180  			continue
   181  		}
   182  
   183  		var (
   184  			root, modPrefix string
   185  			isLocal         bool
   186  		)
   187  		if MainModules.Contains(mod.Path) {
   188  			if MainModules.ModRoot(mod) == "" {
   189  				continue // If there is no main module, we can't search in it.
   190  			}
   191  			root = MainModules.ModRoot(mod)
   192  			modPrefix = MainModules.PathPrefix(mod)
   193  			isLocal = true
   194  		} else {
   195  			var err error
   196  			root, isLocal, err = fetch(ctx, mod)
   197  			if err != nil {
   198  				m.AddError(err)
   199  				continue
   200  			}
   201  			modPrefix = mod.Path
   202  		}
   203  		if mi, err := modindex.GetModule(root); err == nil {
   204  			walkFromIndex(mi, modPrefix, isMatch, treeCanMatch, tags, have, addPkg)
   205  			continue
   206  		} else if !errors.Is(err, modindex.ErrNotIndexed) {
   207  			m.AddError(err)
   208  		}
   209  
   210  		prune := pruneVendor
   211  		if isLocal {
   212  			prune |= pruneGoMod
   213  		}
   214  		walkPkgs(root, modPrefix, prune)
   215  	}
   216  }
   217  
   218  // walkFromIndex matches packages in a module using the module index. modroot
   219  // is the module's root directory on disk, index is the modindex.Module for the
   220  // module, and importPathRoot is the module's path prefix.
   221  func walkFromIndex(index *modindex.Module, importPathRoot string, isMatch, treeCanMatch func(string) bool, tags, have map[string]bool, addPkg func(string)) {
   222  	index.Walk(func(reldir string) {
   223  		// Avoid .foo, _foo, and testdata subdirectory trees.
   224  		p := reldir
   225  		for {
   226  			elem, rest, found := strings.Cut(p, string(filepath.Separator))
   227  			if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
   228  				return
   229  			}
   230  			if found && elem == "vendor" {
   231  				// Ignore this path if it contains the element "vendor" anywhere
   232  				// except for the last element (packages named vendor are allowed
   233  				// for historical reasons). Note that found is true when this
   234  				// isn't the last path element.
   235  				return
   236  			}
   237  			if !found {
   238  				// Didn't find the separator, so we're considering the last element.
   239  				break
   240  			}
   241  			p = rest
   242  		}
   243  
   244  		// Don't use GOROOT/src.
   245  		if reldir == "" && importPathRoot == "" {
   246  			return
   247  		}
   248  
   249  		name := path.Join(importPathRoot, filepath.ToSlash(reldir))
   250  		if !treeCanMatch(name) {
   251  			return
   252  		}
   253  
   254  		if !have[name] {
   255  			have[name] = true
   256  			if isMatch(name) {
   257  				if _, _, err := index.Package(reldir).ScanDir(tags); err != imports.ErrNoGo {
   258  					addPkg(name)
   259  				}
   260  			}
   261  		}
   262  	})
   263  }
   264  
   265  // MatchInModule identifies the packages matching the given pattern within the
   266  // given module version, which does not need to be in the build list or module
   267  // requirement graph.
   268  //
   269  // If m is the zero module.Version, MatchInModule matches the pattern
   270  // against the standard library (std and cmd) in GOROOT/src.
   271  func MatchInModule(ctx context.Context, pattern string, m module.Version, tags map[string]bool) *search.Match {
   272  	match := search.NewMatch(pattern)
   273  	if m == (module.Version{}) {
   274  		matchPackages(ctx, match, tags, includeStd, nil)
   275  	}
   276  
   277  	LoadModFile(ctx) // Sets Target, needed by fetch and matchPackages.
   278  
   279  	if !match.IsLiteral() {
   280  		matchPackages(ctx, match, tags, omitStd, []module.Version{m})
   281  		return match
   282  	}
   283  
   284  	root, isLocal, err := fetch(ctx, m)
   285  	if err != nil {
   286  		match.Errs = []error{err}
   287  		return match
   288  	}
   289  
   290  	dir, haveGoFiles, err := dirInModule(pattern, m.Path, root, isLocal)
   291  	if err != nil {
   292  		match.Errs = []error{err}
   293  		return match
   294  	}
   295  	if haveGoFiles {
   296  		if _, _, err := scanDir(root, dir, tags); err != imports.ErrNoGo {
   297  			// ErrNoGo indicates that the directory is not actually a Go package,
   298  			// perhaps due to the tags in use. Any other non-nil error indicates a
   299  			// problem with one or more of the Go source files, but such an error does
   300  			// not stop the package from existing, so it has no impact on matching.
   301  			match.Pkgs = []string{pattern}
   302  		}
   303  	}
   304  	return match
   305  }
   306  

View as plain text