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 ignorePatternsMap := parseIgnorePatterns(ctx, treeCanMatch, modules)
78 walkPkgs := func(root, importPathRoot string, prune pruning) {
79 _, span := trace.StartSpan(ctx, "walkPkgs "+root)
80 defer span.Done()
81
82
83
84
85 cleanRoot := filepath.Clean(root)
86 root = str.WithFilePathSeparator(cleanRoot)
87 err := fsys.WalkDir(root, func(pkgDir string, d fs.DirEntry, err error) error {
88 if err != nil {
89 m.AddError(err)
90 return nil
91 }
92
93 want := true
94 elem := ""
95 relPkgDir := filepath.ToSlash(pkgDir[len(root):])
96
97
98 if pkgDir == root {
99 if importPathRoot == "" {
100 return nil
101 }
102 } else {
103
104 _, elem = filepath.Split(pkgDir)
105 if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
106 want = false
107 } else if ignorePatternsMap[cleanRoot] != nil && ignorePatternsMap[cleanRoot].ShouldIgnore(relPkgDir) {
108 if cfg.BuildX {
109 fmt.Fprintf(os.Stderr, "# ignoring directory %s\n", pkgDir)
110 }
111 want = false
112 }
113 }
114
115 name := path.Join(importPathRoot, relPkgDir)
116 if !treeCanMatch(name) {
117 want = false
118 }
119
120 if !d.IsDir() {
121 if d.Type()&fs.ModeSymlink != 0 && want && strings.Contains(m.Pattern(), "...") {
122 if target, err := fsys.Stat(pkgDir); err == nil && target.IsDir() {
123 fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", pkgDir)
124 }
125 }
126 return nil
127 }
128
129 if !want {
130 return filepath.SkipDir
131 }
132
133 if (prune&pruneGoMod != 0) && pkgDir != root {
134 if info, err := os.Stat(filepath.Join(pkgDir, "go.mod")); err == nil && !info.IsDir() {
135 return filepath.SkipDir
136 }
137 }
138
139 if !have[name] {
140 have[name] = true
141 if isMatch(name) {
142 q.Add(func() {
143 if _, _, err := scanDir(root, pkgDir, tags); err != imports.ErrNoGo {
144 addPkg(name)
145 }
146 })
147 }
148 }
149
150 if elem == "vendor" && (prune&pruneVendor != 0) {
151 return filepath.SkipDir
152 }
153 return nil
154 })
155 if err != nil {
156 m.AddError(err)
157 }
158 }
159
160
161 defer func() {
162 <-q.Idle()
163 sort.Strings(m.Pkgs)
164 }()
165
166 if filter == includeStd {
167 walkPkgs(cfg.GOROOTsrc, "", pruneGoMod)
168 if treeCanMatch("cmd") {
169 walkPkgs(filepath.Join(cfg.GOROOTsrc, "cmd"), "cmd", pruneGoMod)
170 }
171 }
172
173 if cfg.BuildMod == "vendor" {
174 for _, mod := range MainModules.Versions() {
175 if modRoot := MainModules.ModRoot(mod); modRoot != "" {
176 walkPkgs(modRoot, MainModules.PathPrefix(mod), pruneGoMod|pruneVendor)
177 }
178 }
179 if HasModRoot() {
180 walkPkgs(VendorDir(), "", pruneVendor)
181 }
182 return
183 }
184
185 for _, mod := range modules {
186 if gover.IsToolchain(mod.Path) || !treeCanMatch(mod.Path) {
187 continue
188 }
189
190 var (
191 root, modPrefix string
192 isLocal bool
193 )
194 if MainModules.Contains(mod.Path) {
195 if MainModules.ModRoot(mod) == "" {
196 continue
197 }
198 root = MainModules.ModRoot(mod)
199 modPrefix = MainModules.PathPrefix(mod)
200 isLocal = true
201 } else {
202 var err error
203 root, isLocal, err = fetch(ctx, mod)
204 if err != nil {
205 m.AddError(err)
206 continue
207 }
208 modPrefix = mod.Path
209 }
210 if mi, err := modindex.GetModule(root); err == nil {
211 walkFromIndex(mi, modPrefix, isMatch, treeCanMatch, tags, have, addPkg, ignorePatternsMap[root], root)
212 continue
213 } else if !errors.Is(err, modindex.ErrNotIndexed) {
214 m.AddError(err)
215 }
216
217 prune := pruneVendor
218 if isLocal {
219 prune |= pruneGoMod
220 }
221 walkPkgs(root, modPrefix, prune)
222 }
223 }
224
225
226
227
228 func walkFromIndex(index *modindex.Module, importPathRoot string, isMatch, treeCanMatch func(string) bool, tags, have map[string]bool, addPkg func(string), ignorePatterns *search.IgnorePatterns, modRoot string) {
229 index.Walk(func(reldir string) {
230
231 p := reldir
232 for {
233 elem, rest, found := strings.Cut(p, string(filepath.Separator))
234 if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
235 return
236 }
237 if found && elem == "vendor" {
238
239
240
241
242 return
243 }
244 if !found {
245
246 break
247 }
248 p = rest
249 }
250
251 if ignorePatterns != nil && ignorePatterns.ShouldIgnore(reldir) {
252 if cfg.BuildX {
253 absPath := filepath.Join(modRoot, reldir)
254 fmt.Fprintf(os.Stderr, "# ignoring directory %s\n", absPath)
255 }
256 return
257 }
258
259
260 if reldir == "" && importPathRoot == "" {
261 return
262 }
263
264 name := path.Join(importPathRoot, filepath.ToSlash(reldir))
265 if !treeCanMatch(name) {
266 return
267 }
268
269 if !have[name] {
270 have[name] = true
271 if isMatch(name) {
272 if _, _, err := index.Package(reldir).ScanDir(tags); err != imports.ErrNoGo {
273 addPkg(name)
274 }
275 }
276 }
277 })
278 }
279
280
281
282
283
284
285
286 func MatchInModule(ctx context.Context, pattern string, m module.Version, tags map[string]bool) *search.Match {
287 match := search.NewMatch(pattern)
288 if m == (module.Version{}) {
289 matchPackages(ctx, match, tags, includeStd, nil)
290 }
291
292 LoadModFile(ctx)
293
294 if !match.IsLiteral() {
295 matchPackages(ctx, match, tags, omitStd, []module.Version{m})
296 return match
297 }
298
299 root, isLocal, err := fetch(ctx, m)
300 if err != nil {
301 match.Errs = []error{err}
302 return match
303 }
304
305 dir, haveGoFiles, err := dirInModule(pattern, m.Path, root, isLocal)
306 if err != nil {
307 match.Errs = []error{err}
308 return match
309 }
310 if haveGoFiles {
311 if _, _, err := scanDir(root, dir, tags); err != imports.ErrNoGo {
312
313
314
315
316 match.Pkgs = []string{pattern}
317 }
318 }
319 return match
320 }
321
322
323
324
325 func parseIgnorePatterns(ctx context.Context, treeCanMatch func(string) bool, modules []module.Version) map[string]*search.IgnorePatterns {
326 ignorePatternsMap := make(map[string]*search.IgnorePatterns)
327 for _, mod := range modules {
328 if gover.IsToolchain(mod.Path) || !treeCanMatch(mod.Path) {
329 continue
330 }
331 var modRoot string
332 var ignorePatterns []string
333 if MainModules.Contains(mod.Path) {
334 modRoot = MainModules.ModRoot(mod)
335 if modRoot == "" {
336 continue
337 }
338 modIndex := MainModules.Index(mod)
339 if modIndex == nil {
340 continue
341 }
342 ignorePatterns = modIndex.ignore
343 } else if cfg.BuildMod != "vendor" {
344
345
346 var err error
347 modRoot, _, err = fetch(ctx, mod)
348 if err != nil {
349 continue
350 }
351 summary, err := goModSummary(mod)
352 if err != nil {
353 continue
354 }
355 ignorePatterns = summary.ignore
356 }
357 ignorePatternsMap[modRoot] = search.NewIgnorePatterns(ignorePatterns)
358 }
359 return ignorePatternsMap
360 }
361
View as plain text