package cfg import ( "crypto/sha256" "fmt" "io/ioutil" "sort" "strings" "time" "gopkg.in/yaml.v2" ) // Lockfile represents a glide.lock file. type Lockfile struct { Hash string `yaml:"hash"` Updated time.Time `yaml:"updated"` Imports Locks `yaml:"imports"` DevImports Locks `yaml:"testImports"` } // LockfileFromYaml returns an instance of Lockfile from YAML func LockfileFromYaml(yml []byte) (*Lockfile, error) { lock := &Lockfile{} err := yaml.Unmarshal([]byte(yml), &lock) return lock, err } // Marshal converts a Config instance to YAML func (lf *Lockfile) Marshal() ([]byte, error) { yml, err := yaml.Marshal(&lf) if err != nil { return []byte{}, err } return yml, nil } // MarshalYAML is a hook for gopkg.in/yaml.v2. // It sorts import subpackages lexicographically for reproducibility. func (lf *Lockfile) MarshalYAML() (interface{}, error) { for _, imp := range lf.Imports { sort.Strings(imp.Subpackages) } // Ensure elements on testImport don't already exist on import. var newDI Locks var found bool for _, imp := range lf.DevImports { found = false for i := 0; i < len(lf.Imports); i++ { if lf.Imports[i].Name == imp.Name { found = true if lf.Imports[i].Version != imp.Version { return lf, fmt.Errorf("Generating lock YAML produced conflicting versions of %s. import (%s), testImport (%s)", imp.Name, lf.Imports[i].Version, imp.Version) } } } if !found { newDI = append(newDI, imp) } } lf.DevImports = newDI for _, imp := range lf.DevImports { sort.Strings(imp.Subpackages) } return lf, nil } // WriteFile writes a Glide lock 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 (lf *Lockfile) WriteFile(lockpath string) error { o, err := lf.Marshal() if err != nil { return err } return ioutil.WriteFile(lockpath, o, 0666) } // Clone returns a clone of Lockfile func (lf *Lockfile) Clone() *Lockfile { n := &Lockfile{} n.Hash = lf.Hash n.Updated = lf.Updated n.Imports = lf.Imports.Clone() n.DevImports = lf.DevImports.Clone() return n } // Fingerprint returns a hash of the contents minus the date. This allows for // two lockfiles to be compared irrespective of their updated times. func (lf *Lockfile) Fingerprint() ([32]byte, error) { c := lf.Clone() c.Updated = time.Time{} // Set the time to be the nil equivalent sort.Sort(c.Imports) sort.Sort(c.DevImports) yml, err := c.Marshal() if err != nil { return [32]byte{}, err } return sha256.Sum256(yml), nil } // ReadLockFile loads the contents of a glide.lock file. func ReadLockFile(lockpath string) (*Lockfile, error) { yml, err := ioutil.ReadFile(lockpath) if err != nil { return nil, err } lock, err := LockfileFromYaml(yml) if err != nil { return nil, err } return lock, nil } // Locks is a slice of locked dependencies. type Locks []*Lock // Clone returns a Clone of Locks. func (l Locks) Clone() Locks { n := make(Locks, 0, len(l)) for _, v := range l { n = append(n, v.Clone()) } return n } // Len returns the length of the Locks. This is needed for sorting with // the sort package. func (l Locks) Len() int { return len(l) } // Less is needed for the sort interface. It compares two locks based on // their name. func (l Locks) Less(i, j int) bool { // Names are normalized to lowercase because case affects sorting order. For // example, Masterminds comes before kylelemons. Making them lowercase // causes kylelemons to come first which is what is expected. return strings.ToLower(l[i].Name) < strings.ToLower(l[j].Name) } // Swap is needed for the sort interface. It swaps the position of two // locks. func (l Locks) Swap(i, j int) { l[i], l[j] = l[j], l[i] } // Lock represents an individual locked dependency. type Lock struct { Name string `yaml:"name"` Version string `yaml:"version"` 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"` } // Clone creates a clone of a Lock. func (l *Lock) Clone() *Lock { return &Lock{ Name: l.Name, Version: l.Version, Repository: l.Repository, VcsType: l.VcsType, Subpackages: l.Subpackages, Arch: l.Arch, Os: l.Os, } } // LockFromDependency converts a Dependency to a Lock func LockFromDependency(dep *Dependency) *Lock { return &Lock{ Name: dep.Name, Version: dep.Pin, Repository: dep.Repository, VcsType: dep.VcsType, Subpackages: dep.Subpackages, Arch: dep.Arch, Os: dep.Os, } } // NewLockfile is used to create an instance of Lockfile. func NewLockfile(ds, tds Dependencies, hash string) (*Lockfile, error) { lf := &Lockfile{ Hash: hash, Updated: time.Now(), Imports: make([]*Lock, len(ds)), DevImports: make([]*Lock, 0), } for i := 0; i < len(ds); i++ { lf.Imports[i] = LockFromDependency(ds[i]) } sort.Sort(lf.Imports) var found bool for i := 0; i < len(tds); i++ { found = false for ii := 0; ii < len(ds); ii++ { if ds[ii].Name == tds[i].Name { found = true if ds[ii].Reference != tds[i].Reference { return &Lockfile{}, fmt.Errorf("Generating lock produced conflicting versions of %s. import (%s), testImport (%s)", tds[i].Name, ds[ii].Reference, tds[i].Reference) } break } } if !found { lf.DevImports = append(lf.DevImports, LockFromDependency(tds[i])) } } sort.Sort(lf.DevImports) return lf, nil } // LockfileFromMap takes a map of dependencies and generates a lock Lockfile instance. func LockfileFromMap(ds map[string]*Dependency, hash string) *Lockfile { lf := &Lockfile{ Hash: hash, Updated: time.Now(), Imports: make([]*Lock, len(ds)), } i := 0 for name, dep := range ds { lf.Imports[i] = LockFromDependency(dep) lf.Imports[i].Name = name i++ } sort.Sort(lf.Imports) return lf }