]> Cypherpunks.ru repositories - gostls13.git/blob - src/cmd/go/internal/modload/search.go
cmd/go: add go get go@version and toolchain@version
[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                 mod := MainModules.mustGetSingleMainModule()
168                 if modRoot := MainModules.ModRoot(mod); modRoot != "" {
169                         walkPkgs(modRoot, MainModules.PathPrefix(mod), pruneGoMod|pruneVendor)
170                         walkPkgs(filepath.Join(modRoot, "vendor"), "", pruneVendor)
171                 }
172                 return
173         }
174
175         for _, mod := range modules {
176                 if gover.IsToolchain(mod.Path) || !treeCanMatch(mod.Path) {
177                         continue
178                 }
179
180                 var (
181                         root, modPrefix string
182                         isLocal         bool
183                 )
184                 if MainModules.Contains(mod.Path) {
185                         if MainModules.ModRoot(mod) == "" {
186                                 continue // If there is no main module, we can't search in it.
187                         }
188                         root = MainModules.ModRoot(mod)
189                         modPrefix = MainModules.PathPrefix(mod)
190                         isLocal = true
191                 } else {
192                         var err error
193                         root, isLocal, err = fetch(ctx, mod)
194                         if err != nil {
195                                 m.AddError(err)
196                                 continue
197                         }
198                         modPrefix = mod.Path
199                 }
200                 if mi, err := modindex.GetModule(root); err == nil {
201                         walkFromIndex(mi, modPrefix, isMatch, treeCanMatch, tags, have, addPkg)
202                         continue
203                 } else if !errors.Is(err, modindex.ErrNotIndexed) {
204                         m.AddError(err)
205                 }
206
207                 prune := pruneVendor
208                 if isLocal {
209                         prune |= pruneGoMod
210                 }
211                 walkPkgs(root, modPrefix, prune)
212         }
213
214         return
215 }
216
217 // walkFromIndex matches packages in a module using the module index. modroot
218 // is the module's root directory on disk, index is the modindex.Module for the
219 // module, and importPathRoot is the module's path prefix.
220 func walkFromIndex(index *modindex.Module, importPathRoot string, isMatch, treeCanMatch func(string) bool, tags, have map[string]bool, addPkg func(string)) {
221         index.Walk(func(reldir string) {
222                 // Avoid .foo, _foo, and testdata subdirectory trees.
223                 p := reldir
224                 for {
225                         elem, rest, found := strings.Cut(p, string(filepath.Separator))
226                         if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
227                                 return
228                         }
229                         if found && elem == "vendor" {
230                                 // Ignore this path if it contains the element "vendor" anywhere
231                                 // except for the last element (packages named vendor are allowed
232                                 // for historical reasons). Note that found is true when this
233                                 // isn't the last path element.
234                                 return
235                         }
236                         if !found {
237                                 // Didn't find the separator, so we're considering the last element.
238                                 break
239                         }
240                         p = rest
241                 }
242
243                 // Don't use GOROOT/src.
244                 if reldir == "" && importPathRoot == "" {
245                         return
246                 }
247
248                 name := path.Join(importPathRoot, filepath.ToSlash(reldir))
249                 if !treeCanMatch(name) {
250                         return
251                 }
252
253                 if !have[name] {
254                         have[name] = true
255                         if isMatch(name) {
256                                 if _, _, err := index.Package(reldir).ScanDir(tags); err != imports.ErrNoGo {
257                                         addPkg(name)
258                                 }
259                         }
260                 }
261         })
262 }
263
264 // MatchInModule identifies the packages matching the given pattern within the
265 // given module version, which does not need to be in the build list or module
266 // requirement graph.
267 //
268 // If m is the zero module.Version, MatchInModule matches the pattern
269 // against the standard library (std and cmd) in GOROOT/src.
270 func MatchInModule(ctx context.Context, pattern string, m module.Version, tags map[string]bool) *search.Match {
271         match := search.NewMatch(pattern)
272         if m == (module.Version{}) {
273                 matchPackages(ctx, match, tags, includeStd, nil)
274         }
275
276         LoadModFile(ctx) // Sets Target, needed by fetch and matchPackages.
277
278         if !match.IsLiteral() {
279                 matchPackages(ctx, match, tags, omitStd, []module.Version{m})
280                 return match
281         }
282
283         root, isLocal, err := fetch(ctx, m)
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 }