github.com/riscv/riscv-go@v0.0.0-20200123204226-124ebd6fcc8e/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 _, err = cfg.BuildContext.ImportDir(path, 0) 97 if err != nil { 98 if _, noGo := err.(*build.NoGoError); noGo { 99 return nil 100 } 101 } 102 pkgs = append(pkgs, name) 103 return nil 104 }) 105 } 106 return pkgs 107 } 108 109 // MatchPackagesInFS returns a list of package paths matching pattern, 110 // which must begin with ./ or ../ 111 // (see go help packages for pattern syntax). 112 func MatchPackagesInFS(pattern string) []string { 113 // Find directory to begin the scan. 114 // Could be smarter but this one optimization 115 // is enough for now, since ... is usually at the 116 // end of a path. 117 i := strings.Index(pattern, "...") 118 dir, _ := path.Split(pattern[:i]) 119 120 // pattern begins with ./ or ../. 121 // path.Clean will discard the ./ but not the ../. 122 // We need to preserve the ./ for pattern matching 123 // and in the returned import paths. 124 prefix := "" 125 if strings.HasPrefix(pattern, "./") { 126 prefix = "./" 127 } 128 match := matchPattern(pattern) 129 130 var pkgs []string 131 filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error { 132 if err != nil || !fi.IsDir() { 133 return nil 134 } 135 if path == dir { 136 // filepath.Walk starts at dir and recurses. For the recursive case, 137 // the path is the result of filepath.Join, which calls filepath.Clean. 138 // The initial case is not Cleaned, though, so we do this explicitly. 139 // 140 // This converts a path like "./io/" to "io". Without this step, running 141 // "cd $GOROOT/src; go list ./io/..." would incorrectly skip the io 142 // package, because prepending the prefix "./" to the unclean path would 143 // result in "././io", and match("././io") returns false. 144 path = filepath.Clean(path) 145 } 146 147 // Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..". 148 _, elem := filepath.Split(path) 149 dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".." 150 if dot || strings.HasPrefix(elem, "_") || elem == "testdata" { 151 return filepath.SkipDir 152 } 153 154 name := prefix + filepath.ToSlash(path) 155 if !match(name) { 156 return nil 157 } 158 159 // We keep the directory if we can import it, or if we can't import it 160 // due to invalid Go source files. This means that directories containing 161 // parse errors will be built (and fail) instead of being silently skipped 162 // as not matching the pattern. Go 1.5 and earlier skipped, but that 163 // behavior means people miss serious mistakes. 164 // See golang.org/issue/11407. 165 if p, err := cfg.BuildContext.ImportDir(path, 0); err != nil && (p == nil || len(p.InvalidGoFiles) == 0) { 166 if _, noGo := err.(*build.NoGoError); !noGo { 167 log.Print(err) 168 } 169 return nil 170 } 171 pkgs = append(pkgs, name) 172 return nil 173 }) 174 return pkgs 175 } 176 177 // treeCanMatchPattern(pattern)(name) reports whether 178 // name or children of name can possibly match pattern. 179 // Pattern is the same limited glob accepted by matchPattern. 180 func treeCanMatchPattern(pattern string) func(name string) bool { 181 wildCard := false 182 if i := strings.Index(pattern, "..."); i >= 0 { 183 wildCard = true 184 pattern = pattern[:i] 185 } 186 return func(name string) bool { 187 return len(name) <= len(pattern) && hasPathPrefix(pattern, name) || 188 wildCard && strings.HasPrefix(name, pattern) 189 } 190 } 191 192 // matchPattern(pattern)(name) reports whether 193 // name matches pattern. Pattern is a limited glob 194 // pattern in which '...' means 'any string' and there 195 // is no other special syntax. 196 func matchPattern(pattern string) func(name string) bool { 197 re := regexp.QuoteMeta(pattern) 198 re = strings.Replace(re, `\.\.\.`, `.*`, -1) 199 // Special case: foo/... matches foo too. 200 if strings.HasSuffix(re, `/.*`) { 201 re = re[:len(re)-len(`/.*`)] + `(/.*)?` 202 } 203 reg := regexp.MustCompile(`^` + re + `$`) 204 return func(name string) bool { 205 return reg.MatchString(name) 206 } 207 } 208 209 // ImportPaths returns the import paths to use for the given command line. 210 func ImportPaths(args []string) []string { 211 args = ImportPathsNoDotExpansion(args) 212 var out []string 213 for _, a := range args { 214 if strings.Contains(a, "...") { 215 if build.IsLocalImport(a) { 216 out = append(out, allPackagesInFS(a)...) 217 } else { 218 out = append(out, allPackages(a)...) 219 } 220 continue 221 } 222 out = append(out, a) 223 } 224 return out 225 } 226 227 // ImportPathsNoDotExpansion returns the import paths to use for the given 228 // command line, but it does no ... expansion. 229 func ImportPathsNoDotExpansion(args []string) []string { 230 if len(args) == 0 { 231 return []string{"."} 232 } 233 var out []string 234 for _, a := range args { 235 // Arguments are supposed to be import paths, but 236 // as a courtesy to Windows developers, rewrite \ to / 237 // in command-line arguments. Handles .\... and so on. 238 if filepath.Separator == '\\' { 239 a = strings.Replace(a, `\`, `/`, -1) 240 } 241 242 // Put argument in canonical form, but preserve leading ./. 243 if strings.HasPrefix(a, "./") { 244 a = "./" + path.Clean(a) 245 if a == "./." { 246 a = "." 247 } 248 } else { 249 a = path.Clean(a) 250 } 251 if IsMetaPackage(a) { 252 out = append(out, allPackages(a)...) 253 continue 254 } 255 out = append(out, a) 256 } 257 return out 258 } 259 260 // isMetaPackage checks if name is a reserved package name that expands to multiple packages. 261 func IsMetaPackage(name string) bool { 262 return name == "std" || name == "cmd" || name == "all" 263 }