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.
21 "cmd/go/internal/fsys"
22 "cmd/go/internal/gover"
23 "cmd/go/internal/imports"
24 "cmd/go/internal/modindex"
26 "cmd/go/internal/search"
28 "cmd/go/internal/trace"
29 "cmd/internal/pkgpattern"
31 "golang.org/x/mod/module"
37 omitStd = stdFilter(iota)
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")
50 isMatch := func(string) bool { return true }
51 treeCanMatch := func(string) bool { return true }
53 isMatch = pkgpattern.MatchPattern(m.Pattern())
54 treeCanMatch = pkgpattern.TreeCanMatchPattern(m.Pattern())
58 have := map[string]bool{
59 "builtin": true, // ignore pseudo-package that exists only for documentation
61 addPkg := func(p string) {
63 m.Pkgs = append(m.Pkgs, p)
66 if !cfg.BuildContext.CgoEnabled {
67 have["runtime/cgo"] = true // ignore during walk
72 pruneVendor = pruning(1 << iota)
76 q := par.NewQueue(runtime.GOMAXPROCS(0))
78 walkPkgs := func(root, importPathRoot string, prune pruning) {
79 _, span := trace.StartSpan(ctx, "walkPkgs "+root)
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 {
95 // Don't use GOROOT/src but do walk down into it.
97 if importPathRoot == "" {
101 // Avoid .foo, _foo, and testdata subdirectory trees.
102 _, elem = filepath.Split(pkgDir)
103 if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
108 name := path.Join(importPathRoot, filepath.ToSlash(pkgDir[len(root):]))
109 if !treeCanMatch(name) {
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)
123 return filepath.SkipDir
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
136 if _, _, err := scanDir(root, pkgDir, tags); err != imports.ErrNoGo {
143 if elem == "vendor" && (prune&pruneVendor != 0) {
144 return filepath.SkipDir
153 // Wait for all in-flight operations to complete before returning.
156 sort.Strings(m.Pkgs) // sort everything we added for determinism
159 if filter == includeStd {
160 walkPkgs(cfg.GOROOTsrc, "", pruneGoMod)
161 if treeCanMatch("cmd") {
162 walkPkgs(filepath.Join(cfg.GOROOTsrc, "cmd"), "cmd", pruneGoMod)
166 if cfg.BuildMod == "vendor" {
167 mod := MainModules.mustGetSingleMainModule()
168 if modRoot := MainModules.ModRoot(mod); modRoot != "" {
169 walkPkgs(modRoot, MainModules.PathPrefix(mod), pruneGoMod|pruneVendor)
170 walkPkgs(filepath.Join(modRoot, "vendor"), "", pruneVendor)
175 for _, mod := range modules {
176 if gover.IsToolchain(mod.Path) || !treeCanMatch(mod.Path) {
181 root, modPrefix string
184 if MainModules.Contains(mod.Path) {
185 if MainModules.ModRoot(mod) == "" {
186 continue // If there is no main module, we can't search in it.
188 root = MainModules.ModRoot(mod)
189 modPrefix = MainModules.PathPrefix(mod)
193 root, isLocal, err = fetch(ctx, mod)
200 if mi, err := modindex.GetModule(root); err == nil {
201 walkFromIndex(mi, modPrefix, isMatch, treeCanMatch, tags, have, addPkg)
203 } else if !errors.Is(err, modindex.ErrNotIndexed) {
211 walkPkgs(root, modPrefix, prune)
217 // walkFromIndex matches packages in a module using the module index. modroot
218 // is the module's root directory on disk, index is the modindex.Module for the
219 // module, and importPathRoot is the module's path prefix.
220 func walkFromIndex(index *modindex.Module, importPathRoot string, isMatch, treeCanMatch func(string) bool, tags, have map[string]bool, addPkg func(string)) {
221 index.Walk(func(reldir string) {
222 // Avoid .foo, _foo, and testdata subdirectory trees.
225 elem, rest, found := strings.Cut(p, string(filepath.Separator))
226 if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
229 if found && elem == "vendor" {
230 // Ignore this path if it contains the element "vendor" anywhere
231 // except for the last element (packages named vendor are allowed
232 // for historical reasons). Note that found is true when this
233 // isn't the last path element.
237 // Didn't find the separator, so we're considering the last element.
243 // Don't use GOROOT/src.
244 if reldir == "" && importPathRoot == "" {
248 name := path.Join(importPathRoot, filepath.ToSlash(reldir))
249 if !treeCanMatch(name) {
256 if _, _, err := index.Package(reldir).ScanDir(tags); err != imports.ErrNoGo {
264 // MatchInModule identifies the packages matching the given pattern within the
265 // given module version, which does not need to be in the build list or module
266 // requirement graph.
268 // If m is the zero module.Version, MatchInModule matches the pattern
269 // against the standard library (std and cmd) in GOROOT/src.
270 func MatchInModule(ctx context.Context, pattern string, m module.Version, tags map[string]bool) *search.Match {
271 match := search.NewMatch(pattern)
272 if m == (module.Version{}) {
273 matchPackages(ctx, match, tags, includeStd, nil)
276 LoadModFile(ctx) // Sets Target, needed by fetch and matchPackages.
278 if !match.IsLiteral() {
279 matchPackages(ctx, match, tags, omitStd, []module.Version{m})
283 root, isLocal, err := fetch(ctx, m)
285 match.Errs = []error{err}
289 dir, haveGoFiles, err := dirInModule(pattern, m.Path, root, isLocal)
291 match.Errs = []error{err}
295 if _, _, err := scanDir(root, dir, tags); err != imports.ErrNoGo {
296 // ErrNoGo indicates that the directory is not actually a Go package,
297 // perhaps due to the tags in use. Any other non-nil error indicates a
298 // problem with one or more of the Go source files, but such an error does
299 // not stop the package from existing, so it has no impact on matching.
300 match.Pkgs = []string{pattern}