Skip to content

Commit 1e3cdd1

Browse files
author
Jay Conrod
committed
cmd/go: print require chains in build list errors
mvs.BuildList and functions that invoke it directly (UpgradeAll) now return an *mvs.BuildListError when there is an error retrieving the requirements for a module. This new error prints the chain of requirements from the main module to the module where the error occurred. These errors come up most commonly when a go.mod file has an unexpected module path or can't be parsed for some other reason. It's currently difficult to debug these errors because it's not clear where the "bad" module is required from. Tools like "go list -m" and "go mod why" don't work without the build graph. Fixes #30661 Change-Id: I3c9d4683dcd9a5d7c259e5e4cc7e1ee209700b10 Reviewed-on: https://go-review.googlesource.com/c/go/+/166984 Run-TryBot: Jay Conrod <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Bryan C. Mills <[email protected]>
1 parent 8c896aa commit 1e3cdd1

10 files changed

+209
-46
lines changed

src/cmd/go/internal/modload/load.go

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,13 +1023,11 @@ func (r *mvsReqs) required(mod module.Version) ([]module.Version, error) {
10231023
gomod := filepath.Join(dir, "go.mod")
10241024
data, err := ioutil.ReadFile(gomod)
10251025
if err != nil {
1026-
base.Errorf("go: parsing %s: %v", base.ShortPath(gomod), err)
1027-
return nil, ErrRequire
1026+
return nil, fmt.Errorf("parsing %s: %v", base.ShortPath(gomod), err)
10281027
}
10291028
f, err := modfile.ParseLax(gomod, data, nil)
10301029
if err != nil {
1031-
base.Errorf("go: parsing %s: %v", base.ShortPath(gomod), err)
1032-
return nil, ErrRequire
1030+
return nil, fmt.Errorf("parsing %s: %v", base.ShortPath(gomod), err)
10331031
}
10341032
if f.Go != nil {
10351033
r.versions.LoadOrStore(mod, f.Go.Version)
@@ -1050,22 +1048,18 @@ func (r *mvsReqs) required(mod module.Version) ([]module.Version, error) {
10501048

10511049
data, err := modfetch.GoMod(mod.Path, mod.Version)
10521050
if err != nil {
1053-
base.Errorf("go: %s@%s: %v\n", mod.Path, mod.Version, err)
1054-
return nil, ErrRequire
1051+
return nil, fmt.Errorf("%s@%s: %v", mod.Path, mod.Version, err)
10551052
}
10561053
f, err := modfile.ParseLax("go.mod", data, nil)
10571054
if err != nil {
1058-
base.Errorf("go: %s@%s: parsing go.mod: %v", mod.Path, mod.Version, err)
1059-
return nil, ErrRequire
1055+
return nil, fmt.Errorf("%s@%s: parsing go.mod: %v", mod.Path, mod.Version, err)
10601056
}
10611057

10621058
if f.Module == nil {
1063-
base.Errorf("go: %s@%s: parsing go.mod: missing module line", mod.Path, mod.Version)
1064-
return nil, ErrRequire
1059+
return nil, fmt.Errorf("%s@%s: parsing go.mod: missing module line", mod.Path, mod.Version)
10651060
}
10661061
if mpath := f.Module.Mod.Path; mpath != origPath && mpath != mod.Path {
1067-
base.Errorf("go: %s@%s: parsing go.mod: unexpected module path %q", mod.Path, mod.Version, mpath)
1068-
return nil, ErrRequire
1062+
return nil, fmt.Errorf("%s@%s: parsing go.mod: unexpected module path %q", mod.Path, mod.Version, mpath)
10691063
}
10701064
if f.Go != nil {
10711065
r.versions.LoadOrStore(mod, f.Go.Version)
@@ -1074,11 +1068,6 @@ func (r *mvsReqs) required(mod module.Version) ([]module.Version, error) {
10741068
return r.modFileToList(f), nil
10751069
}
10761070

1077-
// ErrRequire is the sentinel error returned when Require encounters problems.
1078-
// It prints the problems directly to standard error, so that multiple errors
1079-
// can be displayed easily.
1080-
var ErrRequire = errors.New("error loading module requirements")
1081-
10821071
func (*mvsReqs) Max(v1, v2 string) string {
10831072
if v1 != "" && semver.Compare(v1, v2) == -1 {
10841073
return v2

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

Lines changed: 95 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ package mvs
99
import (
1010
"fmt"
1111
"sort"
12+
"strings"
1213
"sync"
14+
"sync/atomic"
1315

1416
"cmd/go/internal/base"
1517
"cmd/go/internal/module"
@@ -59,12 +61,38 @@ type Reqs interface {
5961
Previous(m module.Version) (module.Version, error)
6062
}
6163

62-
type MissingModuleError struct {
63-
Module module.Version
64+
// BuildListError decorates an error that occurred gathering requirements
65+
// while constructing a build list. BuildListError prints the chain
66+
// of requirements to the module where the error occurred.
67+
type BuildListError struct {
68+
Err error
69+
Stack []module.Version
6470
}
6571

66-
func (e *MissingModuleError) Error() string {
67-
return fmt.Sprintf("missing module: %v", e.Module)
72+
func (e *BuildListError) Error() string {
73+
b := &strings.Builder{}
74+
errMsg := e.Err.Error()
75+
stack := e.Stack
76+
77+
// Don't print modules at the beginning of the chain without a
78+
// version. These always seem to be the main module or a
79+
// synthetic module ("target@").
80+
for len(stack) > 0 && stack[len(stack)-1].Version == "" {
81+
stack = stack[:len(stack)-1]
82+
}
83+
84+
// Don't print the last module if the error message already
85+
// starts with module path and version.
86+
if len(stack) > 0 && strings.HasPrefix(errMsg, fmt.Sprintf("%s@%s: ", stack[0].Path, stack[0].Version)) {
87+
// error already mentions module
88+
stack = stack[1:]
89+
}
90+
91+
for i := len(stack) - 1; i >= 0; i-- {
92+
fmt.Fprintf(b, "%s@%s ->\n\t", stack[i].Path, stack[i].Version)
93+
}
94+
b.WriteString(errMsg)
95+
return b.String()
6896
}
6997

7098
// BuildList returns the build list for the target module.
@@ -78,33 +106,40 @@ func buildList(target module.Version, reqs Reqs, upgrade func(module.Version) mo
78106
// does high-latency network operations.
79107
var work par.Work
80108
work.Add(target)
109+
110+
type modGraphNode struct {
111+
m module.Version
112+
required []module.Version
113+
upgrade module.Version
114+
err error
115+
}
81116
var (
82117
mu sync.Mutex
83-
min = map[string]string{target.Path: target.Version}
84-
firstErr error
118+
modGraph = map[module.Version]*modGraphNode{}
119+
min = map[string]string{} // maps module path to minimum required version
120+
haveErr int32
85121
)
122+
123+
work.Add(target)
86124
work.Do(10, func(item interface{}) {
87125
m := item.(module.Version)
88-
required, err := reqs.Required(m)
89126

127+
node := &modGraphNode{m: m}
90128
mu.Lock()
91-
if err != nil && firstErr == nil {
92-
firstErr = err
93-
}
94-
if firstErr != nil {
95-
mu.Unlock()
96-
return
97-
}
129+
modGraph[m] = node
98130
if v, ok := min[m.Path]; !ok || reqs.Max(v, m.Version) != v {
99131
min[m.Path] = m.Version
100132
}
101133
mu.Unlock()
102134

103-
for _, r := range required {
104-
if r.Path == "" {
105-
base.Errorf("Required(%v) returned zero module in list", m)
106-
continue
107-
}
135+
required, err := reqs.Required(m)
136+
if err != nil {
137+
node.err = err
138+
atomic.StoreInt32(&haveErr, 1)
139+
return
140+
}
141+
node.required = required
142+
for _, r := range node.required {
108143
work.Add(r)
109144
}
110145

@@ -114,25 +149,58 @@ func buildList(target module.Version, reqs Reqs, upgrade func(module.Version) mo
114149
base.Errorf("Upgrade(%v) returned zero module", m)
115150
return
116151
}
117-
work.Add(u)
152+
if u != m {
153+
node.upgrade = u
154+
work.Add(u)
155+
}
118156
}
119157
})
120158

121-
if firstErr != nil {
122-
return nil, firstErr
159+
// If there was an error, find the shortest path from the target to the
160+
// node where the error occurred so we can report a useful error message.
161+
if haveErr != 0 {
162+
// neededBy[a] = b means a was added to the module graph by b.
163+
neededBy := make(map[*modGraphNode]*modGraphNode)
164+
q := make([]*modGraphNode, 0, len(modGraph))
165+
q = append(q, modGraph[target])
166+
for len(q) > 0 {
167+
node := q[0]
168+
q = q[1:]
169+
170+
if node.err != nil {
171+
err := &BuildListError{Err: node.err}
172+
for n := node; n != nil; n = neededBy[n] {
173+
err.Stack = append(err.Stack, n.m)
174+
}
175+
return nil, err
176+
}
177+
178+
neighbors := node.required
179+
if node.upgrade.Path != "" {
180+
neighbors = append(neighbors, node.upgrade)
181+
}
182+
for _, neighbor := range neighbors {
183+
nn := modGraph[neighbor]
184+
if neededBy[nn] != nil {
185+
continue
186+
}
187+
neededBy[nn] = node
188+
q = append(q, nn)
189+
}
190+
}
123191
}
192+
193+
// Construct the list by traversing the graph again, replacing older
194+
// modules with required minimum versions.
124195
if v := min[target.Path]; v != target.Version {
125196
panic(fmt.Sprintf("mistake: chose version %q instead of target %+v", v, target)) // TODO: Don't panic.
126197
}
127198

128199
list := []module.Version{target}
129200
listed := map[string]bool{target.Path: true}
130201
for i := 0; i < len(list); i++ {
131-
m := list[i]
132-
required, err := reqs.Required(m)
133-
if err != nil {
134-
return nil, err
135-
}
202+
n := modGraph[list[i]]
203+
required := n.required
136204
for _, r := range required {
137205
v := min[r.Path]
138206
if r.Path != target.Path && reqs.Max(v, r.Version) != v {

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package mvs
66

77
import (
8+
"fmt"
89
"reflect"
910
"strings"
1011
"testing"
@@ -446,7 +447,7 @@ func (r reqsMap) Upgrade(m module.Version) (module.Version, error) {
446447
}
447448
}
448449
if u.Path == "" {
449-
return module.Version{}, &MissingModuleError{module.Version{Path: m.Path, Version: ""}}
450+
return module.Version{}, fmt.Errorf("missing module: %v", module.Version{Path: m.Path})
450451
}
451452
return u, nil
452453
}
@@ -467,7 +468,7 @@ func (r reqsMap) Previous(m module.Version) (module.Version, error) {
467468
func (r reqsMap) Required(m module.Version) ([]module.Version, error) {
468469
rr, ok := r[m]
469470
if !ok {
470-
return nil, &MissingModuleError{m}
471+
return nil, fmt.Errorf("missing module: %v", m)
471472
}
472473
return rr, nil
473474
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
example.com/badchain/a v1.0.0
2+
3+
-- .mod --
4+
module example.com/badchain/a
5+
6+
require example.com/badchain/b v1.0.0
7+
-- .info --
8+
{"Version":"v1.0.0"}
9+
-- a.go --
10+
package a
11+
12+
import _ "example.com/badchain/b"
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
example.com/badchain/a v1.1.0
2+
3+
-- .mod --
4+
module example.com/badchain/a
5+
6+
require example.com/badchain/b v1.1.0
7+
-- .info --
8+
{"Version":"v1.1.0"}
9+
-- a.go --
10+
package a
11+
12+
import _ "example.com/badchain/b"
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
example.com/badchain/b v1.0.0
2+
3+
-- .mod --
4+
module example.com/badchain/b
5+
6+
require example.com/badchain/c v1.0.0
7+
-- .info --
8+
{"Version":"v1.0.0"}
9+
-- b.go --
10+
package b
11+
12+
import _ "example.com/badchain/c"
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
example.com/badchain/b v1.1.0
2+
3+
-- .mod --
4+
module example.com/badchain/b
5+
6+
require example.com/badchain/c v1.1.0
7+
-- .info --
8+
{"Version":"v1.1.0"}
9+
-- b.go --
10+
package b
11+
12+
import _ "example.com/badchain/c"
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
example.com/badchain/c v1.0.0
2+
3+
-- .mod --
4+
module example.com/badchain/c
5+
-- .info --
6+
{"Version":"v1.0.0"}
7+
-- c.go --
8+
package c
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
example.com/badchain/c v1.1.0
2+
3+
-- .mod --
4+
module example.com/badchain/wrong
5+
-- .info --
6+
{"Version":"v1.1.0"}
7+
-- c.go --
8+
package c
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
[short] skip
2+
env GO111MODULE=on
3+
4+
# Download everything to avoid "finding" messages in stderr later.
5+
cp go.mod.orig go.mod
6+
go mod download
7+
go mod download example.com/badchain/[email protected]
8+
go mod download example.com/badchain/[email protected]
9+
go mod download example.com/badchain/[email protected]
10+
11+
# Try to upgrade example.com/badchain/a (and its dependencies).
12+
! go get -u example.com/badchain/a
13+
cmp stderr upgrade-a-expected
14+
cmp go.mod go.mod.orig
15+
16+
# Try to upgrade the main module. This upgrades everything, including
17+
# modules that aren't direct requirements, so the error stack is shorter.
18+
! go get -u
19+
cmp stderr upgrade-main-expected
20+
cmp go.mod go.mod.orig
21+
22+
# Upgrade manually. Listing modules should produce an error.
23+
go mod edit -require=example.com/badchain/[email protected]
24+
! go list -m
25+
cmp stderr list-expected
26+
27+
-- go.mod.orig --
28+
module m
29+
30+
require example.com/badchain/a v1.0.0
31+
-- upgrade-main-expected --
32+
go get: example.com/badchain/[email protected] ->
33+
example.com/badchain/[email protected]: parsing go.mod: unexpected module path "example.com/badchain/wrong"
34+
-- upgrade-a-expected --
35+
go get: example.com/badchain/[email protected] ->
36+
example.com/badchain/[email protected] ->
37+
example.com/badchain/[email protected]: parsing go.mod: unexpected module path "example.com/badchain/wrong"
38+
-- list-expected --
39+
go: example.com/badchain/[email protected] ->
40+
example.com/badchain/[email protected] ->
41+
example.com/badchain/[email protected]: parsing go.mod: unexpected module path "example.com/badchain/wrong"

0 commit comments

Comments
 (0)