fix: improve runtime version parsing (#4961)
This commit is contained in:
parent
98b685cc0d
commit
54d089d106
@ -1,11 +1,7 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"go/version"
|
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
hcversion "github.com/hashicorp/go-version"
|
hcversion "github.com/hashicorp/go-version"
|
||||||
@ -92,56 +88,3 @@ func detectGoVersion() string {
|
|||||||
|
|
||||||
return "1.17"
|
return "1.17"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trims the Go version to keep only M.m.
|
|
||||||
// Since Go 1.21 the version inside the go.mod can be a patched version (ex: 1.21.0).
|
|
||||||
// The version can also include information which we want to remove (ex: 1.21alpha1)
|
|
||||||
// https://go.dev/doc/toolchain#versions
|
|
||||||
// This a problem with staticcheck and gocritic.
|
|
||||||
func trimGoVersion(v string) string {
|
|
||||||
if v == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
exp := regexp.MustCompile(`(\d\.\d+)(?:\.\d+|[a-z]+\d)`)
|
|
||||||
|
|
||||||
if exp.MatchString(v) {
|
|
||||||
return exp.FindStringSubmatch(v)[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRuntimeGoVersion() string {
|
|
||||||
goVersion := runtime.Version()
|
|
||||||
|
|
||||||
parts := strings.Fields(goVersion)
|
|
||||||
|
|
||||||
if len(parts) == 0 {
|
|
||||||
return goVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
// When using GOEXPERIMENT, the version returned might look something like "go1.23.0 X:boringcrypto".
|
|
||||||
return parts[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkGoVersion(goVersion string) error {
|
|
||||||
langVersion := version.Lang(getRuntimeGoVersion())
|
|
||||||
|
|
||||||
runtimeVersion, err := hcversion.NewVersion(strings.TrimPrefix(langVersion, "go"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
targetedVersion, err := hcversion.NewVersion(trimGoVersion(goVersion))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if runtimeVersion.LessThan(targetedVersion) {
|
|
||||||
return fmt.Errorf("the Go language version (%s) used to build golangci-lint is lower than the targeted Go version (%s)",
|
|
||||||
langVersion, goVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@ -4,7 +4,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestIsGoGreaterThanOrEqual(t *testing.T) {
|
func TestIsGoGreaterThanOrEqual(t *testing.T) {
|
||||||
@ -84,104 +83,3 @@ func TestIsGoGreaterThanOrEqual(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_trimGoVersion(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
desc string
|
|
||||||
version string
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
desc: "patched version",
|
|
||||||
version: "1.22.0",
|
|
||||||
expected: "1.22",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "minor version",
|
|
||||||
version: "1.22",
|
|
||||||
expected: "1.22",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "RC version",
|
|
||||||
version: "1.22rc1",
|
|
||||||
expected: "1.22",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "alpha version",
|
|
||||||
version: "1.22alpha1",
|
|
||||||
expected: "1.22",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "beta version",
|
|
||||||
version: "1.22beta1",
|
|
||||||
expected: "1.22",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "semver RC version",
|
|
||||||
version: "1.22.0-rc1",
|
|
||||||
expected: "1.22",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range testCases {
|
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
version := trimGoVersion(test.version)
|
|
||||||
assert.Equal(t, test.expected, version)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_checkGoVersion(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
desc string
|
|
||||||
version string
|
|
||||||
require require.ErrorAssertionFunc
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
desc: "version greater than runtime version (patch)",
|
|
||||||
version: "1.30.1",
|
|
||||||
require: require.Error,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "version greater than runtime version (family)",
|
|
||||||
version: "1.30",
|
|
||||||
require: require.Error,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "version greater than runtime version (RC)",
|
|
||||||
version: "1.30.0-rc1",
|
|
||||||
require: require.Error,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "version equals to runtime version",
|
|
||||||
version: getRuntimeGoVersion(),
|
|
||||||
require: require.NoError,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "version lower than runtime version (patch)",
|
|
||||||
version: "1.19.1",
|
|
||||||
require: require.NoError,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "version lower than runtime version (family)",
|
|
||||||
version: "1.19",
|
|
||||||
require: require.NoError,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "version lower than runtime version (RC)",
|
|
||||||
version: "1.19.0-rc1",
|
|
||||||
require: require.NoError,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range testCases {
|
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
err := checkGoVersion(test.version)
|
|
||||||
test.require(t, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
|
|
||||||
"github.com/golangci/golangci-lint/pkg/exitcodes"
|
"github.com/golangci/golangci-lint/pkg/exitcodes"
|
||||||
"github.com/golangci/golangci-lint/pkg/fsutils"
|
"github.com/golangci/golangci-lint/pkg/fsutils"
|
||||||
|
"github.com/golangci/golangci-lint/pkg/goutil"
|
||||||
"github.com/golangci/golangci-lint/pkg/logutils"
|
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -74,7 +75,7 @@ func (l *Loader) Load(opts LoadOptions) error {
|
|||||||
|
|
||||||
l.handleGoVersion()
|
l.handleGoVersion()
|
||||||
|
|
||||||
err = checkGoVersion(l.cfg.Run.Go)
|
err = goutil.CheckGoVersion(l.cfg.Run.Go)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -295,7 +296,7 @@ func (l *Loader) handleGoVersion() {
|
|||||||
l.cfg.LintersSettings.Gofumpt.LangVersion = l.cfg.Run.Go
|
l.cfg.LintersSettings.Gofumpt.LangVersion = l.cfg.Run.Go
|
||||||
}
|
}
|
||||||
|
|
||||||
trimmedGoVersion := trimGoVersion(l.cfg.Run.Go)
|
trimmedGoVersion := goutil.TrimGoVersion(l.cfg.Run.Go)
|
||||||
|
|
||||||
l.cfg.LintersSettings.Revive.Go = trimmedGoVersion
|
l.cfg.LintersSettings.Revive.Go = trimmedGoVersion
|
||||||
|
|
||||||
|
@ -9,8 +9,6 @@ import (
|
|||||||
"go/types"
|
"go/types"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
@ -18,6 +16,7 @@ import (
|
|||||||
"golang.org/x/tools/go/packages"
|
"golang.org/x/tools/go/packages"
|
||||||
|
|
||||||
"github.com/golangci/golangci-lint/pkg/goanalysis/load"
|
"github.com/golangci/golangci-lint/pkg/goanalysis/load"
|
||||||
|
"github.com/golangci/golangci-lint/pkg/goutil"
|
||||||
"github.com/golangci/golangci-lint/pkg/logutils"
|
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -153,12 +152,18 @@ func (lp *loadingPackage) loadFromSource(loadMode LoadMode) error {
|
|||||||
return imp.Types, nil
|
return imp.Types, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(ldez) temporary workaround
|
||||||
|
rv, err := goutil.CleanRuntimeVersion()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
tc := &types.Config{
|
tc := &types.Config{
|
||||||
Importer: importerFunc(importer),
|
Importer: importerFunc(importer),
|
||||||
Error: func(err error) {
|
Error: func(err error) {
|
||||||
pkg.Errors = append(pkg.Errors, lp.convertError(err)...)
|
pkg.Errors = append(pkg.Errors, lp.convertError(err)...)
|
||||||
},
|
},
|
||||||
GoVersion: getGoVersion(),
|
GoVersion: rv, // TODO(ldez) temporary workaround
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = types.NewChecker(tc, pkg.Fset, pkg.Types, pkg.TypesInfo).Files(pkg.Syntax)
|
_ = types.NewChecker(tc, pkg.Fset, pkg.Types, pkg.TypesInfo).Files(pkg.Syntax)
|
||||||
@ -502,17 +507,3 @@ func sizeOfReflectValueTreeBytes(rv reflect.Value, visitedPtrs map[uintptr]struc
|
|||||||
panic("unknown rv of type " + rv.String())
|
panic("unknown rv of type " + rv.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(ldez) temporary workaround
|
|
||||||
func getGoVersion() string {
|
|
||||||
goVersion := runtime.Version()
|
|
||||||
|
|
||||||
parts := strings.Fields(goVersion)
|
|
||||||
|
|
||||||
if len(parts) == 0 {
|
|
||||||
return goVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
// When using GOEXPERIMENT, the version returned might look something like "go1.23.0 X:boringcrypto".
|
|
||||||
return parts[0]
|
|
||||||
}
|
|
||||||
|
75
pkg/goutil/version.go
Normal file
75
pkg/goutil/version.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package goutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/version"
|
||||||
|
"regexp"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
hcversion "github.com/hashicorp/go-version"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CheckGoVersion(goVersion string) error {
|
||||||
|
rv, err := CleanRuntimeVersion()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("clean runtime version: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
langVersion := version.Lang(rv)
|
||||||
|
|
||||||
|
runtimeVersion, err := hcversion.NewVersion(strings.TrimPrefix(langVersion, "go"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
targetedVersion, err := hcversion.NewVersion(TrimGoVersion(goVersion))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if runtimeVersion.LessThan(targetedVersion) {
|
||||||
|
return fmt.Errorf("the Go language version (%s) used to build golangci-lint is lower than the targeted Go version (%s)",
|
||||||
|
langVersion, goVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrimGoVersion Trims the Go version to keep only M.m.
|
||||||
|
// Since Go 1.21 the version inside the go.mod can be a patched version (ex: 1.21.0).
|
||||||
|
// The version can also include information which we want to remove (ex: 1.21alpha1)
|
||||||
|
// https://go.dev/doc/toolchain#versions
|
||||||
|
// This a problem with staticcheck and gocritic.
|
||||||
|
func TrimGoVersion(v string) string {
|
||||||
|
if v == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
exp := regexp.MustCompile(`(\d\.\d+)(?:\.\d+|[a-z]+\d)`)
|
||||||
|
|
||||||
|
if exp.MatchString(v) {
|
||||||
|
return exp.FindStringSubmatch(v)[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func CleanRuntimeVersion() (string, error) {
|
||||||
|
return cleanRuntimeVersion(runtime.Version())
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanRuntimeVersion(rv string) (string, error) {
|
||||||
|
parts := strings.Fields(rv)
|
||||||
|
|
||||||
|
for _, part := range parts {
|
||||||
|
// Allow to handle:
|
||||||
|
// - GOEXPERIMENT -> "go1.23.0 X:boringcrypto"
|
||||||
|
// - devel -> "devel go1.24-e705a2d Wed Aug 7 01:16:42 2024 +0000 linux/amd64"
|
||||||
|
if strings.HasPrefix(part, "go1.") {
|
||||||
|
return part, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("invalid Go runtime version: %s", rv)
|
||||||
|
}
|
152
pkg/goutil/version_test.go
Normal file
152
pkg/goutil/version_test.go
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
package goutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCheckGoVersion(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
version string
|
||||||
|
require require.ErrorAssertionFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "version greater than runtime version (patch)",
|
||||||
|
version: "1.30.1",
|
||||||
|
require: require.Error,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "version greater than runtime version (family)",
|
||||||
|
version: "1.30",
|
||||||
|
require: require.Error,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "version greater than runtime version (RC)",
|
||||||
|
version: "1.30.0-rc1",
|
||||||
|
require: require.Error,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "version equals to runtime version",
|
||||||
|
version: func() string {
|
||||||
|
rv, _ := CleanRuntimeVersion()
|
||||||
|
return rv
|
||||||
|
}(),
|
||||||
|
require: require.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "version lower than runtime version (patch)",
|
||||||
|
version: "1.19.1",
|
||||||
|
require: require.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "version lower than runtime version (family)",
|
||||||
|
version: "1.19",
|
||||||
|
require: require.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "version lower than runtime version (RC)",
|
||||||
|
version: "1.19.0-rc1",
|
||||||
|
require: require.NoError,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
err := CheckGoVersion(test.version)
|
||||||
|
test.require(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrimGoVersion(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
version string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "patched version",
|
||||||
|
version: "1.22.0",
|
||||||
|
expected: "1.22",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "minor version",
|
||||||
|
version: "1.22",
|
||||||
|
expected: "1.22",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "RC version",
|
||||||
|
version: "1.22rc1",
|
||||||
|
expected: "1.22",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "alpha version",
|
||||||
|
version: "1.22alpha1",
|
||||||
|
expected: "1.22",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "beta version",
|
||||||
|
version: "1.22beta1",
|
||||||
|
expected: "1.22",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "semver RC version",
|
||||||
|
version: "1.22.0-rc1",
|
||||||
|
expected: "1.22",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
version := TrimGoVersion(test.version)
|
||||||
|
assert.Equal(t, test.expected, version)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_cleanRuntimeVersion(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
version string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "go version",
|
||||||
|
version: "go1.22.0",
|
||||||
|
expected: "go1.22.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "language version",
|
||||||
|
version: "go1.22",
|
||||||
|
expected: "go1.22",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "GOEXPERIMENT",
|
||||||
|
version: "go1.23.0 X:boringcrypto",
|
||||||
|
expected: "go1.23.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "devel",
|
||||||
|
version: "devel go1.24-e705a2d Wed Aug 7 01:16:42 2024 +0000 linux/amd64",
|
||||||
|
expected: "go1.24-e705a2d",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
v, err := cleanRuntimeVersion(test.version)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, test.expected, v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user