github.com/akutz/glide@v0.0.0-20160913145734-3895d9e41edb/cfg/config.go (about)

     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  }