]> Cypherpunks.ru repositories - gostls13.git/blob - src/cmd/go/internal/gover/gover.go
cmd/go: add gover.Max and gover.MaxToolchain
[gostls13.git] / src / cmd / go / internal / gover / gover.go
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.
4
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.
11 package gover
12
13 import (
14         "cmp"
15 )
16
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.)
23 type version struct {
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 ""
29 }
30
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 {
37         vx := parse(x)
38         vy := parse(y)
39
40         if c := cmpInt(vx.major, vy.major); c != 0 {
41                 return c
42         }
43         if c := cmpInt(vx.minor, vy.minor); c != 0 {
44                 return c
45         }
46         if c := cmpInt(vx.patch, vy.patch); c != 0 {
47                 return c
48         }
49         if c := cmp.Compare(vx.kind, vy.kind); c != 0 { // "" < alpha < beta < rc
50                 return c
51         }
52         if c := cmpInt(vx.pre, vy.pre); c != 0 {
53                 return c
54         }
55         return 0
56 }
57
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 {
63                 return y
64         }
65         return x
66 }
67
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 {
73                 return y
74         }
75         return x
76 }
77
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
82 //
83 //      1.21 < 1.21rc1 < 1.21.0
84 //
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 {
88         v := parse(x)
89         return v != version{} && v.patch == "" && v.kind == "" && v.pre == ""
90 }
91
92 // Lang returns the Go language version. For example, Lang("1.2.3") == "1.2".
93 func Lang(x string) string {
94         v := parse(x)
95         if v.minor == "" {
96                 return v.major
97         }
98         return v.major + "." + v.minor
99 }
100
101 // IsPrerelease reports whether v denotes a Go prerelease version.
102 func IsPrerelease(x string) bool {
103         return parse(x).kind != ""
104 }
105
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
108 // Go version.
109 //
110 // Examples:
111 //
112 //      Prev("1.2") = "1.1"
113 //      Prev("1.3rc4") = "1.2"
114 //
115 func Prev(x string) string {
116         v := parse(x)
117         if cmpInt(v.minor, "1") <= 0 {
118                 return v.major
119         }
120         return v.major + "." + decInt(v.minor)
121 }
122
123 // IsValid reports whether the version x is valid.
124 func IsValid(x string) bool {
125         return parse(x) != version{}
126 }
127
128 // parse parses the Go version string x into a version.
129 // It returns the zero version if x is malformed.
130 func parse(x string) version {
131         var v version
132
133         // Parse major version.
134         var ok bool
135         v.major, x, ok = cutInt(x)
136         if !ok {
137                 return version{}
138         }
139         if x == "" {
140                 // Interpret "1" as "1.0.0".
141                 v.minor = "0"
142                 v.patch = "0"
143                 return v
144         }
145
146         // Parse . before minor version.
147         if x[0] != '.' {
148                 return version{}
149         }
150
151         // Parse minor version.
152         v.minor, x, ok = cutInt(x[1:])
153         if !ok {
154                 return version{}
155         }
156         if x == "" {
157                 // Patch missing is same as "0" for older versions.
158                 // Starting in Go 1.21, patch missing is different from explicit .0.
159                 if cmpInt(v.minor, "21") < 0 {
160                         v.patch = "0"
161                 }
162                 return v
163         }
164
165         // Parse patch if present.
166         if x[0] == '.' {
167                 v.patch, x, ok = cutInt(x[1:])
168                 if !ok || x != "" {
169                         // Note that we are disallowing prereleases (alpha, beta, rc) for patch releases here (x != "").
170                         // Allowing them would be a bit confusing because we already have:
171                         //      1.21 < 1.21rc1
172                         // But a prerelease of a patch would have the opposite effect:
173                         //      1.21.3rc1 < 1.21.3
174                         // We've never needed them before, so let's not start now.
175                         return version{}
176                 }
177                 return v
178         }
179
180         // Parse prerelease.
181         i := 0
182         for i < len(x) && (x[i] < '0' || '9' < x[i]) {
183                 i++
184         }
185         if i == 0 {
186                 return version{}
187         }
188         v.kind, x = x[:i], x[i:]
189         if x == "" {
190                 return v
191         }
192         v.pre, x, ok = cutInt(x)
193         if !ok || x != "" {
194                 return version{}
195         }
196
197         return v
198 }
199
200 // cutInt scans the leading decimal number at the start of x to an integer
201 // and returns that value and the rest of the string.
202 func cutInt(x string) (n, rest string, ok bool) {
203         i := 0
204         for i < len(x) && '0' <= x[i] && x[i] <= '9' {
205                 i++
206         }
207         if i == 0 || x[0] == '0' && i != 1 {
208                 return "", "", false
209         }
210         return x[:i], x[i:], true
211 }
212
213 // cmpInt returns cmp.Compare(x, y) interpreting x and y as decimal numbers.
214 // (Copied from golang.org/x/mod/semver's compareInt.)
215 func cmpInt(x, y string) int {
216         if x == y {
217                 return 0
218         }
219         if len(x) < len(y) {
220                 return -1
221         }
222         if len(x) > len(y) {
223                 return +1
224         }
225         if x < y {
226                 return -1
227         } else {
228                 return +1
229         }
230 }
231
232 // decInt returns the decimal string decremented by 1, or the empty string
233 // if the decimal is all zeroes.
234 // (Copied from golang.org/x/mod/module's decDecimal.)
235 func decInt(decimal string) string {
236         // Scan right to left turning 0s to 9s until you find a digit to decrement.
237         digits := []byte(decimal)
238         i := len(digits) - 1
239         for ; i >= 0 && digits[i] == '0'; i-- {
240                 digits[i] = '9'
241         }
242         if i < 0 {
243                 // decimal is all zeros
244                 return ""
245         }
246         if i == 0 && digits[i] == '1' && len(digits) > 1 {
247                 digits = digits[1:]
248         } else {
249                 digits[i]--
250         }
251         return string(digits)
252 }