Black Lives Matter. Support the Equal Justice Initiative.

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

Documentation: cmd/go/internal/modload

     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 {
    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  		if HasModRoot() {
   135  			walkPkgs(ModRoot(), targetPrefix, pruneGoMod|pruneVendor)
   136  			walkPkgs(filepath.Join(ModRoot(), "vendor"), "", pruneVendor)
   137  		}
   138  		return
   139  	}
   140  
   141  	for _, mod := range modules {
   142  		if !treeCanMatch(mod.Path) {
   143  			continue
   144  		}
   145  
   146  		var (
   147  			root, modPrefix string
   148  			isLocal         bool
   149  		)
   150  		if mod == Target {
   151  			if !HasModRoot() {
   152  				continue // If there is no main module, we can't search in it.
   153  			}
   154  			root = ModRoot()
   155  			modPrefix = targetPrefix
   156  			isLocal = true
   157  		} else {
   158  			var err error
   159  			const needSum = true
   160  			root, isLocal, err = fetch(ctx, mod, needSum)
   161  			if err != nil {
   162  				m.AddError(err)
   163  				continue
   164  			}
   165  			modPrefix = mod.Path
   166  		}
   167  
   168  		prune := pruneVendor
   169  		if isLocal {
   170  			prune |= pruneGoMod
   171  		}
   172  		walkPkgs(root, modPrefix, prune)
   173  	}
   174  
   175  	return
   176  }
   177  
   178  // MatchInModule identifies the packages matching the given pattern within the
   179  // given module version, which does not need to be in the build list or module
   180  // requirement graph.
   181  //
   182  // If m is the zero module.Version, MatchInModule matches the pattern
   183  // against the standard library (std and cmd) in GOROOT/src.
   184  func MatchInModule(ctx context.Context, pattern string, m module.Version, tags map[string]bool) *search.Match {
   185  	match := search.NewMatch(pattern)
   186  	if m == (module.Version{}) {
   187  		matchPackages(ctx, match, tags, includeStd, nil)
   188  	}
   189  
   190  	LoadModFile(ctx)
   191  
   192  	if !match.IsLiteral() {
   193  		matchPackages(ctx, match, tags, omitStd, []module.Version{m})
   194  		return match
   195  	}
   196  
   197  	const needSum = true
   198  	root, isLocal, err := fetch(ctx, m, needSum)
   199  	if err != nil {
   200  		match.Errs = []error{err}
   201  		return match
   202  	}
   203  
   204  	dir, haveGoFiles, err := dirInModule(pattern, m.Path, root, isLocal)
   205  	if err != nil {
   206  		match.Errs = []error{err}
   207  		return match
   208  	}
   209  	if haveGoFiles {
   210  		if _, _, err := scanDir(dir, tags); err != imports.ErrNoGo {
   211  			// ErrNoGo indicates that the directory is not actually a Go package,
   212  			// perhaps due to the tags in use. Any other non-nil error indicates a
   213  			// problem with one or more of the Go source files, but such an error does
   214  			// not stop the package from existing, so it has no impact on matching.
   215  			match.Pkgs = []string{pattern}
   216  		}
   217  	}
   218  	return match
   219  }
   220  

View as plain text