github.com/bir3/gocompiler@v0.3.205/src/cmd/gocmd/internal/modload/search.go (about)

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