Skip to content

Commit e9b2c29

Browse files
committed
cmd/cue: implement --language-version flag for cue mod edit and init
This allows the language version to be chosen at `cue mod init` time and also to be changed later. Fixes #3247. Fixes #2920. Signed-off-by: Roger Peppe <[email protected]> Change-Id: Iaa7324d164242ecc8ff6ce793e16dfaf88a532b4 Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1196822 TryBot-Result: CUEcueckoo <[email protected]> Unity-Result: CUE porcuepine <[email protected]> Reviewed-by: Paul Jolly <[email protected]>
1 parent 71eb3be commit e9b2c29

File tree

8 files changed

+166
-44
lines changed

8 files changed

+166
-44
lines changed

cmd/cue/cmd/flags.go

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -20,38 +20,39 @@ import (
2020

2121
// Common flags
2222
const (
23-
flagAll flagName = "all"
24-
flagAllErrors flagName = "all-errors"
25-
flagCheck flagName = "check"
26-
flagDiff flagName = "diff"
27-
flagDryRun flagName = "dry-run"
28-
flagEscape flagName = "escape"
29-
flagExpression flagName = "expression"
30-
flagExt flagName = "ext"
31-
flagFiles flagName = "files"
32-
flagForce flagName = "force"
33-
flagGlob flagName = "name"
34-
flagIgnore flagName = "ignore"
35-
flagInject flagName = "inject"
36-
flagInjectVars flagName = "inject-vars"
37-
flagInlineImports flagName = "inline-imports"
38-
flagJSON flagName = "json"
39-
flagList flagName = "list"
40-
flagMerge flagName = "merge"
41-
flagOut flagName = "out"
42-
flagOutFile flagName = "outfile"
43-
flagPackage flagName = "package"
44-
flagPath flagName = "path"
45-
flagProtoEnum flagName = "proto_enum"
46-
flagProtoPath flagName = "proto_path"
47-
flagRecursive flagName = "recursive"
48-
flagSchema flagName = "schema"
49-
flagSimplify flagName = "simplify"
50-
flagSource flagName = "source"
51-
flagStrict flagName = "strict"
52-
flagTrace flagName = "trace"
53-
flagVerbose flagName = "verbose"
54-
flagWithContext flagName = "with-context"
23+
flagAll flagName = "all"
24+
flagAllErrors flagName = "all-errors"
25+
flagCheck flagName = "check"
26+
flagDiff flagName = "diff"
27+
flagDryRun flagName = "dry-run"
28+
flagEscape flagName = "escape"
29+
flagExpression flagName = "expression"
30+
flagExt flagName = "ext"
31+
flagFiles flagName = "files"
32+
flagForce flagName = "force"
33+
flagGlob flagName = "name"
34+
flagIgnore flagName = "ignore"
35+
flagInject flagName = "inject"
36+
flagInjectVars flagName = "inject-vars"
37+
flagInlineImports flagName = "inline-imports"
38+
flagJSON flagName = "json"
39+
flagLanguageVersion flagName = "language-version"
40+
flagList flagName = "list"
41+
flagMerge flagName = "merge"
42+
flagOut flagName = "out"
43+
flagOutFile flagName = "outfile"
44+
flagPackage flagName = "package"
45+
flagPath flagName = "path"
46+
flagProtoEnum flagName = "proto_enum"
47+
flagProtoPath flagName = "proto_path"
48+
flagRecursive flagName = "recursive"
49+
flagSchema flagName = "schema"
50+
flagSimplify flagName = "simplify"
51+
flagSource flagName = "source"
52+
flagStrict flagName = "strict"
53+
flagTrace flagName = "trace"
54+
flagVerbose flagName = "verbose"
55+
flagWithContext flagName = "with-context"
5556

5657
// Hidden flags.
5758
flagCpuProfile flagName = "cpuprofile"

cmd/cue/cmd/modedit.go

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import (
2121
"path/filepath"
2222
"strconv"
2323

24+
"cuelang.org/go/internal/cueversion"
25+
"cuelang.org/go/internal/mod/semver"
2426
"cuelang.org/go/mod/modfile"
2527
"cuelang.org/go/mod/module"
2628
"github.com/spf13/cobra"
@@ -54,8 +56,9 @@ Note that this command is not yet stable and may be changed.
5456
RunE: mkRunE(c, editCmd.run),
5557
Args: cobra.ExactArgs(0),
5658
}
57-
addFlagVar(cmd, flagFunc(editCmd.flagSource), "source", "set the source field")
59+
addFlagVar(cmd, flagFunc(editCmd.flagSource), string(flagSource), "set the source field")
5860
addFlagVar(cmd, boolFlagFunc(editCmd.flagDropSource), "drop-source", "remove the source field")
61+
addFlagVar(cmd, flagFunc(editCmd.flagLanguageVersion), string(flagLanguageVersion), "set language.version ('current' means current language version)")
5962
addFlagVar(cmd, flagFunc(editCmd.flagModule), "module", "set the module path")
6063
addFlagVar(cmd, flagFunc(editCmd.flagRequire), "require", "add a required module@version")
6164
addFlagVar(cmd, flagFunc(editCmd.flagDropRequire), "drop-require", "remove a requirement")
@@ -88,7 +91,7 @@ func (c *modEditCmd) run(cmd *Command, args []string) error {
8891
}
8992
newData, err := mf.Format()
9093
if err != nil {
91-
return fmt.Errorf("internal error: invalid module.cue file generated: %v", err)
94+
return fmt.Errorf("invalid resulting module.cue file after edits: %v", err)
9295
}
9396
if bytes.Equal(newData, data) {
9497
return nil
@@ -128,6 +131,41 @@ func (c *modEditCmd) flagDropSource(arg bool) error {
128131
return nil
129132
}
130133

134+
func (c *modEditCmd) flagLanguageVersion(arg string) error {
135+
editFunc, err := addLanguageVersion(arg)
136+
if err != nil {
137+
return err
138+
}
139+
c.addEdit(editFunc)
140+
return nil
141+
}
142+
143+
func addLanguageVersion(v string) (func(*modfile.File) error, error) {
144+
if v == "current" {
145+
v = cueversion.LanguageVersion()
146+
} else {
147+
if semver.Canonical(v) != v {
148+
return nil, fmt.Errorf("language version %q is not canonical (must include major, minor and patch versions)", v)
149+
}
150+
151+
if min := modfile.EarliestClosedSchemaVersion(); semver.Compare(v, min) < 0 {
152+
// TODO(rogpeppe) We might want to relax this to allow people to
153+
// declare an earlier language version (see https://cuelang.org/issue/3145).
154+
return nil, fmt.Errorf("language version %q is too early for module.cue schema (earliest allowed is %s)", v, min)
155+
}
156+
if max := cueversion.LanguageVersion(); semver.Compare(v, max) > 0 {
157+
return nil, fmt.Errorf("language version %q may not be after current language version %s", v, max)
158+
}
159+
}
160+
return func(f *modfile.File) error {
161+
if f.Language == nil {
162+
f.Language = &modfile.Language{}
163+
}
164+
f.Language.Version = v
165+
return nil
166+
}, nil
167+
}
168+
131169
func (c *modEditCmd) flagModule(arg string) error {
132170
if err := module.CheckPath(arg); err != nil {
133171
return err

cmd/cue/cmd/modinit.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222

2323
"github.com/spf13/cobra"
2424

25-
"cuelang.org/go/internal/cueversion"
2625
"cuelang.org/go/mod/modfile"
2726
"cuelang.org/go/mod/module"
2827
)
@@ -45,6 +44,7 @@ in the module.
4544

4645
cmd.Flags().BoolP(string(flagForce), "f", false, "force moving old-style cue.mod file")
4746
cmd.Flags().String(string(flagSource), "", "set the source field")
47+
cmd.Flags().String(string(flagLanguageVersion), "current", "set the language version ('current' means current language version)")
4848
return cmd
4949
}
5050

@@ -84,8 +84,12 @@ func runModInit(cmd *Command, args []string) (err error) {
8484
return err
8585
}
8686
}
87-
mf.Language = &modfile.Language{
88-
Version: cueversion.LanguageVersion(),
87+
editFunc, err := addLanguageVersion(flagLanguageVersion.String(cmd))
88+
if err != nil {
89+
return err
90+
}
91+
if err := editFunc(mf); err != nil {
92+
return err
8993
}
9094

9195
err = os.Mkdir(mod, 0755)

cmd/cue/cmd/testdata/script/modedit_initial.txtar

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,27 @@ cmp cue.mod/module.cue want-module-4
1919
exec cue mod edit --module othermain.org@v1
2020
cmp cue.mod/module.cue want-module-5
2121

22+
# Set specific version.
23+
exec cue mod edit --language-version v0.9.2
24+
cmp cue.mod/module.cue want-module-6
25+
26+
# Set latest version.
27+
exec cue mod edit --language-version current
28+
cmpenv cue.mod/module.cue want-module-7
29+
30+
# Set version earlier than earliest module schema version.
31+
! exec cue mod edit --language-version v0.4.3
32+
cmp stderr want-stderr-8
33+
34+
# Set version too new.
35+
! exec cue mod edit --language-version v2.3.4
36+
cmpenv stderr want-stderr-9
37+
38+
# Check that it's an error to set the version earlier than
39+
# allowed by some of the fields already present.
40+
exec cue mod edit --source self
41+
! exec cue mod edit --language-version v0.8.0
42+
cmp stderr want-stderr-10
2243

2344
-- cue.mod/module.cue --
2445
module: "main.org@v0"
@@ -66,3 +87,19 @@ module: "othermain.org@v1"
6687
language: {
6788
version: "v0.9.0-alpha.0"
6889
}
90+
-- want-module-6 --
91+
module: "othermain.org@v1"
92+
language: {
93+
version: "v0.9.2"
94+
}
95+
-- want-module-7 --
96+
module: "othermain.org@v1"
97+
language: {
98+
version: "$CUE_LANGUAGE_VERSION"
99+
}
100+
-- want-stderr-8 --
101+
invalid argument "v0.4.3" for "--language-version" flag: language version "v0.4.3" is too early for module.cue schema (earliest allowed is v0.8.0-alpha.0)
102+
-- want-stderr-9 --
103+
invalid argument "v2.3.4" for "--language-version" flag: language version "v2.3.4" may not be after current language version $CUE_LANGUAGE_VERSION
104+
-- want-stderr-10 --
105+
invalid resulting module.cue file after edits: cannot parse result: invalid module.cue file: source field is not allowed at this language version; need at least v0.9.0-alpha.0
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Check we can initialize the module with an explicit version.
2+
3+
# Set current version.
4+
exec cue mod init --language-version current foo.example
5+
cmpenv cue.mod/module.cue want-module-1
6+
rm cue.mod
7+
8+
# Set specific version.
9+
exec cue mod init --language-version v0.9.2 foo.example
10+
cmp cue.mod/module.cue want-module-2
11+
rm cue.mod
12+
13+
# Set version earlier than earliest module schema version.
14+
! exec cue mod init --language-version v0.4.3 foo.example
15+
cmp stderr want-stderr-3
16+
rm cue.mod
17+
18+
# Set version too new.
19+
! exec cue mod init --language-version v2.3.4 foo.example
20+
cmp stderr want-stderr-4
21+
rm cue.mod
22+
23+
# Set version that's incompatible with the source field.
24+
! exec cue mod init --language-version v0.8.0 --source self foo.example
25+
cmp stderr want-stderr-5
26+
rm cue.mod
27+
28+
-- want-module-1 --
29+
module: "foo.example"
30+
language: {
31+
version: "$CUE_LANGUAGE_VERSION"
32+
}
33+
-- want-module-2 --
34+
module: "foo.example"
35+
language: {
36+
version: "v0.9.2"
37+
}
38+
-- want-stderr-3 --
39+
language version "v0.4.3" is too early for module.cue schema (earliest allowed is v0.8.0-alpha.0)
40+
-- want-stderr-4 --
41+
language version "v2.3.4" may not be after current language version v0.10.0
42+
-- want-stderr-5 --
43+
cannot parse result: invalid module.cue file: source field is not allowed at this language version; need at least v0.9.0-alpha.0

cmd/cue/cmd/testdata/script/modinit_without_version.txtar

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
# Check that cue mod init is independent of the module version;
22
# even though CUE's current module version will often be a v0 pseudo-version
33
# or a pre-release, we will always use the current language version in init.
4-
env-fill want-module
54
exec cue mod init foo.example
6-
cmp cue.mod/module.cue want-module
5+
cmpenv cue.mod/module.cue want-module
76

87
# cue mod tidy should be a no-op after cue mod init
98
exec cue mod tidy
10-
cmp cue.mod/module.cue want-module
9+
cmpenv cue.mod/module.cue want-module
1110

1211
-- want-module --
1312
module: "foo.example"

mod/modfile/modfile.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ func (f *File) Format() ([]byte, error) {
156156
// before formatting the output.
157157
f1, err := ParseNonStrict(data, "-")
158158
if err != nil {
159-
return nil, fmt.Errorf("cannot round-trip module file: %v", strings.TrimSuffix(errors.Details(err, nil), "\n"))
159+
return nil, fmt.Errorf("cannot parse result: %v", strings.TrimSuffix(errors.Details(err, nil), "\n"))
160160
}
161161
if f.Language != nil && f1.actualSchemaVersion == "v0.0.0" {
162162
// It's not a legacy module file (because the language field is present)
@@ -363,7 +363,7 @@ func FixLegacy(modfile []byte, filename string) (*File, error) {
363363
}
364364
f, err = ParseNonStrict(data, "fixed-"+filename)
365365
if err != nil {
366-
return nil, fmt.Errorf("cannot round-trip fixed module file %q: %v", data, err)
366+
return nil, fmt.Errorf("cannot parse resulting module file %q: %v", data, err)
367367
}
368368
return f, nil
369369
}

mod/modfile/modfile_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,7 @@ language: {
500500
Version: "v0.4.3",
501501
},
502502
},
503-
wantError: `cannot round-trip module file: cannot find schema suitable for reading module file with language version "v0.4.3"`,
503+
wantError: `cannot parse result: cannot find schema suitable for reading module file with language version "v0.4.3"`,
504504
}, {
505505
name: "WithInvalidModuleVersion",
506506
file: &File{
@@ -509,7 +509,7 @@ language: {
509509
Version: "badversion--",
510510
},
511511
},
512-
wantError: `cannot round-trip module file: language version "badversion--" in module.cue is not valid semantic version`,
512+
wantError: `cannot parse result: language version "badversion--" in module.cue is not valid semantic version`,
513513
}, {
514514
name: "WithNonNilEmptyDeps",
515515
file: &File{

0 commit comments

Comments
 (0)