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