Attention is currently required from: Michael Matloob, Russ Cox.
7 comments:
Patchset:
(Still reviewing the tests.)
File src/cmd/go/internal/gover/gover.go:
Patch Set #4, Line 132: && v.minor != "2"
A comment for the `v.minor` condition here would be really helpful. 😅
On https://go.dev/dl/ I see `go1`, `go1.2.2`, and `go1.3`, but not `go1.1` or `go1.2`. Do we also need special cases for `1` and `1.1`? (If not, why not?)
File src/cmd/go/internal/toolchain/toolchain_stub.go:
// nop for systems that don't even define syscall.Exec, like js/wasm.
func Switch() {
}
I think `Switch` itself is now portable? (Only `SwitchTo` calls `execGoToolchain`.)
Patch Set #4, Line 13: func SwitchTo(string) {
(nit) For consistency, I think this `SwitchTo` should call `base.Fatalf`. That maintains the invariant that a call to `SwitchTo` never returns.
File src/cmd/go/internal/toolchain/version.go:
Patch Set #4, Line 55: // Assuming
(incomplete comment)
Patch Set #4, Line 57: os.Args = append(os.Args, "go@"+v)
A comment for this mutation would be helpful — I'm not quite sure what it's doing.
It seems to be assuming that `TryVersion` is a `go get` command? But that's kind of a layering violation, and (more importantly) will prevent the reinvoked command from upgrading to an even higher `go` version if that is needed to resolve a (possibly transitive) dependency encountered after making further edits to the module graph.
Patch Set #4, Line 58: base.Errorf("go: switching to go%v for %v", v, gover.Lang(version))
I'm surprised to see a calls to `base.Errorf` preceding calls to `SwitchTo` here: if `SwitchTo` is changed to call `base.ExitIfErrors` at any point this will cause the program to fail, and if `SwitchTo` itself fails it should already set the exit status appropriately.
Can these be `fmt.Sprintf(os.Stderr, …)` instead?
To view, visit change 498118. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Michael Matloob, Russ Cox.
1 comment:
File src/cmd/go/testdata/script/mod_get_exec.txt:
Patch Set #4, Line 22: ? go get rsc.io/tmp/usego12@f84876e
Can we use `GOPROXY=direct` with a `vcs-test.golang.org` repo (`src/cmd/go/testdata/vcstest`) instead of an `rsc.io` repo here?
I've been trying to make the tests more self-contained, and contributor CLs for bug fixes necessarily can't include updates to real `rsc.io` modules.
To view, visit change 498118. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Michael Matloob, Russ Cox.
4 comments:
File src/cmd/go/internal/modget/get.go:
Patch Set #5, Line 383: if tooNew, ok := err.(*gover.TooNewError); ok {
This seems like an ok stopgap if we've missed something elsewhere, but I would be surprised if we can ever actually get to here with a `TooNewError` — I would expect `resolveQueries` and/or `applyUpgrades` to trigger this earlier in the process.
Maybe add a TODO for a telemetry measurement here?
File src/cmd/go/internal/toolchain/version.go:
Patch Set #4, Line 57: os.Args = append(os.Args, "go@"+v)
This is documented now although there is still a problem, as you noted. […]
I would expect that when we reinvoke the command with the original arguments it should repeat the work up to this point and generally arrive at the same effective Go version. What happens if we drop `AddGoVersionArg` and always reinvoke with the original `os.Args[1:]` as-is?
File src/cmd/go/internal/toolchain/version.go:
Patch Set #5, Line 53: println("QUERY", version, q)
(debug prints)
Patch Set #5, Line 54: _, m, err := modload.QueryPattern(ctx, "go", q, noneSelected, allowed)
I would expect to see `modload.Query` instead of `QueryPattern` here, since we're searching for a specific module version rather than packages matching a pattern.
(That said, `QueryPattern` shouldn't be doing much more work for a path with only one component...)
To view, visit change 498118. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Michael Matloob, Russ Cox.
1 comment:
File src/cmd/go/internal/toolchain/toolchain.go:
Patch Set #7, Line 168: func SwitchTo(gotoolchain string) {
Fail explicitly here if `CanSwitch()` is false?
To view, visit change 498118. To unsubscribe, or for help writing mail filters, visit settings.
File src/cmd/go/internal/modget/get.go:
Patch Set #8, Line 1823: changed, err := modload.EditBuildList(ctx, additions, resolved)
We will eventually need to check for a `*gover.TooNewError` here, I think.
(The edits we make to the build list may cause other transitive upgrades or downgrades that pull in a requirement for a higher Go version.)
To view, visit change 498118. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Michael Matloob, Russ Cox.
5 comments:
File src/cmd/go/internal/modget/get.go:
Patch Set #5, Line 383: if tooNew, ok := err.(*gover.TooNewError); ok {
This happens during "go get go@newversion". […]
Ah, this is a missing toolchain switch in `modload.LoadModGraph`, which should (but currently does not) fail if the requested (or effective) `goVersion` is too new.
That needs a fair amount of plumbing, but I have a pending CL that implements it. I'll clean that up and mail it in a bit.
(In the meantime, perhaps make this a TODO to remove the tryVersion call when LoadModGraph correctly catches the problem?)
File src/cmd/go/internal/modget/get.go:
Patch Set #11, Line 1914: !gover.IsValid(version)
If the requested version isn't even syntactically valid we should probably either log something here or add a case in `(*gover.TooNewError).Error` for invalid versions.
If the `tryVersion` call does not succeed in switching, the caller is likely to log a `TooNewError`, and that error text would be misleading if the version isn't even syntactically valid.
File src/cmd/go/internal/modload/init.go:
Patch Set #11, Line 853: if cfg.CmdName != "mod tidy" {
A comment here would be helpful — why avoid this in `mod tidy`?
(Is this because the minimum `go` version might be reduced during tidying
Patch Set #11, Line 1171: // We only want to see it in roots if it is on the command line.
I don't think this is quite right given the invariants in `EditBuildList`, but I can fix that up in a followup change along with the pruning fixes for that function.
File src/cmd/go/internal/toolchain/toolchain.go:
Patch Set #11, Line 164: // Otherwise we use the
(truncated sentence)
To view, visit change 498118. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Michael Matloob, Russ Cox.
Patch set 11:Code-Review +2
Gopher Robot submitted this change.
11 is the latest approved patch-set.
The change was submitted with unreviewed changes in the following files:
```
The name of the file: src/cmd/go/internal/modload/init.go
Insertions: 5, Deletions: 0.
@@ -851,6 +851,11 @@
// 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()}})
}
```
```
The name of the file: src/cmd/go/internal/modget/get.go
Insertions: 7, Deletions: 1.
@@ -384,6 +384,8 @@
// 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)
@@ -1911,7 +1913,11 @@
// 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) || (!toolchain.HasAuto() && !toolchain.HasPath()) || gover.Compare(version, gover.Local()) <= 0 {
+ 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)
```
```
The name of the file: src/cmd/go/internal/toolchain/toolchain.go
Insertions: 0, Deletions: 1.
@@ -161,7 +161,6 @@
//
// 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
// Otherwise we use the latest release.
func NewerToolchain(ctx context.Context, version string) (string, error) {
var versions *modfetch.Versions
```
cmd/go: switch to newer toolchain in go get as needed
If we run 'go get g...@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 <go...@golang.org>
Reviewed-by: Bryan Mills <bcm...@google.com>
Run-TryBot: Russ Cox <r...@golang.org>
Auto-Submit: Russ Cox <r...@golang.org>
---
D src/cmd/go/gotoolchain_stub.go
M src/cmd/go/internal/envcmd/env.go
M src/cmd/go/internal/gover/gover.go
M src/cmd/go/internal/gover/gover_test.go
M src/cmd/go/internal/gover/mod.go
M src/cmd/go/internal/gover/toolchain.go
M src/cmd/go/internal/modget/get.go
M src/cmd/go/internal/modload/buildlist.go
M src/cmd/go/internal/modload/init.go
M src/cmd/go/internal/modload/modfile.go
M src/cmd/go/internal/modload/query.go
A src/cmd/go/internal/toolchain/exec.go
A src/cmd/go/internal/toolchain/exec_stub.go
R src/cmd/go/internal/toolchain/toolchain.go
A src/cmd/go/internal/toolchain/toolchain_test.go
M src/cmd/go/main.go
A src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.18.1.linux-amd64.txt
A src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.18.3.linux-amd64.txt
A src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.18.5.linux-amd64.txt
A src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.18.7.linux-amd64.txt
A src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.18.9.linux-amd64.txt
A src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.18.linux-amd64.txt
A src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22.0.linux-amd64.txt
A src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22.1.linux-amd64.txt
A src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22.3.linux-amd64.txt
A src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22.5.linux-amd64.txt
A src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22.7.linux-amd64.txt
A src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22.9.linux-amd64.txt
A src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22rc1.linux-amd64.txt
A src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.23.0.linux-amd64.txt
A src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.23.5.linux-amd64.txt
A src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.23.9.linux-amd64.txt
A src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.24rc1.linux-amd64.txt
A src/cmd/go/testdata/mod/rsc.io_needall_v0.0.1.txt
A src/cmd/go/testdata/mod/rsc.io_needgo1183_v0.0.1.txt
A src/cmd/go/testdata/mod/rsc.io_needgo118_v0.0.1.txt
A src/cmd/go/testdata/mod/rsc.io_needgo121_v0.0.1.txt
A src/cmd/go/testdata/mod/rsc.io_needgo1223_v0.0.1.txt
A src/cmd/go/testdata/mod/rsc.io_needgo122_v0.0.1.txt
A src/cmd/go/testdata/mod/rsc.io_needgo123_v0.0.1.txt
A src/cmd/go/testdata/mod/rsc.io_needgo124_v0.0.1.txt
M src/cmd/go/testdata/script/gotoolchain.txt
A src/cmd/go/testdata/script/mod_get_exec_toolchain.txt
M src/cmd/go/testdata/script/mod_get_future.txt
M src/cmd/go/testdata/script/mod_go_version.txt
M src/cmd/go/testdata/script/mod_goline_too_new.txt
M src/cmd/go/testdata/script/mod_toolchain.txt
A src/cmd/go/testdata/script/old_tidy_toolchain.txt
48 files changed, 858 insertions(+), 136 deletions(-)
diff --git a/src/cmd/go/gotoolchain_stub.go b/src/cmd/go/gotoolchain_stub.go
deleted file mode 100644
index 9d1d7db..0000000
--- a/src/cmd/go/gotoolchain_stub.go
+++ /dev/null
@@ -1,11 +0,0 @@
-// 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 main
-
-// nop for systems that don't even define syscall.Exec, like js/wasm.
-func switchGoToolchain() {
-}
diff --git a/src/cmd/go/internal/envcmd/env.go b/src/cmd/go/internal/envcmd/env.go
index b99176f..02a5343 100644
--- a/src/cmd/go/internal/envcmd/env.go
+++ b/src/cmd/go/internal/envcmd/env.go
@@ -254,6 +254,12 @@
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()...)
diff --git a/src/cmd/go/internal/gover/gover.go b/src/cmd/go/internal/gover/gover.go
index 2c4dae7..fbea612 100644
--- a/src/cmd/go/internal/gover/gover.go
+++ b/src/cmd/go/internal/gover/gover.go
@@ -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 @@
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.
diff --git a/src/cmd/go/internal/gover/gover_test.go b/src/cmd/go/internal/gover/gover_test.go
index b79e2a0..97b3b76 100644
--- a/src/cmd/go/internal/gover/gover_test.go
+++ b/src/cmd/go/internal/gover/gover_test.go
@@ -77,6 +77,7 @@
{"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 @@
}
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 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 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)
diff --git a/src/cmd/go/internal/gover/mod.go b/src/cmd/go/internal/gover/mod.go
index 19f522c..18e5635 100644
--- a/src/cmd/go/internal/gover/mod.go
+++ b/src/cmd/go/internal/gover/mod.go
@@ -101,3 +101,12 @@
}
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) != ""
+}
diff --git a/src/cmd/go/internal/gover/toolchain.go b/src/cmd/go/internal/gover/toolchain.go
index c7f6e31..48bc86b 100644
--- a/src/cmd/go/internal/gover/toolchain.go
+++ b/src/cmd/go/internal/gover/toolchain.go
@@ -78,7 +78,7 @@
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")
diff --git a/src/cmd/go/internal/modget/get.go b/src/cmd/go/internal/modget/get.go
index 3c86358..eaa2b7d 100644
--- a/src/cmd/go/internal/modget/get.go
+++ b/src/cmd/go/internal/modget/get.go
@@ -43,6 +43,7 @@
"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 @@
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 @@
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 @@
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)
+}
diff --git a/src/cmd/go/internal/modload/buildlist.go b/src/cmd/go/internal/modload/buildlist.go
index 76500ab..517ecfc 100644
--- a/src/cmd/go/internal/modload/buildlist.go
+++ b/src/cmd/go/internal/modload/buildlist.go
@@ -609,7 +609,10 @@
// 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 @@
}
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
diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go
index e8ff920..db407b8 100644
--- a/src/cmd/go/internal/modload/init.go
+++ b/src/cmd/go/internal/modload/init.go
@@ -848,7 +848,16 @@
// 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 @@
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 @@
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 @@
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 @@
})
}
+ 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)
diff --git a/src/cmd/go/internal/modload/modfile.go b/src/cmd/go/internal/modload/modfile.go
index d97eb7c..77d2d7f 100644
--- a/src/cmd/go/internal/modload/modfile.go
+++ b/src/cmd/go/internal/modload/modfile.go
@@ -511,13 +511,16 @@
}
// 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 ||
diff --git a/src/cmd/go/internal/modload/query.go b/src/cmd/go/internal/modload/query.go
index 038199f..b26a036 100644
--- a/src/cmd/go/internal/modload/query.go
+++ b/src/cmd/go/internal/modload/query.go
@@ -546,7 +546,7 @@
}
}
- 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
index 0000000..4e6a13e
--- /dev/null
+++ b/src/cmd/go/internal/toolchain/exec.go
@@ -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)
+}
diff --git a/src/cmd/go/internal/toolchain/exec_stub.go b/src/cmd/go/internal/toolchain/exec_stub.go
new file mode 100644
index 0000000..e212379
--- /dev/null
+++ b/src/cmd/go/internal/toolchain/exec_stub.go
@@ -0,0 +1,13 @@
+// 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"
+
+func execGoToolchain(gotoolchain, dir, exe string) {
+ base.Fatalf("execGoToolchain unsupported")
+}
diff --git a/src/cmd/go/gotoolchain.go b/src/cmd/go/internal/toolchain/toolchain.go
similarity index 76%
rename from src/cmd/go/gotoolchain.go
rename to src/cmd/go/internal/toolchain/toolchain.go
index ae442db..04d5474 100644
--- a/src/cmd/go/gotoolchain.go
+++ b/src/cmd/go/internal/toolchain/toolchain.go
@@ -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 @@
"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 @@
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 @@
minToolchain = "go" + minVers
}
- pathOnly := gotoolchain == "path"
if gotoolchain == "auto" || gotoolchain == "path" {
gotoolchain = minToolchain
@@ -155,6 +153,103 @@
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 @@
}
// 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 @@
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
index 0000000..e8ed566
--- /dev/null
+++ b/src/cmd/go/internal/toolchain/toolchain_test.go
@@ -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, ""},
+}
diff --git a/src/cmd/go/main.go b/src/cmd/go/main.go
index e441b4e..c4a75f8 100644
--- a/src/cmd/go/main.go
+++ b/src/cmd/go/main.go
@@ -7,6 +7,7 @@
package main
import (
+ "cmd/go/internal/toolchain"
"cmd/go/internal/workcmd"
"context"
"flag"
@@ -91,7 +92,7 @@
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
index 0000000..3713de3
--- /dev/null
+++ b/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.18.1.linux-amd64.txt
@@ -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
index 0000000..8eda1ee
--- /dev/null
+++ b/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.18.3.linux-amd64.txt
@@ -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
index 0000000..d74ef25
--- /dev/null
+++ b/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.18.5.linux-amd64.txt
@@ -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
index 0000000..2fc7f85
--- /dev/null
+++ b/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.18.7.linux-amd64.txt
@@ -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
index 0000000..7b07851
--- /dev/null
+++ b/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.18.9.linux-amd64.txt
@@ -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
index 0000000..2c80ce5
--- /dev/null
+++ b/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.18.linux-amd64.txt
@@ -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
index 0000000..215c547
--- /dev/null
+++ b/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22.0.linux-amd64.txt
@@ -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
index 0000000..ac36e3f
--- /dev/null
+++ b/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22.1.linux-amd64.txt
@@ -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
index 0000000..1178a48
--- /dev/null
+++ b/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22.3.linux-amd64.txt
@@ -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
index 0000000..d330127
--- /dev/null
+++ b/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22.5.linux-amd64.txt
@@ -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
index 0000000..a72863b
--- /dev/null
+++ b/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22.7.linux-amd64.txt
@@ -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
index 0000000..ac55849
--- /dev/null
+++ b/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22.9.linux-amd64.txt
@@ -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
index 0000000..b384f28
--- /dev/null
+++ b/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.22rc1.linux-amd64.txt
@@ -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
index 0000000..bbc1377
--- /dev/null
+++ b/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.23.0.linux-amd64.txt
@@ -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
index 0000000..206e8ad
--- /dev/null
+++ b/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.23.5.linux-amd64.txt
@@ -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
index 0000000..7d03776
--- /dev/null
+++ b/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.23.9.linux-amd64.txt
@@ -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
index 0000000..4b61ddd
--- /dev/null
+++ b/src/cmd/go/testdata/mod/golang.org_toolchain_v0.0.1-go1.24rc1.linux-amd64.txt
@@ -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
index 0000000..0a1582a
--- /dev/null
+++ b/src/cmd/go/testdata/mod/rsc.io_needall_v0.0.1.txt
@@ -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
index 0000000..a41296e
--- /dev/null
+++ b/src/cmd/go/testdata/mod/rsc.io_needgo1183_v0.0.1.txt
@@ -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
index 0000000..805eac7
--- /dev/null
+++ b/src/cmd/go/testdata/mod/rsc.io_needgo118_v0.0.1.txt
@@ -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
index 0000000..5b05960
--- /dev/null
+++ b/src/cmd/go/testdata/mod/rsc.io_needgo121_v0.0.1.txt
@@ -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
index 0000000..f166a82
--- /dev/null
+++ b/src/cmd/go/testdata/mod/rsc.io_needgo1223_v0.0.1.txt
@@ -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
index 0000000..59116eb
--- /dev/null
+++ b/src/cmd/go/testdata/mod/rsc.io_needgo122_v0.0.1.txt
@@ -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
index 0000000..0ec5571
--- /dev/null
+++ b/src/cmd/go/testdata/mod/rsc.io_needgo123_v0.0.1.txt
@@ -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
index 0000000..634f504
--- /dev/null
+++ b/src/cmd/go/testdata/mod/rsc.io_needgo124_v0.0.1.txt
@@ -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() {}
diff --git a/src/cmd/go/testdata/script/gotoolchain.txt b/src/cmd/go/testdata/script/gotoolchain.txt
index 40a4b13..abfa5c8 100644
--- a/src/cmd/go/testdata/script/gotoolchain.txt
+++ b/src/cmd/go/testdata/script/gotoolchain.txt
@@ -73,13 +73,13 @@
# 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
index 0000000..a9aa27ec
--- /dev/null
+++ b/src/cmd/go/testdata/script/mod_get_exec_toolchain.txt
@@ -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/need...@v0.0.1 requires go >= 1.22'
+stderr 'rsc.io/need...@v0.0.1 requires go >= 1.23'
+stderr 'rsc.io/nee...@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/need...@v0.0.1 requires go >= 1.22'
+stderr 'rsc.io/need...@v0.0.1 requires go >= 1.23'
+stderr 'rsc.io/nee...@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 g...@1.22 should resolve to the latest 1.22
+env GOTOOLCHAIN=local
+cp go.mod.new go.mod
+! go get g...@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 g...@1.22
+stderr '^go: switching to go1.22.9$'
+
+# go get g...@1.22rc1 should use 1.22rc1 exactly, not a later release.
+env GOTOOLCHAIN=local
+cp go.mod.new go.mod
+! go get g...@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 g...@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 g...@1.22.1 should use 1.22.1 exactly, not a later release.
+env GOTOOLCHAIN=local
+cp go.mod.new go.mod
+! go get g...@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 g...@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/need...@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/needg...@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/need...@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
diff --git a/src/cmd/go/testdata/script/mod_get_future.txt b/src/cmd/go/testdata/script/mod_get_future.txt
index 997e5cb..6f2985a 100644
--- a/src/cmd/go/testdata/script/mod_get_future.txt
+++ b/src/cmd/go/testdata/script/mod_get_future.txt
@@ -1,6 +1,6 @@
env TESTGO_VERSION=go1.21
! go mod download rsc.io/fut...@v1.0.0
-stderr '^go: rsc.io/fut...@v1.0.0 requires go 1.999 \(running go 1.21; go.mod sets go 1.21\)$'
+stderr '^go: rsc.io/fut...@v1.0.0 requires go >= 1.999 \(running go 1.21; go.mod sets go 1.21\)$'
-- go.mod --
module m
diff --git a/src/cmd/go/testdata/script/mod_go_version.txt b/src/cmd/go/testdata/script/mod_go_version.txt
index f0af3ae..4e6baf8 100644
--- a/src/cmd/go/testdata/script/mod_go_version.txt
+++ b/src/cmd/go/testdata/script/mod_go_version.txt
@@ -4,9 +4,9 @@
env TESTGO_VERSION=go1.21
! go list
-stderr -count=1 '^go: s...@v1.0.0: sub requires go 1.999 \(running go 1.21; go.mod sets go 1.1\)$'
+stderr -count=1 '^go: s...@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: s...@v1.0.0: sub requires go 1.999 \(running go 1.21; go.mod sets go 1.1\)$'
+stderr -count=1 '^go: s...@v1.0.0: sub requires go >= 1.999 \(running go 1.21; go.mod sets go 1.1\)$'
-- go.mod --
module m
diff --git a/src/cmd/go/testdata/script/mod_goline_too_new.txt b/src/cmd/go/testdata/script/mod_goline_too_new.txt
index d34efb5..97b0af5 100644
--- a/src/cmd/go/testdata/script/mod_goline_too_new.txt
+++ b/src/cmd/go/testdata/script/mod_goline_too_new.txt
@@ -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
diff --git a/src/cmd/go/testdata/script/mod_toolchain.txt b/src/cmd/go/testdata/script/mod_toolchain.txt
index bdaa859..d0f8b91 100644
--- a/src/cmd/go/testdata/script/mod_toolchain.txt
+++ b/src/cmd/go/testdata/script/mod_toolchain.txt
@@ -1,68 +1,69 @@
-[!net:golang.org] skip
-
-env GOPROXY=https://proxy.golang.org/
env TESTGO_VERSION=go1.100
-go get tool...@go1.20.1
-stderr '^go: added toolchain go1.20.1$'
+env TESTGO_VERSION_SWITCH=1
+
+go get tool...@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 tool...@go1.20.1
-stderr '^go: added toolchain go1.20.1$'
+go get tool...@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 g...@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 g...@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 g...@1.22.3 tool...@1.22.3
+stderr '^go: removed toolchain go1.100$'
! grep toolchain go.mod
-go get g...@1.20.1 tool...@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 g...@1.22.1 tool...@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 g...@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 g...@1.22.3 tool...@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 tool...@1.20.1
-stderr '^go: downgraded go 1.20.3 => 1.20.1$'
- # ! stderr toolchain
-grep 'go 1.20.1' go.mod
+go get tool...@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 g...@1.20.3
-stderr 'go: updating go.mod requires go 1.20.3 \(running go 1.20.1; GOTOOLCHAIN=local\)$'
-
-go get tool...@1.20.3
-grep 'toolchain go1.20.3' go.mod
+! go get g...@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 g...@1.20.1
-grep 'go 1.20.1' go.mod
-go get m...@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 tool...@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 g...@1.28.0
+go get g...@1.22.1
+grep 'go 1.22.1' go.mod
+go get m...@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 tool...@go1.23.9 g...@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 @@
-- 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
index 0000000..d4b5af2
--- /dev/null
+++ b/src/cmd/go/testdata/script/old_tidy_toolchain.txt
@@ -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
To view, visit change 498118. To unsubscribe, or for help writing mail filters, visit settings.