gomod := filepath.Join(dir, "go.mod")
data, err := ioutil.ReadFile(gomod)
if err != nil {
- base.Errorf("go: parsing %s: %v", base.ShortPath(gomod), err)
- return nil, ErrRequire
+ return nil, fmt.Errorf("parsing %s: %v", base.ShortPath(gomod), err)
}
f, err := modfile.ParseLax(gomod, data, nil)
if err != nil {
- base.Errorf("go: parsing %s: %v", base.ShortPath(gomod), err)
- return nil, ErrRequire
+ return nil, fmt.Errorf("parsing %s: %v", base.ShortPath(gomod), err)
}
if f.Go != nil {
r.versions.LoadOrStore(mod, f.Go.Version)
data, err := modfetch.GoMod(mod.Path, mod.Version)
if err != nil {
- base.Errorf("go: %s@%s: %v\n", mod.Path, mod.Version, err)
- return nil, ErrRequire
+ return nil, fmt.Errorf("%s@%s: %v", mod.Path, mod.Version, err)
}
f, err := modfile.ParseLax("go.mod", data, nil)
if err != nil {
- base.Errorf("go: %s@%s: parsing go.mod: %v", mod.Path, mod.Version, err)
- return nil, ErrRequire
+ return nil, fmt.Errorf("%s@%s: parsing go.mod: %v", mod.Path, mod.Version, err)
}
if f.Module == nil {
- base.Errorf("go: %s@%s: parsing go.mod: missing module line", mod.Path, mod.Version)
- return nil, ErrRequire
+ return nil, fmt.Errorf("%s@%s: parsing go.mod: missing module line", mod.Path, mod.Version)
}
if mpath := f.Module.Mod.Path; mpath != origPath && mpath != mod.Path {
- base.Errorf("go: %s@%s: parsing go.mod: unexpected module path %q", mod.Path, mod.Version, mpath)
- return nil, ErrRequire
+ return nil, fmt.Errorf("%s@%s: parsing go.mod: unexpected module path %q", mod.Path, mod.Version, mpath)
}
if f.Go != nil {
r.versions.LoadOrStore(mod, f.Go.Version)
return r.modFileToList(f), nil
}
-// ErrRequire is the sentinel error returned when Require encounters problems.
-// It prints the problems directly to standard error, so that multiple errors
-// can be displayed easily.
-var ErrRequire = errors.New("error loading module requirements")
-
func (*mvsReqs) Max(v1, v2 string) string {
if v1 != "" && semver.Compare(v1, v2) == -1 {
return v2
import (
"fmt"
"sort"
+ "strings"
"sync"
+ "sync/atomic"
"cmd/go/internal/base"
"cmd/go/internal/module"
Previous(m module.Version) (module.Version, error)
}
-type MissingModuleError struct {
- Module module.Version
+// BuildListError decorates an error that occurred gathering requirements
+// 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
}
-func (e *MissingModuleError) Error() string {
- return fmt.Sprintf("missing module: %v", e.Module)
+func (e *BuildListError) Error() string {
+ b := &strings.Builder{}
+ 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 == "" {
+ 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:]
+ }
+
+ for i := len(stack) - 1; i >= 0; i-- {
+ fmt.Fprintf(b, "%s@%s ->\n\t", stack[i].Path, stack[i].Version)
+ }
+ b.WriteString(errMsg)
+ return b.String()
}
// BuildList returns the build list for the target module.
// does high-latency network operations.
var work par.Work
work.Add(target)
+
+ type modGraphNode struct {
+ m module.Version
+ required []module.Version
+ upgrade module.Version
+ err error
+ }
var (
mu sync.Mutex
- min = map[string]string{target.Path: target.Version}
- firstErr error
+ modGraph = map[module.Version]*modGraphNode{}
+ min = map[string]string{} // maps module path to minimum required version
+ haveErr int32
)
+
+ work.Add(target)
work.Do(10, func(item interface{}) {
m := item.(module.Version)
- required, err := reqs.Required(m)
+ node := &modGraphNode{m: m}
mu.Lock()
- if err != nil && firstErr == nil {
- firstErr = err
- }
- if firstErr != nil {
- mu.Unlock()
- return
- }
+ modGraph[m] = node
if v, ok := min[m.Path]; !ok || reqs.Max(v, m.Version) != v {
min[m.Path] = m.Version
}
mu.Unlock()
- for _, r := range required {
- if r.Path == "" {
- base.Errorf("Required(%v) returned zero module in list", m)
- continue
- }
+ required, err := reqs.Required(m)
+ if err != nil {
+ node.err = err
+ atomic.StoreInt32(&haveErr, 1)
+ return
+ }
+ node.required = required
+ for _, r := range node.required {
work.Add(r)
}
base.Errorf("Upgrade(%v) returned zero module", m)
return
}
- work.Add(u)
+ if u != m {
+ node.upgrade = u
+ work.Add(u)
+ }
}
})
- if firstErr != nil {
- return nil, firstErr
+ // If there was an error, find the shortest path from the target to the
+ // node where the error occurred so we can report a useful error message.
+ if haveErr != 0 {
+ // neededBy[a] = b means a was added to the module graph by b.
+ neededBy := make(map[*modGraphNode]*modGraphNode)
+ q := make([]*modGraphNode, 0, len(modGraph))
+ q = append(q, modGraph[target])
+ for len(q) > 0 {
+ node := q[0]
+ 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)
+ }
+ return nil, err
+ }
+
+ neighbors := node.required
+ if node.upgrade.Path != "" {
+ neighbors = append(neighbors, node.upgrade)
+ }
+ for _, neighbor := range neighbors {
+ nn := modGraph[neighbor]
+ if neededBy[nn] != nil {
+ continue
+ }
+ neededBy[nn] = node
+ q = append(q, nn)
+ }
+ }
}
+
+ // Construct the list by traversing the graph again, replacing older
+ // modules with required minimum versions.
if v := min[target.Path]; v != target.Version {
panic(fmt.Sprintf("mistake: chose version %q instead of target %+v", v, target)) // TODO: Don't panic.
}
list := []module.Version{target}
listed := map[string]bool{target.Path: true}
for i := 0; i < len(list); i++ {
- m := list[i]
- required, err := reqs.Required(m)
- if err != nil {
- return nil, err
- }
+ n := modGraph[list[i]]
+ required := n.required
for _, r := range required {
v := min[r.Path]
if r.Path != target.Path && reqs.Max(v, r.Version) != v {
package mvs
import (
+ "fmt"
"reflect"
"strings"
"testing"
}
}
if u.Path == "" {
- return module.Version{}, &MissingModuleError{module.Version{Path: m.Path, Version: ""}}
+ return module.Version{}, fmt.Errorf("missing module: %v", module.Version{Path: m.Path})
}
return u, nil
}
func (r reqsMap) Required(m module.Version) ([]module.Version, error) {
rr, ok := r[m]
if !ok {
- return nil, &MissingModuleError{m}
+ return nil, fmt.Errorf("missing module: %v", m)
}
return rr, nil
}
--- /dev/null
+example.com/badchain/a v1.0.0
+
+-- .mod --
+module example.com/badchain/a
+
+require example.com/badchain/b v1.0.0
+-- .info --
+{"Version":"v1.0.0"}
+-- a.go --
+package a
+
+import _ "example.com/badchain/b"
--- /dev/null
+example.com/badchain/a v1.1.0
+
+-- .mod --
+module example.com/badchain/a
+
+require example.com/badchain/b v1.1.0
+-- .info --
+{"Version":"v1.1.0"}
+-- a.go --
+package a
+
+import _ "example.com/badchain/b"
--- /dev/null
+example.com/badchain/b v1.0.0
+
+-- .mod --
+module example.com/badchain/b
+
+require example.com/badchain/c v1.0.0
+-- .info --
+{"Version":"v1.0.0"}
+-- b.go --
+package b
+
+import _ "example.com/badchain/c"
--- /dev/null
+example.com/badchain/b v1.1.0
+
+-- .mod --
+module example.com/badchain/b
+
+require example.com/badchain/c v1.1.0
+-- .info --
+{"Version":"v1.1.0"}
+-- b.go --
+package b
+
+import _ "example.com/badchain/c"
--- /dev/null
+example.com/badchain/c v1.0.0
+
+-- .mod --
+module example.com/badchain/c
+-- .info --
+{"Version":"v1.0.0"}
+-- c.go --
+package c
--- /dev/null
+example.com/badchain/c v1.1.0
+
+-- .mod --
+module example.com/badchain/wrong
+-- .info --
+{"Version":"v1.1.0"}
+-- c.go --
+package c
--- /dev/null
+[short] skip
+env GO111MODULE=on
+
+# Download everything to avoid "finding" messages in stderr later.
+cp go.mod.orig go.mod
+go mod download
+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).
+! go get -u example.com/badchain/a
+cmp stderr upgrade-a-expected
+cmp go.mod go.mod.orig
+
+# Try to upgrade the main module. This upgrades everything, including
+# modules that aren't direct requirements, so the error stack is shorter.
+! go get -u
+cmp stderr upgrade-main-expected
+cmp go.mod go.mod.orig
+
+# Upgrade 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
+
+-- go.mod.orig --
+module m
+
+require example.com/badchain/a v1.0.0
+-- upgrade-main-expected --
+go get: example.com/badchain/c@v1.0.0 ->
+ 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 ->
+ 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 ->
+ example.com/badchain/c@v1.1.0: parsing go.mod: unexpected module path "example.com/badchain/wrong"