github.com/zxy12/go_duplicate_112_new@v0.0.0-20200807091221-747231827200/src/cmd/go/internal/search/search.go (about) 1 // Copyright 2017 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 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 // A Match represents the result of matching a single package pattern. 21 type Match struct { 22 Pattern string // the pattern itself 23 Literal bool // whether it is a literal (no wildcards) 24 Pkgs []string // matching packages (dirs or import paths) 25 } 26 27 // MatchPackages returns all the packages that can be found 28 // under the $GOPATH directories and $GOROOT matching pattern. 29 // The pattern is either "all" (all packages), "std" (standard packages), 30 // "cmd" (standard commands), or a path including "...". 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, // ignore pseudo-package that exists only for documentation 45 } 46 if !cfg.BuildContext.CgoEnabled { 47 have["runtime/cgo"] = true // ignore during walk 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 // Avoid .foo, _foo, and testdata directory trees. 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 // The name "std" is only the standard library. 74 // If the name is cmd, it's the root of the command tree. 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 // If we are expanding "cmd", skip main 108 // packages under cmd/vendor. At least as of 109 // March, 2017, there is one there for the 110 // vendored pprof tool. 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 // MatchPackagesInFS is like allPackages but is passed a pattern 129 // beginning ./ or ../, meaning it should scan the tree rooted 130 // at the given directory. There are ... in the pattern too. 131 // (See go help packages for pattern syntax.) 132 func MatchPackagesInFS(pattern string) *Match { 133 m := &Match{ 134 Pattern: pattern, 135 Literal: false, 136 } 137 138 // Find directory to begin the scan. 139 // Could be smarter but this one optimization 140 // is enough for now, since ... is usually at the 141 // end of a path. 142 i := strings.Index(pattern, "...") 143 dir, _ := path.Split(pattern[:i]) 144 145 // pattern begins with ./ or ../. 146 // path.Clean will discard the ./ but not the ../. 147 // We need to preserve the ./ for pattern matching 148 // and in the returned import paths. 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 // filepath.Walk starts at dir and recurses. For the recursive case, 173 // the path is the result of filepath.Join, which calls filepath.Clean. 174 // The initial case is not Cleaned, though, so we do this explicitly. 175 // 176 // This converts a path like "./io/" to "io". Without this step, running 177 // "cd $GOROOT/src; go list ./io/..." would incorrectly skip the io 178 // package, because prepending the prefix "./" to the unclean path would 179 // result in "././io", and match("././io") returns false. 180 top = true 181 path = filepath.Clean(path) 182 } 183 184 // Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..". 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 // Ignore other modules found in subdirectories. 193 if _, err := os.Stat(filepath.Join(path, "go.mod")); err == nil { 194 return filepath.SkipDir 195 } 196 } 197 198 name := prefix + filepath.ToSlash(path) 199 if !match(name) { 200 return nil 201 } 202 203 // We keep the directory if we can import it, or if we can't import it 204 // due to invalid Go source files. This means that directories containing 205 // parse errors will be built (and fail) instead of being silently skipped 206 // as not matching the pattern. Go 1.5 and earlier skipped, but that 207 // behavior means people miss serious mistakes. 208 // See golang.org/issue/11407. 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 // TreeCanMatchPattern(pattern)(name) reports whether 222 // name or children of name can possibly match pattern. 223 // Pattern is the same limited glob accepted by matchPattern. 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 // MatchPattern(pattern)(name) reports whether 237 // name matches pattern. Pattern is a limited glob 238 // pattern in which '...' means 'any string' and there 239 // is no other special syntax. 240 // Unfortunately, there are two special cases. Quoting "go help packages": 241 // 242 // First, /... at the end of the pattern can match an empty string, 243 // so that net/... matches both net and packages in its subdirectories, like net/http. 244 // Second, any slash-separted pattern element containing a wildcard never 245 // participates in a match of the "vendor" element in the path of a vendored 246 // package, so that ./... does not match packages in subdirectories of 247 // ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do. 248 // Note, however, that a directory named vendor that itself contains code 249 // is not a vendored package: cmd/vendor would be a command named vendor, 250 // and the pattern cmd/... matches it. 251 func MatchPattern(pattern string) func(name string) bool { 252 // Convert pattern to regular expression. 253 // The strategy for the trailing /... is to nest it in an explicit ? expression. 254 // The strategy for the vendor exclusion is to change the unmatchable 255 // vendor strings to a disallowed code point (vendorChar) and to use 256 // "(anything but that codepoint)*" as the implementation of the ... wildcard. 257 // This is a bit complicated but the obvious alternative, 258 // namely a hand-written search like in most shell glob matchers, 259 // is too easy to make accidentally exponential. 260 // Using package regexp guarantees linear-time matching. 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 // replaceVendor returns the result of replacing 291 // non-trailing vendor path elements in x with repl. 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 // WarnUnmatched warns about patterns that didn't match any packages. 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 // ImportPaths returns the matching paths to use for the given command line. 315 // It calls ImportPathsQuiet and then WarnUnmatched. 316 func ImportPaths(patterns []string) []*Match { 317 matches := ImportPathsQuiet(patterns) 318 WarnUnmatched(matches) 319 return matches 320 } 321 322 // ImportPathsQuiet is like ImportPaths but does not warn about patterns with no matches. 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 if strings.Contains(a, "...") { 331 if build.IsLocalImport(a) { 332 out = append(out, MatchPackagesInFS(a)) 333 } else { 334 out = append(out, MatchPackages(a)) 335 } 336 continue 337 } 338 out = append(out, &Match{Pattern: a, Literal: true, Pkgs: []string{a}}) 339 } 340 return out 341 } 342 343 // CleanPatterns returns the patterns to use for the given 344 // command line. It canonicalizes the patterns but does not 345 // evaluate any matches. 346 func CleanPatterns(patterns []string) []string { 347 if len(patterns) == 0 { 348 return []string{"."} 349 } 350 var out []string 351 for _, a := range patterns { 352 // Arguments are supposed to be import paths, but 353 // as a courtesy to Windows developers, rewrite \ to / 354 // in command-line arguments. Handles .\... and so on. 355 if filepath.Separator == '\\' { 356 a = strings.ReplaceAll(a, `\`, `/`) 357 } 358 359 // Put argument in canonical form, but preserve leading ./. 360 if strings.HasPrefix(a, "./") { 361 a = "./" + path.Clean(a) 362 if a == "./." { 363 a = "." 364 } 365 } else { 366 a = path.Clean(a) 367 } 368 out = append(out, a) 369 } 370 return out 371 } 372 373 // IsMetaPackage checks if name is a reserved package name that expands to multiple packages. 374 func IsMetaPackage(name string) bool { 375 return name == "std" || name == "cmd" || name == "all" 376 } 377 378 // hasPathPrefix reports whether the path s begins with the 379 // elements in prefix. 380 func hasPathPrefix(s, prefix string) bool { 381 switch { 382 default: 383 return false 384 case len(s) == len(prefix): 385 return s == prefix 386 case len(s) > len(prefix): 387 if prefix != "" && prefix[len(prefix)-1] == '/' { 388 return strings.HasPrefix(s, prefix) 389 } 390 return s[len(prefix)] == '/' && s[:len(prefix)] == prefix 391 } 392 } 393 394 // hasFilepathPrefix reports whether the path s begins with the 395 // elements in prefix. 396 func hasFilepathPrefix(s, prefix string) bool { 397 switch { 398 default: 399 return false 400 case len(s) == len(prefix): 401 return s == prefix 402 case len(s) > len(prefix): 403 if prefix != "" && prefix[len(prefix)-1] == filepath.Separator { 404 return strings.HasPrefix(s, prefix) 405 } 406 return s[len(prefix)] == filepath.Separator && s[:len(prefix)] == prefix 407 } 408 } 409 410 // IsStandardImportPath reports whether $GOROOT/src/path should be considered 411 // part of the standard distribution. For historical reasons we allow people to add 412 // their own code to $GOROOT instead of using $GOPATH, but we assume that 413 // code will start with a domain name (dot in the first element). 414 // 415 // Note that this function is meant to evaluate whether a directory found in GOROOT 416 // should be treated as part of the standard library. It should not be used to decide 417 // that a directory found in GOPATH should be rejected: directories in GOPATH 418 // need not have dots in the first element, and they just take their chances 419 // with future collisions in the standard library. 420 func IsStandardImportPath(path string) bool { 421 i := strings.Index(path, "/") 422 if i < 0 { 423 i = len(path) 424 } 425 elem := path[:i] 426 return !strings.Contains(elem, ".") 427 } 428 429 // IsRelativePath reports whether pattern should be interpreted as a directory 430 // path relative to the current directory, as opposed to a pattern matching 431 // import paths. 432 func IsRelativePath(pattern string) bool { 433 return strings.HasPrefix(pattern, "./") || strings.HasPrefix(pattern, "../") || pattern == "." || pattern == ".." 434 } 435 436 // InDir checks whether path is in the file tree rooted at dir. 437 // If so, InDir returns an equivalent path relative to dir. 438 // If not, InDir returns an empty string. 439 // InDir makes some effort to succeed even in the presence of symbolic links. 440 // TODO(rsc): Replace internal/test.inDir with a call to this function for Go 1.12. 441 func InDir(path, dir string) string { 442 if rel := inDirLex(path, dir); rel != "" { 443 return rel 444 } 445 xpath, err := filepath.EvalSymlinks(path) 446 if err != nil || xpath == path { 447 xpath = "" 448 } else { 449 if rel := inDirLex(xpath, dir); rel != "" { 450 return rel 451 } 452 } 453 454 xdir, err := filepath.EvalSymlinks(dir) 455 if err == nil && xdir != dir { 456 if rel := inDirLex(path, xdir); rel != "" { 457 return rel 458 } 459 if xpath != "" { 460 if rel := inDirLex(xpath, xdir); rel != "" { 461 return rel 462 } 463 } 464 } 465 return "" 466 } 467 468 // inDirLex is like inDir but only checks the lexical form of the file names. 469 // It does not consider symbolic links. 470 // TODO(rsc): This is a copy of str.HasFilePathPrefix, modified to 471 // return the suffix. Most uses of str.HasFilePathPrefix should probably 472 // be calling InDir instead. 473 func inDirLex(path, dir string) string { 474 pv := strings.ToUpper(filepath.VolumeName(path)) 475 dv := strings.ToUpper(filepath.VolumeName(dir)) 476 path = path[len(pv):] 477 dir = dir[len(dv):] 478 switch { 479 default: 480 return "" 481 case pv != dv: 482 return "" 483 case len(path) == len(dir): 484 if path == dir { 485 return "." 486 } 487 return "" 488 case dir == "": 489 return path 490 case len(path) > len(dir): 491 if dir[len(dir)-1] == filepath.Separator { 492 if path[:len(dir)] == dir { 493 return path[len(dir):] 494 } 495 return "" 496 } 497 if path[len(dir)] == filepath.Separator && path[:len(dir)] == dir { 498 if len(path) == len(dir)+1 { 499 return "." 500 } 501 return path[len(dir)+1:] 502 } 503 return "" 504 } 505 }