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