Source File
gover.go
Belonging Package
internal/gover
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package gover implements support for Go toolchain versions like 1.21.0 and 1.21rc1.
// (For historical reasons, Go does not use semver for its toolchains.)
// This package provides the same basic analysis that golang.org/x/mod/semver does for semver.
//
// The go/version package should be imported instead of this one when possible.
// Note that this package works on "1.21" while go/version works on "go1.21".
package gover
import (
)
// A Version is a parsed Go version: major[.Minor[.Patch]][kind[pre]]
// The numbers are the original decimal strings to avoid integer overflows
// and since there is very little actual math. (Probably overflow doesn't matter in practice,
// but at the time this code was written, there was an existing test that used
// go1.99999999999, which does not fit in an int on 32-bit platforms.
// The "big decimal" representation avoids the problem entirely.)
type Version struct {
Major string // decimal
Minor string // decimal or ""
Patch string // decimal or ""
Kind string // "", "alpha", "beta", "rc"
Pre string // decimal or ""
}
// Compare returns -1, 0, or +1 depending on whether
// x < y, x == y, or x > y, interpreted as toolchain versions.
// The versions x and y must not begin with a "go" prefix: just "1.21" not "go1.21".
// Malformed versions compare less than well-formed versions and equal to each other.
// The language version "1.21" compares less than the release candidate and eventual releases "1.21rc1" and "1.21.0".
func (, string) int {
:= Parse()
:= Parse()
if := CmpInt(.Major, .Major); != 0 {
return
}
if := CmpInt(.Minor, .Minor); != 0 {
return
}
if := CmpInt(.Patch, .Patch); != 0 {
return
}
if := cmp.Compare(.Kind, .Kind); != 0 { // "" < alpha < beta < rc
return
}
if := CmpInt(.Pre, .Pre); != 0 {
return
}
return 0
}
// Max returns the maximum of x and y interpreted as toolchain versions,
// compared using Compare.
// If x and y compare equal, Max returns x.
func (, string) string {
if Compare(, ) < 0 {
return
}
return
}
// IsLang reports whether v denotes the overall Go language version
// and not a specific release. Starting with the Go 1.21 release, "1.x" denotes
// the overall language version; the first release is "1.x.0".
// The distinction is important because the relative ordering is
//
// 1.21 < 1.21rc1 < 1.21.0
//
// meaning that Go 1.21rc1 and Go 1.21.0 will both handle go.mod files that
// say "go 1.21", but Go 1.21rc1 will not handle files that say "go 1.21.0".
func ( string) bool {
:= Parse()
return != Version{} && .Patch == "" && .Kind == "" && .Pre == ""
}
// Lang returns the Go language version. For example, Lang("1.2.3") == "1.2".
func ( string) string {
:= Parse()
if .Minor == "" || .Major == "1" && .Minor == "0" {
return .Major
}
return .Major + "." + .Minor
}
// IsValid reports whether the version x is valid.
func ( string) bool {
return Parse() != Version{}
}
// Parse parses the Go version string x into a version.
// It returns the zero version if x is malformed.
func ( string) Version {
var Version
// Parse major version.
var bool
.Major, , = cutInt()
if ! {
return Version{}
}
if == "" {
// Interpret "1" as "1.0.0".
.Minor = "0"
.Patch = "0"
return
}
// Parse . before minor version.
if [0] != '.' {
return Version{}
}
// Parse minor version.
.Minor, , = cutInt([1:])
if ! {
return Version{}
}
if == "" {
// Patch missing is same as "0" for older versions.
// Starting in Go 1.21, patch missing is different from explicit .0.
if CmpInt(.Minor, "21") < 0 {
.Patch = "0"
}
return
}
// Parse patch if present.
if [0] == '.' {
.Patch, , = cutInt([1:])
if ! || != "" {
// Note that we are disallowing prereleases (alpha, beta, rc) for patch releases here (x != "").
// Allowing them would be a bit confusing because we already have:
// 1.21 < 1.21rc1
// But a prerelease of a patch would have the opposite effect:
// 1.21.3rc1 < 1.21.3
// We've never needed them before, so let's not start now.
return Version{}
}
return
}
// Parse prerelease.
:= 0
for < len() && ([] < '0' || '9' < []) {
if [] < 'a' || 'z' < [] {
return Version{}
}
++
}
if == 0 {
return Version{}
}
.Kind, = [:], [:]
if == "" {
return
}
.Pre, , = cutInt()
if ! || != "" {
return Version{}
}
return
}
// cutInt scans the leading decimal number at the start of x to an integer
// and returns that value and the rest of the string.
func cutInt( string) (, string, bool) {
:= 0
for < len() && '0' <= [] && [] <= '9' {
++
}
if == 0 || [0] == '0' && != 1 { // no digits or unnecessary leading zero
return "", "", false
}
return [:], [:], true
}
// CmpInt returns cmp.Compare(x, y) interpreting x and y as decimal numbers.
// (Copied from golang.org/x/mod/semver's compareInt.)
func (, string) int {
if == {
return 0
}
if len() < len() {
return -1
}
if len() > len() {
return +1
}
if < {
return -1
} else {
return +1
}
}
// DecInt returns the decimal string decremented by 1, or the empty string
// if the decimal is all zeroes.
// (Copied from golang.org/x/mod/module's decDecimal.)
func ( string) string {
// Scan right to left turning 0s to 9s until you find a digit to decrement.
:= []byte()
:= len() - 1
for ; >= 0 && [] == '0'; -- {
[] = '9'
}
if < 0 {
// decimal is all zeros
return ""
}
if == 0 && [] == '1' && len() > 1 {
= [1:]
} else {
[]--
}
return string()
}
The pages are generated with Golds v0.7.0-preview. (GOOS=linux GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds. |