gopkg.in/tools/godep.v54@v54.0.0-20160222173036-53bf4cf4341d/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 if p.Goroot { 217 return nil 218 } 219 220 if p.SrcRoot == "" { 221 for _, base := range build.Default.SrcDirs() { 222 if strings.HasPrefix(p.Dir, base) { 223 p.SrcRoot = base 224 } 225 } 226 } 227 228 if p.SrcRoot == "" { 229 return errors.New("Unable to find SrcRoot for package " + p.ImportPath) 230 } 231 232 if p.Root == "" { 233 p.Root = filepath.Dir(p.SrcRoot) 234 } 235 236 var buildMatch = "+build " 237 var buildFieldSplit = func(r rune) bool { 238 return unicode.IsSpace(r) || r == ',' 239 } 240 241 debugln("Filling package:", p.ImportPath, "from", p.Dir) 242 gofiles, err := filepath.Glob(filepath.Join(p.Dir, "*.go")) 243 if err != nil { 244 debugln("Error globbing", err) 245 return err 246 } 247 248 var testImports []string 249 var imports []string 250 NextFile: 251 for _, file := range gofiles { 252 debugln(file) 253 pf, err := parser.ParseFile(token.NewFileSet(), file, nil, parser.ParseComments) 254 if err != nil { 255 return err 256 } 257 testFile := strings.HasSuffix(file, "_test.go") 258 fname := filepath.Base(file) 259 if testFile { 260 p.TestGoFiles = append(p.TestGoFiles, fname) 261 } else { 262 p.GoFiles = append(p.GoFiles, fname) 263 } 264 if len(pf.Comments) > 0 { 265 for _, c := range pf.Comments { 266 ct := c.Text() 267 if i := strings.Index(ct, buildMatch); i != -1 { 268 for _, b := range strings.FieldsFunc(ct[i+len(buildMatch):], buildFieldSplit) { 269 //TODO: appengine is a special case for now: https://github.com/tools/godep/issues/353 270 if b == "ignore" || b == "appengine" { 271 p.IgnoredGoFiles = append(p.IgnoredGoFiles, fname) 272 continue NextFile 273 } 274 } 275 } 276 } 277 } 278 for _, is := range pf.Imports { 279 name, err := strconv.Unquote(is.Path.Value) 280 if err != nil { 281 return err // can't happen? 282 } 283 if testFile { 284 testImports = append(testImports, name) 285 } else { 286 imports = append(imports, name) 287 } 288 } 289 } 290 imports = uniq(imports) 291 testImports = uniq(testImports) 292 p.Imports = imports 293 p.TestImports = testImports 294 return nil 295 } 296 297 // All of the following functions were vendored from go proper. Locations are noted in comments, but may change in future Go versions. 298 299 // importPaths returns the import paths to use for the given command line. 300 // $GOROOT/src/cmd/main.go:366 301 func importPaths(args []string) []string { 302 args = importPathsNoDotExpansion(args) 303 var out []string 304 for _, a := range args { 305 if strings.Contains(a, "...") { 306 if build.IsLocalImport(a) { 307 out = append(out, allPackagesInFS(a)...) 308 } else { 309 out = append(out, allPackages(a)...) 310 } 311 continue 312 } 313 out = append(out, a) 314 } 315 return out 316 } 317 318 // importPathsNoDotExpansion returns the import paths to use for the given 319 // command line, but it does no ... expansion. 320 // $GOROOT/src/cmd/main.go:332 321 func importPathsNoDotExpansion(args []string) []string { 322 if len(args) == 0 { 323 return []string{"."} 324 } 325 var out []string 326 for _, a := range args { 327 // Arguments are supposed to be import paths, but 328 // as a courtesy to Windows developers, rewrite \ to / 329 // in command-line arguments. Handles .\... and so on. 330 if filepath.Separator == '\\' { 331 a = strings.Replace(a, `\`, `/`, -1) 332 } 333 334 // Put argument in canonical form, but preserve leading ./. 335 if strings.HasPrefix(a, "./") { 336 a = "./" + pathpkg.Clean(a) 337 if a == "./." { 338 a = "." 339 } 340 } else { 341 a = pathpkg.Clean(a) 342 } 343 if a == "all" || a == "std" || a == "cmd" { 344 out = append(out, allPackages(a)...) 345 continue 346 } 347 out = append(out, a) 348 } 349 return out 350 } 351 352 // allPackagesInFS is like allPackages but is passed a pattern 353 // beginning ./ or ../, meaning it should scan the tree rooted 354 // at the given directory. There are ... in the pattern too. 355 // $GOROOT/src/cmd/main.go:620 356 func allPackagesInFS(pattern string) []string { 357 pkgs := matchPackagesInFS(pattern) 358 if len(pkgs) == 0 { 359 fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern) 360 } 361 return pkgs 362 } 363 364 // allPackages returns all the packages that can be found 365 // under the $GOPATH directories and $GOROOT matching pattern. 366 // The pattern is either "all" (all packages), "std" (standard packages), 367 // "cmd" (standard commands), or a path including "...". 368 // $GOROOT/src/cmd/main.go:542 369 func allPackages(pattern string) []string { 370 pkgs := matchPackages(pattern) 371 if len(pkgs) == 0 { 372 fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern) 373 } 374 return pkgs 375 } 376 377 // $GOROOT/src/cmd/main.go:554 378 func matchPackages(pattern string) []string { 379 match := func(string) bool { return true } 380 treeCanMatch := func(string) bool { return true } 381 if pattern != "all" && pattern != "std" && pattern != "cmd" { 382 match = matchPattern(pattern) 383 treeCanMatch = treeCanMatchPattern(pattern) 384 } 385 386 have := map[string]bool{ 387 "builtin": true, // ignore pseudo-package that exists only for documentation 388 } 389 if !build.Default.CgoEnabled { 390 have["runtime/cgo"] = true // ignore during walk 391 } 392 var pkgs []string 393 394 for _, src := range build.Default.SrcDirs() { 395 if (pattern == "std" || pattern == "cmd") && src != gorootSrc { 396 continue 397 } 398 src = filepath.Clean(src) + string(filepath.Separator) 399 root := src 400 if pattern == "cmd" { 401 root += "cmd" + string(filepath.Separator) 402 } 403 filepath.Walk(root, func(path string, fi os.FileInfo, err error) error { 404 if err != nil || !fi.IsDir() || path == src { 405 return nil 406 } 407 408 // Avoid .foo, _foo, and testdata directory trees. 409 _, elem := filepath.Split(path) 410 if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" { 411 return filepath.SkipDir 412 } 413 414 name := filepath.ToSlash(path[len(src):]) 415 if pattern == "std" && (strings.Contains(name, ".") || name == "cmd") { 416 // The name "std" is only the standard library. 417 // If the name has a dot, assume it's a domain name for go get, 418 // and if the name is cmd, it's the root of the command tree. 419 return filepath.SkipDir 420 } 421 if !treeCanMatch(name) { 422 return filepath.SkipDir 423 } 424 if have[name] { 425 return nil 426 } 427 have[name] = true 428 if !match(name) { 429 return nil 430 } 431 _, err = build.ImportDir(path, 0) 432 if err != nil { 433 if _, noGo := err.(*build.NoGoError); noGo { 434 return nil 435 } 436 } 437 pkgs = append(pkgs, name) 438 return nil 439 }) 440 } 441 return pkgs 442 } 443 444 // treeCanMatchPattern(pattern)(name) reports whether 445 // name or children of name can possibly match pattern. 446 // Pattern is the same limited glob accepted by matchPattern. 447 // $GOROOT/src/cmd/main.go:527 448 func treeCanMatchPattern(pattern string) func(name string) bool { 449 wildCard := false 450 if i := strings.Index(pattern, "..."); i >= 0 { 451 wildCard = true 452 pattern = pattern[:i] 453 } 454 return func(name string) bool { 455 return len(name) <= len(pattern) && hasPathPrefix(pattern, name) || 456 wildCard && strings.HasPrefix(name, pattern) 457 } 458 } 459 460 // hasPathPrefix reports whether the path s begins with the 461 // elements in prefix. 462 // $GOROOT/src/cmd/main.go:489 463 func hasPathPrefix(s, prefix string) bool { 464 switch { 465 default: 466 return false 467 case len(s) == len(prefix): 468 return s == prefix 469 case len(s) > len(prefix): 470 if prefix != "" && prefix[len(prefix)-1] == '/' { 471 return strings.HasPrefix(s, prefix) 472 } 473 return s[len(prefix)] == '/' && s[:len(prefix)] == prefix 474 } 475 } 476 477 // $GOROOT/src/cmd/go/main.go:631 478 func matchPackagesInFS(pattern string) []string { 479 // Find directory to begin the scan. 480 // Could be smarter but this one optimization 481 // is enough for now, since ... is usually at the 482 // end of a path. 483 i := strings.Index(pattern, "...") 484 dir, _ := pathpkg.Split(pattern[:i]) 485 486 // pattern begins with ./ or ../. 487 // path.Clean will discard the ./ but not the ../. 488 // We need to preserve the ./ for pattern matching 489 // and in the returned import paths. 490 prefix := "" 491 if strings.HasPrefix(pattern, "./") { 492 prefix = "./" 493 } 494 match := matchPattern(pattern) 495 496 var pkgs []string 497 filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error { 498 if err != nil || !fi.IsDir() { 499 return nil 500 } 501 if path == dir { 502 // filepath.Walk starts at dir and recurses. For the recursive case, 503 // the path is the result of filepath.Join, which calls filepath.Clean. 504 // The initial case is not Cleaned, though, so we do this explicitly. 505 // 506 // This converts a path like "./io/" to "io". Without this step, running 507 // "cd $GOROOT/src; go list ./io/..." would incorrectly skip the io 508 // package, because prepending the prefix "./" to the unclean path would 509 // result in "././io", and match("././io") returns false. 510 path = filepath.Clean(path) 511 } 512 513 // Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..". 514 _, elem := filepath.Split(path) 515 dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".." 516 if dot || strings.HasPrefix(elem, "_") || elem == "testdata" { 517 return filepath.SkipDir 518 } 519 520 name := prefix + filepath.ToSlash(path) 521 if !match(name) { 522 return nil 523 } 524 if _, err = build.ImportDir(path, 0); err != nil { 525 if _, noGo := err.(*build.NoGoError); !noGo { 526 log.Print(err) 527 } 528 return nil 529 } 530 pkgs = append(pkgs, name) 531 return nil 532 }) 533 return pkgs 534 }