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