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 var modRoot string
206
207 func SetModRoot(dir string) {
208 modRoot = dir
209 }
210
211
212
213
214
215
216
217
218 func (m *Match) MatchDirs() {
219 m.Dirs = []string{}
220 if !m.IsLocal() {
221 m.AddError(fmt.Errorf("internal error: MatchDirs: %s is not a valid filesystem pattern", m.pattern))
222 return
223 }
224
225 if m.IsLiteral() {
226 m.Dirs = []string{m.pattern}
227 return
228 }
229
230
231
232
233
234 cleanPattern := filepath.Clean(m.pattern)
235 isLocal := strings.HasPrefix(m.pattern, "./") || (os.PathSeparator == '\\' && strings.HasPrefix(m.pattern, `.\`))
236 prefix := ""
237 if cleanPattern != "." && isLocal {
238 prefix = "./"
239 cleanPattern = "." + string(os.PathSeparator) + cleanPattern
240 }
241 slashPattern := filepath.ToSlash(cleanPattern)
242 match := MatchPattern(slashPattern)
243
244
245
246
247
248 i := strings.Index(cleanPattern, "...")
249 dir, _ := filepath.Split(cleanPattern[:i])
250
251
252
253
254
255
256 if modRoot != "" {
257 abs, err := filepath.Abs(dir)
258 if err != nil {
259 m.AddError(err)
260 return
261 }
262 if !hasFilepathPrefix(abs, modRoot) {
263 m.AddError(fmt.Errorf("directory %s is outside module root (%s)", abs, modRoot))
264 return
265 }
266 }
267
268 err := fsys.Walk(dir, func(path string, fi fs.FileInfo, err error) error {
269 if err != nil {
270 return err
271 }
272 if !fi.IsDir() {
273 return nil
274 }
275 top := false
276 if path == dir {
277
278
279
280
281
282
283
284
285 top = true
286 path = filepath.Clean(path)
287 }
288
289
290 _, elem := filepath.Split(path)
291 dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".."
292 if dot || strings.HasPrefix(elem, "_") || elem == "testdata" {
293 return filepath.SkipDir
294 }
295
296 if !top && cfg.ModulesEnabled {
297
298 if fi, err := fsys.Stat(filepath.Join(path, "go.mod")); err == nil && !fi.IsDir() {
299 return filepath.SkipDir
300 }
301 }
302
303 name := prefix + filepath.ToSlash(path)
304 if !match(name) {
305 return nil
306 }
307
308
309
310
311
312
313
314 if p, err := cfg.BuildContext.ImportDir(path, 0); err != nil && (p == nil || len(p.InvalidGoFiles) == 0) {
315 if _, noGo := err.(*build.NoGoError); noGo {
316
317
318 return nil
319 }
320
321
322
323 }
324 m.Dirs = append(m.Dirs, name)
325 return nil
326 })
327 if err != nil {
328 m.AddError(err)
329 }
330 }
331
332
333
334
335 func TreeCanMatchPattern(pattern string) func(name string) bool {
336 wildCard := false
337 if i := strings.Index(pattern, "..."); i >= 0 {
338 wildCard = true
339 pattern = pattern[:i]
340 }
341 return func(name string) bool {
342 return len(name) <= len(pattern) && hasPathPrefix(pattern, name) ||
343 wildCard && strings.HasPrefix(name, pattern)
344 }
345 }
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362 func MatchPattern(pattern string) func(name string) bool {
363
364
365
366
367
368
369
370
371
372
373 const vendorChar = "\x00"
374
375 if strings.Contains(pattern, vendorChar) {
376 return func(name string) bool { return false }
377 }
378
379 re := regexp.QuoteMeta(pattern)
380 re = replaceVendor(re, vendorChar)
381 switch {
382 case strings.HasSuffix(re, `/`+vendorChar+`/\.\.\.`):
383 re = strings.TrimSuffix(re, `/`+vendorChar+`/\.\.\.`) + `(/vendor|/` + vendorChar + `/\.\.\.)`
384 case re == vendorChar+`/\.\.\.`:
385 re = `(/vendor|/` + vendorChar + `/\.\.\.)`
386 case strings.HasSuffix(re, `/\.\.\.`):
387 re = strings.TrimSuffix(re, `/\.\.\.`) + `(/\.\.\.)?`
388 }
389 re = strings.ReplaceAll(re, `\.\.\.`, `[^`+vendorChar+`]*`)
390
391 reg := regexp.MustCompile(`^` + re + `$`)
392
393 return func(name string) bool {
394 if strings.Contains(name, vendorChar) {
395 return false
396 }
397 return reg.MatchString(replaceVendor(name, vendorChar))
398 }
399 }
400
401
402
403 func replaceVendor(x, repl string) string {
404 if !strings.Contains(x, "vendor") {
405 return x
406 }
407 elem := strings.Split(x, "/")
408 for i := 0; i < len(elem)-1; i++ {
409 if elem[i] == "vendor" {
410 elem[i] = repl
411 }
412 }
413 return strings.Join(elem, "/")
414 }
415
416
417 func WarnUnmatched(matches []*Match) {
418 for _, m := range matches {
419 if len(m.Pkgs) == 0 && len(m.Errs) == 0 {
420 fmt.Fprintf(os.Stderr, "go: warning: %q matched no packages\n", m.pattern)
421 }
422 }
423 }
424
425
426
427 func ImportPaths(patterns []string) []*Match {
428 matches := ImportPathsQuiet(patterns)
429 WarnUnmatched(matches)
430 return matches
431 }
432
433
434 func ImportPathsQuiet(patterns []string) []*Match {
435 var out []*Match
436 for _, a := range CleanPatterns(patterns) {
437 m := NewMatch(a)
438 if m.IsLocal() {
439 m.MatchDirs()
440
441
442
443
444 m.Pkgs = make([]string, len(m.Dirs))
445 for i, dir := range m.Dirs {
446 absDir := dir
447 if !filepath.IsAbs(dir) {
448 absDir = filepath.Join(base.Cwd(), dir)
449 }
450 if bp, _ := cfg.BuildContext.ImportDir(absDir, build.FindOnly); bp.ImportPath != "" && bp.ImportPath != "." {
451 m.Pkgs[i] = bp.ImportPath
452 } else {
453 m.Pkgs[i] = dir
454 }
455 }
456 } else {
457 m.MatchPackages()
458 }
459
460 out = append(out, m)
461 }
462 return out
463 }
464
465
466
467
468
469 func CleanPatterns(patterns []string) []string {
470 if len(patterns) == 0 {
471 return []string{"."}
472 }
473 var out []string
474 for _, a := range patterns {
475 var p, v string
476 if build.IsLocalImport(a) || filepath.IsAbs(a) {
477 p = a
478 } else if i := strings.IndexByte(a, '@'); i < 0 {
479 p = a
480 } else {
481 p = a[:i]
482 v = a[i:]
483 }
484
485
486
487
488
489 if filepath.IsAbs(p) {
490 p = filepath.Clean(p)
491 } else {
492 if filepath.Separator == '\\' {
493 p = strings.ReplaceAll(p, `\`, `/`)
494 }
495
496
497 if strings.HasPrefix(p, "./") {
498 p = "./" + path.Clean(p)
499 if p == "./." {
500 p = "."
501 }
502 } else {
503 p = path.Clean(p)
504 }
505 }
506
507 out = append(out, p+v)
508 }
509 return out
510 }
511
512
513
514 func hasPathPrefix(s, prefix string) bool {
515 switch {
516 default:
517 return false
518 case len(s) == len(prefix):
519 return s == prefix
520 case len(s) > len(prefix):
521 if prefix != "" && prefix[len(prefix)-1] == '/' {
522 return strings.HasPrefix(s, prefix)
523 }
524 return s[len(prefix)] == '/' && s[:len(prefix)] == prefix
525 }
526 }
527
528
529
530 func hasFilepathPrefix(s, prefix string) bool {
531 switch {
532 default:
533 return false
534 case len(s) == len(prefix):
535 return s == prefix
536 case len(s) > len(prefix):
537 if prefix != "" && prefix[len(prefix)-1] == filepath.Separator {
538 return strings.HasPrefix(s, prefix)
539 }
540 return s[len(prefix)] == filepath.Separator && s[:len(prefix)] == prefix
541 }
542 }
543
544
545
546
547
548
549
550
551
552
553
554 func IsStandardImportPath(path string) bool {
555 i := strings.Index(path, "/")
556 if i < 0 {
557 i = len(path)
558 }
559 elem := path[:i]
560 return !strings.Contains(elem, ".")
561 }
562
563
564
565
566 func IsRelativePath(pattern string) bool {
567 return strings.HasPrefix(pattern, "./") || strings.HasPrefix(pattern, "../") || pattern == "." || pattern == ".."
568 }
569
570
571
572
573
574 func InDir(path, dir string) string {
575 if rel := inDirLex(path, dir); rel != "" {
576 return rel
577 }
578 xpath, err := filepath.EvalSymlinks(path)
579 if err != nil || xpath == path {
580 xpath = ""
581 } else {
582 if rel := inDirLex(xpath, dir); rel != "" {
583 return rel
584 }
585 }
586
587 xdir, err := filepath.EvalSymlinks(dir)
588 if err == nil && xdir != dir {
589 if rel := inDirLex(path, xdir); rel != "" {
590 return rel
591 }
592 if xpath != "" {
593 if rel := inDirLex(xpath, xdir); rel != "" {
594 return rel
595 }
596 }
597 }
598 return ""
599 }
600
601
602
603
604
605
606 func inDirLex(path, dir string) string {
607 pv := strings.ToUpper(filepath.VolumeName(path))
608 dv := strings.ToUpper(filepath.VolumeName(dir))
609 path = path[len(pv):]
610 dir = dir[len(dv):]
611 switch {
612 default:
613 return ""
614 case pv != dv:
615 return ""
616 case len(path) == len(dir):
617 if path == dir {
618 return "."
619 }
620 return ""
621 case dir == "":
622 return path
623 case len(path) > len(dir):
624 if dir[len(dir)-1] == filepath.Separator {
625 if path[:len(dir)] == dir {
626 return path[len(dir):]
627 }
628 return ""
629 }
630 if path[len(dir)] == filepath.Separator && path[:len(dir)] == dir {
631 if len(path) == len(dir)+1 {
632 return "."
633 }
634 return path[len(dir)+1:]
635 }
636 return ""
637 }
638 }
639
View as plain text