Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

goversion: parse new version format and simplify version representation #3470

Merged
merged 1 commit into from
Aug 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions pkg/goversion/compat.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,19 @@ func Compatible(producer string, warnonly bool) error {
if ver.IsDevel() {
return nil
}
verstr := fmt.Sprintf("%d.%d.%d", ver.Major, ver.Minor, ver.Rev)
if !ver.AfterOrEqual(GoVersion{MinSupportedVersionOfGoMajor, MinSupportedVersionOfGoMinor, -1, 0, 0, ""}) {
if !ver.AfterOrEqual(GoVersion{MinSupportedVersionOfGoMajor, MinSupportedVersionOfGoMinor, betaRev(0), "", ""}) {
if warnonly {
logflags.WriteError(fmt.Sprintf(goTooOldWarn, verstr))
logflags.WriteError(fmt.Sprintf(goTooOldWarn, ver.String()))
return nil
}
return fmt.Errorf(goTooOldErr, verstr)
return fmt.Errorf(goTooOldErr, ver.String())
}
if ver.AfterOrEqual(GoVersion{MaxSupportedVersionOfGoMajor, MaxSupportedVersionOfGoMinor + 1, -1, 0, 0, ""}) {
if ver.AfterOrEqual(GoVersion{MaxSupportedVersionOfGoMajor, MaxSupportedVersionOfGoMinor + 1, betaRev(0), "", ""}) {
if warnonly {
logflags.WriteError(fmt.Sprintf(dlvTooOldWarn, verstr))
logflags.WriteError(fmt.Sprintf(dlvTooOldWarn, ver.String()))
return nil
}
return fmt.Errorf(dlvTooOldErr, verstr)
return fmt.Errorf(dlvTooOldErr, ver.String())
}
return nil
}
88 changes: 65 additions & 23 deletions pkg/goversion/go_version.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package goversion

import (
"fmt"
"os/exec"
"strconv"
"strings"
Expand All @@ -10,16 +11,28 @@ import (
// the Go compiler version used to compile
// the target binary.
type GoVersion struct {
Major int
Minor int
Rev int
Beta int
RC int
Proposal string
Major int
Minor int
Rev int // revision number or negative number for beta and rc releases
Proposal string
Toolchain string
}

const (
betaStart = -1000
betaEnd = -2000
)

func betaRev(beta int) int {
return beta + betaEnd
}

func rcRev(rc int) int {
return rc + betaStart
}

var (
GoVer18Beta = GoVersion{1, 8, -1, 0, 0, ""}
GoVer18Beta = GoVersion{1, 8, betaRev(0), "", ""}
)

// Parse parses a go version string
Expand All @@ -28,7 +41,7 @@ func Parse(ver string) (GoVersion, bool) {
var err1, err2, err3 error

if strings.HasPrefix(ver, "devel") {
return GoVersion{-1, 0, 0, 0, 0, ""}, true
return GoVersion{-1, 0, 0, "", ""}, true
}

if strings.HasPrefix(ver, "go") {
Expand All @@ -40,15 +53,22 @@ func Parse(ver string) (GoVersion, bool) {
var vr []string

if vr = strings.SplitN(v[1], "beta", 2); len(vr) == 2 {
r.Beta, err3 = strconv.Atoi(vr[1])
// old beta releases goX.YbetaZ
var beta int
beta, err3 = strconv.Atoi(vr[1])
r.Rev = betaRev(beta)
} else if vr = strings.SplitN(v[1], "b", 2); len(vr) == 2 {
// old boringcrypto version goX.YbZ
if _, err := strconv.Atoi(vr[1]); err != nil {
return GoVersion{}, false
}
} else {
vr = strings.SplitN(v[1], "rc", 2)
if len(vr) == 2 {
r.RC, err3 = strconv.Atoi(vr[1])
// rc release goX.YrcZ
var rc int
rc, err3 = strconv.Atoi(vr[1])
r.Rev = rcRev(rc)
} else {
r.Minor, err2 = strconv.Atoi(v[1])
if err2 != nil {
Expand All @@ -58,8 +78,9 @@ func Parse(ver string) (GoVersion, bool) {
}
}

// old major release (if none of the options above apply) goX.Y

r.Minor, err2 = strconv.Atoi(vr[0])
r.Rev = -1
r.Proposal = ""

if err1 != nil || err2 != nil || err3 != nil {
Expand All @@ -73,10 +94,15 @@ func Parse(ver string) (GoVersion, bool) {
r.Major, err1 = strconv.Atoi(v[0])
r.Minor, err2 = strconv.Atoi(v[1])

vr := strings.SplitN(v[2], "b", 2)
if len(vr) == 2 {
if vr := strings.SplitN(v[2], "-", 2); len(vr) == 2 {
// minor version with toolchain modifier goX.Y.Z-anything
r.Rev, err3 = strconv.Atoi(vr[0])
r.Toolchain = vr[1]
} else if vr := strings.SplitN(v[2], "b", 2); len(vr) == 2 {
// old boringcrypto version goX.Y.ZbW
r.Rev, err3 = strconv.Atoi(vr[0])
} else {
// minor version goX.Y.Z
r.Rev, err3 = strconv.Atoi(v[2])
}

Expand All @@ -89,6 +115,8 @@ func Parse(ver string) (GoVersion, bool) {

case 4:

// old proposal release goX.Y.Z.anything

r.Major, err1 = strconv.Atoi(v[0])
r.Minor, err2 = strconv.Atoi(v[1])
r.Rev, err3 = strconv.Atoi(v[2])
Expand Down Expand Up @@ -128,14 +156,6 @@ func (v *GoVersion) AfterOrEqual(b GoVersion) bool {
return true
}

if v.Beta < b.Beta {
return false
}

if v.RC < b.RC {
return false
}

return true
}

Expand All @@ -145,6 +165,28 @@ func (v *GoVersion) IsDevel() bool {
return v.Major < 0
}

func (v *GoVersion) String() string {
switch {
case v.Rev < betaStart:
// beta version
return fmt.Sprintf("go%d.%dbeta%d", v.Major, v.Minor, v.Rev-betaEnd)
case v.Rev < 0:
// rc version
return fmt.Sprintf("go%d.%drc%d", v.Major, v.Minor, v.Rev-betaStart)
case v.Proposal != "":
// with proposal
return fmt.Sprintf("go%d.%d.%d.%s", v.Major, v.Minor, v.Rev, v.Proposal)
case v.Rev == 0 && v.Minor < 21:
// old major version
return fmt.Sprintf("go%d.%d", v.Major, v.Minor)
case v.Toolchain != "":
return fmt.Sprintf("go%d.%d.%d-%s", v.Major, v.Minor, v.Rev, v.Toolchain)
default:
// post go1.21 major version or minor version
return fmt.Sprintf("go%d.%d.%d", v.Major, v.Minor, v.Rev)
}
}

const goVersionPrefix = "go version "

// Installed runs "go version" and parses the output
Expand Down Expand Up @@ -178,7 +220,7 @@ func VersionAfterOrEqualRev(version string, major, minor, rev int) bool {
if ver.IsDevel() {
return true
}
return ver.AfterOrEqual(GoVersion{major, minor, rev, 0, 0, ""})
return ver.AfterOrEqual(GoVersion{major, minor, rev, "", ""})
}

const producerVersionPrefix = "Go cmd/compile "
Expand All @@ -190,7 +232,7 @@ func ProducerAfterOrEqual(producer string, major, minor int) bool {
if ver.IsDevel() {
return true
}
return ver.AfterOrEqual(GoVersion{major, minor, -1, 0, 0, ""})
return ver.AfterOrEqual(GoVersion{major, minor, 0, "", ""})
}

func ParseProducer(producer string) GoVersion {
Expand Down
83 changes: 72 additions & 11 deletions pkg/goversion/version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,95 @@ import (
"testing"
)

func versionAfterOrEqual(t *testing.T, verStr string, ver GoVersion) {
func parseVer(t *testing.T, verStr string) GoVersion {
pver, ok := Parse(verStr)
if !ok {
t.Fatalf("Could not parse version string <%s>", verStr)
}
return pver
}

func versionAfterOrEqual(t *testing.T, verStr string, ver GoVersion) {
t.Helper()
pver := parseVer(t, verStr)
if !pver.AfterOrEqual(ver) {
t.Fatalf("Version <%s> parsed as %v not after %v", verStr, pver, ver)
}
t.Logf("version string <%s> → %v", verStr, ver)
}

func TestParseVersionString(t *testing.T) {
versionAfterOrEqual(t, "go1.4", GoVersion{1, 4, 0, 0, 0, ""})
versionAfterOrEqual(t, "go1.5.0", GoVersion{1, 5, 0, 0, 0, ""})
versionAfterOrEqual(t, "go1.4.2", GoVersion{1, 4, 2, 0, 0, ""})
versionAfterOrEqual(t, "go1.5beta2", GoVersion{1, 5, -1, 2, 0, ""})
versionAfterOrEqual(t, "go1.5rc2", GoVersion{1, 5, -1, 0, 2, ""})
versionAfterOrEqual(t, "go1.6.1 (appengine-1.9.37)", GoVersion{1, 6, 1, 0, 0, ""})
versionAfterOrEqual(t, "go1.8.1.typealias", GoVersion{1, 6, 1, 0, 0, ""})
versionAfterOrEqual(t, "go1.8b1", GoVersion{1, 8, -1, 0, 0, ""})
versionAfterOrEqual(t, "go1.16.4b7", GoVersion{1, 16, 4, 0, 0, ""})
func versionAfterOrEqual2(t *testing.T, verStr1, verStr2 string) {
t.Helper()
pver1 := parseVer(t, verStr1)
pver2 := parseVer(t, verStr2)
if !pver1.AfterOrEqual(pver2) {
t.Fatalf("Version <%s> %#v not after or equal to <%s> %#v", verStr1, pver1, verStr2, pver2)
}
}

func versionEqual(t *testing.T, verStr string, ver GoVersion) {
t.Helper()
pver := parseVer(t, verStr)
if pver != ver {
t.Fatalf("Version <%s> parsed as %v not equal to %v", verStr, pver, ver)
}
t.Logf("version string <%s> → %v", verStr, ver)
}

func TestParseVersionStringAfterOrEqual(t *testing.T) {
versionAfterOrEqual(t, "go1.4", GoVersion{1, 4, 0, "", ""})
versionAfterOrEqual(t, "go1.5.0", GoVersion{1, 5, 0, "", ""})
versionAfterOrEqual(t, "go1.4.2", GoVersion{1, 4, 2, "", ""})
versionAfterOrEqual(t, "go1.5beta2", GoVersion{1, 5, betaRev(2), "", ""})
versionAfterOrEqual(t, "go1.5rc2", GoVersion{1, 5, rcRev(2), "", ""})
versionAfterOrEqual(t, "go1.6.1 (appengine-1.9.37)", GoVersion{1, 6, 1, "", ""})
versionAfterOrEqual(t, "go1.8.1.typealias", GoVersion{1, 6, 1, "", ""})
versionAfterOrEqual(t, "go1.8b1", GoVersion{1, 8, 0, "", ""})
versionAfterOrEqual(t, "go1.16.4b7", GoVersion{1, 16, 4, "", ""})
ver, ok := Parse("devel +17efbfc Tue Jul 28 17:39:19 2015 +0000 linux/amd64")
if !ok {
t.Fatalf("Could not parse devel version string")
}
if !ver.IsDevel() {
t.Fatalf("Devel version string not correctly recognized")
}

versionAfterOrEqual2(t, "go1.16", "go1.16b1")
versionAfterOrEqual2(t, "go1.16", "go1.16rc1")
versionAfterOrEqual2(t, "go1.16rc1", "go1.16beta1")
versionAfterOrEqual2(t, "go1.16beta2", "go1.16beta1")
versionAfterOrEqual2(t, "go1.16rc10", "go1.16rc8")
}

func TestParseVersionStringEqual(t *testing.T) {
versionEqual(t, "go1.4", GoVersion{1, 4, 0, "", ""})
versionEqual(t, "go1.5.0", GoVersion{1, 5, 0, "", ""})
versionEqual(t, "go1.4.2", GoVersion{1, 4, 2, "", ""})
versionEqual(t, "go1.5beta2", GoVersion{1, 5, betaRev(2), "", ""})
versionEqual(t, "go1.5rc2", GoVersion{1, 5, rcRev(2), "", ""})
versionEqual(t, "go1.6.1 (appengine-1.9.37)", GoVersion{1, 6, 1, "", ""})
versionEqual(t, "go1.8.1.typealias", GoVersion{1, 8, 1, "typealias", ""})
versionEqual(t, "go1.8b1", GoVersion{1, 8, 0, "", ""})
versionEqual(t, "go1.16.4b7", GoVersion{1, 16, 4, "", ""})
versionEqual(t, "go1.21.1-something", GoVersion{1, 21, 1, "", "something"})
versionEqual(t, "devel +17efbfc Tue Jul 28 17:39:19 2015 +0000 linux/amd64", GoVersion{Major: -1})
}

func TestRoundtrip(t *testing.T) {
for _, verStr := range []string{
"go1.4",
"go1.4.2",
"go1.5beta2",
"go1.5rc2",
"go1.8.1.typealias",
"go1.21.1-something",
"go1.21.0",
} {
pver := parseVer(t, verStr)
if pver.String() != verStr {
t.Fatalf("roundtrip mismatch <%s> -> %#v -> <%s>", verStr, pver, pver.String())
}
}
}

func TestInstalled(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion pkg/proc/scope_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
)

func TestScopeWithEscapedVariable(t *testing.T) {
if ver, _ := goversion.Parse(runtime.Version()); ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 9, Rev: -1, Beta: 3}) {
if ver, _ := goversion.Parse(runtime.Version()); ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 9}) {
return
}

Expand Down