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