github.com/masterminds/glide@v0.13.4-0.20190710143844-b94b39d657d8/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  
   358  	// Check if a .git directory exists under the old vendor dir. If it does,
   359  	// move it over to the newly-generated vendor dir - the user is probably
   360  	// submoduling, and it's easy enough not to break their setup.
   361  	ivg := filepath.Join(i.VendorPath(), ".git")
   362  	gitInfo, err := os.Stat(ivg)
   363  	if err == nil {
   364  		msg.Info("Preserving existing vendor/.git")
   365  		vpg := filepath.Join(vp, ".git")
   366  		err = os.Rename(ivg, vpg)
   367  
   368  		if terr, ok := err.(*os.LinkError); ok {
   369  			if gitInfo.IsDir() {
   370  				err = fixcle(ivg, vpg, terr)
   371  			} else {
   372  				// When this is a submodule, .git is just a file. Don't try to copy
   373  				// it as a directory in this case (see #828).
   374  				err = gpath.CopyFile(ivg, vpg)
   375  			}
   376  
   377  			if err != nil {
   378  				msg.Warn("Failed to preserve existing vendor/.git")
   379  			}
   380  		}
   381  	}
   382  
   383  	err = gpath.CustomRemoveAll(i.VendorPath())
   384  	if err != nil {
   385  		return err
   386  	}
   387  
   388  	err = gpath.CustomRename(vp, i.VendorPath())
   389  	if terr, ok := err.(*os.LinkError); ok {
   390  		return fixcle(vp, i.VendorPath(), terr)
   391  	}
   392  
   393  	return err
   394  
   395  }
   396  
   397  // fixcle is a helper function that tries to recover from cross-device rename
   398  // errors by falling back to copying.
   399  func fixcle(from, to string, terr *os.LinkError) error {
   400  	// When there are different physical devices we cannot rename cross device.
   401  	// Instead we copy.
   402  
   403  	// syscall.EXDEV is the common name for the cross device link error
   404  	// which has varying output text across different operating systems.
   405  	if terr.Err == syscall.EXDEV {
   406  		msg.Debug("Cross link err, trying manual copy: %s", terr)
   407  		return gpath.CopyDir(from, to)
   408  	} else if runtime.GOOS == "windows" {
   409  		// In windows it can drop down to an operating system call that
   410  		// returns an operating system error with a different number and
   411  		// message. Checking for that as a fall back.
   412  		noerr, ok := terr.Err.(syscall.Errno)
   413  		// 0x11 (ERROR_NOT_SAME_DEVICE) is the windows error.
   414  		// See https://msdn.microsoft.com/en-us/library/cc231199.aspx
   415  		if ok && noerr == 0x11 {
   416  			msg.Debug("Cross link err on Windows, trying manual copy: %s", terr)
   417  			return gpath.CopyDir(from, to)
   418  		}
   419  	}
   420  
   421  	return terr
   422  }
   423  
   424  // List resolves the complete dependency tree and returns a list of dependencies.
   425  func (i *Installer) List(conf *cfg.Config) []*cfg.Dependency {
   426  	base := "."
   427  
   428  	ic := newImportCache()
   429  
   430  	v := &VersionHandler{
   431  		Use:       ic,
   432  		Imported:  make(map[string]bool),
   433  		Conflicts: make(map[string]bool),
   434  		Config:    conf,
   435  	}
   436  
   437  	// Update imports
   438  	res, err := dependency.NewResolver(base)
   439  	if err != nil {
   440  		msg.Die("Failed to create a resolver: %s", err)
   441  	}
   442  	res.Config = conf
   443  	res.VersionHandler = v
   444  	res.ResolveAllFiles = i.ResolveAllFiles
   445  
   446  	msg.Info("Resolving imports")
   447  	_, _, err = res.ResolveLocal(false)
   448  	if err != nil {
   449  		msg.Die("Failed to resolve local packages: %s", err)
   450  	}
   451  
   452  	_, err = allPackages(conf.Imports, res, false)
   453  	if err != nil {
   454  		msg.Die("Failed to retrieve a list of dependencies: %s", err)
   455  	}
   456  
   457  	if len(conf.DevImports) > 0 {
   458  		msg.Warn("dev imports not resolved.")
   459  	}
   460  
   461  	return conf.Imports
   462  }
   463  
   464  // LazyConcurrentUpdate updates only deps that are not already checkout out at the right version.
   465  //
   466  // This is only safe when updating from a lock file.
   467  func LazyConcurrentUpdate(deps []*cfg.Dependency, i *Installer, c *cfg.Config) error {
   468  
   469  	newDeps := []*cfg.Dependency{}
   470  	for _, dep := range deps {
   471  
   472  		key, err := cache.Key(dep.Remote())
   473  		if err != nil {
   474  			newDeps = append(newDeps, dep)
   475  			continue
   476  		}
   477  		destPath := filepath.Join(cache.Location(), "src", key)
   478  
   479  		// Get a VCS object for this directory
   480  		repo, err := dep.GetRepo(destPath)
   481  		if err != nil {
   482  			newDeps = append(newDeps, dep)
   483  			continue
   484  		}
   485  
   486  		ver, err := repo.Version()
   487  		if err != nil {
   488  			newDeps = append(newDeps, dep)
   489  			continue
   490  		}
   491  		if dep.Reference != "" {
   492  			ci, err := repo.CommitInfo(dep.Reference)
   493  			if err == nil && ci.Commit == dep.Reference {
   494  				msg.Info("--> Found desired version locally %s %s!", dep.Name, dep.Reference)
   495  				continue
   496  			}
   497  		}
   498  
   499  		msg.Debug("--> Queue %s for update (%s != %s).", dep.Name, ver, dep.Reference)
   500  		newDeps = append(newDeps, dep)
   501  	}
   502  	if len(newDeps) > 0 {
   503  		return ConcurrentUpdate(newDeps, i, c)
   504  	}
   505  
   506  	return nil
   507  }
   508  
   509  // ConcurrentUpdate takes a list of dependencies and updates in parallel.
   510  func ConcurrentUpdate(deps []*cfg.Dependency, i *Installer, c *cfg.Config) error {
   511  	done := make(chan struct{}, concurrentWorkers)
   512  	in := make(chan *cfg.Dependency, concurrentWorkers)
   513  	var wg sync.WaitGroup
   514  	var lock sync.Mutex
   515  	var returnErr error
   516  
   517  	for ii := 0; ii < concurrentWorkers; ii++ {
   518  		go func(ch <-chan *cfg.Dependency) {
   519  			for {
   520  				select {
   521  				case dep := <-ch:
   522  					loc := dep.Remote()
   523  					key, err := cache.Key(loc)
   524  					if err != nil {
   525  						msg.Die(err.Error())
   526  					}
   527  					cache.Lock(key)
   528  					if err := VcsUpdate(dep, i.Force, i.Updated); err != nil {
   529  						msg.Err("Update failed for %s: %s\n", dep.Name, err)
   530  						// Capture the error while making sure the concurrent
   531  						// operations don't step on each other.
   532  						lock.Lock()
   533  						if returnErr == nil {
   534  							returnErr = err
   535  						} else {
   536  							returnErr = cli.NewMultiError(returnErr, err)
   537  						}
   538  						lock.Unlock()
   539  					}
   540  					cache.Unlock(key)
   541  					wg.Done()
   542  				case <-done:
   543  					return
   544  				}
   545  			}
   546  		}(in)
   547  	}
   548  
   549  	for _, dep := range deps {
   550  		if !c.HasIgnore(dep.Name) {
   551  			wg.Add(1)
   552  			in <- dep
   553  		}
   554  	}
   555  
   556  	wg.Wait()
   557  
   558  	// Close goroutines setting the version
   559  	for ii := 0; ii < concurrentWorkers; ii++ {
   560  		done <- struct{}{}
   561  	}
   562  
   563  	return returnErr
   564  }
   565  
   566  // allPackages gets a list of all packages required to satisfy the given deps.
   567  func allPackages(deps []*cfg.Dependency, res *dependency.Resolver, addTest bool) ([]string, error) {
   568  	if len(deps) == 0 {
   569  		return []string{}, nil
   570  	}
   571  
   572  	vdir, err := gpath.Vendor()
   573  	if err != nil {
   574  		return []string{}, err
   575  	}
   576  	vdir += string(os.PathSeparator)
   577  	ll, err := res.ResolveAll(deps, addTest)
   578  	if err != nil {
   579  		return []string{}, err
   580  	}
   581  
   582  	for i := 0; i < len(ll); i++ {
   583  		ll[i] = strings.TrimPrefix(ll[i], vdir)
   584  	}
   585  	return ll, nil
   586  }
   587  
   588  // MissingPackageHandler is a dependency.MissingPackageHandler.
   589  //
   590  // When a package is not found, this attempts to resolve and fetch.
   591  //
   592  // When a package is found on the GOPATH, this notifies the user.
   593  type MissingPackageHandler struct {
   594  	home    string
   595  	force   bool
   596  	Config  *cfg.Config
   597  	Use     *importCache
   598  	updated *UpdateTracker
   599  }
   600  
   601  // NotFound attempts to retrieve a package when not found in the local cache
   602  // folder. It will attempt to get it from the remote location info.
   603  func (m *MissingPackageHandler) NotFound(pkg string, addTest bool) (bool, error) {
   604  	err := m.fetchToCache(pkg, addTest)
   605  	if err != nil {
   606  		return false, err
   607  	}
   608  
   609  	return true, err
   610  }
   611  
   612  // OnGopath will either copy a package, already found in the GOPATH, to the
   613  // vendor/ directory or download it from the internet. This is dependent if
   614  // useGopath on the installer is set to true to copy from the GOPATH.
   615  func (m *MissingPackageHandler) OnGopath(pkg string, addTest bool) (bool, error) {
   616  
   617  	err := m.fetchToCache(pkg, addTest)
   618  	if err != nil {
   619  		return false, err
   620  	}
   621  
   622  	return true, err
   623  }
   624  
   625  // InVendor updates a package in the vendor/ directory to make sure the latest
   626  // is available.
   627  func (m *MissingPackageHandler) InVendor(pkg string, addTest bool) error {
   628  	return m.fetchToCache(pkg, addTest)
   629  }
   630  
   631  // PkgPath resolves the location on the filesystem where the package should be.
   632  // This handles making sure to use the cache location.
   633  func (m *MissingPackageHandler) PkgPath(pkg string) string {
   634  	root, sub := util.NormalizeName(pkg)
   635  
   636  	// For the parent applications source skip the cache.
   637  	if root == m.Config.Name {
   638  		pth := gpath.Basepath()
   639  		return filepath.Join(pth, filepath.FromSlash(sub))
   640  	}
   641  
   642  	d := m.Config.Imports.Get(root)
   643  	if d == nil {
   644  		d = m.Config.DevImports.Get(root)
   645  	}
   646  
   647  	if d == nil {
   648  		d, _ = m.Use.Get(root)
   649  
   650  		if d == nil {
   651  			d = &cfg.Dependency{Name: root}
   652  		}
   653  	}
   654  
   655  	key, err := cache.Key(d.Remote())
   656  	if err != nil {
   657  		msg.Die("Error generating cache key for %s", d.Name)
   658  	}
   659  
   660  	return filepath.Join(cache.Location(), "src", key, filepath.FromSlash(sub))
   661  }
   662  
   663  func (m *MissingPackageHandler) fetchToCache(pkg string, addTest bool) error {
   664  	root := util.GetRootFromPackage(pkg)
   665  	// Skip any references to the root package.
   666  	if root == m.Config.Name {
   667  		return nil
   668  	}
   669  
   670  	d := m.Config.Imports.Get(root)
   671  	if d == nil && addTest {
   672  		d = m.Config.DevImports.Get(root)
   673  	}
   674  
   675  	// If the dependency is nil it means the Config doesn't yet know about it.
   676  	if d == nil {
   677  		d, _ = m.Use.Get(root)
   678  		// We don't know about this dependency so we create a basic instance.
   679  		if d == nil {
   680  			d = &cfg.Dependency{Name: root}
   681  		}
   682  
   683  		if addTest {
   684  			m.Config.DevImports = append(m.Config.DevImports, d)
   685  		} else {
   686  			m.Config.Imports = append(m.Config.Imports, d)
   687  		}
   688  	}
   689  
   690  	return VcsUpdate(d, m.force, m.updated)
   691  }
   692  
   693  // VersionHandler handles setting the proper version in the VCS.
   694  type VersionHandler struct {
   695  
   696  	// If Try to use the version here if we have one. This is a cache and will
   697  	// change over the course of setting versions.
   698  	Use *importCache
   699  
   700  	// Cache if importing scan has already occurred here.
   701  	Imported map[string]bool
   702  
   703  	Config *cfg.Config
   704  
   705  	// There's a problem where many sub-packages have been asked to set a version
   706  	// and you can end up with numerous conflict messages that are exactly the
   707  	// same. We are keeping track to only display them once.
   708  	// the parent pac
   709  	Conflicts map[string]bool
   710  }
   711  
   712  // Process imports dependencies for a package
   713  func (d *VersionHandler) Process(pkg string) (e error) {
   714  	root := util.GetRootFromPackage(pkg)
   715  
   716  	// Skip any references to the root package.
   717  	if root == d.Config.Name {
   718  		return nil
   719  	}
   720  
   721  	// We have not tried to import, yet.
   722  	// Should we look in places other than the root of the project?
   723  	if d.Imported[root] == false {
   724  		d.Imported[root] = true
   725  		p := d.pkgPath(root)
   726  		f, deps, err := importer.Import(p)
   727  		if f && err == nil {
   728  			for _, dep := range deps {
   729  
   730  				// The fist one wins. Would something smater than this be better?
   731  				exists, _ := d.Use.Get(dep.Name)
   732  				if exists == nil && (dep.Reference != "" || dep.Repository != "") {
   733  					d.Use.Add(dep.Name, dep, root)
   734  				}
   735  			}
   736  		} else if err != nil {
   737  			msg.Err("Unable to import from %s. Err: %s", root, err)
   738  			e = err
   739  		}
   740  	}
   741  
   742  	return
   743  }
   744  
   745  // SetVersion sets the version for a package. If that package version is already
   746  // set it handles the case by:
   747  // - keeping the already set version
   748  // - proviting messaging about the version conflict
   749  // TODO(mattfarina): The way version setting happens can be improved. Currently not optimal.
   750  func (d *VersionHandler) SetVersion(pkg string, addTest bool) (e error) {
   751  	root := util.GetRootFromPackage(pkg)
   752  
   753  	// Skip any references to the root package.
   754  	if root == d.Config.Name {
   755  		return nil
   756  	}
   757  
   758  	v := d.Config.Imports.Get(root)
   759  	if addTest {
   760  		if v == nil {
   761  			v = d.Config.DevImports.Get(root)
   762  		} else if d.Config.DevImports.Has(root) {
   763  			// Both imports and test imports lists the same dependency.
   764  			// There are import chains (because the import tree is resolved
   765  			// before the test tree) that can cause this.
   766  			tempD := d.Config.DevImports.Get(root)
   767  			if tempD.Reference != v.Reference {
   768  				msg.Warn("Using import %s (version %s) for test instead of testImport (version %s).", v.Name, v.Reference, tempD.Reference)
   769  			}
   770  			// TODO(mattfarina): Note repo difference in a warning.
   771  		}
   772  	}
   773  
   774  	dep, req := d.Use.Get(root)
   775  	if dep != nil && v != nil {
   776  		if v.Reference == "" && dep.Reference != "" {
   777  			v.Reference = dep.Reference
   778  			// Clear the pin, if set, so the new version can be used.
   779  			v.Pin = ""
   780  			dep = v
   781  		} else if v.Reference != "" && dep.Reference != "" && v.Reference != dep.Reference {
   782  			dest := d.pkgPath(pkg)
   783  			dep = determineDependency(v, dep, dest, req)
   784  		} else {
   785  			dep = v
   786  		}
   787  
   788  	} else if v != nil {
   789  		dep = v
   790  	} else if dep != nil {
   791  		// We've got an imported dependency to use and don't already have a
   792  		// record of it. Append it to the Imports.
   793  		if addTest {
   794  			d.Config.DevImports = append(d.Config.DevImports, dep)
   795  		} else {
   796  			d.Config.Imports = append(d.Config.Imports, dep)
   797  		}
   798  	} else {
   799  		// If we've gotten here we don't have any depenency objects.
   800  		r, sp := util.NormalizeName(pkg)
   801  		dep = &cfg.Dependency{
   802  			Name: r,
   803  		}
   804  		if sp != "" {
   805  			dep.Subpackages = []string{sp}
   806  		}
   807  		if addTest {
   808  			d.Config.DevImports = append(d.Config.DevImports, dep)
   809  		} else {
   810  			d.Config.Imports = append(d.Config.Imports, dep)
   811  		}
   812  	}
   813  
   814  	err := VcsVersion(dep)
   815  	if err != nil {
   816  		msg.Warn("Unable to set version on %s to %s. Err: %s", root, dep.Reference, err)
   817  		e = err
   818  	}
   819  
   820  	return
   821  }
   822  
   823  func (d *VersionHandler) pkgPath(pkg string) string {
   824  	root, sub := util.NormalizeName(pkg)
   825  
   826  	// For the parent applications source skip the cache.
   827  	if root == d.Config.Name {
   828  		pth := gpath.Basepath()
   829  		return filepath.Join(pth, filepath.FromSlash(sub))
   830  	}
   831  
   832  	dep := d.Config.Imports.Get(root)
   833  	if dep == nil {
   834  		dep = d.Config.DevImports.Get(root)
   835  	}
   836  
   837  	if dep == nil {
   838  		dep, _ = d.Use.Get(root)
   839  
   840  		if dep == nil {
   841  			dep = &cfg.Dependency{Name: root}
   842  		}
   843  	}
   844  
   845  	key, err := cache.Key(dep.Remote())
   846  	if err != nil {
   847  		msg.Die("Error generating cache key for %s", dep.Name)
   848  	}
   849  
   850  	return filepath.Join(cache.Location(), "src", key, filepath.FromSlash(sub))
   851  }
   852  
   853  func determineDependency(v, dep *cfg.Dependency, dest, req string) *cfg.Dependency {
   854  	repo, err := v.GetRepo(dest)
   855  	if err != nil {
   856  		singleWarn("Unable to access repo for %s\n", v.Name)
   857  		singleInfo("Keeping %s %s", v.Name, v.Reference)
   858  		return v
   859  	}
   860  
   861  	vIsRef := repo.IsReference(v.Reference)
   862  	depIsRef := repo.IsReference(dep.Reference)
   863  
   864  	// Both are references and they are different ones.
   865  	if vIsRef && depIsRef {
   866  		singleWarn("Conflict: %s rev is currently %s, but %s wants %s\n", v.Name, v.Reference, req, dep.Reference)
   867  
   868  		displayCommitInfo(repo, v)
   869  		displayCommitInfo(repo, dep)
   870  
   871  		singleInfo("Keeping %s %s", v.Name, v.Reference)
   872  		return v
   873  	} else if vIsRef {
   874  		// The current one is a reference and the suggestion is a SemVer constraint.
   875  		con, err := semver.NewConstraint(dep.Reference)
   876  		if err != nil {
   877  			singleWarn("Version issue for %s: '%s' is neither a reference or semantic version constraint\n", dep.Name, dep.Reference)
   878  			singleInfo("Keeping %s %s", v.Name, v.Reference)
   879  			return v
   880  		}
   881  
   882  		ver, err := semver.NewVersion(v.Reference)
   883  		if err != nil {
   884  			// The existing version is not a semantic version.
   885  			singleWarn("Conflict: %s version is %s, but also asked for %s\n", v.Name, v.Reference, dep.Reference)
   886  			displayCommitInfo(repo, v)
   887  			singleInfo("Keeping %s %s", v.Name, v.Reference)
   888  			return v
   889  		}
   890  
   891  		if con.Check(ver) {
   892  			singleInfo("Keeping %s %s because it fits constraint '%s'", v.Name, v.Reference, dep.Reference)
   893  			return v
   894  		}
   895  		singleWarn("Conflict: %s version is %s but does not meet constraint '%s'\n", v.Name, v.Reference, dep.Reference)
   896  		singleInfo("Keeping %s %s", v.Name, v.Reference)
   897  		return v
   898  	} else if depIsRef {
   899  
   900  		con, err := semver.NewConstraint(v.Reference)
   901  		if err != nil {
   902  			singleWarn("Version issue for %s: '%s' is neither a reference or semantic version constraint\n", v.Name, v.Reference)
   903  			singleInfo("Keeping %s %s", v.Name, v.Reference)
   904  			return v
   905  		}
   906  
   907  		ver, err := semver.NewVersion(dep.Reference)
   908  		if err != nil {
   909  			singleWarn("Conflict: %s version is %s, but also asked for %s\n", v.Name, v.Reference, dep.Reference)
   910  			displayCommitInfo(repo, dep)
   911  			singleInfo("Keeping %s %s", v.Name, v.Reference)
   912  			return v
   913  		}
   914  
   915  		if con.Check(ver) {
   916  			v.Reference = dep.Reference
   917  			singleInfo("Using %s %s because it fits constraint '%s'", v.Name, v.Reference, v.Reference)
   918  			return v
   919  		}
   920  		singleWarn("Conflict: %s semantic version constraint is %s but '%s' does not meet the constraint\n", v.Name, v.Reference, v.Reference)
   921  		singleInfo("Keeping %s %s", v.Name, v.Reference)
   922  		return v
   923  	}
   924  	// Neither is a vcs reference and both could be semantic version
   925  	// constraints that are different.
   926  
   927  	_, err = semver.NewConstraint(dep.Reference)
   928  	if err != nil {
   929  		// dd.Reference is not a reference or a valid constraint.
   930  		singleWarn("Version %s %s is not a reference or valid semantic version constraint\n", dep.Name, dep.Reference)
   931  		singleInfo("Keeping %s %s", v.Name, v.Reference)
   932  		return v
   933  	}
   934  
   935  	_, err = semver.NewConstraint(v.Reference)
   936  	if err != nil {
   937  		// existing.Reference is not a reference or a valid constraint.
   938  		// We really should never end up here.
   939  		singleWarn("Version %s %s is not a reference or valid semantic version constraint\n", v.Name, v.Reference)
   940  
   941  		v.Reference = dep.Reference
   942  		v.Pin = ""
   943  		singleInfo("Using %s %s because it is a valid version", v.Name, v.Reference)
   944  		return v
   945  	}
   946  
   947  	// Both versions are constraints. Try to merge them.
   948  	// If either comparison has an || skip merging. That's complicated.
   949  	ddor := strings.Index(dep.Reference, "||")
   950  	eor := strings.Index(v.Reference, "||")
   951  	if ddor == -1 && eor == -1 {
   952  		// Add the comparisons together.
   953  		newRef := v.Reference + ", " + dep.Reference
   954  		v.Reference = newRef
   955  		v.Pin = ""
   956  		singleInfo("Combining %s semantic version constraints %s and %s", v.Name, v.Reference, dep.Reference)
   957  		return v
   958  	}
   959  	singleWarn("Conflict: %s version is %s, but also asked for %s\n", v.Name, v.Reference, dep.Reference)
   960  	singleInfo("Keeping %s %s", v.Name, v.Reference)
   961  	return v
   962  }
   963  
   964  var warningMessage = make(map[string]bool)
   965  var infoMessage = make(map[string]bool)
   966  
   967  func singleWarn(ft string, v ...interface{}) {
   968  	m := fmt.Sprintf(ft, v...)
   969  	_, f := warningMessage[m]
   970  	if !f {
   971  		msg.Warn(m)
   972  		warningMessage[m] = true
   973  	}
   974  }
   975  
   976  func singleInfo(ft string, v ...interface{}) {
   977  	m := fmt.Sprintf(ft, v...)
   978  	_, f := infoMessage[m]
   979  	if !f {
   980  		msg.Info(m)
   981  		infoMessage[m] = true
   982  	}
   983  }
   984  
   985  type importCache struct {
   986  	cache map[string]*cfg.Dependency
   987  	from  map[string]string
   988  }
   989  
   990  func newImportCache() *importCache {
   991  	return &importCache{
   992  		cache: make(map[string]*cfg.Dependency),
   993  		from:  make(map[string]string),
   994  	}
   995  }
   996  
   997  func (i *importCache) Get(name string) (*cfg.Dependency, string) {
   998  	d, f := i.cache[name]
   999  	if f {
  1000  		return d, i.from[name]
  1001  	}
  1002  
  1003  	return nil, ""
  1004  }
  1005  
  1006  func (i *importCache) Add(name string, dep *cfg.Dependency, root string) {
  1007  	i.cache[name] = dep
  1008  	i.from[name] = root
  1009  }
  1010  
  1011  var displayCommitInfoPrefix = msg.Default.Color(msg.Green, "[INFO] ")
  1012  var displayCommitInfoTemplate = "%s reference %s:\n" +
  1013  	displayCommitInfoPrefix + "- author: %s\n" +
  1014  	displayCommitInfoPrefix + "- commit date: %s\n" +
  1015  	displayCommitInfoPrefix + "- subject (first line): %s\n"
  1016  
  1017  func displayCommitInfo(repo vcs.Repo, dep *cfg.Dependency) {
  1018  	c, err := repo.CommitInfo(dep.Reference)
  1019  	ref := dep.Reference
  1020  
  1021  	if err == nil {
  1022  		tgs, err2 := repo.TagsFromCommit(c.Commit)
  1023  		if err2 == nil && len(tgs) > 0 {
  1024  			if tgs[0] != dep.Reference {
  1025  				ref = ref + " (" + tgs[0] + ")"
  1026  			}
  1027  		}
  1028  		singleInfo(displayCommitInfoTemplate, dep.Name, ref, c.Author, c.Date.Format(time.RFC1123Z), commitSubjectFirstLine(c.Message))
  1029  	}
  1030  }
  1031  
  1032  func commitSubjectFirstLine(sub string) string {
  1033  	lines := strings.Split(sub, "\n")
  1034  	if len(lines) <= 1 {
  1035  		return sub
  1036  	}
  1037  
  1038  	return lines[0]
  1039  }