Skip to content

Commit f1dce31

Browse files
author
Jay Conrod
committed
cmd/go: with -mod=vendor, don't panic if there are duplicate requirements
In loadModFile with -mod=vendor, load the vendor list and use it to initialize the module graph before calling updateRoots. In updateLazyRoots with any mode other than "mod", return the original *Requirements if no roots needed to be upgraded, even if there are inconsistencies. This means 'go list -m -mod=readonly' and -mod=vendor may succeed if there are duplicate requirements or requirements on versions of the main module. Fixes #47565 Change-Id: I4640dffc4a7359367cc910da0d29e3538bfd1ca4 Reviewed-on: https://go-review.googlesource.com/c/go/+/340252 Trust: Jay Conrod <[email protected]> Trust: Bryan C. Mills <[email protected]> Reviewed-by: Bryan C. Mills <[email protected]>
1 parent 7aeaad5 commit f1dce31

File tree

4 files changed

+72
-32
lines changed

4 files changed

+72
-32
lines changed

src/cmd/go/internal/modload/buildlist.go

+19
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,19 @@ func (rs *Requirements) rootSelected(path string) (version string, ok bool) {
191191
return "", false
192192
}
193193

194+
// hasRedundantRoot returns true if the root list contains multiple requirements
195+
// of the same module or a requirement on any version of the main module.
196+
// Redundant requirements should be pruned, but they may influence version
197+
// selection.
198+
func (rs *Requirements) hasRedundantRoot() bool {
199+
for i, m := range rs.rootModules {
200+
if m.Path == Target.Path || (i > 0 && m.Path == rs.rootModules[i-1].Path) {
201+
return true
202+
}
203+
}
204+
return false
205+
}
206+
194207
// Graph returns the graph of module requirements loaded from the current
195208
// root modules (as reported by RootModules).
196209
//
@@ -882,6 +895,12 @@ func updateLazyRoots(ctx context.Context, direct map[string]bool, rs *Requiremen
882895
// and (trivially) version.
883896

884897
if !rootsUpgraded {
898+
if cfg.BuildMod != "mod" {
899+
// The only changes to the root set (if any) were to remove duplicates.
900+
// The requirements are consistent (if perhaps redundant), so keep the
901+
// original rs to preserve its ModuleGraph.
902+
return rs, nil
903+
}
885904
// The root set has converged: every root going into this iteration was
886905
// already at its selected version, although we have have removed other
887906
// (redundant) roots for the same path.

src/cmd/go/internal/modload/init.go

+18-21
Original file line numberDiff line numberDiff line change
@@ -449,13 +449,22 @@ func loadModFile(ctx context.Context) (rs *Requirements, needCommit bool) {
449449
}
450450

451451
setDefaultBuildMod() // possibly enable automatic vendoring
452-
rs = requirementsFromModFile(ctx)
453-
452+
rs = requirementsFromModFile()
454453
if cfg.BuildMod == "vendor" {
455454
readVendorList()
456455
checkVendorConsistency()
457456
rs.initVendor(vendorList)
458457
}
458+
if rs.hasRedundantRoot() {
459+
// If any module path appears more than once in the roots, we know that the
460+
// go.mod file needs to be updated even though we have not yet loaded any
461+
// transitive dependencies.
462+
rs, err = updateRoots(ctx, rs.direct, rs, nil, nil, false)
463+
if err != nil {
464+
base.Fatalf("go: %v", err)
465+
}
466+
}
467+
459468
if index.goVersionV == "" {
460469
// TODO(#45551): Do something more principled instead of checking
461470
// cfg.CmdName directly here.
@@ -530,7 +539,12 @@ func CreateModFile(ctx context.Context, modPath string) {
530539
base.Fatalf("go: %v", err)
531540
}
532541

533-
commitRequirements(ctx, modFileGoVersion(), requirementsFromModFile(ctx))
542+
rs := requirementsFromModFile()
543+
rs, err = updateRoots(ctx, rs.direct, rs, nil, nil, false)
544+
if err != nil {
545+
base.Fatalf("go: %v", err)
546+
}
547+
commitRequirements(ctx, modFileGoVersion(), rs)
534548

535549
// Suggest running 'go mod tidy' unless the project is empty. Even if we
536550
// imported all the correct requirements above, we're probably missing
@@ -641,9 +655,8 @@ func initTarget(m module.Version) {
641655

642656
// requirementsFromModFile returns the set of non-excluded requirements from
643657
// the global modFile.
644-
func requirementsFromModFile(ctx context.Context) *Requirements {
658+
func requirementsFromModFile() *Requirements {
645659
roots := make([]module.Version, 0, len(modFile.Require))
646-
mPathCount := map[string]int{Target.Path: 1}
647660
direct := map[string]bool{}
648661
for _, r := range modFile.Require {
649662
if index != nil && index.exclude[r.Mod] {
@@ -656,28 +669,12 @@ func requirementsFromModFile(ctx context.Context) *Requirements {
656669
}
657670

658671
roots = append(roots, r.Mod)
659-
mPathCount[r.Mod.Path]++
660672
if !r.Indirect {
661673
direct[r.Mod.Path] = true
662674
}
663675
}
664676
module.Sort(roots)
665677
rs := newRequirements(modDepthFromGoVersion(modFileGoVersion()), roots, direct)
666-
667-
// If any module path appears more than once in the roots, we know that the
668-
// go.mod file needs to be updated even though we have not yet loaded any
669-
// transitive dependencies.
670-
for _, n := range mPathCount {
671-
if n > 1 {
672-
var err error
673-
rs, err = updateRoots(ctx, rs.direct, rs, nil, nil, false)
674-
if err != nil {
675-
base.Fatalf("go: %v", err)
676-
}
677-
break
678-
}
679-
}
680-
681678
return rs
682679
}
683680

src/cmd/go/testdata/script/mod_tidy_lazy_self.txt

+6-11
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,13 @@
22
# 'go mod tidy' should not panic if the main module initially
33
# requires an older version of itself.
44

5+
# A module may require an older version of itself without error. This is
6+
# inconsistent (the required version is never selected), but we still get
7+
# a reproducible build list.
8+
go list -m all
9+
stdout '^golang.org/issue/46078$'
510

6-
# A module that explicitly requires an older version of itself should be
7-
# rejected as inconsistent: we enforce that every explicit requirement is the
8-
# selected version of its module path, but the selected version of the main
9-
# module is always itself — not some explicit version.
10-
11-
! go list -m all
12-
stderr '^go: updates to go\.mod needed; to update it:\n\tgo mod tidy$'
13-
14-
15-
# The suggested 'go mod tidy' command should succeed (not crash).
16-
11+
# 'go mod tidy' should fix this (and not crash).
1712
go mod tidy
1813

1914

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# 'go list -mod=vendor' should succeed even when go.mod contains redundant
2+
# requirements. Verifies #47565.
3+
go list -mod=vendor
4+
5+
-- go.mod --
6+
module m
7+
8+
go 1.17
9+
10+
require example.com/m v0.0.0
11+
require example.com/m v0.0.0
12+
13+
replace example.com/m v0.0.0 => ./m
14+
-- m/go.mod --
15+
module example.com/m
16+
17+
go 1.17
18+
-- m/m.go --
19+
package m
20+
-- use.go --
21+
package use
22+
23+
import _ "example.com/m"
24+
-- vendor/example.com/m/m.go --
25+
package m
26+
-- vendor/modules.txt --
27+
# example.com/m v0.0.0 => ./m
28+
## explicit; go 1.17
29+
example.com/m

0 commit comments

Comments
 (0)