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