github.com/cockroachdb/cockroachdb-parser@v0.23.3-0.20240213214944-911057d40c9a/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 // 59 // "v1.2.3-beta+md" 60 func (v Version) String() string { 61 var b strings.Builder 62 fmt.Fprintf(&b, "v%d.%d.%d", v.major, v.minor, v.patch) 63 if v.preRelease != "" { 64 fmt.Fprintf(&b, "-%s", v.preRelease) 65 } 66 if v.metadata != "" { 67 fmt.Fprintf(&b, "+%s", v.metadata) 68 } 69 return b.String() 70 } 71 72 // versionRE is the regexp that is used to verify that a version string is 73 // of the form "vMAJOR.MINOR.PATCH[-PRERELEASE][+METADATA]". This 74 // conforms to https://semver.org/spec/v2.0.0.html 75 var versionRE = regexp.MustCompile( 76 `^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-.]+)?(\+[0-9A-Za-z-.]+|)?$`, 77 // ^major ^minor ^patch ^preRelease ^metadata 78 ) 79 80 // numericRE is the regexp used to check if an identifier is numeric. 81 var numericRE = regexp.MustCompile(`^(0|[1-9][0-9]*)$`) 82 83 // Parse creates a version from a string. The string must be a valid semantic 84 // version (as per https://semver.org/spec/v2.0.0.html) in the format: 85 // 86 // "vMINOR.MAJOR.PATCH[-PRERELEASE][+METADATA]". 87 // 88 // MINOR, MAJOR, and PATCH are numeric values (without any leading 0s). 89 // PRERELEASE and METADATA can contain ASCII characters and digits, hyphens and 90 // dots. 91 func Parse(str string) (*Version, error) { 92 if !versionRE.MatchString(str) { 93 return nil, errors.Errorf("invalid version string '%s'", str) 94 } 95 96 var v Version 97 r := strings.NewReader(str) 98 // Read the major.minor.patch part. 99 _, err := fmt.Fscanf(r, "v%d.%d.%d", &v.major, &v.minor, &v.patch) 100 if err != nil { 101 panic(fmt.Sprintf("invalid version '%s' passed the regex: %s", str, err)) 102 } 103 remaining := str[len(str)-r.Len():] 104 // Read the pre-release, if present. 105 if len(remaining) > 0 && remaining[0] == '-' { 106 p := strings.IndexRune(remaining, '+') 107 if p == -1 { 108 p = len(remaining) 109 } 110 v.preRelease = remaining[1:p] 111 remaining = remaining[p:] 112 } 113 // Read the metadata, if present. 114 if len(remaining) > 0 { 115 if remaining[0] != '+' { 116 panic(fmt.Sprintf("invalid version '%s' passed the regex", str)) 117 } 118 v.metadata = remaining[1:] 119 } 120 return &v, nil 121 } 122 123 // MustParse is like Parse but panics on any error. Recommended as an 124 // initializer for global values. 125 func MustParse(str string) *Version { 126 v, err := Parse(str) 127 if err != nil { 128 panic(err) 129 } 130 return v 131 } 132 133 func cmpVal(a, b int32) int { 134 if a > b { 135 return +1 136 } 137 if a < b { 138 return -1 139 } 140 return 0 141 } 142 143 // Compare returns -1, 0, or +1 indicating the relative ordering of versions. 144 func (v *Version) Compare(w *Version) int { 145 if v := cmpVal(v.major, w.major); v != 0 { 146 return v 147 } 148 if v := cmpVal(v.minor, w.minor); v != 0 { 149 return v 150 } 151 if v := cmpVal(v.patch, w.patch); v != 0 { 152 return v 153 } 154 if v.preRelease != w.preRelease { 155 if v.preRelease == "" && w.preRelease != "" { 156 // 1.0.0 is greater than 1.0.0-alpha. 157 return 1 158 } 159 if v.preRelease != "" && w.preRelease == "" { 160 // 1.0.0-alpha is less than 1.0.0. 161 return -1 162 } 163 164 // Quoting from https://semver.org/spec/v2.0.0.html: 165 // Precedence for two pre-release versions with the same major, minor, and 166 // patch version MUST be determined by comparing each dot separated 167 // identifier from left to right until a difference is found as follows: 168 // (1) Identifiers consisting of only digits are compared numerically. 169 // (2) identifiers with letters or hyphens are compared lexically in ASCII 170 // sort order. 171 // (3) Numeric identifiers always have lower precedence than non-numeric 172 // identifiers. 173 // (4) A larger set of pre-release fields has a higher precedence than a 174 // smaller set, if all of the preceding identifiers are equal. 175 // 176 vs := strings.Split(v.preRelease, ".") 177 ws := strings.Split(w.preRelease, ".") 178 for ; len(vs) > 0 && len(ws) > 0; vs, ws = vs[1:], ws[1:] { 179 vStr, wStr := vs[0], ws[0] 180 if vStr == wStr { 181 continue 182 } 183 vNumeric := numericRE.MatchString(vStr) 184 wNumeric := numericRE.MatchString(wStr) 185 switch { 186 case vNumeric && wNumeric: 187 // Case 1. 188 vVal, err := strconv.Atoi(vStr) 189 if err != nil { 190 panic(err) 191 } 192 wVal, err := strconv.Atoi(wStr) 193 if err != nil { 194 panic(err) 195 } 196 if vVal == wVal { 197 panic("different strings yield the same numbers") 198 } 199 if vVal < wVal { 200 return -1 201 } 202 return 1 203 204 case vNumeric: 205 // Case 3. 206 return -1 207 208 case wNumeric: 209 // Case 3. 210 return 1 211 212 default: 213 // Case 2. 214 if vStr < wStr { 215 return -1 216 } 217 return 1 218 } 219 } 220 221 if len(vs) > 0 { 222 // Case 4. 223 return +1 224 } 225 if len(ws) > 0 { 226 // Case 4. 227 return -1 228 } 229 } 230 231 return 0 232 } 233 234 // AtLeast returns true if v >= w. 235 func (v *Version) AtLeast(w *Version) bool { 236 return v.Compare(w) >= 0 237 }