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>
This commit is contained in:
Russ Cox 2023-06-04 22:34:52 -04:00
parent bf016520e2
commit 35268a9960
16 changed files with 355 additions and 175 deletions

2
go.env
View File

@ -8,5 +8,5 @@ GOPROXY=https://proxy.golang.org,direct
GOSUMDB=sum.golang.org GOSUMDB=sum.golang.org
# Automatically download newer toolchains as directed by go.mod files. # 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 GOTOOLCHAIN=auto

View File

@ -671,6 +671,14 @@
// //
// go get example.com/mod@none // 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. // See https://golang.org/ref/mod#go-get for details.
// //
// In earlier versions of Go, 'go get' was used to build and install packages. // 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 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'. // For more about specifying packages, see 'go help packages'.
// //
// This text describes the behavior of get using modules to manage source // This text describes the behavior of get using modules to manage source

View File

@ -84,6 +84,9 @@ func ModIsValid(path, vers string) bool {
// The caller is assumed to have checked that ModIsValid(path, vers) is true. // The caller is assumed to have checked that ModIsValid(path, vers) is true.
func ModIsPrefix(path, vers string) bool { func ModIsPrefix(path, vers string) bool {
if IsToolchain(path) { if IsToolchain(path) {
if path == "toolchain" {
return IsLang(FromToolchain(vers))
}
return IsLang(vers) return IsLang(vers)
} }
// Semver // Semver
@ -110,3 +113,15 @@ func ModIsPrerelease(path, vers string) bool {
} }
return semver.Prerelease(vers) != "" 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)
}

View File

@ -14,20 +14,17 @@ import (
// FromToolchain returns the Go version for the named toolchain, // FromToolchain returns the Go version for the named toolchain,
// derived from the name itself (not by running the 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. // A suffix after the VERSION introduced by a +, -, space, or tab is removed.
// Examples: // Examples:
// //
// FromToolchain("go1.2.3") == "1.2.3" // FromToolchain("go1.2.3") == "1.2.3"
// FromToolchain("go1.2.3-bigcorp") == "1.2.3" // FromToolchain("go1.2.3-bigcorp") == "1.2.3"
// FromToolchain("gccgo-go1.23rc4") == "1.23rc4"
// FromToolchain("invalid") == "" // FromToolchain("invalid") == ""
func FromToolchain(name string) string { func FromToolchain(name string) string {
var v string var v string
if strings.HasPrefix(name, "go") { if strings.HasPrefix(name, "go") {
v = name[2:] v = name[2:]
} else if i := strings.Index(name, "-go"); i >= 0 {
v = name[i+3:]
} else { } else {
return "" return ""
} }

View File

@ -14,6 +14,6 @@ var fromToolchainTests = []testCase1[string, string]{
{"go1.2.3+bigcorp", ""}, {"go1.2.3+bigcorp", ""},
{"go1.2.3-bigcorp", "1.2.3"}, {"go1.2.3-bigcorp", "1.2.3"},
{"go1.2.3-bigcorp more text", "1.2.3"}, {"go1.2.3-bigcorp more text", "1.2.3"},
{"gccgo-go1.23rc4", "1.23rc4"}, {"gccgo-go1.23rc4", ""},
{"gccgo-go1.23rc4-bigdwarf", "1.23rc4"}, {"gccgo-go1.23rc4-bigdwarf", ""},
} }

View File

@ -33,6 +33,14 @@ import (
// useSumDB reports whether to use the Go checksum database for the given module. // useSumDB reports whether to use the Go checksum database for the given module.
func useSumDB(mod module.Version) bool { 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) 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" 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) key := strings.Fields(gosumdb)
if len(key) >= 1 { if len(key) >= 1 {
if k := knownGOSUMDB[key[0]]; k != "" { if k := knownGOSUMDB[key[0]]; k != "" {

View File

@ -72,6 +72,14 @@ To remove a dependency on a module and downgrade modules that require it:
go get example.com/mod@none 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. See https://golang.org/ref/mod#go-get for details.
In earlier versions of Go, 'go get' was used to build and install packages. 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 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'. For more about specifying packages, see 'go help packages'.
This text describes the behavior of get using modules to manage source This text describes the behavior of get using modules to manage source

View File

@ -393,7 +393,7 @@ func newQueryMatcher(path string, query, current string, allowed AllowedFunc) (*
qm.mayUseLatest = true qm.mayUseLatest = true
} else { } else {
qm.mayUseLatest = module.IsPseudoVersion(current) 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 } qm.filter = func(mv string) bool { return gover.ModCompare(qm.path, mv, current) >= 0 }
} }

View File

@ -81,11 +81,12 @@ func FilterEnv(env []string) []string {
return out 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 // the GOTOOLCHAIN environment variable or the user's configuration
// or go.mod file. // or go.mod file.
// It must be called early in startup. // It must be called early in startup.
func Switch() { // See https://go.dev/doc/toolchain#select.
func Select() {
log.SetPrefix("go: ") log.SetPrefix("go: ")
defer log.SetPrefix("") defer log.SetPrefix("")
@ -93,6 +94,21 @@ func Switch() {
return 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") gotoolchain := cfg.Getenv("GOTOOLCHAIN")
gover.Startup.GOTOOLCHAIN = gotoolchain gover.Startup.GOTOOLCHAIN = gotoolchain
if gotoolchain == "" { if gotoolchain == "" {
@ -105,77 +121,70 @@ func Switch() {
return return
} }
// Note: minToolchain is what https://go.dev/doc/toolchain#select calls the default toolchain.
minToolchain := gover.LocalToolchain() minToolchain := gover.LocalToolchain()
minVers := gover.Local() minVers := gover.Local()
if min, mode, ok := strings.Cut(gotoolchain, "+"); ok { // go1.2.3+auto var mode string
v := gover.FromToolchain(min) if gotoolchain == "auto" {
if v == "" { mode = "auto"
base.Fatalf("invalid GOTOOLCHAIN %q: invalid minimum toolchain %q", gotoolchain, min) } 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 if plus && suffix != "auto" && suffix != "path" {
minVers = v
if mode != "auto" && mode != "path" {
base.Fatalf("invalid GOTOOLCHAIN %q: only version suffixes are +auto and +path", gotoolchain) base.Fatalf("invalid GOTOOLCHAIN %q: only version suffixes are +auto and +path", gotoolchain)
} }
gotoolchain = mode mode = suffix
} }
if gotoolchain == "auto" || gotoolchain == "path" { gotoolchain = minToolchain
gotoolchain = minToolchain if (mode == "auto" || mode == "path") && !goInstallVersion() {
// Read go.mod to find new minimum and suggested toolchain.
// Locate and read go.mod or go.work. file, goVers, toolchain := modGoToolchain()
// For go install m@v, it's the installed module's go.mod. gover.Startup.AutoFile = file
if m, goVers, ok := goInstallVersion(); ok { if toolchain == "default" {
if gover.Compare(goVers, minVers) > 0 { // "default" means always use the default toolchain,
// Always print, because otherwise there's no way for the user to know // which is already set, so nothing to do here.
// that a non-default toolchain version is being used here. // Note that if we have Go 1.21 installed originally,
// (Normally you can run "go version", but go install m@v ignores the // GOTOOLCHAIN=go1.30.0+auto or GOTOOLCHAIN=go1.30.0,
// context that "go version" works in.) // and the go.mod says "toolchain default", we use Go 1.30, not Go 1.21.
var err error // That is, default overrides the "auto" part of the calculation
gotoolchain, err = NewerToolchain(context.Background(), goVers) // but not the minimum that the user has set.
if err != nil { // Of course, if the go.mod also says "go 1.35", using Go 1.30
fmt.Fprintf(os.Stderr, "go: %v\n", err) // will provoke an error about the toolchain being too old.
gotoolchain = "go" + goVers // That's what people who use toolchain default want:
} // only ever use the toolchain configured by the user
fmt.Fprintf(os.Stderr, "go: using %s for %v\n", gotoolchain, m) // (including its environment and go env -w file).
} gover.Startup.AutoToolchain = toolchain
} else { } else {
file, goVers, toolchain := modGoToolchain() if toolchain != "" {
gover.Startup.AutoFile = file // Accept toolchain only if it is >= our min.
if toolchain == "local" { toolVers := gover.FromToolchain(toolchain)
// Local means always use the default local toolchain, if toolVers == "" || (!strings.HasPrefix(toolchain, "go") && !strings.Contains(toolchain, "-go")) {
// which is already set, so nothing to do here. base.Fatalf("invalid toolchain %q in %s", toolchain, base.ShortPath(file))
// 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 gover.Compare(goVers, minVers) > 0 { if gover.Compare(toolVers, minVers) >= 0 {
gotoolchain = "go" + goVers gotoolchain = toolchain
gover.Startup.AutoGoVersion = goVers minVers = toolVers
gover.Startup.AutoToolchain = "" // in case we are overriding it for being too old 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) base.Fatalf("invalid GOTOOLCHAIN %q", gotoolchain)
} }
SwitchTo(gotoolchain) Exec(gotoolchain)
} }
// NewerToolchain returns the name of the toolchain to use when we need // 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. // 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. // 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. // "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 // "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 var TestVersionSwitch string
// SwitchTo invokes the specified Go toolchain or else prints an error and exits the process. // Exec 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 // If $GOTOOLCHAIN is set to path or min+path, Exec only considers the PATH
// as a source of Go toolchains. Otherwise SwitchTo tries the PATH but then downloads // as a source of Go toolchains. Otherwise Exec tries the PATH but then downloads
// a toolchain if necessary. // a toolchain if necessary.
func SwitchTo(gotoolchain string) { func Exec(gotoolchain string) {
log.SetPrefix("go: ") log.SetPrefix("go: ")
count, _ := strconv.Atoi(os.Getenv(countEnv)) 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") 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. // goInstallVersion reports whether the command line 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. // If so, Select must not read the go.mod or go.work file in "auto" or "path" mode.
func goInstallVersion() (m module.Version, goVers string, found bool) { func goInstallVersion() bool {
// Note: We assume there are no flags between 'go' and 'install' or 'run'. // Note: We assume there are no flags between 'go' and 'install' or 'run'.
// During testing there are some debugging flags that are accepted // During testing there are some debugging flags that are accepted
// in that position, but in production go binaries there are not. // in that position, but in production go binaries there are not.
if len(os.Args) < 3 || (os.Args[1] != "install" && os.Args[1] != "run") { 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 var arg string
switch os.Args[1] { switch os.Args[1] {
default:
return false
case "install": case "install":
// Cannot parse 'go install' command line precisely, because there // We would like to let 'go install -newflag pkg@version' work even
// may be new flags we don't know about. Instead, assume the final // across a toolchain switch. To make that work, assume the pkg@version
// argument is a pkg@version we can use. // is the last argument and skip the flag parsing.
arg = os.Args[len(os.Args)-1] arg = os.Args[len(os.Args)-1]
case "run": case "run":
// For run, the pkg@version can be anywhere on the command line. // 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. // because it is preceded by run flags and followed by arguments to the
// We do the best we can by interrogating the CmdRun flags and assume // program being run. To handle that precisely, we have to interpret the
// that any unknown flag does not take an argument. // 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:] args := os.Args[2:]
for i := 0; i < len(args); i++ { for i := 0; i < len(args); i++ {
a := args[i] a := args[i]
@ -526,19 +540,21 @@ func goInstallVersion() (m module.Version, goVers string, found bool) {
break break
} }
if a == "-" { if a == "-" {
break // non-flag but also non-pkg@version
return false
} }
if a == "--" { if a == "--" {
if i+1 < len(args) { if i+1 >= len(args) {
arg = args[i+1] return false
} }
arg = args[i+1]
break break
} }
a = strings.TrimPrefix(a, "-") a = strings.TrimPrefix(a, "-")
a = strings.TrimPrefix(a, "-") a = strings.TrimPrefix(a, "-")
if strings.HasPrefix(a, "-") { if strings.HasPrefix(a, "-") {
// non-flag but also non-m@v // non-flag but also non-pkg@version
break return false
} }
if strings.Contains(a, "=") { if strings.Contains(a, "=") {
// already has value // already has value
@ -546,8 +562,8 @@ func goInstallVersion() (m module.Version, goVers string, found bool) {
} }
f := run.CmdRun.Flag.Lookup(a) f := run.CmdRun.Flag.Lookup(a)
if f == nil { if f == nil {
// Unknown flag. Assume it doesn't take a value: best we can do. // Unknown flag. Give up. The command is going to fail in flag parsing.
continue return false
} }
if bf, ok := f.Value.(interface{ IsBoolFlag() bool }); ok && bf.IsBoolFlag() { if bf, ok := f.Value.(interface{ IsBoolFlag() bool }); ok && bf.IsBoolFlag() {
// Does not take value. // 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) { if !strings.Contains(arg, "@") || build.IsLocalImport(arg) || filepath.IsAbs(arg) {
return module.Version{}, "", false return false
} }
m.Path, m.Version, _ = strings.Cut(arg, "@") path, version, _ := strings.Cut(arg, "@")
if m.Path == "" || m.Version == "" || gover.IsToolchain(m.Path) { if path == "" || version == "" || gover.IsToolchain(path) {
return module.Version{}, "", false 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. // Set up modules without an explicit go.mod, to download go.mod.
modload.ForceUseModules = true modload.ForceUseModules = true
modload.RootMode = modload.NoRoot modload.RootMode = modload.NoRoot
@ -573,21 +600,17 @@ func goInstallVersion() (m module.Version, goVers string, found bool) {
// See internal/load.PackagesAndErrorsOutsideModule // See internal/load.PackagesAndErrorsOutsideModule
ctx := context.Background() ctx := context.Background()
allowed := modload.CheckAllowed 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. // Don't check for retractions if a specific revision is requested.
allowed = nil allowed = nil
} }
noneSelected := func(path string) (version string) { return "none" } noneSelected := func(path string) (version string) { return "none" }
_, err := modload.QueryPackages(ctx, m.Path, m.Version, noneSelected, allowed) _, err := modload.QueryPackages(ctx, path, version, noneSelected, allowed)
if tooNew := (*gover.TooNewError)(nil); errors.As(err, &tooNew) { if errors.Is(err, gover.ErrTooNew) {
m.Path, m.Version, _ = strings.Cut(tooNew.What, "@") // Run early switch, same one go install or go run would eventually do,
return m, tooNew.GoVersion, true // if it understood all the command-line flags.
SwitchOrFatal(ctx, err)
} }
// QueryPackages succeeded, or it failed for a reason other than return true // pkg@version found
// 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
} }

View File

@ -22,6 +22,8 @@ import (
// *gover.TooNewErrors (potentially wrapped) and switching is // *gover.TooNewErrors (potentially wrapped) and switching is
// permitted by GOTOOLCHAIN, Switch switches to a new toolchain. // permitted by GOTOOLCHAIN, Switch switches to a new toolchain.
// Otherwise Switch prints all the errors using base.Error. // Otherwise Switch prints all the errors using base.Error.
//
// See https://go.dev/doc/toolchain#switch.
type Switcher struct { type Switcher struct {
TooNew *gover.TooNewError // max go requirement observed TooNew *gover.TooNewError // max go requirement observed
Errors []error // errors collected so far 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) 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") panic("unreachable")
} }

View File

@ -92,7 +92,7 @@ var _ = go11tag
func main() { func main() {
log.SetFlags(0) log.SetFlags(0)
toolchain.Switch() toolchain.Select()
flag.Usage = base.Usage flag.Usage = base.Usage
flag.Parse() flag.Parse()

View File

@ -70,15 +70,15 @@ go mod edit -go=1.700 -toolchain=go1.300
go version go version
stdout go1.700 # toolchain too old, ignored 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 go version
stdout go1.500 stdout go1.500
go mod edit -go=1.700 -toolchain=local go mod edit -go=1.700 -toolchain=default
go version go version
stdout go1.500 # toolchain local is like GOTOOLCHAIN=local and wins stdout go1.500 # toolchain local is like GOTOOLCHAIN=local and wins
! go build ! 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. # GOTOOLCHAIN=path does the same.
env GOTOOLCHAIN=path env GOTOOLCHAIN=path
@ -94,25 +94,33 @@ go mod edit -go=1.700 -toolchain=go1.300
go version go version
stdout go1.700 # toolchain too old, ignored 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 go version
stdout go1.500 stdout go1.500
go mod edit -go=1.700 -toolchain=local go mod edit -go=1.700 -toolchain=default
go version 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 ! 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 # GOTOOLCHAIN=min+auto with toolchain default uses min, not local
env GOTOOLCHAIN=go1.400+auto
go mod edit -go=1.300 -toolchain=default
go version
stdout 1.400 # not 1.500 local toolchain
env GOTOOLCHAIN=go1.600+auto
go mod edit -go=1.300 -toolchain=default
go version
stdout 1.600 # not 1.500 local toolchain
# GOTOOLCHAIN names can have -suffix
env GOTOOLCHAIN=go1.800-bigcorp env GOTOOLCHAIN=go1.800-bigcorp
go version go version
stdout go1.800-bigcorp stdout go1.800-bigcorp
env GOTOOLCHAIN=bigcorp-go1.100
go version
stdout bigcorp-go1.100
env GOTOOLCHAIN=auto env GOTOOLCHAIN=auto
go mod edit -go=1.999 -toolchain=go1.800-bigcorp go mod edit -go=1.999 -toolchain=go1.800-bigcorp
go version go version
@ -122,14 +130,6 @@ go mod edit -go=1.777 -toolchain=go1.800-bigcorp
go version go version
stdout go1.800-bigcorp stdout go1.800-bigcorp
go mod edit -go=1.999 -toolchain=bigcorp-go1.800
go version
stdout go1.999
go mod edit -go=1.777 -toolchain=bigcorp-go1.800
go version
stdout bigcorp-go1.800
# go.work takes priority over go.mod # go.work takes priority over go.mod
go mod edit -go=1.700 -toolchain=go1.999-wrong go mod edit -go=1.700 -toolchain=go1.999-wrong
go work init go work init
@ -137,13 +137,13 @@ go work edit -go=1.400 -toolchain=go1.600-right
go version go version
stdout go1.600-right stdout go1.600-right
go work edit -go=1.400 -toolchain=local go work edit -go=1.400 -toolchain=default
go version go version
stdout go1.500 stdout go1.500
# go.work misconfiguration does not break go work edit # 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 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 go version
stdout go1.500 stdout go1.500
@ -154,7 +154,7 @@ stdout go1.600
rm go.work rm go.work
# go.mod misconfiguration does not break go mod edit # 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 go version
stdout go1.500 stdout go1.500
@ -177,19 +177,6 @@ go mod edit -go=1.501 -toolchain=none
go version go version
stdout go1.501 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)' env TESTGO_VERSION='go1.500 (bigcorp)'
go mod edit -go=1.499 -toolchain=none go mod edit -go=1.499 -toolchain=none
go version go version
@ -208,60 +195,55 @@ env TESTGO_VERSION=go1.2.3
go mod edit -go=1.999 -toolchain=go1.998 go mod edit -go=1.999 -toolchain=go1.998
! go install rsc.io/fortune/nonexist@v0.0.1 ! 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' 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 ! 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' 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 should handle unknown flags to find m@v
! go install -unknownflag rsc.io/fortune/nonexist@v0.0.1 ! 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' stderr '^flag provided but not defined: -unknownflag'
! go install -unknownflag arg rsc.io/fortune/nonexist@v0.0.1 ! 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' 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 ! 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' stderr '^flag provided but not defined: -unknownflag'
! go run -unknownflag oops rsc.io/fortune/nonexist@v0.0.1
! stderr switching
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 ! 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 '^go: rsc.io/fortune@v0.0.1 requires go >= 1.21rc999; switching to go1.22.9$'
stderr '^flag provided but not defined: -unknown' 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'
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 install m@v should handle queries # go install m@v should handle queries
! go install rsc.io/fortune/nonexist@v0.0 ! 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' 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 run m@v should handle queries
! go install rsc.io/fortune/nonexist@v0 ! 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' 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 m@v should use local toolchain if not upgrading
! go install rsc.io/fortune/nonexist@v1 ! go install rsc.io/fortune/nonexist@v1
! stderr go1.22.9 ! stderr go1.22.9
! stderr '^go: using' ! stderr switching
stderr '^go: downloading rsc.io/fortune v1.0.0$' 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' 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 m@v should use local toolchain if not upgrading
! go run rsc.io/fortune/nonexist@v1 ! go run rsc.io/fortune/nonexist@v1
! stderr go1.22.9 ! 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' 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'

View File

@ -1,8 +1,6 @@
# This test only checks that basic network lookups work. # This test only checks that basic network lookups work.
# The full test of toolchain version selection is in gotoolchain.txt. # The full test of toolchain version selection is in gotoolchain.txt.
[short] skip
env TESTGO_VERSION=go1.21actual env TESTGO_VERSION=go1.21actual
# GOTOOLCHAIN from network, does not exist # GOTOOLCHAIN from network, does not exist
@ -16,7 +14,34 @@ env GOTOOLCHAIN=go1.999testmod
go version go version
stderr 'go: downloading go1.999testmod \(.*/.*\)' 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 # Test a real GOTOOLCHAIN
[short] skip
[!net:golang.org] skip [!net:golang.org] skip
[!GOOS:darwin] [!GOOS:windows] [!GOOS:linux] skip [!GOOS:darwin] [!GOOS:windows] [!GOOS:linux] skip
[!GOARCH:amd64] [!GOARCH:arm64] skip [!GOARCH:amd64] [!GOARCH:arm64] skip

View File

@ -7,8 +7,8 @@ env GO111MODULE=on
go mod edit -toolchain=go1.9 go mod edit -toolchain=go1.9
grep 'toolchain go1.9' go.mod grep 'toolchain go1.9' go.mod
go mod edit -toolchain=local go mod edit -toolchain=default
grep 'toolchain local' go.mod grep 'toolchain default' go.mod
go mod edit -toolchain=none go mod edit -toolchain=none
! grep toolchain go.mod ! grep toolchain go.mod

View File

@ -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

View File

@ -7,8 +7,8 @@ env GO111MODULE=on
go work edit -toolchain=go1.9 go work edit -toolchain=go1.9
grep 'toolchain go1.9' go.work grep 'toolchain go1.9' go.work
go work edit -toolchain=local go work edit -toolchain=default
grep 'toolchain local' go.work grep 'toolchain default' go.work
go work edit -toolchain=none go work edit -toolchain=none
! grep toolchain go.work ! grep toolchain go.work