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.
18 "cmd/go/internal/fsys"
19 "cmd/go/internal/imports"
20 "cmd/go/internal/modindex"
21 "cmd/go/internal/search"
23 "golang.org/x/mod/module"
29 omitStd = stdFilter(iota)
33 // matchPackages is like m.MatchPackages, but uses a local variable (rather than
34 // a global) for tags, can include or exclude packages in the standard library,
35 // and is restricted to the given list of modules.
36 func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, filter stdFilter, modules []module.Version) {
39 isMatch := func(string) bool { return true }
40 treeCanMatch := func(string) bool { return true }
42 isMatch = search.MatchPattern(m.Pattern())
43 treeCanMatch = search.TreeCanMatchPattern(m.Pattern())
46 have := map[string]bool{
47 "builtin": true, // ignore pseudo-package that exists only for documentation
49 if !cfg.BuildContext.CgoEnabled {
50 have["runtime/cgo"] = true // ignore during walk
55 pruneVendor = pruning(1 << iota)
59 walkPkgs := func(root, importPathRoot string, prune pruning) {
60 root = filepath.Clean(root)
61 err := fsys.Walk(root, func(path string, fi fs.FileInfo, err error) error {
70 // Don't use GOROOT/src but do walk down into it.
72 if importPathRoot == "" {
76 // Avoid .foo, _foo, and testdata subdirectory trees.
77 _, elem = filepath.Split(path)
78 if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
83 name := importPathRoot + filepath.ToSlash(path[len(root):])
84 if importPathRoot == "" {
85 name = name[1:] // cut leading slash
87 if !treeCanMatch(name) {
92 if fi.Mode()&fs.ModeSymlink != 0 && want && strings.Contains(m.Pattern(), "...") {
93 if target, err := fsys.Stat(path); err == nil && target.IsDir() {
94 fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path)
101 return filepath.SkipDir
103 // Stop at module boundaries.
104 if (prune&pruneGoMod != 0) && path != root {
105 if fi, err := os.Stat(filepath.Join(path, "go.mod")); err == nil && !fi.IsDir() {
106 return filepath.SkipDir
113 if _, _, err := scanDir(root, path, tags); err != imports.ErrNoGo {
114 m.Pkgs = append(m.Pkgs, name)
119 if elem == "vendor" && (prune&pruneVendor != 0) {
120 return filepath.SkipDir
129 if filter == includeStd {
130 walkPkgs(cfg.GOROOTsrc, "", pruneGoMod)
131 if treeCanMatch("cmd") {
132 walkPkgs(filepath.Join(cfg.GOROOTsrc, "cmd"), "cmd", pruneGoMod)
136 if cfg.BuildMod == "vendor" {
137 mod := MainModules.mustGetSingleMainModule()
138 if modRoot := MainModules.ModRoot(mod); modRoot != "" {
139 walkPkgs(modRoot, MainModules.PathPrefix(mod), pruneGoMod|pruneVendor)
140 walkPkgs(filepath.Join(modRoot, "vendor"), "", pruneVendor)
145 for _, mod := range modules {
146 if !treeCanMatch(mod.Path) {
151 root, modPrefix string
154 if MainModules.Contains(mod.Path) {
155 if MainModules.ModRoot(mod) == "" {
156 continue // If there is no main module, we can't search in it.
158 root = MainModules.ModRoot(mod)
159 modPrefix = MainModules.PathPrefix(mod)
164 root, isLocal, err = fetch(ctx, mod, needSum)
171 if mi, err := modindex.Get(root); err == nil {
172 walkFromIndex(ctx, m, tags, root, mi, have, modPrefix)
174 } else if !errors.Is(err, modindex.ErrNotIndexed) {
182 walkPkgs(root, modPrefix, prune)
188 // walkFromIndex matches packages in a module using the module index. modroot
189 // is the module's root directory on disk, index is the ModuleIndex for the
190 // module, and importPathRoot is the module's path prefix.
191 func walkFromIndex(ctx context.Context, m *search.Match, tags map[string]bool, modroot string, index *modindex.ModuleIndex, have map[string]bool, importPathRoot string) {
192 isMatch := func(string) bool { return true }
193 treeCanMatch := func(string) bool { return true }
195 isMatch = search.MatchPattern(m.Pattern())
196 treeCanMatch = search.TreeCanMatchPattern(m.Pattern())
199 for _, reldir := range index.Packages() {
200 // Avoid .foo, _foo, and testdata subdirectory trees.
203 elem, rest, found := strings.Cut(p, string(filepath.Separator))
204 if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
205 continue loopPackages
207 if found && elem == "vendor" {
208 // Ignore this path if it contains the element "vendor" anywhere
209 // except for the last element (packages named vendor are allowed
210 // for historical reasons). Note that found is true when this
211 // isn't the last path element.
212 continue loopPackages
215 // Didn't find the separator, so we're considering the last element.
221 // Don't use GOROOT/src.
222 if reldir == "" && importPathRoot == "" {
226 name := path.Join(importPathRoot, filepath.ToSlash(reldir))
227 if !treeCanMatch(name) {
234 if _, _, err := index.ScanDir(reldir, tags); err != imports.ErrNoGo {
235 m.Pkgs = append(m.Pkgs, name)
242 // MatchInModule identifies the packages matching the given pattern within the
243 // given module version, which does not need to be in the build list or module
244 // requirement graph.
246 // If m is the zero module.Version, MatchInModule matches the pattern
247 // against the standard library (std and cmd) in GOROOT/src.
248 func MatchInModule(ctx context.Context, pattern string, m module.Version, tags map[string]bool) *search.Match {
249 match := search.NewMatch(pattern)
250 if m == (module.Version{}) {
251 matchPackages(ctx, match, tags, includeStd, nil)
254 LoadModFile(ctx) // Sets Target, needed by fetch and matchPackages.
256 if !match.IsLiteral() {
257 matchPackages(ctx, match, tags, omitStd, []module.Version{m})
262 root, isLocal, err := fetch(ctx, m, needSum)
264 match.Errs = []error{err}
268 dir, haveGoFiles, err := dirInModule(pattern, m.Path, root, isLocal)
270 match.Errs = []error{err}
274 if _, _, err := scanDir(root, dir, tags); err != imports.ErrNoGo {
275 // ErrNoGo indicates that the directory is not actually a Go package,
276 // perhaps due to the tags in use. Any other non-nil error indicates a
277 // problem with one or more of the Go source files, but such an error does
278 // not stop the package from existing, so it has no impact on matching.
279 match.Pkgs = []string{pattern}