]> Cypherpunks.ru repositories - gostls13.git/commitdiff
cmd/go: additional doc-inspired tests and bug fixes
authorRuss Cox <rsc@golang.org>
Mon, 5 Jun 2023 02:34:52 +0000 (22:34 -0400)
committerRuss Cox <rsc@golang.org>
Tue, 6 Jun 2023 19:18:46 +0000 (19:18 +0000)
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>

16 files changed:
go.env
src/cmd/go/alldocs.go
src/cmd/go/internal/gover/mod.go
src/cmd/go/internal/gover/toolchain.go
src/cmd/go/internal/gover/toolchain_test.go
src/cmd/go/internal/modfetch/sumdb.go
src/cmd/go/internal/modget/get.go
src/cmd/go/internal/modload/query.go
src/cmd/go/internal/toolchain/select.go [moved from src/cmd/go/internal/toolchain/toolchain.go with 73% similarity]
src/cmd/go/internal/toolchain/switch.go [moved from src/cmd/go/internal/toolchain/reqs.go with 98% similarity]
src/cmd/go/main.go
src/cmd/go/testdata/script/gotoolchain_local.txt
src/cmd/go/testdata/script/gotoolchain_net.txt
src/cmd/go/testdata/script/mod_edit_toolchain.txt
src/cmd/go/testdata/script/mod_get_toolchain.txt [new file with mode: 0644]
src/cmd/go/testdata/script/work_edit_toolchain.txt

diff --git a/go.env b/go.env
index 9bab8ffd73dd71d988461183eb9eed312c0e443e..6ff2b921d464bc64266c9d84a67a33fb5505d216 100644 (file)
--- 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
index 7ef763d6be6239c9bf7b6c88f8ccdc9506d73df4..05ee094ea7499b1a65f403634f9203d5b1951fee 100644 (file)
 //
 //     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.
 //
 // 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
index 18e5635cdf724fe067e37dfc43a7a67fe677509d..d3cc17068def6d19cc51f4a25a2a61b1bbd543a9 100644 (file)
@@ -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)
+}
index bd0d52ad84055170de1621f45ab86edabcc968d5..efa2de46a51acd313d2f785e82d773809b6f3f65 100644 (file)
@@ -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 ""
        }
index 7d05f1d0c39e656a2a80d76eec291f36ab5844c8..d1c22fbc37cb9cecc61852ad01031bc3d9810b6f 100644 (file)
@@ -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", ""},
 }
index 492b03bd84ac8b6e0d3758accb79e614dd567fe2..6e60e7d97642d5d861a2775da6cf15b351e21daf 100644 (file)
@@ -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 != "" {
index 43708a3cab67b7af115aace4a594e6c72108023c..5d9eb70489ec329228d16c8aac23319435985ef2 100644 (file)
@@ -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
index 945b6e1642d97960eca35b1032c457cfb7e37824..81c32d27a19d6f4e54e0313270890603ef6d590f 100644 (file)
@@ -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 }
                }
 
similarity index 73%
rename from src/cmd/go/internal/toolchain/toolchain.go
rename to src/cmd/go/internal/toolchain/select.go
index e6ff584480a0c398a7ca2a008808232bcca537af..6aac8c1eaad9e4a15e442e7a7373bc08a2346dba 100644 (file)
@@ -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
 }
similarity index 98%
rename from src/cmd/go/internal/toolchain/reqs.go
rename to src/cmd/go/internal/toolchain/switch.go
index e5ca8d0eb4c2d07165ecfd3edc66eee2878b22c3..b35198748d4edf32e580f19a92d872d9db72a279 100644 (file)
@@ -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")
 }
 
index af13f01240c3feaba9d82cbaa23a7728d50b8fe9..d050792998a10314ed23f958f13478d94c99ed1f 100644 (file)
@@ -92,7 +92,7 @@ var _ = go11tag
 
 func main() {
        log.SetFlags(0)
-       toolchain.Switch()
+       toolchain.Select()
 
        flag.Usage = base.Usage
        flag.Parse()
index 18b4faabde7e49d239a5f1c2a384456d148adce9..313c541501576468007071f4081390770650d2a1 100644 (file)
@@ -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'
index d04229d293747eb26d78d11539cc2ade40b51a7a..72bb2b72526980aaa0e000ce2f38839d22df8b1e 100644 (file)
@@ -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
index 525e4dd54a75fd79a8ee79b3c4cd41ebc3258ed4..bb544be344d4f1a6f5b0cdaf128afc66311a971e 100644 (file)
@@ -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 (file)
index 0000000..143ad32
--- /dev/null
@@ -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
index a171296707b3a4d737fec54c00ffe473285f08ed..b4e260d23876023391f67892ee9c1903ee6be170 100644 (file)
@@ -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