Skip to content

Commit 4eca0c7

Browse files
committed
internal/mod/mvs: adapt to CUE semantics
Note: experimental feature. The mvs tests rely on using module.Version in a way that doesn't conform to the new requirements for that type. Given that mvs doesn't actually care that it's specifically dealing with module.Version per se, we make it generic across any version-like value. This allows us to avoid rewriting all the (fairly extensive) tests and seems a reasonably natural fit for the generic nature of the MVS algorithm itself. For #2330. Signed-off-by: Roger Peppe <[email protected]> Change-Id: Ib1b16c3267ac98fde30347f118f0eb6b2764a473 Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1168705 Unity-Result: CUE porcuepine <[email protected]> Reviewed-by: Paul Jolly <[email protected]> TryBot-Result: CUEcueckoo <[email protected]>
1 parent a102419 commit 4eca0c7

File tree

7 files changed

+289
-188
lines changed

7 files changed

+289
-188
lines changed

internal/mod/module/module.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ func MustNewVersion(path string, vers string) Version {
103103
}
104104

105105
// NewVersion forms a Version from the given path and version.
106-
// The version must be canonical or empty.
106+
// The version must be canonical, empty or "none".
107107
// If the path doesn't have a major version suffix, one will be added
108108
// if the version isn't empty; if the version is empty, it's an error.
109109
func NewVersion(path string, vers string) (Version, error) {

internal/mod/module/versions.go

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package module
2+
3+
import (
4+
"golang.org/x/mod/semver"
5+
)
6+
7+
// Versions implements mvs.Versions[Version].
8+
type Versions struct{}
9+
10+
// New implements mvs.Versions[Version].Version.
11+
func (Versions) Version(v Version) string {
12+
return v.Version()
13+
}
14+
15+
// New implements mvs.Versions[Version].Path.
16+
func (Versions) Path(v Version) string {
17+
return v.Path()
18+
}
19+
20+
// New implements mvs.Versions[Version].New.
21+
func (Versions) New(p, v string) (Version, error) {
22+
return NewVersion(p, v)
23+
}
24+
25+
// Max implements mvs.Reqs.Max.
26+
//
27+
// It is consistent with semver.Compare except that as a special case,
28+
// the version "" is considered higher than all other versions. The main
29+
// module (also known as the target) has no version and must be chosen
30+
// over other versions of the same module in the module dependency
31+
// graph.
32+
//
33+
// See [mvs.Reqs] for more detail.
34+
func (Versions) Max(v1, v2 string) string {
35+
if v1 == "none" || v2 == "" {
36+
return v2
37+
}
38+
if v2 == "none" || v1 == "" {
39+
return v1
40+
}
41+
if semver.Compare(v1, v2) > 0 {
42+
return v1
43+
}
44+
return v2
45+
}

internal/mod/module/versions_test.go

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package module_test
2+
3+
import (
4+
"cuelang.org/go/internal/mod/module"
5+
"cuelang.org/go/internal/mod/mvs"
6+
)
7+
8+
var _ mvs.Versions[module.Version] = module.Versions{}

internal/mod/mvs/errors.go

+22-33
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
//go:build ignore
2-
31
// Copyright 2020 The Go Authors. All rights reserved.
42
// Use of this source code is governed by a BSD-style
53
// license that can be found in the LICENSE file.
@@ -9,20 +7,19 @@ package mvs
97
import (
108
"fmt"
119
"strings"
12-
13-
"golang.org/x/mod/module"
1410
)
1511

1612
// BuildListError decorates an error that occurred gathering requirements
1713
// while constructing a build list. BuildListError prints the chain
1814
// of requirements to the module where the error occurred.
19-
type BuildListError struct {
15+
type BuildListError[V comparable] struct {
2016
Err error
21-
stack []buildListErrorElem
17+
stack []buildListErrorElem[V]
18+
vs Versions[V]
2219
}
2320

24-
type buildListErrorElem struct {
25-
m module.Version
21+
type buildListErrorElem[V comparable] struct {
22+
m V
2623

2724
// nextReason is the reason this module depends on the next module in the
2825
// stack. Typically either "requires", or "updating to".
@@ -37,69 +34,61 @@ type buildListErrorElem struct {
3734
// explicit upgrade or downgrade (as opposed to an existing requirement in a
3835
// go.mod file). A nil isVersionChange function indicates that none of the path
3936
// steps are due to explicit version changes.
40-
func NewBuildListError(err error, path []module.Version, isVersionChange func(from, to module.Version) bool) *BuildListError {
41-
stack := make([]buildListErrorElem, 0, len(path))
37+
func NewBuildListError[V comparable](err error, path []V, vs Versions[V], isVersionChange func(from, to V) bool) *BuildListError[V] {
38+
stack := make([]buildListErrorElem[V], 0, len(path))
4239
for len(path) > 1 {
4340
reason := "requires"
4441
if isVersionChange != nil && isVersionChange(path[0], path[1]) {
4542
reason = "updating to"
4643
}
47-
stack = append(stack, buildListErrorElem{
44+
stack = append(stack, buildListErrorElem[V]{
4845
m: path[0],
4946
nextReason: reason,
5047
})
5148
path = path[1:]
5249
}
53-
stack = append(stack, buildListErrorElem{m: path[0]})
50+
stack = append(stack, buildListErrorElem[V]{m: path[0]})
5451

55-
return &BuildListError{
52+
return &BuildListError[V]{
5653
Err: err,
5754
stack: stack,
55+
vs: vs,
5856
}
5957
}
6058

6159
// Module returns the module where the error occurred. If the module stack
6260
// is empty, this returns a zero value.
63-
func (e *BuildListError) Module() module.Version {
61+
func (e *BuildListError[V]) Module() V {
6462
if len(e.stack) == 0 {
65-
return module.Version{}
63+
return *new(V)
6664
}
6765
return e.stack[len(e.stack)-1].m
6866
}
6967

70-
func (e *BuildListError) Error() string {
68+
func (e *BuildListError[V]) Error() string {
7169
b := &strings.Builder{}
7270
stack := e.stack
7371

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

8179
if len(stack) == 0 {
8280
b.WriteString(e.Err.Error())
8381
} else {
8482
for _, elem := range stack[:len(stack)-1] {
85-
fmt.Fprintf(b, "%s %s\n\t", elem.m, elem.nextReason)
83+
fmt.Fprintf(b, "%v %s\n\t", elem.m, elem.nextReason)
8684
}
87-
// Ensure that the final module path and version are included as part of the
88-
// error message.
8985
m := stack[len(stack)-1].m
90-
if mErr, ok := e.Err.(*module.ModuleError); ok {
91-
actual := module.Version{Path: mErr.Path, Version: mErr.Version}
92-
if v, ok := mErr.Err.(*module.InvalidVersionError); ok {
93-
actual.Version = v.Version
94-
}
95-
if actual == m {
96-
fmt.Fprintf(b, "%v", e.Err)
97-
} else {
98-
fmt.Fprintf(b, "%s (replaced by %s): %v", m, actual, mErr.Err)
99-
}
100-
} else {
101-
fmt.Fprintf(b, "%v", module.VersionError(m, e.Err))
102-
}
86+
fmt.Fprintf(b, "%v: %v", m, e.Err)
87+
// TODO the original mvs code was careful to ensure that the final module path
88+
// and version were included as part of the error message, but it did that
89+
// by checking for mod/module-specific error types, but we don't want this
90+
// package to depend on module. We could potentially do it by making those
91+
// errors implement interface types defined in this package.
10392
}
10493
return b.String()
10594
}

0 commit comments

Comments
 (0)