diff --git a/cmd/cue/cmd/flags.go b/cmd/cue/cmd/flags.go index 1c869c87e4a..8a11af2ceb9 100644 --- a/cmd/cue/cmd/flags.go +++ b/cmd/cue/cmd/flags.go @@ -25,6 +25,7 @@ const ( flagAll flagName = "all" flagAllErrors flagName = "all-errors" flagAllMajor flagName = "all-major" + flagAllVersions flagName = "all-versions" flagCheck flagName = "check" flagDiff flagName = "diff" flagDryRun flagName = "dry-run" @@ -34,6 +35,7 @@ const ( flagExt flagName = "ext" flagFiles flagName = "files" flagForce flagName = "force" + flagFrom flagName = "from" flagGlob flagName = "name" flagIdent flagName = "ident" flagIgnore flagName = "ignore" @@ -44,6 +46,8 @@ const ( flagLanguageVersion flagName = "language-version" flagList flagName = "list" flagMerge flagName = "merge" + flagMod flagName = "mod" + flagNoDeps flagName = "no-deps" flagOut flagName = "out" flagOutFile flagName = "outfile" flagPackage flagName = "package" @@ -55,6 +59,7 @@ const ( flagSimplify flagName = "simplify" flagSource flagName = "source" flagStrict flagName = "strict" + flagTo flagName = "to" flagTrace flagName = "trace" flagUpdateIdent flagName = "update-ident" flagVerbose flagName = "verbose" diff --git a/cmd/cue/cmd/mod.go b/cmd/cue/cmd/mod.go index ab53c7a657f..72fa7447228 100644 --- a/cmd/cue/cmd/mod.go +++ b/cmd/cue/cmd/mod.go @@ -48,6 +48,7 @@ See also: cmd.AddCommand(newModFixCmd(c)) cmd.AddCommand(newModGetCmd(c)) cmd.AddCommand(newModInitCmd(c)) + cmd.AddCommand(newModMirrorCmd(c)) cmd.AddCommand(newModRegistryCmd(c)) cmd.AddCommand(newModRenameCmd(c)) cmd.AddCommand(newModResolveCmd(c)) diff --git a/cmd/cue/cmd/modmirror.go b/cmd/cue/cmd/modmirror.go new file mode 100644 index 00000000000..89da9ce0d3f --- /dev/null +++ b/cmd/cue/cmd/modmirror.go @@ -0,0 +1,241 @@ +// Copyright 2025 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 ( + "context" + "fmt" + + "github.com/spf13/cobra" + + "cuelang.org/go/internal/mod/modload" + "cuelang.org/go/internal/mod/semver" + "cuelang.org/go/mod/modconfig" + "cuelang.org/go/mod/modfile" + "cuelang.org/go/mod/modregistry" + "cuelang.org/go/mod/module" +) + +func newModMirrorCmd(c *Command) *cobra.Command { + cmd := &cobra.Command{ + Use: "mirror [module...]", + Short: "mirror module content between registries", + Long: `WARNING: THIS COMMAND IS EXPERIMENTAL. + +This commmand ensures that a set of modules and their dependencies +are available ("mirrored") in a registry. + +For each module specified on the command line, it ensures that the +module and all the modules in depends on +are present in both the "from" registry and the "to" registry and that the +contents are the same in each. If the --no-deps is specified, only +the module itself but not its dependencies will be mirrored. + +A module may be specified as @, in which case the +specified version will be mirrored. If the version is canonical (for example v1.2.3), then +exactly that version will be mirrored, otherwise the latest corresponding +version will be mirrored (or all corresponding versions if --all-versions +is specified). + +For example: + + # Copy from $CUE_REGISTRY (usually the Central Registry) to my.registry.example + cue mod mirror --to my.registry.example foo.com/m1@v1.2.3 bar.org@v2 + +will copy the exact module foo.com/m1@v1.2.3 but the latest version +of bar.org@2, or all v2.x.y versions if --all-versions is given. +If no major version is specified, the latest major version will be chosen. + +By default the latest version is chosen by consulting the source registry, +unless the --mod flag is specified, in which case the current module's +dependencies will be used. When --mod is given and no modules +are specified on the command line, all the current module's dependencies will +be mirrored. + +Note that this command is not yet stable and may be changed. +`, + RunE: mkRunE(c, runModMirror), + } + cmd.Flags().BoolP(string(flagDryRun), "n", false, "only run simulation") + cmd.Flags().Bool(string(flagNoDeps), false, "do not copy module dependencies") + cmd.Flags().String(string(flagFrom), "", "source registry (defaults to $CUE_REGISTRY)") + cmd.Flags().String(string(flagTo), "", "destination registry (defaults to $CUE_REGISTRY)") + cmd.Flags().BoolP(string(flagAllVersions), "a", false, "copy all available versions of the specified modules") + cmd.Flags().BoolP(string(flagMod), "m", false, "mirror the current main module's dependency modules by default") + + return cmd +} + +func runModMirror(cmd *Command, args []string) error { + ctx := cmd.Context() + //dryRun := flagDryRun.Bool(cmd) // TODO + noDeps := flagNoDeps.Bool(cmd) + srcRegStr := flagFrom.String(cmd) + dstRegStr := flagTo.String(cmd) + allVersions := flagAllVersions.Bool(cmd) + useMod := flagMod.Bool(cmd) + + // TODO configure concurrency limit? + + srcResolver, err := modconfig.NewResolver(newModConfig(srcRegStr)) + if err != nil { + return err + } + srcReg := modregistry.NewClientWithResolver(srcResolver) + + dstResolver, err := modconfig.NewResolver(newModConfig(dstRegStr)) + if err != nil { + return err + } + dstReg := modregistry.NewClientWithResolver(dstResolver) + + var mf *modfile.File + if useMod { + // Read current module to get dependencies. + var err error + _, mf, _, err = readModuleFile() + if err != nil { + return err + } + } + // If no modules specified, possibly mirror from the current module if --mod is set. + modules := args + if len(modules) == 0 && useMod { + deps := mf.DepVersions() + modules = make([]string, 0, len(deps)) + for _, dv := range deps { + if allVersions { + modules = append(modules, dv.BasePath()) + } else { + modules = append(modules, dv.String()) + } + } + } + if len(modules) == 0 { + // Nothing to do. + return nil + } + + // First expand the module list to list specific versions of all the + // initial modules to copy. + // TODO concurrency + var expanded []module.Version + for _, m := range modules { + mpath, mvers, ok := module.SplitPathVersion(m) + if !ok || semver.Canonical(mvers) != mvers { + if useMod { + // Resolve the version from the module file. + mv, ok := mf.ModuleForImportPath(mpath) + if !ok { + return fmt.Errorf("no version for %q found in module file", mpath) + } + expanded = append(expanded, mv) + continue + } + versions, err := srcReg.ModuleVersions(ctx, m) + if err != nil { + return err + } + if len(versions) == 0 { + return fmt.Errorf("no versions found for module %v", m) + } + if allVersions { + for _, v := range versions { + mv, err := module.NewVersion(mpath, v) + if err != nil { + return err + } + expanded = append(expanded, mv) + } + } else { + mv, err := module.NewVersion(mpath, modload.LatestVersion(versions)) + if err != nil { + return err + } + expanded = append(expanded, mv) + } + } else { + mv, err := module.ParseVersion(m) + if err != nil { + return err + } + expanded = append(expanded, mv) + } + } + + // Now copy the modules and their dependencies recursively, depth-first. + mm := &modMirror{ + allVersions: allVersions, + srcReg: srcReg, + dstReg: dstReg, + noDeps: noDeps, + done: make(map[module.Version]bool), + } + // TODO concurrency + for _, m := range expanded { + if err := mm.mirrorWithDeps(ctx, m); err != nil { + return err + } + } + return nil +} + +type modMirror struct { + allVersions bool + srcReg *modregistry.Client + dstReg *modregistry.Client + done map[module.Version]bool + noDeps bool +} + +func (mm *modMirror) mirrorWithDeps(ctx context.Context, mv module.Version) error { + mm.done[mv] = true + m, err := mm.srcReg.GetModule(ctx, mv) + if err != nil { + return err + } + modFileData, err := m.ModuleFile(ctx) + if err != nil { + return err + } + mf, err := modfile.Parse(modFileData, mv.String()+"/cue.mod/module.cue") + if err != nil { + return err + } + if !mm.noDeps { + // TODO technically this can copy more than is strictly necessary + // when we're operating in module mode, because the main + // module will only require the latest version of any of its dependencies, + // but those individual dependencies may themselves require + // earlier versions of those modules. + // It's safer to do things this way as it means that we're guaranteed + // that every individual module in the target registry has all its + // dependencies present, but there could be room for a mode that + // does a more parsimonious copy. + for _, dep := range mf.DepVersions() { + if mm.done[dep] { + continue + } + if err := mm.mirrorWithDeps(ctx, dep); err != nil { + return err + } + } + } + fmt.Printf("mirroring %v\n", mv) + if err := mm.srcReg.Mirror(ctx, mm.dstReg, mv); err != nil { + return err + } + return nil +} diff --git a/cmd/cue/cmd/registry.go b/cmd/cue/cmd/registry.go index 23ed382cda0..e7423f7f923 100644 --- a/cmd/cue/cmd/registry.go +++ b/cmd/cue/cmd/registry.go @@ -14,17 +14,18 @@ import ( // getRegistryResolver returns an implementation of [modregistry.Resolver] // that resolves to registries as specified in the configuration. func getRegistryResolver() (*modconfig.Resolver, error) { - return modconfig.NewResolver(newModConfig()) + return modconfig.NewResolver(newModConfig("")) } func getCachedRegistry() (modload.Registry, error) { - return modconfig.NewRegistry(newModConfig()) + return modconfig.NewRegistry(newModConfig("")) } -func newModConfig() *modconfig.Config { +func newModConfig(registry string) *modconfig.Config { return &modconfig.Config{ - Transport: httpTransport(), - ClientType: "cmd/cue", + Transport: httpTransport(), + ClientType: "cmd/cue", + CUERegistry: registry, } } diff --git a/cmd/cue/cmd/testdata/script/modmirror_initial.txtar b/cmd/cue/cmd/testdata/script/modmirror_initial.txtar new file mode 100644 index 00000000000..1e4caea418f --- /dev/null +++ b/cmd/cue/cmd/testdata/script/modmirror_initial.txtar @@ -0,0 +1,167 @@ +memregistry MEMREGISTRY + +env oldRegistry=$CUE_REGISTRY +exec cue mod mirror --to $MEMREGISTRY example.com +cmp stdout want-mirror1-stdout + +# Check that we can use the mirrored content. +cd example +env CUE_REGISTRY=$MEMREGISTRY +exec cue mod tidy +exec cue export +cmp stdout $WORK/want-export-stdout + +# Try again with all versions. +exec cue mod mirror --to $MEMREGISTRY --from $oldRegistry --all-versions example.com +cmp stdout $WORK/want-mirror2-stdout + +-- want-mirror1-stdout -- +mirroring baz.org@v0.5.0 +mirroring bar.com@v0.5.0 +mirroring baz.org@v0.0.2 +mirroring bar.com@v0.0.2 +mirroring baz.org@v0.10.1 +mirroring foo.com/bar/hello@v0.2.3 +mirroring example.com@v0.0.1 +-- want-mirror2-stdout -- +mirroring foo.com/bar/hello@v0.2.10 +mirroring example.com@v0.0.0 +mirroring baz.org@v0.5.0 +mirroring bar.com@v0.5.0 +mirroring baz.org@v0.0.2 +mirroring bar.com@v0.0.2 +mirroring baz.org@v0.10.1 +mirroring foo.com/bar/hello@v0.2.3 +mirroring example.com@v0.0.1 +-- want-export-stdout -- +{ + "foo.com/bar/hello@v0": "v0.2.3", + "bar.com@v0": "v0.5.0", + "baz.org@v0": "v0.10.1", + "main": "main", + "example.com@v0": "v0.0.1" +} +-- example/cue.mod/module.cue -- +module: "main.org" +language: version: "v0.8.0" + +-- example/main.cue -- +package main +import "example.com@v0:main" + +main +-- _registry/example.com_v0.0.0/cue.mod/module.cue -- +module: "example.com@v0" +language: version: "v0.8.0" +deps: { + "foo.com/bar/hello@v0": v: "v0.2.10" +} + +-- _registry/example.com_v0.0.0/top.cue -- +package main + +"example.com@v0": "v0.0.0" + +-- _registry/example.com_v0.0.1/cue.mod/module.cue -- +module: "example.com@v0" +language: version: "v0.8.0" +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/unused.com_v0.2.4/cue.mod/module.cue -- +module: "unused.com@v0" +language: version: "v0.8.0" + +-- _registry/foo.com_bar_hello_v0.2.3/cue.mod/module.cue -- +module: "foo.com/bar/hello@v0" +language: version: "v0.8.0" +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/foo.com_bar_hello_v0.2.10/cue.mod/module.cue -- +module: "foo.com/bar/hello@v0" +language: version: "v0.8.0" + +-- _registry/foo.com_bar_hello_v0.2.10/x.cue -- +package hello + +-- _registry/bar.com_v0.0.2/cue.mod/module.cue -- +module: "bar.com@v0" +language: version: "v0.8.0" +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" +language: version: "v0.8.0" +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" +language: version: "v0.8.0" + +-- _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" +language: version: "v0.8.0" + +-- _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" +language: version: "v0.8.0" + +-- _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" +language: version: "v0.8.0" + +-- _registry/baz.org_v0.10.1/baz.cue -- +package baz +"baz.org@v0": "v0.10.1" diff --git a/cmd/cue/cmd/testdata/script/modmirror_modmode.txtar b/cmd/cue/cmd/testdata/script/modmirror_modmode.txtar new file mode 100644 index 00000000000..39371f9524d --- /dev/null +++ b/cmd/cue/cmd/testdata/script/modmirror_modmode.txtar @@ -0,0 +1,164 @@ +memregistry MEMREGISTRY + +cd example +exec cue mod mirror -m --to $MEMREGISTRY example.com +cmp stdout $WORK/want-mirror1-stdout + +# Check that we can use the mirrored content. +env CUE_REGISTRY=$MEMREGISTRY +exec cue mod tidy +exec cue export +cmp stdout $WORK/want-export-stdout + +# Check that if we pass no arguments in --mod mode that +# it copies the dependencies of the current module. +memregistry MEMREGISTRY2 +exec cue mod mirror --to $MEMREGISTRY2 --mod +cmp stdout $WORK/want-mirror2-stdout + +-- want-mirror1-stdout -- +mirroring baz.org@v0.5.0 +mirroring bar.com@v0.5.0 +mirroring baz.org@v0.0.2 +mirroring bar.com@v0.0.2 +mirroring baz.org@v0.10.1 +mirroring foo.com/bar/hello@v0.2.3 +mirroring example.com@v0.0.1 +-- want-mirror2-stdout -- +mirroring baz.org@v0.5.0 +mirroring bar.com@v0.5.0 +mirroring baz.org@v0.10.1 +mirroring baz.org@v0.0.2 +mirroring bar.com@v0.0.2 +mirroring foo.com/bar/hello@v0.2.3 +mirroring example.com@v0.0.1 +mirroring foo.com/bar/hello@v0.2.3 +-- want-export-stdout -- +{ + "foo.com/bar/hello@v0": "v0.2.3", + "bar.com@v0": "v0.5.0", + "baz.org@v0": "v0.10.1", + "main": "main", + "example.com@v0": "v0.0.1" +} +-- example/cue.mod/module.cue -- +module: "main.org" +language: version: "v0.8.0" + +// We're depending on a version that's earlier +// then the latest published version. +deps: "example.com@v0": { + default: true + v: "v0.0.1" +} +-- example/main.cue -- +package main +import "example.com@v0:main" + +main +-- _registry/example.com_v0.0.2/cue.mod/module.cue -- +module: "example.com@v0" +language: version: "v0.8.0" + +-- _registry/example.com_v0.0.2/top.cue -- +package main + +"example.com@v0": "v0.0.2" + +-- _registry/example.com_v0.0.1/cue.mod/module.cue -- +module: "example.com@v0" +language: version: "v0.8.0" +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/unused.com_v0.2.4/cue.mod/module.cue -- +module: "unused.com@v0" +language: version: "v0.8.0" + +-- _registry/foo.com_bar_hello_v0.2.3/cue.mod/module.cue -- +module: "foo.com/bar/hello@v0" +language: version: "v0.8.0" +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" +language: version: "v0.8.0" +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" +language: version: "v0.8.0" +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" +language: version: "v0.8.0" + +-- _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" +language: version: "v0.8.0" + +-- _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" +language: version: "v0.8.0" + +-- _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" +language: version: "v0.8.0" + +-- _registry/baz.org_v0.10.1/baz.cue -- +package baz +"baz.org@v0": "v0.10.1" diff --git a/cmd/cue/cmd/testdata/script/modmirror_nodeps.txtar b/cmd/cue/cmd/testdata/script/modmirror_nodeps.txtar new file mode 100644 index 00000000000..553f9a8f7d4 --- /dev/null +++ b/cmd/cue/cmd/testdata/script/modmirror_nodeps.txtar @@ -0,0 +1,137 @@ +env oldRegistry=$CUE_REGISTRY +memregistry MEMREGISTRY + +exec cue mod mirror --no-deps --to $MEMREGISTRY example.com +cmp stdout want-mirror-stdout + +# Check that using the registry on its own does not work +# because the deps aren't there. +cd example +env CUE_REGISTRY=$MEMREGISTRY +! exec cue mod tidy +cmp stderr $WORK/want-tidy-stderr + +# Use a combined registry and check that works. +env CUE_REGISTRY=example.com=$MEMREGISTRY,$oldRegistry +exec cue mod tidy +exec cue export +cmp stdout $WORK/want-export-stdout + +-- want-mirror-stdout -- +mirroring example.com@v0.0.1 +-- want-tidy-stderr -- +failed to resolve "foo.com/bar/hello": cannot find module providing package foo.com/bar/hello +-- want-export-stdout -- +{ + "foo.com/bar/hello@v0": "v0.2.3", + "bar.com@v0": "v0.5.0", + "baz.org@v0": "v0.10.1", + "main": "main", + "example.com@v0": "v0.0.1" +} +-- example/cue.mod/module.cue -- +module: "main.org" +language: version: "v0.8.0" + +-- example/main.cue -- +package main +import "example.com@v0:main" + +main +-- _registry/example.com_v0.0.1/cue.mod/module.cue -- +module: "example.com@v0" +language: version: "v0.8.0" +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/unused.com_v0.2.4/cue.mod/module.cue -- +module: "unused.com@v0" +language: version: "v0.8.0" + +-- _registry/foo.com_bar_hello_v0.2.3/cue.mod/module.cue -- +module: "foo.com/bar/hello@v0" +language: version: "v0.8.0" +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" +language: version: "v0.8.0" +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" +language: version: "v0.8.0" +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" +language: version: "v0.8.0" + +-- _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" +language: version: "v0.8.0" + +-- _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" +language: version: "v0.8.0" + +-- _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" +language: version: "v0.8.0" + +-- _registry/baz.org_v0.10.1/baz.cue -- +package baz +"baz.org@v0": "v0.10.1" diff --git a/cmd/cue/cmd/testdata/script/registry_invalid_env.txtar b/cmd/cue/cmd/testdata/script/registry_invalid_env.txtar index 116174e3d49..5e37fc11990 100644 --- a/cmd/cue/cmd/testdata/script/registry_invalid_env.txtar +++ b/cmd/cue/cmd/testdata/script/registry_invalid_env.txtar @@ -3,7 +3,7 @@ env CUE_REGISTRY=malformed!registry@url cmp stderr expect-stderr -- expect-stderr -- -bad value for $CUE_REGISTRY: invalid registry "malformed!registry@url": invalid host name "malformed!registry@url" in registry +bad value for registry: invalid registry "malformed!registry@url": invalid host name "malformed!registry@url" in registry -- main.cue -- package main import "example.com/e" diff --git a/cue/load/module_test.go b/cue/load/module_test.go index dc1177335d8..05f3d489925 100644 --- a/cue/load/module_test.go +++ b/cue/load/module_test.go @@ -53,7 +53,7 @@ func TestModuleLoadWithInvalidRegistryConfig(t *testing.T) { } } insts = load.Instances([]string{"."}, cfg) - qt.Assert(t, qt.ErrorMatches(insts[0].Err, `import failed: .*main.cue:2:8: cannot find package "example.com@v0": cannot fetch example.com@v0.0.1: bad value for \$CUE_REGISTRY: invalid registry "invalid}host:": invalid host name "invalid}host:" in registry`)) + qt.Assert(t, qt.ErrorMatches(insts[0].Err, `import failed: .*main.cue:2:8: cannot find package "example.com@v0": cannot fetch example.com@v0.0.1: bad value for registry: invalid registry "invalid}host:": invalid host name "invalid}host:" in registry`)) // Try again with environment variables passed in Env. // This is really just a smoke test to make sure that Env is @@ -63,7 +63,7 @@ func TestModuleLoadWithInvalidRegistryConfig(t *testing.T) { "CUE_CACHE_DIR=" + cacheDir, } insts = load.Instances([]string{"."}, cfg) - qt.Assert(t, qt.ErrorMatches(insts[0].Err, `import failed: .*main.cue:2:8: cannot find package "example.com@v0": cannot fetch example.com@v0.0.1: bad value for \$CUE_REGISTRY: invalid registry "invalid}host2:": invalid host name "invalid}host2:" in registry`)) + qt.Assert(t, qt.ErrorMatches(insts[0].Err, `import failed: .*main.cue:2:8: cannot find package "example.com@v0": cannot fetch example.com@v0.0.1: bad value for registry: invalid registry "invalid}host2:": invalid host name "invalid}host2:" in registry`)) } func TestModuleFetch(t *testing.T) { diff --git a/internal/mod/modload/query.go b/internal/mod/modload/query.go index a5bafa8ce50..60ad09fa851 100644 --- a/internal/mod/modload/query.go +++ b/internal/mod/modload/query.go @@ -83,7 +83,7 @@ func (ld *loader) queryLatestModules(ctx context.Context, pkgPath string, rs *mo return module.Version{}, err } logf("-> %q", versions) - if v := latestVersion(versions); v != "" { + if v := LatestVersion(versions); v != "" { return module.NewVersion(prefix, v) } return module.Version{}, nil @@ -115,9 +115,9 @@ func (ld *loader) queryLatestModules(ctx context.Context, pkgPath string, rs *mo return candidates, parts.Version == "", queryErr } -// latestVersion returns the latest of any of the given versions, +// LatestVersion returns the latest of any of the given versions, // ignoring prerelease versions if there is any stable version. -func latestVersion(versions []string) string { +func LatestVersion(versions []string) string { maxStable := "" maxAny := "" for _, v := range versions { diff --git a/internal/mod/modload/update.go b/internal/mod/modload/update.go index 5bc585cb271..fce72bb6aad 100644 --- a/internal/mod/modload/update.go +++ b/internal/mod/modload/update.go @@ -155,7 +155,7 @@ func resolveUpdateVersions(ctx context.Context, reg Registry, rs *modrequirement setError(fmt.Errorf("no versions found for module %s", v)) return } - chosen := latestVersion(possibleVersions) + chosen := LatestVersion(possibleVersions) mv, err := module.NewVersion(mpath, chosen) if err != nil { // Should never happen, because we've checked that diff --git a/mod/modconfig/modconfig.go b/mod/modconfig/modconfig.go index 01102451a28..e0699669ea7 100644 --- a/mod/modconfig/modconfig.go +++ b/mod/modconfig/modconfig.go @@ -74,6 +74,12 @@ type Config struct { // the current process's environment will be used. Env []string + // CUERegistry specifies the registry or registries to use + // to resolve modules. If it is empty, $CUE_REGISTRY + // is used. + // Experimental: this field might go away in a future version. + CUERegistry string + // ClientType is used as part of the User-Agent header // that's added in each outgoing HTTP request. // If it's empty, it defaults to "cuelang.org/go". @@ -94,7 +100,10 @@ func NewResolver(cfg *Config) (*Resolver, error) { getenv := getenvFunc(cfg.Env) var configData []byte var configPath string - cueRegistry := getenv("CUE_REGISTRY") + cueRegistry := cfg.CUERegistry + if cueRegistry == "" { + cueRegistry = getenv("CUE_REGISTRY") + } kind, rest, _ := strings.Cut(cueRegistry, ":") switch kind { case "file": @@ -104,7 +113,7 @@ func NewResolver(cfg *Config) (*Resolver, error) { } configData, configPath = data, rest case "inline": - configData, configPath = []byte(rest), "$CUE_REGISTRY" + configData, configPath = []byte(rest), "inline" case "simple": cueRegistry = rest } @@ -116,7 +125,7 @@ func NewResolver(cfg *Config) (*Resolver, error) { resolver, err = modresolve.ParseCUERegistry(cueRegistry, DefaultRegistry) } if err != nil { - return nil, fmt.Errorf("bad value for $CUE_REGISTRY: %v", err) + return nil, fmt.Errorf("bad value for registry: %v", err) } return &Resolver{ resolver: resolver,