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
22
23 type Match struct {
24 pattern string
25 Dirs []string
26 Pkgs []string
27 Errs []error
28
29
30
31
32
33 }
34
35
36
37 func NewMatch(pattern string) *Match {
38 return &Match{pattern: pattern}
39 }
40
41
42 func (m *Match) Pattern() string { return m.pattern }
43
44
45 func (m *Match) AddError(err error) {
46 m.Errs = append(m.Errs, &MatchError{Match: m, Err: err})
47 }
48
49
50
51
52 func (m *Match) IsLiteral() bool {
53 return !strings.Contains(m.pattern, "...") && !m.IsMeta()
54 }
55
56
57
58 func (m *Match) IsLocal() bool {
59 return build.IsLocalImport(m.pattern) || filepath.IsAbs(m.pattern)
60 }
61
62
63
64 func (m *Match) IsMeta() bool {
65 return IsMetaPackage(m.pattern)
66 }
67
68
69 func IsMetaPackage(name string) bool {
70 return name == "std" || name == "cmd" || name == "tool" || name == "all"
71 }
72
73
74
75 type MatchError struct {
76 Match *Match
77 Err error
78 }
79
80 func (e *MatchError) Error() string {
81 if e.Match.IsLiteral() {
82 return fmt.Sprintf("%s: %v", e.Match.Pattern(), e.Err)
83 }
84 return fmt.Sprintf("pattern %s: %v", e.Match.Pattern(), e.Err)
85 }
86
87 func (e *MatchError) Unwrap() error {
88 return e.Err
89 }
90
91
92
93
94
95
96
97
98 func (m *Match) MatchPackages() {
99 m.Pkgs = []string{}
100 if m.IsLocal() {
101 m.AddError(fmt.Errorf("internal error: MatchPackages: %s is not a valid package pattern", m.pattern))
102 return
103 }
104
105 if m.IsLiteral() {
106 m.Pkgs = []string{m.pattern}
107 return
108 }
109
110 match := func(string) bool { return true }
111 treeCanMatch := func(string) bool { return true }
112 if !m.IsMeta() {
113 match = pkgpattern.MatchPattern(m.pattern)
114 treeCanMatch = pkgpattern.TreeCanMatchPattern(m.pattern)
115 }
116
117 have := map[string]bool{
118 "builtin": true,
119 }
120 if !cfg.BuildContext.CgoEnabled {
121 have["runtime/cgo"] = true
122 }
123
124 for _, src := range cfg.BuildContext.SrcDirs() {
125 if (m.pattern == "std" || m.pattern == "cmd") && src != cfg.GOROOTsrc {
126 continue
127 }
128
129
130
131
132 src = str.WithFilePathSeparator(filepath.Clean(src))
133 root := src
134 if m.pattern == "cmd" {
135 root += "cmd" + string(filepath.Separator)
136 }
137
138 err := fsys.Walk(root, func(path string, fi fs.FileInfo, err error) error {
139 if err != nil {
140 return err
141 }
142 if path == src {
143 return nil
144 }
145
146 want := true
147
148 _, elem := filepath.Split(path)
149 if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
150 want = false
151 }
152
153 name := filepath.ToSlash(path[len(src):])
154 if m.pattern == "std" && (!IsStandardImportPath(name) || name == "cmd") {
155
156
157 want = false
158 }
159 if !treeCanMatch(name) {
160 want = false
161 }
162
163 if !fi.IsDir() {
164 if fi.Mode()&fs.ModeSymlink != 0 && want && strings.Contains(m.pattern, "...") {
165 if target, err := fsys.Stat(path); err == nil && target.IsDir() {
166 fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path)
167 }
168 }
169 return nil
170 }
171 if !want {
172 return filepath.SkipDir
173 }
174
175 if have[name] {
176 return nil
177 }
178 have[name] = true
179 if !match(name) {
180 return nil
181 }
182 pkg, err := cfg.BuildContext.ImportDir(path, 0)
183 if err != nil {
184 if _, noGo := err.(*build.NoGoError); noGo {
185
186
187 return nil
188 }
189
190
191
192 }
193
194
195
196
197
198 if m.pattern == "cmd" && pkg != nil && strings.HasPrefix(pkg.ImportPath, "cmd/vendor") && pkg.Name == "main" {
199 return nil
200 }
201
202 m.Pkgs = append(m.Pkgs, name)
203 return nil
204 })
205 if err != nil {
206 m.AddError(err)
207 }
208 }
209 }
210
211
212
213
214
215
216
217
218 func (m *Match) MatchDirs(modRoots []string) {
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 := pkgpattern.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 len(modRoots) > 1 {
257 abs, err := filepath.Abs(dir)
258 if err != nil {
259 m.AddError(err)
260 return
261 }
262 var found bool
263 for _, modRoot := range modRoots {
264 if modRoot != "" && str.HasFilePathPrefix(abs, modRoot) {
265 found = true
266 }
267 }
268 if !found {
269 plural := ""
270 if len(modRoots) > 1 {
271 plural = "s"
272 }
273 m.AddError(fmt.Errorf("directory %s is outside module root%s (%s)", abs, plural, strings.Join(modRoots, ", ")))
274 }
275 }
276
277
278
279
280 dir = str.WithFilePathSeparator(dir)
281 err := fsys.Walk(dir, func(path string, fi fs.FileInfo, err error) error {
282 if err != nil {
283 return err
284 }
285 if !fi.IsDir() {
286 return nil
287 }
288 top := false
289 if path == dir {
290
291
292
293
294
295
296
297
298 top = true
299 path = filepath.Clean(path)
300 }
301
302
303 _, elem := filepath.Split(path)
304 dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".."
305 if dot || strings.HasPrefix(elem, "_") || elem == "testdata" {
306 return filepath.SkipDir
307 }
308
309 if !top && cfg.ModulesEnabled {
310
311 if fi, err := fsys.Stat(filepath.Join(path, "go.mod")); err == nil && !fi.IsDir() {
312 return filepath.SkipDir
313 }
314 }
315
316 name := prefix + filepath.ToSlash(path)
317 if !match(name) {
318 return nil
319 }
320
321
322
323
324
325
326
327 if p, err := cfg.BuildContext.ImportDir(path, 0); err != nil && (p == nil || len(p.InvalidGoFiles) == 0) {
328 if _, noGo := err.(*build.NoGoError); noGo {
329
330
331 return nil
332 }
333
334
335
336 }
337 m.Dirs = append(m.Dirs, name)
338 return nil
339 })
340 if err != nil {
341 m.AddError(err)
342 }
343 }
344
345
346 func WarnUnmatched(matches []*Match) {
347 for _, m := range matches {
348 if len(m.Pkgs) == 0 && len(m.Errs) == 0 {
349 fmt.Fprintf(os.Stderr, "go: warning: %q matched no packages\n", m.pattern)
350 }
351 }
352 }
353
354
355
356 func ImportPaths(patterns, modRoots []string) []*Match {
357 matches := ImportPathsQuiet(patterns, modRoots)
358 WarnUnmatched(matches)
359 return matches
360 }
361
362
363 func ImportPathsQuiet(patterns, modRoots []string) []*Match {
364 patterns = CleanPatterns(patterns)
365 out := make([]*Match, 0, len(patterns))
366 for _, a := range patterns {
367 m := NewMatch(a)
368 if m.IsLocal() {
369 m.MatchDirs(modRoots)
370
371
372
373
374 m.Pkgs = make([]string, len(m.Dirs))
375 for i, dir := range m.Dirs {
376 absDir := dir
377 if !filepath.IsAbs(dir) {
378 absDir = filepath.Join(base.Cwd(), dir)
379 }
380 if bp, _ := cfg.BuildContext.ImportDir(absDir, build.FindOnly); bp.ImportPath != "" && bp.ImportPath != "." {
381 m.Pkgs[i] = bp.ImportPath
382 } else {
383 m.Pkgs[i] = dir
384 }
385 }
386 } else {
387 m.MatchPackages()
388 }
389
390 out = append(out, m)
391 }
392 return out
393 }
394
395
396
397
398
399 func CleanPatterns(patterns []string) []string {
400 if len(patterns) == 0 {
401 return []string{"."}
402 }
403 out := make([]string, 0, len(patterns))
404 for _, a := range patterns {
405 var p, v string
406 if build.IsLocalImport(a) || filepath.IsAbs(a) {
407 p = a
408 } else if i := strings.IndexByte(a, '@'); i < 0 {
409 p = a
410 } else {
411 p = a[:i]
412 v = a[i:]
413 }
414
415
416
417
418
419 if filepath.IsAbs(p) {
420 p = filepath.Clean(p)
421 } else {
422 if filepath.Separator == '\\' {
423 p = strings.ReplaceAll(p, `\`, `/`)
424 }
425
426
427 if strings.HasPrefix(p, "./") {
428 p = "./" + path.Clean(p)
429 if p == "./." {
430 p = "."
431 }
432 } else {
433 p = path.Clean(p)
434 }
435 }
436
437 out = append(out, p+v)
438 }
439 return out
440 }
441
442
443
444
445
446
447
448
449
450
451
452 func IsStandardImportPath(path string) bool {
453 i := strings.Index(path, "/")
454 if i < 0 {
455 i = len(path)
456 }
457 elem := path[:i]
458 return !strings.Contains(elem, ".")
459 }
460
461
462
463
464 func IsRelativePath(pattern string) bool {
465 return strings.HasPrefix(pattern, "./") || strings.HasPrefix(pattern, "../") || pattern == "." || pattern == ".."
466 }
467
468
469
470
471
472 func InDir(path, dir string) string {
473
474
475 inDirLex := func(path, dir string) (string, bool) {
476 if dir == "" {
477 return path, true
478 }
479 rel := str.TrimFilePathPrefix(path, dir)
480 if rel == path {
481 return "", false
482 }
483 if rel == "" {
484 return ".", true
485 }
486 return rel, true
487 }
488
489 if rel, ok := inDirLex(path, dir); ok {
490 return rel
491 }
492 xpath, err := filepath.EvalSymlinks(path)
493 if err != nil || xpath == path {
494 xpath = ""
495 } else {
496 if rel, ok := inDirLex(xpath, dir); ok {
497 return rel
498 }
499 }
500
501 xdir, err := filepath.EvalSymlinks(dir)
502 if err == nil && xdir != dir {
503 if rel, ok := inDirLex(path, xdir); ok {
504 return rel
505 }
506 if xpath != "" {
507 if rel, ok := inDirLex(xpath, xdir); ok {
508 return rel
509 }
510 }
511 }
512 return ""
513 }
514
View as plain text