github.com/flyinox/gosm@v0.0.0-20171117061539-16768cb62077/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 // replaceVendor returns the result of replacing 270 // non-trailing vendor path elements in x with repl. 271 func replaceVendor(x, repl string) string { 272 if !strings.Contains(x, "vendor") { 273 return x 274 } 275 elem := strings.Split(x, "/") 276 for i := 0; i < len(elem)-1; i++ { 277 if elem[i] == "vendor" { 278 elem[i] = repl 279 } 280 } 281 return strings.Join(elem, "/") 282 } 283 284 // ImportPaths returns the import paths to use for the given command line. 285 func ImportPaths(args []string) []string { 286 args = ImportPathsNoDotExpansion(args) 287 var out []string 288 for _, a := range args { 289 if strings.Contains(a, "...") { 290 if build.IsLocalImport(a) { 291 out = append(out, allPackagesInFS(a)...) 292 } else { 293 out = append(out, allPackages(a)...) 294 } 295 continue 296 } 297 out = append(out, a) 298 } 299 return out 300 } 301 302 // ImportPathsNoDotExpansion returns the import paths to use for the given 303 // command line, but it does no ... expansion. 304 func ImportPathsNoDotExpansion(args []string) []string { 305 if len(args) == 0 { 306 return []string{"."} 307 } 308 var out []string 309 for _, a := range args { 310 // Arguments are supposed to be import paths, but 311 // as a courtesy to Windows developers, rewrite \ to / 312 // in command-line arguments. Handles .\... and so on. 313 if filepath.Separator == '\\' { 314 a = strings.Replace(a, `\`, `/`, -1) 315 } 316 317 // Put argument in canonical form, but preserve leading ./. 318 if strings.HasPrefix(a, "./") { 319 a = "./" + path.Clean(a) 320 if a == "./." { 321 a = "." 322 } 323 } else { 324 a = path.Clean(a) 325 } 326 if IsMetaPackage(a) { 327 out = append(out, allPackages(a)...) 328 continue 329 } 330 out = append(out, a) 331 } 332 return out 333 } 334 335 // isMetaPackage checks if name is a reserved package name that expands to multiple packages. 336 func IsMetaPackage(name string) bool { 337 return name == "std" || name == "cmd" || name == "all" 338 }