]> Cypherpunks.ru repositories - gostls13.git/blob - src/cmd/go/internal/gover/gover.go
cmd/go: switch to newer toolchain in go get as needed
[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 // IsLang reports whether v denotes the overall Go language version
59 // and not a specific release. Starting with the Go 1.21 release, "1.x" denotes
60 // the overall language version; the first release is "1.x.0".
61 // The distinction is important because the relative ordering is
62 //
63 //      1.21 < 1.21rc1 < 1.21.0
64 //
65 // meaning that Go 1.21rc1 and Go 1.21.0 will both handle go.mod files that
66 // say "go 1.21", but Go 1.21rc1 will not handle files that say "go 1.21.0".
67 func IsLang(x string) bool {
68         v := parse(x)
69         return v != version{} && v.patch == "" && v.kind == "" && v.pre == ""
70 }
71
72 // Lang returns the Go language version. For example, Lang("1.2.3") == "1.2".
73 func Lang(x string) string {
74         v := parse(x)
75         if v.minor == "" {
76                 return v.major
77         }
78         return v.major + "." + v.minor
79 }
80
81 // IsPrerelease reports whether v denotes a Go prerelease version.
82 func IsPrerelease(x string) bool {
83         return parse(x).kind != ""
84 }
85
86 // Prev returns the Go major release immediately preceding v,
87 // or v itself if v is the first Go major release (1.0) or not a supported
88 // Go version.
89 //
90 // Examples:
91 //
92 //      Prev("1.2") = "1.1"
93 //      Prev("1.3rc4") = "1.2"
94 //
95 func Prev(x string) string {
96         v := parse(x)
97         if cmpInt(v.minor, "1") <= 0 {
98                 return v.major
99         }
100         return v.major + "." + decInt(v.minor)
101 }
102
103 // IsValid reports whether the version x is valid.
104 func IsValid(x string) bool {
105         return parse(x) != version{}
106 }
107
108 // parse parses the Go version string x into a version.
109 // It returns the zero version if x is malformed.
110 func parse(x string) version {
111         var v version
112
113         // Parse major version.
114         var ok bool
115         v.major, x, ok = cutInt(x)
116         if !ok {
117                 return version{}
118         }
119         if x == "" {
120                 // Interpret "1" as "1.0.0".
121                 v.minor = "0"
122                 v.patch = "0"
123                 return v
124         }
125
126         // Parse . before minor version.
127         if x[0] != '.' {
128                 return version{}
129         }
130
131         // Parse minor version.
132         v.minor, x, ok = cutInt(x[1:])
133         if !ok {
134                 return version{}
135         }
136         if x == "" {
137                 // Patch missing is same as "0" for older versions.
138                 // Starting in Go 1.21, patch missing is different from explicit .0.
139                 if cmpInt(v.minor, "21") < 0 {
140                         v.patch = "0"
141                 }
142                 return v
143         }
144
145         // Parse patch if present.
146         if x[0] == '.' {
147                 v.patch, x, ok = cutInt(x[1:])
148                 if !ok || x != "" {
149                         // Note that we are disallowing prereleases (alpha, beta, rc) for patch releases here (x != "").
150                         // Allowing them would be a bit confusing because we already have:
151                         //      1.21 < 1.21rc1
152                         // But a prerelease of a patch would have the opposite effect:
153                         //      1.21.3rc1 < 1.21.3
154                         // We've never needed them before, so let's not start now.
155                         return version{}
156                 }
157                 return v
158         }
159
160         // Parse prerelease.
161         i := 0
162         for i < len(x) && (x[i] < '0' || '9' < x[i]) {
163                 i++
164         }
165         if i == 0 {
166                 return version{}
167         }
168         v.kind, x = x[:i], x[i:]
169         if x == "" {
170                 return v
171         }
172         v.pre, x, ok = cutInt(x)
173         if !ok || x != "" {
174                 return version{}
175         }
176
177         return v
178 }
179
180 // cutInt scans the leading decimal number at the start of x to an integer
181 // and returns that value and the rest of the string.
182 func cutInt(x string) (n, rest string, ok bool) {
183         i := 0
184         for i < len(x) && '0' <= x[i] && x[i] <= '9' {
185                 i++
186         }
187         if i == 0 || x[0] == '0' && i != 1 {
188                 return "", "", false
189         }
190         return x[:i], x[i:], true
191 }
192
193 // cmpInt returns cmp.Compare(x, y) interpreting x and y as decimal numbers.
194 // (Copied from golang.org/x/mod/semver's compareInt.)
195 func cmpInt(x, y string) int {
196         if x == y {
197                 return 0
198         }
199         if len(x) < len(y) {
200                 return -1
201         }
202         if len(x) > len(y) {
203                 return +1
204         }
205         if x < y {
206                 return -1
207         } else {
208                 return +1
209         }
210 }
211
212 // decInt returns the decimal string decremented by 1, or the empty string
213 // if the decimal is all zeroes.
214 // (Copied from golang.org/x/mod/module's decDecimal.)
215 func decInt(decimal string) string {
216         // Scan right to left turning 0s to 9s until you find a digit to decrement.
217         digits := []byte(decimal)
218         i := len(digits) - 1
219         for ; i >= 0 && digits[i] == '0'; i-- {
220                 digits[i] = '9'
221         }
222         if i < 0 {
223                 // decimal is all zeros
224                 return ""
225         }
226         if i == 0 && digits[i] == '1' && len(digits) > 1 {
227                 digits = digits[1:]
228         } else {
229                 digits[i]--
230         }
231         return string(digits)
232 }