]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go: switch to newer toolchain in go get as needed
authorRuss Cox <rsc@golang.org>
Wed, 24 May 2023 21:37:11 +0000 (17:37 -0400)
committerGopher Robot <gobot@golang.org>
Tue, 30 May 2023 19:11:44 +0000 (19:11 +0000)
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>

47 files changed:
src/cmd/go/internal/envcmd/env.go
src/cmd/go/internal/gover/gover.go
src/cmd/go/internal/gover/gover_test.go
src/cmd/go/internal/gover/mod.go
src/cmd/go/internal/gover/toolchain.go
src/cmd/go/internal/modget/get.go
src/cmd/go/internal/modload/buildlist.go
src/cmd/go/internal/modload/init.go
src/cmd/go/internal/modload/modfile.go
src/cmd/go/internal/modload/query.go
src/cmd/go/internal/toolchain/exec.go [new file with mode: 0644]
src/cmd/go/internal/toolchain/exec_stub.go [moved from src/cmd/go/gotoolchain_stub.go with 56% similarity]
src/cmd/go/internal/toolchain/toolchain.go [moved from src/cmd/go/gotoolchain.go with 76% similarity]
src/cmd/go/internal/toolchain/toolchain_test.go [new file with mode: 0644]
src/cmd/go/main.go
src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.18.1.linux-amd64.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.18.3.linux-amd64.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.18.5.linux-amd64.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.18.7.linux-amd64.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.18.9.linux-amd64.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.18.linux-amd64.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22.0.linux-amd64.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22.1.linux-amd64.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22.3.linux-amd64.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22.5.linux-amd64.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22.7.linux-amd64.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22.9.linux-amd64.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22rc1.linux-amd64.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.23.0.linux-amd64.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.23.5.linux-amd64.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.23.9.linux-amd64.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.24rc1.linux-amd64.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/rsc.io_needall_v0.0.1.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/rsc.io_needgo1183_v0.0.1.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/rsc.io_needgo118_v0.0.1.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/rsc.io_needgo121_v0.0.1.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/rsc.io_needgo1223_v0.0.1.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/rsc.io_needgo122_v0.0.1.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/rsc.io_needgo123_v0.0.1.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/rsc.io_needgo124_v0.0.1.txt [new file with mode: 0644]
src/cmd/go/testdata/script/gotoolchain.txt
src/cmd/go/testdata/script/mod_get_exec_toolchain.txt [new file with mode: 0644]
src/cmd/go/testdata/script/mod_get_future.txt
src/cmd/go/testdata/script/mod_go_version.txt
src/cmd/go/testdata/script/mod_goline_too_new.txt
src/cmd/go/testdata/script/mod_toolchain.txt
src/cmd/go/testdata/script/old_tidy_toolchain.txt [new file with mode: 0644]

index b99176f939d29cb78b21ae36c3725344c6b0d2aa..02a5343362fc9ba5764a92c6348dbe4edf3cda42 100644 (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()...)
 
index 2c4dae77570993e9fbf76bb45000d45c0f3a4c9a..fbea6122274f4d1b74fd21622e22d11682e1617d 100644 (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.
index b79e2a0f0c720ba53b2a6d586b52635f34a7170d..97b3b761c885e133d00a32b157b3c67bcb2bc70d 100644 (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)
index 19f522c099ec9806ffde8e4ca89ea79c694a47c5..18e5635cdf724fe067e37dfc43a7a67fe677509d 100644 (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) != ""
+}
index c7f6e3185e193b3e194ab47f80a6cb97f23b6747..48bc86b568482303d53f4c919449ad5dca0c5a72 100644 (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")
index 3c86358ee3e3988902accf7045340ddb54a05a78..eaa2b7d5dbb24bbcc44d0d2754ca03cca6aed0e6 100644 (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)
+}
index 76500ab33f7282dfed5c0dccfa59f74fbdbdc35d..517ecfcf6650d8b508cb97e6940d457fd44357a0 100644 (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
index e8ff9204ad43281637f3150e47f9fc65096c7966..db407b88a77602542db142695506cab480f71779 100644 (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)
index d97eb7cb625644cefad08fcdfee1e7ef2bb48ffc..77d2d7f86a3a087030ee47c5ddc783a2a1066ce5 100644 (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 ||
index 038199f286b709a6ae7e7eb8ac743c35825f3369..b26a036cacfcee4ec7a7baebf54f98a02e39de19 100644 (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)
diff --git a/src/cmd/go/internal/toolchain/exec.go b/src/cmd/go/internal/toolchain/exec.go
new file mode 100644 (file)
index 0000000..4e6a13e
--- /dev/null
@@ -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)
+}
similarity index 56%
rename from src/cmd/go/gotoolchain_stub.go
rename to src/cmd/go/internal/toolchain/exec_stub.go
index 9d1d7dbd463b1fcf3662bdff9b666cb78ad8023f..e2123790a7e945923ad3db7cebdef5b6b2262277 100644 (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")
 }
similarity index 76%
rename from src/cmd/go/gotoolchain.go
rename to src/cmd/go/internal/toolchain/toolchain.go
index ae442db82bd45cc2004d966d5098990ef8365383..04d54744844112f5b2e54718ae4f8743984ccb8e 100644 (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
diff --git a/src/cmd/go/internal/toolchain/toolchain_test.go b/src/cmd/go/internal/toolchain/toolchain_test.go
new file mode 100644 (file)
index 0000000..e8ed566
--- /dev/null
@@ -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, ""},
+}
index e441b4ea014d80493706919da533b730b19b0da8..c4a75f87e3663ef45c39923483c62be683a4d8b2 100644 (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()
diff --git a/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.18.1.linux-amd64.txt b/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.18.1.linux-amd64.txt
new file mode 100644 (file)
index 0000000..3713de3
--- /dev/null
@@ -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
diff --git a/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.18.3.linux-amd64.txt b/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.18.3.linux-amd64.txt
new file mode 100644 (file)
index 0000000..8eda1ee
--- /dev/null
@@ -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
diff --git a/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.18.5.linux-amd64.txt b/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.18.5.linux-amd64.txt
new file mode 100644 (file)
index 0000000..d74ef25
--- /dev/null
@@ -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
diff --git a/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.18.7.linux-amd64.txt b/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.18.7.linux-amd64.txt
new file mode 100644 (file)
index 0000000..2fc7f85
--- /dev/null
@@ -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
diff --git a/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.18.9.linux-amd64.txt b/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.18.9.linux-amd64.txt
new file mode 100644 (file)
index 0000000..7b07851
--- /dev/null
@@ -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
diff --git a/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.18.linux-amd64.txt b/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.18.linux-amd64.txt
new file mode 100644 (file)
index 0000000..2c80ce5
--- /dev/null
@@ -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
diff --git a/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22.0.linux-amd64.txt b/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22.0.linux-amd64.txt
new file mode 100644 (file)
index 0000000..215c547
--- /dev/null
@@ -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
diff --git a/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22.1.linux-amd64.txt b/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22.1.linux-amd64.txt
new file mode 100644 (file)
index 0000000..ac36e3f
--- /dev/null
@@ -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
diff --git a/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22.3.linux-amd64.txt b/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22.3.linux-amd64.txt
new file mode 100644 (file)
index 0000000..1178a48
--- /dev/null
@@ -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
diff --git a/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22.5.linux-amd64.txt b/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22.5.linux-amd64.txt
new file mode 100644 (file)
index 0000000..d330127
--- /dev/null
@@ -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
diff --git a/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22.7.linux-amd64.txt b/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22.7.linux-amd64.txt
new file mode 100644 (file)
index 0000000..a72863b
--- /dev/null
@@ -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
diff --git a/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22.9.linux-amd64.txt b/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22.9.linux-amd64.txt
new file mode 100644 (file)
index 0000000..ac55849
--- /dev/null
@@ -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
diff --git a/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22rc1.linux-amd64.txt b/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22rc1.linux-amd64.txt
new file mode 100644 (file)
index 0000000..b384f28
--- /dev/null
@@ -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
diff --git a/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.23.0.linux-amd64.txt b/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.23.0.linux-amd64.txt
new file mode 100644 (file)
index 0000000..bbc1377
--- /dev/null
@@ -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
diff --git a/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.23.5.linux-amd64.txt b/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.23.5.linux-amd64.txt
new file mode 100644 (file)
index 0000000..206e8ad
--- /dev/null
@@ -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
diff --git a/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.23.9.linux-amd64.txt b/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.23.9.linux-amd64.txt
new file mode 100644 (file)
index 0000000..7d03776
--- /dev/null
@@ -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
diff --git a/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.24rc1.linux-amd64.txt b/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.24rc1.linux-amd64.txt
new file mode 100644 (file)
index 0000000..4b61ddd
--- /dev/null
@@ -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
diff --git a/src/cmd/go/testdata/mod/rsc.io_needall_v0.0.1.txt b/src/cmd/go/testdata/mod/rsc.io_needall_v0.0.1.txt
new file mode 100644 (file)
index 0000000..0a1582a
--- /dev/null
@@ -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() {}
diff --git a/src/cmd/go/testdata/mod/rsc.io_needgo1183_v0.0.1.txt b/src/cmd/go/testdata/mod/rsc.io_needgo1183_v0.0.1.txt
new file mode 100644 (file)
index 0000000..a41296e
--- /dev/null
@@ -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() {}
diff --git a/src/cmd/go/testdata/mod/rsc.io_needgo118_v0.0.1.txt b/src/cmd/go/testdata/mod/rsc.io_needgo118_v0.0.1.txt
new file mode 100644 (file)
index 0000000..805eac7
--- /dev/null
@@ -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() {}
diff --git a/src/cmd/go/testdata/mod/rsc.io_needgo121_v0.0.1.txt b/src/cmd/go/testdata/mod/rsc.io_needgo121_v0.0.1.txt
new file mode 100644 (file)
index 0000000..5b05960
--- /dev/null
@@ -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() {}
diff --git a/src/cmd/go/testdata/mod/rsc.io_needgo1223_v0.0.1.txt b/src/cmd/go/testdata/mod/rsc.io_needgo1223_v0.0.1.txt
new file mode 100644 (file)
index 0000000..f166a82
--- /dev/null
@@ -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() {}
diff --git a/src/cmd/go/testdata/mod/rsc.io_needgo122_v0.0.1.txt b/src/cmd/go/testdata/mod/rsc.io_needgo122_v0.0.1.txt
new file mode 100644 (file)
index 0000000..59116eb
--- /dev/null
@@ -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() {}
diff --git a/src/cmd/go/testdata/mod/rsc.io_needgo123_v0.0.1.txt b/src/cmd/go/testdata/mod/rsc.io_needgo123_v0.0.1.txt
new file mode 100644 (file)
index 0000000..0ec5571
--- /dev/null
@@ -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() {}
diff --git a/src/cmd/go/testdata/mod/rsc.io_needgo124_v0.0.1.txt b/src/cmd/go/testdata/mod/rsc.io_needgo124_v0.0.1.txt
new file mode 100644 (file)
index 0000000..634f504
--- /dev/null
@@ -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() {}
index 40a4b13c9d34220474f748bbb58eaedaccd6bea5..abfa5c8cc0c7f60a91519b515bda14343c6afb32 100644 (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
diff --git a/src/cmd/go/testdata/script/mod_get_exec_toolchain.txt b/src/cmd/go/testdata/script/mod_get_exec_toolchain.txt
new file mode 100644 (file)
index 0000000..a9aa27e
--- /dev/null
@@ -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
index 997e5cb288bd12c0da4b3570ad6cf36e882a4a29..6f2985af866bf7881ca9b8034bd37ebdc07a8097 100644 (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
index f0af3ae307e2e398605c061e3da2d51f09043d4f..4e6baf89f1e5e77ea09d8edcb2e95823beb3ceab 100644 (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
index d34efb5bd355aac57a11f9023b32f6973d60befd..97b0af52c1d17b689fad8a329b5edda5645737ab 100644 (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
index bdaa859bd9099548a7b9285b899ccd6b30761732..d0f8b913e7d2a9cdde8f2ebdaaf74b90521da70e 100644 (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
+
+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
@@ -72,4 +73,4 @@ replace m2 v1.0.0 => ./m2
 
 -- m2/go.mod --
 module m2
-go 1.22
+go 1.23
diff --git a/src/cmd/go/testdata/script/old_tidy_toolchain.txt b/src/cmd/go/testdata/script/old_tidy_toolchain.txt
new file mode 100644 (file)
index 0000000..d4b5af2
--- /dev/null
@@ -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