2016-10-18 07:39:37 +03:00
|
|
|
// Copyright 2016 The Go Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
2017-01-27 23:39:07 +03:00
|
|
|
package dep
|
2016-10-18 07:39:37 +03:00
|
|
|
|
|
|
|
import (
|
2016-12-01 03:37:28 +03:00
|
|
|
"bytes"
|
2016-10-18 07:39:37 +03:00
|
|
|
"encoding/hex"
|
|
|
|
"encoding/json"
|
|
|
|
"io"
|
2016-12-06 00:30:33 +03:00
|
|
|
"sort"
|
2016-10-18 07:39:37 +03:00
|
|
|
|
2017-03-20 21:13:48 +03:00
|
|
|
"github.com/pelletier/go-toml"
|
2017-03-22 18:58:50 +03:00
|
|
|
"github.com/pkg/errors"
|
2016-10-18 07:39:37 +03:00
|
|
|
"github.com/sdboyer/gps"
|
|
|
|
)
|
|
|
|
|
2017-01-27 23:39:07 +03:00
|
|
|
const LockName = "lock.json"
|
|
|
|
|
|
|
|
type Lock struct {
|
2016-10-18 07:39:37 +03:00
|
|
|
Memo []byte
|
|
|
|
P []gps.LockedProject
|
|
|
|
}
|
|
|
|
|
|
|
|
type rawLock struct {
|
|
|
|
Memo string `json:"memo"`
|
|
|
|
P []lockedDep `json:"projects"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type lockedDep struct {
|
2017-03-07 22:47:13 +03:00
|
|
|
Name string `json:"name"`
|
|
|
|
Version string `json:"version,omitempty"`
|
|
|
|
Branch string `json:"branch,omitempty"`
|
|
|
|
Revision string `json:"revision"`
|
|
|
|
Source string `json:"source,omitempty"`
|
|
|
|
Packages []string `json:"packages"`
|
2016-10-18 07:39:37 +03:00
|
|
|
}
|
|
|
|
|
2017-01-27 23:39:07 +03:00
|
|
|
func readLock(r io.Reader) (*Lock, error) {
|
2016-10-18 07:39:37 +03:00
|
|
|
rl := rawLock{}
|
|
|
|
err := json.NewDecoder(r).Decode(&rl)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
b, err := hex.DecodeString(rl.Memo)
|
|
|
|
if err != nil {
|
2017-03-22 18:58:50 +03:00
|
|
|
return nil, errors.Errorf("invalid hash digest in lock's memo field")
|
2016-10-18 07:39:37 +03:00
|
|
|
}
|
2017-01-27 23:39:07 +03:00
|
|
|
l := &Lock{
|
2016-10-18 07:39:37 +03:00
|
|
|
Memo: b,
|
|
|
|
P: make([]gps.LockedProject, len(rl.P)),
|
|
|
|
}
|
|
|
|
|
2016-10-18 08:18:18 +03:00
|
|
|
for i, ld := range rl.P {
|
2016-10-18 07:39:37 +03:00
|
|
|
r := gps.Revision(ld.Revision)
|
|
|
|
|
2017-01-25 00:48:14 +03:00
|
|
|
var v gps.Version = r
|
2016-10-18 07:39:37 +03:00
|
|
|
if ld.Version != "" {
|
|
|
|
if ld.Branch != "" {
|
2017-03-22 18:58:50 +03:00
|
|
|
return nil, errors.Errorf("lock file specified both a branch (%s) and version (%s) for %s", ld.Branch, ld.Version, ld.Name)
|
2016-10-18 07:39:37 +03:00
|
|
|
}
|
|
|
|
v = gps.NewVersion(ld.Version).Is(r)
|
|
|
|
} else if ld.Branch != "" {
|
|
|
|
v = gps.NewBranch(ld.Branch).Is(r)
|
|
|
|
} else if r == "" {
|
2017-03-22 18:58:50 +03:00
|
|
|
return nil, errors.Errorf("lock file has entry for %s, but specifies no branch or version", ld.Name)
|
2016-10-18 07:39:37 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
id := gps.ProjectIdentifier{
|
|
|
|
ProjectRoot: gps.ProjectRoot(ld.Name),
|
2017-03-07 22:47:13 +03:00
|
|
|
Source: ld.Source,
|
2016-10-18 07:39:37 +03:00
|
|
|
}
|
2016-10-18 08:18:18 +03:00
|
|
|
l.P[i] = gps.NewLockedProject(id, v, ld.Packages)
|
2016-10-18 07:39:37 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return l, nil
|
|
|
|
}
|
|
|
|
|
2017-01-27 23:39:07 +03:00
|
|
|
func (l *Lock) InputHash() []byte {
|
2016-10-18 07:39:37 +03:00
|
|
|
return l.Memo
|
|
|
|
}
|
|
|
|
|
2017-01-27 23:39:07 +03:00
|
|
|
func (l *Lock) Projects() []gps.LockedProject {
|
2016-10-18 07:39:37 +03:00
|
|
|
return l.P
|
|
|
|
}
|
2016-11-30 08:17:59 +03:00
|
|
|
|
2017-03-20 21:13:48 +03:00
|
|
|
// toRaw converts the manifest into a representation suitable to write to the lock file
|
|
|
|
func (l *Lock) toRaw() rawLock {
|
2016-11-30 08:17:59 +03:00
|
|
|
raw := rawLock{
|
|
|
|
Memo: hex.EncodeToString(l.Memo),
|
2016-11-30 19:32:24 +03:00
|
|
|
P: make([]lockedDep, len(l.P)),
|
2016-11-30 08:17:59 +03:00
|
|
|
}
|
|
|
|
|
2017-01-27 23:39:07 +03:00
|
|
|
sort.Sort(SortedLockedProjects(l.P))
|
2016-12-03 20:33:40 +03:00
|
|
|
|
2016-11-30 08:17:59 +03:00
|
|
|
for k, lp := range l.P {
|
|
|
|
id := lp.Ident()
|
|
|
|
ld := lockedDep{
|
2017-03-07 22:47:13 +03:00
|
|
|
Name: string(id.ProjectRoot),
|
|
|
|
Source: id.Source,
|
|
|
|
Packages: lp.Packages(),
|
2016-11-30 08:17:59 +03:00
|
|
|
}
|
|
|
|
|
2016-11-30 19:50:57 +03:00
|
|
|
v := lp.Version()
|
2017-03-02 22:13:42 +03:00
|
|
|
ld.Revision, ld.Branch, ld.Version = getVersionInfo(v)
|
2016-11-30 19:50:57 +03:00
|
|
|
|
2016-11-30 08:17:59 +03:00
|
|
|
raw.P[k] = ld
|
|
|
|
}
|
|
|
|
|
2016-11-30 20:50:41 +03:00
|
|
|
// TODO sort output - #15
|
2016-12-01 03:37:28 +03:00
|
|
|
|
2017-03-20 21:13:48 +03:00
|
|
|
return raw
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *Lock) MarshalJSON() ([]byte, error) {
|
|
|
|
raw := l.toRaw()
|
|
|
|
|
2016-12-01 03:37:28 +03:00
|
|
|
var buf bytes.Buffer
|
|
|
|
enc := json.NewEncoder(&buf)
|
|
|
|
enc.SetIndent("", " ")
|
|
|
|
enc.SetEscapeHTML(false)
|
|
|
|
err := enc.Encode(raw)
|
|
|
|
|
|
|
|
return buf.Bytes(), err
|
2016-11-30 08:17:59 +03:00
|
|
|
}
|
2016-12-01 08:25:48 +03:00
|
|
|
|
2017-03-20 21:13:48 +03:00
|
|
|
func (l *Lock) MarshalTOML() (string, error) {
|
|
|
|
raw := l.toRaw()
|
|
|
|
|
|
|
|
// TODO(carolynvs) Consider adding reflection-based marshal functionality to go-toml
|
|
|
|
m := make(map[string]interface{})
|
|
|
|
m["memo"] = raw.Memo
|
|
|
|
p := make([]map[string]interface{}, len(raw.P))
|
|
|
|
for i := 0; i < len(p); i++ {
|
2017-03-21 17:36:26 +03:00
|
|
|
srcPrj := raw.P[i]
|
2017-03-20 21:13:48 +03:00
|
|
|
prj := make(map[string]interface{})
|
2017-03-21 17:36:26 +03:00
|
|
|
prj["name"] = srcPrj.Name
|
|
|
|
prj["revision"] = srcPrj.Revision
|
2017-03-20 21:13:48 +03:00
|
|
|
|
2017-03-21 17:36:26 +03:00
|
|
|
if srcPrj.Source != "" {
|
|
|
|
prj["source"] = srcPrj.Source
|
2017-03-20 21:13:48 +03:00
|
|
|
}
|
2017-03-21 17:36:26 +03:00
|
|
|
if srcPrj.Branch != "" {
|
|
|
|
prj["branch"] = srcPrj.Branch
|
2017-03-20 21:13:48 +03:00
|
|
|
}
|
2017-03-21 17:36:26 +03:00
|
|
|
if srcPrj.Version != "" {
|
|
|
|
prj["version"] = srcPrj.Version
|
2017-03-20 21:13:48 +03:00
|
|
|
}
|
|
|
|
|
2017-03-21 17:36:26 +03:00
|
|
|
pkgs := make([]interface{}, len(srcPrj.Packages))
|
|
|
|
for j := range srcPrj.Packages {
|
|
|
|
pkgs[j] = srcPrj.Packages[j]
|
2017-03-20 21:13:48 +03:00
|
|
|
}
|
|
|
|
prj["packages"] = pkgs
|
|
|
|
|
|
|
|
p[i] = prj
|
|
|
|
}
|
|
|
|
m["projects"] = p
|
|
|
|
|
|
|
|
t, err := toml.TreeFromMap(m)
|
|
|
|
if err != nil {
|
|
|
|
return "", errors.Wrap(err, "Unable to marshal lock to TOML tree")
|
|
|
|
}
|
|
|
|
|
|
|
|
result, err := t.ToTomlString()
|
|
|
|
return result, errors.Wrap(err, "Unable to marshal lock to TOML string")
|
|
|
|
}
|
|
|
|
|
2017-03-02 22:13:42 +03:00
|
|
|
// TODO(carolynvs) this should be moved to gps
|
|
|
|
func getVersionInfo(v gps.Version) (revision string, branch string, version string) {
|
2017-03-09 22:28:42 +03:00
|
|
|
// Figure out how to get the underlying revision
|
|
|
|
switch tv := v.(type) {
|
|
|
|
case gps.UnpairedVersion:
|
|
|
|
// TODO we could error here, if we want to be very defensive about not
|
|
|
|
// allowing a lock to be written if without an immmutable revision
|
|
|
|
case gps.Revision:
|
2017-03-02 22:13:42 +03:00
|
|
|
revision = tv.String()
|
2017-03-09 22:28:42 +03:00
|
|
|
case gps.PairedVersion:
|
2017-03-02 22:13:42 +03:00
|
|
|
revision = tv.Underlying().String()
|
2017-03-09 22:28:42 +03:00
|
|
|
}
|
2017-03-02 22:13:42 +03:00
|
|
|
|
|
|
|
switch v.Type() {
|
|
|
|
case gps.IsBranch:
|
|
|
|
branch = v.String()
|
|
|
|
case gps.IsSemver, gps.IsVersion:
|
|
|
|
version = v.String()
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
2017-03-09 22:28:42 +03:00
|
|
|
}
|
|
|
|
|
2017-01-27 23:39:07 +03:00
|
|
|
// LockFromInterface converts an arbitrary gps.Lock to dep's representation of a
|
2017-01-19 03:11:38 +03:00
|
|
|
// lock. If the input is already dep's *lock, the input is returned directly.
|
2016-12-01 08:25:48 +03:00
|
|
|
//
|
|
|
|
// Data is defensively copied wherever necessary to ensure the resulting *lock
|
|
|
|
// shares no memory with the original lock.
|
|
|
|
//
|
|
|
|
// As gps.Solution is a superset of gps.Lock, this can also be used to convert
|
2017-01-19 03:11:38 +03:00
|
|
|
// solutions to dep's lock format.
|
2017-01-27 23:39:07 +03:00
|
|
|
func LockFromInterface(in gps.Lock) *Lock {
|
2016-12-01 08:25:48 +03:00
|
|
|
if in == nil {
|
|
|
|
return nil
|
2017-01-27 23:39:07 +03:00
|
|
|
} else if l, ok := in.(*Lock); ok {
|
2016-12-01 08:25:48 +03:00
|
|
|
return l
|
|
|
|
}
|
|
|
|
|
|
|
|
h, p := in.InputHash(), in.Projects()
|
|
|
|
|
2017-01-27 23:39:07 +03:00
|
|
|
l := &Lock{
|
2016-12-01 08:25:48 +03:00
|
|
|
Memo: make([]byte, len(h)),
|
|
|
|
P: make([]gps.LockedProject, len(p)),
|
|
|
|
}
|
|
|
|
|
|
|
|
copy(l.Memo, h)
|
|
|
|
copy(l.P, p)
|
|
|
|
return l
|
|
|
|
}
|
2016-12-03 20:33:40 +03:00
|
|
|
|
2017-01-27 23:39:07 +03:00
|
|
|
type SortedLockedProjects []gps.LockedProject
|
2016-12-03 20:33:40 +03:00
|
|
|
|
2017-01-27 23:39:07 +03:00
|
|
|
func (s SortedLockedProjects) Len() int { return len(s) }
|
|
|
|
func (s SortedLockedProjects) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
|
|
|
func (s SortedLockedProjects) Less(i, j int) bool {
|
2016-12-03 20:33:40 +03:00
|
|
|
l, r := s[i].Ident(), s[j].Ident()
|
|
|
|
|
|
|
|
if l.ProjectRoot < r.ProjectRoot {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if r.ProjectRoot < l.ProjectRoot {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2017-01-04 05:19:24 +03:00
|
|
|
return l.Source < r.Source
|
2016-12-03 20:33:40 +03:00
|
|
|
}
|