Skip to content

Commit 54d089d

Browse files
authored
fix: improve runtime version parsing (#4961)
1 parent 98b685c commit 54d089d

File tree

6 files changed

+238
-178
lines changed

6 files changed

+238
-178
lines changed

pkg/config/config.go

-57
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
package config
22

33
import (
4-
"fmt"
5-
"go/version"
64
"os"
7-
"regexp"
8-
"runtime"
95
"strings"
106

117
hcversion "github.com/hashicorp/go-version"
@@ -92,56 +88,3 @@ func detectGoVersion() string {
9288

9389
return "1.17"
9490
}
95-
96-
// Trims the Go version to keep only M.m.
97-
// Since Go 1.21 the version inside the go.mod can be a patched version (ex: 1.21.0).
98-
// The version can also include information which we want to remove (ex: 1.21alpha1)
99-
// https://go.dev/doc/toolchain#versions
100-
// This a problem with staticcheck and gocritic.
101-
func trimGoVersion(v string) string {
102-
if v == "" {
103-
return ""
104-
}
105-
106-
exp := regexp.MustCompile(`(\d\.\d+)(?:\.\d+|[a-z]+\d)`)
107-
108-
if exp.MatchString(v) {
109-
return exp.FindStringSubmatch(v)[1]
110-
}
111-
112-
return v
113-
}
114-
115-
func getRuntimeGoVersion() string {
116-
goVersion := runtime.Version()
117-
118-
parts := strings.Fields(goVersion)
119-
120-
if len(parts) == 0 {
121-
return goVersion
122-
}
123-
124-
// When using GOEXPERIMENT, the version returned might look something like "go1.23.0 X:boringcrypto".
125-
return parts[0]
126-
}
127-
128-
func checkGoVersion(goVersion string) error {
129-
langVersion := version.Lang(getRuntimeGoVersion())
130-
131-
runtimeVersion, err := hcversion.NewVersion(strings.TrimPrefix(langVersion, "go"))
132-
if err != nil {
133-
return err
134-
}
135-
136-
targetedVersion, err := hcversion.NewVersion(trimGoVersion(goVersion))
137-
if err != nil {
138-
return err
139-
}
140-
141-
if runtimeVersion.LessThan(targetedVersion) {
142-
return fmt.Errorf("the Go language version (%s) used to build golangci-lint is lower than the targeted Go version (%s)",
143-
langVersion, goVersion)
144-
}
145-
146-
return nil
147-
}

pkg/config/config_test.go

-102
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"testing"
55

66
"github.com/stretchr/testify/assert"
7-
"github.com/stretchr/testify/require"
87
)
98

109
func TestIsGoGreaterThanOrEqual(t *testing.T) {
@@ -84,104 +83,3 @@ func TestIsGoGreaterThanOrEqual(t *testing.T) {
8483
})
8584
}
8685
}
87-
88-
func Test_trimGoVersion(t *testing.T) {
89-
testCases := []struct {
90-
desc string
91-
version string
92-
expected string
93-
}{
94-
{
95-
desc: "patched version",
96-
version: "1.22.0",
97-
expected: "1.22",
98-
},
99-
{
100-
desc: "minor version",
101-
version: "1.22",
102-
expected: "1.22",
103-
},
104-
{
105-
desc: "RC version",
106-
version: "1.22rc1",
107-
expected: "1.22",
108-
},
109-
{
110-
desc: "alpha version",
111-
version: "1.22alpha1",
112-
expected: "1.22",
113-
},
114-
{
115-
desc: "beta version",
116-
version: "1.22beta1",
117-
expected: "1.22",
118-
},
119-
{
120-
desc: "semver RC version",
121-
version: "1.22.0-rc1",
122-
expected: "1.22",
123-
},
124-
}
125-
126-
for _, test := range testCases {
127-
t.Run(test.desc, func(t *testing.T) {
128-
t.Parallel()
129-
130-
version := trimGoVersion(test.version)
131-
assert.Equal(t, test.expected, version)
132-
})
133-
}
134-
}
135-
136-
func Test_checkGoVersion(t *testing.T) {
137-
testCases := []struct {
138-
desc string
139-
version string
140-
require require.ErrorAssertionFunc
141-
}{
142-
{
143-
desc: "version greater than runtime version (patch)",
144-
version: "1.30.1",
145-
require: require.Error,
146-
},
147-
{
148-
desc: "version greater than runtime version (family)",
149-
version: "1.30",
150-
require: require.Error,
151-
},
152-
{
153-
desc: "version greater than runtime version (RC)",
154-
version: "1.30.0-rc1",
155-
require: require.Error,
156-
},
157-
{
158-
desc: "version equals to runtime version",
159-
version: getRuntimeGoVersion(),
160-
require: require.NoError,
161-
},
162-
{
163-
desc: "version lower than runtime version (patch)",
164-
version: "1.19.1",
165-
require: require.NoError,
166-
},
167-
{
168-
desc: "version lower than runtime version (family)",
169-
version: "1.19",
170-
require: require.NoError,
171-
},
172-
{
173-
desc: "version lower than runtime version (RC)",
174-
version: "1.19.0-rc1",
175-
require: require.NoError,
176-
},
177-
}
178-
179-
for _, test := range testCases {
180-
t.Run(test.desc, func(t *testing.T) {
181-
t.Parallel()
182-
183-
err := checkGoVersion(test.version)
184-
test.require(t, err)
185-
})
186-
}
187-
}

pkg/config/loader.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414

1515
"github.com/golangci/golangci-lint/pkg/exitcodes"
1616
"github.com/golangci/golangci-lint/pkg/fsutils"
17+
"github.com/golangci/golangci-lint/pkg/goutil"
1718
"github.com/golangci/golangci-lint/pkg/logutils"
1819
)
1920

@@ -74,7 +75,7 @@ func (l *Loader) Load(opts LoadOptions) error {
7475

7576
l.handleGoVersion()
7677

77-
err = checkGoVersion(l.cfg.Run.Go)
78+
err = goutil.CheckGoVersion(l.cfg.Run.Go)
7879
if err != nil {
7980
return err
8081
}
@@ -295,7 +296,7 @@ func (l *Loader) handleGoVersion() {
295296
l.cfg.LintersSettings.Gofumpt.LangVersion = l.cfg.Run.Go
296297
}
297298

298-
trimmedGoVersion := trimGoVersion(l.cfg.Run.Go)
299+
trimmedGoVersion := goutil.TrimGoVersion(l.cfg.Run.Go)
299300

300301
l.cfg.LintersSettings.Revive.Go = trimmedGoVersion
301302

pkg/goanalysis/runner_loadingpackage.go

+8-17
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,14 @@ import (
99
"go/types"
1010
"os"
1111
"reflect"
12-
"runtime"
13-
"strings"
1412
"sync"
1513
"sync/atomic"
1614

1715
"golang.org/x/tools/go/gcexportdata"
1816
"golang.org/x/tools/go/packages"
1917

2018
"github.com/golangci/golangci-lint/pkg/goanalysis/load"
19+
"github.com/golangci/golangci-lint/pkg/goutil"
2120
"github.com/golangci/golangci-lint/pkg/logutils"
2221
)
2322

@@ -153,12 +152,18 @@ func (lp *loadingPackage) loadFromSource(loadMode LoadMode) error {
153152
return imp.Types, nil
154153
}
155154

155+
// TODO(ldez) temporary workaround
156+
rv, err := goutil.CleanRuntimeVersion()
157+
if err != nil {
158+
return err
159+
}
160+
156161
tc := &types.Config{
157162
Importer: importerFunc(importer),
158163
Error: func(err error) {
159164
pkg.Errors = append(pkg.Errors, lp.convertError(err)...)
160165
},
161-
GoVersion: getGoVersion(),
166+
GoVersion: rv, // TODO(ldez) temporary workaround
162167
}
163168

164169
_ = 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
502507
panic("unknown rv of type " + rv.String())
503508
}
504509
}
505-
506-
// TODO(ldez) temporary workaround
507-
func getGoVersion() string {
508-
goVersion := runtime.Version()
509-
510-
parts := strings.Fields(goVersion)
511-
512-
if len(parts) == 0 {
513-
return goVersion
514-
}
515-
516-
// When using GOEXPERIMENT, the version returned might look something like "go1.23.0 X:boringcrypto".
517-
return parts[0]
518-
}

pkg/goutil/version.go

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package goutil
2+
3+
import (
4+
"fmt"
5+
"go/version"
6+
"regexp"
7+
"runtime"
8+
"strings"
9+
10+
hcversion "github.com/hashicorp/go-version"
11+
)
12+
13+
func CheckGoVersion(goVersion string) error {
14+
rv, err := CleanRuntimeVersion()
15+
if err != nil {
16+
return fmt.Errorf("clean runtime version: %w", err)
17+
}
18+
19+
langVersion := version.Lang(rv)
20+
21+
runtimeVersion, err := hcversion.NewVersion(strings.TrimPrefix(langVersion, "go"))
22+
if err != nil {
23+
return err
24+
}
25+
26+
targetedVersion, err := hcversion.NewVersion(TrimGoVersion(goVersion))
27+
if err != nil {
28+
return err
29+
}
30+
31+
if runtimeVersion.LessThan(targetedVersion) {
32+
return fmt.Errorf("the Go language version (%s) used to build golangci-lint is lower than the targeted Go version (%s)",
33+
langVersion, goVersion)
34+
}
35+
36+
return nil
37+
}
38+
39+
// TrimGoVersion Trims the Go version to keep only M.m.
40+
// Since Go 1.21 the version inside the go.mod can be a patched version (ex: 1.21.0).
41+
// The version can also include information which we want to remove (ex: 1.21alpha1)
42+
// https://go.dev/doc/toolchain#versions
43+
// This a problem with staticcheck and gocritic.
44+
func TrimGoVersion(v string) string {
45+
if v == "" {
46+
return ""
47+
}
48+
49+
exp := regexp.MustCompile(`(\d\.\d+)(?:\.\d+|[a-z]+\d)`)
50+
51+
if exp.MatchString(v) {
52+
return exp.FindStringSubmatch(v)[1]
53+
}
54+
55+
return v
56+
}
57+
58+
func CleanRuntimeVersion() (string, error) {
59+
return cleanRuntimeVersion(runtime.Version())
60+
}
61+
62+
func cleanRuntimeVersion(rv string) (string, error) {
63+
parts := strings.Fields(rv)
64+
65+
for _, part := range parts {
66+
// Allow to handle:
67+
// - GOEXPERIMENT -> "go1.23.0 X:boringcrypto"
68+
// - devel -> "devel go1.24-e705a2d Wed Aug 7 01:16:42 2024 +0000 linux/amd64"
69+
if strings.HasPrefix(part, "go1.") {
70+
return part, nil
71+
}
72+
}
73+
74+
return "", fmt.Errorf("invalid Go runtime version: %s", rv)
75+
}

0 commit comments

Comments
 (0)