| // 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. |
| |
| package dep |
| |
| import ( |
| "bytes" |
| "encoding/hex" |
| "io" |
| "sort" |
| |
| "github.com/golang/dep/internal/gps" |
| "github.com/pelletier/go-toml" |
| "github.com/pkg/errors" |
| ) |
| |
| // LockName is the lock file name used by dep. |
| const LockName = "Gopkg.lock" |
| |
| // Lock holds lock file data and implements gps.Lock. |
| type Lock struct { |
| SolveMeta SolveMeta |
| P []gps.LockedProject |
| } |
| |
| // SolveMeta holds solver meta data. |
| type SolveMeta struct { |
| InputsDigest []byte |
| AnalyzerName string |
| AnalyzerVersion int |
| SolverName string |
| SolverVersion int |
| } |
| |
| type rawLock struct { |
| SolveMeta solveMeta `toml:"solve-meta"` |
| Projects []rawLockedProject `toml:"projects"` |
| } |
| |
| type solveMeta struct { |
| InputsDigest string `toml:"inputs-digest"` |
| AnalyzerName string `toml:"analyzer-name"` |
| AnalyzerVersion int `toml:"analyzer-version"` |
| SolverName string `toml:"solver-name"` |
| SolverVersion int `toml:"solver-version"` |
| } |
| |
| type rawLockedProject struct { |
| Name string `toml:"name"` |
| Branch string `toml:"branch,omitempty"` |
| Revision string `toml:"revision"` |
| Version string `toml:"version,omitempty"` |
| Source string `toml:"source,omitempty"` |
| Packages []string `toml:"packages"` |
| } |
| |
| func readLock(r io.Reader) (*Lock, error) { |
| buf := &bytes.Buffer{} |
| _, err := buf.ReadFrom(r) |
| if err != nil { |
| return nil, errors.Wrap(err, "Unable to read byte stream") |
| } |
| |
| raw := rawLock{} |
| err = toml.Unmarshal(buf.Bytes(), &raw) |
| if err != nil { |
| return nil, errors.Wrap(err, "Unable to parse the lock as TOML") |
| } |
| |
| return fromRawLock(raw) |
| } |
| |
| func fromRawLock(raw rawLock) (*Lock, error) { |
| var err error |
| l := &Lock{ |
| P: make([]gps.LockedProject, len(raw.Projects)), |
| } |
| |
| l.SolveMeta.InputsDigest, err = hex.DecodeString(raw.SolveMeta.InputsDigest) |
| if err != nil { |
| return nil, errors.Errorf("invalid hash digest in lock's memo field") |
| } |
| |
| l.SolveMeta.AnalyzerName = raw.SolveMeta.AnalyzerName |
| l.SolveMeta.AnalyzerVersion = raw.SolveMeta.AnalyzerVersion |
| l.SolveMeta.SolverName = raw.SolveMeta.SolverName |
| l.SolveMeta.SolverVersion = raw.SolveMeta.SolverVersion |
| |
| for i, ld := range raw.Projects { |
| r := gps.Revision(ld.Revision) |
| |
| var v gps.Version = r |
| if ld.Version != "" { |
| if ld.Branch != "" { |
| return nil, errors.Errorf("lock file specified both a branch (%s) and version (%s) for %s", ld.Branch, ld.Version, ld.Name) |
| } |
| v = gps.NewVersion(ld.Version).Pair(r) |
| } else if ld.Branch != "" { |
| v = gps.NewBranch(ld.Branch).Pair(r) |
| } else if r == "" { |
| return nil, errors.Errorf("lock file has entry for %s, but specifies no branch or version", ld.Name) |
| } |
| |
| id := gps.ProjectIdentifier{ |
| ProjectRoot: gps.ProjectRoot(ld.Name), |
| Source: ld.Source, |
| } |
| l.P[i] = gps.NewLockedProject(id, v, ld.Packages) |
| } |
| |
| return l, nil |
| } |
| |
| // InputHash returns the hash of inputs which produced this lock data. |
| func (l *Lock) InputHash() []byte { |
| return l.SolveMeta.InputsDigest |
| } |
| |
| // Projects returns the list of LockedProjects contained in the lock data. |
| func (l *Lock) Projects() []gps.LockedProject { |
| return l.P |
| } |
| |
| // toRaw converts the manifest into a representation suitable to write to the lock file |
| func (l *Lock) toRaw() rawLock { |
| raw := rawLock{ |
| SolveMeta: solveMeta{ |
| InputsDigest: hex.EncodeToString(l.SolveMeta.InputsDigest), |
| AnalyzerName: l.SolveMeta.AnalyzerName, |
| AnalyzerVersion: l.SolveMeta.AnalyzerVersion, |
| SolverName: l.SolveMeta.SolverName, |
| SolverVersion: l.SolveMeta.SolverVersion, |
| }, |
| Projects: make([]rawLockedProject, len(l.P)), |
| } |
| |
| sort.Sort(SortedLockedProjects(l.P)) |
| |
| for k, lp := range l.P { |
| id := lp.Ident() |
| ld := rawLockedProject{ |
| Name: string(id.ProjectRoot), |
| Source: id.Source, |
| Packages: lp.Packages(), |
| } |
| |
| v := lp.Version() |
| ld.Revision, ld.Branch, ld.Version = gps.VersionComponentStrings(v) |
| |
| raw.Projects[k] = ld |
| } |
| |
| return raw |
| } |
| |
| // MarshalTOML serializes this lock into TOML via an intermediate raw form. |
| func (l *Lock) MarshalTOML() ([]byte, error) { |
| raw := l.toRaw() |
| result, err := toml.Marshal(raw) |
| return result, errors.Wrap(err, "Unable to marshal lock to TOML string") |
| } |
| |
| // LockFromSolution converts a gps.Solution to dep's representation of a lock. |
| // |
| // Data is defensively copied wherever necessary to ensure the resulting *lock |
| // shares no memory with the original lock. |
| func LockFromSolution(in gps.Solution) *Lock { |
| h, p := in.InputHash(), in.Projects() |
| |
| l := &Lock{ |
| SolveMeta: SolveMeta{ |
| InputsDigest: make([]byte, len(h)), |
| AnalyzerName: in.AnalyzerName(), |
| AnalyzerVersion: in.AnalyzerVersion(), |
| SolverName: in.SolverName(), |
| SolverVersion: in.SolverVersion(), |
| }, |
| P: make([]gps.LockedProject, len(p)), |
| } |
| |
| copy(l.SolveMeta.InputsDigest, h) |
| copy(l.P, p) |
| return l |
| } |
| |
| // SortedLockedProjects implements sort.Interface. |
| type SortedLockedProjects []gps.LockedProject |
| |
| 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 { |
| l, r := s[i].Ident(), s[j].Ident() |
| |
| if l.ProjectRoot < r.ProjectRoot { |
| return true |
| } |
| if r.ProjectRoot < l.ProjectRoot { |
| return false |
| } |
| |
| return l.Source < r.Source |
| } |