]> Cypherpunks.ru repositories - gostls13.git/blob - src/cmd/go/internal/modload/search.go
cmd/go: traverse module-root symlinks in Walk calls
[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/str"
27         "cmd/go/internal/trace"
28         "cmd/internal/pkgpattern"
29
30         "golang.org/x/mod/module"
31 )
32
33 type stdFilter int8
34
35 const (
36         omitStd = stdFilter(iota)
37         includeStd
38 )
39
40 // matchPackages is like m.MatchPackages, but uses a local variable (rather than
41 // a global) for tags, can include or exclude packages in the standard library,
42 // and is restricted to the given list of modules.
43 func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, filter stdFilter, modules []module.Version) {
44         ctx, span := trace.StartSpan(ctx, "modload.matchPackages")
45         defer span.Done()
46
47         m.Pkgs = []string{}
48
49         isMatch := func(string) bool { return true }
50         treeCanMatch := func(string) bool { return true }
51         if !m.IsMeta() {
52                 isMatch = pkgpattern.MatchPattern(m.Pattern())
53                 treeCanMatch = pkgpattern.TreeCanMatchPattern(m.Pattern())
54         }
55
56         var mu sync.Mutex
57         have := map[string]bool{
58                 "builtin": true, // ignore pseudo-package that exists only for documentation
59         }
60         addPkg := func(p string) {
61                 mu.Lock()
62                 m.Pkgs = append(m.Pkgs, p)
63                 mu.Unlock()
64         }
65         if !cfg.BuildContext.CgoEnabled {
66                 have["runtime/cgo"] = true // ignore during walk
67         }
68
69         type pruning int8
70         const (
71                 pruneVendor = pruning(1 << iota)
72                 pruneGoMod
73         )
74
75         q := par.NewQueue(runtime.GOMAXPROCS(0))
76
77         walkPkgs := func(root, importPathRoot string, prune pruning) {
78                 _, span := trace.StartSpan(ctx, "walkPkgs "+root)
79                 defer span.Done()
80
81                 // If the root itself is a symlink to a directory,
82                 // we want to follow it (see https://go.dev/issue/50807).
83                 // Add a trailing separator to force that to happen.
84                 root = str.WithFilePathSeparator(filepath.Clean(root))
85                 err := fsys.Walk(root, func(pkgDir string, fi fs.FileInfo, err error) error {
86                         if err != nil {
87                                 m.AddError(err)
88                                 return nil
89                         }
90
91                         want := true
92                         elem := ""
93
94                         // Don't use GOROOT/src but do walk down into it.
95                         if pkgDir == root {
96                                 if importPathRoot == "" {
97                                         return nil
98                                 }
99                         } else {
100                                 // Avoid .foo, _foo, and testdata subdirectory trees.
101                                 _, elem = filepath.Split(pkgDir)
102                                 if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
103                                         want = false
104                                 }
105                         }
106
107                         name := path.Join(importPathRoot, filepath.ToSlash(pkgDir[len(root):]))
108                         if !treeCanMatch(name) {
109                                 want = false
110                         }
111
112                         if !fi.IsDir() {
113                                 if fi.Mode()&fs.ModeSymlink != 0 && want && strings.Contains(m.Pattern(), "...") {
114                                         if target, err := fsys.Stat(pkgDir); err == nil && target.IsDir() {
115                                                 fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", pkgDir)
116                                         }
117                                 }
118                                 return nil
119                         }
120
121                         if !want {
122                                 return filepath.SkipDir
123                         }
124                         // Stop at module boundaries.
125                         if (prune&pruneGoMod != 0) && pkgDir != root {
126                                 if fi, err := os.Stat(filepath.Join(pkgDir, "go.mod")); err == nil && !fi.IsDir() {
127                                         return filepath.SkipDir
128                                 }
129                         }
130
131                         if !have[name] {
132                                 have[name] = true
133                                 if isMatch(name) {
134                                         q.Add(func() {
135                                                 if _, _, err := scanDir(root, pkgDir, tags); err != imports.ErrNoGo {
136                                                         addPkg(name)
137                                                 }
138                                         })
139                                 }
140                         }
141
142                         if elem == "vendor" && (prune&pruneVendor != 0) {
143                                 return filepath.SkipDir
144                         }
145                         return nil
146                 })
147                 if err != nil {
148                         m.AddError(err)
149                 }
150         }
151
152         // Wait for all in-flight operations to complete before returning.
153         defer func() {
154                 <-q.Idle()
155                 sort.Strings(m.Pkgs) // sort everything we added for determinism
156         }()
157
158         if filter == includeStd {
159                 walkPkgs(cfg.GOROOTsrc, "", pruneGoMod)
160                 if treeCanMatch("cmd") {
161                         walkPkgs(filepath.Join(cfg.GOROOTsrc, "cmd"), "cmd", pruneGoMod)
162                 }
163         }
164
165         if cfg.BuildMod == "vendor" {
166                 mod := MainModules.mustGetSingleMainModule()
167                 if modRoot := MainModules.ModRoot(mod); modRoot != "" {
168                         walkPkgs(modRoot, MainModules.PathPrefix(mod), pruneGoMod|pruneVendor)
169                         walkPkgs(filepath.Join(modRoot, "vendor"), "", pruneVendor)
170                 }
171                 return
172         }
173
174         for _, mod := range modules {
175                 if !treeCanMatch(mod.Path) {
176                         continue
177                 }
178
179                 var (
180                         root, modPrefix string
181                         isLocal         bool
182                 )
183                 if MainModules.Contains(mod.Path) {
184                         if MainModules.ModRoot(mod) == "" {
185                                 continue // If there is no main module, we can't search in it.
186                         }
187                         root = MainModules.ModRoot(mod)
188                         modPrefix = MainModules.PathPrefix(mod)
189                         isLocal = true
190                 } else {
191                         var err error
192                         root, isLocal, err = fetch(ctx, mod)
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         root, isLocal, err := fetch(ctx, m)
283         if err != nil {
284                 match.Errs = []error{err}
285                 return match
286         }
287
288         dir, haveGoFiles, err := dirInModule(pattern, m.Path, root, isLocal)
289         if err != nil {
290                 match.Errs = []error{err}
291                 return match
292         }
293         if haveGoFiles {
294                 if _, _, err := scanDir(root, dir, tags); err != imports.ErrNoGo {
295                         // ErrNoGo indicates that the directory is not actually a Go package,
296                         // perhaps due to the tags in use. Any other non-nil error indicates a
297                         // problem with one or more of the Go source files, but such an error does
298                         // not stop the package from existing, so it has no impact on matching.
299                         match.Pkgs = []string{pattern}
300                 }
301         }
302         return match
303 }