Source file 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  }
   224  

View as plain text