Skip to content

Commit eab8208

Browse files
committed
cmd/go: detect inconsistent 'go get' version requests
If x v1.0.0 requires y v1.2.0, then go get x@v1.0.0 y@v1.0.0 needs to fail gracefully. Fixes #25917. Change-Id: I9b426af23a30310fcb0c3545a8d97feb58b8ddbe Reviewed-on: https://go-review.googlesource.com/124800 Reviewed-by: Bryan C. Mills <bcmills@google.com>
1 parent a59f443 commit eab8208

File tree

3 files changed

+96
-2
lines changed

3 files changed

+96
-2
lines changed

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

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,9 +206,15 @@ func runGet(cmd *base.Command, args []string) {
206206

207207
modload.LoadBuildList()
208208

209+
// Do not allow any updating of go.mod until we've applied
210+
// all the requested changes and checked that the result matches
211+
// what was requested.
212+
modload.DisallowWriteGoMod()
213+
209214
// A task holds the state for processing a single get argument (path@vers).
210215
type task struct {
211-
arg string // original argument
216+
arg string // original argument
217+
index int
212218
path string // package path part of arg
213219
forceModulePath bool // path must be interpreted as a module path
214220
vers string // version part of arg
@@ -429,6 +435,7 @@ func runGet(cmd *base.Command, args []string) {
429435
base.Fatalf("go get: %v", err)
430436
}
431437
required = upgraded[1:] // slice off upgradeTarget
438+
base.ExitIfErrors()
432439
}
433440

434441
// Put together the final build list as described above (1) (2) (3).
@@ -441,8 +448,9 @@ func runGet(cmd *base.Command, args []string) {
441448
list = append(list, required...)
442449
modload.SetBuildList(list)
443450
modload.ReloadBuildList() // note: does not update go.mod
451+
base.ExitIfErrors()
444452

445-
// Apply any needed downgrades.
453+
// Scan for and apply any needed downgrades.
446454
var down []module.Version
447455
for _, m := range modload.BuildList() {
448456
t := byPath[m.Path]
@@ -458,8 +466,64 @@ func runGet(cmd *base.Command, args []string) {
458466
modload.SetBuildList(list)
459467
modload.ReloadBuildList() // note: does not update go.mod
460468
}
469+
base.ExitIfErrors()
470+
471+
// Scan for any upgrades lost by the downgrades.
472+
lost := make(map[string]string)
473+
for _, m := range modload.BuildList() {
474+
t := byPath[m.Path]
475+
if t != nil && semver.Compare(m.Version, t.m.Version) != 0 {
476+
lost[m.Path] = m.Version
477+
}
478+
}
479+
if len(lost) > 0 {
480+
desc := func(m module.Version) string {
481+
s := m.Path + "@" + m.Version
482+
t := byPath[m.Path]
483+
if t != nil && t.arg != s {
484+
s += " from " + t.arg
485+
}
486+
return s
487+
}
488+
downByPath := make(map[string]module.Version)
489+
for _, d := range down {
490+
downByPath[d.Path] = d
491+
}
492+
var buf strings.Builder
493+
fmt.Fprintf(&buf, "go get: inconsistent versions:")
494+
for _, t := range tasks {
495+
if lost[t.m.Path] == "" {
496+
continue
497+
}
498+
// We lost t because its build list requires a newer version of something in down.
499+
// Figure out exactly what.
500+
// Repeatedly constructing the build list is inefficient
501+
// if there are MANY command-line arguments,
502+
// but at least all the necessary requirement lists are cached at this point.
503+
list, err := mvs.BuildList(t.m, reqs)
504+
if err != nil {
505+
base.Fatalf("go get: %v", err)
506+
}
507+
508+
fmt.Fprintf(&buf, "\n\t%s", desc(t.m))
509+
sep := " requires"
510+
for _, m := range list {
511+
if down, ok := downByPath[m.Path]; ok && semver.Compare(down.Version, m.Version) < 0 {
512+
fmt.Fprintf(&buf, "%s %s@%s (not %s)", sep, m.Path, m.Version, desc(down))
513+
sep = ","
514+
}
515+
}
516+
if sep != "," {
517+
// We have no idea why this happened.
518+
// At least report the problem.
519+
fmt.Fprintf(&buf, " ended up at %v unexpectedly (please report at golang.org/issue/new)", lost[t.m.Path])
520+
}
521+
}
522+
base.Fatalf("%v", buf.String())
523+
}
461524

462525
// Everything succeeded. Update go.mod.
526+
modload.AllowWriteGoMod()
463527
modload.WriteGoMod()
464528

465529
// If -m was specified, we're done after the module work. No download, no build.

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,8 +465,27 @@ func findImportComment(file string) string {
465465
return path
466466
}
467467

468+
var allowWriteGoMod = true
469+
470+
// DisallowWriteGoMod causes future calls to WriteGoMod to do nothing at all.
471+
func DisallowWriteGoMod() {
472+
allowWriteGoMod = false
473+
}
474+
475+
// AllowWriteGoMod undoes the effect of DisallowWriteGoMod:
476+
// future calls to WriteGoMod will update go.mod if needed.
477+
// Note that any past calls have been discarded, so typically
478+
// a call to AlowWriteGoMod should be followed by a call to WriteGoMod.
479+
func AllowWriteGoMod() {
480+
allowWriteGoMod = true
481+
}
482+
468483
// WriteGoMod writes the current build list back to go.mod.
469484
func WriteGoMod() {
485+
if !allowWriteGoMod {
486+
return
487+
}
488+
470489
modfetch.WriteGoSum()
471490

472491
if loaded != nil {

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,17 @@ go get rsc.io/sampler@none
1111
go list -m all
1212
stdout 'rsc.io/quote v1.3.0'
1313

14+
# downgrade should report inconsistencies and not change go.mod
15+
go get rsc.io/quote@v1.5.1
16+
go list -m all
17+
stdout 'rsc.io/quote v1.5.1'
18+
stdout 'rsc.io/sampler v1.3.0'
19+
! go get rsc.io/sampler@v1.0.0 rsc.io/quote@v1.5.2 golang.org/x/text@none
20+
stderr 'go get: inconsistent versions:\n\trsc.io/quote@v1.5.2 requires golang.org/x/text@v0.0.0-20170915032832-14c0d48ead0c \(not golang.org/x/text@none\), rsc.io/sampler@v1.3.0 \(not rsc.io/sampler@v1.0.0\)'
21+
go list -m all
22+
stdout 'rsc.io/quote v1.5.1'
23+
stdout 'rsc.io/sampler v1.3.0'
24+
1425
-- go.mod --
1526
module x
1627
require rsc.io/quote v1.5.1

0 commit comments

Comments
 (0)