This repository has been archived by the owner. It is now read-only.
Permalink
Cannot retrieve contributors at this time
259 lines (219 sloc)
6.97 KB
// 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" | |
"io" | |
"sort" | |
"github.com/golang/dep/gps" | |
"github.com/golang/dep/gps/verify" | |
"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 metadata about the solving process that created the lock that | |
// is not specific to any individual project. | |
type SolveMeta struct { | |
AnalyzerName string | |
AnalyzerVersion int | |
SolverName string | |
SolverVersion int | |
InputImports []string | |
} | |
type rawLock struct { | |
SolveMeta solveMeta `toml:"solve-meta"` | |
Projects []rawLockedProject `toml:"projects"` | |
} | |
type solveMeta struct { | |
AnalyzerName string `toml:"analyzer-name"` | |
AnalyzerVersion int `toml:"analyzer-version"` | |
SolverName string `toml:"solver-name"` | |
SolverVersion int `toml:"solver-version"` | |
InputImports []string `toml:"input-imports"` | |
} | |
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"` | |
PruneOpts string `toml:"pruneopts"` | |
Digest string `toml:"digest"` | |
} | |
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) { | |
l := &Lock{ | |
P: make([]gps.LockedProject, 0, len(raw.Projects)), | |
} | |
l.SolveMeta.AnalyzerName = raw.SolveMeta.AnalyzerName | |
l.SolveMeta.AnalyzerVersion = raw.SolveMeta.AnalyzerVersion | |
l.SolveMeta.SolverName = raw.SolveMeta.SolverName | |
l.SolveMeta.SolverVersion = raw.SolveMeta.SolverVersion | |
l.SolveMeta.InputImports = raw.SolveMeta.InputImports | |
for _, 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, | |
} | |
var err error | |
vp := verify.VerifiableProject{ | |
LockedProject: gps.NewLockedProject(id, v, ld.Packages), | |
} | |
if ld.Digest != "" { | |
vp.Digest, err = verify.ParseVersionedDigest(ld.Digest) | |
if err != nil { | |
return nil, err | |
} | |
} | |
po, err := gps.ParsePruneOptions(ld.PruneOpts) | |
if err != nil { | |
return nil, errors.Errorf("%s in prune options for %s", err.Error(), ld.Name) | |
} | |
// Add the vendor pruning bit so that gps doesn't get confused | |
vp.PruneOpts = po | gps.PruneNestedVendorDirs | |
l.P = append(l.P, vp) | |
} | |
return l, nil | |
} | |
// Projects returns the list of LockedProjects contained in the lock data. | |
func (l *Lock) Projects() []gps.LockedProject { | |
if l == nil || l == (*Lock)(nil) { | |
return nil | |
} | |
return l.P | |
} | |
// InputImports reports the list of input imports that were used in generating | |
// this Lock. | |
func (l *Lock) InputImports() []string { | |
if l == nil || l == (*Lock)(nil) { | |
return nil | |
} | |
return l.SolveMeta.InputImports | |
} | |
// HasProjectWithRoot checks if the lock contains a project with the provided | |
// ProjectRoot. | |
// | |
// This check is O(n) in the number of projects. | |
func (l *Lock) HasProjectWithRoot(root gps.ProjectRoot) bool { | |
for _, p := range l.P { | |
if p.Ident().ProjectRoot == root { | |
return true | |
} | |
} | |
return false | |
} | |
func (l *Lock) dup() *Lock { | |
l2 := &Lock{ | |
SolveMeta: l.SolveMeta, | |
P: make([]gps.LockedProject, len(l.P)), | |
} | |
l2.SolveMeta.InputImports = make([]string, len(l.SolveMeta.InputImports)) | |
copy(l2.SolveMeta.InputImports, l.SolveMeta.InputImports) | |
copy(l2.P, l.P) | |
return l2 | |
} | |
// toRaw converts the manifest into a representation suitable to write to the lock file | |
func (l *Lock) toRaw() rawLock { | |
raw := rawLock{ | |
SolveMeta: solveMeta{ | |
AnalyzerName: l.SolveMeta.AnalyzerName, | |
AnalyzerVersion: l.SolveMeta.AnalyzerVersion, | |
InputImports: l.SolveMeta.InputImports, | |
SolverName: l.SolveMeta.SolverName, | |
SolverVersion: l.SolveMeta.SolverVersion, | |
}, | |
Projects: make([]rawLockedProject, 0, len(l.P)), | |
} | |
sort.Slice(l.P, func(i, j int) bool { | |
return l.P[i].Ident().Less(l.P[j].Ident()) | |
}) | |
for _, 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) | |
// This will panic if the lock isn't the expected dynamic type. We can | |
// relax this later if it turns out to create real problems, but there's | |
// no intended case in which this is untrue, so it's preferable to start | |
// by failing hard if those expectations aren't met. | |
vp := lp.(verify.VerifiableProject) | |
ld.Digest = vp.Digest.String() | |
ld.PruneOpts = (vp.PruneOpts & ^gps.PruneNestedVendorDirs).String() | |
raw.Projects = append(raw.Projects, ld) | |
} | |
return raw | |
} | |
// MarshalTOML serializes this lock into TOML via an intermediate raw form. | |
func (l *Lock) MarshalTOML() ([]byte, error) { | |
raw := l.toRaw() | |
var buf bytes.Buffer | |
enc := toml.NewEncoder(&buf).ArraysWithOneElementPerLine(true) | |
err := enc.Encode(raw) | |
return buf.Bytes(), errors.Wrap(err, "Unable to marshal lock to TOML string") | |
} | |
// LockFromSolution converts a gps.Solution to dep's representation of a lock. | |
// It makes sure that that the provided prune options are set correctly, as the | |
// solver does not use VerifiableProjects for new selections it makes. | |
// | |
// Data is defensively copied wherever necessary to ensure the resulting *Lock | |
// shares no memory with the input solution. | |
func LockFromSolution(in gps.Solution, prune gps.CascadingPruneOptions) *Lock { | |
p := in.Projects() | |
l := &Lock{ | |
SolveMeta: SolveMeta{ | |
AnalyzerName: in.AnalyzerName(), | |
AnalyzerVersion: in.AnalyzerVersion(), | |
InputImports: in.InputImports(), | |
SolverName: in.SolverName(), | |
SolverVersion: in.SolverVersion(), | |
}, | |
P: make([]gps.LockedProject, 0, len(p)), | |
} | |
for _, lp := range p { | |
if vp, ok := lp.(verify.VerifiableProject); ok { | |
l.P = append(l.P, vp) | |
} else { | |
l.P = append(l.P, verify.VerifiableProject{ | |
LockedProject: lp, | |
PruneOpts: prune.PruneOptionsFor(lp.Ident().ProjectRoot), | |
}) | |
} | |
} | |
return l | |
} |