| package cfg |
| |
| import ( |
| "crypto/sha256" |
| "fmt" |
| "io/ioutil" |
| "reflect" |
| "strings" |
| |
| "github.com/Masterminds/glide/util" |
| "github.com/Masterminds/vcs" |
| "gopkg.in/yaml.v2" |
| ) |
| |
| // Config is the top-level configuration object. |
| type Config struct { |
| Name string `yaml:"package"` |
| Ignore []string `yaml:"ignore,omitempty"` |
| Imports Dependencies `yaml:"import"` |
| DevImports Dependencies `yaml:"devimport,omitempty"` |
| } |
| |
| // A transitive representation of a dependency for importing and exporting to yaml. |
| type cf struct { |
| Name string `yaml:"package"` |
| Ignore []string `yaml:"ignore,omitempty"` |
| Imports Dependencies `yaml:"import"` |
| DevImports Dependencies `yaml:"devimport,omitempty"` |
| } |
| |
| // ConfigFromYaml returns an instance of Config from YAML |
| func ConfigFromYaml(yml []byte) (*Config, error) { |
| cfg := &Config{} |
| err := yaml.Unmarshal([]byte(yml), &cfg) |
| return cfg, err |
| } |
| |
| // Marshal converts a Config instance to YAML |
| func (c *Config) Marshal() ([]byte, error) { |
| yml, err := yaml.Marshal(&c) |
| if err != nil { |
| return []byte{}, err |
| } |
| return yml, nil |
| } |
| |
| // UnmarshalYAML is a hook for gopkg.in/yaml.v2 in the unmarshalling process |
| func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error { |
| newConfig := &cf{} |
| if err := unmarshal(&newConfig); err != nil { |
| return err |
| } |
| c.Name = newConfig.Name |
| c.Ignore = newConfig.Ignore |
| c.Imports = newConfig.Imports |
| c.DevImports = newConfig.DevImports |
| |
| // Cleanup the Config object now that we have it. |
| err := c.DeDupe() |
| |
| return err |
| } |
| |
| // MarshalYAML is a hook for gopkg.in/yaml.v2 in the marshaling process |
| func (c *Config) MarshalYAML() (interface{}, error) { |
| newConfig := &cf{ |
| Name: c.Name, |
| Ignore: c.Ignore, |
| } |
| i, err := c.Imports.Clone().DeDupe() |
| if err != nil { |
| return newConfig, err |
| } |
| |
| di, err := c.DevImports.Clone().DeDupe() |
| if err != nil { |
| return newConfig, err |
| } |
| |
| newConfig.Imports = i |
| newConfig.DevImports = di |
| |
| return newConfig, nil |
| } |
| |
| // HasDependency returns true if the given name is listed as an import or dev import. |
| func (c *Config) HasDependency(name string) bool { |
| for _, d := range c.Imports { |
| if d.Name == name { |
| return true |
| } |
| } |
| for _, d := range c.DevImports { |
| if d.Name == name { |
| return true |
| } |
| } |
| return false |
| } |
| |
| // HasIgnore returns true if the given name is listed on the ignore list. |
| func (c *Config) HasIgnore(name string) bool { |
| for _, v := range c.Ignore { |
| |
| // Check for both a name and to make sure sub-packages are ignored as |
| // well. |
| if v == name || strings.HasPrefix(name, v+"/") { |
| return true |
| } |
| } |
| |
| return false |
| } |
| |
| // Clone performs a deep clone of the Config instance |
| func (c *Config) Clone() *Config { |
| n := &Config{} |
| n.Name = c.Name |
| n.Ignore = c.Ignore |
| n.Imports = c.Imports.Clone() |
| n.DevImports = c.DevImports.Clone() |
| return n |
| } |
| |
| // WriteFile writes a Glide YAML file. |
| // |
| // This is a convenience function that marshals the YAML and then writes it to |
| // the given file. If the file exists, it will be clobbered. |
| func (c *Config) WriteFile(glidepath string) error { |
| o, err := c.Marshal() |
| if err != nil { |
| return err |
| } |
| return ioutil.WriteFile(glidepath, o, 0666) |
| } |
| |
| // DeDupe consolidates duplicate dependencies on a Config instance |
| func (c *Config) DeDupe() error { |
| |
| // Remove duplicates in the imports |
| var err error |
| c.Imports, err = c.Imports.DeDupe() |
| if err != nil { |
| return err |
| } |
| c.DevImports, err = c.DevImports.DeDupe() |
| if err != nil { |
| return err |
| } |
| |
| // If the name on the config object is part of the imports remove it. |
| found := -1 |
| for i, dep := range c.Imports { |
| if dep.Name == c.Name { |
| found = i |
| } |
| } |
| if found >= 0 { |
| c.Imports = append(c.Imports[:found], c.Imports[found+1:]...) |
| } |
| |
| found = -1 |
| for i, dep := range c.DevImports { |
| if dep.Name == c.Name { |
| found = i |
| } |
| } |
| if found >= 0 { |
| c.DevImports = append(c.DevImports[:found], c.DevImports[found+1:]...) |
| } |
| |
| // If something is on the ignore list remove it from the imports. |
| for _, v := range c.Ignore { |
| found = -1 |
| for k, d := range c.Imports { |
| if v == d.Name { |
| found = k |
| } |
| } |
| if found >= 0 { |
| c.Imports = append(c.Imports[:found], c.Imports[found+1:]...) |
| } |
| |
| found = -1 |
| for k, d := range c.DevImports { |
| if v == d.Name { |
| found = k |
| } |
| } |
| if found >= 0 { |
| c.DevImports = append(c.DevImports[:found], c.DevImports[found+1:]...) |
| } |
| } |
| |
| return nil |
| } |
| |
| // AddImport appends dependencies to the import list, deduplicating as we go. |
| func (c *Config) AddImport(deps ...*Dependency) error { |
| t := c.Imports |
| t = append(t, deps...) |
| t, err := t.DeDupe() |
| if err != nil { |
| return err |
| } |
| c.Imports = t |
| return nil |
| } |
| |
| // Hash generates a sha256 hash for a given Config |
| func (c *Config) Hash() (string, error) { |
| yml, err := c.Marshal() |
| if err != nil { |
| return "", err |
| } |
| |
| hash := sha256.New() |
| hash.Write(yml) |
| return fmt.Sprintf("%x", hash.Sum(nil)), nil |
| } |
| |
| // Dependencies is a collection of Dependency |
| type Dependencies []*Dependency |
| |
| // Get a dependency by name |
| func (d Dependencies) Get(name string) *Dependency { |
| for _, dep := range d { |
| if dep.Name == name { |
| return dep |
| } |
| } |
| return nil |
| } |
| |
| // Clone performs a deep clone of Dependencies |
| func (d Dependencies) Clone() Dependencies { |
| n := make(Dependencies, 0, len(d)) |
| for _, v := range d { |
| n = append(n, v.Clone()) |
| } |
| return n |
| } |
| |
| // DeDupe cleans up duplicates on a list of dependencies. |
| func (d Dependencies) DeDupe() (Dependencies, error) { |
| checked := map[string]int{} |
| imports := make(Dependencies, 0, 1) |
| i := 0 |
| for _, dep := range d { |
| // The first time we encounter a dependency add it to the list |
| if val, ok := checked[dep.Name]; !ok { |
| checked[dep.Name] = i |
| imports = append(imports, dep) |
| i++ |
| } else { |
| // In here we've encountered a dependency for the second time. |
| // Make sure the details are the same or return an error. |
| v := imports[val] |
| if dep.Reference != v.Reference { |
| return d, fmt.Errorf("Import %s repeated with different versions '%s' and '%s'", dep.Name, dep.Reference, v.Reference) |
| } |
| if dep.Repository != v.Repository || dep.VcsType != v.VcsType { |
| return d, fmt.Errorf("Import %s repeated with different Repository details", dep.Name) |
| } |
| if !reflect.DeepEqual(dep.Os, v.Os) || !reflect.DeepEqual(dep.Arch, v.Arch) { |
| return d, fmt.Errorf("Import %s repeated with different OS or Architecture filtering", dep.Name) |
| } |
| imports[checked[dep.Name]].Subpackages = stringArrayDeDupe(v.Subpackages, dep.Subpackages...) |
| } |
| } |
| |
| return imports, nil |
| } |
| |
| // Dependency describes a package that the present package depends upon. |
| type Dependency struct { |
| Name string `yaml:"package"` |
| Reference string `yaml:"version,omitempty"` |
| Pin string `yaml:"-"` |
| Repository string `yaml:"repo,omitempty"` |
| VcsType string `yaml:"vcs,omitempty"` |
| Subpackages []string `yaml:"subpackages,omitempty"` |
| Arch []string `yaml:"arch,omitempty"` |
| Os []string `yaml:"os,omitempty"` |
| UpdateAsVendored bool `yaml:"-"` |
| } |
| |
| // A transitive representation of a dependency for importing and exploting to yaml. |
| type dep struct { |
| Name string `yaml:"package"` |
| Reference string `yaml:"version,omitempty"` |
| Ref string `yaml:"ref,omitempty"` |
| Repository string `yaml:"repo,omitempty"` |
| VcsType string `yaml:"vcs,omitempty"` |
| Subpackages []string `yaml:"subpackages,omitempty"` |
| Arch []string `yaml:"arch,omitempty"` |
| Os []string `yaml:"os,omitempty"` |
| } |
| |
| // UnmarshalYAML is a hook for gopkg.in/yaml.v2 in the unmarshaling process |
| func (d *Dependency) UnmarshalYAML(unmarshal func(interface{}) error) error { |
| newDep := &dep{} |
| err := unmarshal(&newDep) |
| if err != nil { |
| return err |
| } |
| d.Name = newDep.Name |
| d.Reference = newDep.Reference |
| d.Repository = newDep.Repository |
| d.VcsType = newDep.VcsType |
| d.Subpackages = newDep.Subpackages |
| d.Arch = newDep.Arch |
| d.Os = newDep.Os |
| |
| if d.Reference == "" && newDep.Ref != "" { |
| d.Reference = newDep.Ref |
| } |
| |
| // Make sure only legitimate VCS are listed. |
| d.VcsType = filterVcsType(d.VcsType) |
| |
| // Get the root name for the package |
| o := d.Name |
| d.Name = util.GetRootFromPackage(d.Name) |
| subpkg := strings.TrimPrefix(o, d.Name) |
| if len(subpkg) > 0 && subpkg != "/" { |
| d.Subpackages = append(d.Subpackages, strings.TrimPrefix(subpkg, "/")) |
| } |
| |
| return nil |
| } |
| |
| // MarshalYAML is a hook for gopkg.in/yaml.v2 in the marshaling process |
| func (d *Dependency) MarshalYAML() (interface{}, error) { |
| |
| // Make sure we only write the correct vcs type to file |
| t := filterVcsType(d.VcsType) |
| newDep := &dep{ |
| Name: d.Name, |
| Reference: d.Reference, |
| Repository: d.Repository, |
| VcsType: t, |
| Subpackages: d.Subpackages, |
| Arch: d.Arch, |
| Os: d.Os, |
| } |
| |
| return newDep, nil |
| } |
| |
| // GetRepo retrieves a Masterminds/vcs repo object configured for the root |
| // of the package being retrieved. |
| func (d *Dependency) GetRepo(dest string) (vcs.Repo, error) { |
| |
| // The remote location is either the configured repo or the package |
| // name as an https url. |
| var remote string |
| if len(d.Repository) > 0 { |
| remote = d.Repository |
| } else { |
| remote = "https://" + d.Name |
| } |
| |
| // If the VCS type has a value we try that first. |
| if len(d.VcsType) > 0 && d.VcsType != "None" { |
| switch vcs.Type(d.VcsType) { |
| case vcs.Git: |
| return vcs.NewGitRepo(remote, dest) |
| case vcs.Svn: |
| return vcs.NewSvnRepo(remote, dest) |
| case vcs.Hg: |
| return vcs.NewHgRepo(remote, dest) |
| case vcs.Bzr: |
| return vcs.NewBzrRepo(remote, dest) |
| default: |
| return nil, fmt.Errorf("Unknown VCS type %s set for %s", d.VcsType, d.Name) |
| } |
| } |
| |
| // When no type set we try to autodetect. |
| return vcs.NewRepo(remote, dest) |
| } |
| |
| // Clone creates a clone of a Dependency |
| func (d *Dependency) Clone() *Dependency { |
| return &Dependency{ |
| Name: d.Name, |
| Reference: d.Reference, |
| Pin: d.Pin, |
| Repository: d.Repository, |
| VcsType: d.VcsType, |
| Subpackages: d.Subpackages, |
| Arch: d.Arch, |
| Os: d.Os, |
| UpdateAsVendored: d.UpdateAsVendored, |
| } |
| } |
| |
| // HasSubpackage returns if the subpackage is present on the dependency |
| func (d *Dependency) HasSubpackage(sub string) bool { |
| |
| for _, v := range d.Subpackages { |
| if sub == v { |
| return true |
| } |
| } |
| |
| return false |
| } |
| |
| func stringArrayDeDupe(s []string, items ...string) []string { |
| for _, item := range items { |
| exists := false |
| for _, v := range s { |
| if v == item { |
| exists = true |
| } |
| } |
| if !exists { |
| s = append(s, item) |
| } |
| } |
| return s |
| } |
| |
| func filterVcsType(vcs string) string { |
| switch vcs { |
| case "git", "hg", "bzr", "svn": |
| return vcs |
| case "mercurial": |
| return "hg" |
| case "bazaar": |
| return "bzr" |
| case "subversion": |
| return "svn" |
| default: |
| return "" |
| } |
| } |