blob: c03ac41747747949dc403da21f57017ae66463f4 [file] [log] [blame]
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
}