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