github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/util/version/version.go (about) 1 // Copyright 2018 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package version 12 13 import ( 14 "fmt" 15 "regexp" 16 "strconv" 17 "strings" 18 19 "github.com/cockroachdb/errors" 20 ) 21 22 // Version represents a semantic version; see 23 // https://semver.org/spec/v2.0.0.html. 24 type Version struct { 25 major int32 26 minor int32 27 patch int32 28 preRelease string 29 metadata string 30 } 31 32 // Major returns the major (first) version number. 33 func (v *Version) Major() int { 34 return int(v.major) 35 } 36 37 // Minor returns the minor (second) version number. 38 func (v *Version) Minor() int { 39 return int(v.minor) 40 } 41 42 // Patch returns the patch (third) version number. 43 func (v *Version) Patch() int { 44 return int(v.patch) 45 } 46 47 // PreRelease returns the pre-release version (if present). 48 func (v *Version) PreRelease() string { 49 return v.preRelease 50 } 51 52 // Metadata returns the metadata (if present). 53 func (v *Version) Metadata() string { 54 return v.metadata 55 } 56 57 // String returns the string representation, in the format: 58 // "v1.2.3-beta+md" 59 func (v Version) String() string { 60 var b strings.Builder 61 fmt.Fprintf(&b, "v%d.%d.%d", v.major, v.minor, v.patch) 62 if v.preRelease != "" { 63 fmt.Fprintf(&b, "-%s", v.preRelease) 64 } 65 if v.metadata != "" { 66 fmt.Fprintf(&b, "+%s", v.metadata) 67 } 68 return b.String() 69 } 70 71 // versionRE is the regexp that is used to verify that a version string is 72 // of the form "vMAJOR.MINOR.PATCH[-PRERELEASE][+METADATA]". This 73 // conforms to https://semver.org/spec/v2.0.0.html 74 var versionRE = regexp.MustCompile( 75 `^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-.]+)?(\+[0-9A-Za-z-.]+|)?$`, 76 // ^major ^minor ^patch ^preRelease ^metadata 77 ) 78 79 // numericRE is the regexp used to check if an identifier is numeric. 80 var numericRE = regexp.MustCompile(`^(0|[1-9][0-9]*)$`) 81 82 // Parse creates a version from a string. The string must be a valid semantic 83 // version (as per https://semver.org/spec/v2.0.0.html) in the format: 84 // "vMINOR.MAJOR.PATCH[-PRERELEASE][+METADATA]". 85 // MINOR, MAJOR, and PATCH are numeric values (without any leading 0s). 86 // PRERELEASE and METADATA can contain ASCII characters and digits, hyphens and 87 // dots. 88 func Parse(str string) (*Version, error) { 89 if !versionRE.MatchString(str) { 90 return nil, errors.Errorf("invalid version string '%s'", str) 91 } 92 93 var v Version 94 r := strings.NewReader(str) 95 // Read the major.minor.patch part. 96 _, err := fmt.Fscanf(r, "v%d.%d.%d", &v.major, &v.minor, &v.patch) 97 if err != nil { 98 panic(fmt.Sprintf("invalid version '%s' passed the regex: %s", str, err)) 99 } 100 remaining := str[len(str)-r.Len():] 101 // Read the pre-release, if present. 102 if len(remaining) > 0 && remaining[0] == '-' { 103 p := strings.IndexRune(remaining, '+') 104 if p == -1 { 105 p = len(remaining) 106 } 107 v.preRelease = remaining[1:p] 108 remaining = remaining[p:] 109 } 110 // Read the metadata, if present. 111 if len(remaining) > 0 { 112 if remaining[0] != '+' { 113 panic(fmt.Sprintf("invalid version '%s' passed the regex", str)) 114 } 115 v.metadata = remaining[1:] 116 } 117 return &v, nil 118 } 119 120 // MustParse is like Parse but panics on any error. Recommended as an 121 // initializer for global values. 122 func MustParse(str string) *Version { 123 v, err := Parse(str) 124 if err != nil { 125 panic(err) 126 } 127 return v 128 } 129 130 func cmpVal(a, b int32) int { 131 if a > b { 132 return +1 133 } 134 if a < b { 135 return -1 136 } 137 return 0 138 } 139 140 // Compare returns -1, 0, or +1 indicating the relative ordering of versions. 141 func (v *Version) Compare(w *Version) int { 142 if v := cmpVal(v.major, w.major); v != 0 { 143 return v 144 } 145 if v := cmpVal(v.minor, w.minor); v != 0 { 146 return v 147 } 148 if v := cmpVal(v.patch, w.patch); v != 0 { 149 return v 150 } 151 if v.preRelease != w.preRelease { 152 if v.preRelease == "" && w.preRelease != "" { 153 // 1.0.0 is greater than 1.0.0-alpha. 154 return 1 155 } 156 if v.preRelease != "" && w.preRelease == "" { 157 // 1.0.0-alpha is less than 1.0.0. 158 return -1 159 } 160 161 // Quoting from https://semver.org/spec/v2.0.0.html: 162 // Precedence for two pre-release versions with the same major, minor, and 163 // patch version MUST be determined by comparing each dot separated 164 // identifier from left to right until a difference is found as follows: 165 // (1) Identifiers consisting of only digits are compared numerically. 166 // (2) identifiers with letters or hyphens are compared lexically in ASCII 167 // sort order. 168 // (3) Numeric identifiers always have lower precedence than non-numeric 169 // identifiers. 170 // (4) A larger set of pre-release fields has a higher precedence than a 171 // smaller set, if all of the preceding identifiers are equal. 172 // 173 vs := strings.Split(v.preRelease, ".") 174 ws := strings.Split(w.preRelease, ".") 175 for ; len(vs) > 0 && len(ws) > 0; vs, ws = vs[1:], ws[1:] { 176 vStr, wStr := vs[0], ws[0] 177 if vStr == wStr { 178 continue 179 } 180 vNumeric := numericRE.MatchString(vStr) 181 wNumeric := numericRE.MatchString(wStr) 182 switch { 183 case vNumeric && wNumeric: 184 // Case 1. 185 vVal, err := strconv.Atoi(vStr) 186 if err != nil { 187 panic(err) 188 } 189 wVal, err := strconv.Atoi(wStr) 190 if err != nil { 191 panic(err) 192 } 193 if vVal == wVal { 194 panic("different strings yield the same numbers") 195 } 196 if vVal < wVal { 197 return -1 198 } 199 return 1 200 201 case vNumeric: 202 // Case 3. 203 return -1 204 205 case wNumeric: 206 // Case 3. 207 return 1 208 209 default: 210 // Case 2. 211 if vStr < wStr { 212 return -1 213 } 214 return 1 215 } 216 } 217 218 if len(vs) > 0 { 219 // Case 4. 220 return +1 221 } 222 if len(ws) > 0 { 223 // Case 4. 224 return -1 225 } 226 } 227 228 return 0 229 } 230 231 // AtLeast returns true if v >= w. 232 func (v *Version) AtLeast(w *Version) bool { 233 return v.Compare(w) >= 0 234 }