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