github.com/szyn/glide@v0.12.2/repo/installer.go (about) 1 package repo 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "path/filepath" 8 "runtime" 9 "strings" 10 "sync" 11 "syscall" 12 "time" 13 14 "github.com/Masterminds/glide/cache" 15 "github.com/Masterminds/glide/cfg" 16 "github.com/Masterminds/glide/dependency" 17 "github.com/Masterminds/glide/importer" 18 "github.com/Masterminds/glide/msg" 19 gpath "github.com/Masterminds/glide/path" 20 "github.com/Masterminds/glide/util" 21 "github.com/Masterminds/semver" 22 "github.com/Masterminds/vcs" 23 "github.com/codegangsta/cli" 24 ) 25 26 // Installer provides facilities for installing the repos in a config file. 27 type Installer struct { 28 29 // Force the install when certain normally stopping conditions occur. 30 Force bool 31 32 // Home is the location of cache 33 Home string 34 35 // Vendor contains the path to put the vendor packages 36 Vendor string 37 38 // ResolveAllFiles enables a resolver that will examine the dependencies 39 // of every file of every package, rather than only following imported 40 // packages. 41 ResolveAllFiles bool 42 43 // ResolveTest sets if test dependencies should be resolved. 44 ResolveTest bool 45 46 // Updated tracks the packages that have been remotely fetched. 47 Updated *UpdateTracker 48 } 49 50 // NewInstaller returns an Installer instance ready to use. This is the constructor. 51 func NewInstaller() *Installer { 52 i := &Installer{} 53 i.Updated = NewUpdateTracker() 54 return i 55 } 56 57 // VendorPath returns the path to the location to put vendor packages 58 func (i *Installer) VendorPath() string { 59 if i.Vendor != "" { 60 return i.Vendor 61 } 62 63 vp, err := gpath.Vendor() 64 if err != nil { 65 return filepath.FromSlash("./vendor") 66 } 67 68 return vp 69 } 70 71 // Install installs the dependencies from a Lockfile. 72 func (i *Installer) Install(lock *cfg.Lockfile, conf *cfg.Config) (*cfg.Config, error) { 73 74 // Create a config setup based on the Lockfile data to process with 75 // existing commands. 76 newConf := &cfg.Config{} 77 newConf.Name = conf.Name 78 79 newConf.Imports = make(cfg.Dependencies, len(lock.Imports)) 80 for k, v := range lock.Imports { 81 newConf.Imports[k] = cfg.DependencyFromLock(v) 82 } 83 84 newConf.DevImports = make(cfg.Dependencies, len(lock.DevImports)) 85 for k, v := range lock.DevImports { 86 newConf.DevImports[k] = cfg.DependencyFromLock(v) 87 } 88 89 newConf.DeDupe() 90 91 if len(newConf.Imports) == 0 && len(newConf.DevImports) == 0 { 92 msg.Info("No dependencies found. Nothing installed.") 93 return newConf, nil 94 } 95 96 msg.Info("Downloading dependencies. Please wait...") 97 98 err := LazyConcurrentUpdate(newConf.Imports, i, newConf) 99 if err != nil { 100 return newConf, err 101 } 102 err = LazyConcurrentUpdate(newConf.DevImports, i, newConf) 103 104 return newConf, err 105 } 106 107 // Checkout reads the config file and checks out all dependencies mentioned there. 108 // 109 // This is used when initializing an empty vendor directory, or when updating a 110 // vendor directory based on changed config. 111 func (i *Installer) Checkout(conf *cfg.Config) error { 112 113 msg.Info("Downloading dependencies. Please wait...") 114 115 if err := ConcurrentUpdate(conf.Imports, i, conf); err != nil { 116 return err 117 } 118 119 if i.ResolveTest { 120 return ConcurrentUpdate(conf.DevImports, i, conf) 121 } 122 123 return nil 124 } 125 126 // Update updates all dependencies. 127 // 128 // It begins with the dependencies in the config file, but also resolves 129 // transitive dependencies. The returned lockfile has all of the dependencies 130 // listed, but the version reconciliation has not been done. 131 // 132 // In other words, all versions in the Lockfile will be empty. 133 func (i *Installer) Update(conf *cfg.Config) error { 134 base := "." 135 136 ic := newImportCache() 137 138 m := &MissingPackageHandler{ 139 home: i.Home, 140 force: i.Force, 141 Config: conf, 142 Use: ic, 143 updated: i.Updated, 144 } 145 146 v := &VersionHandler{ 147 Use: ic, 148 Imported: make(map[string]bool), 149 Conflicts: make(map[string]bool), 150 Config: conf, 151 } 152 153 // Update imports 154 res, err := dependency.NewResolver(base) 155 res.ResolveTest = i.ResolveTest 156 if err != nil { 157 msg.Die("Failed to create a resolver: %s", err) 158 } 159 res.Config = conf 160 res.Handler = m 161 res.VersionHandler = v 162 res.ResolveAllFiles = i.ResolveAllFiles 163 msg.Info("Resolving imports") 164 165 imps, timps, err := res.ResolveLocal(false) 166 if err != nil { 167 msg.Die("Failed to resolve local packages: %s", err) 168 } 169 var deps cfg.Dependencies 170 var tdeps cfg.Dependencies 171 for _, v := range imps { 172 n := res.Stripv(v) 173 if conf.HasIgnore(n) { 174 continue 175 } 176 rt, sub := util.NormalizeName(n) 177 if sub == "" { 178 sub = "." 179 } 180 d := deps.Get(rt) 181 if d == nil { 182 nd := &cfg.Dependency{ 183 Name: rt, 184 Subpackages: []string{sub}, 185 } 186 deps = append(deps, nd) 187 } else if !d.HasSubpackage(sub) { 188 d.Subpackages = append(d.Subpackages, sub) 189 } 190 } 191 if i.ResolveTest { 192 for _, v := range timps { 193 n := res.Stripv(v) 194 if conf.HasIgnore(n) { 195 continue 196 } 197 rt, sub := util.NormalizeName(n) 198 if sub == "" { 199 sub = "." 200 } 201 d := deps.Get(rt) 202 if d == nil { 203 d = tdeps.Get(rt) 204 } 205 if d == nil { 206 nd := &cfg.Dependency{ 207 Name: rt, 208 Subpackages: []string{sub}, 209 } 210 tdeps = append(tdeps, nd) 211 } else if !d.HasSubpackage(sub) { 212 d.Subpackages = append(d.Subpackages, sub) 213 } 214 } 215 } 216 217 _, err = allPackages(deps, res, false) 218 if err != nil { 219 msg.Die("Failed to retrieve a list of dependencies: %s", err) 220 } 221 222 if i.ResolveTest { 223 msg.Debug("Resolving test dependencies") 224 _, err = allPackages(tdeps, res, true) 225 if err != nil { 226 msg.Die("Failed to retrieve a list of test dependencies: %s", err) 227 } 228 } 229 230 msg.Info("Downloading dependencies. Please wait...") 231 232 err = ConcurrentUpdate(conf.Imports, i, conf) 233 if err != nil { 234 return err 235 } 236 237 if i.ResolveTest { 238 err = ConcurrentUpdate(conf.DevImports, i, conf) 239 if err != nil { 240 return err 241 } 242 } 243 244 return nil 245 } 246 247 // Export from the cache to the vendor directory 248 func (i *Installer) Export(conf *cfg.Config) error { 249 tempDir, err := ioutil.TempDir(gpath.Tmp, "glide-vendor") 250 if err != nil { 251 return err 252 } 253 defer func() { 254 err = os.RemoveAll(tempDir) 255 if err != nil { 256 msg.Err(err.Error()) 257 } 258 }() 259 260 vp := filepath.Join(tempDir, "vendor") 261 err = os.MkdirAll(vp, 0755) 262 263 msg.Info("Exporting resolved dependencies...") 264 done := make(chan struct{}, concurrentWorkers) 265 in := make(chan *cfg.Dependency, concurrentWorkers) 266 var wg sync.WaitGroup 267 var lock sync.Mutex 268 var returnErr error 269 270 for ii := 0; ii < concurrentWorkers; ii++ { 271 go func(ch <-chan *cfg.Dependency) { 272 for { 273 select { 274 case dep := <-ch: 275 loc := dep.Remote() 276 key, err := cache.Key(loc) 277 if err != nil { 278 msg.Die(err.Error()) 279 } 280 cache.Lock(key) 281 282 cdir := filepath.Join(cache.Location(), "src", key) 283 repo, err := dep.GetRepo(cdir) 284 if err != nil { 285 msg.Die(err.Error()) 286 } 287 msg.Info("--> Exporting %s", dep.Name) 288 if err := repo.ExportDir(filepath.Join(vp, filepath.ToSlash(dep.Name))); err != nil { 289 msg.Err("Export failed for %s: %s\n", dep.Name, err) 290 // Capture the error while making sure the concurrent 291 // operations don't step on each other. 292 lock.Lock() 293 if returnErr == nil { 294 returnErr = err 295 } else { 296 returnErr = cli.NewMultiError(returnErr, err) 297 } 298 lock.Unlock() 299 } 300 cache.Unlock(key) 301 wg.Done() 302 case <-done: 303 return 304 } 305 } 306 }(in) 307 } 308 309 for _, dep := range conf.Imports { 310 if !conf.HasIgnore(dep.Name) { 311 err = os.MkdirAll(filepath.Join(vp, filepath.ToSlash(dep.Name)), 0755) 312 if err != nil { 313 lock.Lock() 314 if returnErr == nil { 315 returnErr = err 316 } else { 317 returnErr = cli.NewMultiError(returnErr, err) 318 } 319 lock.Unlock() 320 } 321 wg.Add(1) 322 in <- dep 323 } 324 } 325 326 if i.ResolveTest { 327 for _, dep := range conf.DevImports { 328 if !conf.HasIgnore(dep.Name) { 329 err = os.MkdirAll(filepath.Join(vp, filepath.ToSlash(dep.Name)), 0755) 330 if err != nil { 331 lock.Lock() 332 if returnErr == nil { 333 returnErr = err 334 } else { 335 returnErr = cli.NewMultiError(returnErr, err) 336 } 337 lock.Unlock() 338 } 339 wg.Add(1) 340 in <- dep 341 } 342 } 343 } 344 345 wg.Wait() 346 347 // Close goroutines setting the version 348 for ii := 0; ii < concurrentWorkers; ii++ { 349 done <- struct{}{} 350 } 351 352 if returnErr != nil { 353 return returnErr 354 } 355 356 msg.Info("Replacing existing vendor dependencies") 357 err = os.RemoveAll(i.VendorPath()) 358 if err != nil { 359 return err 360 } 361 362 err = os.Rename(vp, i.VendorPath()) 363 364 if err != nil { 365 // When there are different physical devices we cannot rename cross device. 366 // Instead we copy. 367 switch terr := err.(type) { 368 case *os.LinkError: 369 // syscall.EXDEV is the common name for the cross device link error 370 // which has varying output text across different operating systems. 371 if terr.Err == syscall.EXDEV { 372 msg.Debug("Cross link err, trying manual copy: %s", err) 373 return gpath.CopyDir(vp, i.VendorPath()) 374 } else if runtime.GOOS == "windows" { 375 // In windows it can drop down to an operating system call that 376 // returns an operating system error with a different number and 377 // message. Checking for that as a fall back. 378 noerr, ok := terr.Err.(syscall.Errno) 379 // 0x11 (ERROR_NOT_SAME_DEVICE) is the windows error. 380 // See https://msdn.microsoft.com/en-us/library/cc231199.aspx 381 if ok && noerr == 0x11 { 382 msg.Debug("Cross link err on Windows, trying manual copy: %s", err) 383 return gpath.CopyDir(vp, i.VendorPath()) 384 } 385 } 386 } 387 } 388 389 return err 390 391 } 392 393 // List resolves the complete dependency tree and returns a list of dependencies. 394 func (i *Installer) List(conf *cfg.Config) []*cfg.Dependency { 395 base := "." 396 397 ic := newImportCache() 398 399 v := &VersionHandler{ 400 Use: ic, 401 Imported: make(map[string]bool), 402 Conflicts: make(map[string]bool), 403 Config: conf, 404 } 405 406 // Update imports 407 res, err := dependency.NewResolver(base) 408 if err != nil { 409 msg.Die("Failed to create a resolver: %s", err) 410 } 411 res.Config = conf 412 res.VersionHandler = v 413 res.ResolveAllFiles = i.ResolveAllFiles 414 415 msg.Info("Resolving imports") 416 _, _, err = res.ResolveLocal(false) 417 if err != nil { 418 msg.Die("Failed to resolve local packages: %s", err) 419 } 420 421 _, err = allPackages(conf.Imports, res, false) 422 if err != nil { 423 msg.Die("Failed to retrieve a list of dependencies: %s", err) 424 } 425 426 if len(conf.DevImports) > 0 { 427 msg.Warn("dev imports not resolved.") 428 } 429 430 return conf.Imports 431 } 432 433 // LazyConcurrentUpdate updates only deps that are not already checkout out at the right version. 434 // 435 // This is only safe when updating from a lock file. 436 func LazyConcurrentUpdate(deps []*cfg.Dependency, i *Installer, c *cfg.Config) error { 437 438 newDeps := []*cfg.Dependency{} 439 for _, dep := range deps { 440 441 key, err := cache.Key(dep.Remote()) 442 if err != nil { 443 newDeps = append(newDeps, dep) 444 continue 445 } 446 destPath := filepath.Join(cache.Location(), "src", key) 447 448 // Get a VCS object for this directory 449 repo, err := dep.GetRepo(destPath) 450 if err != nil { 451 newDeps = append(newDeps, dep) 452 continue 453 } 454 455 ver, err := repo.Version() 456 if err != nil { 457 newDeps = append(newDeps, dep) 458 continue 459 } 460 if dep.Reference != "" { 461 ci, err := repo.CommitInfo(dep.Reference) 462 if err == nil && ci.Commit == dep.Reference { 463 msg.Info("--> Found desired version locally %s %s!", dep.Name, dep.Reference) 464 continue 465 } 466 } 467 468 msg.Debug("--> Queue %s for update (%s != %s).", dep.Name, ver, dep.Reference) 469 newDeps = append(newDeps, dep) 470 } 471 if len(newDeps) > 0 { 472 return ConcurrentUpdate(newDeps, i, c) 473 } 474 475 return nil 476 } 477 478 // ConcurrentUpdate takes a list of dependencies and updates in parallel. 479 func ConcurrentUpdate(deps []*cfg.Dependency, i *Installer, c *cfg.Config) error { 480 done := make(chan struct{}, concurrentWorkers) 481 in := make(chan *cfg.Dependency, concurrentWorkers) 482 var wg sync.WaitGroup 483 var lock sync.Mutex 484 var returnErr error 485 486 for ii := 0; ii < concurrentWorkers; ii++ { 487 go func(ch <-chan *cfg.Dependency) { 488 for { 489 select { 490 case dep := <-ch: 491 loc := dep.Remote() 492 key, err := cache.Key(loc) 493 if err != nil { 494 msg.Die(err.Error()) 495 } 496 cache.Lock(key) 497 if err := VcsUpdate(dep, i.Force, i.Updated); err != nil { 498 msg.Err("Update failed for %s: %s\n", dep.Name, err) 499 // Capture the error while making sure the concurrent 500 // operations don't step on each other. 501 lock.Lock() 502 if returnErr == nil { 503 returnErr = err 504 } else { 505 returnErr = cli.NewMultiError(returnErr, err) 506 } 507 lock.Unlock() 508 } 509 cache.Unlock(key) 510 wg.Done() 511 case <-done: 512 return 513 } 514 } 515 }(in) 516 } 517 518 for _, dep := range deps { 519 if !c.HasIgnore(dep.Name) { 520 wg.Add(1) 521 in <- dep 522 } 523 } 524 525 wg.Wait() 526 527 // Close goroutines setting the version 528 for ii := 0; ii < concurrentWorkers; ii++ { 529 done <- struct{}{} 530 } 531 532 return returnErr 533 } 534 535 // allPackages gets a list of all packages required to satisfy the given deps. 536 func allPackages(deps []*cfg.Dependency, res *dependency.Resolver, addTest bool) ([]string, error) { 537 if len(deps) == 0 { 538 return []string{}, nil 539 } 540 541 vdir, err := gpath.Vendor() 542 if err != nil { 543 return []string{}, err 544 } 545 vdir += string(os.PathSeparator) 546 ll, err := res.ResolveAll(deps, addTest) 547 if err != nil { 548 return []string{}, err 549 } 550 551 for i := 0; i < len(ll); i++ { 552 ll[i] = strings.TrimPrefix(ll[i], vdir) 553 } 554 return ll, nil 555 } 556 557 // MissingPackageHandler is a dependency.MissingPackageHandler. 558 // 559 // When a package is not found, this attempts to resolve and fetch. 560 // 561 // When a package is found on the GOPATH, this notifies the user. 562 type MissingPackageHandler struct { 563 home string 564 force bool 565 Config *cfg.Config 566 Use *importCache 567 updated *UpdateTracker 568 } 569 570 // NotFound attempts to retrieve a package when not found in the local cache 571 // folder. It will attempt to get it from the remote location info. 572 func (m *MissingPackageHandler) NotFound(pkg string, addTest bool) (bool, error) { 573 err := m.fetchToCache(pkg, addTest) 574 if err != nil { 575 return false, err 576 } 577 578 return true, err 579 } 580 581 // OnGopath will either copy a package, already found in the GOPATH, to the 582 // vendor/ directory or download it from the internet. This is dependent if 583 // useGopath on the installer is set to true to copy from the GOPATH. 584 func (m *MissingPackageHandler) OnGopath(pkg string, addTest bool) (bool, error) { 585 586 err := m.fetchToCache(pkg, addTest) 587 if err != nil { 588 return false, err 589 } 590 591 return true, err 592 } 593 594 // InVendor updates a package in the vendor/ directory to make sure the latest 595 // is available. 596 func (m *MissingPackageHandler) InVendor(pkg string, addTest bool) error { 597 return m.fetchToCache(pkg, addTest) 598 } 599 600 // PkgPath resolves the location on the filesystem where the package should be. 601 // This handles making sure to use the cache location. 602 func (m *MissingPackageHandler) PkgPath(pkg string) string { 603 root, sub := util.NormalizeName(pkg) 604 605 // For the parent applications source skip the cache. 606 if root == m.Config.Name { 607 pth := gpath.Basepath() 608 return filepath.Join(pth, filepath.FromSlash(sub)) 609 } 610 611 d := m.Config.Imports.Get(root) 612 if d == nil { 613 d = m.Config.DevImports.Get(root) 614 } 615 616 if d == nil { 617 d, _ = m.Use.Get(root) 618 619 if d == nil { 620 d = &cfg.Dependency{Name: root} 621 } 622 } 623 624 key, err := cache.Key(d.Remote()) 625 if err != nil { 626 msg.Die("Error generating cache key for %s", d.Name) 627 } 628 629 return filepath.Join(cache.Location(), "src", key, filepath.FromSlash(sub)) 630 } 631 632 func (m *MissingPackageHandler) fetchToCache(pkg string, addTest bool) error { 633 root := util.GetRootFromPackage(pkg) 634 // Skip any references to the root package. 635 if root == m.Config.Name { 636 return nil 637 } 638 639 d := m.Config.Imports.Get(root) 640 if d == nil && addTest { 641 d = m.Config.DevImports.Get(root) 642 } 643 644 // If the dependency is nil it means the Config doesn't yet know about it. 645 if d == nil { 646 d, _ = m.Use.Get(root) 647 // We don't know about this dependency so we create a basic instance. 648 if d == nil { 649 d = &cfg.Dependency{Name: root} 650 } 651 652 if addTest { 653 m.Config.DevImports = append(m.Config.DevImports, d) 654 } else { 655 m.Config.Imports = append(m.Config.Imports, d) 656 } 657 } 658 659 return VcsUpdate(d, m.force, m.updated) 660 } 661 662 // VersionHandler handles setting the proper version in the VCS. 663 type VersionHandler struct { 664 665 // If Try to use the version here if we have one. This is a cache and will 666 // change over the course of setting versions. 667 Use *importCache 668 669 // Cache if importing scan has already occurred here. 670 Imported map[string]bool 671 672 Config *cfg.Config 673 674 // There's a problem where many sub-packages have been asked to set a version 675 // and you can end up with numerous conflict messages that are exactly the 676 // same. We are keeping track to only display them once. 677 // the parent pac 678 Conflicts map[string]bool 679 } 680 681 // Process imports dependencies for a package 682 func (d *VersionHandler) Process(pkg string) (e error) { 683 root := util.GetRootFromPackage(pkg) 684 685 // Skip any references to the root package. 686 if root == d.Config.Name { 687 return nil 688 } 689 690 // We have not tried to import, yet. 691 // Should we look in places other than the root of the project? 692 if d.Imported[root] == false { 693 d.Imported[root] = true 694 p := d.pkgPath(root) 695 f, deps, err := importer.Import(p) 696 if f && err == nil { 697 for _, dep := range deps { 698 699 // The fist one wins. Would something smater than this be better? 700 exists, _ := d.Use.Get(dep.Name) 701 if exists == nil && (dep.Reference != "" || dep.Repository != "") { 702 d.Use.Add(dep.Name, dep, root) 703 } 704 } 705 } else if err != nil { 706 msg.Err("Unable to import from %s. Err: %s", root, err) 707 e = err 708 } 709 } 710 711 return 712 } 713 714 // SetVersion sets the version for a package. If that package version is already 715 // set it handles the case by: 716 // - keeping the already set version 717 // - proviting messaging about the version conflict 718 // TODO(mattfarina): The way version setting happens can be improved. Currently not optimal. 719 func (d *VersionHandler) SetVersion(pkg string, addTest bool) (e error) { 720 root := util.GetRootFromPackage(pkg) 721 722 // Skip any references to the root package. 723 if root == d.Config.Name { 724 return nil 725 } 726 727 v := d.Config.Imports.Get(root) 728 if addTest { 729 if v == nil { 730 v = d.Config.DevImports.Get(root) 731 } else if d.Config.DevImports.Has(root) { 732 // Both imports and test imports lists the same dependency. 733 // There are import chains (because the import tree is resolved 734 // before the test tree) that can cause this. 735 tempD := d.Config.DevImports.Get(root) 736 if tempD.Reference != v.Reference { 737 msg.Warn("Using import %s (version %s) for test instead of testImport (version %s).", v.Name, v.Reference, tempD.Reference) 738 } 739 // TODO(mattfarina): Note repo difference in a warning. 740 } 741 } 742 743 dep, req := d.Use.Get(root) 744 if dep != nil && v != nil { 745 if v.Reference == "" && dep.Reference != "" { 746 v.Reference = dep.Reference 747 // Clear the pin, if set, so the new version can be used. 748 v.Pin = "" 749 dep = v 750 } else if v.Reference != "" && dep.Reference != "" && v.Reference != dep.Reference { 751 dest := d.pkgPath(pkg) 752 dep = determineDependency(v, dep, dest, req) 753 } else { 754 dep = v 755 } 756 757 } else if v != nil { 758 dep = v 759 } else if dep != nil { 760 // We've got an imported dependency to use and don't already have a 761 // record of it. Append it to the Imports. 762 if addTest { 763 d.Config.DevImports = append(d.Config.DevImports, dep) 764 } else { 765 d.Config.Imports = append(d.Config.Imports, dep) 766 } 767 } else { 768 // If we've gotten here we don't have any depenency objects. 769 r, sp := util.NormalizeName(pkg) 770 dep = &cfg.Dependency{ 771 Name: r, 772 } 773 if sp != "" { 774 dep.Subpackages = []string{sp} 775 } 776 if addTest { 777 d.Config.DevImports = append(d.Config.DevImports, dep) 778 } else { 779 d.Config.Imports = append(d.Config.Imports, dep) 780 } 781 } 782 783 err := VcsVersion(dep) 784 if err != nil { 785 msg.Warn("Unable to set version on %s to %s. Err: %s", root, dep.Reference, err) 786 e = err 787 } 788 789 return 790 } 791 792 func (d *VersionHandler) pkgPath(pkg string) string { 793 root, sub := util.NormalizeName(pkg) 794 795 // For the parent applications source skip the cache. 796 if root == d.Config.Name { 797 pth := gpath.Basepath() 798 return filepath.Join(pth, filepath.FromSlash(sub)) 799 } 800 801 dep := d.Config.Imports.Get(root) 802 if dep == nil { 803 dep = d.Config.DevImports.Get(root) 804 } 805 806 if dep == nil { 807 dep, _ = d.Use.Get(root) 808 809 if dep == nil { 810 dep = &cfg.Dependency{Name: root} 811 } 812 } 813 814 key, err := cache.Key(dep.Remote()) 815 if err != nil { 816 msg.Die("Error generating cache key for %s", dep.Name) 817 } 818 819 return filepath.Join(cache.Location(), "src", key, filepath.FromSlash(sub)) 820 } 821 822 func determineDependency(v, dep *cfg.Dependency, dest, req string) *cfg.Dependency { 823 repo, err := v.GetRepo(dest) 824 if err != nil { 825 singleWarn("Unable to access repo for %s\n", v.Name) 826 singleInfo("Keeping %s %s", v.Name, v.Reference) 827 return v 828 } 829 830 vIsRef := repo.IsReference(v.Reference) 831 depIsRef := repo.IsReference(dep.Reference) 832 833 // Both are references and they are different ones. 834 if vIsRef && depIsRef { 835 singleWarn("Conflict: %s rev is currently %s, but %s wants %s\n", v.Name, v.Reference, req, dep.Reference) 836 837 displayCommitInfo(repo, v) 838 displayCommitInfo(repo, dep) 839 840 singleInfo("Keeping %s %s", v.Name, v.Reference) 841 return v 842 } else if vIsRef { 843 // The current one is a reference and the suggestion is a SemVer constraint. 844 con, err := semver.NewConstraint(dep.Reference) 845 if err != nil { 846 singleWarn("Version issue for %s: '%s' is neither a reference or semantic version constraint\n", dep.Name, dep.Reference) 847 singleInfo("Keeping %s %s", v.Name, v.Reference) 848 return v 849 } 850 851 ver, err := semver.NewVersion(v.Reference) 852 if err != nil { 853 // The existing version is not a semantic version. 854 singleWarn("Conflict: %s version is %s, but also asked for %s\n", v.Name, v.Reference, dep.Reference) 855 displayCommitInfo(repo, v) 856 singleInfo("Keeping %s %s", v.Name, v.Reference) 857 return v 858 } 859 860 if con.Check(ver) { 861 singleInfo("Keeping %s %s because it fits constraint '%s'", v.Name, v.Reference, dep.Reference) 862 return v 863 } 864 singleWarn("Conflict: %s version is %s but does not meet constraint '%s'\n", v.Name, v.Reference, dep.Reference) 865 singleInfo("Keeping %s %s", v.Name, v.Reference) 866 return v 867 } else if depIsRef { 868 869 con, err := semver.NewConstraint(v.Reference) 870 if err != nil { 871 singleWarn("Version issue for %s: '%s' is neither a reference or semantic version constraint\n", v.Name, v.Reference) 872 singleInfo("Keeping %s %s", v.Name, v.Reference) 873 return v 874 } 875 876 ver, err := semver.NewVersion(dep.Reference) 877 if err != nil { 878 singleWarn("Conflict: %s version is %s, but also asked for %s\n", v.Name, v.Reference, dep.Reference) 879 displayCommitInfo(repo, dep) 880 singleInfo("Keeping %s %s", v.Name, v.Reference) 881 return v 882 } 883 884 if con.Check(ver) { 885 v.Reference = dep.Reference 886 singleInfo("Using %s %s because it fits constraint '%s'", v.Name, v.Reference, v.Reference) 887 return v 888 } 889 singleWarn("Conflict: %s semantic version constraint is %s but '%s' does not meet the constraint\n", v.Name, v.Reference, v.Reference) 890 singleInfo("Keeping %s %s", v.Name, v.Reference) 891 return v 892 } 893 // Neither is a vcs reference and both could be semantic version 894 // constraints that are different. 895 896 _, err = semver.NewConstraint(dep.Reference) 897 if err != nil { 898 // dd.Reference is not a reference or a valid constraint. 899 singleWarn("Version %s %s is not a reference or valid semantic version constraint\n", dep.Name, dep.Reference) 900 singleInfo("Keeping %s %s", v.Name, v.Reference) 901 return v 902 } 903 904 _, err = semver.NewConstraint(v.Reference) 905 if err != nil { 906 // existing.Reference is not a reference or a valid constraint. 907 // We really should never end up here. 908 singleWarn("Version %s %s is not a reference or valid semantic version constraint\n", v.Name, v.Reference) 909 910 v.Reference = dep.Reference 911 v.Pin = "" 912 singleInfo("Using %s %s because it is a valid version", v.Name, v.Reference) 913 return v 914 } 915 916 // Both versions are constraints. Try to merge them. 917 // If either comparison has an || skip merging. That's complicated. 918 ddor := strings.Index(dep.Reference, "||") 919 eor := strings.Index(v.Reference, "||") 920 if ddor == -1 && eor == -1 { 921 // Add the comparisons together. 922 newRef := v.Reference + ", " + dep.Reference 923 v.Reference = newRef 924 v.Pin = "" 925 singleInfo("Combining %s semantic version constraints %s and %s", v.Name, v.Reference, dep.Reference) 926 return v 927 } 928 singleWarn("Conflict: %s version is %s, but also asked for %s\n", v.Name, v.Reference, dep.Reference) 929 singleInfo("Keeping %s %s", v.Name, v.Reference) 930 return v 931 } 932 933 var warningMessage = make(map[string]bool) 934 var infoMessage = make(map[string]bool) 935 936 func singleWarn(ft string, v ...interface{}) { 937 m := fmt.Sprintf(ft, v...) 938 _, f := warningMessage[m] 939 if !f { 940 msg.Warn(m) 941 warningMessage[m] = true 942 } 943 } 944 945 func singleInfo(ft string, v ...interface{}) { 946 m := fmt.Sprintf(ft, v...) 947 _, f := infoMessage[m] 948 if !f { 949 msg.Info(m) 950 infoMessage[m] = true 951 } 952 } 953 954 type importCache struct { 955 cache map[string]*cfg.Dependency 956 from map[string]string 957 } 958 959 func newImportCache() *importCache { 960 return &importCache{ 961 cache: make(map[string]*cfg.Dependency), 962 from: make(map[string]string), 963 } 964 } 965 966 func (i *importCache) Get(name string) (*cfg.Dependency, string) { 967 d, f := i.cache[name] 968 if f { 969 return d, i.from[name] 970 } 971 972 return nil, "" 973 } 974 975 func (i *importCache) Add(name string, dep *cfg.Dependency, root string) { 976 i.cache[name] = dep 977 i.from[name] = root 978 } 979 980 var displayCommitInfoPrefix = msg.Default.Color(msg.Green, "[INFO] ") 981 var displayCommitInfoTemplate = "%s reference %s:\n" + 982 displayCommitInfoPrefix + "- author: %s\n" + 983 displayCommitInfoPrefix + "- commit date: %s\n" + 984 displayCommitInfoPrefix + "- subject (first line): %s\n" 985 986 func displayCommitInfo(repo vcs.Repo, dep *cfg.Dependency) { 987 c, err := repo.CommitInfo(dep.Reference) 988 ref := dep.Reference 989 990 if err == nil { 991 tgs, err2 := repo.TagsFromCommit(c.Commit) 992 if err2 == nil && len(tgs) > 0 { 993 if tgs[0] != dep.Reference { 994 ref = ref + " (" + tgs[0] + ")" 995 } 996 } 997 singleInfo(displayCommitInfoTemplate, dep.Name, ref, c.Author, c.Date.Format(time.RFC1123Z), commitSubjectFirstLine(c.Message)) 998 } 999 } 1000 1001 func commitSubjectFirstLine(sub string) string { 1002 lines := strings.Split(sub, "\n") 1003 if len(lines) <= 1 { 1004 return sub 1005 } 1006 1007 return lines[0] 1008 }