mirror of
https://github.com/github/codeql.git
synced 2025-12-17 01:03:14 +01:00
Merge pull request #16460 from github/mbg/go/semver-type
Go: Use new type for all semantic versions
This commit is contained in:
2
go/extractor/autobuilder/BUILD.bazel
generated
2
go/extractor/autobuilder/BUILD.bazel
generated
@@ -15,7 +15,6 @@ go_library(
|
||||
"//go/extractor/project",
|
||||
"//go/extractor/toolchain",
|
||||
"//go/extractor/util",
|
||||
"//go/extractor/vendor/golang.org/x/mod/semver",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -23,4 +22,5 @@ go_test(
|
||||
name = "autobuilder_test",
|
||||
srcs = ["build-environment_test.go"],
|
||||
embed = [":autobuilder"],
|
||||
deps = ["//go/extractor/util"],
|
||||
)
|
||||
|
||||
@@ -8,53 +8,51 @@ import (
|
||||
"github.com/github/codeql-go/extractor/diagnostics"
|
||||
"github.com/github/codeql-go/extractor/project"
|
||||
"github.com/github/codeql-go/extractor/toolchain"
|
||||
"golang.org/x/mod/semver"
|
||||
"github.com/github/codeql-go/extractor/util"
|
||||
)
|
||||
|
||||
const minGoVersion = "1.11"
|
||||
const maxGoVersion = "1.22"
|
||||
var minGoVersion = util.NewSemVer("1.11")
|
||||
var maxGoVersion = util.NewSemVer("1.22")
|
||||
|
||||
type versionInfo struct {
|
||||
goModVersion string // The version of Go found in the go directive in the `go.mod` file.
|
||||
goModVersionFound bool // Whether a `go` directive was found in the `go.mod` file.
|
||||
goEnvVersion string // The version of Go found in the environment.
|
||||
goEnvVersionFound bool // Whether an installation of Go was found in the environment.
|
||||
goModVersion util.SemVer // The version of Go found in the go directive in the `go.mod` file.
|
||||
goEnvVersion util.SemVer // The version of Go found in the environment.
|
||||
}
|
||||
|
||||
func (v versionInfo) String() string {
|
||||
return fmt.Sprintf(
|
||||
"go.mod version: %s, go.mod directive found: %t, go env version: %s, go installation found: %t",
|
||||
v.goModVersion, v.goModVersionFound, v.goEnvVersion, v.goEnvVersionFound)
|
||||
"go.mod version: %s, go env version: %s",
|
||||
v.goModVersion, v.goEnvVersion)
|
||||
}
|
||||
|
||||
// Check if `version` is lower than `minGoVersion`. Note that for this comparison we ignore the
|
||||
// patch part of the version, so 1.20.1 and 1.20 are considered equal.
|
||||
func belowSupportedRange(version string) bool {
|
||||
return semver.Compare(semver.MajorMinor("v"+version), "v"+minGoVersion) < 0
|
||||
func belowSupportedRange(version util.SemVer) bool {
|
||||
return version.MajorMinor().IsOlderThan(minGoVersion.MajorMinor())
|
||||
}
|
||||
|
||||
// Check if `version` is higher than `maxGoVersion`. Note that for this comparison we ignore the
|
||||
// patch part of the version, so 1.20.1 and 1.20 are considered equal.
|
||||
func aboveSupportedRange(version string) bool {
|
||||
return semver.Compare(semver.MajorMinor("v"+version), "v"+maxGoVersion) > 0
|
||||
func aboveSupportedRange(version util.SemVer) bool {
|
||||
return version.MajorMinor().IsNewerThan(maxGoVersion.MajorMinor())
|
||||
}
|
||||
|
||||
// Check if `version` is lower than `minGoVersion` or higher than `maxGoVersion`. Note that for
|
||||
// this comparison we ignore the patch part of the version, so 1.20.1 and 1.20 are considered
|
||||
// equal.
|
||||
func outsideSupportedRange(version string) bool {
|
||||
func outsideSupportedRange(version util.SemVer) bool {
|
||||
return belowSupportedRange(version) || aboveSupportedRange(version)
|
||||
}
|
||||
|
||||
// Assuming `v.goModVersionFound` is false, emit a diagnostic and return the version to install,
|
||||
// or the empty string if we should not attempt to install a version of Go.
|
||||
func getVersionWhenGoModVersionNotFound(v versionInfo) (msg, version string) {
|
||||
if !v.goEnvVersionFound {
|
||||
func getVersionWhenGoModVersionNotFound(v versionInfo) (msg string, version util.SemVer) {
|
||||
if v.goEnvVersion == nil {
|
||||
// There is no Go version installed in the environment. We have no indication which version
|
||||
// was intended to be used to build this project. Go versions are generally backwards
|
||||
// compatible, so we install the maximum supported version.
|
||||
msg = "No version of Go installed and no `go.mod` file found. Requesting the maximum " +
|
||||
"supported version of Go (" + maxGoVersion + ")."
|
||||
"supported version of Go (" + maxGoVersion.String() + ")."
|
||||
version = maxGoVersion
|
||||
diagnostics.EmitNoGoModAndNoGoEnv(msg)
|
||||
} else if outsideSupportedRange(v.goEnvVersion) {
|
||||
@@ -62,8 +60,8 @@ func getVersionWhenGoModVersionNotFound(v versionInfo) (msg, version string) {
|
||||
// which version was intended to be used to build this project. Go versions are generally
|
||||
// backwards compatible, so we install the maximum supported version.
|
||||
msg = "No `go.mod` file found. The version of Go installed in the environment (" +
|
||||
v.goEnvVersion + ") is outside of the supported range (" + minGoVersion + "-" +
|
||||
maxGoVersion + "). Requesting the maximum supported version of Go (" + maxGoVersion +
|
||||
v.goEnvVersion.String() + ") is outside of the supported range (" + minGoVersion.String() + "-" +
|
||||
maxGoVersion.String() + "). Requesting the maximum supported version of Go (" + maxGoVersion.String() +
|
||||
")."
|
||||
version = maxGoVersion
|
||||
diagnostics.EmitNoGoModAndGoEnvUnsupported(msg)
|
||||
@@ -71,9 +69,9 @@ func getVersionWhenGoModVersionNotFound(v versionInfo) (msg, version string) {
|
||||
// The version of Go that is installed is supported. We have no indication which version
|
||||
// was intended to be used to build this project. We assume that the installed version is
|
||||
// suitable and do not install a version of Go.
|
||||
msg = "No `go.mod` file found. Version " + v.goEnvVersion + " installed in the " +
|
||||
msg = "No `go.mod` file found. Version " + v.goEnvVersion.String() + " installed in the " +
|
||||
"environment is supported. Not requesting any version of Go."
|
||||
version = ""
|
||||
version = nil
|
||||
diagnostics.EmitNoGoModAndGoEnvSupported(msg)
|
||||
}
|
||||
|
||||
@@ -82,57 +80,57 @@ func getVersionWhenGoModVersionNotFound(v versionInfo) (msg, version string) {
|
||||
|
||||
// Assuming `v.goModVersion` is above the supported range, emit a diagnostic and return the
|
||||
// version to install, or the empty string if we should not attempt to install a version of Go.
|
||||
func getVersionWhenGoModVersionTooHigh(v versionInfo) (msg, version string) {
|
||||
if !v.goEnvVersionFound {
|
||||
func getVersionWhenGoModVersionTooHigh(v versionInfo) (msg string, version util.SemVer) {
|
||||
if v.goEnvVersion == nil {
|
||||
// The version in the `go.mod` file is above the supported range. There is no Go version
|
||||
// installed. We install the maximum supported version as a best effort.
|
||||
msg = "The version of Go found in the `go.mod` file (" + v.goModVersion +
|
||||
") is above the supported range (" + minGoVersion + "-" + maxGoVersion +
|
||||
msg = "The version of Go found in the `go.mod` file (" + v.goModVersion.String() +
|
||||
") is above the supported range (" + minGoVersion.String() + "-" + maxGoVersion.String() +
|
||||
"). No version of Go installed. Requesting the maximum supported version of Go (" +
|
||||
maxGoVersion + ")."
|
||||
maxGoVersion.String() + ")."
|
||||
version = maxGoVersion
|
||||
diagnostics.EmitGoModVersionTooHighAndNoGoEnv(msg)
|
||||
} else if aboveSupportedRange(v.goEnvVersion) {
|
||||
// The version in the `go.mod` file is above the supported range. The version of Go that
|
||||
// is installed is above the supported range. We do not install a version of Go.
|
||||
msg = "The version of Go found in the `go.mod` file (" + v.goModVersion +
|
||||
") is above the supported range (" + minGoVersion + "-" + maxGoVersion +
|
||||
"). The version of Go installed in the environment (" + v.goEnvVersion +
|
||||
") is above the supported range (" + minGoVersion + "-" + maxGoVersion +
|
||||
msg = "The version of Go found in the `go.mod` file (" + v.goModVersion.String() +
|
||||
") is above the supported range (" + minGoVersion.String() + "-" + maxGoVersion.String() +
|
||||
"). The version of Go installed in the environment (" + v.goEnvVersion.String() +
|
||||
") is above the supported range (" + minGoVersion.String() + "-" + maxGoVersion.String() +
|
||||
"). Not requesting any version of Go."
|
||||
version = ""
|
||||
version = nil
|
||||
diagnostics.EmitGoModVersionTooHighAndEnvVersionTooHigh(msg)
|
||||
} else if belowSupportedRange(v.goEnvVersion) {
|
||||
// The version in the `go.mod` file is above the supported range. The version of Go that
|
||||
// is installed is below the supported range. We install the maximum supported version as
|
||||
// a best effort.
|
||||
msg = "The version of Go found in the `go.mod` file (" + v.goModVersion +
|
||||
") is above the supported range (" + minGoVersion + "-" + maxGoVersion +
|
||||
"). The version of Go installed in the environment (" + v.goEnvVersion +
|
||||
") is below the supported range (" + minGoVersion + "-" + maxGoVersion +
|
||||
"). Requesting the maximum supported version of Go (" + maxGoVersion + ")."
|
||||
msg = "The version of Go found in the `go.mod` file (" + v.goModVersion.String() +
|
||||
") is above the supported range (" + minGoVersion.String() + "-" + maxGoVersion.String() +
|
||||
"). The version of Go installed in the environment (" + v.goEnvVersion.String() +
|
||||
") is below the supported range (" + minGoVersion.String() + "-" + maxGoVersion.String() +
|
||||
"). Requesting the maximum supported version of Go (" + maxGoVersion.String() + ")."
|
||||
version = maxGoVersion
|
||||
diagnostics.EmitGoModVersionTooHighAndEnvVersionTooLow(msg)
|
||||
} else if semver.Compare("v"+maxGoVersion, "v"+v.goEnvVersion) > 0 {
|
||||
} else if maxGoVersion.IsNewerThan(v.goEnvVersion) {
|
||||
// The version in the `go.mod` file is above the supported range. The version of Go that
|
||||
// is installed is supported and below the maximum supported version. We install the
|
||||
// maximum supported version as a best effort.
|
||||
msg = "The version of Go found in the `go.mod` file (" + v.goModVersion +
|
||||
") is above the supported range (" + minGoVersion + "-" + maxGoVersion +
|
||||
"). The version of Go installed in the environment (" + v.goEnvVersion +
|
||||
") is below the maximum supported version (" + maxGoVersion +
|
||||
"). Requesting the maximum supported version of Go (" + maxGoVersion + ")."
|
||||
msg = "The version of Go found in the `go.mod` file (" + v.goModVersion.String() +
|
||||
") is above the supported range (" + minGoVersion.String() + "-" + maxGoVersion.String() +
|
||||
"). The version of Go installed in the environment (" + v.goEnvVersion.String() +
|
||||
") is below the maximum supported version (" + maxGoVersion.String() +
|
||||
"). Requesting the maximum supported version of Go (" + maxGoVersion.String() + ")."
|
||||
version = maxGoVersion
|
||||
diagnostics.EmitGoModVersionTooHighAndEnvVersionBelowMax(msg)
|
||||
} else {
|
||||
// The version in the `go.mod` file is above the supported range. The version of Go that
|
||||
// is installed is the maximum supported version. We do not install a version of Go.
|
||||
msg = "The version of Go found in the `go.mod` file (" + v.goModVersion +
|
||||
") is above the supported range (" + minGoVersion + "-" + maxGoVersion +
|
||||
"). The version of Go installed in the environment (" + v.goEnvVersion +
|
||||
") is the maximum supported version (" + maxGoVersion +
|
||||
msg = "The version of Go found in the `go.mod` file (" + v.goModVersion.String() +
|
||||
") is above the supported range (" + minGoVersion.String() + "-" + maxGoVersion.String() +
|
||||
"). The version of Go installed in the environment (" + v.goEnvVersion.String() +
|
||||
") is the maximum supported version (" + maxGoVersion.String() +
|
||||
"). Not requesting any version of Go."
|
||||
version = ""
|
||||
version = nil
|
||||
diagnostics.EmitGoModVersionTooHighAndEnvVersionMax(msg)
|
||||
}
|
||||
|
||||
@@ -141,35 +139,35 @@ func getVersionWhenGoModVersionTooHigh(v versionInfo) (msg, version string) {
|
||||
|
||||
// Assuming `v.goModVersion` is below the supported range, emit a diagnostic and return the
|
||||
// version to install, or the empty string if we should not attempt to install a version of Go.
|
||||
func getVersionWhenGoModVersionTooLow(v versionInfo) (msg, version string) {
|
||||
if !v.goEnvVersionFound {
|
||||
func getVersionWhenGoModVersionTooLow(v versionInfo) (msg string, version util.SemVer) {
|
||||
if v.goEnvVersion == nil {
|
||||
// There is no Go version installed. The version in the `go.mod` file is below the
|
||||
// supported range. Go versions are generally backwards compatible, so we install the
|
||||
// minimum supported version.
|
||||
msg = "The version of Go found in the `go.mod` file (" + v.goModVersion +
|
||||
") is below the supported range (" + minGoVersion + "-" + maxGoVersion +
|
||||
msg = "The version of Go found in the `go.mod` file (" + v.goModVersion.String() +
|
||||
") is below the supported range (" + minGoVersion.String() + "-" + maxGoVersion.String() +
|
||||
"). No version of Go installed. Requesting the minimum supported version of Go (" +
|
||||
minGoVersion + ")."
|
||||
minGoVersion.String() + ")."
|
||||
version = minGoVersion
|
||||
diagnostics.EmitGoModVersionTooLowAndNoGoEnv(msg)
|
||||
} else if outsideSupportedRange(v.goEnvVersion) {
|
||||
// The version of Go that is installed is outside of the supported range. The version
|
||||
// in the `go.mod` file is below the supported range. Go versions are generally
|
||||
// backwards compatible, so we install the minimum supported version.
|
||||
msg = "The version of Go found in the `go.mod` file (" + v.goModVersion +
|
||||
") is below the supported range (" + minGoVersion + "-" + maxGoVersion +
|
||||
"). The version of Go installed in the environment (" + v.goEnvVersion +
|
||||
") is outside of the supported range (" + minGoVersion + "-" + maxGoVersion + "). " +
|
||||
"Requesting the minimum supported version of Go (" + minGoVersion + ")."
|
||||
msg = "The version of Go found in the `go.mod` file (" + v.goModVersion.String() +
|
||||
") is below the supported range (" + minGoVersion.String() + "-" + maxGoVersion.String() +
|
||||
"). The version of Go installed in the environment (" + v.goEnvVersion.String() +
|
||||
") is outside of the supported range (" + minGoVersion.String() + "-" + maxGoVersion.String() + "). " +
|
||||
"Requesting the minimum supported version of Go (" + minGoVersion.String() + ")."
|
||||
version = minGoVersion
|
||||
diagnostics.EmitGoModVersionTooLowAndEnvVersionUnsupported(msg)
|
||||
} else {
|
||||
// The version of Go that is installed is supported. The version in the `go.mod` file is
|
||||
// below the supported range. We do not install a version of Go.
|
||||
msg = "The version of Go installed in the environment (" + v.goEnvVersion +
|
||||
msg = "The version of Go installed in the environment (" + v.goEnvVersion.String() +
|
||||
") is supported and is high enough for the version found in the `go.mod` file (" +
|
||||
v.goModVersion + "). Not requesting any version of Go."
|
||||
version = ""
|
||||
v.goModVersion.String() + "). Not requesting any version of Go."
|
||||
version = nil
|
||||
diagnostics.EmitGoModVersionTooLowAndEnvVersionSupported(msg)
|
||||
}
|
||||
|
||||
@@ -178,40 +176,40 @@ func getVersionWhenGoModVersionTooLow(v versionInfo) (msg, version string) {
|
||||
|
||||
// Assuming `v.goModVersion` is in the supported range, emit a diagnostic and return the version
|
||||
// to install, or the empty string if we should not attempt to install a version of Go.
|
||||
func getVersionWhenGoModVersionSupported(v versionInfo) (msg, version string) {
|
||||
if !v.goEnvVersionFound {
|
||||
func getVersionWhenGoModVersionSupported(v versionInfo) (msg string, version util.SemVer) {
|
||||
if v.goEnvVersion == nil {
|
||||
// There is no Go version installed. The version in the `go.mod` file is supported.
|
||||
// We install the version from the `go.mod` file.
|
||||
msg = "No version of Go installed. Requesting the version of Go found in the `go.mod` " +
|
||||
"file (" + v.goModVersion + ")."
|
||||
"file (" + v.goModVersion.String() + ")."
|
||||
version = v.goModVersion
|
||||
diagnostics.EmitGoModVersionSupportedAndNoGoEnv(msg)
|
||||
} else if outsideSupportedRange(v.goEnvVersion) {
|
||||
// The version of Go that is installed is outside of the supported range. The version in
|
||||
// the `go.mod` file is supported. We install the version from the `go.mod` file.
|
||||
msg = "The version of Go installed in the environment (" + v.goEnvVersion +
|
||||
") is outside of the supported range (" + minGoVersion + "-" + maxGoVersion + "). " +
|
||||
msg = "The version of Go installed in the environment (" + v.goEnvVersion.String() +
|
||||
") is outside of the supported range (" + minGoVersion.String() + "-" + maxGoVersion.String() + "). " +
|
||||
"Requesting the version of Go from the `go.mod` file (" +
|
||||
v.goModVersion + ")."
|
||||
v.goModVersion.String() + ")."
|
||||
version = v.goModVersion
|
||||
diagnostics.EmitGoModVersionSupportedAndGoEnvUnsupported(msg)
|
||||
} else if semver.Compare("v"+v.goModVersion, "v"+v.goEnvVersion) > 0 {
|
||||
} else if v.goModVersion.IsNewerThan(v.goEnvVersion) {
|
||||
// The version of Go that is installed is supported. The version in the `go.mod` file is
|
||||
// supported and is higher than the version that is installed. We install the version from
|
||||
// the `go.mod` file.
|
||||
msg = "The version of Go installed in the environment (" + v.goEnvVersion +
|
||||
") is lower than the version found in the `go.mod` file (" + v.goModVersion +
|
||||
"). Requesting the version of Go from the `go.mod` file (" + v.goModVersion + ")."
|
||||
msg = "The version of Go installed in the environment (" + v.goEnvVersion.String() +
|
||||
") is lower than the version found in the `go.mod` file (" + v.goModVersion.String() +
|
||||
"). Requesting the version of Go from the `go.mod` file (" + v.goModVersion.String() + ")."
|
||||
version = v.goModVersion
|
||||
diagnostics.EmitGoModVersionSupportedHigherGoEnv(msg)
|
||||
} else {
|
||||
// The version of Go that is installed is supported. The version in the `go.mod` file is
|
||||
// supported and is lower than or equal to the version that is installed. We do not install
|
||||
// a version of Go.
|
||||
msg = "The version of Go installed in the environment (" + v.goEnvVersion +
|
||||
msg = "The version of Go installed in the environment (" + v.goEnvVersion.String() +
|
||||
") is supported and is high enough for the version found in the `go.mod` file (" +
|
||||
v.goModVersion + "). Not requesting any version of Go."
|
||||
version = ""
|
||||
v.goModVersion.String() + "). Not requesting any version of Go."
|
||||
version = nil
|
||||
diagnostics.EmitGoModVersionSupportedLowerEqualGoEnv(msg)
|
||||
}
|
||||
|
||||
@@ -231,8 +229,8 @@ func getVersionWhenGoModVersionSupported(v versionInfo) (msg, version string) {
|
||||
// | *In supported range* | No action | No action | Install version from go.mod if newer than installed | Install max supported if newer than installed |
|
||||
// | *Above max supported* | Install max supported | Install min supported | Install version from go.mod | No action |
|
||||
// +-----------------------+-----------------------+-----------------------+-----------------------------------------------------+------------------------------------------------+
|
||||
func getVersionToInstall(v versionInfo) (msg, version string) {
|
||||
if !v.goModVersionFound {
|
||||
func getVersionToInstall(v versionInfo) (msg string, version util.SemVer) {
|
||||
if v.goModVersion == nil {
|
||||
return getVersionWhenGoModVersionNotFound(v)
|
||||
}
|
||||
|
||||
@@ -249,12 +247,12 @@ func getVersionToInstall(v versionInfo) (msg, version string) {
|
||||
|
||||
// Output some JSON to stdout specifying the version of Go to install, unless `version` is the
|
||||
// empty string.
|
||||
func outputEnvironmentJson(version string) {
|
||||
func outputEnvironmentJson(version util.SemVer) {
|
||||
var content string
|
||||
if version == "" {
|
||||
if version == nil {
|
||||
content = `{ "go": {} }`
|
||||
} else {
|
||||
content = `{ "go": { "version": "` + version + `" } }`
|
||||
content = `{ "go": { "version": "` + version.StandardSemVer() + `" } }`
|
||||
}
|
||||
_, err := fmt.Fprint(os.Stdout, content)
|
||||
|
||||
@@ -273,13 +271,11 @@ func IdentifyEnvironment() {
|
||||
defer project.RemoveTemporaryExtractorFiles()
|
||||
|
||||
// Find the greatest Go version required by any of the workspaces.
|
||||
greatestGoVersion := project.RequiredGoVersion(&workspaces)
|
||||
v.goModVersion, v.goModVersionFound = greatestGoVersion.Version, greatestGoVersion.Found
|
||||
v.goModVersion = project.RequiredGoVersion(&workspaces)
|
||||
|
||||
// Find which, if any, version of Go is installed on the system already.
|
||||
v.goEnvVersionFound = toolchain.IsInstalled()
|
||||
if v.goEnvVersionFound {
|
||||
v.goEnvVersion = toolchain.GetEnvGoVersion()[2:]
|
||||
if toolchain.IsInstalled() {
|
||||
v.goEnvVersion = toolchain.GetEnvGoSemVer()
|
||||
}
|
||||
|
||||
// Determine which version of Go we should recommend to install.
|
||||
|
||||
@@ -1,47 +1,55 @@
|
||||
package autobuilder
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/github/codeql-go/extractor/util"
|
||||
)
|
||||
|
||||
func TestGetVersionToInstall(t *testing.T) {
|
||||
tests := map[versionInfo]string{
|
||||
type inputVersions struct {
|
||||
modVersion string
|
||||
envVersion string
|
||||
}
|
||||
tests := map[inputVersions]string{
|
||||
// getVersionWhenGoModVersionNotFound()
|
||||
{"", false, "", false}: maxGoVersion,
|
||||
{"", false, "1.2.2", true}: maxGoVersion,
|
||||
{"", false, "9999.0.1", true}: maxGoVersion,
|
||||
{"", false, "1.11.13", true}: "",
|
||||
{"", false, "1.20.3", true}: "",
|
||||
{"", ""}: maxGoVersion.String(),
|
||||
{"", "1.2.2"}: maxGoVersion.String(),
|
||||
{"", "9999.0.1"}: maxGoVersion.String(),
|
||||
{"", "1.11.13"}: "",
|
||||
{"", "1.20.3"}: "",
|
||||
|
||||
// getVersionWhenGoModVersionTooHigh()
|
||||
{"9999.0", true, "", false}: maxGoVersion,
|
||||
{"9999.0", true, "9999.0.1", true}: "",
|
||||
{"9999.0", true, "1.1", true}: maxGoVersion,
|
||||
{"9999.0", true, minGoVersion, false}: maxGoVersion,
|
||||
{"9999.0", true, maxGoVersion, true}: "",
|
||||
{"9999.0", ""}: maxGoVersion.String(),
|
||||
{"9999.0", "9999.0.1"}: "",
|
||||
{"9999.0", "1.1"}: maxGoVersion.String(),
|
||||
{"9999.0", minGoVersion.String()}: maxGoVersion.String(),
|
||||
{"9999.0", maxGoVersion.String()}: "",
|
||||
|
||||
// getVersionWhenGoModVersionTooLow()
|
||||
{"0.0", true, "", false}: minGoVersion,
|
||||
{"0.0", true, "9999.0", true}: minGoVersion,
|
||||
{"0.0", true, "1.2.2", true}: minGoVersion,
|
||||
{"0.0", true, "1.20.3", true}: "",
|
||||
{"0.0", ""}: minGoVersion.String(),
|
||||
{"0.0", "9999.0"}: minGoVersion.String(),
|
||||
{"0.0", "1.2.2"}: minGoVersion.String(),
|
||||
{"0.0", "1.20.3"}: "",
|
||||
|
||||
// getVersionWhenGoModVersionSupported()
|
||||
{"1.20", true, "", false}: "1.20",
|
||||
{"1.11", true, "", false}: "1.11",
|
||||
{"1.20", true, "1.2.2", true}: "1.20",
|
||||
{"1.11", true, "1.2.2", true}: "1.11",
|
||||
{"1.20", true, "9999.0.1", true}: "1.20",
|
||||
{"1.11", true, "9999.0.1", true}: "1.11",
|
||||
{"1.20", ""}: "1.20",
|
||||
{"1.11", ""}: "1.11",
|
||||
{"1.20", "1.2.2"}: "1.20",
|
||||
{"1.11", "1.2.2"}: "1.11",
|
||||
{"1.20", "9999.0.1"}: "1.20",
|
||||
{"1.11", "9999.0.1"}: "1.11",
|
||||
// go.mod version > go installation version
|
||||
{"1.20", true, "1.11.13", true}: "1.20",
|
||||
{"1.20", true, "1.12", true}: "1.20",
|
||||
{"1.20", "1.11.13"}: "1.20",
|
||||
{"1.20", "1.12"}: "1.20",
|
||||
// go.mod version <= go installation version (Note comparisons ignore the patch version)
|
||||
{"1.11", true, "1.20", true}: "",
|
||||
{"1.11", true, "1.20.3", true}: "",
|
||||
{"1.20", true, "1.20.3", true}: "",
|
||||
{"1.11", "1.20"}: "",
|
||||
{"1.11", "1.20.3"}: "",
|
||||
{"1.20", "1.20.3"}: "",
|
||||
}
|
||||
for input, expected := range tests {
|
||||
_, actual := getVersionToInstall(input)
|
||||
if actual != expected {
|
||||
_, actual := getVersionToInstall(versionInfo{util.NewSemVer(input.modVersion), util.NewSemVer(input.envVersion)})
|
||||
if actual != util.NewSemVer(expected) {
|
||||
t.Errorf("Expected getVersionToInstall(\"%s\") to be \"%s\", but got \"%s\".", input, expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
1
go/extractor/cli/go-autobuilder/BUILD.bazel
generated
1
go/extractor/cli/go-autobuilder/BUILD.bazel
generated
@@ -14,7 +14,6 @@ go_library(
|
||||
"//go/extractor/project",
|
||||
"//go/extractor/toolchain",
|
||||
"//go/extractor/util",
|
||||
"//go/extractor/vendor/golang.org/x/mod/semver",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -10,8 +10,6 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/mod/semver"
|
||||
|
||||
"github.com/github/codeql-go/extractor/autobuilder"
|
||||
"github.com/github/codeql-go/extractor/diagnostics"
|
||||
"github.com/github/codeql-go/extractor/project"
|
||||
@@ -156,7 +154,7 @@ func getNeedGopath(workspace project.GoWorkspace, importpath string) bool {
|
||||
// Try to update `go.mod` and `go.sum` if the go version is >= 1.16.
|
||||
func tryUpdateGoModAndGoSum(workspace project.GoWorkspace) {
|
||||
// Go 1.16 and later won't automatically attempt to update go.mod / go.sum during package loading, so try to update them here:
|
||||
if workspace.ModMode != project.ModVendor && workspace.DepMode == project.GoGetWithModules && semver.Compare(toolchain.GetEnvGoSemVer(), "v1.16") >= 0 {
|
||||
if workspace.ModMode != project.ModVendor && workspace.DepMode == project.GoGetWithModules && toolchain.GetEnvGoSemVer().IsAtLeast(toolchain.V1_16) {
|
||||
for _, goMod := range workspace.Modules {
|
||||
// stat go.mod and go.sum
|
||||
goModPath := goMod.Path
|
||||
@@ -542,12 +540,12 @@ func installDependenciesAndBuild() {
|
||||
|
||||
// This diagnostic is not required if the system Go version is 1.21 or greater, since the
|
||||
// Go tooling should install required Go versions as needed.
|
||||
if semver.Compare(toolchain.GetEnvGoSemVer(), "v1.21.0") < 0 && greatestGoVersion.Found && semver.Compare("v"+greatestGoVersion.Version, toolchain.GetEnvGoSemVer()) > 0 {
|
||||
diagnostics.EmitNewerGoVersionNeeded(toolchain.GetEnvGoSemVer(), "v"+greatestGoVersion.Version)
|
||||
if toolchain.GetEnvGoSemVer().IsOlderThan(toolchain.V1_21) && greatestGoVersion != nil && greatestGoVersion.IsNewerThan(toolchain.GetEnvGoSemVer()) {
|
||||
diagnostics.EmitNewerGoVersionNeeded(toolchain.GetEnvGoSemVer().String(), greatestGoVersion.String())
|
||||
if val, _ := os.LookupEnv("GITHUB_ACTIONS"); val == "true" {
|
||||
log.Printf(
|
||||
"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",
|
||||
"v"+greatestGoVersion.Version,
|
||||
greatestGoVersion,
|
||||
toolchain.GetEnvGoSemVer())
|
||||
}
|
||||
}
|
||||
@@ -559,7 +557,7 @@ func installDependenciesAndBuild() {
|
||||
for i, workspace := range workspaces {
|
||||
goVersionInfo := workspace.RequiredGoVersion()
|
||||
|
||||
fixGoVendorIssues(&workspace, goVersionInfo.Found)
|
||||
fixGoVendorIssues(&workspace, goVersionInfo != nil)
|
||||
|
||||
tryUpdateGoModAndGoSum(workspace)
|
||||
|
||||
|
||||
1
go/extractor/project/BUILD.bazel
generated
1
go/extractor/project/BUILD.bazel
generated
@@ -12,7 +12,6 @@ go_library(
|
||||
"//go/extractor/toolchain",
|
||||
"//go/extractor/util",
|
||||
"//go/extractor/vendor/golang.org/x/mod/modfile",
|
||||
"//go/extractor/vendor/golang.org/x/mod/semver",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"github.com/github/codeql-go/extractor/toolchain"
|
||||
"github.com/github/codeql-go/extractor/util"
|
||||
"golang.org/x/mod/modfile"
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
|
||||
// DependencyInstallerMode is an enum describing how dependencies should be installed
|
||||
@@ -49,53 +48,47 @@ type GoWorkspace struct {
|
||||
}
|
||||
|
||||
// Represents a nullable version string.
|
||||
type GoVersionInfo struct {
|
||||
// The version string, if any
|
||||
Version string
|
||||
// A value indicating whether a version string was found
|
||||
Found bool
|
||||
}
|
||||
type GoVersionInfo = util.SemVer
|
||||
|
||||
// Determines the version of Go that is required by this workspace. This is, in order of preference:
|
||||
// 1. The Go version specified in the `go.work` file, if any.
|
||||
// 2. The greatest Go version specified in any `go.mod` file, if any.
|
||||
func (workspace *GoWorkspace) RequiredGoVersion() GoVersionInfo {
|
||||
func (workspace *GoWorkspace) RequiredGoVersion() util.SemVer {
|
||||
if workspace.WorkspaceFile != nil && workspace.WorkspaceFile.Go != nil {
|
||||
// If we have parsed a `go.work` file, return the version number from it.
|
||||
return GoVersionInfo{Version: workspace.WorkspaceFile.Go.Version, Found: true}
|
||||
return util.NewSemVer(workspace.WorkspaceFile.Go.Version)
|
||||
} else if workspace.Modules != nil && len(workspace.Modules) > 0 {
|
||||
// Otherwise, if we have `go.work` files, find the greatest Go version in those.
|
||||
var greatestVersion string = ""
|
||||
var greatestVersion util.SemVer = nil
|
||||
for _, module := range workspace.Modules {
|
||||
if module.Module != nil && module.Module.Go != nil {
|
||||
// If we have parsed the file, retrieve the version number we have already obtained.
|
||||
if greatestVersion == "" || semver.Compare("v"+module.Module.Go.Version, "v"+greatestVersion) > 0 {
|
||||
greatestVersion = module.Module.Go.Version
|
||||
modVersion := util.NewSemVer(module.Module.Go.Version)
|
||||
if greatestVersion == nil || modVersion.IsNewerThan(greatestVersion) {
|
||||
greatestVersion = modVersion
|
||||
}
|
||||
} else {
|
||||
modVersion := tryReadGoDirective(module.Path)
|
||||
if modVersion.Found && (greatestVersion == "" || semver.Compare("v"+modVersion.Version, "v"+greatestVersion) > 0) {
|
||||
greatestVersion = modVersion.Version
|
||||
if modVersion != nil && (greatestVersion == nil || modVersion.IsNewerThan(greatestVersion)) {
|
||||
greatestVersion = modVersion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we have found some version, return it.
|
||||
if greatestVersion != "" {
|
||||
return GoVersionInfo{Version: greatestVersion, Found: true}
|
||||
}
|
||||
return greatestVersion
|
||||
}
|
||||
|
||||
return GoVersionInfo{Version: "", Found: false}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Finds the greatest Go version required by any of the given `workspaces`.
|
||||
// Returns a `GoVersionInfo` value with `Found: false` if no version information is available.
|
||||
func RequiredGoVersion(workspaces *[]GoWorkspace) GoVersionInfo {
|
||||
greatestGoVersion := GoVersionInfo{Version: "", Found: false}
|
||||
func RequiredGoVersion(workspaces *[]GoWorkspace) util.SemVer {
|
||||
var greatestGoVersion util.SemVer = nil
|
||||
for _, workspace := range *workspaces {
|
||||
goVersionInfo := workspace.RequiredGoVersion()
|
||||
if goVersionInfo.Found && (!greatestGoVersion.Found || semver.Compare("v"+goVersionInfo.Version, "v"+greatestGoVersion.Version) > 0) {
|
||||
if goVersionInfo != nil && (greatestGoVersion == nil || goVersionInfo.IsNewerThan(greatestGoVersion)) {
|
||||
greatestGoVersion = goVersionInfo
|
||||
}
|
||||
}
|
||||
@@ -183,7 +176,7 @@ var toolchainVersionRe *regexp.Regexp = regexp.MustCompile(`(?m)^([0-9]+\.[0-9]+
|
||||
// there is no `toolchain` directive, and the Go language version is not a valid toolchain version.
|
||||
func hasInvalidToolchainVersion(modFile *modfile.File) bool {
|
||||
return modFile.Toolchain == nil && modFile.Go != nil &&
|
||||
!toolchainVersionRe.Match([]byte(modFile.Go.Version)) && semver.Compare("v"+modFile.Go.Version, "v1.21.0") >= 0
|
||||
!toolchainVersionRe.Match([]byte(modFile.Go.Version)) && util.NewSemVer(modFile.Go.Version).IsAtLeast(toolchain.V1_21)
|
||||
}
|
||||
|
||||
// Given a list of `go.mod` file paths, try to parse them all. The resulting array of `GoModule` objects
|
||||
@@ -537,17 +530,14 @@ const (
|
||||
|
||||
// argsForGoVersion returns the arguments to pass to the Go compiler for the given `ModMode` and
|
||||
// Go version
|
||||
func (m ModMode) ArgsForGoVersion(version string) []string {
|
||||
func (m ModMode) ArgsForGoVersion(version util.SemVer) []string {
|
||||
switch m {
|
||||
case ModUnset:
|
||||
return []string{}
|
||||
case ModReadonly:
|
||||
return []string{"-mod=readonly"}
|
||||
case ModMod:
|
||||
if !semver.IsValid(version) {
|
||||
log.Fatalf("Invalid Go semver: '%s'", version)
|
||||
}
|
||||
if semver.Compare(version, "v1.14") < 0 {
|
||||
if version.IsOlderThan(toolchain.V1_14) {
|
||||
return []string{} // -mod=mod is the default behaviour for go <= 1.13, and is not accepted as an argument
|
||||
} else {
|
||||
return []string{"-mod=mod"}
|
||||
@@ -574,7 +564,7 @@ func getModMode(depMode DependencyInstallerMode, baseDir string) ModMode {
|
||||
|
||||
// Tries to open `go.mod` and read a go directive, returning the version and whether it was found.
|
||||
// The version string is returned in the "1.2.3" format.
|
||||
func tryReadGoDirective(path string) GoVersionInfo {
|
||||
func tryReadGoDirective(path string) util.SemVer {
|
||||
versionRe := regexp.MustCompile(`(?m)^go[ \t\r]+([0-9]+\.[0-9]+(\.[0-9]+)?)`)
|
||||
goMod, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
@@ -583,9 +573,9 @@ func tryReadGoDirective(path string) GoVersionInfo {
|
||||
matches := versionRe.FindSubmatch(goMod)
|
||||
if matches != nil {
|
||||
if len(matches) > 1 {
|
||||
return GoVersionInfo{string(matches[1]), true}
|
||||
return util.NewSemVer(string(matches[1]))
|
||||
}
|
||||
}
|
||||
}
|
||||
return GoVersionInfo{"", false}
|
||||
return nil
|
||||
}
|
||||
|
||||
6
go/extractor/toolchain/BUILD.bazel
generated
6
go/extractor/toolchain/BUILD.bazel
generated
@@ -7,14 +7,12 @@ go_library(
|
||||
srcs = ["toolchain.go"],
|
||||
importpath = "github.com/github/codeql-go/extractor/toolchain",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//go/extractor/util",
|
||||
"//go/extractor/vendor/golang.org/x/mod/semver",
|
||||
],
|
||||
deps = ["//go/extractor/util"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "toolchain_test",
|
||||
srcs = ["toolchain_test.go"],
|
||||
embed = [":toolchain"],
|
||||
deps = ["//go/extractor/util"],
|
||||
)
|
||||
|
||||
@@ -11,9 +11,13 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/github/codeql-go/extractor/util"
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
|
||||
var V1_14 = util.NewSemVer("v1.14.0")
|
||||
var V1_16 = util.NewSemVer("v1.16.0")
|
||||
var V1_18 = util.NewSemVer("v1.18.0")
|
||||
var V1_21 = util.NewSemVer("v1.21.0")
|
||||
|
||||
// Check if Go is installed in the environment.
|
||||
func IsInstalled() bool {
|
||||
_, err := exec.LookPath("go")
|
||||
@@ -23,11 +27,11 @@ func IsInstalled() bool {
|
||||
// The default Go version that is available on a system and a set of all versions
|
||||
// that we know are installed on the system.
|
||||
var goVersion = ""
|
||||
var goVersions = map[string]struct{}{}
|
||||
var goVersions = map[util.SemVer]struct{}{}
|
||||
|
||||
// Adds an entry to the set of installed Go versions for the normalised `version` number.
|
||||
func addGoVersion(version string) {
|
||||
goVersions[semver.Canonical("v"+version)] = struct{}{}
|
||||
func addGoVersion(version util.SemVer) {
|
||||
goVersions[version] = struct{}{}
|
||||
}
|
||||
|
||||
// Returns the current Go version as returned by 'go version', e.g. go1.14.4
|
||||
@@ -53,19 +57,19 @@ func GetEnvGoVersion() string {
|
||||
}
|
||||
|
||||
goVersion = parseGoVersion(string(out))
|
||||
addGoVersion(goVersion[2:])
|
||||
addGoVersion(util.NewSemVer(goVersion))
|
||||
}
|
||||
return goVersion
|
||||
}
|
||||
|
||||
// Determines whether, to our knowledge, `version` is available on the current system.
|
||||
func HasGoVersion(version string) bool {
|
||||
_, found := goVersions[semver.Canonical("v"+version)]
|
||||
func HasGoVersion(version util.SemVer) bool {
|
||||
_, found := goVersions[version]
|
||||
return found
|
||||
}
|
||||
|
||||
// Attempts to install the Go toolchain `version`.
|
||||
func InstallVersion(workingDir string, version string) bool {
|
||||
func InstallVersion(workingDir string, version util.SemVer) bool {
|
||||
// No need to install it if we know that it is already installed.
|
||||
if HasGoVersion(version) {
|
||||
return true
|
||||
@@ -74,7 +78,7 @@ func InstallVersion(workingDir string, version string) bool {
|
||||
// Construct a command to invoke `go version` with `GOTOOLCHAIN=go1.N.0` to give
|
||||
// Go a valid toolchain version to download the toolchain we need; subsequent commands
|
||||
// should then work even with an invalid version that's still in `go.mod`
|
||||
toolchainArg := "GOTOOLCHAIN=go" + semver.Canonical("v" + version)[1:]
|
||||
toolchainArg := "GOTOOLCHAIN=go" + version.String()[1:]
|
||||
versionCmd := Version()
|
||||
versionCmd.Dir = workingDir
|
||||
versionCmd.Env = append(os.Environ(), toolchainArg)
|
||||
@@ -107,20 +111,12 @@ func InstallVersion(workingDir string, version string) bool {
|
||||
}
|
||||
|
||||
// Returns the current Go version in semver format, e.g. v1.14.4
|
||||
func GetEnvGoSemVer() string {
|
||||
func GetEnvGoSemVer() util.SemVer {
|
||||
goVersion := GetEnvGoVersion()
|
||||
if !strings.HasPrefix(goVersion, "go") {
|
||||
log.Fatalf("Expected 'go version' output of the form 'go1.2.3'; got '%s'", goVersion)
|
||||
}
|
||||
// Go versions don't follow the SemVer format, but the only exception we normally care about
|
||||
// is release candidates; so this is a horrible hack to convert e.g. `go1.22rc1` into `go1.22-rc1`
|
||||
// which is compatible with the SemVer specification
|
||||
rcIndex := strings.Index(goVersion, "rc")
|
||||
if rcIndex != -1 {
|
||||
return semver.Canonical("v"+goVersion[2:rcIndex]) + "-" + goVersion[rcIndex:]
|
||||
} else {
|
||||
return semver.Canonical("v" + goVersion[2:])
|
||||
}
|
||||
return util.NewSemVer(goVersion)
|
||||
}
|
||||
|
||||
// The 'go version' command may output warnings on separate lines before
|
||||
@@ -137,7 +133,7 @@ func parseGoVersion(data string) string {
|
||||
|
||||
// Returns a value indicating whether the system Go toolchain supports workspaces.
|
||||
func SupportsWorkspaces() bool {
|
||||
return semver.Compare(GetEnvGoSemVer(), "v1.18.0") >= 0
|
||||
return GetEnvGoSemVer().IsAtLeast(V1_18)
|
||||
}
|
||||
|
||||
// Run `go mod tidy -e` in the directory given by `path`.
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package toolchain
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/github/codeql-go/extractor/util"
|
||||
)
|
||||
|
||||
func TestParseGoVersion(t *testing.T) {
|
||||
tests := map[string]string{
|
||||
@@ -16,7 +20,7 @@ func TestParseGoVersion(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestHasGoVersion(t *testing.T) {
|
||||
if HasGoVersion("1.21") {
|
||||
if HasGoVersion(util.NewSemVer("1.21")) {
|
||||
t.Error("Expected HasGoVersion(\"1.21\") to be false, but got true")
|
||||
}
|
||||
}
|
||||
|
||||
12
go/extractor/util/BUILD.bazel
generated
12
go/extractor/util/BUILD.bazel
generated
@@ -4,13 +4,21 @@ load("@rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "util",
|
||||
srcs = ["util.go"],
|
||||
srcs = [
|
||||
"semver.go",
|
||||
"util.go",
|
||||
],
|
||||
importpath = "github.com/github/codeql-go/extractor/util",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = ["//go/extractor/vendor/golang.org/x/mod/semver"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "util_test",
|
||||
srcs = ["util_test.go"],
|
||||
srcs = [
|
||||
"semver_test.go",
|
||||
"util_test.go",
|
||||
],
|
||||
embed = [":util"],
|
||||
deps = ["//go/extractor/vendor/golang.org/x/mod/semver"],
|
||||
)
|
||||
|
||||
141
go/extractor/util/semver.go
Normal file
141
go/extractor/util/semver.go
Normal file
@@ -0,0 +1,141 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
|
||||
// A type used to represent values known to be valid semantic versions.
|
||||
type SemVer interface {
|
||||
String() string
|
||||
// Compares this semantic version against the `other`. Returns the following values:
|
||||
//
|
||||
// 0 if both versions are equal.
|
||||
//
|
||||
// -1 if this version is older than the `other`.
|
||||
//
|
||||
// 1 if this version is newer than the `other`.
|
||||
Compare(other SemVer) int
|
||||
// Returns true if this version is newer than the `other`, or false otherwise.
|
||||
IsNewerThan(other SemVer) bool
|
||||
// Returns true if this version is equal to `other` or newer, or false otherwise.
|
||||
IsAtLeast(other SemVer) bool
|
||||
// Returns true if this version is older than the `other`, or false otherwise.
|
||||
IsOlderThan(other SemVer) bool
|
||||
// Returns true if this version is equal to `other` or older, or false otherwise.
|
||||
IsAtMost(other SemVer) bool
|
||||
// Returns the `major.minor` version prefix of the semantic version. For example, "v1.2.3" becomes "v1.2".
|
||||
MajorMinor() SemVer
|
||||
// Renders the semantic version as a standard version string, i.e. without a leading "v".
|
||||
StandardSemVer() string
|
||||
}
|
||||
|
||||
// The internal representation used for values known to be valid semantic versions.
|
||||
//
|
||||
// NOTE: Not exported to prevent invalid values from being constructed.
|
||||
type semVer string
|
||||
|
||||
// Converts the semantic version to a string representation.
|
||||
func (ver semVer) String() string {
|
||||
return string(ver)
|
||||
}
|
||||
|
||||
// Represents `v0.0.0`.
|
||||
func Zero() SemVer {
|
||||
return semVer("v0.0.0")
|
||||
}
|
||||
|
||||
// Constructs a [SemVer] from the given `version` string. The input can be any valid version string
|
||||
// that we commonly deal with. This includes ordinary version strings such as "1.2.3", ones with
|
||||
// the "go" prefix, and ones with the "v" prefix. Go's non-semver-compliant release candidate
|
||||
// versions are also automatically corrected from e.g. "go1.20rc1" to "v1.20-rc1". If given
|
||||
// the empty string, this function return `nil`. Otherwise, for invalid version strings, the function
|
||||
// prints a message to the log and exits the process.
|
||||
//
|
||||
// Note that we deliberately do not format the resulting [SemVer] to be in a `Canonical` representation.
|
||||
// This is because we want to maintain the input version specificity for as long as possible. This is useful
|
||||
// for e.g. `IdentifyEnvironment` where we want to output "1.22" if the project specifies "1.22" as the
|
||||
// required Go version, rather than outputting "1.22.0", which implies a specific patch-level version
|
||||
// when the intention is that any patch-level version of "1.22" is acceptable.
|
||||
func NewSemVer(version string) SemVer {
|
||||
// If the input is the empty string, return `nil` since we use `nil` to represent "no version".
|
||||
if version == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Drop a "go" prefix, if there is one.
|
||||
version = strings.TrimPrefix(version, "go")
|
||||
|
||||
// Go versions don't follow the SemVer format, but the only exception we normally care about
|
||||
// is release candidates; so this is a horrible hack to convert e.g. `1.22rc1` into `1.22-rc1`
|
||||
// which is compatible with the SemVer specification.
|
||||
rcIndex := strings.Index(version, "rc")
|
||||
if rcIndex != -1 {
|
||||
var numeric string
|
||||
prerelease := version[rcIndex:]
|
||||
|
||||
// the version string may already contain a "-";
|
||||
// if it does, drop the "-" since we add it back later
|
||||
if version[rcIndex-1] != '-' {
|
||||
numeric = version[:rcIndex]
|
||||
} else {
|
||||
numeric = version[:rcIndex-1]
|
||||
}
|
||||
|
||||
// add a "v" to the numeric part of the version, if it's not already there
|
||||
if !strings.HasPrefix(numeric, "v") {
|
||||
numeric = "v" + numeric
|
||||
}
|
||||
|
||||
// for the semver library to accept a version containing a prerelease,
|
||||
// the numeric part must be canonical; e.g.. "v0-rc1" is not valid and
|
||||
// must be "v0.0.0-rc1" instead.
|
||||
version = semver.Canonical(numeric) + "-" + prerelease
|
||||
} else if !strings.HasPrefix(version, "v") {
|
||||
// Add the "v" prefix that is required by the `semver` package, if
|
||||
// it's not already there.
|
||||
version = "v" + version
|
||||
}
|
||||
|
||||
// Check that the remaining version string is valid.
|
||||
if !semver.IsValid(version) {
|
||||
log.Fatalf("%s is not a valid version string\n", version)
|
||||
}
|
||||
|
||||
return semVer(version)
|
||||
}
|
||||
|
||||
func (ver semVer) Compare(other SemVer) int {
|
||||
return semver.Compare(string(ver), string(other.String()))
|
||||
}
|
||||
|
||||
func (ver semVer) IsNewerThan(other SemVer) bool {
|
||||
return ver.Compare(other) > 0
|
||||
}
|
||||
|
||||
func (ver semVer) IsAtLeast(other SemVer) bool {
|
||||
return ver.Compare(other) >= 0
|
||||
}
|
||||
|
||||
func (ver semVer) IsOlderThan(other SemVer) bool {
|
||||
return ver.Compare(other) < 0
|
||||
}
|
||||
|
||||
func (ver semVer) IsAtMost(other SemVer) bool {
|
||||
return ver.Compare(other) <= 0
|
||||
}
|
||||
|
||||
func (ver semVer) MajorMinor() SemVer {
|
||||
return semVer(semver.MajorMinor(string(ver)))
|
||||
}
|
||||
|
||||
func (ver semVer) StandardSemVer() string {
|
||||
// Drop the 'v' prefix from the version string.
|
||||
result := string(ver)[1:]
|
||||
|
||||
// Correct the pre-release identifier for use with `setup-go`, if one is present.
|
||||
// This still remains a standard semantic version.
|
||||
return strings.Replace(result, "-rc", "-rc.", 1)
|
||||
}
|
||||
69
go/extractor/util/semver_test.go
Normal file
69
go/extractor/util/semver_test.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
|
||||
func TestNewSemVer(t *testing.T) {
|
||||
type TestPair struct {
|
||||
Input string
|
||||
Expected string
|
||||
}
|
||||
|
||||
// Check the special case for the empty string.
|
||||
result := NewSemVer("")
|
||||
if result != nil {
|
||||
t.Errorf("Expected NewSemVer(\"\") to return nil, but got \"%s\".", result)
|
||||
}
|
||||
|
||||
testData := []TestPair{
|
||||
{"0", "v0"},
|
||||
{"1.0", "v1.0"},
|
||||
{"1.0.2", "v1.0.2"},
|
||||
{"1.20", "v1.20"},
|
||||
{"1.22.3", "v1.22.3"},
|
||||
}
|
||||
|
||||
// prefixes should not affect the result
|
||||
prefixes := []string{"", "go", "v"}
|
||||
// suffixes
|
||||
suffixes := []string{"", "rc1", "-rc1"}
|
||||
|
||||
// Check that we get what we expect for each of the test cases.
|
||||
for _, pair := range testData {
|
||||
for _, prefix := range prefixes {
|
||||
for _, suffix := range suffixes {
|
||||
// combine the input string with the current prefix and suffix
|
||||
input := prefix + pair.Input + suffix
|
||||
result := NewSemVer(input)
|
||||
|
||||
expected := pair.Expected
|
||||
if suffix != "" {
|
||||
expected = semver.Canonical(pair.Expected) + "-rc1"
|
||||
}
|
||||
|
||||
if result.String() != expected {
|
||||
t.Errorf(
|
||||
"Expected NewSemVer(\"%s\") to return \"%s\", but got \"%s\".",
|
||||
input,
|
||||
expected,
|
||||
result,
|
||||
)
|
||||
}
|
||||
|
||||
expected = strings.Replace(expected, "-rc1", "-rc.1", 1)
|
||||
if result.StandardSemVer() != expected[1:] {
|
||||
t.Errorf(
|
||||
"Expected NewSemVer(\"%s\").StandardSemVer() to return \"%s\", but got \"%s\".",
|
||||
input,
|
||||
expected[1:],
|
||||
result.StandardSemVer(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user