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
/
constraints.go
359 lines (304 loc) · 9.37 KB
/
constraints.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
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
package gps
import (
"fmt"
"sort"
"github.com/Masterminds/semver"
)
var (
none = noneConstraint{}
any = anyConstraint{}
)
// A Constraint provides structured limitations on the versions that are
// admissible for a given project.
//
// As with Version, it has a private method because the gps's internal
// implementation of the problem is complete, and the system relies on type
// magic to operate.
type Constraint interface {
fmt.Stringer
// Matches indicates if the provided Version is allowed by the Constraint.
Matches(Version) bool
// MatchesAny indicates if the intersection of the Constraint with the
// provided Constraint would yield a Constraint that could allow *any*
// Version.
MatchesAny(Constraint) bool
// Intersect computes the intersection of the Constraint with the provided
// Constraint.
Intersect(Constraint) Constraint
// typedString emits the normal stringified representation of the provided
// constraint, prefixed with a string that uniquely identifies the type of
// the constraint.
//
// It also forces Constraint to be a private/sealed interface, which is a
// design goal of the system.
typedString() string
}
// NewSemverConstraint attempts to construct a semver Constraint object from the
// input string.
//
// If the input string cannot be made into a valid semver Constraint, an error
// is returned.
func NewSemverConstraint(body string) (Constraint, error) {
c, err := semver.NewConstraint(body)
if err != nil {
return nil, err
}
// If we got a simple semver.Version, simplify by returning our
// corresponding type
if sv, ok := c.(*semver.Version); ok {
return semVersion{sv: sv}, nil
}
return semverConstraint{c: c}, nil
}
type semverConstraint struct {
c semver.Constraint
}
func (c semverConstraint) String() string {
return c.c.String()
}
func (c semverConstraint) typedString() string {
return fmt.Sprintf("svc-%s", c.c.String())
}
func (c semverConstraint) Matches(v Version) bool {
switch tv := v.(type) {
case versionTypeUnion:
for _, elem := range tv {
if c.Matches(elem) {
return true
}
}
case semVersion:
return c.c.Matches(tv.sv) == nil
case versionPair:
if tv2, ok := tv.v.(semVersion); ok {
return c.c.Matches(tv2.sv) == nil
}
}
return false
}
func (c semverConstraint) MatchesAny(c2 Constraint) bool {
return c.Intersect(c2) != none
}
func (c semverConstraint) Intersect(c2 Constraint) Constraint {
switch tc := c2.(type) {
case anyConstraint:
return c
case versionTypeUnion:
for _, elem := range tc {
if rc := c.Intersect(elem); rc != none {
return rc
}
}
case semverConstraint:
rc := c.c.Intersect(tc.c)
if !semver.IsNone(rc) {
return semverConstraint{c: rc}
}
case semVersion:
rc := c.c.Intersect(tc.sv)
if !semver.IsNone(rc) {
// If single version intersected with constraint, we know the result
// must be the single version, so just return it back out
return c2
}
case versionPair:
if tc2, ok := tc.v.(semVersion); ok {
rc := c.c.Intersect(tc2.sv)
if !semver.IsNone(rc) {
// same reasoning as previous case
return c2
}
}
}
return none
}
// IsAny indicates if the provided constraint is the wildcard "Any" constraint.
func IsAny(c Constraint) bool {
_, ok := c.(anyConstraint)
return ok
}
// Any returns a constraint that will match anything.
func Any() Constraint {
return anyConstraint{}
}
// anyConstraint is an unbounded constraint - it matches all other types of
// constraints. It mirrors the behavior of the semver package's any type.
type anyConstraint struct{}
func (anyConstraint) String() string {
return "*"
}
func (anyConstraint) typedString() string {
return "any-*"
}
func (anyConstraint) Matches(Version) bool {
return true
}
func (anyConstraint) MatchesAny(Constraint) bool {
return true
}
func (anyConstraint) Intersect(c Constraint) Constraint {
return c
}
// noneConstraint is the empty set - it matches no versions. It mirrors the
// behavior of the semver package's none type.
type noneConstraint struct{}
func (noneConstraint) String() string {
return ""
}
func (noneConstraint) typedString() string {
return "none-"
}
func (noneConstraint) Matches(Version) bool {
return false
}
func (noneConstraint) MatchesAny(Constraint) bool {
return false
}
func (noneConstraint) Intersect(Constraint) Constraint {
return none
}
// A ProjectConstraint combines a ProjectIdentifier with a Constraint. It
// indicates that, if packages contained in the ProjectIdentifier enter the
// depgraph, they must do so at a version that is allowed by the Constraint.
type ProjectConstraint struct {
Ident ProjectIdentifier
Constraint Constraint
}
// ProjectConstraints is a map of projects, as identified by their import path
// roots (ProjectRoots) to the corresponding ProjectProperties.
//
// They are the standard form in which Manifests declare their required
// dependency properties - constraints and network locations - as well as the
// form in which RootManifests declare their overrides.
type ProjectConstraints map[ProjectRoot]ProjectProperties
type workingConstraint struct {
Ident ProjectIdentifier
Constraint Constraint
overrNet, overrConstraint bool
}
func pcSliceToMap(l []ProjectConstraint, r ...[]ProjectConstraint) ProjectConstraints {
final := make(ProjectConstraints)
for _, pc := range l {
final[pc.Ident.ProjectRoot] = ProjectProperties{
Source: pc.Ident.Source,
Constraint: pc.Constraint,
}
}
for _, pcs := range r {
for _, pc := range pcs {
if pp, exists := final[pc.Ident.ProjectRoot]; exists {
// Technically this should be done through a bridge for
// cross-version-type matching...but this is a one off for root and
// that's just ridiculous for this.
pp.Constraint = pp.Constraint.Intersect(pc.Constraint)
final[pc.Ident.ProjectRoot] = pp
} else {
final[pc.Ident.ProjectRoot] = ProjectProperties{
Source: pc.Ident.Source,
Constraint: pc.Constraint,
}
}
}
}
return final
}
func (m ProjectConstraints) asSortedSlice() []ProjectConstraint {
pcs := make([]ProjectConstraint, len(m))
k := 0
for pr, pp := range m {
pcs[k] = ProjectConstraint{
Ident: ProjectIdentifier{
ProjectRoot: pr,
Source: pp.Source,
},
Constraint: pp.Constraint,
}
k++
}
sort.Stable(sortedConstraints(pcs))
return pcs
}
// merge pulls in all the constraints from other ProjectConstraints map(s),
// merging them with the receiver into a new ProjectConstraints map.
//
// If duplicate ProjectRoots are encountered, the constraints are intersected
// together and the latter's NetworkName, if non-empty, is taken.
func (m ProjectConstraints) merge(other ...ProjectConstraints) (out ProjectConstraints) {
plen := len(m)
for _, pcm := range other {
plen += len(pcm)
}
out = make(ProjectConstraints, plen)
for pr, pp := range m {
out[pr] = pp
}
for _, pcm := range other {
for pr, pp := range pcm {
if rpp, exists := out[pr]; exists {
pp.Constraint = pp.Constraint.Intersect(rpp.Constraint)
if pp.Source == "" {
pp.Source = rpp.Source
}
}
out[pr] = pp
}
}
return
}
// overrideAll treats the receiver ProjectConstraints map as a set of override
// instructions, and applies overridden values to the ProjectConstraints.
//
// A slice of workingConstraint is returned, allowing differentiation between
// values that were or were not overridden.
func (m ProjectConstraints) overrideAll(pcm ProjectConstraints) (out []workingConstraint) {
out = make([]workingConstraint, len(pcm))
k := 0
for pr, pp := range pcm {
out[k] = m.override(pr, pp)
k++
}
sort.Stable(sortedWC(out))
return
}
// override replaces a single ProjectConstraint with a workingConstraint,
// overriding its values if a corresponding entry exists in the
// ProjectConstraints map.
func (m ProjectConstraints) override(pr ProjectRoot, pp ProjectProperties) workingConstraint {
wc := workingConstraint{
Ident: ProjectIdentifier{
ProjectRoot: pr,
Source: pp.Source,
},
Constraint: pp.Constraint,
}
if opp, has := m[pr]; has {
// The rule for overrides is that *any* non-zero value for the prop
// should be considered an override, even if it's equal to what's
// already there.
if opp.Constraint != nil {
wc.Constraint = opp.Constraint
wc.overrConstraint = true
}
// This may appear incorrect, because the solver encodes meaning into
// the empty string for NetworkName (it means that it would use the
// import path by default, but could be coerced into using an alternate
// URL). However, that 'coercion' can only happen if there's a
// disagreement between projects on where a dependency should be sourced
// from. Such disagreement is exactly what overrides preclude, so
// there's no need to preserve the meaning of "" here - thus, we can
// treat it as a zero value and ignore it, rather than applying it.
if opp.Source != "" {
wc.Ident.Source = opp.Source
wc.overrNet = true
}
}
return wc
}
type sortedConstraints []ProjectConstraint
func (s sortedConstraints) Len() int { return len(s) }
func (s sortedConstraints) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s sortedConstraints) Less(i, j int) bool { return s[i].Ident.less(s[j].Ident) }
type sortedWC []workingConstraint
func (s sortedWC) Len() int { return len(s) }
func (s sortedWC) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s sortedWC) Less(i, j int) bool { return s[i].Ident.less(s[j].Ident) }