gopkg.in/tools/godep.v63@v63.0.0-20160503185544-51f9ea00dbee/list.go (about) 1 package main 2 3 import ( 4 "errors" 5 "fmt" 6 "go/build" 7 "go/parser" 8 "go/token" 9 "log" 10 "os" 11 "path/filepath" 12 "regexp" 13 "strconv" 14 "strings" 15 "unicode" 16 17 pathpkg "path" 18 ) 19 20 var ( 21 gorootSrc = filepath.Join(build.Default.GOROOT, "src") 22 ignoreTags = []string{"appengine", "ignore"} //TODO: appengine is a special case for now: https://github.com/tools/godep/issues/353 23 versionMatch = regexp.MustCompile(`\Ago\d+\.\d+\z`) 24 versionNegativeMatch = regexp.MustCompile(`\A\!go\d+\.\d+\z`) 25 ) 26 27 type errorMissingDep struct { 28 i, dir string // import, dir 29 } 30 31 func (e errorMissingDep) Error() string { 32 return "Unable to find dependent package " + e.i + " in context of " + e.dir 33 } 34 35 // packageContext is used to track an import and which package imported it. 36 type packageContext struct { 37 pkg *build.Package // package that imports the import 38 imp string // import 39 } 40 41 // depScanner tracks the processed and to be processed packageContexts 42 type depScanner struct { 43 processed []packageContext 44 todo []packageContext 45 } 46 47 // Next package and import to process 48 func (ds *depScanner) Next() (*build.Package, string) { 49 c := ds.todo[0] 50 ds.processed = append(ds.processed, c) 51 ds.todo = ds.todo[1:] 52 return c.pkg, c.imp 53 } 54 55 // Continue looping? 56 func (ds *depScanner) Continue() bool { 57 if len(ds.todo) > 0 { 58 return true 59 } 60 return false 61 } 62 63 // Add a package and imports to the depScanner. Skips already processed/pending package/import combos 64 func (ds *depScanner) Add(pkg *build.Package, imports ...string) { 65 NextImport: 66 for _, i := range imports { 67 if i == "C" { 68 i = "runtime/cgo" 69 } 70 for _, epc := range ds.processed { 71 if pkg.Dir == epc.pkg.Dir && i == epc.imp { 72 debugln("ctxts epc.pkg.Dir == pkg.Dir && i == epc.imp, skipping", epc.pkg.Dir, i) 73 continue NextImport 74 } 75 } 76 for _, epc := range ds.todo { 77 if pkg.Dir == epc.pkg.Dir && i == epc.imp { 78 debugln("ctxts epc.pkg.Dir == pkg.Dir && i == epc.imp, skipping", epc.pkg.Dir, i) 79 continue NextImport 80 } 81 } 82 pc := packageContext{pkg, i} 83 debugln("Adding pc:", pc.pkg.Dir, pc.imp) 84 ds.todo = append(ds.todo, pc) 85 } 86 } 87 88 var ( 89 pkgCache = make(map[string]*build.Package) // dir => *build.Package 90 ) 91 92 // returns the package in dir either from a cache or by importing it and then caching it 93 func fullPackageInDir(dir string) (*build.Package, error) { 94 var err error 95 pkg, ok := pkgCache[dir] 96 if !ok { 97 pkg, err = build.ImportDir(dir, build.FindOnly) 98 if pkg.Goroot { 99 pkg, err = build.ImportDir(pkg.Dir, 0) 100 } else { 101 fillPackage(pkg) 102 } 103 if err == nil { 104 pkgCache[dir] = pkg 105 } 106 } 107 return pkg, err 108 } 109 110 // listPackage specified by path 111 func listPackage(path string) (*Package, error) { 112 debugln("listPackage", path) 113 var lp *build.Package 114 dir, err := findDirForPath(path, nil) 115 if err != nil { 116 return nil, err 117 } 118 lp, err = fullPackageInDir(dir) 119 p := &Package{ 120 Dir: lp.Dir, 121 Root: lp.Root, 122 ImportPath: lp.ImportPath, 123 XTestImports: lp.XTestImports, 124 TestImports: lp.TestImports, 125 GoFiles: lp.GoFiles, 126 CgoFiles: lp.CgoFiles, 127 TestGoFiles: lp.TestGoFiles, 128 XTestGoFiles: lp.XTestGoFiles, 129 IgnoredGoFiles: lp.IgnoredGoFiles, 130 } 131 p.Standard = lp.Goroot && lp.ImportPath != "" && !strings.Contains(lp.ImportPath, ".") 132 if err != nil || p.Standard { 133 return p, err 134 } 135 debugln("Looking For Package:", path, "in", dir) 136 ppln(lp) 137 138 ds := depScanner{} 139 ds.Add(lp, lp.Imports...) 140 for ds.Continue() { 141 ip, i := ds.Next() 142 143 debugf("Processing import %s for %s\n", i, ip.Dir) 144 pdir, err := findDirForPath(i, ip) 145 if err != nil { 146 return nil, err 147 } 148 dp, err := fullPackageInDir(pdir) 149 if err != nil { // This really should happen in this context though 150 ppln(err) 151 return nil, errorMissingDep{i: i, dir: ip.Dir} 152 } 153 ppln(dp) 154 if !dp.Goroot { 155 // Don't bother adding packages in GOROOT to the dependency scanner, they don't import things from outside of it. 156 ds.Add(dp, dp.Imports...) 157 } 158 debugln("lp:") 159 ppln(lp) 160 debugln("ip:") 161 ppln(ip) 162 if lp == ip { 163 debugln("lp == ip") 164 p.Imports = append(p.Imports, dp.ImportPath) 165 } 166 p.Deps = append(p.Deps, dp.ImportPath) 167 } 168 p.Imports = uniq(p.Imports) 169 p.Deps = uniq(p.Deps) 170 debugln("Done Looking For Package:", path, "in", dir) 171 ppln(p) 172 return p, nil 173 } 174 175 // finds the directory for the given import path in the context of the provided build.Package (if provided) 176 func findDirForPath(path string, ip *build.Package) (string, error) { 177 debugln("findDirForPath", path, ip) 178 var search []string 179 180 if build.IsLocalImport(path) { 181 dir := path 182 if !filepath.IsAbs(dir) { 183 if abs, err := filepath.Abs(dir); err == nil { 184 // interpret relative to current directory 185 dir = abs 186 } 187 } 188 return dir, nil 189 } 190 191 // We need to check to see if the import exists in vendor/ folders up the hierarchy of the importing package 192 if VendorExperiment && ip != nil { 193 debugln("resolving vendor posibilities:", ip.Dir, ip.Root) 194 cr := cleanPath(ip.Root) 195 196 for base := cleanPath(ip.Dir); !pathEqual(base, cr); base = cleanPath(filepath.Dir(base)) { 197 s := filepath.Join(base, "vendor", path) 198 debugln("Adding search dir:", s) 199 search = append(search, s) 200 } 201 } 202 203 for _, base := range build.Default.SrcDirs() { 204 search = append(search, filepath.Join(base, path)) 205 } 206 207 for _, dir := range search { 208 debugln("searching", dir) 209 fi, err := os.Stat(dir) 210 if err == nil && fi.IsDir() { 211 return dir, nil 212 } 213 } 214 215 return "", errPackageNotFound{path} 216 } 217 218 // fillPackage full of info. Assumes p.Dir is set at a minimum 219 func fillPackage(p *build.Package) error { 220 if p.Goroot { 221 return nil 222 } 223 224 if p.SrcRoot == "" { 225 for _, base := range build.Default.SrcDirs() { 226 if strings.HasPrefix(p.Dir, base) { 227 p.SrcRoot = base 228 } 229 } 230 } 231 232 if p.SrcRoot == "" { 233 return errors.New("Unable to find SrcRoot for package " + p.ImportPath) 234 } 235 236 if p.Root == "" { 237 p.Root = filepath.Dir(p.SrcRoot) 238 } 239 240 var buildMatch = "+build " 241 var buildFieldSplit = func(r rune) bool { 242 return unicode.IsSpace(r) || r == ',' 243 } 244 245 debugln("Filling package:", p.ImportPath, "from", p.Dir) 246 gofiles, err := filepath.Glob(filepath.Join(p.Dir, "*.go")) 247 if err != nil { 248 debugln("Error globbing", err) 249 return err 250 } 251 252 var testImports []string 253 var imports []string 254 NextFile: 255 for _, file := range gofiles { 256 debugln(file) 257 pf, err := parser.ParseFile(token.NewFileSet(), file, nil, parser.ImportsOnly|parser.ParseComments) 258 if err != nil { 259 return err 260 } 261 testFile := strings.HasSuffix(file, "_test.go") 262 fname := filepath.Base(file) 263 for _, c := range pf.Comments { 264 ct := c.Text() 265 if i := strings.Index(ct, buildMatch); i != -1 { 266 for _, t := range strings.FieldsFunc(ct[i+len(buildMatch):], buildFieldSplit) { 267 for _, tag := range ignoreTags { 268 if t == tag { 269 p.IgnoredGoFiles = append(p.IgnoredGoFiles, fname) 270 continue NextFile 271 } 272 } 273 274 if versionMatch.MatchString(t) && !isSameOrNewer(t, majorGoVersion) { 275 debugln("Adding", fname, "to ignored list because of version tag", t) 276 p.IgnoredGoFiles = append(p.IgnoredGoFiles, fname) 277 continue NextFile 278 } 279 if versionNegativeMatch.MatchString(t) && isSameOrNewer(t[1:], majorGoVersion) { 280 debugln("Adding", fname, "to ignored list because of version tag", t) 281 p.IgnoredGoFiles = append(p.IgnoredGoFiles, fname) 282 continue NextFile 283 } 284 } 285 } 286 } 287 if testFile { 288 p.TestGoFiles = append(p.TestGoFiles, fname) 289 } else { 290 p.GoFiles = append(p.GoFiles, fname) 291 } 292 for _, is := range pf.Imports { 293 name, err := strconv.Unquote(is.Path.Value) 294 if err != nil { 295 return err // can't happen? 296 } 297 if testFile { 298 testImports = append(testImports, name) 299 } else { 300 imports = append(imports, name) 301 } 302 } 303 } 304 imports = uniq(imports) 305 testImports = uniq(testImports) 306 p.Imports = imports 307 p.TestImports = testImports 308 return nil 309 } 310 311 // All of the following functions were vendored from go proper. Locations are noted in comments, but may change in future Go versions. 312 313 // importPaths returns the import paths to use for the given command line. 314 // $GOROOT/src/cmd/main.go:366 315 func importPaths(args []string) []string { 316 args = importPathsNoDotExpansion(args) 317 var out []string 318 for _, a := range args { 319 if strings.Contains(a, "...") { 320 if build.IsLocalImport(a) { 321 out = append(out, allPackagesInFS(a)...) 322 } else { 323 out = append(out, allPackages(a)...) 324 } 325 continue 326 } 327 out = append(out, a) 328 } 329 return out 330 } 331 332 // importPathsNoDotExpansion returns the import paths to use for the given 333 // command line, but it does no ... expansion. 334 // $GOROOT/src/cmd/main.go:332 335 func importPathsNoDotExpansion(args []string) []string { 336 if len(args) == 0 { 337 return []string{"."} 338 } 339 var out []string 340 for _, a := range args { 341 // Arguments are supposed to be import paths, but 342 // as a courtesy to Windows developers, rewrite \ to / 343 // in command-line arguments. Handles .\... and so on. 344 if filepath.Separator == '\\' { 345 a = strings.Replace(a, `\`, `/`, -1) 346 } 347 348 // Put argument in canonical form, but preserve leading ./. 349 if strings.HasPrefix(a, "./") { 350 a = "./" + pathpkg.Clean(a) 351 if a == "./." { 352 a = "." 353 } 354 } else { 355 a = pathpkg.Clean(a) 356 } 357 if a == "all" || a == "std" || a == "cmd" { 358 out = append(out, allPackages(a)...) 359 continue 360 } 361 out = append(out, a) 362 } 363 return out 364 } 365 366 // allPackagesInFS is like allPackages but is passed a pattern 367 // beginning ./ or ../, meaning it should scan the tree rooted 368 // at the given directory. There are ... in the pattern too. 369 // $GOROOT/src/cmd/main.go:620 370 func allPackagesInFS(pattern string) []string { 371 pkgs := matchPackagesInFS(pattern) 372 if len(pkgs) == 0 { 373 fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern) 374 } 375 return pkgs 376 } 377 378 // allPackages returns all the packages that can be found 379 // under the $GOPATH directories and $GOROOT matching pattern. 380 // The pattern is either "all" (all packages), "std" (standard packages), 381 // "cmd" (standard commands), or a path including "...". 382 // $GOROOT/src/cmd/main.go:542 383 func allPackages(pattern string) []string { 384 pkgs := matchPackages(pattern) 385 if len(pkgs) == 0 { 386 fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern) 387 } 388 return pkgs 389 } 390 391 // $GOROOT/src/cmd/main.go:554 392 func matchPackages(pattern string) []string { 393 match := func(string) bool { return true } 394 treeCanMatch := func(string) bool { return true } 395 if pattern != "all" && pattern != "std" && pattern != "cmd" { 396 match = matchPattern(pattern) 397 treeCanMatch = treeCanMatchPattern(pattern) 398 } 399 400 have := map[string]bool{ 401 "builtin": true, // ignore pseudo-package that exists only for documentation 402 } 403 if !build.Default.CgoEnabled { 404 have["runtime/cgo"] = true // ignore during walk 405 } 406 var pkgs []string 407 408 for _, src := range build.Default.SrcDirs() { 409 if (pattern == "std" || pattern == "cmd") && src != gorootSrc { 410 continue 411 } 412 src = filepath.Clean(src) + string(filepath.Separator) 413 root := src 414 if pattern == "cmd" { 415 root += "cmd" + string(filepath.Separator) 416 } 417 filepath.Walk(root, func(path string, fi os.FileInfo, err error) error { 418 if err != nil || !fi.IsDir() || path == src { 419 return nil 420 } 421 422 // Avoid .foo, _foo, and testdata directory trees. 423 _, elem := filepath.Split(path) 424 if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" { 425 return filepath.SkipDir 426 } 427 428 name := filepath.ToSlash(path[len(src):]) 429 if pattern == "std" && (strings.Contains(name, ".") || name == "cmd") { 430 // The name "std" is only the standard library. 431 // If the name has a dot, assume it's a domain name for go get, 432 // and if the name is cmd, it's the root of the command tree. 433 return filepath.SkipDir 434 } 435 if !treeCanMatch(name) { 436 return filepath.SkipDir 437 } 438 if have[name] { 439 return nil 440 } 441 have[name] = true 442 if !match(name) { 443 return nil 444 } 445 _, err = build.ImportDir(path, 0) 446 if err != nil { 447 if _, noGo := err.(*build.NoGoError); noGo { 448 return nil 449 } 450 } 451 pkgs = append(pkgs, name) 452 return nil 453 }) 454 } 455 return pkgs 456 } 457 458 // treeCanMatchPattern(pattern)(name) reports whether 459 // name or children of name can possibly match pattern. 460 // Pattern is the same limited glob accepted by matchPattern. 461 // $GOROOT/src/cmd/main.go:527 462 func treeCanMatchPattern(pattern string) func(name string) bool { 463 wildCard := false 464 if i := strings.Index(pattern, "..."); i >= 0 { 465 wildCard = true 466 pattern = pattern[:i] 467 } 468 return func(name string) bool { 469 return len(name) <= len(pattern) && hasPathPrefix(pattern, name) || 470 wildCard && strings.HasPrefix(name, pattern) 471 } 472 } 473 474 // hasPathPrefix reports whether the path s begins with the 475 // elements in prefix. 476 // $GOROOT/src/cmd/main.go:489 477 func hasPathPrefix(s, prefix string) bool { 478 switch { 479 default: 480 return false 481 case len(s) == len(prefix): 482 return s == prefix 483 case len(s) > len(prefix): 484 if prefix != "" && prefix[len(prefix)-1] == '/' { 485 return strings.HasPrefix(s, prefix) 486 } 487 return s[len(prefix)] == '/' && s[:len(prefix)] == prefix 488 } 489 } 490 491 // $GOROOT/src/cmd/go/main.go:631 492 func matchPackagesInFS(pattern string) []string { 493 // Find directory to begin the scan. 494 // Could be smarter but this one optimization 495 // is enough for now, since ... is usually at the 496 // end of a path. 497 i := strings.Index(pattern, "...") 498 dir, _ := pathpkg.Split(pattern[:i]) 499 500 // pattern begins with ./ or ../. 501 // path.Clean will discard the ./ but not the ../. 502 // We need to preserve the ./ for pattern matching 503 // and in the returned import paths. 504 prefix := "" 505 if strings.HasPrefix(pattern, "./") { 506 prefix = "./" 507 } 508 match := matchPattern(pattern) 509 510 var pkgs []string 511 filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error { 512 if err != nil || !fi.IsDir() { 513 return nil 514 } 515 if path == dir { 516 // filepath.Walk starts at dir and recurses. For the recursive case, 517 // the path is the result of filepath.Join, which calls filepath.Clean. 518 // The initial case is not Cleaned, though, so we do this explicitly. 519 // 520 // This converts a path like "./io/" to "io". Without this step, running 521 // "cd $GOROOT/src; go list ./io/..." would incorrectly skip the io 522 // package, because prepending the prefix "./" to the unclean path would 523 // result in "././io", and match("././io") returns false. 524 path = filepath.Clean(path) 525 } 526 527 // Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..". 528 _, elem := filepath.Split(path) 529 dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".." 530 if dot || strings.HasPrefix(elem, "_") || elem == "testdata" { 531 return filepath.SkipDir 532 } 533 534 name := prefix + filepath.ToSlash(path) 535 if !match(name) { 536 return nil 537 } 538 if _, err = build.ImportDir(path, 0); err != nil { 539 if _, noGo := err.(*build.NoGoError); !noGo { 540 log.Print(err) 541 } 542 return nil 543 } 544 pkgs = append(pkgs, name) 545 return nil 546 }) 547 return pkgs 548 }