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  	"fmt"
    10  	"io/fs"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  
    15  	"cmd/go/internal/cfg"
    16  	"cmd/go/internal/fsys"
    17  	"cmd/go/internal/imports"
    18  	"cmd/go/internal/search"
    19  
    20  	"golang.org/x/mod/module"
    21  )
    22  
    23  type stdFilter int8
    24  
    25  const (
    26  	omitStd = stdFilter(iota)
    27  	includeStd
    28  )
    29  
    30  // matchPackages is like m.MatchPackages, but uses a local variable (rather than
    31  // a global) for tags, can include or exclude packages in the standard library,
    32  // and is restricted to the given list of modules.
    33  func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, filter stdFilter, modules []module.Version) {
    34  	m.Pkgs = []string{}
    35  
    36  	isMatch := func(string) bool { return true }
    37  	treeCanMatch := func(string) bool { return true }
    38  	if !m.IsMeta() {
    39  		isMatch = search.MatchPattern(m.Pattern())
    40  		treeCanMatch = search.TreeCanMatchPattern(m.Pattern())
    41  	}
    42  
    43  	have := map[string]bool{
    44  		"builtin": true, // ignore pseudo-package that exists only for documentation
    45  	}
    46  	if !cfg.BuildContext.CgoEnabled {
    47  		have["runtime/cgo"] = true // ignore during walk
    48  	}
    49  
    50  	type pruning int8
    51  	const (
    52  		pruneVendor = pruning(1 << iota)
    53  		pruneGoMod
    54  	)
    55  
    56  	walkPkgs := func(root, importPathRoot string, prune pruning) {
    57  		root = filepath.Clean(root)
    58  		err := fsys.Walk(root, func(path string, fi fs.FileInfo, err error) error {
    59  			if err != nil {
    60  				m.AddError(err)
    61  				return nil
    62  			}
    63  
    64  			want := true
    65  			elem := ""
    66  
    67  			// Don't use GOROOT/src but do walk down into it.
    68  			if path == root {
    69  				if importPathRoot == "" {
    70  					return nil
    71  				}
    72  			} else {
    73  				// Avoid .foo, _foo, and testdata subdirectory trees.
    74  				_, elem = filepath.Split(path)
    75  				if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
    76  					want = false
    77  				}
    78  			}
    79  
    80  			name := importPathRoot + filepath.ToSlash(path[len(root):])
    81  			if importPathRoot == "" {
    82  				name = name[1:] // cut leading slash
    83  			}
    84  			if !treeCanMatch(name) {
    85  				want = false
    86  			}
    87  
    88  			if !fi.IsDir() {
    89  				if fi.Mode()&fs.ModeSymlink != 0 && want && strings.Contains(m.Pattern(), "...") {
    90  					if target, err := fsys.Stat(path); err == nil && target.IsDir() {
    91  						fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path)
    92  					}
    93  				}
    94  				return nil
    95  			}
    96  
    97  			if !want {
    98  				return filepath.SkipDir
    99  			}
   100  			// Stop at module boundaries.
   101  			if (prune&pruneGoMod != 0) && path != root {
   102  				if fi, err := os.Stat(filepath.Join(path, "go.mod")); err == nil && !fi.IsDir() {
   103  					return filepath.SkipDir
   104  				}
   105  			}
   106  
   107  			if !have[name] {
   108  				have[name] = true
   109  				if isMatch(name) {
   110  					if _, _, err := scanDir(path, tags); err != imports.ErrNoGo {
   111  						m.Pkgs = append(m.Pkgs, name)
   112  					}
   113  				}
   114  			}
   115  
   116  			if elem == "vendor" && (prune&pruneVendor != 0) {
   117  				return filepath.SkipDir
   118  			}
   119  			return nil
   120  		})
   121  		if err != nil {
   122  			m.AddError(err)
   123  		}
   124  	}
   125  
   126  	if filter == includeStd {
   127  		walkPkgs(cfg.GOROOTsrc, "", pruneGoMod)
   128  		if treeCanMatch("cmd") {
   129  			walkPkgs(filepath.Join(cfg.GOROOTsrc, "cmd"), "cmd", pruneGoMod)
   130  		}
   131  	}
   132  
   133  	if cfg.BuildMod == "vendor" {
   134  		mod := MainModules.mustGetSingleMainModule()
   135  		if modRoot := MainModules.ModRoot(mod); modRoot != "" {
   136  			walkPkgs(modRoot, MainModules.PathPrefix(mod), pruneGoMod|pruneVendor)
   137  			walkPkgs(filepath.Join(modRoot, "vendor"), "", pruneVendor)
   138  		}
   139  		return
   140  	}
   141  
   142  	for _, mod := range modules {
   143  		if !treeCanMatch(mod.Path) {
   144  			continue
   145  		}
   146  
   147  		var (
   148  			root, modPrefix string
   149  			isLocal         bool
   150  		)
   151  		if MainModules.Contains(mod.Path) {
   152  			if MainModules.ModRoot(mod) == "" {
   153  				continue // If there is no main module, we can't search in it.
   154  			}
   155  			root = MainModules.ModRoot(mod)
   156  			modPrefix = MainModules.PathPrefix(mod)
   157  			isLocal = true
   158  		} else {
   159  			var err error
   160  			const needSum = true
   161  			root, isLocal, err = fetch(ctx, mod, needSum)
   162  			if err != nil {
   163  				m.AddError(err)
   164  				continue
   165  			}
   166  			modPrefix = mod.Path
   167  		}
   168  
   169  		prune := pruneVendor
   170  		if isLocal {
   171  			prune |= pruneGoMod
   172  		}
   173  		walkPkgs(root, modPrefix, prune)
   174  	}
   175  
   176  	return
   177  }
   178  
   179  // MatchInModule identifies the packages matching the given pattern within the
   180  // given module version, which does not need to be in the build list or module
   181  // requirement graph.
   182  //
   183  // If m is the zero module.Version, MatchInModule matches the pattern
   184  // against the standard library (std and cmd) in GOROOT/src.
   185  func MatchInModule(ctx context.Context, pattern string, m module.Version, tags map[string]bool) *search.Match {
   186  	match := search.NewMatch(pattern)
   187  	if m == (module.Version{}) {
   188  		matchPackages(ctx, match, tags, includeStd, nil)
   189  	}
   190  
   191  	LoadModFile(ctx) // Sets Target, needed by fetch and matchPackages.
   192  
   193  	if !match.IsLiteral() {
   194  		matchPackages(ctx, match, tags, omitStd, []module.Version{m})
   195  		return match
   196  	}
   197  
   198  	const needSum = true
   199  	root, isLocal, err := fetch(ctx, m, needSum)
   200  	if err != nil {
   201  		match.Errs = []error{err}
   202  		return match
   203  	}
   204  
   205  	dir, haveGoFiles, err := dirInModule(pattern, m.Path, root, isLocal)
   206  	if err != nil {
   207  		match.Errs = []error{err}
   208  		return match
   209  	}
   210  	if haveGoFiles {
   211  		if _, _, err := scanDir(dir, tags); err != imports.ErrNoGo {
   212  			// ErrNoGo indicates that the directory is not actually a Go package,
   213  			// perhaps due to the tags in use. Any other non-nil error indicates a
   214  			// problem with one or more of the Go source files, but such an error does
   215  			// not stop the package from existing, so it has no impact on matching.
   216  			match.Pkgs = []string{pattern}
   217  		}
   218  	}
   219  	return match
   220  }
   221  

View as plain text