aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBryan C. Mills <bcmills@google.com>2023-05-25 17:07:26 -0400
committerGopher Robot <gobot@golang.org>2023-06-02 23:07:08 +0000
commit77786d90fae21f526caaddd23c887a2a7233a299 (patch)
treec781e5ff1abe4e259638e342ea7e00d5a374bf3f
parentc292a74c389aed9cab01d435a8f8cb4bfb087544 (diff)
downloadgo-77786d90fae21f526caaddd23c887a2a7233a299.tar.gz
go-77786d90fae21f526caaddd23c887a2a7233a299.zip
cmd/go: adjust pruning and switch toolchains if needed when go get changes go version
When we do 'go get', the Go version can change now. That means we need to do the pruning conversions that until now have only been necessary in go mod tidy -go=version. We may also need to upgrade the toolchain in order to load enough o the module graph to finish the edit, so we should let a TooNewError bubble up to the caller instead of trying to downgrade the affected module to avoid the error. Revised from CL 498120. For #57001. Change-Id: Ic8994737eca4ed61ccc093a69e46f5a6caa8be87 Reviewed-on: https://go-review.googlesource.com/c/go/+/498267 Reviewed-by: Russ Cox <rsc@golang.org> Auto-Submit: Bryan Mills <bcmills@google.com> Run-TryBot: Bryan Mills <bcmills@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
-rw-r--r--src/cmd/go/internal/modget/get.go4
-rw-r--r--src/cmd/go/internal/modload/buildlist.go47
-rw-r--r--src/cmd/go/internal/modload/edit.go109
-rw-r--r--src/cmd/go/internal/modload/init.go45
-rw-r--r--src/cmd/go/internal/modload/load.go2
-rw-r--r--src/cmd/go/internal/modload/modfile.go4
-rw-r--r--src/cmd/go/testdata/script/mod_indirect_main.txt2
-rw-r--r--src/cmd/go/testdata/script/mod_skip_write.txt2
-rw-r--r--src/cmd/go/testdata/script/mod_tidy_version.txt94
-rw-r--r--src/cmd/go/testdata/script/work_why_download_graph.txt2
10 files changed, 254 insertions, 57 deletions
diff --git a/src/cmd/go/internal/modget/get.go b/src/cmd/go/internal/modget/get.go
index 8a8b8dea22..42ddb9cf38 100644
--- a/src/cmd/go/internal/modget/get.go
+++ b/src/cmd/go/internal/modget/get.go
@@ -1777,6 +1777,10 @@ func (r *resolver) reportChanges(oldReqs, newReqs []module.Version) {
fmt.Fprintf(os.Stderr, "go: removed %s %s\n", c.path, c.old)
} else if gover.ModCompare(c.path, c.new, c.old) > 0 {
fmt.Fprintf(os.Stderr, "go: upgraded %s %s => %s\n", c.path, c.old, c.new)
+ if c.path == "go" && gover.Compare(c.old, gover.ExplicitIndirectVersion) < 0 && gover.Compare(c.new, gover.ExplicitIndirectVersion) >= 0 {
+ fmt.Fprintf(os.Stderr, "\tnote: expanded dependencies to upgrade to go %s or higher; run 'go mod tidy' to clean up\n", gover.ExplicitIndirectVersion)
+ }
+
} else {
fmt.Fprintf(os.Stderr, "go: downgraded %s %s => %s\n", c.path, c.old, c.new)
}
diff --git a/src/cmd/go/internal/modload/buildlist.go b/src/cmd/go/internal/modload/buildlist.go
index ecc07ed91f..a5ad20d9ed 100644
--- a/src/cmd/go/internal/modload/buildlist.go
+++ b/src/cmd/go/internal/modload/buildlist.go
@@ -126,28 +126,20 @@ func newRequirements(pruning modPruning, rootModules []module.Version, direct ma
}
}
- if workFilePath != "" && pruning != workspace {
- panic("in workspace mode, but pruning is not workspace in newRequirements")
- }
- for i, m := range rootModules {
- if m.Version == "" && MainModules.Contains(m.Path) {
- panic(fmt.Sprintf("newRequirements called with untrimmed build list: rootModules[%v] is a main module", i))
+ if pruning != workspace {
+ if workFilePath != "" {
+ panic("in workspace mode, but pruning is not workspace in newRequirements")
}
- if m.Path == "" || m.Version == "" {
- panic(fmt.Sprintf("bad requirement: rootModules[%v] = %v", i, m))
+ for i, m := range rootModules {
+ if m.Version == "" && MainModules.Contains(m.Path) {
+ panic(fmt.Sprintf("newRequirements called with untrimmed build list: rootModules[%v] is a main module", i))
+ }
+ if m.Path == "" || m.Version == "" {
+ panic(fmt.Sprintf("bad requirement: rootModules[%v] = %v", i, m))
+ }
}
}
- // Allow unsorted root modules, because go and toolchain
- // are treated as the final graph roots but not trimmed from the build list,
- // so they always appear at the beginning of the list.
- r := slices.Clip(slices.Clone(rootModules))
- gover.ModSort(r)
- if !reflect.DeepEqual(r, rootModules) {
- fmt.Fprintln(os.Stderr, "RM", rootModules)
- panic("unsorted")
- }
-
rs := &Requirements{
pruning: pruning,
rootModules: rootModules,
@@ -155,12 +147,23 @@ func newRequirements(pruning modPruning, rootModules []module.Version, direct ma
direct: direct,
}
- for _, m := range rootModules {
+ for i, m := range rootModules {
+ if i > 0 {
+ prev := rootModules[i-1]
+ if prev.Path > m.Path || (prev.Path == m.Path && gover.ModCompare(m.Path, prev.Version, m.Version) > 0) {
+ panic(fmt.Sprintf("newRequirements called with unsorted roots: %v", rootModules))
+ }
+ }
+
if v, ok := rs.maxRootVersion[m.Path]; ok && gover.ModCompare(m.Path, v, m.Version) >= 0 {
continue
}
rs.maxRootVersion[m.Path] = m.Version
}
+
+ if rs.maxRootVersion["go"] == "" {
+ panic(`newRequirements called without a "go" version`)
+ }
return rs
}
@@ -223,6 +226,12 @@ func (rs *Requirements) initVendor(vendorList []module.Version) {
})
}
+// GoVersion returns the Go language version for the Requirements.
+func (rs *Requirements) GoVersion() string {
+ v, _ := rs.rootSelected("go")
+ return v
+}
+
// rootSelected returns the version of the root dependency with the given module
// path, or the zero module.Version and ok=false if the module is not a root
// dependency.
diff --git a/src/cmd/go/internal/modload/edit.go b/src/cmd/go/internal/modload/edit.go
index 7ee4db536e..63ee15c764 100644
--- a/src/cmd/go/internal/modload/edit.go
+++ b/src/cmd/go/internal/modload/edit.go
@@ -10,6 +10,7 @@ import (
"cmd/go/internal/mvs"
"cmd/go/internal/par"
"context"
+ "errors"
"fmt"
"maps"
"os"
@@ -46,10 +47,51 @@ func editRequirements(ctx context.Context, rs *Requirements, tryUpgrade, mustSel
panic("editRequirements cannot edit workspace requirements")
}
+ orig := rs
+ // If we already know what go version we will end up on after the edit, and
+ // the pruning for that version is different, go ahead and apply it now.
+ //
+ // If we are changing from pruned to unpruned, then we MUST check the unpruned
+ // graph for conflicts from the start. (Checking only for pruned conflicts
+ // would miss some that would be introduced later.)
+ //
+ // If we are changing from unpruned to pruned, then we would like to avoid
+ // unnecessary downgrades due to conflicts that would be pruned out of the
+ // final graph anyway.
+ //
+ // Note that even if we don't find a go version in mustSelect, it is possible
+ // that we will switch from unpruned to pruned (but not the other way around!)
+ // after applying the edits if we find a dependency that requires a high
+ // enough go version to trigger an upgrade.
+ rootPruning := orig.pruning
+ for _, m := range mustSelect {
+ if m.Path == "go" {
+ rootPruning = pruningForGoVersion(m.Version)
+ break
+ } else if m.Path == "toolchain" && pruningForGoVersion(gover.FromToolchain(m.Version)) == unpruned {
+ // We don't know exactly what go version we will end up at, but we know
+ // that it must be a version supported by the requested toolchain, and
+ // that toolchain does not support pruning.
+ //
+ // TODO(bcmills): 'go get' ought to reject explicit toolchain versions
+ // older than gover.GoStrictVersion. Once that is fixed, is this still
+ // needed?
+ rootPruning = unpruned
+ break
+ }
+ }
+
+ if rootPruning != rs.pruning {
+ rs, err = convertPruning(ctx, rs, rootPruning)
+ if err != nil {
+ return orig, false, err
+ }
+ }
+
// selectedRoot records the edited version (possibly "none") for each module
// path that would be a root in the edited requirements.
var selectedRoot map[string]string // module path → edited version
- if rs.pruning == pruned {
+ if rootPruning == pruned {
selectedRoot = maps.Clone(rs.maxRootVersion)
} else {
// In a module without graph pruning, modules that provide packages imported
@@ -62,7 +104,7 @@ func editRequirements(ctx context.Context, rs *Requirements, tryUpgrade, mustSel
if err != nil {
// If we couldn't load the graph, we don't know what its requirements were
// to begin with, so we can't edit those requirements in a coherent way.
- return rs, false, err
+ return orig, false, err
}
bl := mg.BuildList()[MainModules.Len():]
selectedRoot = make(map[string]string, len(bl))
@@ -182,10 +224,11 @@ func editRequirements(ctx context.Context, rs *Requirements, tryUpgrade, mustSel
// of every root. The upgraded roots are in addition to the original
// roots, so we will have enough information to trace a path to each
// conflict we discover from one or more of the original roots.
- mg, upgradedRoots, err := extendGraph(ctx, rs, roots, selectedRoot)
+ mg, upgradedRoots, err := extendGraph(ctx, rootPruning, roots, selectedRoot)
if err != nil {
- if mg == nil {
- return rs, false, err
+ var tooNew *gover.TooNewError
+ if mg == nil || errors.As(err, &tooNew) {
+ return orig, false, err
}
// We're about to walk the entire extended module graph, so we will find
// any error then — and we will either try to resolve it by downgrading
@@ -196,13 +239,13 @@ func editRequirements(ctx context.Context, rs *Requirements, tryUpgrade, mustSel
// the extended module graph.
extendedRootPruning := make(map[module.Version]modPruning, len(roots)+len(upgradedRoots))
findPruning := func(m module.Version) modPruning {
- if rs.pruning == pruned {
+ if rootPruning == pruned {
summary, _ := mg.loadCache.Get(m)
if summary != nil && summary.pruning == unpruned {
return unpruned
}
}
- return rs.pruning
+ return rootPruning
}
for _, m := range roots {
extendedRootPruning[m] = findPruning(m)
@@ -346,7 +389,7 @@ func editRequirements(ctx context.Context, rs *Requirements, tryUpgrade, mustSel
// the edit. We want to make sure we consider keeping it as-is,
// even if it wouldn't normally be included. (For example, it might
// be a pseudo-version or pre-release.)
- origMG, _ := rs.Graph(ctx)
+ origMG, _ := orig.Graph(ctx)
origV := origMG.Selected(m.Path)
if conflict.Err != nil && origV == m.Version {
@@ -376,14 +419,14 @@ func editRequirements(ctx context.Context, rs *Requirements, tryUpgrade, mustSel
prev.Version = origV
} else if err != nil {
// We don't know the next downgrade to try. Give up.
- return rs, false, err
+ return orig, false, err
}
if rejectedRoot[prev] {
// We already rejected prev in a previous round.
// To ensure that this algorithm terminates, don't try it again.
continue
}
- pruning := rs.pruning
+ pruning := rootPruning
if pruning == pruned {
if summary, err := mg.loadCache.Get(m); err == nil {
pruning = summary.pruning
@@ -460,10 +503,10 @@ func editRequirements(ctx context.Context, rs *Requirements, tryUpgrade, mustSel
break
}
if len(conflicts) > 0 {
- return rs, false, &ConstraintError{Conflicts: conflicts}
+ return orig, false, &ConstraintError{Conflicts: conflicts}
}
- if rs.pruning == unpruned {
+ if rootPruning == unpruned {
// An unpruned go.mod file lists only a subset of the requirements needed
// for building packages. Figure out which requirements need to be explicit.
var rootPaths []string
@@ -493,12 +536,12 @@ func editRequirements(ctx context.Context, rs *Requirements, tryUpgrade, mustSel
}
}
- changed = !slices.Equal(roots, rs.rootModules)
+ changed = rootPruning != orig.pruning || !slices.Equal(roots, orig.rootModules)
if !changed {
// Because the roots we just computed are unchanged, the entire graph must
// be the same as it was before. Save the original rs, since we have
// probably already loaded its requirement graph.
- return rs, false, nil
+ return orig, false, nil
}
// A module that is not even in the build list necessarily cannot provide
@@ -518,7 +561,37 @@ func editRequirements(ctx context.Context, rs *Requirements, tryUpgrade, mustSel
direct[m.Path] = true
}
}
- return newRequirements(rs.pruning, roots, direct), changed, nil
+ edited = newRequirements(rootPruning, roots, direct)
+
+ // If we ended up adding a dependency that upgrades our go version far enough
+ // to activate pruning, we must convert the edited Requirements in order to
+ // avoid dropping transitive dependencies from the build list the next time
+ // someone uses the updated go.mod file.
+ //
+ // Note that it isn't possible to go in the other direction (from pruned to
+ // unpruned) unless the "go" or "toolchain" module is explicitly listed in
+ // mustSelect, which we already handled at the very beginning of the edit.
+ // That is because the virtual "go" module only requires a "toolchain",
+ // and the "toolchain" module never requires anything else, which means that
+ // those two modules will never be downgraded due to a conflict with any other
+ // constraint.
+ if rootPruning == unpruned {
+ if v, ok := edited.rootSelected("go"); ok && pruningForGoVersion(v) == pruned {
+ // Since we computed the edit with the unpruned graph, and the pruned
+ // graph is a strict subset of the unpruned graph, this conversion
+ // preserves the exact (edited) build list that we already computed.
+ //
+ // However, it does that by shoving the whole build list into the roots of
+ // the graph. 'go get' will check for that sort of transition and log a
+ // message reminding the user how to clean up this mess we're about to
+ // make. 😅
+ edited, err = convertPruning(ctx, edited, pruned)
+ if err != nil {
+ return orig, false, err
+ }
+ }
+ }
+ return edited, true, nil
}
// extendGraph loads the module graph from roots, and iteratively extends it by
@@ -532,15 +605,15 @@ func editRequirements(ctx context.Context, rs *Requirements, tryUpgrade, mustSel
// The extended graph is useful for diagnosing version conflicts: for each
// selected module version, it can provide a complete path of requirements from
// some root to that version.
-func extendGraph(ctx context.Context, rs *Requirements, roots []module.Version, selectedRoot map[string]string) (mg *ModuleGraph, upgradedRoot map[module.Version]bool, err error) {
+func extendGraph(ctx context.Context, rootPruning modPruning, roots []module.Version, selectedRoot map[string]string) (mg *ModuleGraph, upgradedRoot map[module.Version]bool, err error) {
for {
- mg, err = readModGraph(ctx, rs.pruning, roots, upgradedRoot)
+ mg, err = readModGraph(ctx, rootPruning, roots, upgradedRoot)
// We keep on going even if err is non-nil until we reach a steady state.
// (Note that readModGraph returns a non-nil *ModuleGraph even in case of
// errors.) The caller may be able to fix the errors by adjusting versions,
// so we really want to return as complete a result as we can.
- if rs.pruning == unpruned {
+ if rootPruning == unpruned {
// Everything is already unpruned, so there isn't anything we can do to
// extend it further.
break
diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go
index 446f4d9fa5..9483bac2d8 100644
--- a/src/cmd/go/internal/modload/init.go
+++ b/src/cmd/go/internal/modload/init.go
@@ -783,17 +783,33 @@ func loadModFile(ctx context.Context, opts *PackageOpts) (*Requirements, error)
// any module.
mainModule := module.Version{Path: "command-line-arguments"}
MainModules = makeMainModules([]module.Version{mainModule}, []string{""}, []*modfile.File{nil}, []*modFileIndex{nil}, nil)
- goVersion := gover.Local()
- rawGoVersion.Store(mainModule, goVersion)
- pruning := pruningForGoVersion(goVersion)
+ var (
+ goVersion string
+ pruning modPruning
+ roots []module.Version
+ direct = map[string]bool{"go": true}
+ )
if inWorkspaceMode() {
+ // Since we are in a workspace, the Go version for the synthetic
+ // "command-line-arguments" module must not exceed the Go version
+ // for the workspace.
+ goVersion = MainModules.GoVersion()
pruning = workspace
+ roots = []module.Version{
+ mainModule,
+ {Path: "go", Version: goVersion},
+ {Path: "toolchain", Version: gover.LocalToolchain()},
+ }
+ } else {
+ goVersion = gover.Local()
+ pruning = pruningForGoVersion(goVersion)
+ roots = []module.Version{
+ {Path: "go", Version: goVersion},
+ {Path: "toolchain", Version: gover.LocalToolchain()},
+ }
}
- roots := []module.Version{
- {Path: "go", Version: gover.Local()},
- {Path: "toolchain", Version: gover.LocalToolchain()},
- }
- requirements = newRequirements(pruning, roots, nil)
+ rawGoVersion.Store(mainModule, goVersion)
+ requirements = newRequirements(pruning, roots, direct)
if cfg.BuildMod == "vendor" {
// For issue 56536: Some users may have GOFLAGS=-mod=vendor set.
// Make sure it behaves as though the fake module is vendored
@@ -1213,13 +1229,16 @@ func requirementsFromModFiles(ctx context.Context, workFile *modfile.WorkFile, m
goVersion = gover.DefaultGoModVersion
}
roots = append(roots, module.Version{Path: "go", Version: goVersion})
- direct["go"] = true
+ direct["go"] = true // Every module directly uses the language and runtime.
- if toolchain == "" {
- toolchain = "go" + goVersion
+ if toolchain != "" {
+ roots = append(roots, module.Version{Path: "toolchain", Version: toolchain})
+ // Leave the toolchain as indirect: nothing in the user's module directly
+ // imports a package from the toolchain, and (like an indirect dependency in
+ // a module without graph pruning) we may remove the toolchain line
+ // automatically if the 'go' version is changed so that it implies the exact
+ // same toolchain.
}
- roots = append(roots, module.Version{Path: "toolchain", Version: toolchain})
- direct["toolchain"] = true
gover.ModSort(roots)
rs := newRequirements(pruning, roots, direct)
diff --git a/src/cmd/go/internal/modload/load.go b/src/cmd/go/internal/modload/load.go
index a96ce0283d..5384d753bf 100644
--- a/src/cmd/go/internal/modload/load.go
+++ b/src/cmd/go/internal/modload/load.go
@@ -1013,6 +1013,8 @@ func loadFromRoots(ctx context.Context, params loaderParams) *loader {
ld.errorf("go: go.mod file indicates go %s, but maximum version supported by tidy is %s\n", ld.GoVersion, gover.Local())
base.ExitIfErrors()
}
+ } else {
+ ld.requirements = overrideRoots(ctx, ld.requirements, []module.Version{{Path: "go", Version: ld.GoVersion}})
}
if ld.Tidy {
diff --git a/src/cmd/go/internal/modload/modfile.go b/src/cmd/go/internal/modload/modfile.go
index 72fc293d8f..026be5eef7 100644
--- a/src/cmd/go/internal/modload/modfile.go
+++ b/src/cmd/go/internal/modload/modfile.go
@@ -639,8 +639,8 @@ 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,
+ if m.Path == "go" && gover.Compare(m.Version, gover.GoStrictVersion) >= 0 {
+ // Declare that go 1.21.3 requires toolchain 1.21.3,
// so that go get knows that downgrading toolchain implies downgrading go
// and similarly upgrading go requires upgrading the toolchain.
return &modFileSummary{module: m, require: []module.Version{{Path: "toolchain", Version: "go" + m.Version}}}, nil
diff --git a/src/cmd/go/testdata/script/mod_indirect_main.txt b/src/cmd/go/testdata/script/mod_indirect_main.txt
index 22b344f866..e84eb9c5cd 100644
--- a/src/cmd/go/testdata/script/mod_indirect_main.txt
+++ b/src/cmd/go/testdata/script/mod_indirect_main.txt
@@ -62,7 +62,5 @@ 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
-golang.org/issue/root toolchain@go1.12
-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
diff --git a/src/cmd/go/testdata/script/mod_skip_write.txt b/src/cmd/go/testdata/script/mod_skip_write.txt
index 1850cdf5fd..db47b9c424 100644
--- a/src/cmd/go/testdata/script/mod_skip_write.txt
+++ b/src/cmd/go/testdata/script/mod_skip_write.txt
@@ -84,8 +84,6 @@ 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
-m toolchain@go1.18
-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_tidy_version.txt b/src/cmd/go/testdata/script/mod_tidy_version.txt
index 11f1d69dc5..e3f2561f5e 100644
--- a/src/cmd/go/testdata/script/mod_tidy_version.txt
+++ b/src/cmd/go/testdata/script/mod_tidy_version.txt
@@ -90,6 +90,37 @@ cp go.mod.orig go.mod
go mod tidy -go=''
cmpenv go.mod go.mod.latest
+# Repeat with go get go@ instead of mod tidy.
+
+# Go 1.16 -> 1.17 should be a no-op.
+cp go.mod.116 go.mod
+go get go@1.16
+cmp go.mod go.mod.116
+
+# Go 1.17 -> 1.16 should leave b (go get is not tidy).
+cp go.mod.117 go.mod
+go get go@1.16
+cmp go.mod go.mod.116from117
+
+# Go 1.15 -> 1.16 should leave d (go get is not tidy).
+cp go.mod.115 go.mod
+go get go@1.16
+cmp go.mod go.mod.116from115
+
+# Go 1.16 -> 1.17 should add b.
+cp go.mod.116 go.mod
+go get go@1.17
+stderr '^\tnote: expanded dependencies to upgrade to go 1.17 or higher; run ''go mod tidy'' to clean up'
+cmp go.mod go.mod.117
+
+# Go 1.16 -> 1.15 should add d,
+# but 'go get' doesn't load enough packages to know that.
+# (This leaves the module untidy, but the user can fix it by running 'go mod tidy'.)
+cp go.mod.116 go.mod
+go get go@1.15 toolchain@none
+cmp go.mod go.mod.115from116
+go mod tidy
+cmp go.mod go.mod.115-2
# Updating the go line to 1.21 or higher also updates the toolchain line,
# only if the toolchain is higher than what would be implied by the go line.
@@ -150,6 +181,47 @@ replace (
example.net/d v0.1.0 => ./d
example.net/d v0.2.0 => ./d
)
+-- go.mod.115from116 --
+module example.com/m
+
+go 1.15
+
+require example.net/a v0.1.0
+
+require example.net/c v0.1.0 // indirect
+
+replace (
+ example.net/a v0.1.0 => ./a
+ example.net/a v0.2.0 => ./a
+ example.net/b v0.1.0 => ./b
+ example.net/b v0.2.0 => ./b
+ example.net/c v0.1.0 => ./c
+ example.net/c v0.2.0 => ./c
+ example.net/d v0.1.0 => ./d
+ example.net/d v0.2.0 => ./d
+)
+-- go.mod.116from115 --
+module example.com/m
+
+go 1.16
+
+require example.net/a v0.1.0
+
+require (
+ example.net/c v0.1.0 // indirect
+ example.net/d v0.1.0 // indirect
+)
+
+replace (
+ example.net/a v0.1.0 => ./a
+ example.net/a v0.2.0 => ./a
+ example.net/b v0.1.0 => ./b
+ example.net/b v0.2.0 => ./b
+ example.net/c v0.1.0 => ./c
+ example.net/c v0.2.0 => ./c
+ example.net/d v0.1.0 => ./d
+ example.net/d v0.2.0 => ./d
+)
-- go.mod.115-2 --
module example.com/m
@@ -213,6 +285,28 @@ replace (
example.net/d v0.1.0 => ./d
example.net/d v0.2.0 => ./d
)
+-- go.mod.116from117 --
+module example.com/m
+
+go 1.16
+
+require example.net/a v0.1.0
+
+require (
+ example.net/b v0.1.0 // indirect
+ example.net/c v0.1.0 // indirect
+)
+
+replace (
+ example.net/a v0.1.0 => ./a
+ example.net/a v0.2.0 => ./a
+ example.net/b v0.1.0 => ./b
+ example.net/b v0.2.0 => ./b
+ example.net/c v0.1.0 => ./c
+ example.net/c v0.2.0 => ./c
+ example.net/d v0.1.0 => ./d
+ example.net/d v0.2.0 => ./d
+)
-- go.mod.latest --
module example.com/m
diff --git a/src/cmd/go/testdata/script/work_why_download_graph.txt b/src/cmd/go/testdata/script/work_why_download_graph.txt
index b86dc00d43..8f1aeddf47 100644
--- a/src/cmd/go/testdata/script/work_why_download_graph.txt
+++ b/src/cmd/go/testdata/script/work_why_download_graph.txt
@@ -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\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'
+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'
-- go.work --
go 1.18