]> Cypherpunks.ru repositories - gostls13.git/blob - src/cmd/go/internal/modload/search.go
cmd/go: use Join functions instead of adding path separators to strings
[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(pkgDir 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 pkgDir == root {
92                                 if importPathRoot == "" {
93                                         return nil
94                                 }
95                         } else {
96                                 // Avoid .foo, _foo, and testdata subdirectory trees.
97                                 _, elem = filepath.Split(pkgDir)
98                                 if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
99                                         want = false
100                                 }
101                         }
102
103                         rel := strings.TrimPrefix(filepath.ToSlash(pkgDir[len(root):]), "/")
104                         name := path.Join(importPathRoot, rel)
105
106                         if !treeCanMatch(name) {
107                                 want = false
108                         }
109
110                         if !fi.IsDir() {
111                                 if fi.Mode()&fs.ModeSymlink != 0 && want && strings.Contains(m.Pattern(), "...") {
112                                         if target, err := fsys.Stat(pkgDir); err == nil && target.IsDir() {
113                                                 fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", pkgDir)
114                                         }
115                                 }
116                                 return nil
117                         }
118
119                         if !want {
120                                 return filepath.SkipDir
121                         }
122                         // Stop at module boundaries.
123                         if (prune&pruneGoMod != 0) && pkgDir != root {
124                                 if fi, err := os.Stat(filepath.Join(pkgDir, "go.mod")); err == nil && !fi.IsDir() {
125                                         return filepath.SkipDir
126                                 }
127                         }
128
129                         if !have[name] {
130                                 have[name] = true
131                                 if isMatch(name) {
132                                         q.Add(func() {
133                                                 if _, _, err := scanDir(root, pkgDir, tags); err != imports.ErrNoGo {
134                                                         addPkg(name)
135                                                 }
136                                         })
137                                 }
138                         }
139
140                         if elem == "vendor" && (prune&pruneVendor != 0) {
141                                 return filepath.SkipDir
142                         }
143                         return nil
144                 })
145                 if err != nil {
146                         m.AddError(err)
147                 }
148         }
149
150         // Wait for all in-flight operations to complete before returning.
151         defer func() {
152                 <-q.Idle()
153                 sort.Strings(m.Pkgs) // sort everything we added for determinism
154         }()
155
156         if filter == includeStd {
157                 walkPkgs(cfg.GOROOTsrc, "", pruneGoMod)
158                 if treeCanMatch("cmd") {
159                         walkPkgs(filepath.Join(cfg.GOROOTsrc, "cmd"), "cmd", pruneGoMod)
160                 }
161         }
162
163         if cfg.BuildMod == "vendor" {
164                 mod := MainModules.mustGetSingleMainModule()
165                 if modRoot := MainModules.ModRoot(mod); modRoot != "" {
166                         walkPkgs(modRoot, MainModules.PathPrefix(mod), pruneGoMod|pruneVendor)
167                         walkPkgs(filepath.Join(modRoot, "vendor"), "", pruneVendor)
168                 }
169                 return
170         }
171
172         for _, mod := range modules {
173                 if !treeCanMatch(mod.Path) {
174                         continue
175                 }
176
177                 var (
178                         root, modPrefix string
179                         isLocal         bool
180                 )
181                 if MainModules.Contains(mod.Path) {
182                         if MainModules.ModRoot(mod) == "" {
183                                 continue // If there is no main module, we can't search in it.
184                         }
185                         root = MainModules.ModRoot(mod)
186                         modPrefix = MainModules.PathPrefix(mod)
187                         isLocal = true
188                 } else {
189                         var err error
190                         root, isLocal, err = fetch(ctx, mod)
191                         if err != nil {
192                                 m.AddError(err)
193                                 continue
194                         }
195                         modPrefix = mod.Path
196                 }
197                 if mi, err := modindex.GetModule(root); err == nil {
198                         walkFromIndex(mi, modPrefix, isMatch, treeCanMatch, tags, have, addPkg)
199                         continue
200                 } else if !errors.Is(err, modindex.ErrNotIndexed) {
201                         m.AddError(err)
202                 }
203
204                 prune := pruneVendor
205                 if isLocal {
206                         prune |= pruneGoMod
207                 }
208                 walkPkgs(root, modPrefix, prune)
209         }
210
211         return
212 }
213
214 // walkFromIndex matches packages in a module using the module index. modroot
215 // is the module's root directory on disk, index is the modindex.Module for the
216 // module, and importPathRoot is the module's path prefix.
217 func walkFromIndex(index *modindex.Module, importPathRoot string, isMatch, treeCanMatch func(string) bool, tags, have map[string]bool, addPkg func(string)) {
218         index.Walk(func(reldir string) {
219                 // Avoid .foo, _foo, and testdata subdirectory trees.
220                 p := reldir
221                 for {
222                         elem, rest, found := strings.Cut(p, string(filepath.Separator))
223                         if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
224                                 return
225                         }
226                         if found && elem == "vendor" {
227                                 // Ignore this path if it contains the element "vendor" anywhere
228                                 // except for the last element (packages named vendor are allowed
229                                 // for historical reasons). Note that found is true when this
230                                 // isn't the last path element.
231                                 return
232                         }
233                         if !found {
234                                 // Didn't find the separator, so we're considering the last element.
235                                 break
236                         }
237                         p = rest
238                 }
239
240                 // Don't use GOROOT/src.
241                 if reldir == "" && importPathRoot == "" {
242                         return
243                 }
244
245                 name := path.Join(importPathRoot, filepath.ToSlash(reldir))
246                 if !treeCanMatch(name) {
247                         return
248                 }
249
250                 if !have[name] {
251                         have[name] = true
252                         if isMatch(name) {
253                                 if _, _, err := index.Package(reldir).ScanDir(tags); err != imports.ErrNoGo {
254                                         addPkg(name)
255                                 }
256                         }
257                 }
258         })
259 }
260
261 // MatchInModule identifies the packages matching the given pattern within the
262 // given module version, which does not need to be in the build list or module
263 // requirement graph.
264 //
265 // If m is the zero module.Version, MatchInModule matches the pattern
266 // against the standard library (std and cmd) in GOROOT/src.
267 func MatchInModule(ctx context.Context, pattern string, m module.Version, tags map[string]bool) *search.Match {
268         match := search.NewMatch(pattern)
269         if m == (module.Version{}) {
270                 matchPackages(ctx, match, tags, includeStd, nil)
271         }
272
273         LoadModFile(ctx) // Sets Target, needed by fetch and matchPackages.
274
275         if !match.IsLiteral() {
276                 matchPackages(ctx, match, tags, omitStd, []module.Version{m})
277                 return match
278         }
279
280         root, isLocal, err := fetch(ctx, m)
281         if err != nil {
282                 match.Errs = []error{err}
283                 return match
284         }
285
286         dir, haveGoFiles, err := dirInModule(pattern, m.Path, root, isLocal)
287         if err != nil {
288                 match.Errs = []error{err}
289                 return match
290         }
291         if haveGoFiles {
292                 if _, _, err := scanDir(root, dir, tags); err != imports.ErrNoGo {
293                         // ErrNoGo indicates that the directory is not actually a Go package,
294                         // perhaps due to the tags in use. Any other non-nil error indicates a
295                         // problem with one or more of the Go source files, but such an error does
296                         // not stop the package from existing, so it has no impact on matching.
297                         match.Pkgs = []string{pattern}
298                 }
299         }
300         return match
301 }