forked from golang/dep
-
Notifications
You must be signed in to change notification settings - Fork 0
/
lock.go
235 lines (197 loc) · 6.11 KB
/
lock.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
// Copyright 2017 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 gps
import (
"bytes"
"fmt"
"sort"
)
// Lock represents data from a lock file (or however the implementing tool
// chooses to store it) at a particular version that is relevant to the
// satisfiability solving process.
//
// In general, the information produced by gps on finding a successful
// solution is all that would be necessary to constitute a lock file, though
// tools can include whatever other information they want in their storage.
type Lock interface {
// The hash digest of inputs to gps that resulted in this lock data.
InputsDigest() []byte
// Projects returns the list of LockedProjects contained in the lock data.
Projects() []LockedProject
}
// LocksAreEq checks if two locks are equivalent. This checks that
// all contained LockedProjects are equal, and optionally (if the third
// parameter is true) whether the locks' input hashes are equal.
func LocksAreEq(l1, l2 Lock, checkHash bool) bool {
// Cheapest ops first
if checkHash && !bytes.Equal(l1.InputsDigest(), l2.InputsDigest()) {
return false
}
p1, p2 := l1.Projects(), l2.Projects()
if len(p1) != len(p2) {
return false
}
p1 = sortedLockedProjects(p1)
p2 = sortedLockedProjects(p2)
for k, lp := range p1 {
if !lp.Eq(p2[k]) {
return false
}
}
return true
}
// sortedLockedProjects returns a sorted copy of lps, or itself if already sorted.
func sortedLockedProjects(lps []LockedProject) []LockedProject {
if len(lps) <= 1 || sort.SliceIsSorted(lps, func(i, j int) bool {
return lps[i].Ident().Less(lps[j].Ident())
}) {
return lps
}
cp := make([]LockedProject, len(lps))
copy(cp, lps)
sort.Slice(cp, func(i, j int) bool {
return cp[i].Ident().Less(cp[j].Ident())
})
return cp
}
// LockedProject is a single project entry from a lock file. It expresses the
// project's name, one or both of version and underlying revision, the network
// URI for accessing it, the path at which it should be placed within a vendor
// directory, and the packages that are used in it.
type LockedProject struct {
pi ProjectIdentifier
v UnpairedVersion
r Revision
pkgs []string
}
// SimpleLock is a helper for tools to easily describe lock data when they know
// that no hash, or other complex information, is available.
type SimpleLock []LockedProject
var _ Lock = SimpleLock{}
// InputsDigest always returns an empty string for SimpleLock. This makes it useless
// as a stable lock to be written to disk, but still useful for some ephemeral
// purposes.
func (SimpleLock) InputsDigest() []byte {
return nil
}
// Projects returns the entire contents of the SimpleLock.
func (l SimpleLock) Projects() []LockedProject {
return l
}
// NewLockedProject creates a new LockedProject struct with a given
// ProjectIdentifier (name and optional upstream source URL), version. and list
// of packages required from the project.
//
// Note that passing a nil version will cause a panic. This is a correctness
// measure to ensure that the solver is never exposed to a version-less lock
// entry. Such a case would be meaningless - the solver would have no choice but
// to simply dismiss that project. By creating a hard failure case via panic
// instead, we are trying to avoid inflicting the resulting pain on the user by
// instead forcing a decision on the Analyzer implementation.
func NewLockedProject(id ProjectIdentifier, v Version, pkgs []string) LockedProject {
if v == nil {
panic("must provide a non-nil version to create a LockedProject")
}
lp := LockedProject{
pi: id,
pkgs: pkgs,
}
switch tv := v.(type) {
case Revision:
lp.r = tv
case branchVersion:
lp.v = tv
case semVersion:
lp.v = tv
case plainVersion:
lp.v = tv
case versionPair:
lp.r = tv.r
lp.v = tv.v
}
return lp
}
// Ident returns the identifier describing the project. This includes both the
// local name (the root name by which the project is referenced in import paths)
// and the network name, where the upstream source lives.
func (lp LockedProject) Ident() ProjectIdentifier {
return lp.pi
}
// Version assembles together whatever version and/or revision data is
// available into a single Version.
func (lp LockedProject) Version() Version {
if lp.r == "" {
return lp.v
}
if lp.v == nil {
return lp.r
}
return lp.v.Pair(lp.r)
}
// Eq checks if two LockedProject instances are equal.
func (lp LockedProject) Eq(lp2 LockedProject) bool {
if lp.pi != lp2.pi {
return false
}
if lp.r != lp2.r {
return false
}
if len(lp.pkgs) != len(lp2.pkgs) {
return false
}
for k, v := range lp.pkgs {
if lp2.pkgs[k] != v {
return false
}
}
v1n := lp.v == nil
v2n := lp2.v == nil
if v1n != v2n {
return false
}
if !v1n && !lp.v.Matches(lp2.v) {
return false
}
return true
}
// Packages returns the list of packages from within the LockedProject that are
// actually used in the import graph. Some caveats:
//
// * The names given are relative to the root import path for the project. If
// the root package itself is imported, it's represented as ".".
// * Just because a package path isn't included in this list doesn't mean it's
// safe to remove - it could contain C files, or other assets, that can't be
// safely removed.
// * The slice is not a copy. If you need to modify it, copy it first.
func (lp LockedProject) Packages() []string {
return lp.pkgs
}
func (lp LockedProject) String() string {
return fmt.Sprintf("%s@%s with packages: %v",
lp.Ident(), lp.Version(), lp.pkgs)
}
type safeLock struct {
h []byte
p []LockedProject
}
func (sl safeLock) InputsDigest() []byte {
return sl.h
}
func (sl safeLock) Projects() []LockedProject {
return sl.p
}
// prepLock ensures a lock is prepared and safe for use by the solver. This is
// mostly about defensively ensuring that no outside routine can modify the lock
// while the solver is in-flight.
//
// This is achieved by copying the lock's data into a new safeLock.
func prepLock(l Lock) safeLock {
pl := l.Projects()
rl := safeLock{
h: l.InputsDigest(),
p: make([]LockedProject, len(pl)),
}
copy(rl.p, pl)
return rl
}