This repository has been archived by the owner on Feb 3, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 22
/
Copy pathlock.go
245 lines (204 loc) · 6.27 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
236
237
238
239
240
241
242
243
244
245
package gps
import (
"bytes"
"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 {
// Indicates the version of the solver used to generate this lock data
//SolverVersion() string
// The hash of inputs to gps that resulted in this lock data
InputHash() []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.InputHash(), l2.InputHash()) {
return false
}
p1, p2 := l1.Projects(), l2.Projects()
if len(p1) != len(p2) {
return false
}
// Check if the slices are sorted already. If they are, we can compare
// without copying. Otherwise, we have to copy to avoid altering the
// original input.
sp1, sp2 := lpsorter(p1), lpsorter(p2)
if len(p1) > 1 && !sort.IsSorted(sp1) {
p1 = make([]LockedProject, len(p1))
copy(p1, l1.Projects())
sort.Sort(lpsorter(p1))
}
if len(p2) > 1 && !sort.IsSorted(sp2) {
p2 = make([]LockedProject, len(p2))
copy(p2, l2.Projects())
sort.Sort(lpsorter(p2))
}
for k, lp := range p1 {
if !lp.Eq(p2[k]) {
return false
}
}
return true
}
// 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{}
// InputHash 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) InputHash() []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.Is(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
}
type safeLock struct {
h []byte
p []LockedProject
}
func (sl safeLock) InputHash() []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.InputHash(),
p: make([]LockedProject, len(pl)),
}
copy(rl.p, pl)
return rl
}
// SortLockedProjects sorts a slice of LockedProject in alphabetical order by
// ProjectRoot.
func SortLockedProjects(lps []LockedProject) {
sort.Stable(lpsorter(lps))
}
type lpsorter []LockedProject
func (lps lpsorter) Swap(i, j int) {
lps[i], lps[j] = lps[j], lps[i]
}
func (lps lpsorter) Len() int {
return len(lps)
}
func (lps lpsorter) Less(i, j int) bool {
return lps[i].pi.ProjectRoot < lps[j].pi.ProjectRoot
}