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