mirror of
https://github.com/golang/go
synced 2024-11-26 20:31:25 -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:
parent
9fd876924a
commit
a094a82452
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
@ -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")
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
96
src/cmd/go/testdata/script/mod_get_boost.txt
vendored
Normal file
96
src/cmd/go/testdata/script/mod_get_boost.txt
vendored
Normal 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"
|
@ -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
|
||||||
|
154
src/cmd/go/testdata/script/mod_get_downup_indirect_pruned.txt
vendored
Normal file
154
src/cmd/go/testdata/script/mod_get_downup_indirect_pruned.txt
vendored
Normal 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
|
@ -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 '
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
19
src/cmd/go/testdata/script/mod_load_badchain.txt
vendored
19
src/cmd/go/testdata/script/mod_load_badchain.txt
vendored
@ -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 --
|
||||||
|
Loading…
Reference in New Issue
Block a user