diff --git a/cmd/cue/cmd/modget.go b/cmd/cue/cmd/modget.go index 156a5d0b8..31753f4f3 100644 --- a/cmd/cue/cmd/modget.go +++ b/cmd/cue/cmd/modget.go @@ -23,6 +23,7 @@ import ( "github.com/spf13/cobra" "cuelang.org/go/internal/mod/modload" + "cuelang.org/go/internal/mod/modpkgload" "cuelang.org/go/mod/modfile" ) @@ -115,6 +116,9 @@ func findModuleRoot() (string, error) { // TODO this logic is duplicated in multiple places. We should // consider deduplicating it. dir := rootWorkingDir() + if modpkgload.InsideCueMod(dir) { + return "", fmt.Errorf("cannot run this command inside the cue.mod directory") + } for { if _, err := os.Stat(filepath.Join(dir, "cue.mod")); err == nil { return dir, nil diff --git a/cmd/cue/cmd/testdata/script/inside_cuemod.txtar b/cmd/cue/cmd/testdata/script/inside_cuemod.txtar index ff51cf4f0..2fa2ead11 100644 --- a/cmd/cue/cmd/testdata/script/inside_cuemod.txtar +++ b/cmd/cue/cmd/testdata/script/inside_cuemod.txtar @@ -13,33 +13,35 @@ stderr 'unknown file extension .mod' exec cue export cue.mod/module.cue cmp stdout $WORK/export-module.stdout -# From cue.mod, reaching into the module root. -# TODO: none of these should work; we should guide the user outside of cue.mod. -# Note that some of these did not work on Windows either. +# From cue.mod, reaching into the module root, all commands should fail. cd $WORK/cue.mod -[!windows] exec cue export .. -exec cue export ../root.cue +! exec cue export .. +stderr 'cannot load packages inside the cue.mod directory' +! exec cue export ../root.cue +stderr 'cannot load packages inside the cue.mod directory' -# From cue.mod. -# TODO: none of these should work; we should guide the user outside of cue.mod. -cd $WORK/cue.mod -exec cue mod tidy -exec cue export . -exec cue export somefile.cue -exec cue export module.cue -# Interestingly, these fail but with internal or confusing errors. +# From cue.mod, all commands should fail. +! exec cue export . +stderr 'cannot load packages inside the cue.mod directory' +! exec cue export somefile.cue +stderr 'cannot load packages inside the cue.mod directory' +! exec cue export module.cue +stderr 'cannot load packages inside the cue.mod directory' ! exec cue export ./subdir -stderr 'internal error:.*non-internal package' +stderr 'cannot load packages inside the cue.mod directory' ! exec cue vet ./... -stderr 'pattern not allowed in external package path' +stderr 'cannot load packages inside the cue.mod directory' +! exec cue mod tidy +stderr 'cannot run this command inside the cue.mod directory' -# From cue.mod/subdir. -# TODO: none of these should work; we should guide the user outside of cue.mod. +# From cue.mod/subdir, all commands should fail. cd $WORK/cue.mod/subdir -exec cue mod tidy -exec cue export . +! exec cue export . +stderr 'cannot load packages inside the cue.mod directory' +! exec cue mod tidy +stderr 'cannot run this command inside the cue.mod directory' -# From cue.mod/pkg/foo.com/bar. +# From cue.mod/pkg/foo.com/bar, loading CUE should work. # Arguably this should not work, but historically it has, and placing packages # in this directory structure is still supported as the old mechanism # to add package dependencies. For now, it continues to work. diff --git a/cue/load/config.go b/cue/load/config.go index 0ed8052ba..14f1d383b 100644 --- a/cue/load/config.go +++ b/cue/load/config.go @@ -29,6 +29,7 @@ import ( "cuelang.org/go/cue/parser" "cuelang.org/go/cue/token" "cuelang.org/go/internal" + "cuelang.org/go/internal/mod/modpkgload" "cuelang.org/go/mod/modconfig" "cuelang.org/go/mod/modfile" "cuelang.org/go/mod/module" @@ -361,6 +362,9 @@ func (c Config) complete() (cfg *Config, err error) { } else if c.Dir, err = filepath.Abs(c.Dir); err != nil { return nil, err } + if modpkgload.InsideCueMod(c.Dir) { + return nil, fmt.Errorf("cannot load packages inside the %s directory", modDir) + } // TODO: we could populate this already with absolute file paths, // but relative paths cannot be added. Consider what is reasonable. diff --git a/internal/mod/modpkgload/pkgload.go b/internal/mod/modpkgload/pkgload.go index 7d01157b4..5d0d4abc3 100644 --- a/internal/mod/modpkgload/pkgload.go +++ b/internal/mod/modpkgload/pkgload.go @@ -5,6 +5,7 @@ import ( "fmt" "io/fs" "maps" + "path/filepath" "runtime" "slices" "strings" @@ -430,3 +431,37 @@ func IsStdlibPackage(pkgPath string) bool { firstElem, _, _ := strings.Cut(pkgPath, "/") return !strings.Contains(firstElem, ".") } + +// InsideCueMod reports whether absDir is inside a cue.mod directory, +// excluding the legacy directories cue.mod/{pkg,usr,gen}, +// which are still supported for placing package dependencies. +// +// For example, /foo/cue.mod and /foo/cue.mod/bar are inside cue.mod, +// but /foo, /foo/cue.modx, and /foo/cue.mod/pkg/example.com are not. +// +// absDir must be an absolute system path that is clean; see [filepath.Clean]. +func InsideCueMod(absDir string) bool { + lastPart := "" + for { + dir, base, found := cutLast(absDir, string(filepath.Separator)) + if base == "cue.mod" { + switch lastPart { + case "pkg", "usr", "gen": + return false + } + return true + } + if !found { + return false + } + absDir = dir + lastPart = base + } +} + +func cutLast(s, sep string) (before, after string, found bool) { + if i := strings.LastIndex(s, sep); i >= 0 { + return s[:i], s[i+len(sep):], true + } + return "", s, false +}