github.com/gagliardetto/golang-go@v0.0.0-20201020153340-53909ea70814/cmd/go/not-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 "github.com/gagliardetto/golang-go/cmd/go/not-internal/base" 9 "github.com/gagliardetto/golang-go/cmd/go/not-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, "github.com/gagliardetto/golang-go/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 MatchPackages but is passed a pattern that 129 // begins with an absolute path or "./" or "../". On Windows, the pattern may 130 // use slash or backslash separators or a mix of both. 131 // 132 // MatchPackagesInFS scans the tree rooted at the directory that contains the 133 // first "..." wildcard and returns a match with packages that 134 func MatchPackagesInFS(pattern string) *Match { 135 m := &Match{ 136 Pattern: pattern, 137 Literal: false, 138 } 139 140 // Clean the path and create a matching predicate. 141 // filepath.Clean removes "./" prefixes (and ".\" on Windows). We need to 142 // preserve these, since they are meaningful in MatchPattern and in 143 // returned import paths. 144 cleanPattern := filepath.Clean(pattern) 145 isLocal := strings.HasPrefix(pattern, "./") || (os.PathSeparator == '\\' && strings.HasPrefix(pattern, `.\`)) 146 prefix := "" 147 if cleanPattern != "." && isLocal { 148 prefix = "./" 149 cleanPattern = "." + string(os.PathSeparator) + cleanPattern 150 } 151 slashPattern := filepath.ToSlash(cleanPattern) 152 match := MatchPattern(slashPattern) 153 154 // Find directory to begin the scan. 155 // Could be smarter but this one optimization 156 // is enough for now, since ... is usually at the 157 // end of a path. 158 i := strings.Index(cleanPattern, "...") 159 dir, _ := filepath.Split(cleanPattern[:i]) 160 161 // pattern begins with ./ or ../. 162 // path.Clean will discard the ./ but not the ../. 163 // We need to preserve the ./ for pattern matching 164 // and in the returned import paths. 165 166 if modRoot != "" { 167 abs, err := filepath.Abs(dir) 168 if err != nil { 169 base.Fatalf("go: %v", err) 170 } 171 if !hasFilepathPrefix(abs, modRoot) { 172 base.Fatalf("go: pattern %s refers to dir %s, outside module root %s", pattern, abs, modRoot) 173 return nil 174 } 175 } 176 177 filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error { 178 if err != nil || !fi.IsDir() { 179 return nil 180 } 181 top := false 182 if path == dir { 183 // filepath.Walk starts at dir and recurses. For the recursive case, 184 // the path is the result of filepath.Join, which calls filepath.Clean. 185 // The initial case is not Cleaned, though, so we do this explicitly. 186 // 187 // This converts a path like "./io/" to "io". Without this step, running 188 // "cd $GOROOT/src; go list ./io/..." would incorrectly skip the io 189 // package, because prepending the prefix "./" to the unclean path would 190 // result in "././io", and match("././io") returns false. 191 top = true 192 path = filepath.Clean(path) 193 } 194 195 // Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..". 196 _, elem := filepath.Split(path) 197 dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".." 198 if dot || strings.HasPrefix(elem, "_") || elem == "testdata" { 199 return filepath.SkipDir 200 } 201 202 if !top && cfg.ModulesEnabled { 203 // Ignore other modules found in subdirectories. 204 if fi, err := os.Stat(filepath.Join(path, "go.mod")); err == nil && !fi.IsDir() { 205 return filepath.SkipDir 206 } 207 } 208 209 name := prefix + filepath.ToSlash(path) 210 if !match(name) { 211 return nil 212 } 213 214 // We keep the directory if we can import it, or if we can't import it 215 // due to invalid Go source files. This means that directories containing 216 // parse errors will be built (and fail) instead of being silently skipped 217 // as not matching the pattern. Go 1.5 and earlier skipped, but that 218 // behavior means people miss serious mistakes. 219 // See golang.org/issue/11407. 220 if p, err := cfg.BuildContext.ImportDir(path, 0); err != nil && (p == nil || len(p.InvalidGoFiles) == 0) { 221 if _, noGo := err.(*build.NoGoError); !noGo { 222 log.Print(err) 223 } 224 return nil 225 } 226 m.Pkgs = append(m.Pkgs, name) 227 return nil 228 }) 229 return m 230 } 231 232 // TreeCanMatchPattern(pattern)(name) reports whether 233 // name or children of name can possibly match pattern. 234 // Pattern is the same limited glob accepted by matchPattern. 235 func TreeCanMatchPattern(pattern string) func(name string) bool { 236 wildCard := false 237 if i := strings.Index(pattern, "..."); i >= 0 { 238 wildCard = true 239 pattern = pattern[:i] 240 } 241 return func(name string) bool { 242 return len(name) <= len(pattern) && hasPathPrefix(pattern, name) || 243 wildCard && strings.HasPrefix(name, pattern) 244 } 245 } 246 247 // MatchPattern(pattern)(name) reports whether 248 // name matches pattern. Pattern is a limited glob 249 // pattern in which '...' means 'any string' and there 250 // is no other special syntax. 251 // Unfortunately, there are two special cases. Quoting "go help packages": 252 // 253 // First, /... at the end of the pattern can match an empty string, 254 // so that net/... matches both net and packages in its subdirectories, like net/http. 255 // Second, any slash-separated pattern element containing a wildcard never 256 // participates in a match of the "vendor" element in the path of a vendored 257 // package, so that ./... does not match packages in subdirectories of 258 // ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do. 259 // Note, however, that a directory named vendor that itself contains code 260 // is not a vendored package: cmd/vendor would be a command named vendor, 261 // and the pattern cmd/... matches it. 262 func MatchPattern(pattern string) func(name string) bool { 263 // Convert pattern to regular expression. 264 // The strategy for the trailing /... is to nest it in an explicit ? expression. 265 // The strategy for the vendor exclusion is to change the unmatchable 266 // vendor strings to a disallowed code point (vendorChar) and to use 267 // "(anything but that codepoint)*" as the implementation of the ... wildcard. 268 // This is a bit complicated but the obvious alternative, 269 // namely a hand-written search like in most shell glob matchers, 270 // is too easy to make accidentally exponential. 271 // Using package regexp guarantees linear-time matching. 272 273 const vendorChar = "\x00" 274 275 if strings.Contains(pattern, vendorChar) { 276 return func(name string) bool { return false } 277 } 278 279 re := regexp.QuoteMeta(pattern) 280 re = replaceVendor(re, vendorChar) 281 switch { 282 case strings.HasSuffix(re, `/`+vendorChar+`/\.\.\.`): 283 re = strings.TrimSuffix(re, `/`+vendorChar+`/\.\.\.`) + `(/vendor|/` + vendorChar + `/\.\.\.)` 284 case re == vendorChar+`/\.\.\.`: 285 re = `(/vendor|/` + vendorChar + `/\.\.\.)` 286 case strings.HasSuffix(re, `/\.\.\.`): 287 re = strings.TrimSuffix(re, `/\.\.\.`) + `(/\.\.\.)?` 288 } 289 re = strings.ReplaceAll(re, `\.\.\.`, `[^`+vendorChar+`]*`) 290 291 reg := regexp.MustCompile(`^` + re + `$`) 292 293 return func(name string) bool { 294 if strings.Contains(name, vendorChar) { 295 return false 296 } 297 return reg.MatchString(replaceVendor(name, vendorChar)) 298 } 299 } 300 301 // replaceVendor returns the result of replacing 302 // non-trailing vendor path elements in x with repl. 303 func replaceVendor(x, repl string) string { 304 if !strings.Contains(x, "vendor") { 305 return x 306 } 307 elem := strings.Split(x, "/") 308 for i := 0; i < len(elem)-1; i++ { 309 if elem[i] == "vendor" { 310 elem[i] = repl 311 } 312 } 313 return strings.Join(elem, "/") 314 } 315 316 // WarnUnmatched warns about patterns that didn't match any packages. 317 func WarnUnmatched(matches []*Match) { 318 for _, m := range matches { 319 if len(m.Pkgs) == 0 { 320 fmt.Fprintf(os.Stderr, "go: warning: %q matched no packages\n", m.Pattern) 321 } 322 } 323 } 324 325 // ImportPaths returns the matching paths to use for the given command line. 326 // It calls ImportPathsQuiet and then WarnUnmatched. 327 func ImportPaths(patterns []string) []*Match { 328 matches := ImportPathsQuiet(patterns) 329 WarnUnmatched(matches) 330 return matches 331 } 332 333 // ImportPathsQuiet is like ImportPaths but does not warn about patterns with no matches. 334 func ImportPathsQuiet(patterns []string) []*Match { 335 var out []*Match 336 for _, a := range CleanPatterns(patterns) { 337 if IsMetaPackage(a) { 338 out = append(out, MatchPackages(a)) 339 continue 340 } 341 342 if build.IsLocalImport(a) || filepath.IsAbs(a) { 343 var m *Match 344 if strings.Contains(a, "...") { 345 m = MatchPackagesInFS(a) 346 } else { 347 m = &Match{Pattern: a, Literal: true, Pkgs: []string{a}} 348 } 349 350 // Change the file import path to a regular import path if the package 351 // is in GOPATH or GOROOT. We don't report errors here; LoadImport 352 // (or something similar) will report them later. 353 for i, dir := range m.Pkgs { 354 if !filepath.IsAbs(dir) { 355 dir = filepath.Join(base.Cwd, dir) 356 } 357 if bp, _ := cfg.BuildContext.ImportDir(dir, build.FindOnly); bp.ImportPath != "" && bp.ImportPath != "." { 358 m.Pkgs[i] = bp.ImportPath 359 } 360 } 361 out = append(out, m) 362 continue 363 } 364 365 if strings.Contains(a, "...") { 366 out = append(out, MatchPackages(a)) 367 continue 368 } 369 370 out = append(out, &Match{Pattern: a, Literal: true, Pkgs: []string{a}}) 371 } 372 return out 373 } 374 375 // CleanPatterns returns the patterns to use for the given command line. It 376 // canonicalizes the patterns but does not evaluate any matches. For patterns 377 // that are not local or absolute paths, it preserves text after '@' to avoid 378 // modifying version queries. 379 func CleanPatterns(patterns []string) []string { 380 if len(patterns) == 0 { 381 return []string{"."} 382 } 383 var out []string 384 for _, a := range patterns { 385 var p, v string 386 if build.IsLocalImport(a) || filepath.IsAbs(a) { 387 p = a 388 } else if i := strings.IndexByte(a, '@'); i < 0 { 389 p = a 390 } else { 391 p = a[:i] 392 v = a[i:] 393 } 394 395 // Arguments may be either file paths or import paths. 396 // As a courtesy to Windows developers, rewrite \ to / 397 // in arguments that look like import paths. 398 // Don't replace slashes in absolute paths. 399 if filepath.IsAbs(p) { 400 p = filepath.Clean(p) 401 } else { 402 if filepath.Separator == '\\' { 403 p = strings.ReplaceAll(p, `\`, `/`) 404 } 405 406 // Put argument in canonical form, but preserve leading ./. 407 if strings.HasPrefix(p, "./") { 408 p = "./" + path.Clean(p) 409 if p == "./." { 410 p = "." 411 } 412 } else { 413 p = path.Clean(p) 414 } 415 } 416 417 out = append(out, p+v) 418 } 419 return out 420 } 421 422 // IsMetaPackage checks if name is a reserved package name that expands to multiple packages. 423 func IsMetaPackage(name string) bool { 424 return name == "std" || name == "cmd" || name == "all" 425 } 426 427 // hasPathPrefix reports whether the path s begins with the 428 // elements in prefix. 429 func hasPathPrefix(s, prefix string) bool { 430 switch { 431 default: 432 return false 433 case len(s) == len(prefix): 434 return s == prefix 435 case len(s) > len(prefix): 436 if prefix != "" && prefix[len(prefix)-1] == '/' { 437 return strings.HasPrefix(s, prefix) 438 } 439 return s[len(prefix)] == '/' && s[:len(prefix)] == prefix 440 } 441 } 442 443 // hasFilepathPrefix reports whether the path s begins with the 444 // elements in prefix. 445 func hasFilepathPrefix(s, prefix string) bool { 446 switch { 447 default: 448 return false 449 case len(s) == len(prefix): 450 return s == prefix 451 case len(s) > len(prefix): 452 if prefix != "" && prefix[len(prefix)-1] == filepath.Separator { 453 return strings.HasPrefix(s, prefix) 454 } 455 return s[len(prefix)] == filepath.Separator && s[:len(prefix)] == prefix 456 } 457 } 458 459 // IsStandardImportPath reports whether $GOROOT/src/path should be considered 460 // part of the standard distribution. For historical reasons we allow people to add 461 // their own code to $GOROOT instead of using $GOPATH, but we assume that 462 // code will start with a domain name (dot in the first element). 463 // 464 // Note that this function is meant to evaluate whether a directory found in GOROOT 465 // should be treated as part of the standard library. It should not be used to decide 466 // that a directory found in GOPATH should be rejected: directories in GOPATH 467 // need not have dots in the first element, and they just take their chances 468 // with future collisions in the standard library. 469 func IsStandardImportPath(path string) bool { 470 i := strings.Index(path, "/") 471 if i < 0 { 472 i = len(path) 473 } 474 elem := path[:i] 475 return !strings.Contains(elem, ".") 476 } 477 478 // IsRelativePath reports whether pattern should be interpreted as a directory 479 // path relative to the current directory, as opposed to a pattern matching 480 // import paths. 481 func IsRelativePath(pattern string) bool { 482 return strings.HasPrefix(pattern, "./") || strings.HasPrefix(pattern, "../") || pattern == "." || pattern == ".." 483 } 484 485 // InDir checks whether path is in the file tree rooted at dir. 486 // If so, InDir returns an equivalent path relative to dir. 487 // If not, InDir returns an empty string. 488 // InDir makes some effort to succeed even in the presence of symbolic links. 489 // TODO(rsc): Replace internal/test.inDir with a call to this function for Go 1.12. 490 func InDir(path, dir string) string { 491 if rel := inDirLex(path, dir); rel != "" { 492 return rel 493 } 494 xpath, err := filepath.EvalSymlinks(path) 495 if err != nil || xpath == path { 496 xpath = "" 497 } else { 498 if rel := inDirLex(xpath, dir); rel != "" { 499 return rel 500 } 501 } 502 503 xdir, err := filepath.EvalSymlinks(dir) 504 if err == nil && xdir != dir { 505 if rel := inDirLex(path, xdir); rel != "" { 506 return rel 507 } 508 if xpath != "" { 509 if rel := inDirLex(xpath, xdir); rel != "" { 510 return rel 511 } 512 } 513 } 514 return "" 515 } 516 517 // inDirLex is like inDir but only checks the lexical form of the file names. 518 // It does not consider symbolic links. 519 // TODO(rsc): This is a copy of str.HasFilePathPrefix, modified to 520 // return the suffix. Most uses of str.HasFilePathPrefix should probably 521 // be calling InDir instead. 522 func inDirLex(path, dir string) string { 523 pv := strings.ToUpper(filepath.VolumeName(path)) 524 dv := strings.ToUpper(filepath.VolumeName(dir)) 525 path = path[len(pv):] 526 dir = dir[len(dv):] 527 switch { 528 default: 529 return "" 530 case pv != dv: 531 return "" 532 case len(path) == len(dir): 533 if path == dir { 534 return "." 535 } 536 return "" 537 case dir == "": 538 return path 539 case len(path) > len(dir): 540 if dir[len(dir)-1] == filepath.Separator { 541 if path[:len(dir)] == dir { 542 return path[len(dir):] 543 } 544 return "" 545 } 546 if path[len(dir)] == filepath.Separator && path[:len(dir)] == dir { 547 if len(path) == len(dir)+1 { 548 return "." 549 } 550 return path[len(dir)+1:] 551 } 552 return "" 553 } 554 }