1
0
mirror of https://github.com/golang/go synced 2024-11-26 22:31:23 -07:00

cmd/go: prune more dependencies in 'go get'

Prior to this change, 'go get' pulled in every version of each module
whose path is explicitly listed in the go.mod file. When graph pruning
is enabled (that is, when the main module is at 'go 1.17' or higher),
that pulled in transitive dependencies of older-than-selected versions
of dependencies, which are normally pruned out by other 'go' commands
(including 'go mod tidy' and 'go mod graph').

To make matters worse, different parts of `go get` were making
different assumptions about which kinds of conflicts would be
reported: the modget package assumed that any conflict is necessarily
due to some explicit constraint, but 'go get' was imposing an
additional constraint that modules could not be incidentally upgraded
in the course of a downgrade. When that additional constraint failed,
the modload package reported the failure as though it were a real
(caller-supplied) constraint, confusing the caller (which couldn't
identify any specific package or argument that caused the failure).

This change fixes both of those problems by replacing the
modload.EditRequirements algorithm with a different one.

The new algorithm is, roughly, as follows.

1. Propose a list of “root requirements” to be written to the updated
   go.mod file.

2. Load the module graph from those requirements mostly as usual, but
   if any root is upgraded due to transitive dependencies, retain the
   original roots and the paths leading from those roots to the
   upgrades. (This forms an “extended graph”, in which we can trace a
   path from to each version that appears in the graph starting at one
   or more of the original roots.)

3. Identify which roots caused any module path to be upgraded above
   its passed-in version constraint. For each such root, either report
   an unresolvable conflict (if the root itself is constrained to a
   specific version) or identify an updated version to propose: either
   a downgrade to the next-highest version, or an upgrade to the
   actually-selected version of the root (if that version is allowed).
   To avoid looping forever or devolving into an NP-complete search,
   we never propose a version that was already rejected previously,
   regardless of what other roots were present alongside it at the
   time.

4. If the version of any root was changed, repeat from (1).

This algorithm is guaranteed to terminate, because there are finitely
many root versions and we permanently reject at least one each time we
downgrade its path to a lower version.

In addition, this change implements support for the '-v' flag to log
more information about version changes at each iteration.

Fixes #56494.
Fixes #55955.

Change-Id: Iebc17dd7586594d5732e228043c3c4c6da230f44
Reviewed-on: https://go-review.googlesource.com/c/go/+/471595
TryBot-Result: Gopher Robot <gobot@golang.org>
Auto-Submit: Bryan Mills <bcmills@google.com>
Run-TryBot: Bryan Mills <bcmills@google.com>
Reviewed-by: Michael Matloob <matloob@golang.org>
This commit is contained in:
Bryan C. Mills 2022-11-18 17:35:45 -05:00 committed by Gopher Robot
parent 9fd876924a
commit a094a82452
13 changed files with 1108 additions and 545 deletions

View File

@ -36,6 +36,7 @@ import (
"sync" "sync"
"cmd/go/internal/base" "cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/imports" "cmd/go/internal/imports"
"cmd/go/internal/modfetch" "cmd/go/internal/modfetch"
"cmd/go/internal/modload" "cmd/go/internal/modload"
@ -1748,6 +1749,16 @@ func (r *resolver) updateBuildList(ctx context.Context, additions []module.Versi
return false return false
} }
if cfg.BuildV {
// Log complete paths for the conflicts before we summarize them.
for _, c := range constraint.Conflicts {
fmt.Fprintf(os.Stderr, "go: %v\n", c.String())
}
}
// modload.EditBuildList reports constraint errors at
// the module level, but 'go get' operates on packages.
// Rewrite the errors to explain them in terms of packages.
reason := func(m module.Version) string { reason := func(m module.Version) string {
rv, ok := r.resolvedVersion[m.Path] rv, ok := r.resolvedVersion[m.Path]
if !ok { if !ok {
@ -1756,7 +1767,17 @@ func (r *resolver) updateBuildList(ctx context.Context, additions []module.Versi
return rv.reason.ResolvedString(module.Version{Path: m.Path, Version: rv.version}) return rv.reason.ResolvedString(module.Version{Path: m.Path, Version: rv.version})
} }
for _, c := range constraint.Conflicts { for _, c := range constraint.Conflicts {
base.Errorf("go: %v requires %v, not %v", reason(c.Source), c.Dep, reason(c.Constraint)) adverb := ""
if len(c.Path) > 2 {
adverb = "indirectly "
}
firstReason := reason(c.Path[0])
last := c.Path[len(c.Path)-1]
if c.Err != nil {
base.Errorf("go: %v %srequires %v: %v", firstReason, adverb, last, c.UnwrapModuleError())
} else {
base.Errorf("go: %v %srequires %v, not %v", firstReason, adverb, last, reason(c.Constraint))
}
} }
return false return false
} }

View File

@ -145,6 +145,11 @@ func newRequirements(pruning modPruning, rootModules []module.Version, direct ma
return rs return rs
} }
// String returns a string describing the Requirements for debugging.
func (rs *Requirements) String() string {
return fmt.Sprintf("{%v %v}", rs.pruning, rs.rootModules)
}
// initVendor initializes rs.graph from the given list of vendored module // initVendor initializes rs.graph from the given list of vendored module
// dependencies, overriding the graph that would normally be loaded from module // dependencies, overriding the graph that would normally be loaded from module
// requirements. // requirements.
@ -235,7 +240,7 @@ func (rs *Requirements) hasRedundantRoot() bool {
// returns a non-nil error of type *mvs.BuildListError. // returns a non-nil error of type *mvs.BuildListError.
func (rs *Requirements) Graph(ctx context.Context) (*ModuleGraph, error) { func (rs *Requirements) Graph(ctx context.Context) (*ModuleGraph, error) {
rs.graphOnce.Do(func() { rs.graphOnce.Do(func() {
mg, mgErr := readModGraph(ctx, rs.pruning, rs.rootModules) mg, mgErr := readModGraph(ctx, rs.pruning, rs.rootModules, nil)
rs.graph.Store(&cachedGraph{mg, mgErr}) rs.graph.Store(&cachedGraph{mg, mgErr})
}) })
cached := rs.graph.Load() cached := rs.graph.Load()
@ -266,9 +271,12 @@ var readModGraphDebugOnce sync.Once
// readModGraph reads and returns the module dependency graph starting at the // readModGraph reads and returns the module dependency graph starting at the
// given roots. // given roots.
// //
// The requirements of the module versions found in the unprune map are included
// in the graph even if they would normally be pruned out.
//
// Unlike LoadModGraph, readModGraph does not attempt to diagnose or update // Unlike LoadModGraph, readModGraph does not attempt to diagnose or update
// inconsistent roots. // inconsistent roots.
func readModGraph(ctx context.Context, pruning modPruning, roots []module.Version) (*ModuleGraph, error) { func readModGraph(ctx context.Context, pruning modPruning, roots []module.Version, unprune map[module.Version]bool) (*ModuleGraph, error) {
if pruning == pruned { if pruning == pruned {
// Enable diagnostics for lazy module loading // Enable diagnostics for lazy module loading
// (https://golang.org/ref/mod#lazy-loading) only if the module graph is // (https://golang.org/ref/mod#lazy-loading) only if the module graph is
@ -355,13 +363,14 @@ func readModGraph(ctx context.Context, pruning modPruning, roots []module.Versio
// cannot assume that the explicit requirements of m (added by loadOne) // cannot assume that the explicit requirements of m (added by loadOne)
// are sufficient to build the packages it contains. We must load its full // are sufficient to build the packages it contains. We must load its full
// transitive dependency graph to be sure that we see all relevant // transitive dependency graph to be sure that we see all relevant
// dependencies. // dependencies. In addition, we must load the requirements of any module
if pruning != pruned || summary.pruning == unpruned { // that is explicitly marked as unpruned.
nextPruning := summary.pruning nextPruning := summary.pruning
if pruning == unpruned { if pruning == unpruned {
nextPruning = unpruned nextPruning = unpruned
} }
for _, r := range summary.require { for _, r := range summary.require {
if pruning != pruned || summary.pruning == unpruned || unprune[r] {
enqueue(r, nextPruning) enqueue(r, nextPruning)
} }
} }
@ -607,17 +616,90 @@ func (e *ConstraintError) Error() string {
b := new(strings.Builder) b := new(strings.Builder)
b.WriteString("version constraints conflict:") b.WriteString("version constraints conflict:")
for _, c := range e.Conflicts { for _, c := range e.Conflicts {
fmt.Fprintf(b, "\n\t%v requires %v, but %v is requested", c.Source, c.Dep, c.Constraint) fmt.Fprintf(b, "\n\t%s", c.Summary())
} }
return b.String() return b.String()
} }
// A Conflict documents that Source requires Dep, which conflicts with Constraint. // A Conflict is a path of requirements starting at a root or proposed root in
// (That is, Dep has the same module path as Constraint but a higher version.) // the requirement graph, explaining why that root either causes a module passed
// in the mustSelect list to EditBuildList to be unattainable, or introduces an
// unresolvable error in loading the requirement graph.
type Conflict struct { type Conflict struct {
Source module.Version // Path is a path of requirements starting at some module version passed in
Dep module.Version // the mustSelect argument and ending at a module whose requirements make that
// version unacceptable. (Path always has len ≥ 1.)
Path []module.Version
// If Err is nil, Constraint is a module version passed in the mustSelect
// argument that has the same module path as, and a lower version than,
// the last element of the Path slice.
Constraint module.Version Constraint module.Version
// If Constraint is unset, Err is an error encountered when loading the
// requirements of the last element in Path.
Err error
}
// UnwrapModuleError returns c.Err, but unwraps it if it is a module.ModuleError
// with a version and path matching the last entry in the Path slice.
func (c Conflict) UnwrapModuleError() error {
me, ok := c.Err.(*module.ModuleError)
if ok && len(c.Path) > 0 {
last := c.Path[len(c.Path)-1]
if me.Path == last.Path && me.Version == last.Version {
return me.Err
}
}
return c.Err
}
// Summary returns a string that describes only the first and last modules in
// the conflict path.
func (c Conflict) Summary() string {
if len(c.Path) == 0 {
return "(internal error: invalid Conflict struct)"
}
first := c.Path[0]
last := c.Path[len(c.Path)-1]
if len(c.Path) == 1 {
if c.Err != nil {
return fmt.Sprintf("%s: %v", first, c.UnwrapModuleError())
}
return fmt.Sprintf("%s is above %s", first, c.Constraint.Version)
}
adverb := ""
if len(c.Path) > 2 {
adverb = "indirectly "
}
if c.Err != nil {
return fmt.Sprintf("%s %srequires %s: %v", first, adverb, last, c.UnwrapModuleError())
}
return fmt.Sprintf("%s %srequires %s, but %s is requested", first, adverb, last, c.Constraint.Version)
}
// String returns a string that describes the full conflict path.
func (c Conflict) String() string {
if len(c.Path) == 0 {
return "(internal error: invalid Conflict struct)"
}
b := new(strings.Builder)
fmt.Fprintf(b, "%v", c.Path[0])
if len(c.Path) == 1 {
fmt.Fprintf(b, " found")
} else {
for _, r := range c.Path[1:] {
fmt.Fprintf(b, " requires\n\t%v", r)
}
}
if c.Constraint != (module.Version{}) {
fmt.Fprintf(b, ", but %v is requested", c.Constraint.Version)
}
if c.Err != nil {
fmt.Fprintf(b, ": %v", c.UnwrapModuleError())
}
return b.String()
} }
// tidyRoots trims the root dependencies to the minimal requirements needed to // tidyRoots trims the root dependencies to the minimal requirements needed to

File diff suppressed because it is too large Load Diff

View File

@ -130,6 +130,19 @@ const (
workspace // pruned to the union of modules in the workspace workspace // pruned to the union of modules in the workspace
) )
func (p modPruning) String() string {
switch p {
case pruned:
return "pruned"
case unpruned:
return "unpruned"
case workspace:
return "workspace"
default:
return fmt.Sprintf("%T(%d)", p, p)
}
}
func pruningForGoVersion(goVersion string) modPruning { func pruningForGoVersion(goVersion string) modPruning {
if semver.Compare("v"+goVersion, ExplicitIndirectVersionV) < 0 { if semver.Compare("v"+goVersion, ExplicitIndirectVersionV) < 0 {
// The go.mod file does not duplicate relevant information about transitive // The go.mod file does not duplicate relevant information about transitive
@ -661,7 +674,6 @@ func goModSummary(m module.Version) (*modFileSummary, error) {
// its dependencies. // its dependencies.
// //
// rawGoModSummary cannot be used on the Target module. // rawGoModSummary cannot be used on the Target module.
func rawGoModSummary(m module.Version) (*modFileSummary, error) { func rawGoModSummary(m module.Version) (*modFileSummary, error) {
if m.Path == "" && MainModules.Contains(m.Path) { if m.Path == "" && MainModules.Contains(m.Path) {
panic("internal error: rawGoModSummary called on the Target module") panic("internal error: rawGoModSummary called on the Target module")

View File

@ -111,14 +111,12 @@ func versions(ctx context.Context, path string, allowed AllowedFunc) (versions [
// //
// Since the version of a main module is not found in the version list, // Since the version of a main module is not found in the version list,
// it has no previous version. // it has no previous version.
func previousVersion(m module.Version) (module.Version, error) { func previousVersion(ctx context.Context, m module.Version) (module.Version, error) {
// TODO(golang.org/issue/38714): thread tracing context through MVS.
if m.Version == "" && MainModules.Contains(m.Path) { if m.Version == "" && MainModules.Contains(m.Path) {
return module.Version{Path: m.Path, Version: "none"}, nil return module.Version{Path: m.Path, Version: "none"}, nil
} }
list, _, err := versions(context.TODO(), m.Path, CheckAllowed) list, _, err := versions(ctx, m.Path, CheckAllowed)
if err != nil { if err != nil {
if errors.Is(err, os.ErrNotExist) { if errors.Is(err, os.ErrNotExist) {
return module.Version{Path: m.Path, Version: "none"}, nil return module.Version{Path: m.Path, Version: "none"}, nil
@ -133,5 +131,6 @@ func previousVersion(m module.Version) (module.Version, error) {
} }
func (*mvsReqs) Previous(m module.Version) (module.Version, error) { func (*mvsReqs) Previous(m module.Version) (module.Version, error) {
return previousVersion(m) // TODO(golang.org/issue/38714): thread tracing context through MVS.
return previousVersion(context.TODO(), m)
} }

View File

@ -0,0 +1,96 @@
# If 'go get -u' finds an upgrade candidate that isn't viable,
# but some other upgraded module's requirement moves past it
# (for example, to a higher prerelease), then we should accept
# the transitive upgrade instead of trying lower roots.
go get -v -u . example.net/b@v0.1.0
cmp go.mod go.mod.want
-- go.mod --
module example
go 1.17
require (
example.net/a v0.1.0
example.net/b v0.1.0
example.net/c v0.1.0
)
replace (
example.net/a v0.1.0 => ./a1
example.net/a v0.2.0-pre => ./a2p
example.net/b v0.1.0 => ./b
example.net/b v0.2.0 => ./b
example.net/c v0.1.0 => ./c1
example.net/c v0.2.0 => ./c2
)
-- go.mod.want --
module example
go 1.17
require (
example.net/a v0.2.0-pre
example.net/b v0.1.0
example.net/c v0.2.0
)
replace (
example.net/a v0.1.0 => ./a1
example.net/a v0.2.0-pre => ./a2p
example.net/b v0.1.0 => ./b
example.net/b v0.2.0 => ./b
example.net/c v0.1.0 => ./c1
example.net/c v0.2.0 => ./c2
)
-- example.go --
package example
import (
_ "example.net/a"
_ "example.net/b"
_ "example.net/c"
)
-- a1/go.mod --
module example.net/a
go 1.17
require example.net/b v0.2.0
-- a1/a.go --
package a
import _ "example.net/b"
-- a2p/go.mod --
module example.net/a
go 1.17
-- a2p/a.go --
package a
-- b/go.mod --
module example.net/b
go 1.17
-- b/b.go --
package b
-- c1/go.mod --
module example.net/c
go 1.17
require example.net/a v0.1.0
-- c1/c.go --
package c
import _ "example.net/a"
-- c2/go.mod --
module example.net/c
go 1.17
require example.net/a v0.2.0-pre
-- c2/c.go --
package c
import _ "example.net/c"

View File

@ -19,17 +19,31 @@
# #
# If we downgrade module d to version 1, we must downgrade b as well. # If we downgrade module d to version 1, we must downgrade b as well.
# If that downgrade selects b version 1, we will upgrade module c to version 2. # If that downgrade selects b version 1, we will upgrade module c to version 2.
# So 'go get d@1' should instead downgrade both b and c to "none".
cp go.mod go.mod.orig cp go.mod go.mod.orig
go mod tidy go mod tidy
cmp go.mod.orig go.mod cmp go.mod.orig go.mod
# Downgrading d to version 1 downgrades b, which upgrades c.
go get example.com/d@v0.1.0 go get example.com/d@v0.1.0
go list -m all go list -m all
! stdout '^example.com/b ' stdout '^example.com/b v0.1.0 '
! stdout '^example.com/c ' stdout '^example.com/c v0.2.0 '
stdout '^example.com/d v0.1.0 ' stdout '^example.com/d v0.1.0 '
cmp go.mod go.mod.down1
# Restoring c to version 1 upgrades d to meet c's requirements.
go get example.com/c@v0.1.0
go list -m all
! stdout '^example.com/b '
stdout '^example.com/c v0.1.0 '
stdout '^example.com/d v0.2.0 '
cmp go.mod go.mod.down2
# If a user explicitly requests the incompatible versions together,
# 'go get' should explain why they are not compatible.
! go get example.com/c@v0.1.0 example.com/d@v0.1.0
stderr '^go: example\.com/c@v0\.1\.0 requires example\.com/d@v0\.2\.0, not example\.com/d@v0\.1\.0'
-- go.mod -- -- go.mod --
module example.com/a module example.com/a
@ -41,6 +55,40 @@ require (
example.com/c v0.1.0 example.com/c v0.1.0
) )
replace (
example.com/b v0.1.0 => ./b1
example.com/b v0.2.0 => ./b2
example.com/c v0.1.0 => ./c1
example.com/c v0.2.0 => ./c2
example.com/d v0.1.0 => ./d
example.com/d v0.2.0 => ./d
)
-- go.mod.down1 --
module example.com/a
go 1.15
require (
example.com/b v0.1.0
example.com/c v0.2.0
example.com/d v0.1.0 // indirect
)
replace (
example.com/b v0.1.0 => ./b1
example.com/b v0.2.0 => ./b2
example.com/c v0.1.0 => ./c1
example.com/c v0.2.0 => ./c2
example.com/d v0.1.0 => ./d
example.com/d v0.2.0 => ./d
)
-- go.mod.down2 --
module example.com/a
go 1.15
require example.com/c v0.1.0
replace ( replace (
example.com/b v0.1.0 => ./b1 example.com/b v0.1.0 => ./b1
example.com/b v0.2.0 => ./b2 example.com/b v0.2.0 => ./b2

View File

@ -0,0 +1,154 @@
# This test illustrates a case where downgrading one module may upgrade another.
# This is the same as mod_get_downup_indirect, but using modules
# with graph pruning enabled (go ≥ 1.17).
# Compare to the downcross1 test case in cmd/go/internal/mvs/mvs_test.go.
# The package import graph used in this test looks like:
#
# a ---- b
# \ \
# \ \
# ----- c ---- d
#
# The module dependency graph originally looks like:
#
# a ---- b.2
# \ \
# \ \
# ----- c.1 ---- d.2
#
# b.1 ---- c.2
#
# If we downgrade module d to version 1, we must downgrade b as well.
# If that downgrade selects b version 1, we will upgrade module c to version 2.
cp go.mod go.mod.orig
go mod tidy
cmp go.mod.orig go.mod
# Downgrading d to version 1 downgrades b, which upgrades c.
go get -v example.com/d@v0.1.0
go list -m all
stdout '^example.com/b v0.1.0 '
stdout '^example.com/c v0.2.0 '
stdout '^example.com/d v0.1.0 '
cmp go.mod go.mod.down1
# Restoring c to version 1 upgrades d to meet c's requirements.
go get example.com/c@v0.1.0
go list -m all
! stdout '^example.com/b '
stdout '^example.com/c v0.1.0 '
stdout '^example.com/d v0.2.0 '
cmp go.mod go.mod.down2
# If a user explicitly requests the incompatible versions together,
# 'go get' should explain why they are not compatible.
! go get example.com/c@v0.1.0 example.com/d@v0.1.0
stderr '^go: example\.com/c@v0\.1\.0 requires example\.com/d@v0\.2\.0, not example\.com/d@v0\.1\.0'
-- go.mod --
module example.com/a
go 1.17
require (
example.com/b v0.2.0
example.com/c v0.1.0
)
replace (
example.com/b v0.1.0 => ./b1
example.com/b v0.2.0 => ./b2
example.com/c v0.1.0 => ./c1
example.com/c v0.2.0 => ./c2
example.com/d v0.1.0 => ./d
example.com/d v0.2.0 => ./d
)
-- go.mod.down1 --
module example.com/a
go 1.17
require (
example.com/b v0.1.0
example.com/c v0.2.0
)
require example.com/d v0.1.0 // indirect
replace (
example.com/b v0.1.0 => ./b1
example.com/b v0.2.0 => ./b2
example.com/c v0.1.0 => ./c1
example.com/c v0.2.0 => ./c2
example.com/d v0.1.0 => ./d
example.com/d v0.2.0 => ./d
)
-- go.mod.down2 --
module example.com/a
go 1.17
require example.com/c v0.1.0
require example.com/d v0.2.0 // indirect
replace (
example.com/b v0.1.0 => ./b1
example.com/b v0.2.0 => ./b2
example.com/c v0.1.0 => ./c1
example.com/c v0.2.0 => ./c2
example.com/d v0.1.0 => ./d
example.com/d v0.2.0 => ./d
)
-- a.go --
package a
import (
_ "example.com/b"
_ "example.com/c"
)
-- b1/go.mod --
module example.com/b
go 1.17
require example.com/c v0.2.0
-- b1/b.go --
package b
import _ "example.com/c"
-- b2/go.mod --
module example.com/b
go 1.17
require example.com/c v0.1.0
-- b2/b.go --
package b
import _ "example.com/c"
-- c1/go.mod --
module example.com/c
go 1.17
require example.com/d v0.2.0
-- c1/c.go --
package c
-- c2/go.mod --
module example.com/c
go 1.17
-- c2/c.go --
package c
-- d/go.mod --
module example.com/d
go 1.17

View File

@ -35,9 +35,7 @@
# so 'go get' should prune it out too, and c should remain at c1 # so 'go get' should prune it out too, and c should remain at c1
# without error. # without error.
# TODO(#56494): This should succeed, not error out. go get a@v0.3.0
! go get a@v0.3.0
stderr 'INTERNAL ERROR'
go list -m c go list -m c
stdout '^c v0.1.0 ' stdout '^c v0.1.0 '

View File

@ -11,4 +11,4 @@ go mod init m
cmp stderr stderr-expected cmp stderr stderr-expected
-- stderr-expected -- -- stderr-expected --
go: example.com/newcycle/a@v1.0.0 requires example.com/newcycle/a@v1.0.1, not example.com/newcycle/a@v1.0.0 go: example.com/newcycle/a@v1.0.0 indirectly requires example.com/newcycle/a@v1.0.1, not example.com/newcycle/a@v1.0.0

View File

@ -6,7 +6,7 @@
# (It used to print v0.1.1 but then silently upgrade to v0.2.0.) # (It used to print v0.1.1 but then silently upgrade to v0.2.0.)
! go get example.net/a@patch ! go get example.net/a@patch
stderr '^go: example.net/a@patch \(v0.1.1\) requires example.net/a@v0.2.0, not example.net/a@patch \(v0.1.1\)$' # TODO: A mention of b v0.1.0 would be nice. stderr '^go: example.net/a@patch \(v0.1.1\) indirectly requires example.net/a@v0.2.0, not example.net/a@patch \(v0.1.1\)$' # TODO: A mention of b v0.1.0 would be nice.
-- go.mod -- -- go.mod --
module example module example

View File

@ -160,7 +160,7 @@ cmp stderr exclude-err
# 'go install pkg@version' should report an error if the module requires a # 'go install pkg@version' should report an error if the module requires a
# higher version of itself. # higher version of itself.
! go install example.com/cmd/a@v1.0.0-newerself ! go install example.com/cmd/a@v1.0.0-newerself
stderr '^go: example.com/cmd/a@v1.0.0-newerself: version constraints conflict:\n\texample.com/cmd@v1.0.0-newerself requires example.com/cmd@v1.0.0, but example.com/cmd@v1.0.0-newerself is requested$' stderr '^go: example.com/cmd/a@v1.0.0-newerself: version constraints conflict:\n\texample.com/cmd@v1.0.0-newerself requires example.com/cmd@v1.0.0, but v1.0.0-newerself is requested$'
# 'go install pkg@version' will only match a retracted version if it's # 'go install pkg@version' will only match a retracted version if it's

View File

@ -16,11 +16,12 @@ cmp go.mod go.mod.orig
# Try to update the main module. This updates everything, including # Try to update the main module. This updates everything, including
# modules that aren't direct requirements, so the error stack is shorter. # modules that aren't direct requirements, so the error stack is shorter.
! go get -u ./... go get -u ./...
cmp stderr update-main-expected cmp stderr update-main-expected
cmp go.mod go.mod.orig cmp go.mod go.mod.withc
# Update manually. Listing modules should produce an error. # Update manually. Listing modules should produce an error.
cp go.mod.orig go.mod
go mod edit -require=example.com/badchain/a@v1.1.0 go mod edit -require=example.com/badchain/a@v1.1.0
! go list -m all ! go list -m all
cmp stderr list-expected cmp stderr list-expected
@ -40,6 +41,15 @@ module m
go 1.13 go 1.13
require example.com/badchain/a v1.0.0 require example.com/badchain/a v1.0.0
-- go.mod.withc --
module m
go 1.13
require (
example.com/badchain/a v1.0.0
example.com/badchain/c v1.0.0
)
-- go.sum -- -- go.sum --
example.com/badchain/a v1.0.0 h1:iJDLiHLmpQgr9Zrv+44UqywAE2IG6WkHnH4uG08vf+s= example.com/badchain/a v1.0.0 h1:iJDLiHLmpQgr9Zrv+44UqywAE2IG6WkHnH4uG08vf+s=
example.com/badchain/a v1.0.0/go.mod h1:6/gnCYHdVrs6mUgatUYUSbuHxEY+/yWedmTggLz23EI= example.com/badchain/a v1.0.0/go.mod h1:6/gnCYHdVrs6mUgatUYUSbuHxEY+/yWedmTggLz23EI=
@ -72,10 +82,9 @@ func Test(t *testing.T) {}
go: example.com/badchain/c@v1.1.0: parsing go.mod: go: example.com/badchain/c@v1.1.0: parsing go.mod:
module declares its path as: badchain.example.com/c module declares its path as: badchain.example.com/c
but was required as: example.com/badchain/c but was required as: example.com/badchain/c
restoring example.com/badchain/c@v1.0.0
-- update-a-expected -- -- update-a-expected --
go: example.com/badchain/a@v1.1.0 requires go: example.com/badchain/a@upgrade (v1.1.0) indirectly requires example.com/badchain/c@v1.1.0: parsing go.mod:
example.com/badchain/b@v1.1.0 requires
example.com/badchain/c@v1.1.0: parsing go.mod:
module declares its path as: badchain.example.com/c module declares its path as: badchain.example.com/c
but was required as: example.com/badchain/c but was required as: example.com/badchain/c
-- list-expected -- -- list-expected --