github.com/anthonymayer/glide@v0.0.0-20160224162501-bff8b50d232e/dependency/resolver.go (about) 1 package dependency 2 3 import ( 4 "container/list" 5 //"go/build" 6 "os" 7 "path/filepath" 8 "strings" 9 10 "github.com/Masterminds/glide/cfg" 11 "github.com/Masterminds/glide/msg" 12 "github.com/Masterminds/glide/util" 13 ) 14 15 // MissingPackageHandler handles the case where a package is missing during scanning. 16 // 17 // It returns true if the package can be passed to the resolver, false otherwise. 18 // False may be returned even if error is nil. 19 type MissingPackageHandler interface { 20 // NotFound is called when the Resolver fails to find a package with the given name. 21 // 22 // NotFound returns true when the resolver should attempt to re-resole the 23 // dependency (e.g. when NotFound has gone and fetched the missing package). 24 // 25 // When NotFound returns false, the Resolver does not try to do any additional 26 // work on the missing package. 27 // 28 // NotFound only returns errors when it fails to perform its internal goals. 29 // When it returns false with no error, this indicates that the handler did 30 // its job, but the resolver should not do any additional work on the 31 // package. 32 NotFound(pkg string) (bool, error) 33 34 // OnGopath is called when the Resolver finds a dependency, but it's only on GOPATH. 35 // 36 // OnGopath provides an opportunity to copy, move, warn, or ignore cases like this. 37 // 38 // OnGopath returns true when the resolver should attempt to re-resolve the 39 // dependency (e.g. when the dependency is copied to a new location). 40 // 41 // When OnGopath returns false, the Resolver does not try to do any additional 42 // work on the package. 43 // 44 // An error indicates that OnGopath cannot complete its intended operation. 45 // Not all false results are errors. 46 OnGopath(pkg string) (bool, error) 47 48 // InVendor is called when the Resolver finds a dependency in the vendor/ directory. 49 // 50 // This can be used update a project found in the vendor/ folder. 51 InVendor(pkg string) error 52 } 53 54 // DefaultMissingPackageHandler is the default handler for missing packages. 55 // 56 // When asked to handle a missing package, it will report the miss as a warning, 57 // and then store the package in the Missing slice for later access. 58 type DefaultMissingPackageHandler struct { 59 Missing []string 60 Gopath []string 61 } 62 63 // NotFound prints a warning and then stores the package name in Missing. 64 // 65 // It never returns an error, and it always returns false. 66 func (d *DefaultMissingPackageHandler) NotFound(pkg string) (bool, error) { 67 msg.Warn("Package %s is not installed", pkg) 68 d.Missing = append(d.Missing, pkg) 69 return false, nil 70 } 71 72 // OnGopath is run when a package is missing from vendor/ but found in the GOPATH 73 func (d *DefaultMissingPackageHandler) OnGopath(pkg string) (bool, error) { 74 msg.Warn("Package %s is only on GOPATH.", pkg) 75 d.Gopath = append(d.Gopath, pkg) 76 return false, nil 77 } 78 79 // InVendor is run when a package is found in the vendor/ folder 80 func (d *DefaultMissingPackageHandler) InVendor(pkg string) error { 81 msg.Info("Package %s found in vendor/ folder", pkg) 82 return nil 83 } 84 85 // VersionHandler sets the version for a package when found while scanning. 86 // 87 // When a package if found it needs to be on the correct version before 88 // scanning its contents to be sure to pick up the right elements for that 89 // version. 90 type VersionHandler interface { 91 92 // Process provides an opportunity to process the codebase for version setting. 93 Process(pkg string) error 94 95 // SetVersion sets the version for a package. An error is returned if there 96 // was a problem setting the version. 97 SetVersion(pkg string) error 98 } 99 100 // DefaultVersionHandler is the default handler for setting the version. 101 // 102 // The default handler leaves the current version and skips setting a version. 103 // For a handler that alters the version see the handler included in the repo 104 // package as part of the installer. 105 type DefaultVersionHandler struct{} 106 107 // Process a package to aide in version setting. 108 func (d *DefaultVersionHandler) Process(pkg string) error { 109 return nil 110 } 111 112 // SetVersion here sends a message when a package is found noting that it 113 // did not set the version. 114 func (d *DefaultVersionHandler) SetVersion(pkg string) error { 115 msg.Warn("Version not set for package %s", pkg) 116 return nil 117 } 118 119 // Resolver resolves a dependency tree. 120 // 121 // It operates in two modes: 122 // - local resolution (ResolveLocal) determines the dependencies of the local project. 123 // - vendor resolving (Resolve, ResolveAll) determines the dependencies of vendored 124 // projects. 125 // 126 // Local resolution is for guessing initial dependencies. Vendor resolution is 127 // for determining vendored dependencies. 128 type Resolver struct { 129 Handler MissingPackageHandler 130 VersionHandler VersionHandler 131 VendorDir string 132 BuildContext *util.BuildCtxt 133 Config *cfg.Config 134 135 // ResolveAllFiles toggles deep scanning. 136 // If this is true, resolve by scanning all files, not by walking the 137 // import tree. 138 ResolveAllFiles bool 139 140 // Items already in the queue. 141 alreadyQ map[string]bool 142 143 // Attempts to scan that had unrecoverable error. 144 hadError map[string]bool 145 146 basedir string 147 seen map[string]bool 148 149 // findCache caches hits from Find. This reduces the number of filesystem 150 // touches that have to be done for dependency resolution. 151 findCache map[string]*PkgInfo 152 } 153 154 // NewResolver returns a new Resolver initialized with the DefaultMissingPackageHandler. 155 // 156 // This will return an error if the given path does not meet the basic criteria 157 // for a Go source project. For example, basedir must have a vendor subdirectory. 158 // 159 // The BuildContext uses the "go/build".Default to resolve dependencies. 160 func NewResolver(basedir string) (*Resolver, error) { 161 162 var err error 163 basedir, err = filepath.Abs(basedir) 164 if err != nil { 165 return nil, err 166 } 167 vdir := filepath.Join(basedir, "vendor") 168 169 buildContext, err := util.GetBuildContext() 170 if err != nil { 171 return nil, err 172 } 173 174 r := &Resolver{ 175 Handler: &DefaultMissingPackageHandler{Missing: []string{}, Gopath: []string{}}, 176 VersionHandler: &DefaultVersionHandler{}, 177 basedir: basedir, 178 VendorDir: vdir, 179 BuildContext: buildContext, 180 seen: map[string]bool{}, 181 alreadyQ: map[string]bool{}, 182 hadError: map[string]bool{}, 183 findCache: map[string]*PkgInfo{}, 184 185 // The config instance here should really be replaced with a real one. 186 Config: &cfg.Config{}, 187 } 188 189 // TODO: Make sure the build context is correctly set up. Especially in 190 // regards to GOROOT, which is not always set. 191 192 return r, nil 193 } 194 195 // Resolve takes a package name and returns all of the imported package names. 196 // 197 // If a package is not found, this calls the Fetcher. If the Fetcher returns 198 // true, it will re-try traversing that package for dependencies. Otherwise it 199 // will add that package to the deps array and continue on without trying it. 200 // And if the Fetcher returns an error, this will stop resolution and return 201 // the error. 202 // 203 // If basepath is set to $GOPATH, this will start from that package's root there. 204 // If basepath is set to a project's vendor path, the scanning will begin from 205 // there. 206 func (r *Resolver) Resolve(pkg, basepath string) ([]string, error) { 207 target := filepath.Join(basepath, filepath.FromSlash(pkg)) 208 //msg.Debug("Scanning %s", target) 209 l := list.New() 210 l.PushBack(target) 211 212 // In this mode, walk the entire tree. 213 if r.ResolveAllFiles { 214 return r.resolveList(l) 215 } 216 return r.resolveImports(l) 217 } 218 219 // ResolveLocal resolves dependencies for the current project. 220 // 221 // This begins with the project, builds up a list of external dependencies. 222 // 223 // If the deep flag is set to true, this will then resolve all of the dependencies 224 // of the dependencies it has found. If not, it will return just the packages that 225 // the base project relies upon. 226 func (r *Resolver) ResolveLocal(deep bool) ([]string, error) { 227 // We build a list of local source to walk, then send this list 228 // to resolveList. 229 l := list.New() 230 alreadySeen := map[string]bool{} 231 err := filepath.Walk(r.basedir, func(path string, fi os.FileInfo, err error) error { 232 if err != nil && err != filepath.SkipDir { 233 return err 234 } 235 if !fi.IsDir() { 236 return nil 237 } 238 if !srcDir(fi) { 239 return filepath.SkipDir 240 } 241 242 // Scan for dependencies, and anything that's not part of the local 243 // package gets added to the scan list. 244 var imps []string 245 p, err := r.BuildContext.ImportDir(path, 0) 246 if err != nil { 247 if strings.HasPrefix(err.Error(), "no buildable Go source") { 248 return nil 249 } else if strings.HasPrefix(err.Error(), "found packages ") { 250 // If we got here it's because a package and multiple packages 251 // declared. This is often because of an example with a package 252 // or main but +build ignore as a build tag. In that case we 253 // try to brute force the packages with a slower scan. 254 imps, err = IterativeScan(path) 255 if err != nil { 256 return err 257 } 258 } else { 259 return err 260 } 261 } else { 262 imps = p.Imports 263 } 264 265 // We are only looking for dependencies in vendor. No root, cgo, etc. 266 for _, imp := range imps { 267 if alreadySeen[imp] { 268 continue 269 } 270 alreadySeen[imp] = true 271 info := r.FindPkg(imp) 272 switch info.Loc { 273 case LocUnknown, LocVendor: 274 l.PushBack(filepath.Join(r.VendorDir, filepath.FromSlash(imp))) // Do we need a path on this? 275 case LocGopath: 276 if !strings.HasPrefix(info.Path, r.basedir) { 277 // FIXME: This is a package outside of the project we're 278 // scanning. It should really be on vendor. But we don't 279 // want it to reference GOPATH. We want it to be detected 280 // and moved. 281 l.PushBack(filepath.Join(r.VendorDir, filepath.FromSlash(imp))) 282 } 283 } 284 } 285 286 return nil 287 }) 288 289 if err != nil { 290 msg.Err("Failed to build an initial list of packages to scan: %s", err) 291 return []string{}, err 292 } 293 294 if deep { 295 if r.ResolveAllFiles { 296 return r.resolveList(l) 297 } 298 return r.resolveImports(l) 299 } 300 301 // If we're not doing a deep scan, we just convert the list into an 302 // array and return. 303 res := make([]string, 0, l.Len()) 304 for e := l.Front(); e != nil; e = e.Next() { 305 res = append(res, e.Value.(string)) 306 } 307 return res, nil 308 } 309 310 // ResolveAll takes a list of packages and returns an inclusive list of all 311 // vendored dependencies. 312 // 313 // While this will scan all of the source code it can find, it will only return 314 // packages that were either explicitly passed in as deps, or were explicitly 315 // imported by the code. 316 // 317 // Packages that are either CGO or on GOROOT are ignored. Packages that are 318 // on GOPATH, but not vendored currently generate a warning. 319 // 320 // If one of the passed in packages does not exist in the vendor directory, 321 // an error is returned. 322 func (r *Resolver) ResolveAll(deps []*cfg.Dependency) ([]string, error) { 323 var queue *list.List 324 if r.ResolveAllFiles { 325 queue = sliceToQueue(deps, r.VendorDir) 326 } else { 327 queue = list.New() 328 } 329 330 loc, err := r.ResolveLocal(false) 331 if err != nil { 332 return []string{}, err 333 } 334 for _, l := range loc { 335 msg.Debug("Adding local Import %s to queue", l) 336 queue.PushBack(l) 337 } 338 339 if r.ResolveAllFiles { 340 return r.resolveList(queue) 341 } 342 return r.resolveImports(queue) 343 } 344 345 // stripv strips the vendor/ prefix from vendored packages. 346 func (r *Resolver) stripv(str string) string { 347 return strings.TrimPrefix(str, r.VendorDir+string(os.PathSeparator)) 348 } 349 350 // vpath adds an absolute vendor path. 351 func (r *Resolver) vpath(str string) string { 352 return filepath.Join(r.basedir, "vendor", str) 353 } 354 355 // resolveImports takes a list of existing packages and resolves their imports. 356 // 357 // It returns a list of all of the packages that it can determine are required 358 // for the given code to function. 359 // 360 // The expectation is that each item in the queue is an absolute path to a 361 // vendored package. This attempts to read that package, and then find 362 // its referenced packages. Those packages are then added to the list 363 // to be scanned next. 364 // 365 // The resolver's handler is used in the cases where a package cannot be 366 // located. 367 func (r *Resolver) resolveImports(queue *list.List) ([]string, error) { 368 for e := queue.Front(); e != nil; e = e.Next() { 369 vdep := e.Value.(string) 370 dep := r.stripv(vdep) 371 372 // Check if marked in the Q and then explicitly mark it. We want to know 373 // if it had previously been marked and ensure it for the future. 374 _, foundQ := r.alreadyQ[dep] 375 r.alreadyQ[dep] = true 376 377 // If we've already encountered an error processing this dependency 378 // skip it. 379 _, foundErr := r.hadError[dep] 380 if foundErr { 381 continue 382 } 383 384 // Skip ignored packages 385 if r.Config.HasIgnore(dep) { 386 msg.Info("Ignoring: %s", dep) 387 continue 388 } 389 r.VersionHandler.Process(dep) 390 391 // Here, we want to import the package and see what imports it has. 392 msg.Debug("Trying to open %s", vdep) 393 var imps []string 394 pkg, err := r.BuildContext.ImportDir(vdep, 0) 395 if err != nil && strings.HasPrefix(err.Error(), "found packages ") { 396 // If we got here it's because a package and multiple packages 397 // declared. This is often because of an example with a package 398 // or main but +build ignore as a build tag. In that case we 399 // try to brute force the packages with a slower scan. 400 msg.Debug("Using Iterative Scanning for %s", dep) 401 imps, err = IterativeScan(vdep) 402 if err != nil { 403 msg.Err("Iterative scanning error %s: %s", dep, err) 404 continue 405 } 406 } else if err != nil { 407 msg.Debug("ImportDir error on %s: %s", vdep, err) 408 if strings.HasPrefix(err.Error(), "no buildable Go source") { 409 msg.Debug("No subpackages declared. Skipping %s.", dep) 410 continue 411 } else if os.IsNotExist(err) && !foundErr && !foundQ { 412 // If the location doesn't exist, there hasn't already been an 413 // error, it's not already been in the Q then try to fetch it. 414 // When there's an error or it's already in the Q (it should be 415 // fetched if it's marked in r.alreadyQ) we skip to make sure 416 // not to get stuck in a recursion. 417 418 // If the location doesn't exist try to fetch it. 419 if ok, err2 := r.Handler.NotFound(dep); ok { 420 r.alreadyQ[dep] = true 421 422 // By adding to the queue it will get reprocessed now that 423 // it exists. 424 queue.PushBack(r.vpath(dep)) 425 r.VersionHandler.SetVersion(dep) 426 } else if err2 != nil { 427 r.hadError[dep] = true 428 msg.Err("Error looking for %s: %s", dep, err2) 429 } else { 430 r.hadError[dep] = true 431 // TODO (mpb): Should we toss this into a Handler to 432 // see if this is on GOPATH and copy it? 433 msg.Info("Not found in vendor/: %s (1)", dep) 434 } 435 } else { 436 r.hadError[dep] = true 437 msg.Err("Error scanning %s: %s", dep, err) 438 } 439 continue 440 } else { 441 imps = pkg.Imports 442 } 443 444 // Range over all of the identified imports and see which ones we 445 // can locate. 446 for _, imp := range imps { 447 pi := r.FindPkg(imp) 448 if pi.Loc != LocCgo && pi.Loc != LocGoroot && pi.Loc != LocAppengine { 449 msg.Debug("Package %s imports %s", dep, imp) 450 } 451 switch pi.Loc { 452 case LocVendor: 453 msg.Debug("In vendor: %s", imp) 454 if _, ok := r.alreadyQ[imp]; !ok { 455 msg.Debug("Marking %s to be scanned.", imp) 456 r.alreadyQ[imp] = true 457 queue.PushBack(r.vpath(imp)) 458 if err := r.Handler.InVendor(imp); err == nil { 459 r.VersionHandler.SetVersion(imp) 460 } else { 461 msg.Warn("Error updating %s: %s", imp, err) 462 } 463 r.VersionHandler.SetVersion(imp) 464 } 465 case LocUnknown: 466 msg.Debug("Missing %s. Trying to resolve.", imp) 467 if ok, err := r.Handler.NotFound(imp); ok { 468 r.alreadyQ[imp] = true 469 queue.PushBack(r.vpath(imp)) 470 r.VersionHandler.SetVersion(imp) 471 } else if err != nil { 472 r.hadError[dep] = true 473 msg.Warn("Error looking for %s: %s", imp, err) 474 } else { 475 r.hadError[dep] = true 476 msg.Info("Not found: %s (2)", imp) 477 } 478 case LocGopath: 479 msg.Debug("Found on GOPATH, not vendor: %s", imp) 480 if _, ok := r.alreadyQ[imp]; !ok { 481 // Only scan it if it gets moved into vendor/ 482 if ok, _ := r.Handler.OnGopath(imp); ok { 483 r.alreadyQ[imp] = true 484 queue.PushBack(r.vpath(imp)) 485 r.VersionHandler.SetVersion(imp) 486 } 487 } 488 } 489 } 490 491 } 492 493 // FIXME: From here to the end is a straight copy of the resolveList() func. 494 res := make([]string, 0, queue.Len()) 495 496 // In addition to generating a list 497 for e := queue.Front(); e != nil; e = e.Next() { 498 t := r.stripv(e.Value.(string)) 499 root, sp := util.NormalizeName(t) 500 501 // TODO(mattfarina): Need to eventually support devImport 502 existing := r.Config.Imports.Get(root) 503 if existing != nil { 504 if sp != "" && !existing.HasSubpackage(sp) { 505 existing.Subpackages = append(existing.Subpackages, sp) 506 } 507 } else { 508 newDep := &cfg.Dependency{ 509 Name: root, 510 } 511 if sp != "" { 512 newDep.Subpackages = []string{sp} 513 } 514 515 r.Config.Imports = append(r.Config.Imports, newDep) 516 } 517 res = append(res, t) 518 } 519 520 return res, nil 521 } 522 523 // resolveList takes a list and resolves it. 524 // 525 // This walks the entire file tree for the given dependencies, not just the 526 // parts that are imported directly. Using this will discover dependencies 527 // regardless of OS, and arch. 528 func (r *Resolver) resolveList(queue *list.List) ([]string, error) { 529 530 var failedDep string 531 for e := queue.Front(); e != nil; e = e.Next() { 532 dep := e.Value.(string) 533 t := strings.TrimPrefix(dep, r.VendorDir+string(os.PathSeparator)) 534 if r.Config.HasIgnore(t) { 535 msg.Info("Ignoring: %s", t) 536 continue 537 } 538 r.VersionHandler.Process(t) 539 //msg.Warn("#### %s ####", dep) 540 //msg.Info("Seen Count: %d", len(r.seen)) 541 // Catch the outtermost dependency. 542 failedDep = dep 543 err := filepath.Walk(dep, func(path string, fi os.FileInfo, err error) error { 544 if err != nil && err != filepath.SkipDir { 545 return err 546 } 547 548 // Skip files. 549 if !fi.IsDir() { 550 return nil 551 } 552 // Skip dirs that are not source. 553 if !srcDir(fi) { 554 //msg.Debug("Skip resource %s", fi.Name()) 555 return filepath.SkipDir 556 } 557 558 // Anything that comes through here has already been through 559 // the queue. 560 r.alreadyQ[path] = true 561 e := r.queueUnseen(path, queue) 562 if err != nil { 563 failedDep = path 564 //msg.Err("Failed to fetch dependency %s: %s", path, err) 565 } 566 return e 567 }) 568 if err != nil && err != filepath.SkipDir { 569 msg.Err("Dependency %s failed to resolve: %s.", failedDep, err) 570 return []string{}, err 571 } 572 } 573 574 res := make([]string, 0, queue.Len()) 575 576 // In addition to generating a list 577 for e := queue.Front(); e != nil; e = e.Next() { 578 t := strings.TrimPrefix(e.Value.(string), r.VendorDir+string(os.PathSeparator)) 579 root, sp := util.NormalizeName(t) 580 581 // TODO(mattfarina): Need to eventually support devImport 582 existing := r.Config.Imports.Get(root) 583 if existing != nil { 584 if sp != "" && !existing.HasSubpackage(sp) { 585 existing.Subpackages = append(existing.Subpackages, sp) 586 } 587 } else { 588 newDep := &cfg.Dependency{ 589 Name: root, 590 } 591 if sp != "" { 592 newDep.Subpackages = []string{sp} 593 } 594 595 r.Config.Imports = append(r.Config.Imports, newDep) 596 } 597 res = append(res, e.Value.(string)) 598 } 599 600 return res, nil 601 } 602 603 // queueUnseenImports scans a package's imports and adds any new ones to the 604 // processing queue. 605 func (r *Resolver) queueUnseen(pkg string, queue *list.List) error { 606 // A pkg is marked "seen" as soon as we have inspected it the first time. 607 // Seen means that we have added all of its imports to the list. 608 609 // Already queued indicates that we've either already put it into the queue 610 // or intentionally not put it in the queue for fatal reasons (e.g. no 611 // buildable source). 612 613 deps, err := r.imports(pkg) 614 if err != nil && !strings.HasPrefix(err.Error(), "no buildable Go source") { 615 msg.Err("Could not find %s: %s", pkg, err) 616 return err 617 // NOTE: If we uncomment this, we get lots of "no buildable Go source" errors, 618 // which don't ever seem to be helpful. They don't actually indicate an error 619 // condition, and it's perfectly okay to run into that condition. 620 //} else if err != nil { 621 // msg.Warn(err.Error()) 622 } 623 624 for _, d := range deps { 625 if _, ok := r.alreadyQ[d]; !ok { 626 r.alreadyQ[d] = true 627 queue.PushBack(d) 628 } 629 } 630 return nil 631 } 632 633 // imports gets all of the imports for a given package. 634 // 635 // If the package is in GOROOT, this will return an empty list (but not 636 // an error). 637 // If it cannot resolve the pkg, it will return an error. 638 func (r *Resolver) imports(pkg string) ([]string, error) { 639 640 if r.Config.HasIgnore(pkg) { 641 msg.Debug("Ignoring %s", pkg) 642 return []string{}, nil 643 } 644 645 // If this pkg is marked seen, we don't scan it again. 646 if _, ok := r.seen[pkg]; ok { 647 msg.Debug("Already saw %s", pkg) 648 return []string{}, nil 649 } 650 651 // FIXME: On error this should try to NotFound to the dependency, and then import 652 // it again. 653 var imps []string 654 p, err := r.BuildContext.ImportDir(pkg, 0) 655 if err != nil && strings.HasPrefix(err.Error(), "found packages ") { 656 // If we got here it's because a package and multiple packages 657 // declared. This is often because of an example with a package 658 // or main but +build ignore as a build tag. In that case we 659 // try to brute force the packages with a slower scan. 660 imps, err = IterativeScan(pkg) 661 if err != nil { 662 return []string{}, err 663 } 664 } else if err != nil { 665 return []string{}, err 666 } else { 667 imps = p.Imports 668 } 669 670 // It is okay to scan a package more than once. In some cases, this is 671 // desirable because the package can change between scans (e.g. as a result 672 // of a failed scan resolving the situation). 673 msg.Debug("=> Scanning %s (%s)", p.ImportPath, pkg) 674 r.seen[pkg] = true 675 676 // Optimization: If it's in GOROOT, it has no imports worth scanning. 677 if p.Goroot { 678 return []string{}, nil 679 } 680 681 // We are only looking for dependencies in vendor. No root, cgo, etc. 682 buf := []string{} 683 for _, imp := range imps { 684 if r.Config.HasIgnore(imp) { 685 msg.Debug("Ignoring %s", imp) 686 continue 687 } 688 info := r.FindPkg(imp) 689 switch info.Loc { 690 case LocUnknown: 691 // Do we resolve here? 692 found, err := r.Handler.NotFound(imp) 693 if err != nil { 694 msg.Err("Failed to fetch %s: %s", imp, err) 695 } 696 if found { 697 buf = append(buf, filepath.Join(r.VendorDir, filepath.FromSlash(imp))) 698 r.VersionHandler.SetVersion(imp) 699 continue 700 } 701 r.seen[info.Path] = true 702 case LocVendor: 703 //msg.Debug("Vendored: %s", imp) 704 buf = append(buf, info.Path) 705 if err := r.Handler.InVendor(imp); err == nil { 706 r.VersionHandler.SetVersion(imp) 707 } else { 708 msg.Warn("Error updating %s: %s", imp, err) 709 } 710 case LocGopath: 711 found, err := r.Handler.OnGopath(imp) 712 if err != nil { 713 msg.Err("Failed to fetch %s: %s", imp, err) 714 } 715 // If the Handler marks this as found, we drop it into the buffer 716 // for subsequent processing. Otherwise, we assume that we're 717 // in a less-than-perfect, but functional, situation. 718 if found { 719 buf = append(buf, filepath.Join(r.VendorDir, filepath.FromSlash(imp))) 720 r.VersionHandler.SetVersion(imp) 721 continue 722 } 723 msg.Warn("Package %s is on GOPATH, but not vendored. Ignoring.", imp) 724 r.seen[info.Path] = true 725 default: 726 // Local packages are an odd case. CGO cannot be scanned. 727 msg.Debug("===> Skipping %s", imp) 728 } 729 } 730 731 return buf, nil 732 } 733 734 // sliceToQueue is a special-purpose function for unwrapping a slice of 735 // dependencies into a queue of fully qualified paths. 736 func sliceToQueue(deps []*cfg.Dependency, basepath string) *list.List { 737 l := list.New() 738 for _, e := range deps { 739 l.PushBack(filepath.Join(basepath, filepath.FromSlash(e.Name))) 740 } 741 return l 742 } 743 744 // PkgLoc describes the location of the package. 745 type PkgLoc uint8 746 747 const ( 748 // LocUnknown indicates the package location is unknown (probably not present) 749 LocUnknown PkgLoc = iota 750 // LocLocal inidcates that the package is in a local dir, not GOPATH or GOROOT. 751 LocLocal 752 // LocVendor indicates that the package is in a vendor/ dir 753 LocVendor 754 // LocGopath inidcates that the package is in GOPATH 755 LocGopath 756 // LocGoroot indicates that the package is in GOROOT 757 LocGoroot 758 // LocCgo indicates that the package is a a CGO package 759 LocCgo 760 // LocAppengine indicates the package is part of the appengine SDK. It's a 761 // special build mode. https://blog.golang.org/the-app-engine-sdk-and-workspaces-gopath 762 // Why does a Google product get a special case build mode with a local 763 // package? 764 LocAppengine 765 ) 766 767 // PkgInfo represents metadata about a package found by the resolver. 768 type PkgInfo struct { 769 Name, Path string 770 Vendored bool 771 Loc PkgLoc 772 } 773 774 // FindPkg takes a package name and attempts to find it on the filesystem 775 // 776 // The resulting PkgInfo will indicate where it was found. 777 func (r *Resolver) FindPkg(name string) *PkgInfo { 778 // We cachae results for FindPkg to reduce the number of filesystem ops 779 // that we have to do. This is a little risky because certain directories, 780 // like GOPATH, can be modified while we're running an operation, and 781 // render the cache inaccurate. 782 // 783 // Unfound items (LocUnknown) are never cached because we assume that as 784 // part of the response, the Resolver may fetch that dependency. 785 if i, ok := r.findCache[name]; ok { 786 //msg.Info("Cache hit on %s", name) 787 return i 788 } 789 790 // 502 individual packages scanned. 791 // No cache: 792 // glide -y etcd.yaml list 0.27s user 0.19s system 85% cpu 0.534 total 793 // With cache: 794 // glide -y etcd.yaml list 0.22s user 0.15s system 85% cpu 0.438 total 795 796 var p string 797 info := &PkgInfo{ 798 Name: name, 799 } 800 801 // Check _only_ if this dep is in the current vendor directory. 802 p = filepath.Join(r.VendorDir, filepath.FromSlash(name)) 803 if pkgExists(p) { 804 info.Path = p 805 info.Loc = LocVendor 806 info.Vendored = true 807 r.findCache[name] = info 808 return info 809 } 810 811 // TODO: Do we need this if we always flatten? 812 // Recurse backward to scan other vendor/ directories 813 //for wd := cwd; wd != "/"; wd = filepath.Dir(wd) { 814 //p = filepath.Join(wd, "vendor", filepath.FromSlash(name)) 815 //if fi, err = os.Stat(p); err == nil && (fi.IsDir() || isLink(fi)) { 816 //info.Path = p 817 //info.PType = ptypeVendor 818 //info.Vendored = true 819 //return info 820 //} 821 //} 822 823 // Check $GOPATH 824 for _, rr := range filepath.SplitList(r.BuildContext.GOPATH) { 825 p = filepath.Join(rr, "src", filepath.FromSlash(name)) 826 if pkgExists(p) { 827 info.Path = p 828 info.Loc = LocGopath 829 r.findCache[name] = info 830 return info 831 } 832 } 833 834 // Check $GOROOT 835 for _, rr := range filepath.SplitList(r.BuildContext.GOROOT) { 836 p = filepath.Join(rr, "src", filepath.FromSlash(name)) 837 if pkgExists(p) { 838 info.Path = p 839 info.Loc = LocGoroot 840 r.findCache[name] = info 841 return info 842 } 843 } 844 845 // If this is "C", we're dealing with cgo 846 if name == "C" { 847 info.Loc = LocCgo 848 r.findCache[name] = info 849 } else if name == "appengine" || name == "appengine_internal" || 850 strings.HasPrefix(name, "appengine/") || 851 strings.HasPrefix(name, "appengine_internal/") { 852 // Appengine is a special case when it comes to Go builds. It is a local 853 // looking package only available within appengine. It's a special case 854 // where Google products are playing with each other. 855 // https://blog.golang.org/the-app-engine-sdk-and-workspaces-gopath 856 info.Loc = LocAppengine 857 r.findCache[name] = info 858 } 859 860 return info 861 } 862 863 func pkgExists(path string) bool { 864 fi, err := os.Stat(path) 865 return err == nil && (fi.IsDir() || isLink(fi)) 866 } 867 868 // isLink returns true if the given FileInfo is a symbolic link. 869 func isLink(fi os.FileInfo) bool { 870 return fi.Mode()&os.ModeSymlink == os.ModeSymlink 871 } 872 873 // IsSrcDir returns true if this is a directory that could have source code, 874 // false otherwise. 875 // 876 // Directories with _ or . prefixes are skipped, as are testdata and vendor. 877 func IsSrcDir(fi os.FileInfo) bool { 878 return srcDir(fi) 879 } 880 881 func srcDir(fi os.FileInfo) bool { 882 if !fi.IsDir() { 883 return false 884 } 885 886 // Ignore _foo and .foo 887 if strings.HasPrefix(fi.Name(), "_") || strings.HasPrefix(fi.Name(), ".") { 888 return false 889 } 890 891 // Ignore testdata. For now, ignore vendor. 892 if fi.Name() == "testdata" || fi.Name() == "vendor" { 893 return false 894 } 895 896 return true 897 }