Skip to content

Commit ea3a3db

Browse files
authored
Merge pull request #16460 from github/mbg/go/semver-type
Go: Use new type for all semantic versions
2 parents 0ab67d1 + 9d1c2c6 commit ea3a3db

File tree

13 files changed

+386
-180
lines changed

13 files changed

+386
-180
lines changed

go/extractor/autobuilder/BUILD.bazel

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

go/extractor/autobuilder/build-environment.go

Lines changed: 79 additions & 83 deletions
Large diffs are not rendered by default.

go/extractor/autobuilder/build-environment_test.go

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,55 @@
11
package autobuilder
22

3-
import "testing"
3+
import (
4+
"testing"
5+
6+
"github.com/github/codeql-go/extractor/util"
7+
)
48

59
func TestGetVersionToInstall(t *testing.T) {
6-
tests := map[versionInfo]string{
10+
type inputVersions struct {
11+
modVersion string
12+
envVersion string
13+
}
14+
tests := map[inputVersions]string{
715
// getVersionWhenGoModVersionNotFound()
8-
{"", false, "", false}: maxGoVersion,
9-
{"", false, "1.2.2", true}: maxGoVersion,
10-
{"", false, "9999.0.1", true}: maxGoVersion,
11-
{"", false, "1.11.13", true}: "",
12-
{"", false, "1.20.3", true}: "",
16+
{"", ""}: maxGoVersion.String(),
17+
{"", "1.2.2"}: maxGoVersion.String(),
18+
{"", "9999.0.1"}: maxGoVersion.String(),
19+
{"", "1.11.13"}: "",
20+
{"", "1.20.3"}: "",
1321

1422
// getVersionWhenGoModVersionTooHigh()
15-
{"9999.0", true, "", false}: maxGoVersion,
16-
{"9999.0", true, "9999.0.1", true}: "",
17-
{"9999.0", true, "1.1", true}: maxGoVersion,
18-
{"9999.0", true, minGoVersion, false}: maxGoVersion,
19-
{"9999.0", true, maxGoVersion, true}: "",
23+
{"9999.0", ""}: maxGoVersion.String(),
24+
{"9999.0", "9999.0.1"}: "",
25+
{"9999.0", "1.1"}: maxGoVersion.String(),
26+
{"9999.0", minGoVersion.String()}: maxGoVersion.String(),
27+
{"9999.0", maxGoVersion.String()}: "",
2028

2129
// getVersionWhenGoModVersionTooLow()
22-
{"0.0", true, "", false}: minGoVersion,
23-
{"0.0", true, "9999.0", true}: minGoVersion,
24-
{"0.0", true, "1.2.2", true}: minGoVersion,
25-
{"0.0", true, "1.20.3", true}: "",
30+
{"0.0", ""}: minGoVersion.String(),
31+
{"0.0", "9999.0"}: minGoVersion.String(),
32+
{"0.0", "1.2.2"}: minGoVersion.String(),
33+
{"0.0", "1.20.3"}: "",
2634

2735
// getVersionWhenGoModVersionSupported()
28-
{"1.20", true, "", false}: "1.20",
29-
{"1.11", true, "", false}: "1.11",
30-
{"1.20", true, "1.2.2", true}: "1.20",
31-
{"1.11", true, "1.2.2", true}: "1.11",
32-
{"1.20", true, "9999.0.1", true}: "1.20",
33-
{"1.11", true, "9999.0.1", true}: "1.11",
36+
{"1.20", ""}: "1.20",
37+
{"1.11", ""}: "1.11",
38+
{"1.20", "1.2.2"}: "1.20",
39+
{"1.11", "1.2.2"}: "1.11",
40+
{"1.20", "9999.0.1"}: "1.20",
41+
{"1.11", "9999.0.1"}: "1.11",
3442
// go.mod version > go installation version
35-
{"1.20", true, "1.11.13", true}: "1.20",
36-
{"1.20", true, "1.12", true}: "1.20",
43+
{"1.20", "1.11.13"}: "1.20",
44+
{"1.20", "1.12"}: "1.20",
3745
// go.mod version <= go installation version (Note comparisons ignore the patch version)
38-
{"1.11", true, "1.20", true}: "",
39-
{"1.11", true, "1.20.3", true}: "",
40-
{"1.20", true, "1.20.3", true}: "",
46+
{"1.11", "1.20"}: "",
47+
{"1.11", "1.20.3"}: "",
48+
{"1.20", "1.20.3"}: "",
4149
}
4250
for input, expected := range tests {
43-
_, actual := getVersionToInstall(input)
44-
if actual != expected {
51+
_, actual := getVersionToInstall(versionInfo{util.NewSemVer(input.modVersion), util.NewSemVer(input.envVersion)})
52+
if actual != util.NewSemVer(expected) {
4553
t.Errorf("Expected getVersionToInstall(\"%s\") to be \"%s\", but got \"%s\".", input, expected, actual)
4654
}
4755
}

go/extractor/cli/go-autobuilder/BUILD.bazel

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

go/extractor/cli/go-autobuilder/go-autobuilder.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ import (
1010
"runtime"
1111
"strings"
1212

13-
"golang.org/x/mod/semver"
14-
1513
"github.com/github/codeql-go/extractor/autobuilder"
1614
"github.com/github/codeql-go/extractor/diagnostics"
1715
"github.com/github/codeql-go/extractor/project"
@@ -156,7 +154,7 @@ func getNeedGopath(workspace project.GoWorkspace, importpath string) bool {
156154
// Try to update `go.mod` and `go.sum` if the go version is >= 1.16.
157155
func tryUpdateGoModAndGoSum(workspace project.GoWorkspace) {
158156
// Go 1.16 and later won't automatically attempt to update go.mod / go.sum during package loading, so try to update them here:
159-
if workspace.ModMode != project.ModVendor && workspace.DepMode == project.GoGetWithModules && semver.Compare(toolchain.GetEnvGoSemVer(), "v1.16") >= 0 {
157+
if workspace.ModMode != project.ModVendor && workspace.DepMode == project.GoGetWithModules && toolchain.GetEnvGoSemVer().IsAtLeast(toolchain.V1_16) {
160158
for _, goMod := range workspace.Modules {
161159
// stat go.mod and go.sum
162160
goModPath := goMod.Path
@@ -542,12 +540,12 @@ func installDependenciesAndBuild() {
542540

543541
// This diagnostic is not required if the system Go version is 1.21 or greater, since the
544542
// Go tooling should install required Go versions as needed.
545-
if semver.Compare(toolchain.GetEnvGoSemVer(), "v1.21.0") < 0 && greatestGoVersion.Found && semver.Compare("v"+greatestGoVersion.Version, toolchain.GetEnvGoSemVer()) > 0 {
546-
diagnostics.EmitNewerGoVersionNeeded(toolchain.GetEnvGoSemVer(), "v"+greatestGoVersion.Version)
543+
if toolchain.GetEnvGoSemVer().IsOlderThan(toolchain.V1_21) && greatestGoVersion != nil && greatestGoVersion.IsNewerThan(toolchain.GetEnvGoSemVer()) {
544+
diagnostics.EmitNewerGoVersionNeeded(toolchain.GetEnvGoSemVer().String(), greatestGoVersion.String())
547545
if val, _ := os.LookupEnv("GITHUB_ACTIONS"); val == "true" {
548546
log.Printf(
549547
"A go.mod file requires version %s of Go, but version %s is installed. Consider adding an actions/setup-go step to your workflow.\n",
550-
"v"+greatestGoVersion.Version,
548+
greatestGoVersion,
551549
toolchain.GetEnvGoSemVer())
552550
}
553551
}
@@ -559,7 +557,7 @@ func installDependenciesAndBuild() {
559557
for i, workspace := range workspaces {
560558
goVersionInfo := workspace.RequiredGoVersion()
561559

562-
fixGoVendorIssues(&workspace, goVersionInfo.Found)
560+
fixGoVendorIssues(&workspace, goVersionInfo != nil)
563561

564562
tryUpdateGoModAndGoSum(workspace)
565563

go/extractor/project/BUILD.bazel

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

go/extractor/project/project.go

Lines changed: 20 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
"github.com/github/codeql-go/extractor/toolchain"
1414
"github.com/github/codeql-go/extractor/util"
1515
"golang.org/x/mod/modfile"
16-
"golang.org/x/mod/semver"
1716
)
1817

1918
// DependencyInstallerMode is an enum describing how dependencies should be installed
@@ -49,53 +48,47 @@ type GoWorkspace struct {
4948
}
5049

5150
// Represents a nullable version string.
52-
type GoVersionInfo struct {
53-
// The version string, if any
54-
Version string
55-
// A value indicating whether a version string was found
56-
Found bool
57-
}
51+
type GoVersionInfo = util.SemVer
5852

5953
// Determines the version of Go that is required by this workspace. This is, in order of preference:
6054
// 1. The Go version specified in the `go.work` file, if any.
6155
// 2. The greatest Go version specified in any `go.mod` file, if any.
62-
func (workspace *GoWorkspace) RequiredGoVersion() GoVersionInfo {
56+
func (workspace *GoWorkspace) RequiredGoVersion() util.SemVer {
6357
if workspace.WorkspaceFile != nil && workspace.WorkspaceFile.Go != nil {
6458
// If we have parsed a `go.work` file, return the version number from it.
65-
return GoVersionInfo{Version: workspace.WorkspaceFile.Go.Version, Found: true}
59+
return util.NewSemVer(workspace.WorkspaceFile.Go.Version)
6660
} else if workspace.Modules != nil && len(workspace.Modules) > 0 {
6761
// Otherwise, if we have `go.work` files, find the greatest Go version in those.
68-
var greatestVersion string = ""
62+
var greatestVersion util.SemVer = nil
6963
for _, module := range workspace.Modules {
7064
if module.Module != nil && module.Module.Go != nil {
7165
// If we have parsed the file, retrieve the version number we have already obtained.
72-
if greatestVersion == "" || semver.Compare("v"+module.Module.Go.Version, "v"+greatestVersion) > 0 {
73-
greatestVersion = module.Module.Go.Version
66+
modVersion := util.NewSemVer(module.Module.Go.Version)
67+
if greatestVersion == nil || modVersion.IsNewerThan(greatestVersion) {
68+
greatestVersion = modVersion
7469
}
7570
} else {
7671
modVersion := tryReadGoDirective(module.Path)
77-
if modVersion.Found && (greatestVersion == "" || semver.Compare("v"+modVersion.Version, "v"+greatestVersion) > 0) {
78-
greatestVersion = modVersion.Version
72+
if modVersion != nil && (greatestVersion == nil || modVersion.IsNewerThan(greatestVersion)) {
73+
greatestVersion = modVersion
7974
}
8075
}
8176
}
8277

8378
// If we have found some version, return it.
84-
if greatestVersion != "" {
85-
return GoVersionInfo{Version: greatestVersion, Found: true}
86-
}
79+
return greatestVersion
8780
}
8881

89-
return GoVersionInfo{Version: "", Found: false}
82+
return nil
9083
}
9184

9285
// Finds the greatest Go version required by any of the given `workspaces`.
9386
// Returns a `GoVersionInfo` value with `Found: false` if no version information is available.
94-
func RequiredGoVersion(workspaces *[]GoWorkspace) GoVersionInfo {
95-
greatestGoVersion := GoVersionInfo{Version: "", Found: false}
87+
func RequiredGoVersion(workspaces *[]GoWorkspace) util.SemVer {
88+
var greatestGoVersion util.SemVer = nil
9689
for _, workspace := range *workspaces {
9790
goVersionInfo := workspace.RequiredGoVersion()
98-
if goVersionInfo.Found && (!greatestGoVersion.Found || semver.Compare("v"+goVersionInfo.Version, "v"+greatestGoVersion.Version) > 0) {
91+
if goVersionInfo != nil && (greatestGoVersion == nil || goVersionInfo.IsNewerThan(greatestGoVersion)) {
9992
greatestGoVersion = goVersionInfo
10093
}
10194
}
@@ -183,7 +176,7 @@ var toolchainVersionRe *regexp.Regexp = regexp.MustCompile(`(?m)^([0-9]+\.[0-9]+
183176
// there is no `toolchain` directive, and the Go language version is not a valid toolchain version.
184177
func hasInvalidToolchainVersion(modFile *modfile.File) bool {
185178
return modFile.Toolchain == nil && modFile.Go != nil &&
186-
!toolchainVersionRe.Match([]byte(modFile.Go.Version)) && semver.Compare("v"+modFile.Go.Version, "v1.21.0") >= 0
179+
!toolchainVersionRe.Match([]byte(modFile.Go.Version)) && util.NewSemVer(modFile.Go.Version).IsAtLeast(toolchain.V1_21)
187180
}
188181

189182
// Given a list of `go.mod` file paths, try to parse them all. The resulting array of `GoModule` objects
@@ -537,17 +530,14 @@ const (
537530

538531
// argsForGoVersion returns the arguments to pass to the Go compiler for the given `ModMode` and
539532
// Go version
540-
func (m ModMode) ArgsForGoVersion(version string) []string {
533+
func (m ModMode) ArgsForGoVersion(version util.SemVer) []string {
541534
switch m {
542535
case ModUnset:
543536
return []string{}
544537
case ModReadonly:
545538
return []string{"-mod=readonly"}
546539
case ModMod:
547-
if !semver.IsValid(version) {
548-
log.Fatalf("Invalid Go semver: '%s'", version)
549-
}
550-
if semver.Compare(version, "v1.14") < 0 {
540+
if version.IsOlderThan(toolchain.V1_14) {
551541
return []string{} // -mod=mod is the default behaviour for go <= 1.13, and is not accepted as an argument
552542
} else {
553543
return []string{"-mod=mod"}
@@ -574,7 +564,7 @@ func getModMode(depMode DependencyInstallerMode, baseDir string) ModMode {
574564

575565
// Tries to open `go.mod` and read a go directive, returning the version and whether it was found.
576566
// The version string is returned in the "1.2.3" format.
577-
func tryReadGoDirective(path string) GoVersionInfo {
567+
func tryReadGoDirective(path string) util.SemVer {
578568
versionRe := regexp.MustCompile(`(?m)^go[ \t\r]+([0-9]+\.[0-9]+(\.[0-9]+)?)`)
579569
goMod, err := os.ReadFile(path)
580570
if err != nil {
@@ -583,9 +573,9 @@ func tryReadGoDirective(path string) GoVersionInfo {
583573
matches := versionRe.FindSubmatch(goMod)
584574
if matches != nil {
585575
if len(matches) > 1 {
586-
return GoVersionInfo{string(matches[1]), true}
576+
return util.NewSemVer(string(matches[1]))
587577
}
588578
}
589579
}
590-
return GoVersionInfo{"", false}
580+
return nil
591581
}

go/extractor/toolchain/BUILD.bazel

Lines changed: 2 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

go/extractor/toolchain/toolchain.go

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,13 @@ import (
1111
"strings"
1212

1313
"github.com/github/codeql-go/extractor/util"
14-
"golang.org/x/mod/semver"
1514
)
1615

16+
var V1_14 = util.NewSemVer("v1.14.0")
17+
var V1_16 = util.NewSemVer("v1.16.0")
18+
var V1_18 = util.NewSemVer("v1.18.0")
19+
var V1_21 = util.NewSemVer("v1.21.0")
20+
1721
// Check if Go is installed in the environment.
1822
func IsInstalled() bool {
1923
_, err := exec.LookPath("go")
@@ -23,11 +27,11 @@ func IsInstalled() bool {
2327
// The default Go version that is available on a system and a set of all versions
2428
// that we know are installed on the system.
2529
var goVersion = ""
26-
var goVersions = map[string]struct{}{}
30+
var goVersions = map[util.SemVer]struct{}{}
2731

2832
// Adds an entry to the set of installed Go versions for the normalised `version` number.
29-
func addGoVersion(version string) {
30-
goVersions[semver.Canonical("v"+version)] = struct{}{}
33+
func addGoVersion(version util.SemVer) {
34+
goVersions[version] = struct{}{}
3135
}
3236

3337
// Returns the current Go version as returned by 'go version', e.g. go1.14.4
@@ -53,19 +57,19 @@ func GetEnvGoVersion() string {
5357
}
5458

5559
goVersion = parseGoVersion(string(out))
56-
addGoVersion(goVersion[2:])
60+
addGoVersion(util.NewSemVer(goVersion))
5761
}
5862
return goVersion
5963
}
6064

6165
// Determines whether, to our knowledge, `version` is available on the current system.
62-
func HasGoVersion(version string) bool {
63-
_, found := goVersions[semver.Canonical("v"+version)]
66+
func HasGoVersion(version util.SemVer) bool {
67+
_, found := goVersions[version]
6468
return found
6569
}
6670

6771
// Attempts to install the Go toolchain `version`.
68-
func InstallVersion(workingDir string, version string) bool {
72+
func InstallVersion(workingDir string, version util.SemVer) bool {
6973
// No need to install it if we know that it is already installed.
7074
if HasGoVersion(version) {
7175
return true
@@ -74,7 +78,7 @@ func InstallVersion(workingDir string, version string) bool {
7478
// Construct a command to invoke `go version` with `GOTOOLCHAIN=go1.N.0` to give
7579
// Go a valid toolchain version to download the toolchain we need; subsequent commands
7680
// should then work even with an invalid version that's still in `go.mod`
77-
toolchainArg := "GOTOOLCHAIN=go" + semver.Canonical("v" + version)[1:]
81+
toolchainArg := "GOTOOLCHAIN=go" + version.String()[1:]
7882
versionCmd := Version()
7983
versionCmd.Dir = workingDir
8084
versionCmd.Env = append(os.Environ(), toolchainArg)
@@ -107,20 +111,12 @@ func InstallVersion(workingDir string, version string) bool {
107111
}
108112

109113
// Returns the current Go version in semver format, e.g. v1.14.4
110-
func GetEnvGoSemVer() string {
114+
func GetEnvGoSemVer() util.SemVer {
111115
goVersion := GetEnvGoVersion()
112116
if !strings.HasPrefix(goVersion, "go") {
113117
log.Fatalf("Expected 'go version' output of the form 'go1.2.3'; got '%s'", goVersion)
114118
}
115-
// Go versions don't follow the SemVer format, but the only exception we normally care about
116-
// is release candidates; so this is a horrible hack to convert e.g. `go1.22rc1` into `go1.22-rc1`
117-
// which is compatible with the SemVer specification
118-
rcIndex := strings.Index(goVersion, "rc")
119-
if rcIndex != -1 {
120-
return semver.Canonical("v"+goVersion[2:rcIndex]) + "-" + goVersion[rcIndex:]
121-
} else {
122-
return semver.Canonical("v" + goVersion[2:])
123-
}
119+
return util.NewSemVer(goVersion)
124120
}
125121

126122
// The 'go version' command may output warnings on separate lines before
@@ -137,7 +133,7 @@ func parseGoVersion(data string) string {
137133

138134
// Returns a value indicating whether the system Go toolchain supports workspaces.
139135
func SupportsWorkspaces() bool {
140-
return semver.Compare(GetEnvGoSemVer(), "v1.18.0") >= 0
136+
return GetEnvGoSemVer().IsAtLeast(V1_18)
141137
}
142138

143139
// Run `go mod tidy -e` in the directory given by `path`.

0 commit comments

Comments
 (0)