From 251649c5f7b3e3f8544b1cc45cc597ae3f2a8962 Mon Sep 17 00:00:00 2001 From: aarzilli Date: Wed, 16 Aug 2023 14:29:03 +0200 Subject: [PATCH] goversion: parse new version format and simplify version representation 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). --- pkg/goversion/compat.go | 13 +++--- pkg/goversion/go_version.go | 88 ++++++++++++++++++++++++++--------- pkg/goversion/version_test.go | 83 ++++++++++++++++++++++++++++----- pkg/proc/scope_test.go | 2 +- 4 files changed, 144 insertions(+), 42 deletions(-) diff --git a/pkg/goversion/compat.go b/pkg/goversion/compat.go index fa2c3392e2..029f4dbb9a 100644 --- a/pkg/goversion/compat.go +++ b/pkg/goversion/compat.go @@ -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 } diff --git a/pkg/goversion/go_version.go b/pkg/goversion/go_version.go index b980ffac74..08b94afb2b 100644 --- a/pkg/goversion/go_version.go +++ b/pkg/goversion/go_version.go @@ -1,6 +1,7 @@ package goversion import ( + "fmt" "os/exec" "strconv" "strings" @@ -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 @@ -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") { @@ -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 { @@ -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 { @@ -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]) } @@ -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]) @@ -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 } @@ -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 @@ -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 " @@ -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 { diff --git a/pkg/goversion/version_test.go b/pkg/goversion/version_test.go index dd67814141..6bf6f22e53 100644 --- a/pkg/goversion/version_test.go +++ b/pkg/goversion/version_test.go @@ -5,27 +5,51 @@ 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") @@ -33,6 +57,43 @@ func TestParseVersionString(t *testing.T) { 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) { diff --git a/pkg/proc/scope_test.go b/pkg/proc/scope_test.go index 8ca4665f05..daca5ad4dc 100644 --- a/pkg/proc/scope_test.go +++ b/pkg/proc/scope_test.go @@ -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 }