-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #15411 from github/mbg/go/refactor-go-autobuilder
- Loading branch information
Showing
7 changed files
with
657 additions
and
616 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,283 @@ | ||
package autobuilder | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"os" | ||
|
||
"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" | ||
) | ||
|
||
const minGoVersion = "1.11" | ||
const maxGoVersion = "1.21" | ||
|
||
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. | ||
} | ||
|
||
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) | ||
} | ||
|
||
// 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 | ||
} | ||
|
||
// 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 | ||
} | ||
|
||
// 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 { | ||
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 { | ||
// 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 + ")." | ||
version = maxGoVersion | ||
diagnostics.EmitNoGoModAndNoGoEnv(msg) | ||
} else if outsideSupportedRange(v.goEnvVersion) { | ||
// The Go version installed in the environment is not supported. 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 `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 + | ||
")." | ||
version = maxGoVersion | ||
diagnostics.EmitNoGoModAndGoEnvUnsupported(msg) | ||
} else { | ||
// 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 " + | ||
"environment is supported. Not requesting any version of Go." | ||
version = "" | ||
diagnostics.EmitNoGoModAndGoEnvSupported(msg) | ||
} | ||
|
||
return msg, version | ||
} | ||
|
||
// 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 { | ||
// 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 + | ||
"). No version of Go installed. Requesting the maximum supported version of Go (" + | ||
maxGoVersion + ")." | ||
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 + | ||
"). Not requesting any version of Go." | ||
version = "" | ||
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 + ")." | ||
version = maxGoVersion | ||
diagnostics.EmitGoModVersionTooHighAndEnvVersionTooLow(msg) | ||
} else if semver.Compare("v"+maxGoVersion, "v"+v.goEnvVersion) > 0 { | ||
// 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 + ")." | ||
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 + | ||
"). Not requesting any version of Go." | ||
version = "" | ||
diagnostics.EmitGoModVersionTooHighAndEnvVersionMax(msg) | ||
} | ||
|
||
return msg, version | ||
} | ||
|
||
// 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 { | ||
// 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 + | ||
"). No version of Go installed. Requesting the minimum supported version of Go (" + | ||
minGoVersion + ")." | ||
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 + ")." | ||
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 + | ||
") is supported and is high enough for the version found in the `go.mod` file (" + | ||
v.goModVersion + "). Not requesting any version of Go." | ||
version = "" | ||
diagnostics.EmitGoModVersionTooLowAndEnvVersionSupported(msg) | ||
} | ||
|
||
return msg, version | ||
} | ||
|
||
// 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 { | ||
// 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 + ")." | ||
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 + "). " + | ||
"Requesting the version of Go from the `go.mod` file (" + | ||
v.goModVersion + ")." | ||
version = v.goModVersion | ||
diagnostics.EmitGoModVersionSupportedAndGoEnvUnsupported(msg) | ||
} else if semver.Compare("v"+v.goModVersion, "v"+v.goEnvVersion) > 0 { | ||
// 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 + ")." | ||
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 + | ||
") is supported and is high enough for the version found in the `go.mod` file (" + | ||
v.goModVersion + "). Not requesting any version of Go." | ||
version = "" | ||
diagnostics.EmitGoModVersionSupportedLowerEqualGoEnv(msg) | ||
} | ||
|
||
return msg, version | ||
} | ||
|
||
// Check the versions of Go found in the environment and in the `go.mod` file, and return a | ||
// version to install. If the version is the empty string then no installation is required. | ||
// We never return a version of Go that is outside of the supported range. | ||
// | ||
// +-----------------------+-----------------------+-----------------------+-----------------------------------------------------+------------------------------------------------+ | ||
// | Found in go.mod > | *None* | *Below min supported* | *In supported range* | *Above max supported | | ||
// | Installed \/ | | | | | | ||
// |-----------------------|-----------------------|-----------------------|-----------------------------------------------------|------------------------------------------------| | ||
// | *None* | Install max supported | Install min supported | Install version from go.mod | Install max supported | | ||
// | *Below min supported* | Install max supported | Install min supported | Install version from go.mod | Install max supported | | ||
// | *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 { | ||
return getVersionWhenGoModVersionNotFound(v) | ||
} | ||
|
||
if aboveSupportedRange(v.goModVersion) { | ||
return getVersionWhenGoModVersionTooHigh(v) | ||
} | ||
|
||
if belowSupportedRange(v.goModVersion) { | ||
return getVersionWhenGoModVersionTooLow(v) | ||
} | ||
|
||
return getVersionWhenGoModVersionSupported(v) | ||
} | ||
|
||
// Output some JSON to stdout specifying the version of Go to install, unless `version` is the | ||
// empty string. | ||
func outputEnvironmentJson(version string) { | ||
var content string | ||
if version == "" { | ||
content = `{ "go": {} }` | ||
} else { | ||
content = `{ "go": { "version": "` + version + `" } }` | ||
} | ||
_, err := fmt.Fprint(os.Stdout, content) | ||
|
||
if err != nil { | ||
log.Println("Failed to write environment json to stdout: ") | ||
log.Println(err) | ||
} | ||
} | ||
|
||
// Get the version of Go to install and output it to stdout as json. | ||
func IdentifyEnvironment() { | ||
var v versionInfo | ||
buildInfo := project.GetBuildInfo(false) | ||
goVersionInfo := project.TryReadGoDirective(buildInfo) | ||
v.goModVersion, v.goModVersionFound = goVersionInfo.Version, goVersionInfo.Found | ||
|
||
v.goEnvVersionFound = toolchain.IsInstalled() | ||
if v.goEnvVersionFound { | ||
v.goEnvVersion = toolchain.GetEnvGoVersion()[2:] | ||
} | ||
|
||
msg, versionToInstall := getVersionToInstall(v) | ||
log.Println(msg) | ||
|
||
outputEnvironmentJson(versionToInstall) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package autobuilder | ||
|
||
import "testing" | ||
|
||
func TestGetVersionToInstall(t *testing.T) { | ||
tests := map[versionInfo]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}: "", | ||
|
||
// 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}: "", | ||
|
||
// 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}: "", | ||
|
||
// 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", | ||
// go.mod version > go installation version | ||
{"1.20", true, "1.11.13", true}: "1.20", | ||
{"1.20", true, "1.12", true}: "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}: "", | ||
} | ||
for input, expected := range tests { | ||
_, actual := getVersionToInstall(input) | ||
if actual != expected { | ||
t.Errorf("Expected getVersionToInstall(\"%s\") to be \"%s\", but got \"%s\".", input, expected, actual) | ||
} | ||
} | ||
} |
Oops, something went wrong.