github.com/liuweiccy/glide@v0.12.3/repo/installer.go (about)

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