1
2
3
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/search"
26 "cmd/go/internal/str"
27 "cmd/go/internal/trace"
28 "cmd/internal/par"
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
42
43
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,
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
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
83
84
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
96 if pkgDir == root {
97 if importPathRoot == "" {
98 return nil
99 }
100 } else {
101
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
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
154 defer func() {
155 <-q.Idle()
156 sort.Strings(m.Pkgs)
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 for _, mod := range MainModules.Versions() {
168 if modRoot := MainModules.ModRoot(mod); modRoot != "" {
169 walkPkgs(modRoot, MainModules.PathPrefix(mod), pruneGoMod|pruneVendor)
170 }
171 }
172 if HasModRoot() {
173 walkPkgs(VendorDir(), "", pruneVendor)
174 }
175 return
176 }
177
178 for _, mod := range modules {
179 if gover.IsToolchain(mod.Path) || !treeCanMatch(mod.Path) {
180 continue
181 }
182
183 var (
184 root, modPrefix string
185 isLocal bool
186 )
187 if MainModules.Contains(mod.Path) {
188 if MainModules.ModRoot(mod) == "" {
189 continue
190 }
191 root = MainModules.ModRoot(mod)
192 modPrefix = MainModules.PathPrefix(mod)
193 isLocal = true
194 } else {
195 var err error
196 root, isLocal, err = fetch(ctx, mod)
197 if err != nil {
198 m.AddError(err)
199 continue
200 }
201 modPrefix = mod.Path
202 }
203 if mi, err := modindex.GetModule(root); err == nil {
204 walkFromIndex(mi, modPrefix, isMatch, treeCanMatch, tags, have, addPkg)
205 continue
206 } else if !errors.Is(err, modindex.ErrNotIndexed) {
207 m.AddError(err)
208 }
209
210 prune := pruneVendor
211 if isLocal {
212 prune |= pruneGoMod
213 }
214 walkPkgs(root, modPrefix, prune)
215 }
216 }
217
218
219
220
221 func walkFromIndex(index *modindex.Module, importPathRoot string, isMatch, treeCanMatch func(string) bool, tags, have map[string]bool, addPkg func(string)) {
222 index.Walk(func(reldir string) {
223
224 p := reldir
225 for {
226 elem, rest, found := strings.Cut(p, string(filepath.Separator))
227 if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
228 return
229 }
230 if found && elem == "vendor" {
231
232
233
234
235 return
236 }
237 if !found {
238
239 break
240 }
241 p = rest
242 }
243
244
245 if reldir == "" && importPathRoot == "" {
246 return
247 }
248
249 name := path.Join(importPathRoot, filepath.ToSlash(reldir))
250 if !treeCanMatch(name) {
251 return
252 }
253
254 if !have[name] {
255 have[name] = true
256 if isMatch(name) {
257 if _, _, err := index.Package(reldir).ScanDir(tags); err != imports.ErrNoGo {
258 addPkg(name)
259 }
260 }
261 }
262 })
263 }
264
265
266
267
268
269
270
271 func MatchInModule(ctx context.Context, pattern string, m module.Version, tags map[string]bool) *search.Match {
272 match := search.NewMatch(pattern)
273 if m == (module.Version{}) {
274 matchPackages(ctx, match, tags, includeStd, nil)
275 }
276
277 LoadModFile(ctx)
278
279 if !match.IsLiteral() {
280 matchPackages(ctx, match, tags, omitStd, []module.Version{m})
281 return match
282 }
283
284 root, isLocal, err := fetch(ctx, m)
285 if err != nil {
286 match.Errs = []error{err}
287 return match
288 }
289
290 dir, haveGoFiles, err := dirInModule(pattern, m.Path, root, isLocal)
291 if err != nil {
292 match.Errs = []error{err}
293 return match
294 }
295 if haveGoFiles {
296 if _, _, err := scanDir(root, dir, tags); err != imports.ErrNoGo {
297
298
299
300
301 match.Pkgs = []string{pattern}
302 }
303 }
304 return match
305 }
306
View as plain text