1// Copyright 2016 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package dep 6 7import ( 8 "bytes" 9 "io" 10 "sort" 11 12 "github.com/golang/dep/gps" 13 "github.com/golang/dep/gps/verify" 14 "github.com/pelletier/go-toml" 15 "github.com/pkg/errors" 16) 17 18// LockName is the lock file name used by dep. 19const LockName = "Gopkg.lock" 20 21// Lock holds lock file data and implements gps.Lock. 22type Lock struct { 23 SolveMeta SolveMeta 24 P []gps.LockedProject 25} 26 27// SolveMeta holds metadata about the solving process that created the lock that 28// is not specific to any individual project. 29type SolveMeta struct { 30 AnalyzerName string 31 AnalyzerVersion int 32 SolverName string 33 SolverVersion int 34 InputImports []string 35} 36 37type rawLock struct { 38 SolveMeta solveMeta `toml:"solve-meta"` 39 Projects []rawLockedProject `toml:"projects"` 40} 41 42type solveMeta struct { 43 AnalyzerName string `toml:"analyzer-name"` 44 AnalyzerVersion int `toml:"analyzer-version"` 45 SolverName string `toml:"solver-name"` 46 SolverVersion int `toml:"solver-version"` 47 InputImports []string `toml:"input-imports"` 48} 49 50type rawLockedProject struct { 51 Name string `toml:"name"` 52 Branch string `toml:"branch,omitempty"` 53 Revision string `toml:"revision"` 54 Version string `toml:"version,omitempty"` 55 Source string `toml:"source,omitempty"` 56 Packages []string `toml:"packages"` 57 PruneOpts string `toml:"pruneopts"` 58 Digest string `toml:"digest"` 59} 60 61func readLock(r io.Reader) (*Lock, error) { 62 buf := &bytes.Buffer{} 63 _, err := buf.ReadFrom(r) 64 if err != nil { 65 return nil, errors.Wrap(err, "Unable to read byte stream") 66 } 67 68 raw := rawLock{} 69 err = toml.Unmarshal(buf.Bytes(), &raw) 70 if err != nil { 71 return nil, errors.Wrap(err, "Unable to parse the lock as TOML") 72 } 73 74 return fromRawLock(raw) 75} 76 77func fromRawLock(raw rawLock) (*Lock, error) { 78 l := &Lock{ 79 P: make([]gps.LockedProject, 0, len(raw.Projects)), 80 } 81 82 l.SolveMeta.AnalyzerName = raw.SolveMeta.AnalyzerName 83 l.SolveMeta.AnalyzerVersion = raw.SolveMeta.AnalyzerVersion 84 l.SolveMeta.SolverName = raw.SolveMeta.SolverName 85 l.SolveMeta.SolverVersion = raw.SolveMeta.SolverVersion 86 l.SolveMeta.InputImports = raw.SolveMeta.InputImports 87 88 for _, ld := range raw.Projects { 89 r := gps.Revision(ld.Revision) 90 91 var v gps.Version = r 92 if ld.Version != "" { 93 if ld.Branch != "" { 94 return nil, errors.Errorf("lock file specified both a branch (%s) and version (%s) for %s", ld.Branch, ld.Version, ld.Name) 95 } 96 v = gps.NewVersion(ld.Version).Pair(r) 97 } else if ld.Branch != "" { 98 v = gps.NewBranch(ld.Branch).Pair(r) 99 } else if r == "" { 100 return nil, errors.Errorf("lock file has entry for %s, but specifies no branch or version", ld.Name) 101 } 102 103 id := gps.ProjectIdentifier{ 104 ProjectRoot: gps.ProjectRoot(ld.Name), 105 Source: ld.Source, 106 } 107 108 var err error 109 vp := verify.VerifiableProject{ 110 LockedProject: gps.NewLockedProject(id, v, ld.Packages), 111 } 112 if ld.Digest != "" { 113 vp.Digest, err = verify.ParseVersionedDigest(ld.Digest) 114 if err != nil { 115 return nil, err 116 } 117 } 118 119 po, err := gps.ParsePruneOptions(ld.PruneOpts) 120 if err != nil { 121 return nil, errors.Errorf("%s in prune options for %s", err.Error(), ld.Name) 122 } 123 // Add the vendor pruning bit so that gps doesn't get confused 124 vp.PruneOpts = po | gps.PruneNestedVendorDirs 125 126 l.P = append(l.P, vp) 127 } 128 129 return l, nil 130} 131 132// Projects returns the list of LockedProjects contained in the lock data. 133func (l *Lock) Projects() []gps.LockedProject { 134 if l == nil || l == (*Lock)(nil) { 135 return nil 136 } 137 return l.P 138} 139 140// InputImports reports the list of input imports that were used in generating 141// this Lock. 142func (l *Lock) InputImports() []string { 143 if l == nil || l == (*Lock)(nil) { 144 return nil 145 } 146 return l.SolveMeta.InputImports 147} 148 149// HasProjectWithRoot checks if the lock contains a project with the provided 150// ProjectRoot. 151// 152// This check is O(n) in the number of projects. 153func (l *Lock) HasProjectWithRoot(root gps.ProjectRoot) bool { 154 for _, p := range l.P { 155 if p.Ident().ProjectRoot == root { 156 return true 157 } 158 } 159 160 return false 161} 162 163func (l *Lock) dup() *Lock { 164 l2 := &Lock{ 165 SolveMeta: l.SolveMeta, 166 P: make([]gps.LockedProject, len(l.P)), 167 } 168 169 l2.SolveMeta.InputImports = make([]string, len(l.SolveMeta.InputImports)) 170 copy(l2.SolveMeta.InputImports, l.SolveMeta.InputImports) 171 copy(l2.P, l.P) 172 173 return l2 174} 175 176// toRaw converts the manifest into a representation suitable to write to the lock file 177func (l *Lock) toRaw() rawLock { 178 raw := rawLock{ 179 SolveMeta: solveMeta{ 180 AnalyzerName: l.SolveMeta.AnalyzerName, 181 AnalyzerVersion: l.SolveMeta.AnalyzerVersion, 182 InputImports: l.SolveMeta.InputImports, 183 SolverName: l.SolveMeta.SolverName, 184 SolverVersion: l.SolveMeta.SolverVersion, 185 }, 186 Projects: make([]rawLockedProject, 0, len(l.P)), 187 } 188 189 sort.Slice(l.P, func(i, j int) bool { 190 return l.P[i].Ident().Less(l.P[j].Ident()) 191 }) 192 193 for _, lp := range l.P { 194 id := lp.Ident() 195 ld := rawLockedProject{ 196 Name: string(id.ProjectRoot), 197 Source: id.Source, 198 Packages: lp.Packages(), 199 } 200 201 v := lp.Version() 202 ld.Revision, ld.Branch, ld.Version = gps.VersionComponentStrings(v) 203 204 // This will panic if the lock isn't the expected dynamic type. We can 205 // relax this later if it turns out to create real problems, but there's 206 // no intended case in which this is untrue, so it's preferable to start 207 // by failing hard if those expectations aren't met. 208 vp := lp.(verify.VerifiableProject) 209 ld.Digest = vp.Digest.String() 210 ld.PruneOpts = (vp.PruneOpts & ^gps.PruneNestedVendorDirs).String() 211 212 raw.Projects = append(raw.Projects, ld) 213 } 214 215 return raw 216} 217 218// MarshalTOML serializes this lock into TOML via an intermediate raw form. 219func (l *Lock) MarshalTOML() ([]byte, error) { 220 raw := l.toRaw() 221 var buf bytes.Buffer 222 enc := toml.NewEncoder(&buf).ArraysWithOneElementPerLine(true) 223 err := enc.Encode(raw) 224 return buf.Bytes(), errors.Wrap(err, "Unable to marshal lock to TOML string") 225} 226 227// LockFromSolution converts a gps.Solution to dep's representation of a lock. 228// It makes sure that that the provided prune options are set correctly, as the 229// solver does not use VerifiableProjects for new selections it makes. 230// 231// Data is defensively copied wherever necessary to ensure the resulting *Lock 232// shares no memory with the input solution. 233func LockFromSolution(in gps.Solution, prune gps.CascadingPruneOptions) *Lock { 234 p := in.Projects() 235 236 l := &Lock{ 237 SolveMeta: SolveMeta{ 238 AnalyzerName: in.AnalyzerName(), 239 AnalyzerVersion: in.AnalyzerVersion(), 240 InputImports: in.InputImports(), 241 SolverName: in.SolverName(), 242 SolverVersion: in.SolverVersion(), 243 }, 244 P: make([]gps.LockedProject, 0, len(p)), 245 } 246 247 for _, lp := range p { 248 if vp, ok := lp.(verify.VerifiableProject); ok { 249 l.P = append(l.P, vp) 250 } else { 251 l.P = append(l.P, verify.VerifiableProject{ 252 LockedProject: lp, 253 PruneOpts: prune.PruneOptionsFor(lp.Ident().ProjectRoot), 254 }) 255 } 256 } 257 258 return l 259} 260