diff --git a/go/extractor/autobuilder/BUILD.bazel b/go/extractor/autobuilder/BUILD.bazel index e40dc3a03215..17acd6afb541 100644 --- a/go/extractor/autobuilder/BUILD.bazel +++ b/go/extractor/autobuilder/BUILD.bazel @@ -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"], ) diff --git a/go/extractor/autobuilder/build-environment.go b/go/extractor/autobuilder/build-environment.go index 21ba9a5aa9a0..07468862fa85 100644 --- a/go/extractor/autobuilder/build-environment.go +++ b/go/extractor/autobuilder/build-environment.go @@ -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.String() + `" } }` } _, err := fmt.Fprint(os.Stdout, content) @@ -273,14 +271,10 @@ 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.String(), greatestGoVersion != nil + 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:] - } + v.goEnvVersion = toolchain.GetEnvGoSemVer() // Determine which version of Go we should recommend to install. msg, versionToInstall := getVersionToInstall(v) diff --git a/go/extractor/autobuilder/build-environment_test.go b/go/extractor/autobuilder/build-environment_test.go index 994381b0b4c7..382e3aa2914a 100644 --- a/go/extractor/autobuilder/build-environment_test.go +++ b/go/extractor/autobuilder/build-environment_test.go @@ -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) } }