| // 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 |
| } |