Skip to content

Commit

Permalink
cmd/cue: implement --language-version flag for cue mod edit and init
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
rogpeppe committed Jul 2, 2024
1 parent 71eb3be commit e9b2c29
Show file tree
Hide file tree
Showing 8 changed files with 166 additions and 44 deletions.
65 changes: 33 additions & 32 deletions cmd/cue/cmd/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,38 +20,39 @@ import (

// Common flags
const (
flagAll flagName = "all"
flagAllErrors flagName = "all-errors"
flagCheck flagName = "check"
flagDiff flagName = "diff"
flagDryRun flagName = "dry-run"
flagEscape flagName = "escape"
flagExpression flagName = "expression"
flagExt flagName = "ext"
flagFiles flagName = "files"
flagForce flagName = "force"
flagGlob flagName = "name"
flagIgnore flagName = "ignore"
flagInject flagName = "inject"
flagInjectVars flagName = "inject-vars"
flagInlineImports flagName = "inline-imports"
flagJSON flagName = "json"
flagList flagName = "list"
flagMerge flagName = "merge"
flagOut flagName = "out"
flagOutFile flagName = "outfile"
flagPackage flagName = "package"
flagPath flagName = "path"
flagProtoEnum flagName = "proto_enum"
flagProtoPath flagName = "proto_path"
flagRecursive flagName = "recursive"
flagSchema flagName = "schema"
flagSimplify flagName = "simplify"
flagSource flagName = "source"
flagStrict flagName = "strict"
flagTrace flagName = "trace"
flagVerbose flagName = "verbose"
flagWithContext flagName = "with-context"
flagAll flagName = "all"
flagAllErrors flagName = "all-errors"
flagCheck flagName = "check"
flagDiff flagName = "diff"
flagDryRun flagName = "dry-run"
flagEscape flagName = "escape"
flagExpression flagName = "expression"
flagExt flagName = "ext"
flagFiles flagName = "files"
flagForce flagName = "force"
flagGlob flagName = "name"
flagIgnore flagName = "ignore"
flagInject flagName = "inject"
flagInjectVars flagName = "inject-vars"
flagInlineImports flagName = "inline-imports"
flagJSON flagName = "json"
flagLanguageVersion flagName = "language-version"
flagList flagName = "list"
flagMerge flagName = "merge"
flagOut flagName = "out"
flagOutFile flagName = "outfile"
flagPackage flagName = "package"
flagPath flagName = "path"
flagProtoEnum flagName = "proto_enum"
flagProtoPath flagName = "proto_path"
flagRecursive flagName = "recursive"
flagSchema flagName = "schema"
flagSimplify flagName = "simplify"
flagSource flagName = "source"
flagStrict flagName = "strict"
flagTrace flagName = "trace"
flagVerbose flagName = "verbose"
flagWithContext flagName = "with-context"

// Hidden flags.
flagCpuProfile flagName = "cpuprofile"
Expand Down
42 changes: 40 additions & 2 deletions cmd/cue/cmd/modedit.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (
"path/filepath"
"strconv"

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

func (c *modEditCmd) flagLanguageVersion(arg string) error {
editFunc, err := addLanguageVersion(arg)
if err != nil {
return err
}
c.addEdit(editFunc)
return nil
}

func addLanguageVersion(v string) (func(*modfile.File) error, error) {
if v == "current" {
v = cueversion.LanguageVersion()
} else {
if semver.Canonical(v) != v {
return nil, fmt.Errorf("language version %q is not canonical (must include major, minor and patch versions)", v)
}

if min := modfile.EarliestClosedSchemaVersion(); semver.Compare(v, min) < 0 {
// TODO(rogpeppe) We might want to relax this to allow people to
// declare an earlier language version (see https://cuelang.org/issue/3145).
return nil, fmt.Errorf("language version %q is too early for module.cue schema (earliest allowed is %s)", v, min)
}
if max := cueversion.LanguageVersion(); semver.Compare(v, max) > 0 {
return nil, fmt.Errorf("language version %q may not be after current language version %s", v, max)
}
}
return func(f *modfile.File) error {
if f.Language == nil {
f.Language = &modfile.Language{}
}
f.Language.Version = v
return nil
}, nil
}

func (c *modEditCmd) flagModule(arg string) error {
if err := module.CheckPath(arg); err != nil {
return err
Expand Down
10 changes: 7 additions & 3 deletions cmd/cue/cmd/modinit.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (

"github.com/spf13/cobra"

"cuelang.org/go/internal/cueversion"
"cuelang.org/go/mod/modfile"
"cuelang.org/go/mod/module"
)
Expand All @@ -45,6 +44,7 @@ in the module.

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

Expand Down Expand Up @@ -84,8 +84,12 @@ func runModInit(cmd *Command, args []string) (err error) {
return err
}
}
mf.Language = &modfile.Language{
Version: cueversion.LanguageVersion(),
editFunc, err := addLanguageVersion(flagLanguageVersion.String(cmd))
if err != nil {
return err
}
if err := editFunc(mf); err != nil {
return err
}

err = os.Mkdir(mod, 0755)
Expand Down
37 changes: 37 additions & 0 deletions cmd/cue/cmd/testdata/script/modedit_initial.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,27 @@ cmp cue.mod/module.cue want-module-4
exec cue mod edit --module othermain.org@v1
cmp cue.mod/module.cue want-module-5

# Set specific version.
exec cue mod edit --language-version v0.9.2
cmp cue.mod/module.cue want-module-6

# Set latest version.
exec cue mod edit --language-version current
cmpenv cue.mod/module.cue want-module-7

# Set version earlier than earliest module schema version.
! exec cue mod edit --language-version v0.4.3
cmp stderr want-stderr-8

# Set version too new.
! exec cue mod edit --language-version v2.3.4
cmpenv stderr want-stderr-9

# Check that it's an error to set the version earlier than
# allowed by some of the fields already present.
exec cue mod edit --source self
! exec cue mod edit --language-version v0.8.0
cmp stderr want-stderr-10

-- cue.mod/module.cue --
module: "main.org@v0"
Expand Down Expand Up @@ -66,3 +87,19 @@ module: "othermain.org@v1"
language: {
version: "v0.9.0-alpha.0"
}
-- want-module-6 --
module: "othermain.org@v1"
language: {
version: "v0.9.2"
}
-- want-module-7 --
module: "othermain.org@v1"
language: {
version: "$CUE_LANGUAGE_VERSION"
}
-- want-stderr-8 --
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)
-- want-stderr-9 --
invalid argument "v2.3.4" for "--language-version" flag: language version "v2.3.4" may not be after current language version $CUE_LANGUAGE_VERSION
-- want-stderr-10 --
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
43 changes: 43 additions & 0 deletions cmd/cue/cmd/testdata/script/modinit_with_explicit_version.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Check we can initialize the module with an explicit version.

# Set current version.
exec cue mod init --language-version current foo.example
cmpenv cue.mod/module.cue want-module-1
rm cue.mod

# Set specific version.
exec cue mod init --language-version v0.9.2 foo.example
cmp cue.mod/module.cue want-module-2
rm cue.mod

# Set version earlier than earliest module schema version.
! exec cue mod init --language-version v0.4.3 foo.example
cmp stderr want-stderr-3
rm cue.mod

# Set version too new.
! exec cue mod init --language-version v2.3.4 foo.example
cmp stderr want-stderr-4
rm cue.mod

# Set version that's incompatible with the source field.
! exec cue mod init --language-version v0.8.0 --source self foo.example
cmp stderr want-stderr-5
rm cue.mod

-- want-module-1 --
module: "foo.example"
language: {
version: "$CUE_LANGUAGE_VERSION"
}
-- want-module-2 --
module: "foo.example"
language: {
version: "v0.9.2"
}
-- want-stderr-3 --
language version "v0.4.3" is too early for module.cue schema (earliest allowed is v0.8.0-alpha.0)
-- want-stderr-4 --
language version "v2.3.4" may not be after current language version v0.10.0
-- want-stderr-5 --
cannot parse result: invalid module.cue file: source field is not allowed at this language version; need at least v0.9.0-alpha.0
5 changes: 2 additions & 3 deletions cmd/cue/cmd/testdata/script/modinit_without_version.txtar
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
# Check that cue mod init is independent of the module version;
# even though CUE's current module version will often be a v0 pseudo-version
# or a pre-release, we will always use the current language version in init.
env-fill want-module
exec cue mod init foo.example
cmp cue.mod/module.cue want-module
cmpenv cue.mod/module.cue want-module

# cue mod tidy should be a no-op after cue mod init
exec cue mod tidy
cmp cue.mod/module.cue want-module
cmpenv cue.mod/module.cue want-module

-- want-module --
module: "foo.example"
Expand Down
4 changes: 2 additions & 2 deletions mod/modfile/modfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ func (f *File) Format() ([]byte, error) {
// before formatting the output.
f1, err := ParseNonStrict(data, "-")
if err != nil {
return nil, fmt.Errorf("cannot round-trip module file: %v", strings.TrimSuffix(errors.Details(err, nil), "\n"))
return nil, fmt.Errorf("cannot parse result: %v", strings.TrimSuffix(errors.Details(err, nil), "\n"))
}
if f.Language != nil && f1.actualSchemaVersion == "v0.0.0" {
// It's not a legacy module file (because the language field is present)
Expand Down Expand Up @@ -363,7 +363,7 @@ func FixLegacy(modfile []byte, filename string) (*File, error) {
}
f, err = ParseNonStrict(data, "fixed-"+filename)
if err != nil {
return nil, fmt.Errorf("cannot round-trip fixed module file %q: %v", data, err)
return nil, fmt.Errorf("cannot parse resulting module file %q: %v", data, err)
}
return f, nil
}
Expand Down
4 changes: 2 additions & 2 deletions mod/modfile/modfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,7 @@ language: {
Version: "v0.4.3",
},
},
wantError: `cannot round-trip module file: cannot find schema suitable for reading module file with language version "v0.4.3"`,
wantError: `cannot parse result: cannot find schema suitable for reading module file with language version "v0.4.3"`,
}, {
name: "WithInvalidModuleVersion",
file: &File{
Expand All @@ -509,7 +509,7 @@ language: {
Version: "badversion--",
},
},
wantError: `cannot round-trip module file: language version "badversion--" in module.cue is not valid semantic version`,
wantError: `cannot parse result: language version "badversion--" in module.cue is not valid semantic version`,
}, {
name: "WithNonNilEmptyDeps",
file: &File{
Expand Down

0 comments on commit e9b2c29

Please sign in to comment.