1
2
3
4
5 package search
6
7 import (
8 "cmd/go/internal/base"
9 "cmd/go/internal/cfg"
10 "cmd/go/internal/fsys"
11 "cmd/go/internal/imports"
12 "cmd/go/internal/modindex"
13 "cmd/go/internal/str"
14 "cmd/internal/pkgpattern"
15 "errors"
16 "fmt"
17 "go/build"
18 "io/fs"
19 "os"
20 "path"
21 "path/filepath"
22 "strings"
23
24 "golang.org/x/mod/modfile"
25 )
26
27
28 type Match struct {
29 pattern string
30 Dirs []string
31 Pkgs []string
32 Errs []error
33
34
35
36
37
38 }
39
40
41
42 func NewMatch(pattern string) *Match {
43 return &Match{pattern: pattern}
44 }
45
46
47 func (m *Match) Pattern() string { return m.pattern }
48
49
50 func (m *Match) AddError(err error) {
51 m.Errs = append(m.Errs, &MatchError{Match: m, Err: err})
52 }
53
54
55
56
57 func (m *Match) IsLiteral() bool {
58 return !strings.Contains(m.pattern, "...") && !m.IsMeta()
59 }
60
61
62
63 func (m *Match) IsLocal() bool {
64 return build.IsLocalImport(m.pattern) || filepath.IsAbs(m.pattern)
65 }
66
67
68
69 func (m *Match) IsMeta() bool {
70 return IsMetaPackage(m.pattern)
71 }
72
73
74 func IsMetaPackage(name string) bool {
75 return name == "std" || name == "cmd" || name == "tool" || name == "work" || name == "all"
76 }
77
78
79
80 type MatchError struct {
81 Match *Match
82 Err error
83 }
84
85 func (e *MatchError) Error() string {
86 if e.Match.IsLiteral() {
87 return fmt.Sprintf("%s: %v", e.Match.Pattern(), e.Err)
88 }
89 return fmt.Sprintf("pattern %s: %v", e.Match.Pattern(), e.Err)
90 }
91
92 func (e *MatchError) Unwrap() error {
93 return e.Err
94 }
95
96
97
98
99
100
101
102
103 func (m *Match) MatchPackages() {
104 m.Pkgs = []string{}
105 if m.IsLocal() {
106 m.AddError(fmt.Errorf("internal error: MatchPackages: %s is not a valid package pattern", m.pattern))
107 return
108 }
109
110 if m.IsLiteral() {
111 m.Pkgs = []string{m.pattern}
112 return
113 }
114
115 match := func(string) bool { return true }
116 treeCanMatch := func(string) bool { return true }
117 if !m.IsMeta() {
118 match = pkgpattern.MatchPattern(m.pattern)
119 treeCanMatch = pkgpattern.TreeCanMatchPattern(m.pattern)
120 }
121
122 have := map[string]bool{
123 "builtin": true,
124 }
125 if !cfg.BuildContext.CgoEnabled {
126 have["runtime/cgo"] = true
127 }
128
129 for _, src := range cfg.BuildContext.SrcDirs() {
130 if (m.pattern == "std" || m.pattern == "cmd") && src != cfg.GOROOTsrc {
131 continue
132 }
133
134
135
136
137 src = str.WithFilePathSeparator(filepath.Clean(src))
138 root := src
139 if m.pattern == "cmd" {
140 root += "cmd" + string(filepath.Separator)
141 }
142
143 err := fsys.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
144 if err != nil {
145 return err
146 }
147 if path == src {
148 return nil
149 }
150
151 want := true
152
153 _, elem := filepath.Split(path)
154 if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
155 want = false
156 }
157
158 name := filepath.ToSlash(path[len(src):])
159 if m.pattern == "std" && (!IsStandardImportPath(name) || name == "cmd") {
160
161
162 want = false
163 }
164 if !treeCanMatch(name) {
165 want = false
166 }
167
168 if !d.IsDir() {
169 if d.Type()&fs.ModeSymlink != 0 && want && strings.Contains(m.pattern, "...") {
170 if target, err := fsys.Stat(path); err == nil && target.IsDir() {
171 fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path)
172 }
173 }
174 return nil
175 }
176 if !want {
177 return filepath.SkipDir
178 }
179
180 if have[name] {
181 return nil
182 }
183 have[name] = true
184 if !match(name) {
185 return nil
186 }
187 pkg, err := cfg.BuildContext.ImportDir(path, 0)
188 if err != nil {
189 if _, noGo := err.(*build.NoGoError); noGo {
190
191
192 return nil
193 }
194
195
196
197 }
198
199
200
201
202
203 if m.pattern == "cmd" && pkg != nil && strings.HasPrefix(pkg.ImportPath, "cmd/vendor") && pkg.Name == "main" {
204 return nil
205 }
206
207 m.Pkgs = append(m.Pkgs, name)
208 return nil
209 })
210 if err != nil {
211 m.AddError(err)
212 }
213 }
214 }
215
216
217 type IgnorePatterns struct {
218 relativePatterns []string
219 anyPatterns []string
220 }
221
222
223
224
225
226
227
228
229
230
231
232 func (ignorePatterns *IgnorePatterns) ShouldIgnore(dir string) bool {
233 if dir == "" {
234 return false
235 }
236 dir = normalizePath(dir)
237 for _, pattern := range ignorePatterns.relativePatterns {
238 if strings.HasPrefix(dir, pattern) {
239 return true
240 }
241 }
242 for _, pattern := range ignorePatterns.anyPatterns {
243 if strings.Contains(dir, pattern) {
244 return true
245 }
246 }
247 return false
248 }
249
250 func NewIgnorePatterns(patterns []string) *IgnorePatterns {
251 var relativePatterns, anyPatterns []string
252 for _, pattern := range patterns {
253 ignorePatternPath, isRelative := strings.CutPrefix(pattern, "./")
254 ignorePatternPath = normalizePath(ignorePatternPath)
255 if isRelative {
256 relativePatterns = append(relativePatterns, ignorePatternPath)
257 } else {
258 anyPatterns = append(anyPatterns, ignorePatternPath)
259 }
260 }
261 return &IgnorePatterns{
262 relativePatterns: relativePatterns,
263 anyPatterns: anyPatterns,
264 }
265 }
266
267
268 func normalizePath(path string) string {
269 path = filepath.ToSlash(path)
270 if !strings.HasPrefix(path, "/") {
271 path = "/" + path
272 }
273 if !strings.HasSuffix(path, "/") {
274 path += "/"
275 }
276 return path
277 }
278
279
280
281
282
283
284
285
286 func (m *Match) MatchDirs(modRoots []string) {
287 m.Dirs = []string{}
288 if !m.IsLocal() {
289 m.AddError(fmt.Errorf("internal error: MatchDirs: %s is not a valid filesystem pattern", m.pattern))
290 return
291 }
292
293 if m.IsLiteral() {
294 m.Dirs = []string{m.pattern}
295 return
296 }
297
298
299
300
301
302 cleanPattern := filepath.Clean(m.pattern)
303 isLocal := strings.HasPrefix(m.pattern, "./") || (os.PathSeparator == '\\' && strings.HasPrefix(m.pattern, `.\`))
304 prefix := ""
305 if cleanPattern != "." && isLocal {
306 prefix = "./"
307 cleanPattern = "." + string(os.PathSeparator) + cleanPattern
308 }
309 slashPattern := filepath.ToSlash(cleanPattern)
310 match := pkgpattern.MatchPattern(slashPattern)
311
312
313
314
315
316 i := strings.Index(cleanPattern, "...")
317 dir, _ := filepath.Split(cleanPattern[:i])
318
319
320
321
322
323
324 var modRoot string
325 if len(modRoots) > 0 {
326 abs, err := filepath.Abs(dir)
327 if err != nil {
328 m.AddError(err)
329 return
330 }
331 var found bool
332 for _, mr := range modRoots {
333 if mr != "" && str.HasFilePathPrefix(abs, mr) {
334 found = true
335 modRoot = mr
336 }
337 }
338 if !found {
339 plural := ""
340 if len(modRoots) > 1 {
341 plural = "s"
342 }
343 m.AddError(fmt.Errorf("directory %s is outside module root%s (%s)", abs, plural, strings.Join(modRoots, ", ")))
344 }
345 }
346
347 ignorePatterns := parseIgnorePatterns(modRoot)
348 tags := imports.Tags()
349
350
351
352 dir = str.WithFilePathSeparator(dir)
353 err := fsys.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
354 if err != nil {
355 return err
356 }
357 if !d.IsDir() {
358 return nil
359 }
360 top := false
361 if path == dir {
362
363
364
365
366
367
368
369
370 top = true
371 path = filepath.Clean(path)
372 }
373
374
375 _, elem := filepath.Split(path)
376 dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".."
377 if dot || strings.HasPrefix(elem, "_") || elem == "testdata" {
378 return filepath.SkipDir
379 }
380 absPath, err := filepath.Abs(path)
381 if err != nil {
382 return err
383 }
384
385 if ignorePatterns != nil && ignorePatterns.ShouldIgnore(InDir(absPath, modRoot)) {
386 if cfg.BuildX {
387 fmt.Fprintf(os.Stderr, "# ignoring directory %s\n", absPath)
388 }
389 return filepath.SkipDir
390 }
391
392 if !top && cfg.ModulesEnabled {
393
394 if info, err := fsys.Stat(filepath.Join(path, "go.mod")); err == nil && !info.IsDir() {
395 return filepath.SkipDir
396 }
397 }
398
399 name := prefix + filepath.ToSlash(path)
400 if !match(name) {
401 return nil
402 }
403
404 if modRoot := moduleRootContaining(modRoots, absPath); modRoot != "" {
405 hasPackage, ok, err := indexedPackage(modRoot, absPath, tags)
406 if err != nil {
407 return err
408 }
409 if ok {
410 if hasPackage {
411 m.Dirs = append(m.Dirs, name)
412 }
413 return nil
414 }
415 }
416
417
418
419
420
421
422
423 if p, err := cfg.BuildContext.ImportDir(path, 0); err != nil && (p == nil || len(p.InvalidGoFiles) == 0) {
424 if _, noGo := err.(*build.NoGoError); noGo {
425
426
427 return nil
428 }
429
430
431
432 }
433 m.Dirs = append(m.Dirs, name)
434 return nil
435 })
436 if err != nil {
437 m.AddError(err)
438 }
439 }
440
441 func moduleRootContaining(modRoots []string, absPath string) string {
442 var modRoot string
443 for _, root := range modRoots {
444 if root != "" && str.HasFilePathPrefix(absPath, root) && len(root) > len(modRoot) {
445 modRoot = root
446 }
447 }
448 return modRoot
449 }
450
451 func indexedPackage(modRoot, absPath string, tags map[string]bool) (hasPackage, ok bool, err error) {
452 ip, err := modindex.GetPackage(modRoot, absPath)
453 if errors.Is(err, modindex.ErrNotIndexed) {
454 return false, false, nil
455 }
456 if err != nil {
457 return false, true, err
458 }
459 _, _, err = ip.ScanDir(tags)
460 return err != imports.ErrNoGo, true, nil
461 }
462
463
464 func WarnUnmatched(matches []*Match) {
465 for _, m := range matches {
466 if len(m.Pkgs) == 0 && len(m.Errs) == 0 {
467 fmt.Fprintf(os.Stderr, "go: warning: %q matched no packages\n", m.pattern)
468 }
469 }
470 }
471
472
473
474 func ImportPaths(patterns []string) []*Match {
475 matches := ImportPathsQuiet(patterns)
476 WarnUnmatched(matches)
477 return matches
478 }
479
480
481 func ImportPathsQuiet(patterns []string) []*Match {
482 patterns = CleanPatterns(patterns)
483 out := make([]*Match, 0, len(patterns))
484 for _, a := range patterns {
485 m := NewMatch(a)
486 if m.IsLocal() {
487 m.MatchDirs(nil)
488
489
490
491
492 m.Pkgs = make([]string, len(m.Dirs))
493 for i, dir := range m.Dirs {
494 absDir := dir
495 if !filepath.IsAbs(dir) {
496 absDir = filepath.Join(base.Cwd(), dir)
497 }
498 if bp, _ := cfg.BuildContext.ImportDir(absDir, build.FindOnly); bp.ImportPath != "" && bp.ImportPath != "." {
499 m.Pkgs[i] = bp.ImportPath
500 } else {
501 m.Pkgs[i] = dir
502 }
503 }
504 } else {
505 m.MatchPackages()
506 }
507
508 out = append(out, m)
509 }
510 return out
511 }
512
513
514
515
516
517 func CleanPatterns(patterns []string) []string {
518 if len(patterns) == 0 {
519 return []string{"."}
520 }
521 out := make([]string, 0, len(patterns))
522 for _, a := range patterns {
523 var p, v string
524 if build.IsLocalImport(a) || filepath.IsAbs(a) {
525 p = a
526 } else if i := strings.IndexByte(a, '@'); i < 0 {
527 p = a
528 } else {
529 p = a[:i]
530 v = a[i:]
531 }
532
533
534
535
536
537 if filepath.IsAbs(p) {
538 p = filepath.Clean(p)
539 } else {
540 p = strings.ReplaceAll(p, `\`, `/`)
541
542
543 if strings.HasPrefix(p, "./") {
544 p = "./" + path.Clean(p)
545 if p == "./." {
546 p = "."
547 }
548 } else {
549 p = path.Clean(p)
550 }
551 }
552
553 out = append(out, p+v)
554 }
555 return out
556 }
557
558
559
560
561
562
563
564
565
566
567
568 func IsStandardImportPath(path string) bool {
569 i := strings.Index(path, "/")
570 if i < 0 {
571 i = len(path)
572 }
573 elem := path[:i]
574 return !strings.Contains(elem, ".")
575 }
576
577
578
579
580 func IsRelativePath(pattern string) bool {
581 return strings.HasPrefix(pattern, "./") || strings.HasPrefix(pattern, "../") || pattern == "." || pattern == ".."
582 }
583
584
585
586
587
588 func InDir(path, dir string) string {
589
590
591 inDirLex := func(path, dir string) (string, bool) {
592 if dir == "" {
593 return path, true
594 }
595 rel := str.TrimFilePathPrefix(path, dir)
596 if rel == path {
597 return "", false
598 }
599 if rel == "" {
600 return ".", true
601 }
602 return rel, true
603 }
604
605 if rel, ok := inDirLex(path, dir); ok {
606 return rel
607 }
608 xpath, err := filepath.EvalSymlinks(path)
609 if err != nil || xpath == path {
610 xpath = ""
611 } else {
612 if rel, ok := inDirLex(xpath, dir); ok {
613 return rel
614 }
615 }
616
617 xdir, err := filepath.EvalSymlinks(dir)
618 if err == nil && xdir != dir {
619 if rel, ok := inDirLex(path, xdir); ok {
620 return rel
621 }
622 if xpath != "" {
623 if rel, ok := inDirLex(xpath, xdir); ok {
624 return rel
625 }
626 }
627 }
628 return ""
629 }
630
631
632
633
634 func parseIgnorePatterns(modRoot string) *IgnorePatterns {
635 if modRoot == "" {
636 return nil
637 }
638 data, err := os.ReadFile(filepath.Join(modRoot, "go.mod"))
639 if err != nil {
640 return nil
641 }
642 modFile, err := modfile.Parse("go.mod", data, nil)
643 if err != nil {
644 return nil
645 }
646 var patterns []string
647 for _, i := range modFile.Ignore {
648 patterns = append(patterns, i.Path)
649 }
650 return NewIgnorePatterns(patterns)
651 }
652
View as plain text