]> Cypherpunks.ru repositories - gostls13.git/blob - src/cmd/go/internal/gover/gover.go
b2a8261feb3ae2d85918da0425922c28c71a493c
[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 func Prev(x string) string {
115         v := parse(x)
116         if cmpInt(v.minor, "1") <= 0 {
117                 return v.major
118         }
119         return v.major + "." + decInt(v.minor)
120 }
121
122 // IsValid reports whether the version x is valid.
123 func IsValid(x string) bool {
124         return parse(x) != version{}
125 }
126
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 {
130         var v version
131
132         // Parse major version.
133         var ok bool
134         v.major, x, ok = cutInt(x)
135         if !ok {
136                 return version{}
137         }
138         if x == "" {
139                 // Interpret "1" as "1.0.0".
140                 v.minor = "0"
141                 v.patch = "0"
142                 return v
143         }
144
145         // Parse . before minor version.
146         if x[0] != '.' {
147                 return version{}
148         }
149
150         // Parse minor version.
151         v.minor, x, ok = cutInt(x[1:])
152         if !ok {
153                 return version{}
154         }
155         if x == "" {
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 {
159                         v.patch = "0"
160                 }
161                 return v
162         }
163
164         // Parse patch if present.
165         if x[0] == '.' {
166                 v.patch, x, ok = cutInt(x[1:])
167                 if !ok || x != "" {
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:
170                         //      1.21 < 1.21rc1
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.
174                         return version{}
175                 }
176                 return v
177         }
178
179         // Parse prerelease.
180         i := 0
181         for i < len(x) && (x[i] < '0' || '9' < x[i]) {
182                 if x[i] < 'a' || 'z' < x[i] {
183                         return version{}
184                 }
185                 i++
186         }
187         if i == 0 {
188                 return version{}
189         }
190         v.kind, x = x[:i], x[i:]
191         if x == "" {
192                 return v
193         }
194         v.pre, x, ok = cutInt(x)
195         if !ok || x != "" {
196                 return version{}
197         }
198
199         return v
200 }
201
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) {
205         i := 0
206         for i < len(x) && '0' <= x[i] && x[i] <= '9' {
207                 i++
208         }
209         if i == 0 || x[0] == '0' && i != 1 {
210                 return "", "", false
211         }
212         return x[:i], x[i:], true
213 }
214
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 {
218         if x == y {
219                 return 0
220         }
221         if len(x) < len(y) {
222                 return -1
223         }
224         if len(x) > len(y) {
225                 return +1
226         }
227         if x < y {
228                 return -1
229         } else {
230                 return +1
231         }
232 }
233
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)
240         i := len(digits) - 1
241         for ; i >= 0 && digits[i] == '0'; i-- {
242                 digits[i] = '9'
243         }
244         if i < 0 {
245                 // decimal is all zeros
246                 return ""
247         }
248         if i == 0 && digits[i] == '1' && len(digits) > 1 {
249                 digits = digits[1:]
250         } else {
251                 digits[i]--
252         }
253         return string(digits)
254 }