aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRuss Cox <rsc@golang.org>2023-06-04 22:34:52 -0400
committerRuss Cox <rsc@golang.org>2023-06-06 19:18:46 +0000
commit35268a996052ca8716caf94467c2ed61140f3862 (patch)
tree4e9b43b9f749a5ead6d098bb825a32c7d4e57219
parentbf016520e2575f8eb5e37634a4ed18c8c8044859 (diff)
downloadgo-35268a996052ca8716caf94467c2ed61140f3862.tar.gz
go-35268a996052ca8716caf94467c2ed61140f3862.zip
cmd/go: additional doc-inspired tests and bug fixes
Additional tests and bug fixes realized while writing go.dev/doc/gotoolchain (CL 500775). - Handle go get toolchain@go1.22 (resolve to latest patch release, same as go get go@1.22). (See modload/query.go and gover/mod.go.) - Handle go get go@patch toolchain@patch. (See modload/query.go and gover/mod.go.) - Remove prefix-goVERSION-suffix form for toolchain name, standardizing on goVERSION-suffix. I have no good explanation for having two forms, so simplify to one. (See vendor and gover.) - Fail toolchain downloads when GOSUMDB=off. Because toolchain downloads cannot always be predicted (especially during switching rather than selection), they cannot be listed in go.sum. We rely on the checksum database for integrity of the download, especially if proxied. If the checksum database is disabled, this integrity check won't happen, so fail toolchain downloads. (See modfetch/sumdb.go and script/gotoolchain_net.txt) - Use names from documentation in package toolchain (Select, Switch; SwitchTo renamed to Exec to avoid both names; reqs.go renamed to switch.go; toolchain.go renamed to select.go.) - Make "go env GOTOOLCHAIN" and "go env -w GOTOOLCHAIN" work even when GOTOOLCHAIN is misconfigured. (See special case at top of Select in select.go.) - Clarify what goInstallVersion does (report whether this is go install or go run pkg@version) and explain the potential version switch more clearly. Use the Switcher directly instead of reimplementing it. (See select.go.) - Document go@ and toolchain@ forms in go help get, linking to go.dev/doc/toolchain. (See modget/get.go.) - Update URL of documentation in $GOROOT/go.env. For #57001. Change-Id: I895ef3519ff95db8710ed23b36ebaf4f648120cb Reviewed-on: https://go-review.googlesource.com/c/go/+/500797 Reviewed-by: Michael Matloob <matloob@golang.org> Run-TryBot: Russ Cox <rsc@golang.org> TryBot-Bypass: Russ Cox <rsc@golang.org>
-rw-r--r--go.env2
-rw-r--r--src/cmd/go/alldocs.go11
-rw-r--r--src/cmd/go/internal/gover/mod.go15
-rw-r--r--src/cmd/go/internal/gover/toolchain.go5
-rw-r--r--src/cmd/go/internal/gover/toolchain_test.go4
-rw-r--r--src/cmd/go/internal/modfetch/sumdb.go12
-rw-r--r--src/cmd/go/internal/modget/get.go11
-rw-r--r--src/cmd/go/internal/modload/query.go2
-rw-r--r--src/cmd/go/internal/toolchain/select.go (renamed from src/cmd/go/internal/toolchain/toolchain.go)235
-rw-r--r--src/cmd/go/internal/toolchain/switch.go (renamed from src/cmd/go/internal/toolchain/reqs.go)4
-rw-r--r--src/cmd/go/main.go2
-rw-r--r--src/cmd/go/testdata/script/gotoolchain_local.txt96
-rw-r--r--src/cmd/go/testdata/script/gotoolchain_net.txt29
-rw-r--r--src/cmd/go/testdata/script/mod_edit_toolchain.txt4
-rw-r--r--src/cmd/go/testdata/script/mod_get_toolchain.txt102
-rw-r--r--src/cmd/go/testdata/script/work_edit_toolchain.txt4
16 files changed, 359 insertions, 179 deletions
diff --git a/go.env b/go.env
index 9bab8ffd73..6ff2b921d4 100644
--- a/go.env
+++ b/go.env
@@ -8,5 +8,5 @@ GOPROXY=https://proxy.golang.org,direct
GOSUMDB=sum.golang.org
# Automatically download newer toolchains as directed by go.mod files.
-# See https://go.dev/s/gotoolchain for details.
+# See https://go.dev/doc/toolchain for details.
GOTOOLCHAIN=auto
diff --git a/src/cmd/go/alldocs.go b/src/cmd/go/alldocs.go
index 7ef763d6be..05ee094ea7 100644
--- a/src/cmd/go/alldocs.go
+++ b/src/cmd/go/alldocs.go
@@ -671,6 +671,14 @@
//
// go get example.com/mod@none
//
+// To upgrade the minimum required Go version to the latest released Go version:
+//
+// go get go@latest
+//
+// To upgrade the Go toolchain to the latest patch release of the current Go toolchain:
+//
+// go get toolchain@patch
+//
// See https://golang.org/ref/mod#go-get for details.
//
// In earlier versions of Go, 'go get' was used to build and install packages.
@@ -705,6 +713,9 @@
//
// For more about modules, see https://golang.org/ref/mod.
//
+// For more about using 'go get' to update the minimum Go version and
+// suggested Go toolchain, see https://go.dev/doc/toolchain.
+//
// For more about specifying packages, see 'go help packages'.
//
// This text describes the behavior of get using modules to manage source
diff --git a/src/cmd/go/internal/gover/mod.go b/src/cmd/go/internal/gover/mod.go
index 18e5635cdf..d3cc17068d 100644
--- a/src/cmd/go/internal/gover/mod.go
+++ b/src/cmd/go/internal/gover/mod.go
@@ -84,6 +84,9 @@ func ModIsValid(path, vers string) bool {
// The caller is assumed to have checked that ModIsValid(path, vers) is true.
func ModIsPrefix(path, vers string) bool {
if IsToolchain(path) {
+ if path == "toolchain" {
+ return IsLang(FromToolchain(vers))
+ }
return IsLang(vers)
}
// Semver
@@ -110,3 +113,15 @@ func ModIsPrerelease(path, vers string) bool {
}
return semver.Prerelease(vers) != ""
}
+
+// ModMajorMinor returns the "major.minor" truncation of the version v,
+// for use as a prefix in "@patch" queries.
+func ModMajorMinor(path, vers string) string {
+ if IsToolchain(path) {
+ if path == "toolchain" {
+ return "go" + Lang(FromToolchain(vers))
+ }
+ return Lang(vers)
+ }
+ return semver.MajorMinor(vers)
+}
diff --git a/src/cmd/go/internal/gover/toolchain.go b/src/cmd/go/internal/gover/toolchain.go
index bd0d52ad84..efa2de46a5 100644
--- a/src/cmd/go/internal/gover/toolchain.go
+++ b/src/cmd/go/internal/gover/toolchain.go
@@ -14,20 +14,17 @@ import (
// FromToolchain returns the Go version for the named toolchain,
// derived from the name itself (not by running the toolchain).
-// A toolchain is named "goVERSION" or "anything-goVERSION".
+// A toolchain is named "goVERSION".
// A suffix after the VERSION introduced by a +, -, space, or tab is removed.
// Examples:
//
// FromToolchain("go1.2.3") == "1.2.3"
// FromToolchain("go1.2.3-bigcorp") == "1.2.3"
-// FromToolchain("gccgo-go1.23rc4") == "1.23rc4"
// FromToolchain("invalid") == ""
func FromToolchain(name string) string {
var v string
if strings.HasPrefix(name, "go") {
v = name[2:]
- } else if i := strings.Index(name, "-go"); i >= 0 {
- v = name[i+3:]
} else {
return ""
}
diff --git a/src/cmd/go/internal/gover/toolchain_test.go b/src/cmd/go/internal/gover/toolchain_test.go
index 7d05f1d0c3..d1c22fbc37 100644
--- a/src/cmd/go/internal/gover/toolchain_test.go
+++ b/src/cmd/go/internal/gover/toolchain_test.go
@@ -14,6 +14,6 @@ var fromToolchainTests = []testCase1[string, string]{
{"go1.2.3+bigcorp", ""},
{"go1.2.3-bigcorp", "1.2.3"},
{"go1.2.3-bigcorp more text", "1.2.3"},
- {"gccgo-go1.23rc4", "1.23rc4"},
- {"gccgo-go1.23rc4-bigdwarf", "1.23rc4"},
+ {"gccgo-go1.23rc4", ""},
+ {"gccgo-go1.23rc4-bigdwarf", ""},
}
diff --git a/src/cmd/go/internal/modfetch/sumdb.go b/src/cmd/go/internal/modfetch/sumdb.go
index 492b03bd84..6e60e7d976 100644
--- a/src/cmd/go/internal/modfetch/sumdb.go
+++ b/src/cmd/go/internal/modfetch/sumdb.go
@@ -33,6 +33,14 @@ import (
// useSumDB reports whether to use the Go checksum database for the given module.
func useSumDB(mod module.Version) bool {
+ if mod.Path == "golang.org/toolchain" {
+ // Downloaded toolchains cannot be listed in go.sum,
+ // so we require checksum database lookups even if
+ // GOSUMDB=off or GONOSUMDB matches the pattern.
+ // If GOSUMDB=off, then the eventual lookup will fail
+ // with a good error message.
+ return true
+ }
return cfg.GOSUMDB != "off" && !module.MatchPrefixPatterns(cfg.GONOSUMDB, mod.Path)
}
@@ -70,6 +78,10 @@ func dbDial() (dbName string, db *sumdb.Client, err error) {
gosumdb = "sum.golang.org https://sum.golang.google.cn"
}
+ if gosumdb == "off" {
+ return "", nil, fmt.Errorf("checksum database disabled by GOSUMDB=off")
+ }
+
key := strings.Fields(gosumdb)
if len(key) >= 1 {
if k := knownGOSUMDB[key[0]]; k != "" {
diff --git a/src/cmd/go/internal/modget/get.go b/src/cmd/go/internal/modget/get.go
index 43708a3cab..5d9eb70489 100644
--- a/src/cmd/go/internal/modget/get.go
+++ b/src/cmd/go/internal/modget/get.go
@@ -72,6 +72,14 @@ To remove a dependency on a module and downgrade modules that require it:
go get example.com/mod@none
+To upgrade the minimum required Go version to the latest released Go version:
+
+ go get go@latest
+
+To upgrade the Go toolchain to the latest patch release of the current Go toolchain:
+
+ go get toolchain@patch
+
See https://golang.org/ref/mod#go-get for details.
In earlier versions of Go, 'go get' was used to build and install packages.
@@ -106,6 +114,9 @@ from a repository.
For more about modules, see https://golang.org/ref/mod.
+For more about using 'go get' to update the minimum Go version and
+suggested Go toolchain, see https://go.dev/doc/toolchain.
+
For more about specifying packages, see 'go help packages'.
This text describes the behavior of get using modules to manage source
diff --git a/src/cmd/go/internal/modload/query.go b/src/cmd/go/internal/modload/query.go
index 945b6e1642..81c32d27a1 100644
--- a/src/cmd/go/internal/modload/query.go
+++ b/src/cmd/go/internal/modload/query.go
@@ -393,7 +393,7 @@ func newQueryMatcher(path string, query, current string, allowed AllowedFunc) (*
qm.mayUseLatest = true
} else {
qm.mayUseLatest = module.IsPseudoVersion(current)
- qm.prefix = semver.MajorMinor(current) + "."
+ qm.prefix = gover.ModMajorMinor(qm.path, current) + "."
qm.filter = func(mv string) bool { return gover.ModCompare(qm.path, mv, current) >= 0 }
}
diff --git a/src/cmd/go/internal/toolchain/toolchain.go b/src/cmd/go/internal/toolchain/select.go
index e6ff584480..6aac8c1eaa 100644
--- a/src/cmd/go/internal/toolchain/toolchain.go
+++ b/src/cmd/go/internal/toolchain/select.go
@@ -81,11 +81,12 @@ func FilterEnv(env []string) []string {
return out
}
-// Switch invokes a different Go toolchain if directed by
+// Select invokes a different Go toolchain if directed by
// the GOTOOLCHAIN environment variable or the user's configuration
// or go.mod file.
// It must be called early in startup.
-func Switch() {
+// See https://go.dev/doc/toolchain#select.
+func Select() {
log.SetPrefix("go: ")
defer log.SetPrefix("")
@@ -93,6 +94,21 @@ func Switch() {
return
}
+ // As a special case, let "go env GOTOOLCHAIN" and "go env -w GOTOOLCHAIN=..."
+ // be handled by the local toolchain, since an older toolchain may not understand it.
+ // This provides an easy way out of "go env -w GOTOOLCHAIN=go1.19" and makes
+ // sure that "go env GOTOOLCHAIN" always prints the local go command's interpretation of it.
+ // We look for these specific command lines in order to avoid mishandling
+ //
+ // GOTOOLCHAIN=go1.999 go env -newflag GOTOOLCHAIN
+ //
+ // where -newflag is a flag known to Go 1.999 but not known to us.
+ if (len(os.Args) == 3 && os.Args[1] == "env" && os.Args[2] == "GOTOOLCHAIN") ||
+ (len(os.Args) == 4 && os.Args[1] == "env" && os.Args[2] == "-w" && strings.HasPrefix(os.Args[3], "GOTOOLCHAIN=")) {
+ return
+ }
+
+ // Interpret GOTOOLCHAIN to select the Go toolchain to run.
gotoolchain := cfg.Getenv("GOTOOLCHAIN")
gover.Startup.GOTOOLCHAIN = gotoolchain
if gotoolchain == "" {
@@ -105,77 +121,70 @@ func Switch() {
return
}
+ // Note: minToolchain is what https://go.dev/doc/toolchain#select calls the default toolchain.
minToolchain := gover.LocalToolchain()
minVers := gover.Local()
- if min, mode, ok := strings.Cut(gotoolchain, "+"); ok { // go1.2.3+auto
- v := gover.FromToolchain(min)
- if v == "" {
- base.Fatalf("invalid GOTOOLCHAIN %q: invalid minimum toolchain %q", gotoolchain, min)
+ var mode string
+ if gotoolchain == "auto" {
+ mode = "auto"
+ } else if gotoolchain == "path" {
+ mode = "path"
+ } else {
+ min, suffix, plus := strings.Cut(gotoolchain, "+") // go1.2.3+auto
+ if min != "local" {
+ v := gover.FromToolchain(gotoolchain)
+ if v == "" {
+ if plus {
+ base.Fatalf("invalid GOTOOLCHAIN %q: invalid minimum toolchain %q", gotoolchain, min)
+ }
+ base.Fatalf("invalid GOTOOLCHAIN %q", gotoolchain)
+ }
+ minToolchain = min
+ minVers = v
}
- minToolchain = min
- minVers = v
- if mode != "auto" && mode != "path" {
+ if plus && suffix != "auto" && suffix != "path" {
base.Fatalf("invalid GOTOOLCHAIN %q: only version suffixes are +auto and +path", gotoolchain)
}
- gotoolchain = mode
- }
-
- if gotoolchain == "auto" || gotoolchain == "path" {
- gotoolchain = minToolchain
-
- // Locate and read go.mod or go.work.
- // For go install m@v, it's the installed module's go.mod.
- if m, goVers, ok := goInstallVersion(); ok {
- if gover.Compare(goVers, minVers) > 0 {
- // Always print, because otherwise there's no way for the user to know
- // that a non-default toolchain version is being used here.
- // (Normally you can run "go version", but go install m@v ignores the
- // context that "go version" works in.)
- var err error
- gotoolchain, err = NewerToolchain(context.Background(), goVers)
- if err != nil {
- fmt.Fprintf(os.Stderr, "go: %v\n", err)
- gotoolchain = "go" + goVers
- }
- fmt.Fprintf(os.Stderr, "go: using %s for %v\n", gotoolchain, m)
- }
+ mode = suffix
+ }
+
+ gotoolchain = minToolchain
+ if (mode == "auto" || mode == "path") && !goInstallVersion() {
+ // Read go.mod to find new minimum and suggested toolchain.
+ file, goVers, toolchain := modGoToolchain()
+ gover.Startup.AutoFile = file
+ if toolchain == "default" {
+ // "default" means always use the default toolchain,
+ // which is already set, so nothing to do here.
+ // Note that if we have Go 1.21 installed originally,
+ // GOTOOLCHAIN=go1.30.0+auto or GOTOOLCHAIN=go1.30.0,
+ // and the go.mod says "toolchain default", we use Go 1.30, not Go 1.21.
+ // That is, default overrides the "auto" part of the calculation
+ // but not the minimum that the user has set.
+ // Of course, if the go.mod also says "go 1.35", using Go 1.30
+ // will provoke an error about the toolchain being too old.
+ // That's what people who use toolchain default want:
+ // only ever use the toolchain configured by the user
+ // (including its environment and go env -w file).
+ gover.Startup.AutoToolchain = toolchain
} else {
- file, goVers, toolchain := modGoToolchain()
- gover.Startup.AutoFile = file
- if toolchain == "local" {
- // Local means always use the default local toolchain,
- // which is already set, so nothing to do here.
- // Note that if we have Go 1.21 installed originally,
- // GOTOOLCHAIN=go1.30.0+auto or GOTOOLCHAIN=go1.30.0,
- // and the go.mod says "toolchain local", we use Go 1.30, not Go 1.21.
- // That is, local overrides the "auto" part of the calculation
- // but not the minimum that the user has set.
- // Of course, if the go.mod also says "go 1.35", using Go 1.30
- // will provoke an error about the toolchain being too old.
- // That's what people who use toolchain local want:
- // only ever use the toolchain configured in the local system
- // (including its environment and go env -w file).
- gover.Startup.AutoToolchain = toolchain
- gotoolchain = "local"
- } else {
- if toolchain != "" {
- // Accept toolchain only if it is >= our min.
- toolVers := gover.FromToolchain(toolchain)
- if toolVers == "" || (!strings.HasPrefix(toolchain, "go") && !strings.Contains(toolchain, "-go")) {
- base.Fatalf("invalid toolchain %q in %s", toolchain, base.ShortPath(file))
- }
- if gover.Compare(toolVers, minVers) >= 0 {
- gotoolchain = toolchain
- minVers = toolVers
- gover.Startup.AutoToolchain = toolchain
- }
+ if toolchain != "" {
+ // Accept toolchain only if it is >= our min.
+ toolVers := gover.FromToolchain(toolchain)
+ if toolVers == "" || (!strings.HasPrefix(toolchain, "go") && !strings.Contains(toolchain, "-go")) {
+ base.Fatalf("invalid toolchain %q in %s", toolchain, base.ShortPath(file))
}
- if gover.Compare(goVers, minVers) > 0 {
- gotoolchain = "go" + goVers
- gover.Startup.AutoGoVersion = goVers
- gover.Startup.AutoToolchain = "" // in case we are overriding it for being too old
+ if gover.Compare(toolVers, minVers) >= 0 {
+ gotoolchain = toolchain
+ minVers = toolVers
+ gover.Startup.AutoToolchain = toolchain
}
}
+ if gover.Compare(goVers, minVers) > 0 {
+ gotoolchain = "go" + goVers
+ gover.Startup.AutoGoVersion = goVers
+ gover.Startup.AutoToolchain = "" // in case we are overriding it for being too old
+ }
}
}
@@ -219,11 +228,12 @@ func Switch() {
base.Fatalf("invalid GOTOOLCHAIN %q", gotoolchain)
}
- SwitchTo(gotoolchain)
+ Exec(gotoolchain)
}
// NewerToolchain returns the name of the toolchain to use when we need
-// to reinvoke a newer toolchain that must support at least the given Go version.
+// to switch to a newer toolchain that must support at least the given Go version.
+// See https://go.dev/doc/toolchain#switch.
//
// If the latest major release is 1.N.0, we use the latest patch release of 1.(N-1) if that's >= version.
// Otherwise we use the latest 1.N if that's allowed.
@@ -345,14 +355,14 @@ func HasPath() bool {
//
// "switch" - simulate version switches by reinvoking the test go binary with a different TESTGO_VERSION.
// "mismatch" - like "switch" but forget to set TESTGO_VERSION, so it looks like we invoked a mismatched toolchain
-// "loop" - like "switch" but
+// "loop" - like "mismatch" but forget the target check, causing a toolchain switching loop
var TestVersionSwitch string
-// SwitchTo invokes the specified Go toolchain or else prints an error and exits the process.
-// If $GOTOOLCHAIN is set to path or min+path, SwitchTo only considers the PATH
-// as a source of Go toolchains. Otherwise SwitchTo tries the PATH but then downloads
+// Exec invokes the specified Go toolchain or else prints an error and exits the process.
+// If $GOTOOLCHAIN is set to path or min+path, Exec only considers the PATH
+// as a source of Go toolchains. Otherwise Exec tries the PATH but then downloads
// a toolchain if necessary.
-func SwitchTo(gotoolchain string) {
+func Exec(gotoolchain string) {
log.SetPrefix("go: ")
count, _ := strconv.Atoi(os.Getenv(countEnv))
@@ -496,28 +506,32 @@ func modGoToolchain() (file, goVers, toolchain string) {
return file, gover.GoModLookup(data, "go"), gover.GoModLookup(data, "toolchain")
}
-// goInstallVersion looks at the command line to see if it is go install m@v or go run m@v.
-// If so, it returns the m@v and the go version from that module's go.mod.
-func goInstallVersion() (m module.Version, goVers string, found bool) {
+// goInstallVersion reports whether the command line is go install m@v or go run m@v.
+// If so, Select must not read the go.mod or go.work file in "auto" or "path" mode.
+func goInstallVersion() bool {
// Note: We assume there are no flags between 'go' and 'install' or 'run'.
// During testing there are some debugging flags that are accepted
// in that position, but in production go binaries there are not.
if len(os.Args) < 3 || (os.Args[1] != "install" && os.Args[1] != "run") {
- return module.Version{}, "", false
+ return false
}
+ // Check for pkg@version.
var arg string
switch os.Args[1] {
+ default:
+ return false
case "install":
- // Cannot parse 'go install' command line precisely, because there
- // may be new flags we don't know about. Instead, assume the final
- // argument is a pkg@version we can use.
+ // We would like to let 'go install -newflag pkg@version' work even
+ // across a toolchain switch. To make that work, assume the pkg@version
+ // is the last argument and skip the flag parsing.
arg = os.Args[len(os.Args)-1]
case "run":
- // For run, the pkg@version can be anywhere on the command line.
- // We don't know the flags, so we can't strictly speaking do this correctly.
- // We do the best we can by interrogating the CmdRun flags and assume
- // that any unknown flag does not take an argument.
+ // For run, the pkg@version can be anywhere on the command line,
+ // because it is preceded by run flags and followed by arguments to the
+ // program being run. To handle that precisely, we have to interpret the
+ // flags a little bit, to know whether each flag takes an optional argument.
+ // We can still allow unknown flags as long as they have an explicit =value.
args := os.Args[2:]
for i := 0; i < len(args); i++ {
a := args[i]
@@ -526,19 +540,21 @@ func goInstallVersion() (m module.Version, goVers string, found bool) {
break
}
if a == "-" {
- break
+ // non-flag but also non-pkg@version
+ return false
}
if a == "--" {
- if i+1 < len(args) {
- arg = args[i+1]
+ if i+1 >= len(args) {
+ return false
}
+ arg = args[i+1]
break
}
a = strings.TrimPrefix(a, "-")
a = strings.TrimPrefix(a, "-")
if strings.HasPrefix(a, "-") {
- // non-flag but also non-m@v
- break
+ // non-flag but also non-pkg@version
+ return false
}
if strings.Contains(a, "=") {
// already has value
@@ -546,8 +562,8 @@ func goInstallVersion() (m module.Version, goVers string, found bool) {
}
f := run.CmdRun.Flag.Lookup(a)
if f == nil {
- // Unknown flag. Assume it doesn't take a value: best we can do.
- continue
+ // Unknown flag. Give up. The command is going to fail in flag parsing.
+ return false
}
if bf, ok := f.Value.(interface{ IsBoolFlag() bool }); ok && bf.IsBoolFlag() {
// Does not take value.
@@ -557,13 +573,24 @@ func goInstallVersion() (m module.Version, goVers string, found bool) {
}
}
if !strings.Contains(arg, "@") || build.IsLocalImport(arg) || filepath.IsAbs(arg) {
- return module.Version{}, "", false
+ return false
}
- m.Path, m.Version, _ = strings.Cut(arg, "@")
- if m.Path == "" || m.Version == "" || gover.IsToolchain(m.Path) {
- return module.Version{}, "", false
+ path, version, _ := strings.Cut(arg, "@")
+ if path == "" || version == "" || gover.IsToolchain(path) {
+ return false
}
+ // It would be correct to simply return true here, bypassing use
+ // of the current go.mod or go.work, and let "go run" or "go install"
+ // do the rest, including a toolchain switch.
+ // Our goal instead is, since we have gone to the trouble of handling
+ // unknown flags to some degree, to run the switch now, so that
+ // these commands can switch to a newer toolchain directed by the
+ // go.mod which may actually understand the flag.
+ // This was brought up during the go.dev/issue/57001 proposal discussion
+ // and may end up being common in self-contained "go install" or "go run"
+ // command lines if we add new flags in the future.
+
// Set up modules without an explicit go.mod, to download go.mod.
modload.ForceUseModules = true
modload.RootMode = modload.NoRoot
@@ -573,21 +600,17 @@ func goInstallVersion() (m module.Version, goVers string, found bool) {
// See internal/load.PackagesAndErrorsOutsideModule
ctx := context.Background()
allowed := modload.CheckAllowed
- if modload.IsRevisionQuery(m.Path, m.Version) {
+ if modload.IsRevisionQuery(path, version) {
// Don't check for retractions if a specific revision is requested.
allowed = nil
}
noneSelected := func(path string) (version string) { return "none" }
- _, err := modload.QueryPackages(ctx, m.Path, m.Version, noneSelected, allowed)
- if tooNew := (*gover.TooNewError)(nil); errors.As(err, &tooNew) {
- m.Path, m.Version, _ = strings.Cut(tooNew.What, "@")
- return m, tooNew.GoVersion, true
- }
-
- // QueryPackages succeeded, or it failed for a reason other than
- // this Go toolchain being too old for the modules encountered.
- // Either way, we identified the m@v on the command line,
- // so return found == true so the caller does not fall back to
- // consulting go.mod.
- return m, "", true
+ _, err := modload.QueryPackages(ctx, path, version, noneSelected, allowed)
+ if errors.Is(err, gover.ErrTooNew) {
+ // Run early switch, same one go install or go run would eventually do,
+ // if it understood all the command-line flags.
+ SwitchOrFatal(ctx, err)
+ }
+
+ return true // pkg@version found
}
diff --git a/src/cmd/go/internal/toolchain/reqs.go b/src/cmd/go/internal/toolchain/switch.go
index e5ca8d0eb4..b35198748d 100644
--- a/src/cmd/go/internal/toolchain/reqs.go
+++ b/src/cmd/go/internal/toolchain/switch.go
@@ -22,6 +22,8 @@ import (
// *gover.TooNewErrors (potentially wrapped) and switching is
// permitted by GOTOOLCHAIN, Switch switches to a new toolchain.
// Otherwise Switch prints all the errors using base.Error.
+//
+// See https://go.dev/doc/toolchain#switch.
type Switcher struct {
TooNew *gover.TooNewError // max go requirement observed
Errors []error // errors collected so far
@@ -91,7 +93,7 @@ func (s *Switcher) Switch(ctx context.Context) {
}
fmt.Fprintf(os.Stderr, "go: %v requires go >= %v; switching to %v\n", s.TooNew.What, s.TooNew.GoVersion, tv)
- SwitchTo(tv)
+ Exec(tv)
panic("unreachable")
}
diff --git a/src/cmd/go/main.go b/src/cmd/go/main.go
index af13f01240..d050792998 100644
--- a/src/cmd/go/main.go
+++ b/src/cmd/go/main.go
@@ -92,7 +92,7 @@ var _ = go11tag
func main() {
log.SetFlags(0)
- toolchain.Switch()
+ toolchain.Select()
flag.Usage = base.Usage
flag.Parse()
diff --git a/src/cmd/go/testdata/script/gotoolchain_local.txt b/src/cmd/go/testdata/script/gotoolchain_local.txt
index 18b4faabde..313c541501 100644
--- a/src/cmd/go/testdata/script/gotoolchain_local.txt
+++ b/src/cmd/go/testdata/script/gotoolchain_local.txt
@@ -70,15 +70,15 @@ go mod edit -go=1.700 -toolchain=go1.300
go version
stdout go1.700 # toolchain too old, ignored
-go mod edit -go=1.300 -toolchain=local
+go mod edit -go=1.300 -toolchain=default
go version
stdout go1.500
-go mod edit -go=1.700 -toolchain=local
+go mod edit -go=1.700 -toolchain=default
go version
stdout go1.500 # toolchain local is like GOTOOLCHAIN=local and wins
! go build
-stderr '^go: go.mod requires go >= 1.700 \(running go 1.500; go.mod sets toolchain local\)'
+stderr '^go: go.mod requires go >= 1.700 \(running go 1.500; go.mod sets toolchain default\)'
# GOTOOLCHAIN=path does the same.
env GOTOOLCHAIN=path
@@ -94,41 +94,41 @@ go mod edit -go=1.700 -toolchain=go1.300
go version
stdout go1.700 # toolchain too old, ignored
-go mod edit -go=1.300 -toolchain=local
+go mod edit -go=1.300 -toolchain=default
go version
stdout go1.500
-go mod edit -go=1.700 -toolchain=local
+go mod edit -go=1.700 -toolchain=default
go version
-stdout go1.500 # toolchain applies even if older than go line
+stdout go1.500 # toolchain default applies even if older than go line
! go build
-stderr '^go: go.mod requires go >= 1.700 \(running go 1.500; GOTOOLCHAIN=path; go.mod sets toolchain local\)'
+stderr '^go: go.mod requires go >= 1.700 \(running go 1.500; GOTOOLCHAIN=path; go.mod sets toolchain default\)'
-# GOTOOLCHAIN names can have prefix- or -suffix
-env GOTOOLCHAIN=go1.800-bigcorp
-go version
-stdout go1.800-bigcorp
+# GOTOOLCHAIN=min+auto with toolchain default uses min, not local
-env GOTOOLCHAIN=bigcorp-go1.100
+env GOTOOLCHAIN=go1.400+auto
+go mod edit -go=1.300 -toolchain=default
go version
-stdout bigcorp-go1.100
+stdout 1.400 # not 1.500 local toolchain
-env GOTOOLCHAIN=auto
-go mod edit -go=1.999 -toolchain=go1.800-bigcorp
+env GOTOOLCHAIN=go1.600+auto
+go mod edit -go=1.300 -toolchain=default
go version
-stdout go1.999
+stdout 1.600 # not 1.500 local toolchain
-go mod edit -go=1.777 -toolchain=go1.800-bigcorp
+# GOTOOLCHAIN names can have -suffix
+env GOTOOLCHAIN=go1.800-bigcorp
go version
stdout go1.800-bigcorp
-go mod edit -go=1.999 -toolchain=bigcorp-go1.800
+env GOTOOLCHAIN=auto
+go mod edit -go=1.999 -toolchain=go1.800-bigcorp
go version
stdout go1.999
-go mod edit -go=1.777 -toolchain=bigcorp-go1.800
+go mod edit -go=1.777 -toolchain=go1.800-bigcorp
go version
-stdout bigcorp-go1.800
+stdout go1.800-bigcorp
# go.work takes priority over go.mod
go mod edit -go=1.700 -toolchain=go1.999-wrong
@@ -137,13 +137,13 @@ go work edit -go=1.400 -toolchain=go1.600-right
go version
stdout go1.600-right
-go work edit -go=1.400 -toolchain=local
+go work edit -go=1.400 -toolchain=default
go version
stdout go1.500
# go.work misconfiguration does not break go work edit
# ('go 1.600 / toolchain local' forces use of 1.500 which can't normally load that go.work; allow work edit to fix it.)
-go work edit -go=1.600 -toolchain=local
+go work edit -go=1.600 -toolchain=default
go version
stdout go1.500
@@ -154,7 +154,7 @@ stdout go1.600
rm go.work
# go.mod misconfiguration does not break go mod edit
-go mod edit -go=1.600 -toolchain=local
+go mod edit -go=1.600 -toolchain=default
go version
stdout go1.500
@@ -177,19 +177,6 @@ go mod edit -go=1.501 -toolchain=none
go version
stdout go1.501
-env TESTGO_VERSION=bigcorp-go1.500
-go mod edit -go=1.499 -toolchain=none
-go version
-stdout bigcorp-go1.500
-
-go mod edit -go=1.500 -toolchain=none
-go version
-stdout bigcorp-go1.500
-
-go mod edit -go=1.501 -toolchain=none
-go version
-stdout go1.501
-
env TESTGO_VERSION='go1.500 (bigcorp)'
go mod edit -go=1.499 -toolchain=none
go version
@@ -208,60 +195,55 @@ env TESTGO_VERSION=go1.2.3
go mod edit -go=1.999 -toolchain=go1.998
! go install rsc.io/fortune/nonexist@v0.0.1
-stderr '^go: using go1.22.9 for rsc.io/fortune@v0.0.1'
+stderr '^go: rsc.io/fortune@v0.0.1 requires go >= 1.21rc999; switching to go1.22.9$'
stderr '^go: rsc.io/fortune/nonexist@v0.0.1: module rsc.io/fortune@v0.0.1 found, but does not contain package rsc.io/fortune/nonexist'
! go run rsc.io/fortune/nonexist@v0.0.1
-stderr '^go: using go1.22.9 for rsc.io/fortune@v0.0.1'
+stderr '^go: rsc.io/fortune@v0.0.1 requires go >= 1.21rc999; switching to go1.22.9$'
stderr '^go: rsc.io/fortune/nonexist@v0.0.1: module rsc.io/fortune@v0.0.1 found, but does not contain package rsc.io/fortune/nonexist'
# go install should handle unknown flags to find m@v
! go install -unknownflag rsc.io/fortune/nonexist@v0.0.1
-stderr '^go: using go1.22.9 for rsc.io/fortune@v0.0.1'
+stderr '^go: rsc.io/fortune@v0.0.1 requires go >= 1.21rc999; switching to go1.22.9$'
stderr '^flag provided but not defined: -unknownflag'
! go install -unknownflag arg rsc.io/fortune/nonexist@v0.0.1
-stderr '^go: using go1.22.9 for rsc.io/fortune@v0.0.1'
+stderr '^go: rsc.io/fortune@v0.0.1 requires go >= 1.21rc999; switching to go1.22.9$'
stderr '^flag provided but not defined: -unknownflag'
-# go run should handle unknown boolean flags and flags with =arg
+# go run cannot handle unknown boolean flags
! go run -unknownflag rsc.io/fortune/nonexist@v0.0.1
-stderr '^go: using go1.22.9 for rsc.io/fortune@v0.0.1'
+! stderr switching
stderr '^flag provided but not defined: -unknownflag'
-! go run -unknown=flag rsc.io/fortune/nonexist@v0.0.1
-stderr '^go: using go1.22.9 for rsc.io/fortune@v0.0.1'
-stderr '^flag provided but not defined: -unknown'
-
-# go run assumes unknown flags don't take arguments
-! go run -unknownflag rsc.io/fortune/nonexist@v0.0.1
-stderr '^go: using go1.22.9 for rsc.io/fortune@v0.0.1'
+! go run -unknownflag oops rsc.io/fortune/nonexist@v0.0.1
+! stderr switching
stderr '^flag provided but not defined: -unknownflag'
-! go run -unknownflag oops rsc.io/fortune/nonexist@v0.0.1 # lost parse, cannot find m@v
-! stderr go1.22.9
-! stderr '^go: using'
-stderr '^flag provided but not defined: -unknownflag'
+# go run can handle unknown flag with argument.
+! go run -unknown=flag rsc.io/fortune/nonexist@v0.0.1
+stderr '^go: rsc.io/fortune@v0.0.1 requires go >= 1.21rc999; switching to go1.22.9$'
+stderr '^flag provided but not defined: -unknown'
# go install m@v should handle queries
! go install rsc.io/fortune/nonexist@v0.0
-stderr '^go: using go1.22.9 for rsc.io/fortune@v0.0.1'
+stderr '^go: rsc.io/fortune@v0.0.1 requires go >= 1.21rc999; switching to go1.22.9$'
stderr '^go: rsc.io/fortune/nonexist@v0.0: module rsc.io/fortune@v0.0 found \(v0.0.1\), but does not contain package rsc.io/fortune/nonexist'
# go run m@v should handle queries
! go install rsc.io/fortune/nonexist@v0
-stderr '^go: using go1.22.9 for rsc.io/fortune@v0.0.1'
+stderr '^go: rsc.io/fortune@v0.0.1 requires go >= 1.21rc999; switching to go1.22.9$'
stderr '^go: rsc.io/fortune/nonexist@v0: module rsc.io/fortune@v0 found \(v0.0.1\), but does not contain package rsc.io/fortune/nonexist'
# go install m@v should use local toolchain if not upgrading
! go install rsc.io/fortune/nonexist@v1
! stderr go1.22.9
-! stderr '^go: using'
+! stderr switching
stderr '^go: downloading rsc.io/fortune v1.0.0$'
stderr '^go: rsc.io/fortune/nonexist@v1: module rsc.io/fortune@v1 found \(v1.0.0\), but does not contain package rsc.io/fortune/nonexist'
# go run m@v should use local toolchain if not upgrading
! go run rsc.io/fortune/nonexist@v1
! stderr go1.22.9
-! stderr '^go: using'
+! stderr switching
stderr '^go: rsc.io/fortune/nonexist@v1: module rsc.io/fortune@v1 found \(v1.0.0\), but does not contain package rsc.io/fortune/nonexist'
diff --git a/src/cmd/go/testdata/script/gotoolchain_net.txt b/src/cmd/go/testdata/script/gotoolchain_net.txt
index d04229d293..72bb2b7252 100644
--- a/src/cmd/go/testdata/script/gotoolchain_net.txt
+++ b/src/cmd/go/testdata/script/gotoolchain_net.txt
@@ -1,8 +1,6 @@
# This test only checks that basic network lookups work.
# The full test of toolchain version selection is in gotoolchain.txt.
-[short] skip
-
env TESTGO_VERSION=go1.21actual
# GOTOOLCHAIN from network, does not exist
@@ -16,7 +14,34 @@ env GOTOOLCHAIN=go1.999testmod
go version
stderr 'go: downloading go1.999testmod \(.*/.*\)'
+# GOTOOLCHAIN cached from network
+go version
+! stderr downloading
+stdout go1.999testmod
+
+# GOTOOLCHAIN with GOSUMDB enabled but at a bad URL should operate in cache and not try badurl
+env oldsumdb=$GOSUMDB
+env GOSUMDB=$oldsumdb' http://badurl'
+go version
+! stderr downloading
+stdout go1.999testmod
+
+# GOTOOLCHAIN with GOSUMB=off should fail, because it cannot access even the cached sumdb info
+# without the sumdb name.
+env GOSUMDB=off
+! go version
+stderr '^go: golang.org/toolchain@v0.0.1-go1.999testmod.[a-z0-9\-]*: verifying module: checksum database disabled by GOSUMDB=off$'
+
+# GOTOOLCHAIN with GOSUMDB enabled but at a bad URL should fail if cache is incomplete
+env GOSUMDB=$oldsumdb' http://badurl'
+rm $GOPATH/pkg/mod/cache/download/sumdb
+! go version
+! stderr downloading
+stderr 'panic: use of network' # test catches network access
+env GOSUMDB=$oldsumdb
+
# Test a real GOTOOLCHAIN
+[short] skip
[!net:golang.org] skip
[!GOOS:darwin] [!GOOS:windows] [!GOOS:linux] skip
[!GOARCH:amd64] [!GOARCH:arm64] skip
diff --git a/src/cmd/go/testdata/script/mod_edit_toolchain.txt b/src/cmd/go/testdata/script/mod_edit_toolchain.txt
index 525e4dd54a..bb544be344 100644
--- a/src/cmd/go/testdata/script/mod_edit_toolchain.txt
+++ b/src/cmd/go/testdata/script/mod_edit_toolchain.txt
@@ -7,8 +7,8 @@ env GO111MODULE=on
go mod edit -toolchain=go1.9
grep 'toolchain go1.9' go.mod
-go mod edit -toolchain=local
-grep 'toolchain local' go.mod
+go mod edit -toolchain=default
+grep 'toolchain default' go.mod
go mod edit -toolchain=none
! grep toolchain go.mod
diff --git a/src/cmd/go/testdata/script/mod_get_toolchain.txt b/src/cmd/go/testdata/script/mod_get_toolchain.txt
new file mode 100644
index 0000000000..143ad32a4e
--- /dev/null
+++ b/src/cmd/go/testdata/script/mod_get_toolchain.txt
@@ -0,0 +1,102 @@
+# setup
+env TESTGO_VERSION=go1.99.0
+env TESTGO_VERSION_SWITCH=switch
+
+# go get go should use the latest Go 1.23
+cp go.mod.orig go.mod
+go get go
+stderr '^go: upgraded go 1.21 => 1.23.9$'
+grep 'go 1.23.9' go.mod
+grep 'toolchain go1.99.0' go.mod
+
+# go get go@1.23 should use the latest Go 1.23
+cp go.mod.orig go.mod
+go get go@1.23
+stderr '^go: upgraded go 1.21 => 1.23.9$'
+grep 'go 1.23.9' go.mod
+grep 'toolchain go1.99.0' go.mod
+
+# go get go@1.22 should use the latest Go 1.22
+cp go.mod.orig go.mod
+go get go@1.22
+stderr '^go: upgraded go 1.21 => 1.22.9$'
+grep 'go 1.22.9' go.mod
+grep 'toolchain go1.99.0' go.mod
+
+# go get go@patch should use the latest patch release
+go get go@1.22.1
+go get go@patch
+stderr '^go: upgraded go 1.22.1 => 1.22.9$'
+grep 'go 1.22.9' go.mod
+grep 'toolchain go1.99.0' go.mod
+
+# go get go@1.24 does NOT find the release candidate
+cp go.mod.orig go.mod
+! go get go@1.24
+stderr '^go: go@1.24: no matching versions for query "1.24"$'
+
+# go get go@1.24rc1 works
+cp go.mod.orig go.mod
+go get go@1.24rc1
+stderr '^go: upgraded go 1.21 => 1.24rc1$'
+grep 'go 1.24rc1' go.mod
+grep 'toolchain go1.99.0' go.mod
+
+# go get go@latest finds the latest Go 1.23
+cp go.mod.orig go.mod
+go get go@latest
+stderr '^go: upgraded go 1.21 => 1.23.9$'
+grep 'go 1.23.9' go.mod
+grep 'toolchain go1.99.0' go.mod
+
+# Again, with toolchains.
+
+# go get toolchain should find go1.999testmod.
+go get toolchain
+stderr '^go: upgraded toolchain go1.99.0 => go1.999testmod$'
+grep 'go 1.23.9' go.mod
+grep 'toolchain go1.999testmod' go.mod
+
+# go get toolchain@go1.23 should use the latest Go 1.23
+go get toolchain@go1.23
+stderr '^go: removed toolchain go1.999testmod$'
+grep 'go 1.23.9' go.mod
+! grep 'toolchain go1.23.9' go.mod # implied
+
+# go get toolchain@go1.22 should use the latest Go 1.22 and downgrade go.
+go get toolchain@go1.22
+stderr '^go: downgraded go 1.23.9 => 1.22.9$'
+grep 'go 1.22.9' go.mod
+! grep 'toolchain go1.22.9' go.mod # implied
+
+# go get toolchain@patch should use the latest patch release
+go get toolchain@go1.22.1
+go get toolchain@patch
+stderr '^go: added toolchain go1.22.9$'
+grep 'go 1.22.1' go.mod
+grep 'toolchain go1.22.9' go.mod
+go get go@1.22.9 toolchain@none
+grep 'go 1.22.9' go.mod
+! grep 'toolchain go1.22.9' go.mod
+
+# go get toolchain@go1.24 does NOT find the release candidate
+! go get toolchain@go1.24
+stderr '^go: toolchain@go1.24: no matching versions for query "go1.24"$'
+
+# go get toolchain@go1.24rc1 works
+go get toolchain@go1.24rc1
+stderr '^go: added toolchain go1.24rc1$'
+grep 'go 1.22.9' go.mod # no longer implied
+grep 'toolchain go1.24rc1' go.mod
+
+# go get toolchain@latest finds go1.999testmod.
+cp go.mod.orig go.mod
+go get toolchain@latest
+stderr '^go: added toolchain go1.999testmod$'
+grep 'go 1.21' go.mod
+grep 'toolchain go1.999testmod' go.mod
+
+-- go.mod.orig --
+module m
+
+go 1.21
diff --git a/src/cmd/go/testdata/script/work_edit_toolchain.txt b/src/cmd/go/testdata/script/work_edit_toolchain.txt
index a171296707..b4e260d238 100644
--- a/src/cmd/go/testdata/script/work_edit_toolchain.txt
+++ b/src/cmd/go/testdata/script/work_edit_toolchain.txt
@@ -7,8 +7,8 @@ env GO111MODULE=on
go work edit -toolchain=go1.9
grep 'toolchain go1.9' go.work
-go work edit -toolchain=local
-grep 'toolchain local' go.work
+go work edit -toolchain=default
+grep 'toolchain default' go.work
go work edit -toolchain=none
! grep toolchain go.work