cmd/go: switch to newer toolchain in go get as needed

If we run 'go get go@1.40' or 'go get m@v' where m has a go.mod
that says 'go 1.40', we need to write a new go.mod that says 'go 1.40'.
But we can't be sure we know how to write a Go 1.40-compatible go.mod.
Instead, download the latest point release of Go 1.40 and invoke it to
finish the get command.

For #57001.

Change-Id: I4133fc3c2ecf91226a6c09a3086275ecc517e223
Reviewed-on: https://go-review.googlesource.com/c/go/+/498118
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Bryan Mills <bcmills@google.com>
Run-TryBot: Russ Cox <rsc@golang.org>
Auto-Submit: Russ Cox <rsc@golang.org>
This commit is contained in:
Russ Cox 2023-05-24 17:37:11 -04:00 committed by Gopher Robot
parent 5b603f79fb
commit 301370c81c
47 changed files with 850 additions and 128 deletions

View File

@ -254,6 +254,12 @@ func runEnv(ctx context.Context, cmd *base.Command, args []string) {
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()...)

View File

@ -10,7 +10,9 @@
// 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
@ -76,6 +78,11 @@ func Lang(x string) string {
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.

View File

@ -77,6 +77,7 @@ var isLangTests = []testCase1[string, bool]{
{"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
}
@ -113,6 +114,7 @@ type testCase3[In1, In2, In3, Out any] struct {
}
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)
@ -121,6 +123,7 @@ func test1[In, Out any](t *testing.T, tests []testCase1[In, Out], name string, f
}
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)
@ -129,6 +132,7 @@ func test2[In1, In2, Out any](t *testing.T, tests []testCase2[In1, In2, Out], na
}
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)

View File

@ -101,3 +101,12 @@ func ModIsPrefix(path, vers string) bool {
}
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) != ""
}

View File

@ -78,7 +78,7 @@ func (e *TooNewError) Error() string {
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")

View File

@ -43,6 +43,7 @@ import (
"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"
@ -379,6 +380,14 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) {
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)
}
@ -1211,6 +1220,20 @@ func (r *resolver) resolveQueries(ctx context.Context, queries []*query) (change
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]
@ -1885,3 +1908,22 @@ func isNoSuchPackageVersion(err error) bool {
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)
}

View File

@ -609,7 +609,10 @@ func EditBuildList(ctx context.Context, add, mustSelect []module.Version) (chang
// 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
@ -622,7 +625,7 @@ func OverrideRoots(ctx context.Context, replace []module.Version) {
}
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

View File

@ -848,7 +848,16 @@ func loadModFile(ctx context.Context, opts *PackageOpts) *Requirements {
// 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.
@ -1163,10 +1172,8 @@ func requirementsFromModFiles(ctx context.Context, workFile *modfile.WorkFile, m
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 {
@ -1197,10 +1204,8 @@ func requirementsFromModFiles(ctx context.Context, workFile *modfile.WorkFile, m
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)
@ -1546,8 +1551,10 @@ func commitRequirements(ctx context.Context) (err error) {
var list []*modfile.Require
toolchain := ""
wroteGo := false
for _, m := range requirements.rootModules {
if m.Path == "go" {
wroteGo = true
forceGoStmt(modFile, mainModule, m.Version)
continue
}
@ -1561,27 +1568,49 @@ func commitRequirements(ctx context.Context) (err error) {
})
}
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)

View File

@ -511,13 +511,16 @@ func (i *modFileIndex) modFileIsDirty(modFile *modfile.File) bool {
}
// 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 ||

View File

@ -546,7 +546,7 @@ func (qm *queryMatcher) filterVersions(ctx context.Context, versions []string) (
}
}
if semver.Prerelease(v) != "" {
if gover.ModIsPrerelease(qm.path, v) {
prereleases = append(prereleases, v)
} else {
releases = append(releases, v)

View File

@ -0,0 +1,55 @@
// 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)
}

View File

@ -4,8 +4,10 @@
//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")
}

View File

@ -2,15 +2,13 @@
// 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"
@ -18,12 +16,12 @@ import (
"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"
@ -51,10 +49,11 @@ const (
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("")
@ -93,7 +92,6 @@ func switchGoToolchain() {
minToolchain = "go" + minVers
}
pathOnly := gotoolchain == "path"
if gotoolchain == "auto" || gotoolchain == "path" {
gotoolchain = minToolchain
@ -155,6 +153,103 @@ func switchGoToolchain() {
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.
@ -169,6 +264,7 @@ func switchGoToolchain() {
}
// Set up modules without an explicit go.mod, to download distribution.
modload.Reset()
modload.ForceUseModules = true
modload.RootMode = modload.NoRoot
modload.Init()
@ -238,46 +334,6 @@ func switchGoToolchain() {
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

View File

@ -0,0 +1,66 @@
// 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, ""},
}

View File

@ -7,6 +7,7 @@
package main
import (
"cmd/go/internal/toolchain"
"cmd/go/internal/workcmd"
"context"
"flag"
@ -91,7 +92,7 @@ var _ = go11tag
func main() {
log.SetFlags(0)
switchGoToolchain()
toolchain.Switch()
flag.Usage = base.Usage
flag.Parse()

View File

@ -0,0 +1,8 @@
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

View File

@ -0,0 +1,8 @@
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

View File

@ -0,0 +1,8 @@
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

View File

@ -0,0 +1,8 @@
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

View File

@ -0,0 +1,8 @@
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

View File

@ -0,0 +1,8 @@
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

View File

@ -0,0 +1,8 @@
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

View File

@ -0,0 +1,8 @@
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

View File

@ -0,0 +1,8 @@
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

View File

@ -0,0 +1,8 @@
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

View File

@ -0,0 +1,8 @@
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

View File

@ -0,0 +1,8 @@
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

View File

@ -0,0 +1,8 @@
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

View File

@ -0,0 +1,8 @@
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

View File

@ -0,0 +1,8 @@
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

View File

@ -0,0 +1,8 @@
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

View File

@ -0,0 +1,8 @@
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

View File

@ -0,0 +1,25 @@
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() {}

View File

@ -0,0 +1,17 @@
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() {}

View File

@ -0,0 +1,17 @@
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() {}

View File

@ -0,0 +1,17 @@
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() {}

View File

@ -0,0 +1,17 @@
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() {}

View File

@ -0,0 +1,17 @@
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() {}

View File

@ -0,0 +1,17 @@
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() {}

View File

@ -0,0 +1,17 @@
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() {}

View File

@ -73,13 +73,13 @@ env TESTGO_VERSION=go1.100
# 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

View File

@ -0,0 +1,130 @@
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

View File

@ -1,6 +1,6 @@
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

View File

@ -4,9 +4,9 @@ env GO111MODULE=on
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

View File

@ -3,24 +3,24 @@
# 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

View File

@ -1,68 +1,69 @@
[!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
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
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
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 m2@v1.0.0
stderr '^go: upgraded go 1.20.1 => 1.22$'
stderr '^go: added m2 v1.0.0$'
grep 'go 1.22' go.mod
go get toolchain@1.22.3
grep 'toolchain go1.22.3' 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 go@1.22.1
grep 'go 1.22.1' go.mod
go get m2@v1.0.0
stderr '^go: upgraded go 1.22.1 => 1.23$'
stderr '^go: added m2 v1.0.0$'
grep 'go 1.23$' go.mod
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
@ -72,4 +73,4 @@ replace m2 v1.0.0 => ./m2
-- m2/go.mod --
module m2
go 1.22
go 1.23

View File

@ -0,0 +1,28 @@
# 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