diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 323338d4..6b59e6e0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -40,12 +40,13 @@ jobs: # Static checks from this point forward. Only run on one Go version and on # Linux, since it's the fastest platform, and the tools behave the same. - - if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.17.x' + - if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.18.x' run: diff <(echo -n) <(gofmt -d .) - - if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.17.x' + - if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.18.x' run: go vet ./... + # TODO: bump to Go 1.18 once staticcheck supports it - if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.17.x' - uses: dominikh/staticcheck-action@v1.1.0 + uses: dominikh/staticcheck-action@v1 with: version: "2021.1.2" install-go: false @@ -61,7 +62,7 @@ jobs: steps: - uses: actions/setup-go@v3 with: - go-version: 1.17.x + go-version: 1.18.x - uses: actions/checkout@v3 - name: Test run: go test -timeout=15m ./... diff --git a/main.go b/main.go index dc838c13..9b6a85d6 100644 --- a/main.go +++ b/main.go @@ -256,7 +256,10 @@ type errJustExit int func (e errJustExit) Error() string { return fmt.Sprintf("exit: %d", e) } -var goVersionSemver string +// toolchainVersionSemver is a semver-compatible version of the Go toolchain currently +// being used, as reported by "go env GOVERSION". +// Note that the version of Go that built the garble binary might be newer. +var toolchainVersionSemver string func goVersionOK() bool { const ( @@ -264,19 +267,41 @@ func goVersionOK() bool { suggestedGoVersion = "1.17.x" ) - rxVersion := regexp.MustCompile(`go\d+\.\d+(\.\d)?`) - version := rxVersion.FindString(cache.GoEnv.GOVERSION) - if version == "" { + // rxVersion looks for a version like "go1.2" or "go1.2.3" + rxVersion := regexp.MustCompile(`go\d+\.\d+(\.\d+)?`) + + toolchainVersionFull := cache.GoEnv.GOVERSION + toolchainVersion := rxVersion.FindString(cache.GoEnv.GOVERSION) + if toolchainVersion == "" { // Go 1.15.x and older do not have GOVERSION yet. - // We could go the extra mile and fetch it via 'go version', + // We could go the extra mile and fetch it via 'go toolchainVersion', // but we'd have to error anyway. fmt.Fprintf(os.Stderr, "Go version is too old; please upgrade to Go %s or a newer devel version\n", suggestedGoVersion) return false } - goVersionSemver = "v" + strings.TrimPrefix(version, "go") - if semver.Compare(goVersionSemver, minGoVersionSemver) < 0 { - fmt.Fprintf(os.Stderr, "Go version %q is too old; please upgrade to Go %s\n", version, suggestedGoVersion) + toolchainVersionSemver = "v" + strings.TrimPrefix(toolchainVersion, "go") + if semver.Compare(toolchainVersionSemver, minGoVersionSemver) < 0 { + fmt.Fprintf(os.Stderr, "Go version %q is too old; please upgrade to Go %s\n", toolchainVersionFull, suggestedGoVersion) + return false + } + + // Ensure that the version of Go that built the garble binary is equal or + // newer than toolchainVersionSemver. + builtVersionFull := os.Getenv("GARBLE_TEST_GOVERSION") + if builtVersionFull == "" { + builtVersionFull = runtime.Version() + } + builtVersion := rxVersion.FindString(builtVersionFull) + if builtVersion == "" { + // If garble built itself, we don't know what Go version was used. + // Fall back to not performing the check against the toolchain version. + return true + } + builtVersionSemver := "v" + strings.TrimPrefix(builtVersion, "go") + if semver.Compare(builtVersionSemver, toolchainVersionSemver) < 0 { + fmt.Fprintf(os.Stderr, "garble was built with %q and is being used with %q; please rebuild garble with the newer version\n", + builtVersionFull, toolchainVersionFull) return false } @@ -473,7 +498,7 @@ This command wraps "go %s". Below is its help: command, "-trimpath", } - if semver.Compare(goVersionSemver, "v1.18.0") >= 0 { + if semver.Compare(toolchainVersionSemver, "v1.18.0") >= 0 { // TODO: remove the conditional once we drop support for 1.17 goArgs = append(goArgs, "-buildvcs=false") } diff --git a/testdata/scripts/goversion.txt b/testdata/scripts/goversion.txt index 444dfec3..b60e0d39 100644 --- a/testdata/scripts/goversion.txt +++ b/testdata/scripts/goversion.txt @@ -5,39 +5,75 @@ go build -o .bin/go$exe ./fakego env PATH=${WORK}/.bin${:}${PATH} # An empty go version. -env GOVERSION='' +env TOOLCHAIN_GOVERSION='' ! garble build -stderr 'Go version is too old; please upgrade to Go 1.17.x or a newer devel version' +stderr 'Go version is too old; please upgrade to Go 1\.17\.x or a newer devel version' # We should error on a devel version that's too old. # Note that they lacked the "goN.M-" prefix. -env GOVERSION='devel +afb5fca Sun Aug 07 00:00:00 2020 +0000' +env TOOLCHAIN_GOVERSION='devel +afb5fca Sun Aug 07 00:00:00 2020 +0000' ! garble build -stderr 'Go version is too old; please upgrade to Go 1.17.x or a newer devel version' +stderr 'Go version is too old; please upgrade to Go 1\.17\.x or a newer devel version' # Another form of old version; with an old "goN.M-" prefix. -env GOVERSION='devel go1.15-afb5fca Sun Aug 07 00:00:00 2020 +0000' +env TOOLCHAIN_GOVERSION='devel go1.15-afb5fca Sun Aug 07 00:00:00 2020 +0000' ! garble build -stderr 'Go version "go1.15" is too old; please upgrade to Go 1.17.x' +stderr 'Go version "devel go1\.15-.*2020.*" is too old; please upgrade to Go 1\.17\.x' -# A current devel version should be fine -env GOVERSION='devel go1.18-ad97d204f0 Sun Sep 12 16:46:58 2021 +0000' +# A current devel version should be fine. +# Note that we don't look at devel version timestamps. +env GARBLE_TEST_GOVERSION='go1.18' +env TOOLCHAIN_GOVERSION='devel go1.18-ad97d204f0 Sun Sep 12 16:46:58 2021 +0000' ! garble build stderr 'mocking the real build' # We should error on a stable version that's too old. -env GOVERSION='go1.14' +env TOOLCHAIN_GOVERSION='go1.14' ! garble build -stderr 'Go version "go1.14" is too old; please upgrade to Go 1.17.x' +stderr 'Go version "go1\.14" is too old; please upgrade to Go 1\.17\.x' ! stderr 'or a newer devel version' # We should accept a future stable version. -env GOVERSION='go1.18.2' +# Note that we need to bump the version of Go that supposedly built it, too. +env GARBLE_TEST_GOVERSION='go1.28.2' +env TOOLCHAIN_GOVERSION='go1.28.2' ! garble build stderr 'mocking the real build' # We should accept custom devel strings. -env GOVERSION='devel go1.18-somecustomversion' +env TOOLCHAIN_GOVERSION='devel go1.18-somecustomversion' +! garble build +stderr 'mocking the real build' + +# The current toolchain may be older than the one that built garble. +env GARBLE_TEST_GOVERSION='go1.18' +env TOOLCHAIN_GOVERSION='go1.17.3' +! garble build +stderr 'mocking the real build' + +# The current toolchain may be equal to the one that built garble. +env GARBLE_TEST_GOVERSION='devel go1.19-6673d5d701 Sun Mar 20 16:05:03 2022 +0000' +env TOOLCHAIN_GOVERSION='devel go1.19-6673d5d701 Sun Mar 20 16:05:03 2022 +0000' +! garble build +stderr 'mocking the real build' + +# The current toolchain must not be newer than the one that built garble. +env GARBLE_TEST_GOVERSION='go1.17' +env TOOLCHAIN_GOVERSION='go1.18.1' +! garble build +stderr 'garble was built with "go1\.17" and is being used with "go1\.18\.1"; please rebuild garble with the newer version' + +# We'll error even if the difference is a minor (bugfix) level. +# In practice it probably wouldn't matter, but in theory it could still lead to tricky bugs. +env GARBLE_TEST_GOVERSION='go1.18.11' +env TOOLCHAIN_GOVERSION='go1.18.14' +! garble build +stderr 'garble was built with "go1\.18\.11" and is being used with "go1\.18\.14"; please rebuild garble with the newer version' + +# If garble builds itself and is then used, it won't know what version built it. +# As a fallback, we drop the comparison against the toolchain's version. +env GARBLE_TEST_GOVERSION='bogus version' +env TOOLCHAIN_GOVERSION='go1.17.3' ! garble build stderr 'mocking the real build' @@ -64,7 +100,7 @@ func main() { enc, _ := json.Marshal(struct{ GOVERSION string } { - GOVERSION: os.Getenv("GOVERSION"), + GOVERSION: os.Getenv("TOOLCHAIN_GOVERSION"), }) fmt.Printf("%s\n", enc) return