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