]> Cypherpunks.ru repositories - gostls13.git/commitdiff
cmd/go: add go get go@version and toolchain@version
authorRuss Cox <rsc@golang.org>
Wed, 24 May 2023 01:12:23 +0000 (21:12 -0400)
committerGopher Robot <gobot@golang.org>
Thu, 25 May 2023 17:51:28 +0000 (17:51 +0000)
go get go@version and toolchain@version updates the
go and toolchain lines in go.mod. If toolchain ends up <= go,
it is dropped.

When the go version crosses certain version boundaries,
it may be necessary to run 'go mod tidy -go=version'.
That's left for a followup CL.

When the go or toolchain version ends up higher than the
current toolchain version, we cannot be sure we know how
to write the file out, so we fail with an error message.
In GOTOOLCHAIN auto mode, the newer toolchain should
be downloaded and reinvoked; that's left for a followup CL too.

For #57001.

Change-Id: Ibfdcc549b40555a53bdb2d019816d18f1bd16be6
Reviewed-on: https://go-review.googlesource.com/c/go/+/497081
TryBot-Result: Gopher Robot <gobot@golang.org>
Auto-Submit: Russ Cox <rsc@golang.org>
Run-TryBot: Russ Cox <rsc@golang.org>
Reviewed-by: Bryan Mills <bcmills@google.com>
23 files changed:
src/cmd/go/internal/gover/mod.go
src/cmd/go/internal/gover/mod_test.go
src/cmd/go/internal/gover/toolchain.go
src/cmd/go/internal/modcmd/tidy.go
src/cmd/go/internal/modcmd/verify.go
src/cmd/go/internal/modfetch/toolchain.go
src/cmd/go/internal/modget/get.go
src/cmd/go/internal/modget/query.go
src/cmd/go/internal/modload/build.go
src/cmd/go/internal/modload/buildlist.go
src/cmd/go/internal/modload/import.go
src/cmd/go/internal/modload/init.go
src/cmd/go/internal/modload/list.go
src/cmd/go/internal/modload/load.go
src/cmd/go/internal/modload/modfile.go
src/cmd/go/internal/modload/query.go
src/cmd/go/internal/modload/search.go
src/cmd/go/testdata/script/mod_goline.txt [new file with mode: 0644]
src/cmd/go/testdata/script/mod_goline_old.txt [new file with mode: 0644]
src/cmd/go/testdata/script/mod_indirect_main.txt
src/cmd/go/testdata/script/mod_skip_write.txt
src/cmd/go/testdata/script/mod_toolchain.txt [new file with mode: 0644]
src/cmd/go/testdata/script/work_why_download_graph.txt

index 8b9032f7b8def0420a84add8440328963347b797..c68738d46d6b00f46931530bdec70af218cf2442 100644 (file)
@@ -36,7 +36,7 @@ func ModCompare(path string, x, y string) int {
                return Compare(x, y)
        }
        if path == "toolchain" {
-               return Compare(untoolchain(x), untoolchain(y))
+               return Compare(maybeToolchainVersion(x), maybeToolchainVersion(y))
        }
        return semver.Compare(x, y)
 }
@@ -72,23 +72,14 @@ func ModSort(list []module.Version) {
 // ModIsValid reports whether vers is a valid version syntax for the module with the given path.
 func ModIsValid(path, vers string) bool {
        if IsToolchain(path) {
-               return parse(vers) != (version{})
+               if path == "toolchain" {
+                       return IsValid(ToolchainVersion(vers))
+               }
+               return IsValid(vers)
        }
        return semver.IsValid(vers)
 }
 
-// untoolchain converts a toolchain name like "go1.2.3" to a Go version like "1.2.3".
-// It also converts "anything-go1.2.3" (for example, "gccgo-go1.2.3") to "1.2.3".
-func untoolchain(x string) string {
-       if strings.HasPrefix(x, "go1") {
-               return x[len("go"):]
-       }
-       if i := strings.Index(x, "-go1"); i >= 0 {
-               return x[i+len("-go"):]
-       }
-       return x
-}
-
 // ModIsPrefix reports whether v is a valid version syntax prefix for the module with the given path.
 // The caller is assumed to have checked that ModIsValid(path, vers) is true.
 func ModIsPrefix(path, vers string) bool {
index 20dd8ca2d01970a6361d0aa88c6e08bd6a11ac54..c92169cb32d50caa013d695f0a6a20b5b1e38494 100644 (file)
@@ -40,7 +40,7 @@ func TestModIsValid(t *testing.T) { test2(t, modIsValidTests, "ModIsValid", ModI
 var modIsValidTests = []testCase2[string, string, bool]{
        {"go", "1.2", true},
        {"go", "v1.2", false},
-       {"toolchain", "1.2", true},
+       {"toolchain", "go1.2", true},
        {"toolchain", "v1.2", false},
        {"rsc.io/quote", "v1.2", true},
        {"rsc.io/quote", "1.2", false},
index bf5a64d056dbd4a9da99cb608a9d64d8d4c01e33..58a4d620f3501136f6c1053c0fc24671a1b6b53d 100644 (file)
@@ -29,6 +29,13 @@ func ToolchainVersion(name string) string {
        return v
 }
 
+func maybeToolchainVersion(name string) string {
+       if IsValid(name) {
+               return name
+       }
+       return ToolchainVersion(name)
+}
+
 // Startup records the information that went into the startup-time version switch.
 // It is initialized by switchGoToolchain.
 var Startup struct {
index 842be72185a9c1a0f463a7813e492af33cfd8b13..7734eda869254debfd6a635613dc5ce50d28cc85 100644 (file)
@@ -117,6 +117,7 @@ func runTidy(ctx context.Context, cmd *base.Command, args []string) {
 
        modload.LoadPackages(ctx, modload.PackageOpts{
                GoVersion:                tidyGo.String(),
+               TidyGo:                   tidyGo.String() != "",
                Tags:                     imports.AnyTags(),
                Tidy:                     true,
                TidyCompatibleVersion:    tidyCompat.String(),
index 861f56b265c4397fc57592944a31fe905fdcbb09..0828c4718d4a34b2fed31d039cd39328f07902e8 100644 (file)
@@ -14,6 +14,7 @@ import (
        "runtime"
 
        "cmd/go/internal/base"
+       "cmd/go/internal/gover"
        "cmd/go/internal/modfetch"
        "cmd/go/internal/modload"
 
@@ -86,6 +87,10 @@ func runVerify(ctx context.Context, cmd *base.Command, args []string) {
 }
 
 func verifyMod(ctx context.Context, mod module.Version) []error {
+       if gover.IsToolchain(mod.Path) {
+               // "go" and "toolchain" have no disk footprint; nothing to verify.
+               return nil
+       }
        var errs []error
        zip, zipErr := modfetch.CachePath(ctx, mod, "zip")
        if zipErr == nil {
index 0c8fd3b039bcec03002771c0e18624af93cf8f75..13e0d8d2acdd3a226753906ecda6ef6419fb5660 100644 (file)
@@ -8,6 +8,7 @@ import (
        "context"
        "fmt"
        "io"
+       "sort"
        "strings"
 
        "cmd/go/internal/gover"
@@ -58,6 +59,16 @@ func (r *toolchainRepo) Versions(ctx context.Context, prefix string) (*Versions,
                        list = append(list, goPrefix+v)
                }
        }
+
+       if r.path == "go" {
+               sort.Slice(list, func(i, j int) bool {
+                       return gover.Compare(list[i], list[j]) < 0
+               })
+       } else {
+               sort.Slice(list, func(i, j int) bool {
+                       return gover.Compare(gover.ToolchainVersion(list[i]), gover.ToolchainVersion(list[j])) < 0
+               })
+       }
        versions.List = list
        return versions, nil
 }
@@ -73,9 +84,9 @@ func (r *toolchainRepo) Stat(ctx context.Context, rev string) (*RevInfo, error)
        // Convert rev to DL version and stat that to make sure it exists.
        prefix := ""
        v := rev
+       v = strings.TrimPrefix(v, "go")
        if r.path == "toolchain" {
                prefix = "go"
-               v = strings.TrimPrefix(v, "go")
        }
        if gover.IsLang(v) {
                return nil, fmt.Errorf("go language version %s is not a toolchain version", rev)
index f29f6328080727daaee395c79d86c7044a954fc3..3649e372be588db2c236ed308bbbbb6313a1f7c0 100644 (file)
@@ -302,7 +302,7 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) {
                        "\tor run 'go help get' or 'go help install'.")
        }
 
-       queries := parseArgs(ctx, args)
+       dropToolchain, queries := parseArgs(ctx, args)
 
        r := newResolver(ctx, queries)
        r.performLocalQueries(ctx)
@@ -371,6 +371,10 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) {
        }
        r.checkPackageProblems(ctx, pkgPatterns)
 
+       if dropToolchain {
+               modload.OverrideRoots(ctx, []module.Version{{Path: "toolchain", Version: "none"}})
+       }
+
        // Everything succeeded. Update go.mod.
        oldReqs := reqsFromGoMod(modload.ModFile())
 
@@ -386,10 +390,9 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) {
 //
 // The command-line arguments are of the form path@version or simply path, with
 // implicit @upgrade. path@none is "downgrade away".
-func parseArgs(ctx context.Context, rawArgs []string) []*query {
+func parseArgs(ctx context.Context, rawArgs []string) (dropToolchain bool, queries []*query) {
        defer base.ExitIfErrors()
 
-       var queries []*query
        for _, arg := range search.CleanPatterns(rawArgs) {
                q, err := newQuery(arg)
                if err != nil {
@@ -397,6 +400,17 @@ func parseArgs(ctx context.Context, rawArgs []string) []*query {
                        continue
                }
 
+               if q.version == "none" {
+                       switch q.pattern {
+                       case "go":
+                               base.Errorf("go: cannot use go@none", q.pattern)
+                               continue
+                       case "toolchain":
+                               dropToolchain = true
+                               continue
+                       }
+               }
+
                // If there were no arguments, CleanPatterns returns ".". Set the raw
                // string back to "" for better errors.
                if len(rawArgs) == 0 {
@@ -420,7 +434,7 @@ func parseArgs(ctx context.Context, rawArgs []string) []*query {
                queries = append(queries, q)
        }
 
-       return queries
+       return dropToolchain, queries
 }
 
 type resolver struct {
@@ -1646,6 +1660,9 @@ func (r *resolver) reportChanges(oldReqs, newReqs []module.Version) {
 
        // Collect changes in modules matched by command line arguments.
        for path, reason := range r.resolvedVersion {
+               if gover.IsToolchain(path) {
+                       continue
+               }
                old := r.initialVersion[path]
                new := reason.version
                if old != new && (old != "" || new != "none") {
@@ -1655,6 +1672,9 @@ func (r *resolver) reportChanges(oldReqs, newReqs []module.Version) {
 
        // Collect changes to explicit requirements in go.mod.
        for _, req := range oldReqs {
+               if gover.IsToolchain(req.Path) {
+                       continue
+               }
                path := req.Path
                old := req.Version
                new := r.buildListVersion[path]
@@ -1663,6 +1683,9 @@ func (r *resolver) reportChanges(oldReqs, newReqs []module.Version) {
                }
        }
        for _, req := range newReqs {
+               if gover.IsToolchain(req.Path) {
+                       continue
+               }
                path := req.Path
                old := r.initialVersion[path]
                new := req.Version
@@ -1671,13 +1694,51 @@ func (r *resolver) reportChanges(oldReqs, newReqs []module.Version) {
                }
        }
 
+       // Toolchain diffs are easier than requirements: diff old and new directly.
+       toolchainVersions := func(reqs []module.Version) (goV, toolchain string) {
+               for _, req := range reqs {
+                       if req.Path == "go" {
+                               goV = req.Version
+                       }
+                       if req.Path == "toolchain" {
+                               toolchain = req.Version
+                       }
+               }
+               return
+       }
+       oldGo, oldToolchain := toolchainVersions(oldReqs)
+       newGo, newToolchain := toolchainVersions(newReqs)
+       if oldGo != newGo {
+               changes["go"] = change{"go", oldGo, newGo}
+       }
+       if oldToolchain != newToolchain {
+               changes["toolchain"] = change{"toolchain", oldToolchain, newToolchain}
+       }
+
        sortedChanges := make([]change, 0, len(changes))
        for _, c := range changes {
                sortedChanges = append(sortedChanges, c)
        }
        sort.Slice(sortedChanges, func(i, j int) bool {
-               return sortedChanges[i].path < sortedChanges[j].path
+               pi := sortedChanges[i].path
+               pj := sortedChanges[j].path
+               if pi == pj {
+                       return false
+               }
+               // go first; toolchain second
+               switch {
+               case pi == "go":
+                       return true
+               case pj == "go":
+                       return false
+               case pi == "toolchain":
+                       return true
+               case pj == "toolchain":
+                       return false
+               }
+               return pi < pj
        })
+
        for _, c := range sortedChanges {
                if c.old == "" {
                        fmt.Fprintf(os.Stderr, "go: added %s %s\n", c.path, c.new)
@@ -1795,10 +1856,16 @@ func (r *resolver) updateBuildList(ctx context.Context, additions []module.Versi
 }
 
 func reqsFromGoMod(f *modfile.File) []module.Version {
-       reqs := make([]module.Version, len(f.Require))
+       reqs := make([]module.Version, len(f.Require), 2+len(f.Require))
        for i, r := range f.Require {
                reqs[i] = r.Mod
        }
+       if f.Go != nil {
+               reqs = append(reqs, module.Version{Path: "go", Version: f.Go.Version})
+       }
+       if f.Toolchain != nil {
+               reqs = append(reqs, module.Version{Path: "toolchain", Version: f.Toolchain.Name})
+       }
        return reqs
 }
 
index d18770e889ecf2b65dc416138ca2060445f3f809..6612f9b1128d06b845a985d127c7d3b553139a1c 100644 (file)
@@ -12,6 +12,7 @@ import (
        "sync"
 
        "cmd/go/internal/base"
+       "cmd/go/internal/gover"
        "cmd/go/internal/modload"
        "cmd/go/internal/search"
        "cmd/go/internal/str"
@@ -229,7 +230,7 @@ func (q *query) isWildcard() bool {
 
 // matchesPath reports whether the given path matches q.pattern.
 func (q *query) matchesPath(path string) bool {
-       if q.matchWildcard != nil {
+       if q.matchWildcard != nil && !gover.IsToolchain(path) {
                return q.matchWildcard(path)
        }
        return path == q.pattern
@@ -241,7 +242,7 @@ func (q *query) canMatchInModule(mPath string) bool {
        if q.canMatchWildcardInModule != nil {
                return q.canMatchWildcardInModule(mPath)
        }
-       return str.HasPathPrefix(q.pattern, mPath)
+       return str.HasPathPrefix(q.pattern, mPath) && !gover.IsToolchain(mPath)
 }
 
 // pathOnce invokes f to generate the pathSet for the given path,
index 5da0472bd4c5e64c5fe64508e96a4de19e11b5c1..b63ea4842891a22c35cf91c93fc9ae90b183d51d 100644 (file)
@@ -309,6 +309,10 @@ func moduleInfo(ctx context.Context, rs *Requirements, m module.Version, mode Li
 
        // completeFromModCache fills in the extra fields in m using the module cache.
        completeFromModCache := func(m *modinfo.ModulePublic) {
+               if gover.IsToolchain(m.Path) {
+                       return
+               }
+
                if old := reuse[module.Version{Path: m.Path, Version: m.Version}]; old != nil {
                        if err := checkReuse(ctx, m.Path, old.Origin); err == nil {
                                *m = *old
index d68260e4559d89334ba37c3899048ae1394792ba..7cebb9f265ae3179fb5f9112819cf95931daef0c 100644 (file)
@@ -157,7 +157,7 @@ func (rs *Requirements) String() string {
 func (rs *Requirements) initVendor(vendorList []module.Version) {
        rs.graphOnce.Do(func() {
                mg := &ModuleGraph{
-                       g: mvs.NewGraph(cmpVersion, MainModules.Versions()),
+                       g: mvs.NewGraph(cmpVersion, MainModules.GraphRoots()),
                }
 
                if MainModules.Len() != 1 {
@@ -305,7 +305,7 @@ func readModGraph(ctx context.Context, pruning modPruning, roots []module.Versio
                mu       sync.Mutex // guards mg.g and hasError during loading
                hasError bool
                mg       = &ModuleGraph{
-                       g: mvs.NewGraph(cmpVersion, MainModules.Versions()),
+                       g: mvs.NewGraph(cmpVersion, MainModules.GraphRoots()),
                }
        )
        if pruning != workspace {
@@ -605,6 +605,24 @@ func EditBuildList(ctx context.Context, add, mustSelect []module.Version) (chang
        return changed, err
 }
 
+// OverrideRoots edits the global requirement roots by replacing the specific module versions.
+func OverrideRoots(ctx context.Context, replace []module.Version) {
+       rs := requirements
+       drop := make(map[string]bool)
+       for _, m := range replace {
+               drop[m.Path] = true
+       }
+       var roots []module.Version
+       for _, m := range rs.rootModules {
+               if !drop[m.Path] {
+                       roots = append(roots, m)
+               }
+       }
+       roots = append(roots, replace...)
+       gover.ModSort(roots)
+       requirements = newRequirements(rs.pruning, roots, rs.direct)
+}
+
 // A ConstraintError describes inconsistent constraints in EditBuildList
 type ConstraintError struct {
        // Conflict lists the source of the conflict for each version in mustSelect
@@ -709,9 +727,9 @@ func (c Conflict) String() string {
 func tidyRoots(ctx context.Context, rs *Requirements, pkgs []*loadPkg) (*Requirements, error) {
        mainModule := MainModules.mustGetSingleMainModule()
        if rs.pruning == unpruned {
-               return tidyUnprunedRoots(ctx, mainModule, rs.direct, pkgs)
+               return tidyUnprunedRoots(ctx, mainModule, rs, pkgs)
        }
-       return tidyPrunedRoots(ctx, mainModule, rs.direct, pkgs)
+       return tidyPrunedRoots(ctx, mainModule, rs, pkgs)
 }
 
 func updateRoots(ctx context.Context, direct map[string]bool, rs *Requirements, pkgs []*loadPkg, add []module.Version, rootsImported bool) (*Requirements, error) {
@@ -757,11 +775,15 @@ func updateWorkspaceRoots(ctx context.Context, rs *Requirements, add []module.Ve
 // To ensure that the loading process eventually converges, the caller should
 // add any needed roots from the tidy root set (without removing existing untidy
 // roots) until the set of roots has converged.
-func tidyPrunedRoots(ctx context.Context, mainModule module.Version, direct map[string]bool, pkgs []*loadPkg) (*Requirements, error) {
+func tidyPrunedRoots(ctx context.Context, mainModule module.Version, old *Requirements, pkgs []*loadPkg) (*Requirements, error) {
        var (
                roots      []module.Version
                pathIsRoot = map[string]bool{mainModule.Path: true}
        )
+       if v, ok := old.rootSelected("go"); ok {
+               roots = append(roots, module.Version{Path: "go", Version: v})
+               pathIsRoot["go"] = true
+       }
        // We start by adding roots for every package in "all".
        //
        // Once that is done, we may still need to add more roots to cover upgraded or
@@ -788,7 +810,7 @@ func tidyPrunedRoots(ctx context.Context, mainModule module.Version, direct map[
                queued[pkg] = true
        }
        gover.ModSort(roots)
-       tidy := newRequirements(pruned, roots, direct)
+       tidy := newRequirements(pruned, roots, old.direct)
 
        for len(queue) > 0 {
                roots = tidy.rootModules
@@ -1197,7 +1219,7 @@ func spotCheckRoots(ctx context.Context, rs *Requirements, mods map[module.Versi
 // the selected version of every module that provided or lexically could have
 // provided a package in pkgs, and includes the selected version of every such
 // module in direct as a root.
-func tidyUnprunedRoots(ctx context.Context, mainModule module.Version, direct map[string]bool, pkgs []*loadPkg) (*Requirements, error) {
+func tidyUnprunedRoots(ctx context.Context, mainModule module.Version, old *Requirements, pkgs []*loadPkg) (*Requirements, error) {
        var (
                // keep is a set of of modules that provide packages or are needed to
                // disambiguate imports.
@@ -1225,6 +1247,9 @@ func tidyUnprunedRoots(ctx context.Context, mainModule module.Version, direct ma
                // without its sum. See #47738.
                altMods = map[string]string{}
        )
+       if v, ok := old.rootSelected("go"); ok {
+               keep = append(keep, module.Version{Path: "go", Version: v})
+       }
        for _, pkg := range pkgs {
                if !pkg.fromExternalModule() {
                        continue
@@ -1232,7 +1257,7 @@ func tidyUnprunedRoots(ctx context.Context, mainModule module.Version, direct ma
                if m := pkg.mod; !keptPath[m.Path] {
                        keep = append(keep, m)
                        keptPath[m.Path] = true
-                       if direct[m.Path] && !inRootPaths[m.Path] {
+                       if old.direct[m.Path] && !inRootPaths[m.Path] {
                                rootPaths = append(rootPaths, m.Path)
                                inRootPaths[m.Path] = true
                        }
@@ -1275,7 +1300,7 @@ func tidyUnprunedRoots(ctx context.Context, mainModule module.Version, direct ma
                }
        }
 
-       return newRequirements(unpruned, min, direct), nil
+       return newRequirements(unpruned, min, old.direct), nil
 }
 
 // updateUnprunedRoots returns a set of root requirements that includes the selected
index 4f7fed4856894631499bf71303648475006f376b..6b4710e2687d9d648a979363f5e896ff18089882 100644 (file)
@@ -371,6 +371,10 @@ func importFromModules(ctx context.Context, path string, rs *Requirements, mg *M
        for {
                var sumErrMods, altMods []module.Version
                for prefix := path; prefix != "."; prefix = pathpkg.Dir(prefix) {
+                       if gover.IsToolchain(prefix) {
+                               // Do not use the synthetic "go" module for "go/ast".
+                               continue
+                       }
                        var (
                                v  string
                                ok bool
index 86be7da243df6147d12d0979a008e6fdcc8e5388..a5363c908befeed64dd14d115b16453f509cc041 100644 (file)
@@ -133,6 +133,18 @@ func (mms *MainModuleSet) Versions() []module.Version {
        return mms.versions
 }
 
+// GraphRoots returns the graph roots for the main module set.
+// Callers should not modify the returned slice.
+// This function is the same as Versions except that in workspace
+// mode it adds a "go" version from the go.work file.
+func (mms *MainModuleSet) GraphRoots() []module.Version {
+       versions := mms.Versions()
+       if inWorkspaceMode() {
+               versions = append(slices.Clip(versions), module.Version{Path: "go", Version: mms.GoVersion()})
+       }
+       return versions
+}
+
 func (mms *MainModuleSet) Contains(path string) bool {
        if mms == nil {
                return false
@@ -606,14 +618,11 @@ func (goModDirtyError) Error() string {
 
 var errGoModDirty error = goModDirtyError{}
 
-func loadWorkFile(path string) (goVersion string, modRoots []string, replaces []*modfile.Replace, err error) {
+func loadWorkFile(path string) (workFile *modfile.WorkFile, modRoots []string, err error) {
        workDir := filepath.Dir(path)
        wf, err := ReadWorkFile(path)
        if err != nil {
-               return "", nil, nil, err
-       }
-       if wf.Go != nil {
-               goVersion = wf.Go.Version
+               return nil, nil, err
        }
        seen := map[string]bool{}
        for _, d := range wf.Use {
@@ -623,13 +632,13 @@ func loadWorkFile(path string) (goVersion string, modRoots []string, replaces []
                }
 
                if seen[modRoot] {
-                       return "", nil, nil, fmt.Errorf("path %s appears multiple times in workspace", modRoot)
+                       return nil, nil, fmt.Errorf("path %s appears multiple times in workspace", modRoot)
                }
                seen[modRoot] = true
                modRoots = append(modRoots, modRoot)
        }
 
-       return goVersion, modRoots, wf.Replace, nil
+       return wf, modRoots, nil
 }
 
 // ReadWorkFile reads and parses the go.work file at the given path.
@@ -703,18 +712,19 @@ func UpdateWorkFile(wf *modfile.WorkFile) {
 // it for global consistency. Most callers outside of the modload package should
 // use LoadModGraph instead.
 func LoadModFile(ctx context.Context) *Requirements {
+       return loadModFile(ctx, nil)
+}
+
+func loadModFile(ctx context.Context, opts *PackageOpts) *Requirements {
        if requirements != nil {
                return requirements
        }
 
        Init()
-       var (
-               workFileGoVersion string
-               workFileReplaces  []*modfile.Replace
-       )
+       var workFile *modfile.WorkFile
        if inWorkspaceMode() {
                var err error
-               workFileGoVersion, modRoots, workFileReplaces, err = loadWorkFile(workFilePath)
+               workFile, modRoots, err = loadWorkFile(workFilePath)
                if err != nil {
                        base.Fatalf("reading go.work: %v", err)
                }
@@ -794,9 +804,17 @@ func LoadModFile(ctx context.Context) *Requirements {
                }
        }
 
-       MainModules = makeMainModules(mainModules, modRoots, modFiles, indices, workFileGoVersion, workFileReplaces)
+       var wfGoVersion string
+       var wfReplace []*modfile.Replace
+       if workFile != nil && workFile.Go != nil {
+               wfGoVersion = workFile.Go.Version
+       }
+       if workFile != nil {
+               wfReplace = workFile.Replace
+       }
+       MainModules = makeMainModules(mainModules, modRoots, modFiles, indices, wfGoVersion, wfReplace)
        setDefaultBuildMod() // possibly enable automatic vendoring
-       rs := requirementsFromModFiles(ctx, modFiles)
+       rs := requirementsFromModFiles(ctx, workFile, modFiles, opts)
 
        if inWorkspaceMode() {
                // We don't need to do anything for vendor or update the mod file so
@@ -908,7 +926,7 @@ func CreateModFile(ctx context.Context, modPath string) {
                base.Fatalf("go: %v", err)
        }
 
-       rs := requirementsFromModFiles(ctx, []*modfile.File{modFile})
+       rs := requirementsFromModFiles(ctx, nil, []*modfile.File{modFile}, nil)
        rs, err = updateRoots(ctx, rs.direct, rs, nil, nil, false)
        if err != nil {
                base.Fatalf("go: %v", err)
@@ -1132,21 +1150,30 @@ func makeMainModules(ms []module.Version, rootDirs []string, modFiles []*modfile
 
 // requirementsFromModFiles returns the set of non-excluded requirements from
 // the global modFile.
-func requirementsFromModFiles(ctx context.Context, modFiles []*modfile.File) *Requirements {
+func requirementsFromModFiles(ctx context.Context, workFile *modfile.WorkFile, modFiles []*modfile.File, opts *PackageOpts) *Requirements {
        var roots []module.Version
        direct := map[string]bool{}
        var pruning modPruning
        if inWorkspaceMode() {
                pruning = workspace
-               roots = make([]module.Version, len(MainModules.Versions()))
+               roots = make([]module.Version, len(MainModules.Versions()), 2+len(MainModules.Versions()))
                copy(roots, MainModules.Versions())
+               // Note: Ignoring the 'go' line in the main modules during mod tidy. See note below.
+               if workFile.Go != nil && (opts == nil || !opts.TidyGo) {
+                       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
+               }
        } else {
                pruning = pruningForGoVersion(MainModules.GoVersion())
                if len(modFiles) != 1 {
                        panic(fmt.Errorf("requirementsFromModFiles called with %v modfiles outside workspace mode", len(modFiles)))
                }
                modFile := modFiles[0]
-               roots = make([]module.Version, 0, len(modFile.Require))
+               roots = make([]module.Version, 0, 2+len(modFile.Require))
                mm := MainModules.mustGetSingleMainModule()
                for _, r := range modFile.Require {
                        if index := MainModules.Index(mm); index != nil && index.exclude[r.Mod] {
@@ -1163,6 +1190,17 @@ func requirementsFromModFiles(ctx context.Context, modFiles []*modfile.File) *Re
                                direct[r.Mod.Path] = true
                        }
                }
+               // Note: Ignoring the 'go' line in the main modules during mod tidy -go=
+               // so that we can find out the implied minimum go line from the
+               // dependencies instead. If it is higher than the -go= flag, we report an error in LoadPackages.
+               if modFile.Go != nil && (opts == nil || !opts.TidyGo) {
+                       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
+               }
        }
        gover.ModSort(roots)
        rs := newRequirements(pruning, roots, direct)
@@ -1276,6 +1314,10 @@ func addGoStmt(modFile *modfile.File, mod module.Version, v string) {
        if modFile.Go != nil && modFile.Go.Version != "" {
                return
        }
+       forceGoStmt(modFile, mod, v)
+}
+
+func forceGoStmt(modFile *modfile.File, mod module.Version, v string) {
        if err := modFile.AddGoStmt(v); err != nil {
                base.Fatalf("go: internal error: %v", err)
        }
@@ -1503,21 +1545,49 @@ func commitRequirements(ctx context.Context) (err error) {
        modFilePath := modFilePath(MainModules.ModRoot(mainModule))
 
        var list []*modfile.Require
+       toolchain := ""
        for _, m := range requirements.rootModules {
+               if m.Path == "go" {
+                       forceGoStmt(modFile, mainModule, m.Version)
+                       continue
+               }
+               if m.Path == "toolchain" {
+                       toolchain = m.Version
+                       continue
+               }
                list = append(list, &modfile.Require{
                        Mod:      m,
                        Indirect: !requirements.direct[m.Path],
                })
        }
+
+       // Update go and toolchain lines.
+       tv := gover.ToolchainVersion(toolchain)
+       // Set go version if missing.
        if modFile.Go == nil || modFile.Go.Version == "" {
-               modFile.AddGoStmt(modFileGoVersion(modFile))
+               v := modFileGoVersion(modFile)
+               if tv != "" && gover.Compare(v, tv) > 0 {
+                       v = tv
+               }
+               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})
        }
 
+       // If toolchain is older than go version, drop it.
+       if gover.Compare(modFile.Go.Version, tv) >= 0 {
+               toolchain = ""
+       }
+       // Remove or add toolchain as needed.
+       if toolchain == "" {
+               modFile.DropToolchainStmt()
+       } else {
+               modFile.AddToolchainStmt(toolchain)
+       }
+
+       // Update require blocks.
        if gover.Compare(modFileGoVersion(modFile), separateIndirectVersion) < 0 {
                modFile.SetRequire(list)
        } else {
index 3df8d017aba87e56bdc086f011d40bf1065526c2..a1c2908eedb44f41486cdcc019faeb35ac97e7e0 100644 (file)
@@ -17,6 +17,7 @@ import (
 
        "cmd/go/internal/base"
        "cmd/go/internal/cfg"
+       "cmd/go/internal/gover"
        "cmd/go/internal/modfetch/codehost"
        "cmd/go/internal/modinfo"
        "cmd/go/internal/search"
@@ -120,6 +121,9 @@ func listModules(ctx context.Context, rs *Requirements, args []string, mode List
        if len(args) == 0 {
                var ms []*modinfo.ModulePublic
                for _, m := range MainModules.Versions() {
+                       if gover.IsToolchain(m.Path) {
+                               continue
+                       }
                        ms = append(ms, moduleInfo(ctx, rs, m, mode, reuse))
                }
                return rs, ms, nil
@@ -219,9 +223,10 @@ func listModules(ctx context.Context, rs *Requirements, args []string, mode List
                // Module path or pattern.
                var match func(string) bool
                if arg == "all" {
-                       match = func(string) bool { return true }
+                       match = func(p string) bool { return !gover.IsToolchain(p) }
                } else if strings.Contains(arg, "...") {
-                       match = pkgpattern.MatchPattern(arg)
+                       mp := pkgpattern.MatchPattern(arg)
+                       match = func(p string) bool { return mp(p) && !gover.IsToolchain(p) }
                } else {
                        var v string
                        if mg == nil {
index 6d620de076923c59df9871d4a5542cd65c898a9b..9eb9e6ddf8ea583f96f33b76477555e08a50d3f1 100644 (file)
@@ -143,6 +143,9 @@ type PackageOpts struct {
        // module.
        GoVersion string
 
+       // TidyGo, if true, indicates that GoVersion is from the tidy -go= flag.
+       TidyGo bool
+
        // Tags are the build tags in effect (as interpreted by the
        // cmd/go/internal/imports package).
        // If nil, treated as equivalent to imports.Tags().
@@ -338,7 +341,7 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
                }
        }
 
-       initialRS := LoadModFile(ctx)
+       initialRS := loadModFile(ctx, &opts)
 
        ld := loadFromRoots(ctx, loaderParams{
                PackageOpts:  opts,
@@ -407,6 +410,17 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
                        }
                }
 
+               // Update the go.mod file's Go version if necessary.
+               if modFile := ModFile(); modFile != nil && ld.GoVersion != "" {
+                       mg, _ := ld.requirements.Graph(ctx)
+                       if ld.TidyGo {
+                               if v := mg.Selected("go"); gover.Compare(ld.GoVersion, v) < 0 {
+                                       base.Fatalf("go: cannot tidy -go=%v: dependencies require %v", ld.GoVersion, v)
+                               }
+                       }
+                       modFile.AddGoStmt(ld.GoVersion)
+               }
+
                if !ExplicitWriteGoMod {
                        modfetch.TrimGoSum(keep)
 
@@ -419,11 +433,6 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
                                base.Fatalf("go: %v", err)
                        }
                }
-
-               // Update the go.mod file's Go version if necessary.
-               if modFile := ModFile(); modFile != nil && ld.GoVersion != "" {
-                       modFile.AddGoStmt(ld.GoVersion)
-               }
        }
 
        // Success! Update go.mod and go.sum (if needed) and return the results.
@@ -628,6 +637,9 @@ var (
 // if dir is in the module cache copy of a module in our build list.
 func pathInModuleCache(ctx context.Context, dir string, rs *Requirements) string {
        tryMod := func(m module.Version) (string, bool) {
+               if gover.IsToolchain(m.Path) {
+                       return "", false
+               }
                var root string
                var err error
                if repl := Replacement(m); repl.Path != "" && repl.Version == "" {
index cb1101630b3a76ae7af9c7bdf20d08f62f6dfc90..d97eb7cb625644cefad08fcdfee1e7ef2bb48ffc 100644 (file)
@@ -52,6 +52,12 @@ const (
        // errors.
        // See https://go.dev/issue/56222.
        tidyGoModSumVersion = "1.21"
+
+       // goStrictVersion is the Go version at which the Go versions
+       // became "strict" in the sense that, restricted to modules at this version
+       // or later, every module must have a go version line ≥ all its dependencies.
+       // It is also the version after which "too new" a version is considered a fatal error.
+       GoStrictVersion = "1.21"
 )
 
 // ReadModFile reads and parses the mod file at gomod. ReadModFile properly applies the
@@ -113,6 +119,7 @@ type modFileIndex struct {
        dataNeedsFix bool // true if fixVersion applied a change while parsing data
        module       module.Version
        goVersion    string // Go version (no "v" or "go" prefix)
+       toolchain    string
        require      map[module.Version]requireMeta
        replace      map[module.Version]module.Version
        exclude      map[module.Version]bool
@@ -455,6 +462,9 @@ func indexModFile(data []byte, modFile *modfile.File, mod module.Version, needsF
                i.goVersion = modFile.Go.Version
                rawGoVersion.Store(mod, modFile.Go.Version)
        }
+       if modFile.Toolchain != nil {
+               i.toolchain = modFile.Toolchain.Name
+       }
 
        i.require = make(map[module.Version]requireMeta, len(modFile.Require))
        for _, r := range modFile.Require {
@@ -492,21 +502,27 @@ func (i *modFileIndex) modFileIsDirty(modFile *modfile.File) bool {
                return true
        }
 
-       if modFile.Go == nil {
-               if i.goVersion != "" {
-                       return true
-               }
-       } else if modFile.Go.Version != i.goVersion {
-               if i.goVersion == "" && cfg.BuildMod != "mod" {
-                       // 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, and should bias toward providing useful behavior.
-               } else {
-                       return true
-               }
+       var goV, toolchain string
+       if modFile.Go != nil {
+               goV = modFile.Go.Version
+       }
+       if modFile.Toolchain != nil {
+               toolchain = modFile.Toolchain.Name
        }
 
-       if len(modFile.Require) != len(i.require) ||
+       // 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
+       // 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 goV != i.goVersion ||
+               toolchain != i.toolchain ||
+               len(modFile.Require) != len(i.require) ||
                len(modFile.Replace) != len(i.replace) ||
                len(modFile.Exclude) != len(i.exclude) {
                return true
@@ -554,6 +570,7 @@ var rawGoVersion sync.Map // map[module.Version]string
 type modFileSummary struct {
        module     module.Version
        goVersion  string
+       toolchain  string
        pruning    modPruning
        require    []module.Version
        retract    []retraction
@@ -579,12 +596,12 @@ type retraction struct {
 //
 // The caller must not modify the returned summary.
 func goModSummary(m module.Version) (*modFileSummary, error) {
-       if m.Path == "go" || m.Path == "toolchain" {
-               return &modFileSummary{module: m}, nil
-       }
        if m.Version == "" && !inWorkspaceMode() && MainModules.Contains(m.Path) {
                panic("internal error: goModSummary called on a main module")
        }
+       if gover.IsToolchain(m.Path) {
+               return rawGoModSummary(m)
+       }
 
        if cfg.BuildMod == "vendor" {
                summary := &modFileSummary{
@@ -639,9 +656,10 @@ func goModSummary(m module.Version) (*modFileSummary, error) {
                // to leave that validation for when we load actual packages from within the
                // module.
                if mpath := summary.module.Path; mpath != m.Path && mpath != actual.Path {
-                       return nil, module.VersionError(actual, fmt.Errorf(`parsing go.mod:
-       module declares its path as: %s
-               but was required as: %s`, mpath, m.Path))
+                       return nil, module.VersionError(actual,
+                               fmt.Errorf("parsing go.mod:\n"+
+                                       "\tmodule declares its path as: %s\n"+
+                                       "\t        but was required as: %s", mpath, m.Path))
                }
        }
 
@@ -680,6 +698,11 @@ func goModSummary(m module.Version) (*modFileSummary, error) {
 // rawGoModSummary cannot be used on the main module outside of workspace mode.
 func rawGoModSummary(m module.Version) (*modFileSummary, error) {
        if gover.IsToolchain(m.Path) {
+               if m.Path == "go" {
+                       // Declare that go 1.2.3 requires toolchain 1.2.3,
+                       // so that go get knows that downgrading toolchain implies downgrading go.
+                       return &modFileSummary{module: m, require: []module.Version{{Path: "toolchain", Version: "go" + m.Version}}}, nil
+               }
                return &modFileSummary{module: m}, nil
        }
        if m.Version == "" && !inWorkspaceMode() && MainModules.Contains(m.Path) {
@@ -704,15 +727,18 @@ func rawGoModSummary(m module.Version) (*modFileSummary, error) {
                        summary.module = f.Module.Mod
                        summary.deprecated = f.Module.Deprecated
                }
-               if f.Go != nil && f.Go.Version != "" {
+               if f.Go != nil {
                        rawGoVersion.LoadOrStore(m, f.Go.Version)
                        summary.goVersion = f.Go.Version
                        summary.pruning = pruningForGoVersion(f.Go.Version)
                } else {
                        summary.pruning = unpruned
                }
+               if f.Toolchain != nil {
+                       summary.toolchain = f.Toolchain.Name
+               }
                if len(f.Require) > 0 {
-                       summary.require = make([]module.Version, 0, len(f.Require))
+                       summary.require = make([]module.Version, 0, len(f.Require)+1)
                        for _, req := range f.Require {
                                summary.require = append(summary.require, req.Mod)
                        }
@@ -721,6 +747,7 @@ func rawGoModSummary(m module.Version) (*modFileSummary, error) {
                        if gover.Compare(summary.goVersion, gover.Local()) > 0 {
                                return nil, &gover.TooNewError{What: summary.module.String(), GoVersion: summary.goVersion}
                        }
+                       summary.require = append(summary.require, module.Version{Path: "go", Version: summary.goVersion})
                }
                if len(f.Retract) > 0 {
                        summary.retract = make([]retraction, 0, len(f.Retract))
index 19ba5b0650993a0bf14283627b9300892fef0ee8..038199f286b709a6ae7e7eb8ac743c35825f3369 100644 (file)
@@ -137,7 +137,7 @@ func queryProxy(ctx context.Context, proxy, path, query, current string, allowed
        defer span.Done()
 
        if current != "" && current != "none" && !gover.ModIsValid(path, current) {
-               return nil, fmt.Errorf("invalid previous version %q", current)
+               return nil, fmt.Errorf("invalid previous version %v@%v", path, current)
        }
        if cfg.BuildMod == "vendor" {
                return nil, errQueryDisabled
@@ -713,6 +713,9 @@ func QueryPattern(ctx context.Context, pattern, query string, current func(strin
                                return r, err
                        }
                        r.Mod.Version = r.Rev.Version
+                       if gover.IsToolchain(r.Mod.Path) {
+                               return r, nil
+                       }
                        root, isLocal, err := fetch(ctx, r.Mod)
                        if err != nil {
                                return r, err
index 627f91f09c11804e37342a819d79426c45644c1f..cb03b697a8ad626b796ef10aaa2a2b1ffe97c9d4 100644 (file)
@@ -19,6 +19,7 @@ import (
 
        "cmd/go/internal/cfg"
        "cmd/go/internal/fsys"
+       "cmd/go/internal/gover"
        "cmd/go/internal/imports"
        "cmd/go/internal/modindex"
        "cmd/go/internal/par"
@@ -172,7 +173,7 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f
        }
 
        for _, mod := range modules {
-               if !treeCanMatch(mod.Path) {
+               if gover.IsToolchain(mod.Path) || !treeCanMatch(mod.Path) {
                        continue
                }
 
diff --git a/src/cmd/go/testdata/script/mod_goline.txt b/src/cmd/go/testdata/script/mod_goline.txt
new file mode 100644 (file)
index 0000000..d7aa34f
--- /dev/null
@@ -0,0 +1,121 @@
+env TESTGO_VERSION=go1.99
+
+! go list -f '{{.Module.GoVersion}}'
+stderr 'go: updates to go.mod needed'
+stderr 'go mod tidy'
+
+go mod tidy
+cat go.mod
+go list -f '{{.Module.GoVersion}}'
+stdout 1.22
+
+# Adding a@v1.0.01 should upgrade to Go 1.23rc1.
+cp go.mod go.mod1
+go get example.com/a@v1.0.1
+stderr '^go: upgraded go 1.22 => 1.23rc1\ngo: upgraded example.com/a v1.0.0 => v1.0.1\ngo: upgraded example.com/b v1.0.0 => v1.0.1$'
+go list -f '{{.Module.GoVersion}}'
+stdout 1.23rc1
+
+ # would be nice but doesn't work yet
+ # go mod why -m go
+ # stderr xxx
+
+# Repeating the update with go@1.24.0 should use that Go version.
+cp go.mod1 go.mod
+go get example.com/a@v1.0.1 go@1.24.0
+go list -f '{{.Module.GoVersion}}'
+stdout 1.24.0
+
+# Go version-constrained updates should report the problems.
+cp go.mod1 go.mod
+! go get example.com/a@v1.0.2 go@1.24.2
+stderr '^go: example.com/a@v1.0.2 requires go@1.25, not go@1.24.2$'
+! go get example.com/a@v1.0.2 go@1.26.3
+stderr '^go: example.com/a@v1.0.2 indirectly requires go@1.27, not go@1.26.3$'
+go get example.com/a@v1.0.2 go@1.28rc1
+go list -f '{{.Module.GoVersion}}'
+stdout 1.28rc1
+go get go@1.24.2
+stderr '^go: downgraded go 1.28rc1 => 1.24.2$'
+stderr '^go: downgraded example.com/a v1.0.2 => v1.0.1$'
+stderr '^go: downgraded example.com/b v1.0.2 => v1.0.1$'
+go list -f '{{.Module.GoVersion}}'
+stdout 1.24.2
+
+-- go.mod --
+module m
+go 1.21
+
+require (
+       example.com/a v1.0.0
+       example.com/b v0.9.0
+)
+
+replace example.com/a v1.0.0 => ./a100
+replace example.com/a v1.0.1 => ./a101
+replace example.com/a v1.0.2 => ./a102
+replace example.com/b v1.0.1 => ./b101
+replace example.com/b v1.0.2 => ./b102
+replace example.com/b v1.0.0 => ./b100
+replace example.com/b v0.9.0 => ./b100
+
+-- x.go --
+package m
+
+import (
+       _ "example.com/a"
+       _ "example.com/b"
+)
+
+-- a100/go.mod --
+module example.com/a
+go 1.22
+
+require example.com/b v1.0.0
+
+-- a100/a.go --
+package a
+
+-- a101/go.mod --
+// this module is technically invalid, since the dep example.com/b has a newer go line than this module,
+// but we should still be able to handle it.
+module example.com/a
+go 1.22
+
+require example.com/b v1.0.1
+
+-- a101/a.go --
+package a
+
+-- a102/go.mod --
+// this module is technically invalid, since the dep example.com/b has a newer go line than this module,
+// but we should still be able to handle it.
+module example.com/a
+go 1.25
+
+require example.com/b v1.0.2
+
+-- a102/a.go --
+package a
+
+-- b100/go.mod --
+module example.com/b
+go 1.22
+
+-- b100/b.go --
+package b
+
+-- b101/go.mod --
+module example.com/b
+go 1.23rc1
+
+-- b101/b.go --
+package b
+
+-- b102/go.mod --
+module example.com/b
+go 1.27
+
+-- b102/b.go --
+package b
+
diff --git a/src/cmd/go/testdata/script/mod_goline_old.txt b/src/cmd/go/testdata/script/mod_goline_old.txt
new file mode 100644 (file)
index 0000000..bbe611b
--- /dev/null
@@ -0,0 +1,72 @@
+env TESTGO_VERSION=go1.24
+
+go list -f '{{.Module.GoVersion}}'
+stdout 1.15
+
+go mod tidy
+go list -f '{{.Module.GoVersion}}'
+stdout 1.15
+
+go get example.com/a@v1.0.1
+go list -f '{{.Module.GoVersion}}'
+stdout 1.15
+
+go get example.com/a@v1.0.1 go@1.16
+go list -f '{{.Module.GoVersion}}'
+stdout 1.16
+
+-- go.mod --
+module m
+go 1.15
+
+require (
+       example.com/a v1.0.0
+       example.com/b v1.0.0
+)
+
+replace example.com/a v1.0.0 => ./a100
+replace example.com/a v1.0.1 => ./a101
+replace example.com/b v1.0.1 => ./b101
+replace example.com/b v1.0.0 => ./b100
+replace example.com/b v0.9.0 => ./b100
+
+-- x.go --
+package m
+
+import (
+       _ "example.com/a"
+       _ "example.com/b"
+)
+
+-- a100/go.mod --
+module example.com/a
+go 1.16
+
+require example.com/b v1.0.0
+
+-- a100/a.go --
+package a
+
+-- a101/go.mod --
+module example.com/a
+go 1.17
+
+require example.com/b v1.0.1
+
+-- a101/a.go --
+package a
+
+-- b100/go.mod --
+module example.com/b
+go 1.18
+
+-- b100/b.go --
+package b
+
+-- b101/go.mod --
+module example.com/b
+go 1.19
+
+-- b101/b.go --
+package b
+
index eeb93f19136753283c2e3bc05fc17d0b13f178ee..43aaa390645ac0af1620b78088fd9c20f1e46678 100644 (file)
@@ -60,6 +60,8 @@ golang.org/issue/root
 golang.org/issue/mirror v0.1.0 => ./mirror-v0.1.0
 golang.org/issue/pkg v0.1.0 => ./pkg-v0.1.0
 -- graph.txt --
+golang.org/issue/root go@1.12
 golang.org/issue/root golang.org/issue/mirror@v0.1.0
+go@1.12 toolchain@go1.12
 golang.org/issue/mirror@v0.1.0 golang.org/issue/root@v0.1.0
 golang.org/issue/root@v0.1.0 golang.org/issue/pkg@v0.1.0
index 9fdb6fc121258721def75cb83756988a7621d388..14b1c3728e3682775c7fa7401aee58950d0bc8ae 100644 (file)
@@ -79,10 +79,12 @@ package use
 
 import _ "rsc.io/quote"
 -- graph.want --
+m go@1.18
 m golang.org/x/text@v0.0.0-20170915032832-14c0d48ead0c
 m rsc.io/quote@v1.5.2
 m rsc.io/sampler@v1.3.0
 m rsc.io/testonly@v1.0.0
+go@1.18 toolchain@go1.18
 rsc.io/quote@v1.5.2 rsc.io/sampler@v1.3.0
 rsc.io/sampler@v1.3.0 golang.org/x/text@v0.0.0-20170915032832-14c0d48ead0c
 -- why.want --
diff --git a/src/cmd/go/testdata/script/mod_toolchain.txt b/src/cmd/go/testdata/script/mod_toolchain.txt
new file mode 100644 (file)
index 0000000..bdaa859
--- /dev/null
@@ -0,0 +1,75 @@
+[!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$'
+! stderr '(added|removed|upgraded|downgraded) go'
+grep 'toolchain go1.20.1' go.mod
+
+go get toolchain@none
+stderr '^go: removed toolchain go1.20.1$'
+! stderr '(added|removed|upgraded|downgraded) go'
+! grep toolchain go.mod
+
+go get toolchain@go1.20.1
+stderr '^go: added toolchain go1.20.1$'
+! stderr '(added|removed|upgraded|downgraded) go'
+grep 'toolchain go1.20.1' go.mod
+
+cat go.mod
+go get go@1.20.3
+stderr '^go: upgraded go 1.10 => 1.20.3$'
+stderr '^go: removed toolchain go1.20.1$'
+grep 'go 1.20.3' go.mod
+! 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.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
+! 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
+
+env TESTGO_VERSION=go1.20.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
+
+env TESTGO_VERSION=go1.30
+go get go@1.20.1
+grep 'go 1.20.1' go.mod
+go get m2@v1.0.0
+stderr '^go: upgraded go 1.20.1 => 1.22$'
+stderr '^go: added m2 v1.0.0$'
+grep 'go 1.22' go.mod
+
+go mod edit -toolchain=go1.29.0  # cannot go get because it doesn't exist
+go get go@1.28.0
+go get toolchain@none
+stderr '^go: removed toolchain go1.29.0'
+! stderr ' go 1'
+grep 'go 1.28.0' go.mod
+
+-- go.mod --
+module m
+go 1.10
+
+replace m2 v1.0.0 => ./m2
+
+-- m2/go.mod --
+module m2
+go 1.22
index 8f1aeddf47106d3abc51ba45610853ed0fab3164..b86dc00d4331c5a31391db4aef240be897b236e0 100644 (file)
@@ -25,7 +25,7 @@ go mod why rsc.io/quote
 stdout '# rsc.io/quote\nexample.com/a\nrsc.io/quote'
 
 go mod graph
-stdout 'example.com/a rsc.io/quote@v1.5.2\nexample.com/b example.com/c@v1.0.0\nrsc.io/quote@v1.5.2 rsc.io/sampler@v1.3.0\nrsc.io/sampler@v1.3.0 golang.org/x/text@v0.0.0-20170915032832-14c0d48ead0c'
+stdout 'example.com/a rsc.io/quote@v1.5.2\nexample.com/b example.com/c@v1.0.0\ngo@1.18 toolchain@go1.18\nrsc.io/quote@v1.5.2 rsc.io/sampler@v1.3.0\nrsc.io/sampler@v1.3.0 golang.org/x/text@v0.0.0-20170915032832-14c0d48ead0c'
 
 -- go.work --
 go 1.18