github.com/sdboyer/gps@v0.16.3/constraints.go (about) 1 package gps 2 3 import ( 4 "fmt" 5 "sort" 6 7 "github.com/Masterminds/semver" 8 ) 9 10 var ( 11 none = noneConstraint{} 12 any = anyConstraint{} 13 ) 14 15 // A Constraint provides structured limitations on the versions that are 16 // admissible for a given project. 17 // 18 // As with Version, it has a private method because the gps's internal 19 // implementation of the problem is complete, and the system relies on type 20 // magic to operate. 21 type Constraint interface { 22 fmt.Stringer 23 24 // Matches indicates if the provided Version is allowed by the Constraint. 25 Matches(Version) bool 26 27 // MatchesAny indicates if the intersection of the Constraint with the 28 // provided Constraint would yield a Constraint that could allow *any* 29 // Version. 30 MatchesAny(Constraint) bool 31 32 // Intersect computes the intersection of the Constraint with the provided 33 // Constraint. 34 Intersect(Constraint) Constraint 35 36 // typedString emits the normal stringified representation of the provided 37 // constraint, prefixed with a string that uniquely identifies the type of 38 // the constraint. 39 // 40 // It also forces Constraint to be a private/sealed interface, which is a 41 // design goal of the system. 42 typedString() string 43 } 44 45 // NewSemverConstraint attempts to construct a semver Constraint object from the 46 // input string. 47 // 48 // If the input string cannot be made into a valid semver Constraint, an error 49 // is returned. 50 func NewSemverConstraint(body string) (Constraint, error) { 51 c, err := semver.NewConstraint(body) 52 if err != nil { 53 return nil, err 54 } 55 // If we got a simple semver.Version, simplify by returning our 56 // corresponding type 57 if sv, ok := c.(*semver.Version); ok { 58 return semVersion{sv: sv}, nil 59 } 60 return semverConstraint{c: c}, nil 61 } 62 63 type semverConstraint struct { 64 c semver.Constraint 65 } 66 67 func (c semverConstraint) String() string { 68 return c.c.String() 69 } 70 71 func (c semverConstraint) typedString() string { 72 return fmt.Sprintf("svc-%s", c.c.String()) 73 } 74 75 func (c semverConstraint) Matches(v Version) bool { 76 switch tv := v.(type) { 77 case versionTypeUnion: 78 for _, elem := range tv { 79 if c.Matches(elem) { 80 return true 81 } 82 } 83 case semVersion: 84 return c.c.Matches(tv.sv) == nil 85 case versionPair: 86 if tv2, ok := tv.v.(semVersion); ok { 87 return c.c.Matches(tv2.sv) == nil 88 } 89 } 90 91 return false 92 } 93 94 func (c semverConstraint) MatchesAny(c2 Constraint) bool { 95 return c.Intersect(c2) != none 96 } 97 98 func (c semverConstraint) Intersect(c2 Constraint) Constraint { 99 switch tc := c2.(type) { 100 case anyConstraint: 101 return c 102 case versionTypeUnion: 103 for _, elem := range tc { 104 if rc := c.Intersect(elem); rc != none { 105 return rc 106 } 107 } 108 case semverConstraint: 109 rc := c.c.Intersect(tc.c) 110 if !semver.IsNone(rc) { 111 return semverConstraint{c: rc} 112 } 113 case semVersion: 114 rc := c.c.Intersect(tc.sv) 115 if !semver.IsNone(rc) { 116 // If single version intersected with constraint, we know the result 117 // must be the single version, so just return it back out 118 return c2 119 } 120 case versionPair: 121 if tc2, ok := tc.v.(semVersion); ok { 122 rc := c.c.Intersect(tc2.sv) 123 if !semver.IsNone(rc) { 124 // same reasoning as previous case 125 return c2 126 } 127 } 128 } 129 130 return none 131 } 132 133 // IsAny indicates if the provided constraint is the wildcard "Any" constraint. 134 func IsAny(c Constraint) bool { 135 _, ok := c.(anyConstraint) 136 return ok 137 } 138 139 // Any returns a constraint that will match anything. 140 func Any() Constraint { 141 return anyConstraint{} 142 } 143 144 // anyConstraint is an unbounded constraint - it matches all other types of 145 // constraints. It mirrors the behavior of the semver package's any type. 146 type anyConstraint struct{} 147 148 func (anyConstraint) String() string { 149 return "*" 150 } 151 152 func (anyConstraint) typedString() string { 153 return "any-*" 154 } 155 156 func (anyConstraint) Matches(Version) bool { 157 return true 158 } 159 160 func (anyConstraint) MatchesAny(Constraint) bool { 161 return true 162 } 163 164 func (anyConstraint) Intersect(c Constraint) Constraint { 165 return c 166 } 167 168 // noneConstraint is the empty set - it matches no versions. It mirrors the 169 // behavior of the semver package's none type. 170 type noneConstraint struct{} 171 172 func (noneConstraint) String() string { 173 return "" 174 } 175 176 func (noneConstraint) typedString() string { 177 return "none-" 178 } 179 180 func (noneConstraint) Matches(Version) bool { 181 return false 182 } 183 184 func (noneConstraint) MatchesAny(Constraint) bool { 185 return false 186 } 187 188 func (noneConstraint) Intersect(Constraint) Constraint { 189 return none 190 } 191 192 // A ProjectConstraint combines a ProjectIdentifier with a Constraint. It 193 // indicates that, if packages contained in the ProjectIdentifier enter the 194 // depgraph, they must do so at a version that is allowed by the Constraint. 195 type ProjectConstraint struct { 196 Ident ProjectIdentifier 197 Constraint Constraint 198 } 199 200 // ProjectConstraints is a map of projects, as identified by their import path 201 // roots (ProjectRoots) to the corresponding ProjectProperties. 202 // 203 // They are the standard form in which Manifests declare their required 204 // dependency properties - constraints and network locations - as well as the 205 // form in which RootManifests declare their overrides. 206 type ProjectConstraints map[ProjectRoot]ProjectProperties 207 208 type workingConstraint struct { 209 Ident ProjectIdentifier 210 Constraint Constraint 211 overrNet, overrConstraint bool 212 } 213 214 func pcSliceToMap(l []ProjectConstraint, r ...[]ProjectConstraint) ProjectConstraints { 215 final := make(ProjectConstraints) 216 217 for _, pc := range l { 218 final[pc.Ident.ProjectRoot] = ProjectProperties{ 219 Source: pc.Ident.Source, 220 Constraint: pc.Constraint, 221 } 222 } 223 224 for _, pcs := range r { 225 for _, pc := range pcs { 226 if pp, exists := final[pc.Ident.ProjectRoot]; exists { 227 // Technically this should be done through a bridge for 228 // cross-version-type matching...but this is a one off for root and 229 // that's just ridiculous for this. 230 pp.Constraint = pp.Constraint.Intersect(pc.Constraint) 231 final[pc.Ident.ProjectRoot] = pp 232 } else { 233 final[pc.Ident.ProjectRoot] = ProjectProperties{ 234 Source: pc.Ident.Source, 235 Constraint: pc.Constraint, 236 } 237 } 238 } 239 } 240 241 return final 242 } 243 244 func (m ProjectConstraints) asSortedSlice() []ProjectConstraint { 245 pcs := make([]ProjectConstraint, len(m)) 246 247 k := 0 248 for pr, pp := range m { 249 pcs[k] = ProjectConstraint{ 250 Ident: ProjectIdentifier{ 251 ProjectRoot: pr, 252 Source: pp.Source, 253 }, 254 Constraint: pp.Constraint, 255 } 256 k++ 257 } 258 259 sort.Stable(sortedConstraints(pcs)) 260 return pcs 261 } 262 263 // merge pulls in all the constraints from other ProjectConstraints map(s), 264 // merging them with the receiver into a new ProjectConstraints map. 265 // 266 // If duplicate ProjectRoots are encountered, the constraints are intersected 267 // together and the latter's NetworkName, if non-empty, is taken. 268 func (m ProjectConstraints) merge(other ...ProjectConstraints) (out ProjectConstraints) { 269 plen := len(m) 270 for _, pcm := range other { 271 plen += len(pcm) 272 } 273 274 out = make(ProjectConstraints, plen) 275 for pr, pp := range m { 276 out[pr] = pp 277 } 278 279 for _, pcm := range other { 280 for pr, pp := range pcm { 281 if rpp, exists := out[pr]; exists { 282 pp.Constraint = pp.Constraint.Intersect(rpp.Constraint) 283 if pp.Source == "" { 284 pp.Source = rpp.Source 285 } 286 } 287 out[pr] = pp 288 } 289 } 290 291 return 292 } 293 294 // overrideAll treats the receiver ProjectConstraints map as a set of override 295 // instructions, and applies overridden values to the ProjectConstraints. 296 // 297 // A slice of workingConstraint is returned, allowing differentiation between 298 // values that were or were not overridden. 299 func (m ProjectConstraints) overrideAll(pcm ProjectConstraints) (out []workingConstraint) { 300 out = make([]workingConstraint, len(pcm)) 301 k := 0 302 for pr, pp := range pcm { 303 out[k] = m.override(pr, pp) 304 k++ 305 } 306 307 sort.Stable(sortedWC(out)) 308 return 309 } 310 311 // override replaces a single ProjectConstraint with a workingConstraint, 312 // overriding its values if a corresponding entry exists in the 313 // ProjectConstraints map. 314 func (m ProjectConstraints) override(pr ProjectRoot, pp ProjectProperties) workingConstraint { 315 wc := workingConstraint{ 316 Ident: ProjectIdentifier{ 317 ProjectRoot: pr, 318 Source: pp.Source, 319 }, 320 Constraint: pp.Constraint, 321 } 322 323 if opp, has := m[pr]; has { 324 // The rule for overrides is that *any* non-zero value for the prop 325 // should be considered an override, even if it's equal to what's 326 // already there. 327 if opp.Constraint != nil { 328 wc.Constraint = opp.Constraint 329 wc.overrConstraint = true 330 } 331 332 // This may appear incorrect, because the solver encodes meaning into 333 // the empty string for NetworkName (it means that it would use the 334 // import path by default, but could be coerced into using an alternate 335 // URL). However, that 'coercion' can only happen if there's a 336 // disagreement between projects on where a dependency should be sourced 337 // from. Such disagreement is exactly what overrides preclude, so 338 // there's no need to preserve the meaning of "" here - thus, we can 339 // treat it as a zero value and ignore it, rather than applying it. 340 if opp.Source != "" { 341 wc.Ident.Source = opp.Source 342 wc.overrNet = true 343 } 344 } 345 346 return wc 347 } 348 349 type sortedConstraints []ProjectConstraint 350 351 func (s sortedConstraints) Len() int { return len(s) } 352 func (s sortedConstraints) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 353 func (s sortedConstraints) Less(i, j int) bool { return s[i].Ident.less(s[j].Ident) } 354 355 type sortedWC []workingConstraint 356 357 func (s sortedWC) Len() int { return len(s) } 358 func (s sortedWC) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 359 func (s sortedWC) Less(i, j int) bool { return s[i].Ident.less(s[j].Ident) }