Skip to content

Commit 97ecc43

Browse files
author
Jay Conrod
committed
cmd/go: don't panic when explaining lost upgrades due to downgrades
If a user runs 'go get mod@vers' where the module transitively requires itself at a newer version, 'go get' attempts to perform a downgrade, which necessarily excludes the requested version of the module. Previously, we called mvs.BuildList with the requested module version as the target. This panicked because BuildList doesn't allow the target module (typically the main module) to require a newer version of itself. With this change, when we lose an upgrade due to a downgrade, we call mvs.BuildList through a wrapper that treats the lost module version as requirement of a synthetic root module, rather than the target module. This avoids the panic. This change also starts reporting errors when an upgraded module is lost entirely (downgrades caused the module to be completely removed from the build list). Fixes #31491 Change-Id: I70ca261c20af7553cad2d3b840a1eaf3d18a4191 Reviewed-on: https://go-review.googlesource.com/c/go/+/177602 Run-TryBot: Jay Conrod <[email protected]> Reviewed-by: Bryan C. Mills <[email protected]>
1 parent 4e7bef8 commit 97ecc43

File tree

6 files changed

+99
-15
lines changed

6 files changed

+99
-15
lines changed

src/cmd/go/internal/modget/get.go

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"fmt"
2424
"os"
2525
"path/filepath"
26+
"sort"
2627
"strings"
2728
"sync"
2829
)
@@ -570,14 +571,23 @@ func runGet(cmd *base.Command, args []string) {
570571
}
571572

572573
// Scan for any upgrades lost by the downgrades.
573-
lost := make(map[string]string)
574-
for _, m := range modload.BuildList() {
575-
t := byPath[m.Path]
576-
if t != nil && semver.Compare(m.Version, t.m.Version) != 0 {
577-
lost[m.Path] = m.Version
574+
var lostUpgrades []*query
575+
var versionByPath map[string]string
576+
if len(down) > 0 {
577+
versionByPath = make(map[string]string)
578+
for _, m := range modload.BuildList() {
579+
versionByPath[m.Path] = m.Version
578580
}
581+
for _, q := range byPath {
582+
if v, ok := versionByPath[q.m.Path]; q.m.Version != "none" && (!ok || semver.Compare(v, q.m.Version) != 0) {
583+
lostUpgrades = append(lostUpgrades, q)
584+
}
585+
}
586+
sort.Slice(lostUpgrades, func(i, j int) bool {
587+
return lostUpgrades[i].m.Path < lostUpgrades[j].m.Path
588+
})
579589
}
580-
if len(lost) > 0 {
590+
if len(lostUpgrades) > 0 {
581591
desc := func(m module.Version) string {
582592
s := m.Path + "@" + m.Version
583593
t := byPath[m.Path]
@@ -590,19 +600,17 @@ func runGet(cmd *base.Command, args []string) {
590600
for _, d := range down {
591601
downByPath[d.Path] = d
592602
}
603+
593604
var buf strings.Builder
594605
fmt.Fprintf(&buf, "go get: inconsistent versions:")
595606
reqs := modload.Reqs()
596-
for _, q := range queries {
597-
if lost[q.m.Path] == "" {
598-
continue
599-
}
607+
for _, q := range lostUpgrades {
600608
// We lost q because its build list requires a newer version of something in down.
601609
// Figure out exactly what.
602610
// Repeatedly constructing the build list is inefficient
603611
// if there are MANY command-line arguments,
604612
// but at least all the necessary requirement lists are cached at this point.
605-
list, err := mvs.BuildList(q.m, reqs)
613+
list, err := buildListForLostUpgrade(q.m, reqs)
606614
if err != nil {
607615
base.Fatalf("go: %v", err)
608616
}
@@ -618,7 +626,12 @@ func runGet(cmd *base.Command, args []string) {
618626
if sep != "," {
619627
// We have no idea why this happened.
620628
// At least report the problem.
621-
fmt.Fprintf(&buf, " ended up at %v unexpectedly (please report at golang.org/issue/new)", lost[q.m.Path])
629+
if v := versionByPath[q.m.Path]; v == "" {
630+
fmt.Fprintf(&buf, " removed unexpectedly")
631+
} else {
632+
fmt.Fprintf(&buf, " ended up at %s unexpectedly", v)
633+
}
634+
fmt.Fprintf(&buf, " (please report at golang.org/issue/new)")
622635
}
623636
}
624637
base.Fatalf("%v", buf.String())
@@ -894,3 +907,29 @@ func (u *upgrader) Upgrade(m module.Version) (module.Version, error) {
894907

895908
return module.Version{Path: m.Path, Version: info.Version}, nil
896909
}
910+
911+
// buildListForLostUpgrade returns the build list for the module graph
912+
// rooted at lost. Unlike mvs.BuildList, the target module (lost) is not
913+
// treated specially. The returned build list may contain a newer version
914+
// of lost.
915+
//
916+
// buildListForLostUpgrade is used after a downgrade has removed a module
917+
// requested at a specific version. This helps us understand the requirements
918+
// implied by each downgrade.
919+
func buildListForLostUpgrade(lost module.Version, reqs mvs.Reqs) ([]module.Version, error) {
920+
return mvs.BuildList(lostUpgradeRoot, &lostUpgradeReqs{Reqs: reqs, lost: lost})
921+
}
922+
923+
var lostUpgradeRoot = module.Version{Path: "lost-upgrade-root", Version: ""}
924+
925+
type lostUpgradeReqs struct {
926+
mvs.Reqs
927+
lost module.Version
928+
}
929+
930+
func (r *lostUpgradeReqs) Required(mod module.Version) ([]module.Version, error) {
931+
if mod == lostUpgradeRoot {
932+
return []module.Version{r.lost}, nil
933+
}
934+
return r.Reqs.Required(mod)
935+
}

src/cmd/go/internal/mvs/mvs.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,6 @@ func BuildList(target module.Version, reqs Reqs) ([]module.Version, error) {
121121
func buildList(target module.Version, reqs Reqs, upgrade func(module.Version) module.Version) ([]module.Version, error) {
122122
// Explore work graph in parallel in case reqs.Required
123123
// does high-latency network operations.
124-
var work par.Work
125-
work.Add(target)
126-
127124
type modGraphNode struct {
128125
m module.Version
129126
required []module.Version
@@ -137,6 +134,7 @@ func buildList(target module.Version, reqs Reqs, upgrade func(module.Version) mo
137134
haveErr int32
138135
)
139136

137+
var work par.Work
140138
work.Add(target)
141139
work.Do(10, func(item interface{}) {
142140
m := item.(module.Version)
@@ -217,6 +215,11 @@ func buildList(target module.Version, reqs Reqs, upgrade func(module.Version) mo
217215
// Construct the list by traversing the graph again, replacing older
218216
// modules with required minimum versions.
219217
if v := min[target.Path]; v != target.Version {
218+
// TODO(jayconrod): there is a special case in modload.mvsReqs.Max
219+
// that prevents us from selecting a newer version of a module
220+
// when the module has no version. This may only be the case for target.
221+
// Should we always panic when target has a version?
222+
// See golang.org/issue/31491, golang.org/issue/29773.
220223
panic(fmt.Sprintf("mistake: chose version %q instead of target %+v", v, target)) // TODO: Don't panic.
221224
}
222225

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
example.com/newcycle/a v1.0.0
2+
3+
Transitively requires v1.0.1 of itself via example.com/newcycle/b
4+
5+
-- .mod --
6+
module example.com/newcycle/a
7+
8+
require example.com/newcycle/b v1.0.0
9+
-- .info --
10+
{"Version":"v1.0.0"}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
example.com/newcycle/a v1.0.1
2+
3+
Transitively requires itself via example.com/newcycle/b
4+
5+
-- .mod --
6+
module example.com/newcycle/a
7+
8+
require example.com/newcycle/b v1.0.0
9+
-- .info --
10+
{"Version":"v1.0.1"}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
example.com/newcycle/b v1.0.0
2+
3+
-- .mod --
4+
module example.com/newcycle/b
5+
6+
require example.com/newcycle/a v1.0.1
7+
-- .info --
8+
{"Version":"v1.0.0"}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
env GO111MODULE=on
2+
3+
# Download modules to avoid stderr chatter
4+
go mod download example.com/newcycle/[email protected]
5+
go mod download example.com/newcycle/[email protected]
6+
go mod download example.com/newcycle/[email protected]
7+
8+
go mod init m
9+
! go get example.com/newcycle/[email protected]
10+
cmp stderr stderr-expected
11+
12+
-- stderr-expected --
13+
go get: inconsistent versions:
14+
example.com/newcycle/[email protected] requires example.com/newcycle/[email protected] (not example.com/newcycle/[email protected])

0 commit comments

Comments
 (0)