github.com/sanprasirt/go@v0.0.0-20170607001320-a027466e4b6d/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 || !fi.IsDir() || path == src { 71 return nil 72 } 73 74 // Avoid .foo, _foo, and testdata directory trees. 75 _, elem := filepath.Split(path) 76 if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" { 77 return filepath.SkipDir 78 } 79 80 name := filepath.ToSlash(path[len(src):]) 81 if pattern == "std" && (!isStandardImportPath(name) || name == "cmd") { 82 // The name "std" is only the standard library. 83 // If the name is cmd, it's the root of the command tree. 84 return filepath.SkipDir 85 } 86 if !treeCanMatch(name) { 87 return filepath.SkipDir 88 } 89 if have[name] { 90 return nil 91 } 92 have[name] = true 93 if !match(name) { 94 return nil 95 } 96 pkg, err := cfg.BuildContext.ImportDir(path, 0) 97 if err != nil { 98 if _, noGo := err.(*build.NoGoError); noGo { 99 return nil 100 } 101 } 102 103 // If we are expanding "cmd", skip main 104 // packages under cmd/vendor. At least as of 105 // March, 2017, there is one there for the 106 // vendored pprof tool. 107 if pattern == "cmd" && strings.HasPrefix(pkg.ImportPath, "cmd/vendor") && pkg.Name == "main" { 108 return nil 109 } 110 111 pkgs = append(pkgs, name) 112 return nil 113 }) 114 } 115 return pkgs 116 } 117 118 // MatchPackagesInFS returns a list of package paths matching pattern, 119 // which must begin with ./ or ../ 120 // (see go help packages for pattern syntax). 121 func MatchPackagesInFS(pattern string) []string { 122 // Find directory to begin the scan. 123 // Could be smarter but this one optimization 124 // is enough for now, since ... is usually at the 125 // end of a path. 126 i := strings.Index(pattern, "...") 127 dir, _ := path.Split(pattern[:i]) 128 129 // pattern begins with ./ or ../. 130 // path.Clean will discard the ./ but not the ../. 131 // We need to preserve the ./ for pattern matching 132 // and in the returned import paths. 133 prefix := "" 134 if strings.HasPrefix(pattern, "./") { 135 prefix = "./" 136 } 137 match := matchPattern(pattern) 138 139 var pkgs []string 140 filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error { 141 if err != nil || !fi.IsDir() { 142 return nil 143 } 144 if path == dir { 145 // filepath.Walk starts at dir and recurses. For the recursive case, 146 // the path is the result of filepath.Join, which calls filepath.Clean. 147 // The initial case is not Cleaned, though, so we do this explicitly. 148 // 149 // This converts a path like "./io/" to "io". Without this step, running 150 // "cd $GOROOT/src; go list ./io/..." would incorrectly skip the io 151 // package, because prepending the prefix "./" to the unclean path would 152 // result in "././io", and match("././io") returns false. 153 path = filepath.Clean(path) 154 } 155 156 // Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..". 157 _, elem := filepath.Split(path) 158 dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".." 159 if dot || strings.HasPrefix(elem, "_") || elem == "testdata" { 160 return filepath.SkipDir 161 } 162 163 name := prefix + filepath.ToSlash(path) 164 if !match(name) { 165 return nil 166 } 167 168 // We keep the directory if we can import it, or if we can't import it 169 // due to invalid Go source files. This means that directories containing 170 // parse errors will be built (and fail) instead of being silently skipped 171 // as not matching the pattern. Go 1.5 and earlier skipped, but that 172 // behavior means people miss serious mistakes. 173 // See golang.org/issue/11407. 174 if p, err := cfg.BuildContext.ImportDir(path, 0); err != nil && (p == nil || len(p.InvalidGoFiles) == 0) { 175 if _, noGo := err.(*build.NoGoError); !noGo { 176 log.Print(err) 177 } 178 return nil 179 } 180 pkgs = append(pkgs, name) 181 return nil 182 }) 183 return pkgs 184 } 185 186 // treeCanMatchPattern(pattern)(name) reports whether 187 // name or children of name can possibly match pattern. 188 // Pattern is the same limited glob accepted by matchPattern. 189 func treeCanMatchPattern(pattern string) func(name string) bool { 190 wildCard := false 191 if i := strings.Index(pattern, "..."); i >= 0 { 192 wildCard = true 193 pattern = pattern[:i] 194 } 195 return func(name string) bool { 196 return len(name) <= len(pattern) && hasPathPrefix(pattern, name) || 197 wildCard && strings.HasPrefix(name, pattern) 198 } 199 } 200 201 // matchPattern(pattern)(name) reports whether 202 // name matches pattern. Pattern is a limited glob 203 // pattern in which '...' means 'any string' and there 204 // is no other special syntax. 205 // Unfortunately, there are two special cases. Quoting "go help packages": 206 // 207 // First, /... at the end of the pattern can match an empty string, 208 // so that net/... matches both net and packages in its subdirectories, like net/http. 209 // Second, any slash-separted pattern element containing a wildcard never 210 // participates in a match of the "vendor" element in the path of a vendored 211 // package, so that ./... does not match packages in subdirectories of 212 // ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do. 213 // Note, however, that a directory named vendor that itself contains code 214 // is not a vendored package: cmd/vendor would be a command named vendor, 215 // and the pattern cmd/... matches it. 216 func matchPattern(pattern string) func(name string) bool { 217 // Convert pattern to regular expression. 218 // The strategy for the trailing /... is to nest it in an explicit ? expression. 219 // The strategy for the vendor exclusion is to change the unmatchable 220 // vendor strings to a disallowed code point (vendorChar) and to use 221 // "(anything but that codepoint)*" as the implementation of the ... wildcard. 222 // This is a bit complicated but the obvious alternative, 223 // namely a hand-written search like in most shell glob matchers, 224 // is too easy to make accidentally exponential. 225 // Using package regexp guarantees linear-time matching. 226 227 const vendorChar = "\x00" 228 229 if strings.Contains(pattern, vendorChar) { 230 return func(name string) bool { return false } 231 } 232 233 re := regexp.QuoteMeta(pattern) 234 re = replaceVendor(re, vendorChar) 235 switch { 236 case strings.HasSuffix(re, `/`+vendorChar+`/\.\.\.`): 237 re = strings.TrimSuffix(re, `/`+vendorChar+`/\.\.\.`) + `(/vendor|/` + vendorChar + `/\.\.\.)` 238 case re == vendorChar+`/\.\.\.`: 239 re = `(/vendor|/` + vendorChar + `/\.\.\.)` 240 case strings.HasSuffix(re, `/\.\.\.`): 241 re = strings.TrimSuffix(re, `/\.\.\.`) + `(/\.\.\.)?` 242 } 243 re = strings.Replace(re, `\.\.\.`, `[^`+vendorChar+`]*`, -1) 244 245 reg := regexp.MustCompile(`^` + re + `$`) 246 247 return func(name string) bool { 248 if strings.Contains(name, vendorChar) { 249 return false 250 } 251 return reg.MatchString(replaceVendor(name, vendorChar)) 252 } 253 } 254 255 // replaceVendor returns the result of replacing 256 // non-trailing vendor path elements in x with repl. 257 func replaceVendor(x, repl string) string { 258 if !strings.Contains(x, "vendor") { 259 return x 260 } 261 elem := strings.Split(x, "/") 262 for i := 0; i < len(elem)-1; i++ { 263 if elem[i] == "vendor" { 264 elem[i] = repl 265 } 266 } 267 return strings.Join(elem, "/") 268 } 269 270 // ImportPaths returns the import paths to use for the given command line. 271 func ImportPaths(args []string) []string { 272 args = ImportPathsNoDotExpansion(args) 273 var out []string 274 for _, a := range args { 275 if strings.Contains(a, "...") { 276 if build.IsLocalImport(a) { 277 out = append(out, allPackagesInFS(a)...) 278 } else { 279 out = append(out, allPackages(a)...) 280 } 281 continue 282 } 283 out = append(out, a) 284 } 285 return out 286 } 287 288 // ImportPathsNoDotExpansion returns the import paths to use for the given 289 // command line, but it does no ... expansion. 290 func ImportPathsNoDotExpansion(args []string) []string { 291 if len(args) == 0 { 292 return []string{"."} 293 } 294 var out []string 295 for _, a := range args { 296 // Arguments are supposed to be import paths, but 297 // as a courtesy to Windows developers, rewrite \ to / 298 // in command-line arguments. Handles .\... and so on. 299 if filepath.Separator == '\\' { 300 a = strings.Replace(a, `\`, `/`, -1) 301 } 302 303 // Put argument in canonical form, but preserve leading ./. 304 if strings.HasPrefix(a, "./") { 305 a = "./" + path.Clean(a) 306 if a == "./." { 307 a = "." 308 } 309 } else { 310 a = path.Clean(a) 311 } 312 if IsMetaPackage(a) { 313 out = append(out, allPackages(a)...) 314 continue 315 } 316 out = append(out, a) 317 } 318 return out 319 } 320 321 // isMetaPackage checks if name is a reserved package name that expands to multiple packages. 322 func IsMetaPackage(name string) bool { 323 return name == "std" || name == "cmd" || name == "all" 324 }