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