]> Cypherpunks.ru repositories - gostls13.git/blob - src/cmd/go/internal/modload/search.go
cmd: relocate search.MatchPattern to cmd/internal/pkgpattern
[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/imports"
23         "cmd/go/internal/modindex"
24         "cmd/go/internal/par"
25         "cmd/go/internal/search"
26         "cmd/go/internal/trace"
27         "cmd/internal/pkgpattern"
28
29         "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                         const needSum = true
192                         root, isLocal, err = fetch(ctx, mod, needSum)
193                         if err != nil {
194                                 m.AddError(err)
195                                 continue
196                         }
197                         modPrefix = mod.Path
198                 }
199                 if mi, err := modindex.GetModule(root); err == nil {
200                         walkFromIndex(mi, modPrefix, isMatch, treeCanMatch, tags, have, addPkg)
201                         continue
202                 } else if !errors.Is(err, modindex.ErrNotIndexed) {
203                         m.AddError(err)
204                 }
205
206                 prune := pruneVendor
207                 if isLocal {
208                         prune |= pruneGoMod
209                 }
210                 walkPkgs(root, modPrefix, prune)
211         }
212
213         return
214 }
215
216 // walkFromIndex matches packages in a module using the module index. modroot
217 // is the module's root directory on disk, index is the modindex.Module for the
218 // module, and importPathRoot is the module's path prefix.
219 func walkFromIndex(index *modindex.Module, importPathRoot string, isMatch, treeCanMatch func(string) bool, tags, have map[string]bool, addPkg func(string)) {
220         index.Walk(func(reldir string) {
221                 // Avoid .foo, _foo, and testdata subdirectory trees.
222                 p := reldir
223                 for {
224                         elem, rest, found := strings.Cut(p, string(filepath.Separator))
225                         if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
226                                 return
227                         }
228                         if found && elem == "vendor" {
229                                 // Ignore this path if it contains the element "vendor" anywhere
230                                 // except for the last element (packages named vendor are allowed
231                                 // for historical reasons). Note that found is true when this
232                                 // isn't the last path element.
233                                 return
234                         }
235                         if !found {
236                                 // Didn't find the separator, so we're considering the last element.
237                                 break
238                         }
239                         p = rest
240                 }
241
242                 // Don't use GOROOT/src.
243                 if reldir == "" && importPathRoot == "" {
244                         return
245                 }
246
247                 name := path.Join(importPathRoot, filepath.ToSlash(reldir))
248                 if !treeCanMatch(name) {
249                         return
250                 }
251
252                 if !have[name] {
253                         have[name] = true
254                         if isMatch(name) {
255                                 if _, _, err := index.Package(reldir).ScanDir(tags); err != imports.ErrNoGo {
256                                         addPkg(name)
257                                 }
258                         }
259                 }
260         })
261 }
262
263 // MatchInModule identifies the packages matching the given pattern within the
264 // given module version, which does not need to be in the build list or module
265 // requirement graph.
266 //
267 // If m is the zero module.Version, MatchInModule matches the pattern
268 // against the standard library (std and cmd) in GOROOT/src.
269 func MatchInModule(ctx context.Context, pattern string, m module.Version, tags map[string]bool) *search.Match {
270         match := search.NewMatch(pattern)
271         if m == (module.Version{}) {
272                 matchPackages(ctx, match, tags, includeStd, nil)
273         }
274
275         LoadModFile(ctx) // Sets Target, needed by fetch and matchPackages.
276
277         if !match.IsLiteral() {
278                 matchPackages(ctx, match, tags, omitStd, []module.Version{m})
279                 return match
280         }
281
282         const needSum = true
283         root, isLocal, err := fetch(ctx, m, needSum)
284         if err != nil {
285                 match.Errs = []error{err}
286                 return match
287         }
288
289         dir, haveGoFiles, err := dirInModule(pattern, m.Path, root, isLocal)
290         if err != nil {
291                 match.Errs = []error{err}
292                 return match
293         }
294         if haveGoFiles {
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}
301                 }
302         }
303         return match
304 }