Skip to content

Commit

Permalink
goversion: parse new version format and simplify version representation
Browse files Browse the repository at this point in the history
Add parsing for the new version format with toolchain (X.Y.Z-something)
and simplify internal representation of versions so that revision, beta
version and rc version are all represented on a single field with rc
and beta versions being negative numbers (this limits rc versions to a
maximum of 1000 which will not be a problem in practice).
  • Loading branch information
aarzilli committed Aug 16, 2023
1 parent b07ef66 commit 251649c
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 42 deletions.
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

0 comments on commit 251649c

Please sign in to comment.