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