github.com/lbryio/lbcd@v0.22.119/version/version.go (about) 1 package version 2 3 import ( 4 "fmt" 5 "runtime/debug" 6 "strconv" 7 "strings" 8 ) 9 10 var appTag = "v0.0.0-local.0" 11 12 // Full returns full version string conforming to semantic versioning 2.0.0 13 // spec (http://semver.org/). 14 // 15 // Major.Minor.Patch-Prerelease+Buildmeta 16 // 17 // Prerelease must be either empty or in the form of Phase.Revision. The Phase 18 // must be local, dev, alpha, beta, or rc. 19 // Buildmeta is full length of 40-digit git commit ID with "-dirty" appended 20 // refelecting uncommited chanegs. 21 // 22 // This function relies injected git version tag in the form of: 23 // 24 // vMajor.Minor.Patch-Prerelease 25 // 26 // The injection can be done with go build flags for example: 27 // 28 // go build -ldflags "-X github.com/lbryio/lbcd/version.appTag=v1.2.3-beta.45" 29 // 30 // Without explicitly injected tag, a default one - "v0.0.0-local.0" is used 31 // indicating a local development build. 32 33 // The version is encoded into a int32 numeric form, which imposes valid ranges 34 // on each component: 35 // 36 // Major: 0 - 41 37 // Minor: 0 - 99 38 // Patch: 0 - 999 39 // 40 // Prerelease: Phase.Revision 41 // Phase: [ local | dev | alpha | beta | rc | ] 42 // Revision: 0 - 99 43 // 44 // Buildmeta: CommitID or CommitID-dirty 45 // 46 // Examples: 47 // 48 // 1.2.3-beta.45+950b68348261e0b4ff288d216269b8ad2a384411 49 // 2.6.4-alpha.3+92d00aaee19d1709ae64b36682ae9897ef91a2ca-dirty 50 51 func Full() string { 52 return parsed.full() 53 } 54 55 // Numeric returns numeric form of full version (excluding meta) in a 32-bit decimal number. 56 // See Full() for more details. 57 func Numeric() int32 { 58 numeric := parsed.major*100000000 + 59 parsed.minor*1000000 + 60 parsed.patch*1000 + 61 parsed.phase.numeric()*100 + 62 parsed.revision 63 64 return int32(numeric) 65 } 66 67 func init() { 68 69 version, prerelease, err := parseTag(appTag) 70 if err != nil { 71 panic(fmt.Errorf("parse tag: %s; %w", appTag, err)) 72 } 73 74 major, minor, patch, err := parseVersion(version) 75 if err != nil { 76 panic(fmt.Errorf("parse version: %s; %w", version, err)) 77 } 78 79 phase, revision, err := parsePrerelease(prerelease) 80 if err != nil { 81 panic(fmt.Errorf("parse prerelease: %s; %w", prerelease, err)) 82 } 83 84 info, ok := debug.ReadBuildInfo() 85 if !ok { 86 panic(fmt.Errorf("binary must be built with Go 1.18+ with module support")) 87 } 88 89 var commit string 90 var modified bool 91 for _, s := range info.Settings { 92 if s.Key == "vcs.revision" { 93 commit = s.Value 94 } 95 if s.Key == "vcs.modified" && s.Value == "true" { 96 modified = true 97 } 98 } 99 100 parsed = parsedVersion{ 101 version: version, 102 major: major, 103 minor: minor, 104 patch: patch, 105 106 prerelease: prerelease, 107 phase: phase, 108 revision: revision, 109 110 commit: commit, 111 modified: modified, 112 } 113 } 114 115 var parsed parsedVersion 116 117 type parsedVersion struct { 118 version string 119 // Semantic Version 120 major int 121 minor int 122 patch int 123 124 // Prerelease 125 prerelease string 126 phase releasePhase 127 revision int 128 129 // Build Metadata 130 commit string 131 modified bool 132 } 133 134 func (v parsedVersion) buildmeta() string { 135 if !v.modified { 136 return v.commit 137 } 138 return v.commit + "-dirty" 139 } 140 141 func (v parsedVersion) full() string { 142 if len(v.prerelease) > 0 { 143 return fmt.Sprintf("%s-%s+%s", v.version, v.prerelease, v.buildmeta()) 144 } 145 return fmt.Sprintf("%s+%s", v.version, v.buildmeta()) 146 } 147 148 func parseTag(tag string) (version string, prerelease string, err error) { 149 150 if len(tag) == 0 || tag[0] != 'v' { 151 return "", "", fmt.Errorf("tag must be prefixed with v; %s", tag) 152 } 153 154 tag = tag[1:] 155 156 if !strings.Contains(tag, "-") { 157 return tag, "", nil 158 } 159 160 strs := strings.Split(tag, "-") 161 162 if len(strs) != 2 { 163 return "", "", fmt.Errorf("tag must be in the form of Version.Revision; %s", tag) 164 } 165 166 version = strs[0] 167 prerelease = strs[1] 168 169 return version, prerelease, nil 170 } 171 172 func parseVersion(ver string) (major int, minor int, patch int, err error) { 173 174 strs := strings.Split(ver, ".") 175 176 if len(strs) != 3 { 177 return major, minor, patch, fmt.Errorf("invalid format; must be in the form of Major.Minor.Patch") 178 } 179 180 major, err = strconv.Atoi(strs[0]) 181 if err != nil { 182 return major, minor, patch, fmt.Errorf("invalid major: %s", strs[0]) 183 } 184 if major < 0 || major > 41 { 185 return major, minor, patch, fmt.Errorf("major must between 0 - 41; got %d", major) 186 } 187 188 minor, err = strconv.Atoi(strs[1]) 189 if err != nil { 190 return major, minor, patch, fmt.Errorf("invalid minor: %s", strs[1]) 191 } 192 if minor < 0 || minor > 99 { 193 return major, minor, patch, fmt.Errorf("minor must between 0 - 99; got %d", minor) 194 } 195 196 patch, err = strconv.Atoi(strs[2]) 197 if err != nil { 198 return major, minor, patch, fmt.Errorf("invalid patch: %s", strs[2]) 199 } 200 if patch < 0 || patch > 999 { 201 return major, minor, patch, fmt.Errorf("patch must between 0 - 999; got %d", patch) 202 } 203 204 return major, minor, patch, nil 205 } 206 207 func parsePrerelease(pre string) (phase releasePhase, revision int, err error) { 208 209 phase = Unkown 210 211 if pre == "" { 212 return GA, 0, nil 213 } 214 215 strs := strings.Split(pre, ".") 216 if len(strs) != 2 { 217 return phase, revision, fmt.Errorf("prerelease must be in the form of Phase.Revision; got: %s", pre) 218 } 219 220 phase = releasePhase(strs[0]) 221 if phase.numeric() == -1 { 222 return phase, revision, fmt.Errorf("phase must be local, dev, alpha, beta, or rc; got: %s", strs[0]) 223 } 224 225 revision, err = strconv.Atoi(strs[1]) 226 if err != nil { 227 return phase, revision, fmt.Errorf("invalid revision: %s", strs[0]) 228 } 229 if revision < 0 || revision > 99 { 230 return phase, revision, fmt.Errorf("revision must between 0 - 999; got %d", revision) 231 } 232 233 return phase, revision, nil 234 } 235 236 type releasePhase string 237 238 const ( 239 Unkown releasePhase = "unkown" 240 Local releasePhase = "local" 241 Dev releasePhase = "dev" 242 Alpha releasePhase = "alpha" 243 Beta releasePhase = "beta" 244 RC releasePhase = "rc" 245 GA releasePhase = "" 246 ) 247 248 func (p releasePhase) numeric() int { 249 250 switch p { 251 case Local: 252 return 0 253 case Dev: 254 return 1 255 case Alpha: 256 return 2 257 case Beta: 258 return 3 259 case RC: 260 return 4 261 case GA: 262 return 5 263 } 264 265 // Unknown phase 266 return -1 267 }