github.com/fibonacci1729/glide@v0.0.0-20160513190140-d9640dc62d0f/repo/installer.go (about) 1 package repo 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "strings" 8 "sync" 9 "time" 10 11 "github.com/Masterminds/glide/cfg" 12 "github.com/Masterminds/glide/dependency" 13 "github.com/Masterminds/glide/importer" 14 "github.com/Masterminds/glide/msg" 15 gpath "github.com/Masterminds/glide/path" 16 "github.com/Masterminds/glide/util" 17 "github.com/Masterminds/semver" 18 "github.com/Masterminds/vcs" 19 "github.com/codegangsta/cli" 20 ) 21 22 // Installer provides facilities for installing the repos in a config file. 23 type Installer struct { 24 25 // Force the install when certain normally stopping conditions occur. 26 Force bool 27 28 // Home is the location of cache 29 Home string 30 31 // Vendor contains the path to put the vendor packages 32 Vendor string 33 34 // Use a cache 35 UseCache bool 36 // Use Gopath to cache 37 UseCacheGopath bool 38 // Use Gopath as a source to read from 39 UseGopath bool 40 41 // UpdateVendored instructs the environment to update in a way that is friendly 42 // to packages that have been "vendored in" (e.g. are copies of source, not repos) 43 UpdateVendored bool 44 45 // DeleteUnused deletes packages that are unused, but found in the vendor dir. 46 DeleteUnused bool 47 48 // ResolveAllFiles enables a resolver that will examine the dependencies 49 // of every file of every package, rather than only following imported 50 // packages. 51 ResolveAllFiles bool 52 53 // Updated tracks the packages that have been remotely fetched. 54 Updated *UpdateTracker 55 } 56 57 func NewInstaller() *Installer { 58 i := &Installer{} 59 i.Updated = NewUpdateTracker() 60 return i 61 } 62 63 // VendorPath returns the path to the location to put vendor packages 64 func (i *Installer) VendorPath() string { 65 if i.Vendor != "" { 66 return i.Vendor 67 } 68 69 vp, err := gpath.Vendor() 70 if err != nil { 71 return filepath.FromSlash("./vendor") 72 } 73 74 return vp 75 } 76 77 // Install installs the dependencies from a Lockfile. 78 func (i *Installer) Install(lock *cfg.Lockfile, conf *cfg.Config) (*cfg.Config, error) { 79 80 cwd, err := gpath.Vendor() 81 if err != nil { 82 return conf, err 83 } 84 85 // Create a config setup based on the Lockfile data to process with 86 // existing commands. 87 newConf := &cfg.Config{} 88 newConf.Name = conf.Name 89 90 newConf.Imports = make(cfg.Dependencies, len(lock.Imports)) 91 for k, v := range lock.Imports { 92 newConf.Imports[k] = &cfg.Dependency{ 93 Name: v.Name, 94 Reference: v.Version, 95 Repository: v.Repository, 96 VcsType: v.VcsType, 97 Subpackages: v.Subpackages, 98 Arch: v.Arch, 99 Os: v.Os, 100 } 101 } 102 103 newConf.DevImports = make(cfg.Dependencies, len(lock.DevImports)) 104 for k, v := range lock.DevImports { 105 newConf.DevImports[k] = &cfg.Dependency{ 106 Name: v.Name, 107 Reference: v.Version, 108 Repository: v.Repository, 109 VcsType: v.VcsType, 110 Subpackages: v.Subpackages, 111 Arch: v.Arch, 112 Os: v.Os, 113 } 114 } 115 116 newConf.DeDupe() 117 118 if len(newConf.Imports) == 0 { 119 msg.Info("No dependencies found. Nothing installed.\n") 120 return newConf, nil 121 } 122 123 ConcurrentUpdate(newConf.Imports, cwd, i, newConf) 124 ConcurrentUpdate(newConf.DevImports, cwd, i, newConf) 125 return newConf, nil 126 } 127 128 // Checkout reads the config file and checks out all dependencies mentioned there. 129 // 130 // This is used when initializing an empty vendor directory, or when updating a 131 // vendor directory based on changed config. 132 func (i *Installer) Checkout(conf *cfg.Config, useDev bool) error { 133 134 dest := i.VendorPath() 135 136 if err := ConcurrentUpdate(conf.Imports, dest, i, conf); err != nil { 137 return err 138 } 139 140 if useDev { 141 return ConcurrentUpdate(conf.DevImports, dest, i, conf) 142 } 143 144 return nil 145 } 146 147 // Update updates all dependencies. 148 // 149 // It begins with the dependencies in the config file, but also resolves 150 // transitive dependencies. The returned lockfile has all of the dependencies 151 // listed, but the version reconciliation has not been done. 152 // 153 // In other words, all versions in the Lockfile will be empty. 154 func (i *Installer) Update(conf *cfg.Config) error { 155 base := "." 156 vpath := i.VendorPath() 157 158 ic := newImportCache() 159 160 m := &MissingPackageHandler{ 161 destination: vpath, 162 163 cache: i.UseCache, 164 cacheGopath: i.UseCacheGopath, 165 useGopath: i.UseGopath, 166 home: i.Home, 167 force: i.Force, 168 updateVendored: i.UpdateVendored, 169 Config: conf, 170 Use: ic, 171 updated: i.Updated, 172 } 173 174 v := &VersionHandler{ 175 Destination: vpath, 176 Use: ic, 177 Imported: make(map[string]bool), 178 Conflicts: make(map[string]bool), 179 Config: conf, 180 } 181 182 // Update imports 183 res, err := dependency.NewResolver(base) 184 if err != nil { 185 msg.Die("Failed to create a resolver: %s", err) 186 } 187 res.Config = conf 188 res.Handler = m 189 res.VersionHandler = v 190 res.ResolveAllFiles = i.ResolveAllFiles 191 msg.Info("Resolving imports") 192 _, err = allPackages(conf.Imports, res) 193 if err != nil { 194 msg.Die("Failed to retrieve a list of dependencies: %s", err) 195 } 196 197 if len(conf.DevImports) > 0 { 198 msg.Warn("dev imports not resolved.") 199 } 200 201 err = ConcurrentUpdate(conf.Imports, vpath, i, conf) 202 203 return err 204 } 205 206 // List resolves the complete dependency tree and returns a list of dependencies. 207 func (i *Installer) List(conf *cfg.Config) []*cfg.Dependency { 208 base := "." 209 vpath := i.VendorPath() 210 211 ic := newImportCache() 212 213 v := &VersionHandler{ 214 Destination: vpath, 215 Use: ic, 216 Imported: make(map[string]bool), 217 Conflicts: make(map[string]bool), 218 Config: conf, 219 } 220 221 // Update imports 222 res, err := dependency.NewResolver(base) 223 if err != nil { 224 msg.Die("Failed to create a resolver: %s", err) 225 } 226 res.Config = conf 227 res.VersionHandler = v 228 res.ResolveAllFiles = i.ResolveAllFiles 229 230 msg.Info("Resolving imports") 231 _, err = allPackages(conf.Imports, res) 232 if err != nil { 233 msg.Die("Failed to retrieve a list of dependencies: %s", err) 234 } 235 236 if len(conf.DevImports) > 0 { 237 msg.Warn("dev imports not resolved.") 238 } 239 240 return conf.Imports 241 } 242 243 // ConcurrentUpdate takes a list of dependencies and updates in parallel. 244 func ConcurrentUpdate(deps []*cfg.Dependency, cwd string, i *Installer, c *cfg.Config) error { 245 done := make(chan struct{}, concurrentWorkers) 246 in := make(chan *cfg.Dependency, concurrentWorkers) 247 var wg sync.WaitGroup 248 var lock sync.Mutex 249 var returnErr error 250 251 msg.Info("Downloading dependencies. Please wait...") 252 253 for ii := 0; ii < concurrentWorkers; ii++ { 254 go func(ch <-chan *cfg.Dependency) { 255 for { 256 select { 257 case dep := <-ch: 258 dest := filepath.Join(i.VendorPath(), dep.Name) 259 if err := VcsUpdate(dep, dest, i.Home, i.UseCache, i.UseCacheGopath, i.UseGopath, i.Force, i.UpdateVendored, i.Updated); err != nil { 260 msg.Err("Update failed for %s: %s\n", dep.Name, err) 261 // Capture the error while making sure the concurrent 262 // operations don't step on each other. 263 lock.Lock() 264 if returnErr == nil { 265 returnErr = err 266 } else { 267 returnErr = cli.NewMultiError(returnErr, err) 268 } 269 lock.Unlock() 270 } 271 wg.Done() 272 case <-done: 273 return 274 } 275 } 276 }(in) 277 } 278 279 for _, dep := range deps { 280 if !c.HasIgnore(dep.Name) { 281 wg.Add(1) 282 in <- dep 283 } 284 } 285 286 wg.Wait() 287 288 // Close goroutines setting the version 289 for ii := 0; ii < concurrentWorkers; ii++ { 290 done <- struct{}{} 291 } 292 293 return returnErr 294 } 295 296 // allPackages gets a list of all packages required to satisfy the given deps. 297 func allPackages(deps []*cfg.Dependency, res *dependency.Resolver) ([]string, error) { 298 if len(deps) == 0 { 299 return []string{}, nil 300 } 301 302 vdir, err := gpath.Vendor() 303 if err != nil { 304 return []string{}, err 305 } 306 vdir += string(os.PathSeparator) 307 ll, err := res.ResolveAll(deps) 308 if err != nil { 309 return []string{}, err 310 } 311 312 for i := 0; i < len(ll); i++ { 313 ll[i] = strings.TrimPrefix(ll[i], vdir) 314 } 315 return ll, nil 316 } 317 318 // MissingPackageHandler is a dependency.MissingPackageHandler. 319 // 320 // When a package is not found, this attempts to resolve and fetch. 321 // 322 // When a package is found on the GOPATH, this notifies the user. 323 type MissingPackageHandler struct { 324 destination string 325 home string 326 cache, cacheGopath, useGopath, force, updateVendored bool 327 Config *cfg.Config 328 Use *importCache 329 updated *UpdateTracker 330 } 331 332 // NotFound attempts to retrieve a package when not found in the local vendor/ 333 // folder. It will attempt to get it from the remote location info. 334 func (m *MissingPackageHandler) NotFound(pkg string) (bool, error) { 335 root := util.GetRootFromPackage(pkg) 336 337 // Skip any references to the root package. 338 if root == m.Config.Name { 339 return false, nil 340 } 341 342 dest := filepath.Join(m.destination, root) 343 344 // This package may have been placed on the list to look for when it wasn't 345 // downloaded but it has since been downloaded before coming to this entry. 346 if _, err := os.Stat(dest); err == nil { 347 // Make sure the location contains files. It may be an empty directory. 348 empty, err := gpath.IsDirectoryEmpty(dest) 349 if err != nil { 350 return false, err 351 } 352 if empty { 353 msg.Warn("%s is an existing location with no files. Fetching a new copy of the dependency.", dest) 354 msg.Debug("Removing empty directory %s", dest) 355 err := os.RemoveAll(dest) 356 if err != nil { 357 msg.Debug("Installer error removing directory %s: %s", dest, err) 358 return false, err 359 } 360 } else { 361 msg.Debug("Found %s", dest) 362 return true, nil 363 } 364 } 365 366 msg.Info("Fetching %s into %s", pkg, m.destination) 367 368 d := m.Config.Imports.Get(root) 369 // If the dependency is nil it means the Config doesn't yet know about it. 370 if d == nil { 371 d, _ = m.Use.Get(root) 372 // We don't know about this dependency so we create a basic instance. 373 if d == nil { 374 d = &cfg.Dependency{Name: root} 375 } 376 377 m.Config.Imports = append(m.Config.Imports, d) 378 } 379 if err := VcsGet(d, dest, m.home, m.cache, m.cacheGopath, m.useGopath); err != nil { 380 return false, err 381 } 382 return true, nil 383 } 384 385 // OnGopath will either copy a package, already found in the GOPATH, to the 386 // vendor/ directory or download it from the internet. This is dependent if 387 // useGopath on the installer is set to true to copy from the GOPATH. 388 func (m *MissingPackageHandler) OnGopath(pkg string) (bool, error) { 389 // If useGopath is false, we fall back to the strategy of fetching from 390 // remote. 391 if !m.useGopath { 392 return m.NotFound(pkg) 393 } 394 395 root := util.GetRootFromPackage(pkg) 396 397 // Skip any references to the root package. 398 if root == m.Config.Name { 399 return false, nil 400 } 401 402 msg.Info("Copying package %s from the GOPATH.", pkg) 403 dest := filepath.Join(m.destination, pkg) 404 // Find package on Gopath 405 for _, gp := range gpath.Gopaths() { 406 src := filepath.Join(gp, pkg) 407 // FIXME: Should probably check if src is a dir or symlink. 408 if _, err := os.Stat(src); err == nil { 409 if err := os.MkdirAll(dest, os.ModeDir|0755); err != nil { 410 return false, err 411 } 412 if err := gpath.CopyDir(src, dest); err != nil { 413 return false, err 414 } 415 return true, nil 416 } 417 } 418 419 msg.Err("Could not locate %s on the GOPATH, though it was found before.", pkg) 420 return false, nil 421 } 422 423 // InVendor updates a package in the vendor/ directory to make sure the latest 424 // is available. 425 func (m *MissingPackageHandler) InVendor(pkg string) error { 426 root := util.GetRootFromPackage(pkg) 427 428 // Skip any references to the root package. 429 if root == m.Config.Name { 430 return nil 431 } 432 433 dest := filepath.Join(m.destination, root) 434 435 d := m.Config.Imports.Get(root) 436 // If the dependency is nil it means the Config doesn't yet know about it. 437 if d == nil { 438 d, _ = m.Use.Get(root) 439 // We don't know about this dependency so we create a basic instance. 440 if d == nil { 441 d = &cfg.Dependency{Name: root} 442 } 443 444 m.Config.Imports = append(m.Config.Imports, d) 445 } 446 447 if err := VcsUpdate(d, dest, m.home, m.cache, m.cacheGopath, m.useGopath, m.force, m.updateVendored, m.updated); err != nil { 448 return err 449 } 450 451 return nil 452 } 453 454 // VersionHandler handles setting the proper version in the VCS. 455 type VersionHandler struct { 456 457 // If Try to use the version here if we have one. This is a cache and will 458 // change over the course of setting versions. 459 Use *importCache 460 461 // Cache if importing scan has already occurred here. 462 Imported map[string]bool 463 464 // Where the packages exist to set the version on. 465 Destination string 466 467 Config *cfg.Config 468 469 // There's a problem where many sub-packages have been asked to set a version 470 // and you can end up with numerous conflict messages that are exactly the 471 // same. We are keeping track to only display them once. 472 // the parent pac 473 Conflicts map[string]bool 474 } 475 476 // Process imports dependencies for a package 477 func (d *VersionHandler) Process(pkg string) (e error) { 478 root := util.GetRootFromPackage(pkg) 479 480 // Skip any references to the root package. 481 if root == d.Config.Name { 482 return nil 483 } 484 485 // We have not tried to import, yet. 486 // Should we look in places other than the root of the project? 487 if d.Imported[root] == false { 488 d.Imported[root] = true 489 p := filepath.Join(d.Destination, root) 490 f, deps, err := importer.Import(p) 491 if f && err == nil { 492 for _, dep := range deps { 493 494 // The fist one wins. Would something smater than this be better? 495 exists, _ := d.Use.Get(dep.Name) 496 if exists == nil && (dep.Reference != "" || dep.Repository != "") { 497 d.Use.Add(dep.Name, dep, root) 498 } 499 } 500 } else if err != nil { 501 msg.Err("Unable to import from %s. Err: %s", root, err) 502 e = err 503 } 504 } 505 506 return 507 } 508 509 // SetVersion sets the version for a package. If that package version is already 510 // set it handles the case by: 511 // - keeping the already set version 512 // - proviting messaging about the version conflict 513 // TODO(mattfarina): The way version setting happens can be improved. Currently not optimal. 514 func (d *VersionHandler) SetVersion(pkg string) (e error) { 515 root := util.GetRootFromPackage(pkg) 516 517 // Skip any references to the root package. 518 if root == d.Config.Name { 519 return nil 520 } 521 522 v := d.Config.Imports.Get(root) 523 524 dep, req := d.Use.Get(root) 525 if dep != nil && v != nil { 526 if v.Reference == "" && dep.Reference != "" { 527 v.Reference = dep.Reference 528 // Clear the pin, if set, so the new version can be used. 529 v.Pin = "" 530 dep = v 531 } else if v.Reference != "" && dep.Reference != "" && v.Reference != dep.Reference { 532 dest := filepath.Join(d.Destination, filepath.FromSlash(v.Name)) 533 dep = determineDependency(v, dep, dest, req) 534 } else { 535 dep = v 536 } 537 538 } else if v != nil { 539 dep = v 540 } else if dep != nil { 541 // We've got an imported dependency to use and don't already have a 542 // record of it. Append it to the Imports. 543 d.Config.Imports = append(d.Config.Imports, dep) 544 } else { 545 // If we've gotten here we don't have any depenency objects. 546 r, sp := util.NormalizeName(pkg) 547 dep = &cfg.Dependency{ 548 Name: r, 549 } 550 if sp != "" { 551 dep.Subpackages = []string{sp} 552 } 553 d.Config.Imports = append(d.Config.Imports, dep) 554 } 555 556 err := VcsVersion(dep, d.Destination) 557 if err != nil { 558 msg.Warn("Unable to set version on %s to %s. Err: %s", root, dep.Reference, err) 559 e = err 560 } 561 562 return 563 } 564 565 func determineDependency(v, dep *cfg.Dependency, dest, req string) *cfg.Dependency { 566 repo, err := v.GetRepo(dest) 567 if err != nil { 568 singleWarn("Unable to access repo for %s\n", v.Name) 569 singleInfo("Keeping %s %s", v.Name, v.Reference) 570 return v 571 } 572 573 vIsRef := repo.IsReference(v.Reference) 574 depIsRef := repo.IsReference(dep.Reference) 575 576 // Both are references and they are different ones. 577 if vIsRef && depIsRef { 578 singleWarn("Conflict: %s rev is currently %s, but %s wants %s\n", v.Name, v.Reference, req, dep.Reference) 579 580 displayCommitInfo(repo, v) 581 displayCommitInfo(repo, dep) 582 583 singleInfo("Keeping %s %s", v.Name, v.Reference) 584 return v 585 } else if vIsRef { 586 // The current one is a reference and the suggestion is a SemVer constraint. 587 con, err := semver.NewConstraint(dep.Reference) 588 if err != nil { 589 singleWarn("Version issue for %s: '%s' is neither a reference or semantic version constraint\n", dep.Name, dep.Reference) 590 singleInfo("Keeping %s %s", v.Name, v.Reference) 591 return v 592 } 593 594 ver, err := semver.NewVersion(v.Reference) 595 if err != nil { 596 // The existing version is not a semantic version. 597 singleWarn("Conflict: %s version is %s, but also asked for %s\n", v.Name, v.Reference, dep.Reference) 598 displayCommitInfo(repo, v) 599 singleInfo("Keeping %s %s", v.Name, v.Reference) 600 return v 601 } 602 603 if con.Check(ver) { 604 singleInfo("Keeping %s %s because it fits constraint '%s'", v.Name, v.Reference, dep.Reference) 605 return v 606 } 607 singleWarn("Conflict: %s version is %s but does not meet constraint '%s'\n", v.Name, v.Reference, dep.Reference) 608 singleInfo("Keeping %s %s", v.Name, v.Reference) 609 return v 610 } else if depIsRef { 611 612 con, err := semver.NewConstraint(v.Reference) 613 if err != nil { 614 singleWarn("Version issue for %s: '%s' is neither a reference or semantic version constraint\n", v.Name, v.Reference) 615 singleInfo("Keeping %s %s", v.Name, v.Reference) 616 return v 617 } 618 619 ver, err := semver.NewVersion(dep.Reference) 620 if err != nil { 621 singleWarn("Conflict: %s version is %s, but also asked for %s\n", v.Name, v.Reference, dep.Reference) 622 displayCommitInfo(repo, dep) 623 singleInfo("Keeping %s %s", v.Name, v.Reference) 624 return v 625 } 626 627 if con.Check(ver) { 628 v.Reference = dep.Reference 629 singleInfo("Using %s %s because it fits constraint '%s'", v.Name, v.Reference, v.Reference) 630 return v 631 } 632 singleWarn("Conflict: %s semantic version constraint is %s but '%s' does not meet the constraint\n", v.Name, v.Reference, v.Reference) 633 singleInfo("Keeping %s %s", v.Name, v.Reference) 634 return v 635 } 636 // Neither is a vcs reference and both could be semantic version 637 // constraints that are different. 638 639 _, err = semver.NewConstraint(dep.Reference) 640 if err != nil { 641 // dd.Reference is not a reference or a valid constraint. 642 singleWarn("Version %s %s is not a reference or valid semantic version constraint\n", dep.Name, dep.Reference) 643 singleInfo("Keeping %s %s", v.Name, v.Reference) 644 return v 645 } 646 647 _, err = semver.NewConstraint(v.Reference) 648 if err != nil { 649 // existing.Reference is not a reference or a valid constraint. 650 // We really should never end up here. 651 singleWarn("Version %s %s is not a reference or valid semantic version constraint\n", v.Name, v.Reference) 652 653 v.Reference = dep.Reference 654 v.Pin = "" 655 singleInfo("Using %s %s because it is a valid version", v.Name, v.Reference) 656 return v 657 } 658 659 // Both versions are constraints. Try to merge them. 660 // If either comparison has an || skip merging. That's complicated. 661 ddor := strings.Index(dep.Reference, "||") 662 eor := strings.Index(v.Reference, "||") 663 if ddor == -1 && eor == -1 { 664 // Add the comparisons together. 665 newRef := v.Reference + ", " + dep.Reference 666 v.Reference = newRef 667 v.Pin = "" 668 singleInfo("Combining %s semantic version constraints %s and %s", v.Name, v.Reference, dep.Reference) 669 return v 670 } 671 singleWarn("Conflict: %s version is %s, but also asked for %s\n", v.Name, v.Reference, dep.Reference) 672 singleInfo("Keeping %s %s", v.Name, v.Reference) 673 return v 674 } 675 676 var warningMessage = make(map[string]bool) 677 var infoMessage = make(map[string]bool) 678 679 func singleWarn(ft string, v ...interface{}) { 680 m := fmt.Sprintf(ft, v...) 681 _, f := warningMessage[m] 682 if !f { 683 msg.Warn(m) 684 warningMessage[m] = true 685 } 686 } 687 688 func singleInfo(ft string, v ...interface{}) { 689 m := fmt.Sprintf(ft, v...) 690 _, f := infoMessage[m] 691 if !f { 692 msg.Info(m) 693 infoMessage[m] = true 694 } 695 } 696 697 type importCache struct { 698 cache map[string]*cfg.Dependency 699 from map[string]string 700 } 701 702 func newImportCache() *importCache { 703 return &importCache{ 704 cache: make(map[string]*cfg.Dependency), 705 from: make(map[string]string), 706 } 707 } 708 709 func (i *importCache) Get(name string) (*cfg.Dependency, string) { 710 d, f := i.cache[name] 711 if f { 712 return d, i.from[name] 713 } 714 715 return nil, "" 716 } 717 718 func (i *importCache) Add(name string, dep *cfg.Dependency, root string) { 719 i.cache[name] = dep 720 i.from[name] = root 721 } 722 723 var displayCommitInfoPrefix = msg.Default.Color(msg.Green, "[INFO] ") 724 var displayCommitInfoTemplate = "%s reference %s:\n" + 725 displayCommitInfoPrefix + "- author: %s\n" + 726 displayCommitInfoPrefix + "- commit date: %s\n" + 727 displayCommitInfoPrefix + "- subject (first line): %s\n" 728 729 func displayCommitInfo(repo vcs.Repo, dep *cfg.Dependency) { 730 c, err := repo.CommitInfo(dep.Reference) 731 if err == nil { 732 singleInfo(displayCommitInfoTemplate, dep.Name, dep.Reference, c.Author, c.Date.Format(time.RFC1123Z), commitSubjectFirstLine(c.Message)) 733 } 734 } 735 736 func commitSubjectFirstLine(sub string) string { 737 lines := strings.Split(sub, "\n") 738 if len(lines) <= 1 { 739 return sub 740 } 741 742 return lines[0] 743 }