Commit 2bdbc942 authored by Jay Conrod's avatar Jay Conrod

cmd/go: print package import chains for some build list errors

When we construct the build list by loading packages (e.g., in
"go build", "go list", or "go test"), we may load additional modules
not mentioned in the original build list. If we encounter an error
loading one of these modules, mvs.BuildList currently returns a
BuildListError with a chain of requirments. Unfortunately, this is not
helpful, since the graph is structured such that these missing modules
are direct requirements of the main module.

With this change, loader.load keeps track of the package that caused
each "missing" module to be added. If an error occurs in a missing
module, the chain of package imports is printed instead of the module
requirements.

Fixes #31475

Change-Id: Ie484814af42ceea3e85fedc38e705ba3a38cd495
Reviewed-on: https://go-review.googlesource.com/c/go/+/171859
Run-TryBot: Jay Conrod <jayconrod@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarBryan C. Mills <bcmills@google.com>
parent f7c96725
...@@ -546,6 +546,7 @@ func (ld *loader) load(roots func() []string) { ...@@ -546,6 +546,7 @@ func (ld *loader) load(roots func() []string) {
for _, m := range buildList { for _, m := range buildList {
haveMod[m] = true haveMod[m] = true
} }
modAddedBy := make(map[module.Version]*loadPkg)
for _, pkg := range ld.pkgs { for _, pkg := range ld.pkgs {
if err, ok := pkg.err.(*ImportMissingError); ok && err.Module.Path != "" { if err, ok := pkg.err.(*ImportMissingError); ok && err.Module.Path != "" {
if err.newMissingVersion != "" { if err.newMissingVersion != "" {
...@@ -558,6 +559,7 @@ func (ld *loader) load(roots func() []string) { ...@@ -558,6 +559,7 @@ func (ld *loader) load(roots func() []string) {
numAdded++ numAdded++
if !haveMod[err.Module] { if !haveMod[err.Module] {
haveMod[err.Module] = true haveMod[err.Module] = true
modAddedBy[err.Module] = pkg
buildList = append(buildList, err.Module) buildList = append(buildList, err.Module)
} }
continue continue
...@@ -573,6 +575,14 @@ func (ld *loader) load(roots func() []string) { ...@@ -573,6 +575,14 @@ func (ld *loader) load(roots func() []string) {
reqs = Reqs() reqs = Reqs()
buildList, err = mvs.BuildList(Target, reqs) buildList, err = mvs.BuildList(Target, reqs)
if err != nil { if err != nil {
// If an error was found in a newly added module, report the package
// import stack instead of the module requirement stack. Packages
// are more descriptive.
if err, ok := err.(*mvs.BuildListError); ok {
if pkg := modAddedBy[err.Module()]; pkg != nil {
base.Fatalf("go: %s: %v", pkg.stackText(), err.Err)
}
}
base.Fatalf("go: %v", err) base.Fatalf("go: %v", err)
} }
} }
...@@ -804,27 +814,33 @@ func (ld *loader) buildStacks() { ...@@ -804,27 +814,33 @@ func (ld *loader) buildStacks() {
// stackText builds the import stack text to use when // stackText builds the import stack text to use when
// reporting an error in pkg. It has the general form // reporting an error in pkg. It has the general form
// //
// import root -> // root imports
// import other -> // other imports
// import other2 -> // other2 tested by
// import pkg // other2.test imports
// pkg
// //
func (pkg *loadPkg) stackText() string { func (pkg *loadPkg) stackText() string {
var stack []*loadPkg var stack []*loadPkg
for p := pkg.stack; p != nil; p = p.stack { for p := pkg; p != nil; p = p.stack {
stack = append(stack, p) stack = append(stack, p)
} }
var buf bytes.Buffer var buf bytes.Buffer
for i := len(stack) - 1; i >= 0; i-- { for i := len(stack) - 1; i >= 0; i-- {
p := stack[i] p := stack[i]
fmt.Fprint(&buf, p.path)
if p.testOf != nil { if p.testOf != nil {
fmt.Fprintf(&buf, "test ->\n\t") fmt.Fprint(&buf, ".test")
}
if i > 0 {
if stack[i-1].testOf == p {
fmt.Fprint(&buf, " tested by\n\t")
} else { } else {
fmt.Fprintf(&buf, "import %q ->\n\t", p.path) fmt.Fprint(&buf, " imports\n\t")
}
} }
} }
fmt.Fprintf(&buf, "import %q", pkg.path)
return buf.String() return buf.String()
} }
......
...@@ -65,7 +65,7 @@ type Reqs interface { ...@@ -65,7 +65,7 @@ type Reqs interface {
// while constructing a build list. BuildListError prints the chain // while constructing a build list. BuildListError prints the chain
// of requirements to the module where the error occurred. // of requirements to the module where the error occurred.
type BuildListError struct { type BuildListError struct {
err error Err error
stack []buildListErrorElem stack []buildListErrorElem
} }
...@@ -77,9 +77,18 @@ type buildListErrorElem struct { ...@@ -77,9 +77,18 @@ type buildListErrorElem struct {
nextReason string nextReason string
} }
// Module returns the module where the error occurred. If the module stack
// is empty, this returns a zero value.
func (e *BuildListError) Module() module.Version {
if len(e.stack) == 0 {
return module.Version{}
}
return e.stack[0].m
}
func (e *BuildListError) Error() string { func (e *BuildListError) Error() string {
b := &strings.Builder{} b := &strings.Builder{}
errMsg := e.err.Error() errMsg := e.Err.Error()
stack := e.stack stack := e.stack
// Don't print modules at the beginning of the chain without a // Don't print modules at the beginning of the chain without a
...@@ -177,7 +186,7 @@ func buildList(target module.Version, reqs Reqs, upgrade func(module.Version) mo ...@@ -177,7 +186,7 @@ func buildList(target module.Version, reqs Reqs, upgrade func(module.Version) mo
if node.err != nil { if node.err != nil {
err := &BuildListError{ err := &BuildListError{
err: node.err, Err: node.err,
stack: []buildListErrorElem{{m: node.m}}, stack: []buildListErrorElem{{m: node.m}},
} }
for n, prev := neededBy[node], node; n != nil; n, prev = neededBy[n], n { for n, prev := neededBy[node], node; n != nil; n, prev = neededBy[n], n {
......
...@@ -19,15 +19,39 @@ cmp go.mod go.mod.orig ...@@ -19,15 +19,39 @@ cmp go.mod go.mod.orig
cmp stderr update-main-expected cmp stderr update-main-expected
cmp go.mod go.mod.orig cmp go.mod go.mod.orig
# update 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 mod edit -require=example.com/badchain/a@v1.1.0
! go list -m ! go list -m
cmp stderr list-expected cmp stderr list-expected
# Try listing a package that imports a package
# in a module without a requirement.
go mod edit -droprequire example.com/badchain/a
! go list m/use
cmp stderr list-missing-expected
! go list -test m/testuse
cmp stderr list-missing-test-expected
-- go.mod.orig -- -- go.mod.orig --
module m module m
require example.com/badchain/a v1.0.0 require example.com/badchain/a v1.0.0
-- use/use.go --
package use
import _ "example.com/badchain/c"
-- testuse/testuse.go --
package testuse
-- testuse/testuse_test.go --
package testuse
import (
"testing"
_ "example.com/badchain/c"
)
func Test(t *testing.T) {}
-- update-main-expected -- -- update-main-expected --
go get: example.com/badchain/c@v1.0.0 updating to 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" example.com/badchain/c@v1.1.0: parsing go.mod: unexpected module path "example.com/badchain/wrong"
...@@ -39,3 +63,10 @@ go get: example.com/badchain/a@v1.1.0 requires ...@@ -39,3 +63,10 @@ go get: example.com/badchain/a@v1.1.0 requires
go: example.com/badchain/a@v1.1.0 requires go: example.com/badchain/a@v1.1.0 requires
example.com/badchain/b@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" example.com/badchain/c@v1.1.0: parsing go.mod: unexpected module path "example.com/badchain/wrong"
-- list-missing-expected --
go: m/use imports
example.com/badchain/c: example.com/badchain/c@v1.1.0: parsing go.mod: unexpected module path "example.com/badchain/wrong"
-- list-missing-test-expected --
go: m/testuse tested by
m/testuse.test imports
example.com/badchain/c: example.com/badchain/c@v1.1.0: parsing go.mod: unexpected module path "example.com/badchain/wrong"
env GO111MODULE=on env GO111MODULE=on
! go list use.go ! go list use.go
stderr 'import "example.com/missingpkg/deprecated": package provided by example.com/missingpkg at latest version v1.0.0 but not at required version v1.0.1-beta' stderr 'example.com/missingpkg/deprecated: package provided by example.com/missingpkg at latest version v1.0.0 but not at required version v1.0.1-beta'
-- use.go -- -- use.go --
package use package use
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment