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