base.Fatalf("go: %v", cfg.ExperimentErr)
}
+ for _, arg := range args {
+ if strings.Contains(arg, "=") {
+ base.Fatalf("go: invalid variable name %q (use -w to set variable)", arg)
+ }
+ }
+
env := cfg.CmdEnv
env = append(env, ExtraEnvVars()...)
// depending on the module path.
package gover
-import "cmp"
+import (
+ "cmp"
+)
// A version is a parsed Go version: major[.minor[.patch]][kind[pre]]
// The numbers are the original decimal strings to avoid integer overflows
return v.major + "." + v.minor
}
+// IsPrerelease reports whether v denotes a Go prerelease version.
+func IsPrerelease(x string) bool {
+ return parse(x).kind != ""
+}
+
// Prev returns the Go major release immediately preceding v,
// or v itself if v is the first Go major release (1.0) or not a supported
// Go version.
{"1.21", true},
{"1.20", false}, // == 1.20.0
{"1.19", false}, // == 1.20.0
+ {"1.3", false}, // == 1.3.0
{"1.2", false}, // == 1.2.0
{"1", false}, // == 1.0.0
}
}
func test1[In, Out any](t *testing.T, tests []testCase1[In, Out], name string, f func(In) Out) {
+ t.Helper()
for _, tt := range tests {
if out := f(tt.in); !reflect.DeepEqual(out, tt.out) {
t.Errorf("%s(%v) = %v, want %v", name, tt.in, out, tt.out)
}
func test2[In1, In2, Out any](t *testing.T, tests []testCase2[In1, In2, Out], name string, f func(In1, In2) Out) {
+ t.Helper()
for _, tt := range tests {
if out := f(tt.in1, tt.in2); !reflect.DeepEqual(out, tt.out) {
t.Errorf("%s(%+v, %+v) = %+v, want %+v", name, tt.in1, tt.in2, out, tt.out)
}
func test3[In1, In2, In3, Out any](t *testing.T, tests []testCase3[In1, In2, In3, Out], name string, f func(In1, In2, In3) Out) {
+ t.Helper()
for _, tt := range tests {
if out := f(tt.in1, tt.in2, tt.in3); !reflect.DeepEqual(out, tt.out) {
t.Errorf("%s(%+v, %+v, %+v) = %+v, want %+v", name, tt.in1, tt.in2, tt.in3, out, tt.out)
}
return true
}
+
+// ModIsPrerelease reports whether v is a prerelease version for the module with the given path.
+// The caller is assumed to have checked that ModIsValid(path, vers) is true.
+func ModIsPrerelease(path, vers string) bool {
+ if IsToolchain(path) {
+ return IsPrerelease(vers)
+ }
+ return semver.Prerelease(vers) != ""
+}
explain += "toolchain " + Startup.AutoToolchain
}
}
- return fmt.Sprintf("%v requires go %v (running go %v%v)", e.What, e.GoVersion, Local(), explain)
+ return fmt.Sprintf("%v requires go >= %v (running go %v%v)", e.What, e.GoVersion, Local(), explain)
}
var ErrTooNew = errors.New("module too new")
"cmd/go/internal/modload"
"cmd/go/internal/par"
"cmd/go/internal/search"
+ "cmd/go/internal/toolchain"
"cmd/go/internal/work"
"golang.org/x/mod/modfile"
oldReqs := reqsFromGoMod(modload.ModFile())
if err := modload.WriteGoMod(ctx); err != nil {
+ if tooNew, ok := err.(*gover.TooNewError); ok {
+ // This can happen for 'go get go@newversion'
+ // when all the required modules are old enough
+ // but the command line is not.
+ // TODO(bcmills): Perhaps LoadModGraph should catch this,
+ // in which case the tryVersion here should be removed.
+ tryVersion(ctx, tooNew.GoVersion)
+ }
base.Fatalf("go: %v", err)
}
for {
prevResolved := resolved
+ // If we found modules that were too new, find the max of the required versions
+ // and then try to switch to a newer toolchain.
+ goVers := ""
+ for _, q := range queries {
+ for _, cs := range q.candidates {
+ if e, ok := cs.err.(*gover.TooNewError); ok && gover.Compare(goVers, e.GoVersion) < 0 {
+ goVers = e.GoVersion
+ }
+ }
+ }
+ if goVers != "" {
+ tryVersion(ctx, goVers)
+ }
+
for _, q := range queries {
unresolved := q.candidates[:0]
var noPackage *modload.PackageNotInModuleError
return isNoSuchModuleVersion(err) || errors.As(err, &noPackage)
}
+
+// tryVersion tries to switch to a Go toolchain appropriate for version,
+// which was either found in a go.mod file of a dependency or resolved
+// on the command line from go@v.
+func tryVersion(ctx context.Context, version string) {
+ if !gover.IsValid(version) {
+ fmt.Fprintf(os.Stderr, "go: misuse of tryVersion: invalid version %q\n", version)
+ return
+ }
+ if (!toolchain.HasAuto() && !toolchain.HasPath()) || gover.Compare(version, gover.Local()) <= 0 {
+ return
+ }
+ tv, err := toolchain.NewerToolchain(ctx, version)
+ if err != nil {
+ base.Errorf("go: %v\n", err)
+ }
+ fmt.Fprintf(os.Stderr, "go: switching to %v\n", tv)
+ toolchain.SwitchTo(tv)
+}
// OverrideRoots edits the global requirement roots by replacing the specific module versions.
func OverrideRoots(ctx context.Context, replace []module.Version) {
- rs := requirements
+ requirements = overrideRoots(ctx, requirements, replace)
+}
+
+func overrideRoots(ctx context.Context, rs *Requirements, replace []module.Version) *Requirements {
drop := make(map[string]bool)
for _, m := range replace {
drop[m.Path] = true
}
roots = append(roots, replace...)
gover.ModSort(roots)
- requirements = newRequirements(rs.pruning, roots, rs.direct)
+ return newRequirements(rs.pruning, roots, rs.direct)
}
// A ConstraintError describes inconsistent constraints in EditBuildList
// TODO(#45551): Do something more principled instead of checking
// cfg.CmdName directly here.
if cfg.BuildMod == "mod" && cfg.CmdName != "mod graph" && cfg.CmdName != "mod why" {
+ // go line is missing from go.mod; add one there and add to derived requirements.
addGoStmt(MainModules.ModFile(mainModule), mainModule, gover.Local())
+ if cfg.CmdName != "mod tidy" {
+ // We want to add the "go" line to the module load in general,
+ // if we do it in "mod tidy", then go mod tidy -go=older for some older version
+ // when we are in a module with no go line will see gover.Local() in the
+ // requirement graph and then report that -go=older is invalid.
+ // go test -run=Script/mod_tidy_version will fail without the tidy exclusion.
+ rs = overrideRoots(ctx, rs, []module.Version{{Path: "go", Version: gover.Local()}})
+ }
// We need to add a 'go' version to the go.mod file, but we must assume
// that its existing contents match something between Go 1.11 and 1.16.
roots = append(roots, module.Version{Path: "go", Version: workFile.Go.Version})
direct["go"] = true
}
- if workFile.Toolchain != nil {
- roots = append(roots, module.Version{Path: "toolchain", Version: workFile.Toolchain.Name})
- direct["toolchain"] = true
- }
+ // Do not add toolchain to roots.
+ // We only want to see it in roots if it is on the command line.
} else {
pruning = pruningForGoVersion(MainModules.GoVersion())
if len(modFiles) != 1 {
roots = append(roots, module.Version{Path: "go", Version: modFile.Go.Version})
direct["go"] = true
}
- if modFile.Toolchain != nil {
- roots = append(roots, module.Version{Path: "toolchain", Version: modFile.Toolchain.Name})
- direct["toolchain"] = true
- }
+ // Do not add "toolchain" to roots.
+ // We only want to see it in roots if it is on the command line.
}
gover.ModSort(roots)
rs := newRequirements(pruning, roots, direct)
var list []*modfile.Require
toolchain := ""
+ wroteGo := false
for _, m := range requirements.rootModules {
if m.Path == "go" {
+ wroteGo = true
forceGoStmt(modFile, mainModule, m.Version)
continue
}
})
}
+ var oldToolchain string
+ if modFile.Toolchain != nil {
+ oldToolchain = modFile.Toolchain.Name
+ }
+ oldToolVers := gover.FromToolchain(oldToolchain)
+
// Update go and toolchain lines.
- tv := gover.FromToolchain(toolchain)
+ toolVers := gover.FromToolchain(toolchain)
+
// Set go version if missing.
if modFile.Go == nil || modFile.Go.Version == "" {
+ wroteGo = true
v := modFileGoVersion(modFile)
- if tv != "" && gover.Compare(v, tv) > 0 {
- v = tv
+ if toolVers != "" && gover.Compare(v, toolVers) > 0 {
+ v = toolVers
}
modFile.AddGoStmt(v)
}
if gover.Compare(modFile.Go.Version, gover.Local()) > 0 {
- // TODO: Reinvoke the newer toolchain if GOTOOLCHAIN=auto.
- base.Fatalf("go: %v", &gover.TooNewError{What: "updating go.mod", GoVersion: modFile.Go.Version})
+ // We cannot assume that we know how to update a go.mod to a newer version.
+ return &gover.TooNewError{What: "updating go.mod", GoVersion: modFile.Go.Version}
}
- // If toolchain is older than go version, drop it.
- if gover.Compare(modFile.Go.Version, tv) >= 0 {
+ // If we update the go line and don't have an explicit instruction
+ // for what to write in toolchain, make sure toolchain is at least our local version,
+ // for reproducibility.
+ if wroteGo && toolchain == "" && gover.Compare(oldToolVers, gover.Local()) < 0 && gover.Compare(modFile.Go.Version, GoStrictVersion) >= 0 {
+ toolVers = gover.Local()
+ toolchain = "go" + toolVers
+ }
+
+ // Default to old toolchain.
+ if toolchain == "" {
+ toolchain = oldToolchain
+ toolVers = oldToolVers
+ }
+ if toolchain == "none" {
toolchain = ""
}
+
// Remove or add toolchain as needed.
- if toolchain == "" {
+ // If toolchain is older than go version, drop it.
+ if toolchain == "" || gover.Compare(modFile.Go.Version, toolVers) >= 0 {
modFile.DropToolchainStmt()
} else {
modFile.AddToolchainStmt(toolchain)
}
// go.mod files did not always require a 'go' version, so do not error out
- // if one is missing — we may be inside an older module in the module cache
+ // if one is missing — we may be inside an older module
// and want to bias toward providing useful behavior.
// go lines are required if we need to declare version 1.17 or later.
// Note that as of CL 303229, a missing go directive implies 1.16,
// not “the latest Go version”.
if goV != i.goVersion && i.goVersion == "" && cfg.BuildMod != "mod" && gover.Compare(goV, "1.17") < 0 {
goV = ""
+ if toolchain != i.toolchain && i.toolchain == "" {
+ toolchain = ""
+ }
}
if goV != i.goVersion ||
}
}
- if semver.Prerelease(v) != "" {
+ if gover.ModIsPrerelease(qm.path, v) {
prereleases = append(prereleases, v)
} else {
releases = append(releases, v)
--- /dev/null
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !js && !wasip1
+
+package toolchain
+
+import (
+ "cmd/go/internal/base"
+ "internal/godebug"
+ "os"
+ "os/exec"
+ "runtime"
+ "syscall"
+)
+
+// execGoToolchain execs the Go toolchain with the given name (gotoolchain),
+// GOROOT directory, and go command executable.
+// The GOROOT directory is empty if we are invoking a command named
+// gotoolchain found in $PATH.
+func execGoToolchain(gotoolchain, dir, exe string) {
+ os.Setenv(gotoolchainSwitchEnv, "1")
+ if dir == "" {
+ os.Unsetenv("GOROOT")
+ } else {
+ os.Setenv("GOROOT", dir)
+ }
+
+ // On Windows, there is no syscall.Exec, so the best we can do
+ // is run a subprocess and exit with the same status.
+ // Doing the same on Unix would be a problem because it wouldn't
+ // propagate signals and such, but there are no signals on Windows.
+ // We also use the exec case when GODEBUG=gotoolchainexec=0,
+ // to allow testing this code even when not on Windows.
+ if godebug.New("#gotoolchainexec").Value() == "0" || runtime.GOOS == "windows" {
+ cmd := exec.Command(exe, os.Args[1:]...)
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ err := cmd.Run()
+ if err != nil {
+ if e, ok := err.(*exec.ExitError); ok && e.ProcessState != nil {
+ if e.ProcessState.Exited() {
+ os.Exit(e.ProcessState.ExitCode())
+ }
+ base.Fatalf("exec %s: %s", gotoolchain, e.ProcessState)
+ }
+ base.Fatalf("exec %s: %s", exe, err)
+ }
+ os.Exit(0)
+ }
+ err := syscall.Exec(exe, os.Args, os.Environ())
+ base.Fatalf("exec %s: %v", gotoolchain, err)
+}
//go:build js || wasip1
-package main
+package toolchain
-// nop for systems that don't even define syscall.Exec, like js/wasm.
-func switchGoToolchain() {
+import "cmd/go/internal/base"
+
+func execGoToolchain(gotoolchain, dir, exe string) {
+ base.Fatalf("execGoToolchain unsupported")
}
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-//go:build !js && !wasip1
-
-package main
+// Package toolchain implements dynamic switching of Go toolchains.
+package toolchain
import (
"context"
"fmt"
"go/build"
- "internal/godebug"
"io/fs"
"log"
"os"
"path/filepath"
"runtime"
"strings"
- "syscall"
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/gover"
"cmd/go/internal/modcmd"
+ "cmd/go/internal/modfetch"
"cmd/go/internal/modload"
"cmd/go/internal/run"
gotoolchainSwitchEnv = "GOTOOLCHAIN_INTERNAL_SWITCH"
)
-// switchGoToolchain invokes a different Go toolchain if directed by
+// Switch invokes a different Go toolchain if directed by
// the GOTOOLCHAIN environment variable or the user's configuration
// or go.mod file.
-func switchGoToolchain() {
+// It must be called early in startup.
+func Switch() {
log.SetPrefix("go: ")
defer log.SetPrefix("")
minToolchain = "go" + minVers
}
- pathOnly := gotoolchain == "path"
if gotoolchain == "auto" || gotoolchain == "path" {
gotoolchain = minToolchain
base.Fatalf("invalid GOTOOLCHAIN %q", gotoolchain)
}
+ SwitchTo(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.
+//
+// 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 release.
+func NewerToolchain(ctx context.Context, version string) (string, error) {
+ var versions *modfetch.Versions
+ err := modfetch.TryProxies(func(proxy string) error {
+ v, err := modfetch.Lookup(ctx, proxy, "go").Versions(ctx, "")
+ if err != nil {
+ return err
+ }
+ versions = v
+ return nil
+ })
+ if err != nil {
+ return "", err
+ }
+ return newerToolchain(version, versions.List)
+}
+
+// newerToolchain implements NewerToolchain where the list of choices is known.
+// It is separated out for easier testing of this logic.
+func newerToolchain(need string, list []string) (string, error) {
+ // Consider each release in the list, from newest to oldest,
+ // considering only entries >= need and then only entries
+ // that are the latest in their language family
+ // (the latest 1.40, the latest 1.39, and so on).
+ // We prefer the latest patch release before the most recent release family,
+ // so if the latest release is 1.40.1 we'll take the latest 1.39.X.
+ // Failing that, we prefer the latest patch release before the most recent
+ // prerelease family, so if the latest release is 1.40rc1 is out but 1.39 is okay,
+ // we'll still take 1.39.X.
+ // Failing that we'll take the latest release.
+ latest := ""
+ for i := len(list) - 1; i >= 0; i-- {
+ v := list[i]
+ if gover.Compare(v, need) < 0 {
+ break
+ }
+ if gover.Lang(latest) == gover.Lang(v) {
+ continue
+ }
+ newer := latest
+ latest = v
+ if newer != "" && !gover.IsPrerelease(newer) {
+ // latest is the last patch release of Go 1.X, and we saw a non-prerelease of Go 1.(X+1),
+ // so latest is the one we want.
+ break
+ }
+ }
+ if latest == "" {
+ return "", fmt.Errorf("no releases found for go >= %v", need)
+ }
+ return "go" + latest, nil
+}
+
+// HasAuto reports whether the GOTOOLCHAIN setting allows "auto" upgrades.
+func HasAuto() bool {
+ env := cfg.Getenv("GOTOOLCHAIN")
+ return env == "auto" || strings.HasSuffix(env, "+auto")
+}
+
+// HasPath reports whether the GOTOOLCHAIN setting allows "path" upgrades.
+func HasPath() bool {
+ env := cfg.Getenv("GOTOOLCHAIN")
+ return env == "path" || strings.HasSuffix(env, "+path")
+}
+
+// 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
+// a toolchain if necessary.
+func SwitchTo(gotoolchain string) {
+ log.SetPrefix("go: ")
+
+ env := cfg.Getenv("GOTOOLCHAIN")
+ pathOnly := env == "path" || strings.HasSuffix(env, "+path")
+
+ // For testing, if TESTGO_VERSION is already in use
+ // (only happens in the cmd/go test binary)
+ // and TESTGO_VERSION_SWITCH=1 is set,
+ // "switch" toolchains by changing TESTGO_VERSION
+ // and reinvoking the current binary.
+ if gover.TestVersion != "" && os.Getenv("TESTGO_VERSION_SWITCH") == "1" {
+ os.Setenv("TESTGO_VERSION", gotoolchain)
+ exe, err := os.Executable()
+ if err != nil {
+ base.Fatalf("%v", err)
+ }
+ execGoToolchain(gotoolchain, os.Getenv("GOROOT"), exe)
+ }
+
// Look in PATH for the toolchain before we download one.
// This allows custom toolchains as well as reuse of toolchains
// already installed using go install golang.org/dl/go1.2.3@latest.
}
// Set up modules without an explicit go.mod, to download distribution.
+ modload.Reset()
modload.ForceUseModules = true
modload.RootMode = modload.NoRoot
modload.Init()
execGoToolchain(gotoolchain, dir, filepath.Join(dir, "bin/go"))
}
-// execGoToolchain execs the Go toolchain with the given name (gotoolchain),
-// GOROOT directory, and go command executable.
-// The GOROOT directory is empty if we are invoking a command named
-// gotoolchain found in $PATH.
-func execGoToolchain(gotoolchain, dir, exe string) {
- os.Setenv(gotoolchainSwitchEnv, "1")
- if dir == "" {
- os.Unsetenv("GOROOT")
- } else {
- os.Setenv("GOROOT", dir)
- }
-
- // On Windows, there is no syscall.Exec, so the best we can do
- // is run a subprocess and exit with the same status.
- // Doing the same on Unix would be a problem because it wouldn't
- // propagate signals and such, but there are no signals on Windows.
- // We also use the exec case when GODEBUG=gotoolchainexec=0,
- // to allow testing this code even when not on Windows.
- if godebug.New("#gotoolchainexec").Value() == "0" || runtime.GOOS == "windows" {
- cmd := exec.Command(exe, os.Args[1:]...)
- cmd.Stdin = os.Stdin
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- fmt.Fprintln(os.Stderr, cmd.Args)
- err := cmd.Run()
- if err != nil {
- if e, ok := err.(*exec.ExitError); ok && e.ProcessState != nil {
- if e.ProcessState.Exited() {
- os.Exit(e.ProcessState.ExitCode())
- }
- base.Fatalf("exec %s: %s", gotoolchain, e.ProcessState)
- }
- base.Fatalf("exec %s: %s", exe, err)
- }
- os.Exit(0)
- }
- err := syscall.Exec(exe, os.Args, os.Environ())
- base.Fatalf("exec %s: %v", gotoolchain, err)
-}
-
// modGoToolchain finds the enclosing go.work or go.mod file
// and returns the go version and toolchain lines from the file.
// The toolchain line overrides the version line
--- /dev/null
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package toolchain
+
+import (
+ "strings"
+ "testing"
+)
+
+func TestNewerToolchain(t *testing.T) {
+ for _, tt := range newerToolchainTests {
+ out, err := newerToolchain(tt.need, tt.list)
+ if (err != nil) != (out == "") {
+ t.Errorf("newerToolchain(%v, %v) = %v, %v, want error", tt.need, tt.list, out, err)
+ continue
+ }
+ if out != tt.out {
+ t.Errorf("newerToolchain(%v, %v) = %v, %v want %v, nil", tt.need, tt.list, out, err, tt.out)
+ }
+ }
+}
+
+var f = strings.Fields
+
+var relRC = []string{"1.39.0", "1.39.1", "1.39.2", "1.40.0", "1.40.1", "1.40.2", "1.41rc1"}
+var rel2 = []string{"1.39.0", "1.39.1", "1.39.2", "1.40.0", "1.40.1", "1.40.2"}
+var rel0 = []string{"1.39.0", "1.39.1", "1.39.2", "1.40.0"}
+var newerToolchainTests = []struct {
+ need string
+ list []string
+ out string
+}{
+ {"1.30", rel0, "go1.39.2"},
+ {"1.30", rel2, "go1.39.2"},
+ {"1.30", relRC, "go1.39.2"},
+ {"1.38", rel0, "go1.39.2"},
+ {"1.38", rel2, "go1.39.2"},
+ {"1.38", relRC, "go1.39.2"},
+ {"1.38.1", rel0, "go1.39.2"},
+ {"1.38.1", rel2, "go1.39.2"},
+ {"1.38.1", relRC, "go1.39.2"},
+ {"1.39", rel0, "go1.39.2"},
+ {"1.39", rel2, "go1.39.2"},
+ {"1.39", relRC, "go1.39.2"},
+ {"1.39.2", rel0, "go1.39.2"},
+ {"1.39.2", rel2, "go1.39.2"},
+ {"1.39.2", relRC, "go1.39.2"},
+ {"1.39.3", rel0, "go1.40.0"},
+ {"1.39.3", rel2, "go1.40.2"},
+ {"1.39.3", relRC, "go1.40.2"},
+ {"1.40", rel0, "go1.40.0"},
+ {"1.40", rel2, "go1.40.2"},
+ {"1.40", relRC, "go1.40.2"},
+ {"1.40.1", rel0, ""},
+ {"1.40.1", rel2, "go1.40.2"},
+ {"1.40.1", relRC, "go1.40.2"},
+ {"1.41", rel0, ""},
+ {"1.41", rel2, ""},
+ {"1.41", relRC, "go1.41rc1"},
+ {"1.41.0", rel0, ""},
+ {"1.41.0", rel2, ""},
+ {"1.41.0", relRC, ""},
+ {"1.40", nil, ""},
+}
package main
import (
+ "cmd/go/internal/toolchain"
"cmd/go/internal/workcmd"
"context"
"flag"
func main() {
log.SetFlags(0)
- switchGoToolchain()
+ toolchain.Switch()
flag.Usage = base.Usage
flag.Parse()
--- /dev/null
+golang.org/toolchain v0.0.1-go1.18.1.linux-amd64
+written by hand
+-- .info --
+{"Version":"v0.0.1-go1.18.1.linux-amd64"}
+-- .mod --
+golang.org/toolchain
+-- go.mod --
+golang.org/toolchain
--- /dev/null
+golang.org/toolchain v0.0.1-go1.18.3.linux-amd64
+written by hand
+-- .info --
+{"Version":"v0.0.1-go1.18.3.linux-amd64"}
+-- .mod --
+golang.org/toolchain
+-- go.mod --
+golang.org/toolchain
--- /dev/null
+golang.org/toolchain v0.0.1-go1.18.5.linux-amd64
+written by hand
+-- .info --
+{"Version":"v0.0.1-go1.18.5.linux-amd64"}
+-- .mod --
+golang.org/toolchain
+-- go.mod --
+golang.org/toolchain
--- /dev/null
+golang.org/toolchain v0.0.1-go1.18.7.linux-amd64
+written by hand
+-- .info --
+{"Version":"v0.0.1-go1.18.7.linux-amd64"}
+-- .mod --
+golang.org/toolchain
+-- go.mod --
+golang.org/toolchain
--- /dev/null
+golang.org/toolchain v0.0.1-go1.18.9.linux-amd64
+written by hand
+-- .info --
+{"Version":"v0.0.1-go1.18.9.linux-amd64"}
+-- .mod --
+golang.org/toolchain
+-- go.mod --
+golang.org/toolchain
--- /dev/null
+golang.org/toolchain v0.0.1-go1.18.linux-amd64
+written by hand
+-- .info --
+{"Version":"v0.0.1-go1.18.linux-amd64"}
+-- .mod --
+golang.org/toolchain
+-- go.mod --
+golang.org/toolchain
--- /dev/null
+golang.org/toolchain v0.0.1-go1.22.0.linux-amd64
+written by hand
+-- .info --
+{"Version":"v0.0.1-go1.22.0.linux-amd64"}
+-- .mod --
+golang.org/toolchain
+-- go.mod --
+golang.org/toolchain
--- /dev/null
+golang.org/toolchain v0.0.1-go1.22.1.linux-amd64
+written by hand
+-- .info --
+{"Version":"v0.0.1-go1.22.1.linux-amd64"}
+-- .mod --
+golang.org/toolchain
+-- go.mod --
+golang.org/toolchain
--- /dev/null
+golang.org/toolchain v0.0.1-go1.22.3.linux-amd64
+written by hand
+-- .info --
+{"Version":"v0.0.1-go1.22.3.linux-amd64"}
+-- .mod --
+golang.org/toolchain
+-- go.mod --
+golang.org/toolchain
--- /dev/null
+golang.org/toolchain v0.0.1-go1.22.5.linux-amd64
+written by hand
+-- .info --
+{"Version":"v0.0.1-go1.22.5.linux-amd64"}
+-- .mod --
+golang.org/toolchain
+-- go.mod --
+golang.org/toolchain
--- /dev/null
+golang.org/toolchain v0.0.1-go1.22.7.linux-amd64
+written by hand
+-- .info --
+{"Version":"v0.0.1-go1.22.7.linux-amd64"}
+-- .mod --
+golang.org/toolchain
+-- go.mod --
+golang.org/toolchain
--- /dev/null
+golang.org/toolchain v0.0.1-go1.22.9.linux-amd64
+written by hand
+-- .info --
+{"Version":"v0.0.1-go1.22.9.linux-amd64"}
+-- .mod --
+golang.org/toolchain
+-- go.mod --
+golang.org/toolchain
--- /dev/null
+golang.org/toolchain v0.0.1-go1.22rc1.linux-amd64
+written by hand
+-- .info --
+{"Version":"v0.0.1-go1.22rc1.linux-amd64"}
+-- .mod --
+golang.org/toolchain
+-- go.mod --
+golang.org/toolchain
--- /dev/null
+golang.org/toolchain v0.0.1-go1.23.0.linux-amd64
+written by hand
+-- .info --
+{"Version":"v0.0.1-go1.23.0.linux-amd64"}
+-- .mod --
+golang.org/toolchain
+-- go.mod --
+golang.org/toolchain
--- /dev/null
+golang.org/toolchain v0.0.1-go1.23.5.linux-amd64
+written by hand
+-- .info --
+{"Version":"v0.0.1-go1.23.5.linux-amd64"}
+-- .mod --
+golang.org/toolchain
+-- go.mod --
+golang.org/toolchain
--- /dev/null
+golang.org/toolchain v0.0.1-go1.23.9.linux-amd64
+written by hand
+-- .info --
+{"Version":"v0.0.1-go1.23.9.linux-amd64"}
+-- .mod --
+golang.org/toolchain
+-- go.mod --
+golang.org/toolchain
--- /dev/null
+golang.org/toolchain v0.0.1-go1.24rc1.linux-amd64
+written by hand
+-- .info --
+{"Version":"v0.0.1-go1.24rc1.linux-amd64"}
+-- .mod --
+golang.org/toolchain
+-- go.mod --
+golang.org/toolchain
--- /dev/null
+rsc.io/needall 0.0.1
+written by hand
+
+-- .mod --
+module rsc.io/needall
+go 1.23
+
+require rsc.io/needgo121 v0.0.1
+require rsc.io/needgo122 v0.0.1
+require rsc.io/needgo123 v0.0.1
+
+-- go.mod --
+module rsc.io/needall
+go 1.23
+
+require rsc.io/needgo121 v0.0.1
+require rsc.io/needgo122 v0.0.1
+require rsc.io/needgo123 v0.0.1
+
+-- .info --
+{"Version":"v0.0.1"}
+-- p.go --
+package p
+
+func F() {}
--- /dev/null
+rsc.io/needgo1183 v0.0.1
+written by hand
+
+-- .mod --
+module rsc.io/needgo1183
+go 1.18.3
+
+-- go.mod --
+module rsc.io/needgo1183
+go 1.18.3
+
+-- .info --
+{"Version":"v0.0.1"}
+-- p.go --
+package p
+
+func F() {}
--- /dev/null
+rsc.io/needgo118 0.0.1
+written by hand
+
+-- .mod --
+module rsc.io/needgo118
+go 1.18
+
+-- go.mod --
+module rsc.io/needgo118
+go 1.18
+
+-- .info --
+{"Version":"v0.0.1"}
+-- p.go --
+package p
+
+func F() {}
--- /dev/null
+rsc.io/needgo121 0.0.1
+written by hand
+
+-- .mod --
+module rsc.io/needgo121
+go 1.21
+
+-- go.mod --
+module rsc.io/needgo121
+go 1.21
+
+-- .info --
+{"Version":"v0.0.1"}
+-- p.go --
+package p
+
+func F() {}
--- /dev/null
+rsc.io/needgo1223 0.0.1
+written by hand
+
+-- .mod --
+module rsc.io/needgo1223
+go 1.22.3
+
+-- go.mod --
+module rsc.io/needgo1223
+go 1.22.3
+
+-- .info --
+{"Version":"v0.0.1"}
+-- p.go --
+package p
+
+func F() {}
--- /dev/null
+rsc.io/needgo122 0.0.1
+written by hand
+
+-- .mod --
+module rsc.io/needgo122
+go 1.22
+
+-- go.mod --
+module rsc.io/needgo122
+go 1.22
+
+-- .info --
+{"Version":"v0.0.1"}
+-- p.go --
+package p
+
+func F() {}
--- /dev/null
+rsc.io/needgo123 0.0.1
+written by hand
+
+-- .mod --
+module rsc.io/needgo123
+go 1.23
+
+-- go.mod --
+module rsc.io/needgo123
+go 1.23
+
+-- .info --
+{"Version":"v0.0.1"}
+-- p.go --
+package p
+
+func F() {}
--- /dev/null
+rsc.io/needgo124 0.0.1
+written by hand
+
+-- .mod --
+module rsc.io/needgo124
+go 1.24
+
+-- go.mod --
+module rsc.io/needgo124
+go 1.24
+
+-- .info --
+{"Version":"v0.0.1"}
+-- p.go --
+package p
+
+func F() {}
# toolchain local in go.mod
cp go1999toolchainlocal go.mod
! go build
-stderr '^go: go.mod requires go 1.999 \(running go 1.100; go.mod sets go 1.999, toolchain local\)$'
+stderr '^go: go.mod requires go >= 1.999 \(running go 1.100; go.mod sets go 1.999, toolchain local\)$'
# toolchain local in go.work
cp empty go.mod
cp go1999toolchainlocal go.work
! go build
-stderr '^go: go.work requires go 1.999 \(running go 1.100; go.work sets go 1.999, toolchain local\)$'
+stderr '^go: go.work requires go >= 1.999 \(running go 1.100; go.work sets go 1.999, toolchain local\)$'
rm go.work
# toolchain line in go.work
--- /dev/null
+env TESTGO_VERSION=go1.21
+env TESTGO_VERSION_SWITCH=1
+
+# GOTOOLCHAIN=auto should run the newer toolchain
+env GOTOOLCHAIN=auto
+cp go.mod.new go.mod
+go get rsc.io/needgo121 rsc.io/needgo122 rsc.io/needgo123 rsc.io/needall
+stderr '^go: switching to go1.23.9$'
+stderr '^go: added rsc.io/needall v0.0.1'
+! stderr 'requires go >= 1.23'
+grep 'go 1.23' go.mod
+grep 'toolchain go1.23.9' go.mod
+
+# GOTOOLCHAIN=min+auto should run the newer toolchain
+env GOTOOLCHAIN=go1.21+auto
+cp go.mod.new go.mod
+go get rsc.io/needgo121 rsc.io/needgo122 rsc.io/needgo123 rsc.io/needall
+stderr '^go: switching to go1.23.9$'
+stderr '^go: added rsc.io/needall v0.0.1'
+! stderr 'requires go >= 1.23'
+grep 'go 1.23' go.mod
+grep 'toolchain go1.23.9' go.mod
+
+# GOTOOLCHAIN=go1.21 should NOT run the newer toolchain
+env GOTOOLCHAIN=go1.21
+cp go.mod.new go.mod
+! go get rsc.io/needgo121 rsc.io/needgo122 rsc.io/needgo123 rsc.io/needall
+! stderr 'switching to go'
+stderr 'rsc.io/needgo122@v0.0.1 requires go >= 1.22'
+stderr 'rsc.io/needgo123@v0.0.1 requires go >= 1.23'
+stderr 'rsc.io/needall@v0.0.1 requires go >= 1.23'
+stderr 'requires go >= 1.23'
+! stderr 'requires go >= 1.21' # that's us!
+cmp go.mod go.mod.new
+
+# GOTOOLCHAIN=local should NOT run the newer toolchain
+env GOTOOLCHAIN=local
+cp go.mod.new go.mod
+! go get rsc.io/needgo121 rsc.io/needgo122 rsc.io/needgo123 rsc.io/needall
+! stderr 'switching to go'
+stderr 'rsc.io/needgo122@v0.0.1 requires go >= 1.22'
+stderr 'rsc.io/needgo123@v0.0.1 requires go >= 1.23'
+stderr 'rsc.io/needall@v0.0.1 requires go >= 1.23'
+stderr 'requires go >= 1.23'
+! stderr 'requires go >= 1.21' # that's us!
+cmp go.mod go.mod.new
+
+# go get go@1.22 should resolve to the latest 1.22
+env GOTOOLCHAIN=local
+cp go.mod.new go.mod
+! go get go@1.22
+stderr '^go: updating go.mod requires go >= 1.22.9 \(running go 1.21; GOTOOLCHAIN=local\)'
+
+env GOTOOLCHAIN=auto
+cp go.mod.new go.mod
+go get go@1.22
+stderr '^go: switching to go1.22.9$'
+
+# go get go@1.22rc1 should use 1.22rc1 exactly, not a later release.
+env GOTOOLCHAIN=local
+cp go.mod.new go.mod
+! go get go@1.22rc1
+stderr '^go: updating go.mod requires go >= 1.22rc1 \(running go 1.21; GOTOOLCHAIN=local\)'
+
+env GOTOOLCHAIN=auto
+cp go.mod.new go.mod
+go get go@1.22rc1
+stderr '^go: switching to go1.22.9$'
+stderr '^go: upgraded go 1.1 => 1.22rc1$'
+stderr '^go: added toolchain go1.22.9$'
+
+# go get go@1.22.1 should use 1.22.1 exactly, not a later release.
+env GOTOOLCHAIN=local
+cp go.mod.new go.mod
+! go get go@1.22.1
+stderr '^go: updating go.mod requires go >= 1.22.1 \(running go 1.21; GOTOOLCHAIN=local\)'
+
+env GOTOOLCHAIN=auto
+cp go.mod.new go.mod
+go get go@1.22.1
+stderr '^go: switching to go1.22.9$'
+stderr '^go: upgraded go 1.1 => 1.22.1$'
+stderr '^go: added toolchain go1.22.9$'
+
+# go get needgo122 (says 'go 1.22') should use 1.22.0, the earliest release we have available
+# (ignoring prereleases).
+env GOTOOLCHAIN=local
+cp go.mod.new go.mod
+! go get rsc.io/needgo122
+stderr '^go: rsc.io/needgo122@v0.0.1 requires go >= 1.22 \(running go 1.21; GOTOOLCHAIN=local\)'
+
+env GOTOOLCHAIN=auto
+cp go.mod.new go.mod
+go get rsc.io/needgo122
+stderr '^go: upgraded go 1.1 => 1.22$'
+stderr '^go: switching to go1.22.9$'
+stderr '^go: added toolchain go1.22.9$'
+
+# go get needgo1223 (says 'go 1.22.3') should use go 1.22.3
+env GOTOOLCHAIN=local
+cp go.mod.new go.mod
+! go get rsc.io/needgo1223
+stderr '^go: rsc.io/needgo1223@v0.0.1 requires go >= 1.22.3 \(running go 1.21; GOTOOLCHAIN=local\)'
+
+env GOTOOLCHAIN=auto
+cp go.mod.new go.mod
+go get rsc.io/needgo1223
+stderr '^go: upgraded go 1.1 => 1.22.3$'
+stderr '^go: switching to go1.22.9$'
+stderr '^go: added toolchain go1.22.9$'
+
+# go get needgo124 (says 'go 1.24') should use go 1.24rc1, the only version available
+env GOTOOLCHAIN=local
+cp go.mod.new go.mod
+! go get rsc.io/needgo124
+stderr '^go: rsc.io/needgo124@v0.0.1 requires go >= 1.24 \(running go 1.21; GOTOOLCHAIN=local\)'
+
+env GOTOOLCHAIN=auto
+cp go.mod.new go.mod
+go get rsc.io/needgo124
+stderr '^go: switching to go1.24rc1'
+stderr '^go: upgraded go 1.1 => 1.24$'
+stderr '^go: added toolchain go1.24rc1$'
+
+-- go.mod.new --
+module m
+go 1.1
+
+-- p.go --
+package p
env TESTGO_VERSION=go1.21
! go mod download rsc.io/future@v1.0.0
-stderr '^go: rsc.io/future@v1.0.0 requires go 1.999 \(running go 1.21; go.mod sets go 1.21\)$'
+stderr '^go: rsc.io/future@v1.0.0 requires go >= 1.999 \(running go 1.21; go.mod sets go 1.21\)$'
-- go.mod --
module m
env TESTGO_VERSION=go1.21
! go list
-stderr -count=1 '^go: sub@v1.0.0: sub requires go 1.999 \(running go 1.21; go.mod sets go 1.1\)$'
+stderr -count=1 '^go: sub@v1.0.0: sub requires go >= 1.999 \(running go 1.21; go.mod sets go 1.1\)$'
! go build sub
-stderr -count=1 '^go: sub@v1.0.0: sub requires go 1.999 \(running go 1.21; go.mod sets go 1.1\)$'
+stderr -count=1 '^go: sub@v1.0.0: sub requires go >= 1.999 \(running go 1.21; go.mod sets go 1.1\)$'
-- go.mod --
module m
# go.mod too new
env GOTOOLCHAIN=local
! go build .
-stderr '^go: go.mod requires go 1.99999 \(running go 1\..+\)$'
+stderr '^go: go.mod requires go >= 1.99999 \(running go 1\..+\)$'
# go.mod referenced from go.work too new
cp go.work.old go.work
! go build .
-stderr '^go: go.mod requires go 1.99999 \(running go 1\..+\)$'
+stderr '^go: go.mod requires go >= 1.99999 \(running go 1\..+\)$'
# go.work too new
cp go.work.new go.work
cp go.mod.old go.mod
! go build .
-stderr '^go: go.work requires go 1.99999 \(running go 1\..+\)$'
+stderr '^go: go.work requires go >= 1.99999 \(running go 1\..+\)$'
# vendor too new
rm go.work
mv notvendor vendor
! go build -mod=vendor .
-stderr '^go: golang.org/x/text in vendor'${/}'modules.txt requires go 1.99999 \(running go 1\..+\)$'
+stderr '^go: golang.org/x/text in vendor'${/}'modules.txt requires go >= 1.99999 \(running go 1\..+\)$'
-- go.mod --
module example
-[!net:golang.org] skip
-
-env GOPROXY=https://proxy.golang.org/
env TESTGO_VERSION=go1.100
-go get toolchain@go1.20.1
-stderr '^go: added toolchain go1.20.1$'
+env TESTGO_VERSION_SWITCH=1
+
+go get toolchain@go1.22.1
+stderr '^go: added toolchain go1.22.1$'
! stderr '(added|removed|upgraded|downgraded) go'
-grep 'toolchain go1.20.1' go.mod
+grep 'toolchain go1.22.1' go.mod
go get toolchain@none
-stderr '^go: removed toolchain go1.20.1$'
+stderr '^go: removed toolchain go1.22.1$'
! stderr '(added|removed|upgraded|downgraded) go'
! grep toolchain go.mod
-go get toolchain@go1.20.1
-stderr '^go: added toolchain go1.20.1$'
+go get toolchain@go1.22.1
+stderr '^go: added toolchain go1.22.1$'
! stderr '(added|removed|upgraded|downgraded) go'
-grep 'toolchain go1.20.1' go.mod
+grep 'toolchain go1.22.1' go.mod
+
+go get go@1.22.3
+stderr '^go: upgraded go 1.10 => 1.22.3$'
+stderr '^go: upgraded toolchain go1.22.1 => go1.100$'
+grep 'go 1.22.3' go.mod
-cat go.mod
-go get go@1.20.3
-stderr '^go: upgraded go 1.10 => 1.20.3$'
-stderr '^go: removed toolchain go1.20.1$'
-grep 'go 1.20.3' go.mod
+go get go@1.22.3 toolchain@1.22.3
+stderr '^go: removed toolchain go1.100$'
! grep toolchain go.mod
-go get go@1.20.1 toolchain@go1.20.3
-stderr '^go: downgraded go 1.20.3 => 1.20.1$'
-stderr '^go: added toolchain go1.20.3$'
-grep 'go 1.20.1' go.mod
-grep 'toolchain go1.20.3' go.mod
+go get go@1.22.1 toolchain@go1.22.3
+stderr '^go: downgraded go 1.22.3 => 1.22.1$'
+stderr '^go: added toolchain go1.22.3$'
+grep 'go 1.22.1' go.mod
+grep 'toolchain go1.22.3' go.mod
-go get go@1.20.3
-stderr '^go: upgraded go 1.20.1 => 1.20.3$'
-stderr '^go: removed toolchain go1.20.3$'
-grep 'go 1.20.3' go.mod
+go get go@1.22.3 toolchain@1.22.3
+stderr '^go: upgraded go 1.22.1 => 1.22.3$'
+stderr '^go: removed toolchain go1.22.3$'
+grep 'go 1.22.3' go.mod
! grep toolchain go.mod
-go get toolchain@1.20.1
-stderr '^go: downgraded go 1.20.3 => 1.20.1$'
- # ! stderr toolchain
-grep 'go 1.20.1' go.mod
+go get toolchain@1.22.1
+stderr '^go: downgraded go 1.22.3 => 1.22.1$'
+! stderr toolchain # already gone, was not added
+grep 'go 1.22.1' go.mod
+! grep toolchain go.mod
-env TESTGO_VERSION=go1.20.1
+env TESTGO_VERSION=go1.22.1
env GOTOOLCHAIN=local
-! go get go@1.20.3
-stderr 'go: updating go.mod requires go 1.20.3 \(running go 1.20.1; GOTOOLCHAIN=local\)$'
-
-go get toolchain@1.20.3
-grep 'toolchain go1.20.3' go.mod
+! go get go@1.22.3
+stderr 'go: updating go.mod requires go >= 1.22.3 \(running go 1.22.1; GOTOOLCHAIN=local\)$'
env TESTGO_VERSION=go1.30
-go get go@1.20.1
-grep 'go 1.20.1' go.mod
+go get toolchain@1.22.3
+grep 'toolchain go1.22.3' go.mod
+
+go get go@1.22.1
+grep 'go 1.22.1' go.mod
go get m2@v1.0.0
-stderr '^go: upgraded go 1.20.1 => 1.22$'
+stderr '^go: upgraded go 1.22.1 => 1.23$'
stderr '^go: added m2 v1.0.0$'
-grep 'go 1.22' go.mod
+grep 'go 1.23$' go.mod
-go mod edit -toolchain=go1.29.0 # cannot go get because it doesn't exist
-go get go@1.28.0
+go get toolchain@go1.23.9 go@1.23.5
go get toolchain@none
-stderr '^go: removed toolchain go1.29.0'
+stderr '^go: removed toolchain go1.23.9'
! stderr ' go 1'
-grep 'go 1.28.0' go.mod
+grep 'go 1.23.5' go.mod
-- go.mod --
module m
-- m2/go.mod --
module m2
-go 1.22
+go 1.23
--- /dev/null
+# Commands in an old module with no go line and no toolchain line,
+# or with only a go line, should succeed.
+# (They should not fail due to the go.mod not being tidy.)
+
+# No go line, no toolchain line.
+go list
+
+# Old go line, no toolchain line.
+go mod edit -go=1.16
+go list
+
+go mod edit -go=1.20
+go list
+
+# New go line, no toolchain line, using same toolchain.
+env TESTGO_VERSION=1.21
+go mod edit -go=1.21
+go list
+
+# New go line, no toolchain line, using newer Go version.
+# (Until we need to update the go line, no toolchain addition.)
+env TESTGO_VERSION=1.21.0
+go list
+
+-- go.mod --
+module m
+-- p.go --
+package p