cfg/config.go   F
last analyzed

Size/Duplication

Total Lines 624
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
cc 123
eloc 354
dl 0
loc 624
rs 2
c 0
b 0
f 0

30 Methods

Rating   Name   Duplication   Size   Complexity  
A cfg.*Dependency.Clone 0 10 1
A cfg.*Config.Hash 0 9 2
A cfg.Owners.Clone 0 6 3
B cfg.stringArrayDeDupe 0 14 7
A cfg.Dependencies.Remove 0 14 5
A cfg.*Config.HasIgnore 0 11 5
A cfg.*Dependency.Vcs 0 15 3
A cfg.*Dependency.HasSubpackage 0 9 4
A cfg.DependencyFromLock 0 9 1
F cfg.*Config.DeDupe 0 58 21
A cfg.*Dependency.MarshalYAML 0 15 1
B cfg.filterVcsType 0 12 6
B cfg.*Dependency.GetRepo 0 26 8
A cfg.ConfigFromYaml 0 4 1
A cfg.Dependencies.Has 0 7 4
A cfg.*Config.Clone 0 12 1
B cfg.*Dependency.UnmarshalYAML 0 35 7
A cfg.*Config.HasExclude 0 9 4
A cfg.*Dependency.Remote 0 15 3
A cfg.Dependencies.Get 0 7 4
A cfg.Dependencies.Clone 0 6 3
A cfg.*Config.WriteFile 0 6 2
A cfg.*Config.MarshalYAML 0 24 3
A cfg.*Config.UnmarshalYAML 0 19 2
B cfg.*Config.HasDependency 0 12 7
C cfg.Dependencies.DeDupe 0 28 9
A cfg.normalizeSlash 0 2 1
A cfg.*Owner.Clone 0 5 1
A cfg.*Config.AddImport 0 9 2
A cfg.*Config.Marshal 0 6 2
1
package cfg
2
3
import (
4
	"crypto/sha256"
5
	"fmt"
6
	"io/ioutil"
7
	"reflect"
8
	"sort"
9
	"strings"
10
11
	"github.com/Masterminds/glide/mirrors"
12
	"github.com/Masterminds/glide/util"
13
	"github.com/Masterminds/vcs"
14
	"gopkg.in/yaml.v2"
15
)
16
17
// Config is the top-level configuration object.
18
type Config struct {
19
20
	// Name is the name of the package or application.
21
	Name string `yaml:"package"`
22
23
	// Description is a short description for a package, application, or library.
24
	// This description is similar but different to a Go package description as
25
	// it is for marketing and presentation purposes rather than technical ones.
26
	Description string `json:"description,omitempty"`
27
28
	// Home is a url to a website for the package.
29
	Home string `yaml:"homepage,omitempty"`
30
31
	// License provides either a SPDX license or a path to a file containing
32
	// the license. For more information on SPDX see http://spdx.org/licenses/.
33
	// When more than one license an SPDX expression can be used.
34
	License string `yaml:"license,omitempty"`
35
36
	// Owners is an array of owners for a project. See the Owner type for
37
	// more detail. These can be one or more people, companies, or other
38
	// organizations.
39
	Owners Owners `yaml:"owners,omitempty"`
40
41
	// Ignore contains a list of packages to ignore fetching. This is useful
42
	// when walking the package tree (including packages of packages) to list
43
	// those to skip.
44
	Ignore []string `yaml:"ignore,omitempty"`
45
46
	// Exclude contains a list of directories in the local application to
47
	// exclude from scanning for dependencies.
48
	Exclude []string `yaml:"excludeDirs,omitempty"`
49
50
	// Imports contains a list of all non-development imports for a project. For
51
	// more detail on how these are captured see the Dependency type.
52
	Imports Dependencies `yaml:"import"`
53
54
	// DevImports contains the test or other development imports for a project.
55
	// See the Dependency type for more details on how this is recorded.
56
	DevImports Dependencies `yaml:"testImport,omitempty"`
57
}
58
59
// A transitive representation of a dependency for importing and exporting to yaml.
60
type cf struct {
61
	Name        string       `yaml:"package"`
62
	Description string       `yaml:"description,omitempty"`
63
	Home        string       `yaml:"homepage,omitempty"`
64
	License     string       `yaml:"license,omitempty"`
65
	Owners      Owners       `yaml:"owners,omitempty"`
66
	Ignore      []string     `yaml:"ignore,omitempty"`
67
	Exclude     []string     `yaml:"excludeDirs,omitempty"`
68
	Imports     Dependencies `yaml:"import"`
69
	DevImports  Dependencies `yaml:"testImport,omitempty"`
70
}
71
72
// ConfigFromYaml returns an instance of Config from YAML
73
func ConfigFromYaml(yml []byte) (*Config, error) {
74
	cfg := &Config{}
75
	err := yaml.Unmarshal([]byte(yml), &cfg)
76
	return cfg, err
77
}
78
79
// Marshal converts a Config instance to YAML
80
func (c *Config) Marshal() ([]byte, error) {
81
	yml, err := yaml.Marshal(&c)
82
	if err != nil {
83
		return []byte{}, err
84
	}
85
	return yml, nil
86
}
87
88
// UnmarshalYAML is a hook for gopkg.in/yaml.v2 in the unmarshalling process
89
func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
90
	newConfig := &cf{}
91
	if err := unmarshal(&newConfig); err != nil {
92
		return err
93
	}
94
	c.Name = newConfig.Name
95
	c.Description = newConfig.Description
96
	c.Home = newConfig.Home
97
	c.License = newConfig.License
98
	c.Owners = newConfig.Owners
99
	c.Ignore = newConfig.Ignore
100
	c.Exclude = newConfig.Exclude
101
	c.Imports = newConfig.Imports
102
	c.DevImports = newConfig.DevImports
103
104
	// Cleanup the Config object now that we have it.
105
	err := c.DeDupe()
106
107
	return err
108
}
109
110
// MarshalYAML is a hook for gopkg.in/yaml.v2 in the marshaling process
111
func (c *Config) MarshalYAML() (interface{}, error) {
112
	newConfig := &cf{
113
		Name:        c.Name,
114
		Description: c.Description,
115
		Home:        c.Home,
116
		License:     c.License,
117
		Owners:      c.Owners,
118
		Ignore:      c.Ignore,
119
		Exclude:     c.Exclude,
120
	}
121
	i, err := c.Imports.Clone().DeDupe()
122
	if err != nil {
123
		return newConfig, err
124
	}
125
126
	di, err := c.DevImports.Clone().DeDupe()
127
	if err != nil {
128
		return newConfig, err
129
	}
130
131
	newConfig.Imports = i
132
	newConfig.DevImports = di
133
134
	return newConfig, nil
135
}
136
137
// HasDependency returns true if the given name is listed as an import or dev import.
138
func (c *Config) HasDependency(name string) bool {
139
	for _, d := range c.Imports {
140
		if d.Name == name {
141
			return true
142
		}
143
	}
144
	for _, d := range c.DevImports {
145
		if d.Name == name {
146
			return true
147
		}
148
	}
149
	return false
150
}
151
152
// HasIgnore returns true if the given name is listed on the ignore list.
153
func (c *Config) HasIgnore(name string) bool {
154
	for _, v := range c.Ignore {
155
156
		// Check for both a name and to make sure sub-packages are ignored as
157
		// well.
158
		if v == name || strings.HasPrefix(name, v+"/") {
159
			return true
160
		}
161
	}
162
163
	return false
164
}
165
166
// HasExclude returns true if the given name is listed on the exclude list.
167
func (c *Config) HasExclude(ex string) bool {
168
	ep := normalizeSlash(ex)
169
	for _, v := range c.Exclude {
170
		if vp := normalizeSlash(v); vp == ep {
171
			return true
172
		}
173
	}
174
175
	return false
176
}
177
178
// Clone performs a deep clone of the Config instance
179
func (c *Config) Clone() *Config {
180
	n := &Config{}
181
	n.Name = c.Name
182
	n.Description = c.Description
183
	n.Home = c.Home
184
	n.License = c.License
185
	n.Owners = c.Owners.Clone()
186
	n.Ignore = c.Ignore
187
	n.Exclude = c.Exclude
188
	n.Imports = c.Imports.Clone()
189
	n.DevImports = c.DevImports.Clone()
190
	return n
191
}
192
193
// WriteFile writes a Glide YAML file.
194
//
195
// This is a convenience function that marshals the YAML and then writes it to
196
// the given file. If the file exists, it will be clobbered.
197
func (c *Config) WriteFile(glidepath string) error {
198
	o, err := c.Marshal()
199
	if err != nil {
200
		return err
201
	}
202
	return ioutil.WriteFile(glidepath, o, 0666)
203
}
204
205
// DeDupe consolidates duplicate dependencies on a Config instance
206
func (c *Config) DeDupe() error {
207
208
	// Remove duplicates in the imports
209
	var err error
210
	c.Imports, err = c.Imports.DeDupe()
211
	if err != nil {
212
		return err
213
	}
214
	c.DevImports, err = c.DevImports.DeDupe()
215
	if err != nil {
216
		return err
217
	}
218
219
	// If the name on the config object is part of the imports remove it.
220
	found := -1
221
	for i, dep := range c.Imports {
222
		if dep.Name == c.Name {
223
			found = i
224
		}
225
	}
226
	if found >= 0 {
227
		c.Imports = append(c.Imports[:found], c.Imports[found+1:]...)
228
	}
229
230
	found = -1
231
	for i, dep := range c.DevImports {
232
		if dep.Name == c.Name {
233
			found = i
234
		}
235
	}
236
	if found >= 0 {
237
		c.DevImports = append(c.DevImports[:found], c.DevImports[found+1:]...)
238
	}
239
240
	// If something is on the ignore list remove it from the imports.
241
	for _, v := range c.Ignore {
242
		found = -1
243
		for k, d := range c.Imports {
244
			if v == d.Name {
245
				found = k
246
			}
247
		}
248
		if found >= 0 {
249
			c.Imports = append(c.Imports[:found], c.Imports[found+1:]...)
250
		}
251
252
		found = -1
253
		for k, d := range c.DevImports {
254
			if v == d.Name {
255
				found = k
256
			}
257
		}
258
		if found >= 0 {
259
			c.DevImports = append(c.DevImports[:found], c.DevImports[found+1:]...)
260
		}
261
	}
262
263
	return nil
264
}
265
266
// AddImport appends dependencies to the import list, deduplicating as we go.
267
func (c *Config) AddImport(deps ...*Dependency) error {
268
	t := c.Imports
269
	t = append(t, deps...)
270
	t, err := t.DeDupe()
271
	if err != nil {
272
		return err
273
	}
274
	c.Imports = t
275
	return nil
276
}
277
278
// Hash generates a sha256 hash for a given Config
279
func (c *Config) Hash() (string, error) {
280
	yml, err := c.Marshal()
281
	if err != nil {
282
		return "", err
283
	}
284
285
	hash := sha256.New()
286
	hash.Write(yml)
287
	return fmt.Sprintf("%x", hash.Sum(nil)), nil
288
}
289
290
// Dependencies is a collection of Dependency
291
type Dependencies []*Dependency
292
293
// Get a dependency by name
294
func (d Dependencies) Get(name string) *Dependency {
295
	for _, dep := range d {
296
		if dep.Name == name {
297
			return dep
298
		}
299
	}
300
	return nil
301
}
302
303
// Has checks if a dependency is on a list of dependencies such as import or testImport
304
func (d Dependencies) Has(name string) bool {
305
	for _, dep := range d {
306
		if dep.Name == name {
307
			return true
308
		}
309
	}
310
	return false
311
}
312
313
// Remove removes a dependency from a list of dependencies
314
func (d Dependencies) Remove(name string) Dependencies {
315
	found := -1
316
	for i, dep := range d {
317
		if dep.Name == name {
318
			found = i
319
		}
320
	}
321
322
	if found >= 0 {
323
		copy(d[found:], d[found+1:])
324
		d[len(d)-1] = nil
325
		return d[:len(d)-1]
326
	}
327
	return d
328
}
329
330
// Clone performs a deep clone of Dependencies
331
func (d Dependencies) Clone() Dependencies {
332
	n := make(Dependencies, 0, len(d))
333
	for _, v := range d {
334
		n = append(n, v.Clone())
335
	}
336
	return n
337
}
338
339
// DeDupe cleans up duplicates on a list of dependencies.
340
func (d Dependencies) DeDupe() (Dependencies, error) {
341
	checked := map[string]int{}
342
	imports := make(Dependencies, 0, 1)
343
	i := 0
344
	for _, dep := range d {
345
		// The first time we encounter a dependency add it to the list
346
		if val, ok := checked[dep.Name]; !ok {
347
			checked[dep.Name] = i
348
			imports = append(imports, dep)
349
			i++
350
		} else {
351
			// In here we've encountered a dependency for the second time.
352
			// Make sure the details are the same or return an error.
353
			v := imports[val]
354
			if dep.Reference != v.Reference {
355
				return d, fmt.Errorf("Import %s repeated with different versions '%s' and '%s'", dep.Name, dep.Reference, v.Reference)
356
			}
357
			if dep.Repository != v.Repository || dep.VcsType != v.VcsType {
358
				return d, fmt.Errorf("Import %s repeated with different Repository details", dep.Name)
359
			}
360
			if !reflect.DeepEqual(dep.Os, v.Os) || !reflect.DeepEqual(dep.Arch, v.Arch) {
361
				return d, fmt.Errorf("Import %s repeated with different OS or Architecture filtering", dep.Name)
362
			}
363
			imports[checked[dep.Name]].Subpackages = stringArrayDeDupe(v.Subpackages, dep.Subpackages...)
364
		}
365
	}
366
367
	return imports, nil
368
}
369
370
// Dependency describes a package that the present package depends upon.
371
type Dependency struct {
372
	Name        string   `yaml:"package"`
373
	Reference   string   `yaml:"version,omitempty"`
374
	Pin         string   `yaml:"-"`
375
	Repository  string   `yaml:"repo,omitempty"`
376
	VcsType     string   `yaml:"vcs,omitempty"`
377
	Subpackages []string `yaml:"subpackages,omitempty"`
378
	Arch        []string `yaml:"arch,omitempty"`
379
	Os          []string `yaml:"os,omitempty"`
380
}
381
382
// A transitive representation of a dependency for importing and exploting to yaml.
383
type dep struct {
384
	Name        string   `yaml:"package"`
385
	Reference   string   `yaml:"version,omitempty"`
386
	Ref         string   `yaml:"ref,omitempty"`
387
	Repository  string   `yaml:"repo,omitempty"`
388
	VcsType     string   `yaml:"vcs,omitempty"`
389
	Subpackages []string `yaml:"subpackages,omitempty"`
390
	Arch        []string `yaml:"arch,omitempty"`
391
	Os          []string `yaml:"os,omitempty"`
392
}
393
394
// DependencyFromLock converts a Lock to a Dependency
395
func DependencyFromLock(lock *Lock) *Dependency {
396
	return &Dependency{
397
		Name:        lock.Name,
398
		Reference:   lock.Version,
399
		Repository:  lock.Repository,
400
		VcsType:     lock.VcsType,
401
		Subpackages: lock.Subpackages,
402
		Arch:        lock.Arch,
403
		Os:          lock.Os,
404
	}
405
}
406
407
// UnmarshalYAML is a hook for gopkg.in/yaml.v2 in the unmarshaling process
408
func (d *Dependency) UnmarshalYAML(unmarshal func(interface{}) error) error {
409
	newDep := &dep{}
410
	err := unmarshal(&newDep)
411
	if err != nil {
412
		return err
413
	}
414
	d.Name = newDep.Name
415
	d.Reference = newDep.Reference
416
	d.Repository = newDep.Repository
417
	d.VcsType = newDep.VcsType
418
	d.Subpackages = newDep.Subpackages
419
	d.Arch = newDep.Arch
420
	d.Os = newDep.Os
421
422
	if d.Reference == "" && newDep.Ref != "" {
423
		d.Reference = newDep.Ref
424
	}
425
426
	// Make sure only legitimate VCS are listed.
427
	d.VcsType = filterVcsType(d.VcsType)
428
429
	// Get the root name for the package
430
	tn, subpkg := util.NormalizeName(d.Name)
431
	d.Name = tn
432
	if subpkg != "" {
433
		d.Subpackages = append(d.Subpackages, subpkg)
434
	}
435
436
	// Older versions of Glide had a / prefix on subpackages in some cases.
437
	// Here that's cleaned up. Someday we should be able to remove this.
438
	for k, v := range d.Subpackages {
439
		d.Subpackages[k] = strings.TrimPrefix(v, "/")
440
	}
441
442
	return nil
443
}
444
445
// MarshalYAML is a hook for gopkg.in/yaml.v2 in the marshaling process
446
func (d *Dependency) MarshalYAML() (interface{}, error) {
447
448
	// Make sure we only write the correct vcs type to file
449
	t := filterVcsType(d.VcsType)
450
	newDep := &dep{
451
		Name:        d.Name,
452
		Reference:   d.Reference,
453
		Repository:  d.Repository,
454
		VcsType:     t,
455
		Subpackages: d.Subpackages,
456
		Arch:        d.Arch,
457
		Os:          d.Os,
458
	}
459
460
	return newDep, nil
461
}
462
463
// Remote returns the remote location to fetch source from. This location is
464
// the central place where mirrors can alter the location.
465
func (d *Dependency) Remote() string {
466
	var r string
467
468
	if d.Repository != "" {
469
		r = d.Repository
470
	} else {
471
		r = "https://" + d.Name
472
	}
473
474
	f, nr, _ := mirrors.Get(r)
475
	if f {
476
		return nr
477
	}
478
479
	return r
480
}
481
482
// Vcs returns the VCS type to fetch source from.
483
func (d *Dependency) Vcs() string {
484
	var r string
485
486
	if d.Repository != "" {
487
		r = d.Repository
488
	} else {
489
		r = "https://" + d.Name
490
	}
491
492
	f, _, nv := mirrors.Get(r)
493
	if f {
494
		return nv
495
	}
496
497
	return d.VcsType
498
}
499
500
// GetRepo retrieves a Masterminds/vcs repo object configured for the root
501
// of the package being retrieved.
502
func (d *Dependency) GetRepo(dest string) (vcs.Repo, error) {
503
504
	// The remote location is either the configured repo or the package
505
	// name as an https url.
506
	remote := d.Remote()
507
508
	VcsType := d.Vcs()
509
510
	// If the VCS type has a value we try that first.
511
	if len(VcsType) > 0 && VcsType != "None" {
512
		switch vcs.Type(VcsType) {
513
		case vcs.Git:
514
			return vcs.NewGitRepo(remote, dest)
515
		case vcs.Svn:
516
			return vcs.NewSvnRepo(remote, dest)
517
		case vcs.Hg:
518
			return vcs.NewHgRepo(remote, dest)
519
		case vcs.Bzr:
520
			return vcs.NewBzrRepo(remote, dest)
521
		default:
522
			return nil, fmt.Errorf("Unknown VCS type %s set for %s", VcsType, d.Name)
523
		}
524
	}
525
526
	// When no type set we try to autodetect.
527
	return vcs.NewRepo(remote, dest)
528
}
529
530
// Clone creates a clone of a Dependency
531
func (d *Dependency) Clone() *Dependency {
532
	return &Dependency{
533
		Name:        d.Name,
534
		Reference:   d.Reference,
535
		Pin:         d.Pin,
536
		Repository:  d.Repository,
537
		VcsType:     d.VcsType,
538
		Subpackages: d.Subpackages,
539
		Arch:        d.Arch,
540
		Os:          d.Os,
541
	}
542
}
543
544
// HasSubpackage returns if the subpackage is present on the dependency
545
func (d *Dependency) HasSubpackage(sub string) bool {
546
547
	for _, v := range d.Subpackages {
548
		if sub == v {
549
			return true
550
		}
551
	}
552
553
	return false
554
}
555
556
// Owners is a list of owners for a project.
557
type Owners []*Owner
558
559
// Clone performs a deep clone of Owners
560
func (o Owners) Clone() Owners {
561
	n := make(Owners, 0, 1)
562
	for _, v := range o {
563
		n = append(n, v.Clone())
564
	}
565
	return n
566
}
567
568
// Owner describes an owner of a package. This can be a person, company, or
569
// other organization. This is useful if someone needs to contact the
570
// owner of a package to address things like a security issue.
571
type Owner struct {
572
573
	// Name describes the name of an organization.
574
	Name string `yaml:"name,omitempty"`
575
576
	// Email is an email address to reach the owner at.
577
	Email string `yaml:"email,omitempty"`
578
579
	// Home is a url to a website for the owner.
580
	Home string `yaml:"homepage,omitempty"`
581
}
582
583
// Clone creates a clone of a Dependency
584
func (o *Owner) Clone() *Owner {
585
	return &Owner{
586
		Name:  o.Name,
587
		Email: o.Email,
588
		Home:  o.Home,
589
	}
590
}
591
592
func stringArrayDeDupe(s []string, items ...string) []string {
593
	for _, item := range items {
594
		exists := false
595
		for _, v := range s {
596
			if v == item {
597
				exists = true
598
			}
599
		}
600
		if !exists {
601
			s = append(s, item)
602
		}
603
	}
604
	sort.Strings(s)
605
	return s
606
}
607
608
func filterVcsType(vcs string) string {
609
	switch vcs {
610
	case "git", "hg", "bzr", "svn":
611
		return vcs
612
	case "mercurial":
613
		return "hg"
614
	case "bazaar":
615
		return "bzr"
616
	case "subversion":
617
		return "svn"
618
	default:
619
		return ""
620
	}
621
}
622
623
func normalizeSlash(k string) string {
624
	return strings.Replace(k, "\\", "/", -1)
625
}
626