1 // Copyright 2023 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.
5 // Package gover implements support for Go toolchain versions like 1.21.0 and 1.21rc1.
6 // (For historical reasons, Go does not use semver for its toolchains.)
7 // This package provides the same basic analysis that golang.org/x/mod/semver does for semver.
8 // It also provides some helpers for extracting versions from go.mod files
9 // and for dealing with module.Versions that may use Go versions or semver
10 // depending on the module path.
17 // A version is a parsed Go version: major[.minor[.patch]][kind[pre]]
18 // The numbers are the original decimal strings to avoid integer overflows
19 // and since there is very little actual math. (Probably overflow doesn't matter in practice,
20 // but at the time this code was written, there was an existing test that used
21 // go1.99999999999, which does not fit in an int on 32-bit platforms.
22 // The "big decimal" representation avoids the problem entirely.)
24 major string // decimal
25 minor string // decimal or ""
26 patch string // decimal or ""
27 kind string // "", "alpha", "beta", "rc"
28 pre string // decimal or ""
31 // Compare returns -1, 0, or +1 depending on whether
32 // x < y, x == y, or x > y, interpreted as toolchain versions.
33 // The versions x and y must not begin with a "go" prefix: just "1.21" not "go1.21".
34 // Malformed versions compare less than well-formed versions and equal to each other.
35 // The language version "1.21" compares less than the release candidate and eventual releases "1.21rc1" and "1.21.0".
36 func Compare(x, y string) int {
40 if c := cmpInt(vx.major, vy.major); c != 0 {
43 if c := cmpInt(vx.minor, vy.minor); c != 0 {
46 if c := cmpInt(vx.patch, vy.patch); c != 0 {
49 if c := cmp.Compare(vx.kind, vy.kind); c != 0 { // "" < alpha < beta < rc
52 if c := cmpInt(vx.pre, vy.pre); c != 0 {
58 // Max returns the maximum of x and y interpreted as toolchain versions,
59 // compared using Compare.
60 // If x and y compare equal, Max returns x.
61 func Max(x, y string) string {
62 if Compare(x, y) < 0 {
68 // Toolchain returns the maximum of x and y interpreted as toolchain names,
69 // compared using Compare(FromToolchain(x), FromToolchain(y)).
70 // If x and y compare equal, Max returns x.
71 func ToolchainMax(x, y string) string {
72 if Compare(FromToolchain(x), FromToolchain(y)) < 0 {
78 // IsLang reports whether v denotes the overall Go language version
79 // and not a specific release. Starting with the Go 1.21 release, "1.x" denotes
80 // the overall language version; the first release is "1.x.0".
81 // The distinction is important because the relative ordering is
83 // 1.21 < 1.21rc1 < 1.21.0
85 // meaning that Go 1.21rc1 and Go 1.21.0 will both handle go.mod files that
86 // say "go 1.21", but Go 1.21rc1 will not handle files that say "go 1.21.0".
87 func IsLang(x string) bool {
89 return v != version{} && v.patch == "" && v.kind == "" && v.pre == ""
92 // Lang returns the Go language version. For example, Lang("1.2.3") == "1.2".
93 func Lang(x string) string {
98 return v.major + "." + v.minor
101 // IsPrerelease reports whether v denotes a Go prerelease version.
102 func IsPrerelease(x string) bool {
103 return parse(x).kind != ""
106 // Prev returns the Go major release immediately preceding v,
107 // or v itself if v is the first Go major release (1.0) or not a supported
112 // Prev("1.2") = "1.1"
113 // Prev("1.3rc4") = "1.2"
114 func Prev(x string) string {
116 if cmpInt(v.minor, "1") <= 0 {
119 return v.major + "." + decInt(v.minor)
122 // IsValid reports whether the version x is valid.
123 func IsValid(x string) bool {
124 return parse(x) != version{}
127 // parse parses the Go version string x into a version.
128 // It returns the zero version if x is malformed.
129 func parse(x string) version {
132 // Parse major version.
134 v.major, x, ok = cutInt(x)
139 // Interpret "1" as "1.0.0".
145 // Parse . before minor version.
150 // Parse minor version.
151 v.minor, x, ok = cutInt(x[1:])
156 // Patch missing is same as "0" for older versions.
157 // Starting in Go 1.21, patch missing is different from explicit .0.
158 if cmpInt(v.minor, "21") < 0 {
164 // Parse patch if present.
166 v.patch, x, ok = cutInt(x[1:])
168 // Note that we are disallowing prereleases (alpha, beta, rc) for patch releases here (x != "").
169 // Allowing them would be a bit confusing because we already have:
171 // But a prerelease of a patch would have the opposite effect:
172 // 1.21.3rc1 < 1.21.3
173 // We've never needed them before, so let's not start now.
181 for i < len(x) && (x[i] < '0' || '9' < x[i]) {
182 if x[i] < 'a' || 'z' < x[i] {
190 v.kind, x = x[:i], x[i:]
194 v.pre, x, ok = cutInt(x)
202 // cutInt scans the leading decimal number at the start of x to an integer
203 // and returns that value and the rest of the string.
204 func cutInt(x string) (n, rest string, ok bool) {
206 for i < len(x) && '0' <= x[i] && x[i] <= '9' {
209 if i == 0 || x[0] == '0' && i != 1 {
212 return x[:i], x[i:], true
215 // cmpInt returns cmp.Compare(x, y) interpreting x and y as decimal numbers.
216 // (Copied from golang.org/x/mod/semver's compareInt.)
217 func cmpInt(x, y string) int {
234 // decInt returns the decimal string decremented by 1, or the empty string
235 // if the decimal is all zeroes.
236 // (Copied from golang.org/x/mod/module's decDecimal.)
237 func decInt(decimal string) string {
238 // Scan right to left turning 0s to 9s until you find a digit to decrement.
239 digits := []byte(decimal)
241 for ; i >= 0 && digits[i] == '0'; i-- {
245 // decimal is all zeros
248 if i == 0 && digits[i] == '1' && len(digits) > 1 {
253 return string(digits)