Skip to content

Commit

Permalink
cmd/cue: implement cue mod get
Browse files Browse the repository at this point in the history
This adds the capability to update a module version
or add a new dependency. The functionality
is more limited than that provided by `go get`:

- arguments are modules, not packages
- downgrade functionality is much more limited (cannot
  downgrade when that's in conflict with other requirements)
- no support for arbitrary dependency constraints such as `<v1.2.3`
- only "latest" and version prefixes are supported.

Fixes #2867.

Signed-off-by: Roger Peppe <[email protected]>
Change-Id: I7fc4d8a2f033a48ee0761ae4dbbf5979711f6189
Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1177696
Reviewed-by: Daniel Martí <[email protected]>
TryBot-Result: CUEcueckoo <[email protected]>
  • Loading branch information
rogpeppe committed Mar 4, 2024
1 parent 2dc8879 commit 93643a3
Show file tree
Hide file tree
Showing 5 changed files with 405 additions and 23 deletions.
5 changes: 3 additions & 2 deletions cmd/cue/cmd/mod.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,11 @@ func newModCmd(c *Command) *cobra.Command {
}),
}

cmd.AddCommand(newModGetCmd(c))
cmd.AddCommand(newModInitCmd(c))
cmd.AddCommand(newModUploadCmd(c))
cmd.AddCommand(newModTidyCmd(c))
cmd.AddCommand(newModRegistryCmd(c))
cmd.AddCommand(newModTidyCmd(c))
cmd.AddCommand(newModUploadCmd(c))
return cmd
}

Expand Down
125 changes: 125 additions & 0 deletions cmd/cue/cmd/modget.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright 2024 The CUE Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
"bytes"
"context"
"fmt"
"os"
"path/filepath"

"github.com/spf13/cobra"

"cuelang.org/go/internal/mod/modload"
)

func newModGetCmd(c *Command) *cobra.Command {
cmd := &cobra.Command{
Use: "get",
Short: "add and upgrade module dependencies",
Long: `WARNING: THIS COMMAND IS EXPERIMENTAL.
Get updates module dependencies, fetching new dependencies if
needed and changing versions to specified versions. It can downgrade
a version only when a higher version is not required by other
dependencies.
Each argument specifies a module path and optionally a version
suffix. If there is no version suffix, the latest non-prerelease version
of the module will be requested; alternatively a suffix of "@latest"
also specifies the latest version.
A version suffix can contain a major version only (@v1), a major and minor
version (@v1.2) or full version (@v1.2.3). If minor or patch version is omitted, the
latest non-prerelease version will be chosen that has the same major
and minor versions.
If the desired version cannot be chosen (for example because a
dependency already uses a later version than the desired version),
this command will fail.
See "cue help environment" for details on how $CUE_REGISTRY is used to
determine the modules registry.
Note: you must enable the modules experiment with:
export CUE_EXPERIMENT=modules
for this command to work.
`,
RunE: mkRunE(c, runModGet),
Args: cobra.MinimumNArgs(1),
}

return cmd
}

func runModGet(cmd *Command, args []string) error {
reg, err := getCachedRegistry()
if err != nil {
return err
}
if reg == nil {
return fmt.Errorf("modules experiment not enabled (enable with CUE_EXPERIMENT=modules)")
}
ctx := context.Background()
modRoot, err := findModuleRoot()
if err != nil {
return err
}
mf, err := modload.UpdateVersions(ctx, os.DirFS(modRoot), ".", reg, args)
if err != nil {
return err
}
// TODO check whether it's changed or not.
data, err := mf.Format()
if err != nil {
return fmt.Errorf("internal error: invalid module.cue file generated: %v", err)
}
modPath := filepath.Join(modRoot, "cue.mod", "module.cue")
oldData, err := os.ReadFile(modPath)
if err != nil {
// Shouldn't happen because modload.Load returns an error
// if it can't load the module file.
return err
}
if bytes.Equal(data, oldData) {
return nil
}
if err := os.WriteFile(modPath, data, 0o666); err != nil {
return err
}
return nil
}

func findModuleRoot() (string, error) {
// TODO this logic is duplicated in multiple places. We should
// consider deduplicating it.
dir, err := os.Getwd()
if err != nil {
return "", err
}
for {
if _, err := os.Stat(filepath.Join(dir, "cue.mod")); err == nil {
return dir, nil
} else if !os.IsNotExist(err) {
return "", err
}
dir1 := filepath.Dir(dir)
if dir1 == dir {
return "", fmt.Errorf("module root not found")
}
dir = dir1
}
}
21 changes: 0 additions & 21 deletions cmd/cue/cmd/modtidy.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,24 +92,3 @@ func runModTidy(cmd *Command, args []string) error {
}
return nil
}

func findModuleRoot() (string, error) {
// TODO this logic is duplicated in multiple places. We should
// consider deduplicating it.
dir, err := os.Getwd()
if err != nil {
return "", err
}
for {
if _, err := os.Stat(filepath.Join(dir, "cue.mod")); err == nil {
return dir, nil
} else if !os.IsNotExist(err) {
return "", err
}
dir1 := filepath.Dir(dir)
if dir1 == dir {
return "", fmt.Errorf("module root not found")
}
dir = dir1
}
}
231 changes: 231 additions & 0 deletions cmd/cue/cmd/testdata/script/modget_initial.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
# Check that cue mod get will upgrade an existing
# dependency.
cp cue.mod/module.cue module-original

# cue mod get should update to v0.10.2 but not to the
# latest prerelease v0.11.0-alpha
exec cue mod get baz.org
cmp cue.mod/module.cue want-module-1
exec cue export .
cmp stdout want-stdout-1

# Check that the same thing happens when we use an
# explicit @latest suffix.
cp module-original cue.mod/module.cue
exec cue mod get baz.org@latest
cmp cue.mod/module.cue want-module-1
exec cue export .
cmp stdout want-stdout-1

# We can update to the latest prerelease by providing an
# explicit version.
exec cue mod get [email protected]
cmp cue.mod/module.cue want-module-2
exec cue export .
cmp stdout want-stdout-2

# Check that we can also do the above by specifying
# an explicit v0.11 prefix
cp want-module-1 cue.mod/module.cue
exec cue mod get [email protected]
cmp cue.mod/module.cue want-module-2
exec cue export .
cmp stdout want-stdout-2

# If we try it again without an argument, it'll downgrade.
exec cue mod get baz.org
cmp cue.mod/module.cue want-module-1
exec cue export .
cmp stdout want-stdout-1

# If we try to downgrade when some other dependency
# is reliant on a later version, we'll get an error.
! exec cue mod get [email protected]
stderr 'other requirements prevent changing module baz.org@v0 to version v0.5.0 \(actual selected version: v0.10.1\)'

-- want-module-1 --
module: "main.org@v0"
deps: {
"bar.com@v0": {
v: "v0.5.0"
}
"baz.org@v0": {
v: "v0.10.2"
}
"example.com@v0": {
v: "v0.0.1"
}
"foo.com/bar/hello@v0": {
v: "v0.2.3"
default: true
}
}
-- want-stdout-1 --
{
"main": "main",
"foo.com/bar/hello@v0": "v0.2.3",
"bar.com@v0": "v0.5.0",
"baz.org@v0": "v0.10.2",
"example.com@v0": "v0.0.1"
}
-- want-module-2 --
module: "main.org@v0"
deps: {
"bar.com@v0": {
v: "v0.5.0"
}
"baz.org@v0": {
v: "v0.11.0-alpha"
}
"example.com@v0": {
v: "v0.0.1"
}
"foo.com/bar/hello@v0": {
v: "v0.2.3"
default: true
}
}
-- want-stdout-2 --
{
"main": "main",
"foo.com/bar/hello@v0": "v0.2.3",
"bar.com@v0": "v0.5.0",
"baz.org@v0": "v0.11.0-alpha",
"example.com@v0": "v0.0.1"
}
-- cue.mod/module.cue --
module: "main.org@v0"
deps: {
"bar.com@v0": {
v: "v0.5.0"
}
"baz.org@v0": {
v: "v0.10.1"
}
"example.com@v0": {
v: "v0.0.1"
}
"foo.com/bar/hello@v0": {
v: "v0.2.3"
default: true
}
}

-- main.cue --
package main
import "example.com@v0:main"

main

-- _registry/example.com_v0.0.1/cue.mod/module.cue --
module: "example.com@v0"
deps: {
"foo.com/bar/hello@v0": v: "v0.2.3"
"bar.com@v0": v: "v0.5.0"
}

-- _registry/example.com_v0.0.1/top.cue --
package main

import a "foo.com/bar/hello"
a
main: "main"
"example.com@v0": "v0.0.1"

-- _registry/example.com_v0.1.2/cue.mod/module.cue --
module: "example.com@v0"

-- _registry/example.com_v0.1.2/top.cue --
package main
"example.com@v0": "v0.1.2"

// TODO: import without a major version should
// the major version from the module.cue file.
main: "main"
"example.com@v0": "v0.0.1"

-- _registry/foo.com_bar_hello_v0.2.3/cue.mod/module.cue --
module: "foo.com/bar/hello@v0"
deps: {
"bar.com@v0": v: "v0.0.2"
"baz.org@v0": v: "v0.10.1"
}

-- _registry/foo.com_bar_hello_v0.2.3/x.cue --
package hello
import (
a "bar.com/bar@v0"
b "baz.org@v0:baz"
)
"foo.com/bar/hello@v0": "v0.2.3"
a
b


-- _registry/bar.com_v0.0.2/cue.mod/module.cue --
module: "bar.com@v0"
deps: "baz.org@v0": v: "v0.0.2"

-- _registry/bar.com_v0.0.2/bar/x.cue --
package bar
import a "baz.org@v0:baz"
"bar.com@v0": "v0.0.2"
a


-- _registry/bar.com_v0.5.0/cue.mod/module.cue --
module: "bar.com@v0"
deps: "baz.org@v0": v: "v0.5.0"

-- _registry/bar.com_v0.5.0/bar/x.cue --
package bar
import a "baz.org@v0:baz"
"bar.com@v0": "v0.5.0"
a


-- _registry/baz.org_v0.0.2/cue.mod/module.cue --
module: "baz.org@v0"

-- _registry/baz.org_v0.0.2/baz.cue --
package baz
"baz.org@v0": "v0.0.2"


-- _registry/baz.org_v0.1.2/cue.mod/module.cue --
module: "baz.org@v0"

-- _registry/baz.org_v0.1.2/baz.cue --
package baz
"baz.org@v0": "v0.1.2"


-- _registry/baz.org_v0.5.0/cue.mod/module.cue --
module: "baz.org@v0"

-- _registry/baz.org_v0.5.0/baz.cue --
package baz
"baz.org@v0": "v0.5.0"


-- _registry/baz.org_v0.10.1/cue.mod/module.cue --
module: "baz.org@v0"

-- _registry/baz.org_v0.10.1/baz.cue --
package baz
"baz.org@v0": "v0.10.1"


-- _registry/baz.org_v0.10.2/cue.mod/module.cue --
module: "baz.org@v0"

-- _registry/baz.org_v0.10.2/baz.cue --
package baz
"baz.org@v0": "v0.10.2"

-- _registry/baz.org_v0.11.0-alpha/cue.mod/module.cue --
module: "baz.org@v0"

-- _registry/baz.org_v0.11.0-alpha/baz.cue --
package baz
"baz.org@v0": "v0.11.0-alpha"
Loading

0 comments on commit 93643a3

Please sign in to comment.