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