github.com/corona10/go@v0.0.0-20180224231303-7a218942be57/src/cmd/go/internal/load/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 load 6 7 import ( 8 "cmd/go/internal/cfg" 9 "fmt" 10 "go/build" 11 "log" 12 "os" 13 "path" 14 "path/filepath" 15 "regexp" 16 "strings" 17 ) 18 19 // allPackages returns all the packages that can be found 20 // under the $GOPATH directories and $GOROOT matching pattern. 21 // The pattern is either "all" (all packages), "std" (standard packages), 22 // "cmd" (standard commands), or a path including "...". 23 func allPackages(pattern string) []string { 24 pkgs := MatchPackages(pattern) 25 if len(pkgs) == 0 { 26 fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern) 27 } 28 return pkgs 29 } 30 31 // allPackagesInFS is like allPackages but is passed a pattern 32 // beginning ./ or ../, meaning it should scan the tree rooted 33 // at the given directory. There are ... in the pattern too. 34 func allPackagesInFS(pattern string) []string { 35 pkgs := MatchPackagesInFS(pattern) 36 if len(pkgs) == 0 { 37 fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern) 38 } 39 return pkgs 40 } 41 42 // MatchPackages returns a list of package paths matching pattern 43 // (see go help packages for pattern syntax). 44 func MatchPackages(pattern string) []string { 45 match := func(string) bool { return true } 46 treeCanMatch := func(string) bool { return true } 47 if !IsMetaPackage(pattern) { 48 match = matchPattern(pattern) 49 treeCanMatch = treeCanMatchPattern(pattern) 50 } 51 52 have := map[string]bool{ 53 "builtin": true, // ignore pseudo-package that exists only for documentation 54 } 55 if !cfg.BuildContext.CgoEnabled { 56 have["runtime/cgo"] = true // ignore during walk 57 } 58 var pkgs []string 59 60 for _, src := range cfg.BuildContext.SrcDirs() { 61 if (pattern == "std" || pattern == "cmd") && src != cfg.GOROOTsrc { 62 continue 63 } 64 src = filepath.Clean(src) + string(filepath.Separator) 65 root := src 66 if pattern == "cmd" { 67 root += "cmd" + string(filepath.Separator) 68 } 69 filepath.Walk(root, func(path string, fi os.FileInfo, err error) error { 70 if err != nil || path == src { 71 return nil 72 } 73 74 want := true 75 // Avoid .foo, _foo, and testdata directory trees. 76 _, elem := filepath.Split(path) 77 if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" { 78 want = false 79 } 80 81 name := filepath.ToSlash(path[len(src):]) 82 if pattern == "std" && (!isStandardImportPath(name) || name == "cmd") { 83 // The name "std" is only the standard library. 84 // If the name is cmd, it's the root of the command tree. 85 want = false 86 } 87 if !treeCanMatch(name) { 88 want = false 89 } 90 91 if !fi.IsDir() { 92 if fi.Mode()&os.ModeSymlink != 0 && want { 93 if target, err := os.Stat(path); err == nil && target.IsDir() { 94 fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path) 95 } 96 } 97 return nil 98 } 99 if !want { 100 return filepath.SkipDir 101 } 102 103 if have[name] { 104 return nil 105 } 106 have[name] = true 107 if !match(name) { 108 return nil 109 } 110 pkg, err := cfg.BuildContext.ImportDir(path, 0) 111 if err != nil { 112 if _, noGo := err.(*build.NoGoError); noGo { 113 return nil 114 } 115 } 116 117 // If we are expanding "cmd", skip main 118 // packages under cmd/vendor. At least as of 119 // March, 2017, there is one there for the 120 // vendored pprof tool. 121 if pattern == "cmd" && strings.HasPrefix(pkg.ImportPath, "cmd/vendor") && pkg.Name == "main" { 122 return nil 123 } 124 125 pkgs = append(pkgs, name) 126 return nil 127 }) 128 } 129 return pkgs 130 } 131 132 // MatchPackagesInFS returns a list of package paths matching pattern, 133 // which must begin with ./ or ../ 134 // (see go help packages for pattern syntax). 135 func MatchPackagesInFS(pattern string) []string { 136 // Find directory to begin the scan. 137 // Could be smarter but this one optimization 138 // is enough for now, since ... is usually at the 139 // end of a path. 140 i := strings.Index(pattern, "...") 141 dir, _ := path.Split(pattern[:i]) 142 143 // pattern begins with ./ or ../. 144 // path.Clean will discard the ./ but not the ../. 145 // We need to preserve the ./ for pattern matching 146 // and in the returned import paths. 147 prefix := "" 148 if strings.HasPrefix(pattern, "./") { 149 prefix = "./" 150 } 151 match := matchPattern(pattern) 152 153 var pkgs []string 154 filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error { 155 if err != nil || !fi.IsDir() { 156 return nil 157 } 158 if path == dir { 159 // filepath.Walk starts at dir and recurses. For the recursive case, 160 // the path is the result of filepath.Join, which calls filepath.Clean. 161 // The initial case is not Cleaned, though, so we do this explicitly. 162 // 163 // This converts a path like "./io/" to "io". Without this step, running 164 // "cd $GOROOT/src; go list ./io/..." would incorrectly skip the io 165 // package, because prepending the prefix "./" to the unclean path would 166 // result in "././io", and match("././io") returns false. 167 path = filepath.Clean(path) 168 } 169 170 // Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..". 171 _, elem := filepath.Split(path) 172 dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".." 173 if dot || strings.HasPrefix(elem, "_") || elem == "testdata" { 174 return filepath.SkipDir 175 } 176 177 name := prefix + filepath.ToSlash(path) 178 if !match(name) { 179 return nil 180 } 181 182 // We keep the directory if we can import it, or if we can't import it 183 // due to invalid Go source files. This means that directories containing 184 // parse errors will be built (and fail) instead of being silently skipped 185 // as not matching the pattern. Go 1.5 and earlier skipped, but that 186 // behavior means people miss serious mistakes. 187 // See golang.org/issue/11407. 188 if p, err := cfg.BuildContext.ImportDir(path, 0); err != nil && (p == nil || len(p.InvalidGoFiles) == 0) { 189 if _, noGo := err.(*build.NoGoError); !noGo { 190 log.Print(err) 191 } 192 return nil 193 } 194 pkgs = append(pkgs, name) 195 return nil 196 }) 197 return pkgs 198 } 199 200 // treeCanMatchPattern(pattern)(name) reports whether 201 // name or children of name can possibly match pattern. 202 // Pattern is the same limited glob accepted by matchPattern. 203 func treeCanMatchPattern(pattern string) func(name string) bool { 204 wildCard := false 205 if i := strings.Index(pattern, "..."); i >= 0 { 206 wildCard = true 207 pattern = pattern[:i] 208 } 209 return func(name string) bool { 210 return len(name) <= len(pattern) && hasPathPrefix(pattern, name) || 211 wildCard && strings.HasPrefix(name, pattern) 212 } 213 } 214 215 // matchPattern(pattern)(name) reports whether 216 // name matches pattern. Pattern is a limited glob 217 // pattern in which '...' means 'any string' and there 218 // is no other special syntax. 219 // Unfortunately, there are two special cases. Quoting "go help packages": 220 // 221 // First, /... at the end of the pattern can match an empty string, 222 // so that net/... matches both net and packages in its subdirectories, like net/http. 223 // Second, any slash-separted pattern element containing a wildcard never 224 // participates in a match of the "vendor" element in the path of a vendored 225 // package, so that ./... does not match packages in subdirectories of 226 // ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do. 227 // Note, however, that a directory named vendor that itself contains code 228 // is not a vendored package: cmd/vendor would be a command named vendor, 229 // and the pattern cmd/... matches it. 230 func matchPattern(pattern string) func(name string) bool { 231 // Convert pattern to regular expression. 232 // The strategy for the trailing /... is to nest it in an explicit ? expression. 233 // The strategy for the vendor exclusion is to change the unmatchable 234 // vendor strings to a disallowed code point (vendorChar) and to use 235 // "(anything but that codepoint)*" as the implementation of the ... wildcard. 236 // This is a bit complicated but the obvious alternative, 237 // namely a hand-written search like in most shell glob matchers, 238 // is too easy to make accidentally exponential. 239 // Using package regexp guarantees linear-time matching. 240 241 const vendorChar = "\x00" 242 243 if strings.Contains(pattern, vendorChar) { 244 return func(name string) bool { return false } 245 } 246 247 re := regexp.QuoteMeta(pattern) 248 re = replaceVendor(re, vendorChar) 249 switch { 250 case strings.HasSuffix(re, `/`+vendorChar+`/\.\.\.`): 251 re = strings.TrimSuffix(re, `/`+vendorChar+`/\.\.\.`) + `(/vendor|/` + vendorChar + `/\.\.\.)` 252 case re == vendorChar+`/\.\.\.`: 253 re = `(/vendor|/` + vendorChar + `/\.\.\.)` 254 case strings.HasSuffix(re, `/\.\.\.`): 255 re = strings.TrimSuffix(re, `/\.\.\.`) + `(/\.\.\.)?` 256 } 257 re = strings.Replace(re, `\.\.\.`, `[^`+vendorChar+`]*`, -1) 258 259 reg := regexp.MustCompile(`^` + re + `$`) 260 261 return func(name string) bool { 262 if strings.Contains(name, vendorChar) { 263 return false 264 } 265 return reg.MatchString(replaceVendor(name, vendorChar)) 266 } 267 } 268 269 // MatchPackage(pattern, cwd)(p) reports whether package p matches pattern in the working directory cwd. 270 func MatchPackage(pattern, cwd string) func(*Package) bool { 271 switch { 272 case strings.HasPrefix(pattern, "./") || strings.HasPrefix(pattern, "../") || pattern == "." || pattern == "..": 273 // Split pattern into leading pattern-free directory path 274 // (including all . and .. elements) and the final pattern. 275 var dir string 276 i := strings.Index(pattern, "...") 277 if i < 0 { 278 dir, pattern = pattern, "" 279 } else { 280 j := strings.LastIndex(pattern[:i], "/") 281 dir, pattern = pattern[:j], pattern[j+1:] 282 } 283 dir = filepath.Join(cwd, dir) 284 if pattern == "" { 285 return func(p *Package) bool { return p.Dir == dir } 286 } 287 matchPath := matchPattern(pattern) 288 return func(p *Package) bool { 289 // Compute relative path to dir and see if it matches the pattern. 290 rel, err := filepath.Rel(dir, p.Dir) 291 if err != nil { 292 // Cannot make relative - e.g. different drive letters on Windows. 293 return false 294 } 295 rel = filepath.ToSlash(rel) 296 if rel == ".." || strings.HasPrefix(rel, "../") { 297 return false 298 } 299 return matchPath(rel) 300 } 301 case pattern == "all": 302 return func(p *Package) bool { return true } 303 case pattern == "std": 304 return func(p *Package) bool { return p.Standard } 305 case pattern == "cmd": 306 return func(p *Package) bool { return p.Standard && strings.HasPrefix(p.ImportPath, "cmd/") } 307 default: 308 matchPath := matchPattern(pattern) 309 return func(p *Package) bool { return matchPath(p.ImportPath) } 310 } 311 } 312 313 // replaceVendor returns the result of replacing 314 // non-trailing vendor path elements in x with repl. 315 func replaceVendor(x, repl string) string { 316 if !strings.Contains(x, "vendor") { 317 return x 318 } 319 elem := strings.Split(x, "/") 320 for i := 0; i < len(elem)-1; i++ { 321 if elem[i] == "vendor" { 322 elem[i] = repl 323 } 324 } 325 return strings.Join(elem, "/") 326 } 327 328 // ImportPaths returns the import paths to use for the given command line. 329 func ImportPaths(args []string) []string { 330 args = ImportPathsNoDotExpansion(args) 331 var out []string 332 for _, a := range args { 333 if strings.Contains(a, "...") { 334 if build.IsLocalImport(a) { 335 out = append(out, allPackagesInFS(a)...) 336 } else { 337 out = append(out, allPackages(a)...) 338 } 339 continue 340 } 341 out = append(out, a) 342 } 343 return out 344 } 345 346 // ImportPathsNoDotExpansion returns the import paths to use for the given 347 // command line, but it does no ... expansion. 348 func ImportPathsNoDotExpansion(args []string) []string { 349 if cmdlineMatchers == nil { 350 SetCmdlinePatterns(args) 351 } 352 if len(args) == 0 { 353 return []string{"."} 354 } 355 var out []string 356 for _, a := range args { 357 // Arguments are supposed to be import paths, but 358 // as a courtesy to Windows developers, rewrite \ to / 359 // in command-line arguments. Handles .\... and so on. 360 if filepath.Separator == '\\' { 361 a = strings.Replace(a, `\`, `/`, -1) 362 } 363 364 // Put argument in canonical form, but preserve leading ./. 365 if strings.HasPrefix(a, "./") { 366 a = "./" + path.Clean(a) 367 if a == "./." { 368 a = "." 369 } 370 } else { 371 a = path.Clean(a) 372 } 373 if IsMetaPackage(a) { 374 out = append(out, allPackages(a)...) 375 continue 376 } 377 out = append(out, a) 378 } 379 return out 380 } 381 382 // IsMetaPackage checks if name is a reserved package name that expands to multiple packages. 383 func IsMetaPackage(name string) bool { 384 return name == "std" || name == "cmd" || name == "all" 385 }