Issues (5)

repo/installer.go (2 issues)

Severity
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...)
0 ignored issues
show
can't check non-constant format in call to Sprintf
Loading history...
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...)
0 ignored issues
show
can't check non-constant format in call to Sprintf
Loading history...
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
}
1040