Skip to content

Commit

Permalink
cmd/go: describe dependencies in build list error messages
Browse files Browse the repository at this point in the history
mvs.BuildList reports errors with a chain of modules to make it
clear why the module where the error occurred was part of the
build. This is a little confusing with "go get -u" since there are
edges in the module graph for requirements and for updates.

With this change, we now print "requires" or "updates to" between
each module version in the chain.

Updates #30661

Change-Id: Ie689500ea86857e715b250b9e0cae0bc6686dc32
Reviewed-on: https://go-review.googlesource.com/c/go/+/171150
Run-TryBot: Jay Conrod <jayconrod@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
  • Loading branch information
Jay Conrod committed Apr 16, 2019
1 parent 6997671 commit f7c9672
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 27 deletions.
45 changes: 30 additions & 15 deletions src/cmd/go/internal/mvs/mvs.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,33 +65,41 @@ type Reqs interface {
// while constructing a build list. BuildListError prints the chain
// of requirements to the module where the error occurred.
type BuildListError struct {
Err error
Stack []module.Version
err error
stack []buildListErrorElem
}

type buildListErrorElem struct {
m module.Version

// nextReason is the reason this module depends on the next module in the
// stack. Typically either "requires", or "upgraded to".
nextReason string
}

func (e *BuildListError) Error() string {
b := &strings.Builder{}
errMsg := e.Err.Error()
stack := e.Stack
errMsg := e.err.Error()
stack := e.stack

// Don't print modules at the beginning of the chain without a
// version. These always seem to be the main module or a
// synthetic module ("target@").
for len(stack) > 0 && stack[len(stack)-1].Version == "" {
for len(stack) > 0 && stack[len(stack)-1].m.Version == "" {
stack = stack[:len(stack)-1]
}

// Don't print the last module if the error message already
// starts with module path and version.
if len(stack) > 0 && strings.HasPrefix(errMsg, fmt.Sprintf("%s@%s: ", stack[0].Path, stack[0].Version)) {
// error already mentions module
stack = stack[1:]
errMentionsLast := len(stack) > 0 && strings.HasPrefix(errMsg, fmt.Sprintf("%s@%s: ", stack[0].m.Path, stack[0].m.Version))
for i := len(stack) - 1; i >= 1; i-- {
fmt.Fprintf(b, "%s@%s %s\n\t", stack[i].m.Path, stack[i].m.Version, stack[i].nextReason)
}

for i := len(stack) - 1; i >= 0; i-- {
fmt.Fprintf(b, "%s@%s ->\n\t", stack[i].Path, stack[i].Version)
if errMentionsLast || len(stack) == 0 {
b.WriteString(errMsg)
} else {
fmt.Fprintf(b, "%s@%s: %s", stack[0].m.Path, stack[0].m.Version, errMsg)
}
b.WriteString(errMsg)
return b.String()
}

Expand Down Expand Up @@ -168,9 +176,16 @@ func buildList(target module.Version, reqs Reqs, upgrade func(module.Version) mo
q = q[1:]

if node.err != nil {
err := &BuildListError{Err: node.err}
for n := node; n != nil; n = neededBy[n] {
err.Stack = append(err.Stack, n.m)
err := &BuildListError{
err: node.err,
stack: []buildListErrorElem{{m: node.m}},
}
for n, prev := neededBy[node], node; n != nil; n, prev = neededBy[n], n {
reason := "requires"
if n.upgrade == prev.m {
reason = "updating to"
}
err.stack = append(err.stack, buildListErrorElem{m: n.m, nextReason: reason})
}
return nil, err
}
Expand Down
24 changes: 12 additions & 12 deletions src/cmd/go/testdata/script/mod_load_badchain.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ go mod download example.com/badchain/a@v1.1.0
go mod download example.com/badchain/b@v1.1.0
go mod download example.com/badchain/c@v1.1.0

# Try to upgrade example.com/badchain/a (and its dependencies).
# Try to update example.com/badchain/a (and its dependencies).
! go get -u example.com/badchain/a
cmp stderr upgrade-a-expected
cmp stderr update-a-expected
cmp go.mod go.mod.orig

# Try to upgrade the main module. This upgrades everything, including
# Try to update the main module. This updates everything, including
# modules that aren't direct requirements, so the error stack is shorter.
! go get -u
cmp stderr upgrade-main-expected
cmp stderr update-main-expected
cmp go.mod go.mod.orig

# Upgrade manually. Listing modules should produce an error.
# update manually. Listing modules should produce an error.
go mod edit -require=example.com/badchain/a@v1.1.0
! go list -m
cmp stderr list-expected
Expand All @@ -28,14 +28,14 @@ cmp stderr list-expected
module m

require example.com/badchain/a v1.0.0
-- upgrade-main-expected --
go get: example.com/badchain/c@v1.0.0 ->
-- update-main-expected --
go get: example.com/badchain/c@v1.0.0 updating to
example.com/badchain/c@v1.1.0: parsing go.mod: unexpected module path "example.com/badchain/wrong"
-- upgrade-a-expected --
go get: example.com/badchain/a@v1.1.0 ->
example.com/badchain/b@v1.1.0 ->
-- update-a-expected --
go get: example.com/badchain/a@v1.1.0 requires
example.com/badchain/b@v1.1.0 requires
example.com/badchain/c@v1.1.0: parsing go.mod: unexpected module path "example.com/badchain/wrong"
-- list-expected --
go: example.com/badchain/a@v1.1.0 ->
example.com/badchain/b@v1.1.0 ->
go: example.com/badchain/a@v1.1.0 requires
example.com/badchain/b@v1.1.0 requires
example.com/badchain/c@v1.1.0: parsing go.mod: unexpected module path "example.com/badchain/wrong"

0 comments on commit f7c9672

Please sign in to comment.