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