cue/load: implement package name qualifiers
Implements the spec.
The package name of imported files now _must_ be
included in the import path. If it it doesn't match
the last path component of the import path, it can
be modified with a :<name> at the end.
Closes #77
https://github.com/cuelang/cue/pull/77
GitOrigin-RevId: d2ad114bf6fb14ca9fc065b074fa5adcf3b4a5bc
Change-Id: I6bec03ed931e4007bc85ece6b73a1ad89c0e0952
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/3000
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/build/import.go b/cue/build/import.go
index 04866b2..3f255f3 100644
--- a/cue/build/import.go
+++ b/cue/build/import.go
@@ -103,7 +103,7 @@
continue
}
if imp.Err != nil {
- return nil
+ return imp.Err
}
imp.ImportPath = path
// imp.parent = inst
diff --git a/cue/errors/errors.go b/cue/errors/errors.go
index fc5aed1..691b7e1 100644
--- a/cue/errors/errors.go
+++ b/cue/errors/errors.go
@@ -239,6 +239,24 @@
// The zero value for an list is an empty list ready to use.
type list []Error
+func (p list) Is(err, target error) bool {
+ for _, e := range p {
+ if xerrors.Is(e, target) {
+ return true
+ }
+ }
+ return false
+}
+
+func (p list) As(err error, target interface{}) bool {
+ for _, e := range p {
+ if xerrors.As(e, target) {
+ return true
+ }
+ }
+ return false
+}
+
// AddNewf adds an Error with given position and error message to an List.
func (p *list) AddNewf(pos token.Pos, msg string, args ...interface{}) {
err := &posError{pos: pos, Message: Message{format: msg, args: args}}
diff --git a/cue/load/config.go b/cue/load/config.go
index 3433680..8b58091 100644
--- a/cue/load/config.go
+++ b/cue/load/config.go
@@ -16,8 +16,10 @@
import (
"os"
+ pathpkg "path"
"path/filepath"
"runtime"
+ "strings"
"cuelang.org/go/cue"
"cuelang.org/go/cue/build"
@@ -29,7 +31,7 @@
cueSuffix = ".cue"
defaultDir = "cue"
modFile = "cue.mod"
- pkgDir = "pkg" // TODO: vendor?
+ pkgDir = "pkg" // TODO: vendor, third_party, _imports?
)
// FromArgsUsage is a partial usage message that applications calling
@@ -133,21 +135,186 @@
Overlay map[string]Source
fileSystem
+
+ loadFunc build.LoadFunc
}
-func (c Config) newInstance(path string) *build.Instance {
- i := c.Context.NewInstance(path, nil)
- i.DisplayPath = path
+func (c *Config) newInstance(pos token.Pos, p importPath) *build.Instance {
+ dir, name, err := c.absDirFromImportPath(pos, p)
+ i := c.Context.NewInstance(dir, c.loadFunc)
+ i.Dir = dir
+ i.PkgName = name
+ i.DisplayPath = string(p)
+ i.ImportPath = string(p)
+ i.Root = c.ModuleRoot
+ i.Module = c.Module
+ i.Err = errors.Append(i.Err, err)
+
return i
}
-func (c Config) newErrInstance(m *match, path string, err error) *build.Instance {
- i := c.Context.NewInstance(path, nil)
- i.DisplayPath = path
- i.ReportError(errors.Promote(err, "instance"))
+func (c *Config) newRelInstance(pos token.Pos, path string) *build.Instance {
+ fs := c.fileSystem
+
+ var err errors.Error
+ dir := path
+
+ p := c.Context.NewInstance(path, c.loadFunc)
+ p.PkgName = c.Package
+ p.DisplayPath = filepath.ToSlash(path)
+ // p.ImportPath = string(dir) // compute unique ID.
+ p.Root = c.ModuleRoot
+ p.Module = c.Module
+
+ if isLocalImport(path) {
+ p.Local = true
+ if c.Dir == "" {
+ err = errors.Append(err, errors.Newf(pos, "cwd unknown"))
+ }
+ dir = filepath.Join(c.Dir, filepath.FromSlash(path))
+ }
+
+ if path == "" {
+ err = errors.Append(err, errors.Newf(pos,
+ "import %q: invalid import path", path))
+ } else if path != cleanImport(path) {
+ err = errors.Append(err, c.loader.errPkgf(nil,
+ "non-canonical import path: %q should be %q", path, pathpkg.Clean(path)))
+ }
+
+ if importPath, e := c.importPathFromAbsDir(fsPath(dir), path, c.Package); e != nil {
+ // Detect later to keep error messages consistent.
+ } else {
+ p.ImportPath = string(importPath)
+ }
+
+ p.Dir = dir
+
+ if fs.isAbsPath(path) || strings.HasPrefix(path, "/") {
+ err = errors.Append(err, errors.Newf(pos,
+ "absolute import path %q not allowed", path))
+ }
+ if err != nil {
+ p.Err = errors.Append(p.Err, err)
+ p.Incomplete = true
+ }
+
+ return p
+}
+
+func (c Config) newErrInstance(pos token.Pos, path importPath, err error) *build.Instance {
+ i := c.newInstance(pos, path)
+ i.Err = errors.Promote(err, "instance")
return i
}
+func toImportPath(dir string) importPath {
+ return importPath(filepath.ToSlash(dir))
+}
+
+type importPath string
+
+type fsPath string
+
+func (c *Config) importPathFromAbsDir(absDir fsPath, key, name string) (importPath, errors.Error) {
+ if c.ModuleRoot == "" {
+ return "", errors.Newf(token.NoPos,
+ "cannot determine import path for %q (root undefined)", key)
+ }
+
+ dir := filepath.Clean(string(absDir))
+ if !strings.HasPrefix(dir, c.ModuleRoot) {
+ return "", errors.Newf(token.NoPos,
+ "cannot determine import path for %q (dir outside of root)", key)
+ }
+
+ pkg := filepath.ToSlash(dir[len(c.ModuleRoot):])
+ switch {
+ case strings.HasPrefix(pkg, "/pkg/"):
+ pkg = pkg[len("/pkg/"):]
+ if pkg == "" {
+ return "", errors.Newf(token.NoPos,
+ "invalid package %q (root of %s)", key, pkgDir)
+ }
+
+ case c.Module == "":
+ return "", errors.Newf(token.NoPos,
+ "cannot determine import path for %q (no module)", key)
+ default:
+ pkg = c.Module + pkg
+ }
+
+ return addImportQualifier(importPath(pkg), name)
+}
+
+func addImportQualifier(pkg importPath, name string) (importPath, errors.Error) {
+ if name != "" {
+ s := string(pkg)
+ if i := strings.LastIndexByte(s, '/'); i >= 0 {
+ s = s[i+1:]
+ }
+ if i := strings.LastIndexByte(s, ':'); i >= 0 {
+ // should never happen, but just in case.
+ s = s[i+1:]
+ if s != name {
+ return "", errors.Newf(token.NoPos,
+ "non-matching package names (%s != %s)", s, name)
+ }
+ } else if s != name {
+ pkg += importPath(":" + name)
+ }
+ }
+
+ return pkg, nil
+}
+
+// absDirFromImportPath converts a giving import path to an absolute directory
+// and a package name. The root directory must be set.
+//
+// The returned directory may not exist.
+func (c *Config) absDirFromImportPath(pos token.Pos, p importPath) (absDir, name string, err errors.Error) {
+ if c.ModuleRoot == "" {
+ return "", "", errors.Newf(pos, "cannot import %q (root undefined)", p)
+ }
+
+ // Extract the package name.
+
+ name = string(p)
+ switch i := strings.LastIndexAny(name, "/:"); {
+ case i < 0:
+ case p[i] == ':':
+ name = string(p[i+1:])
+ p = p[:i]
+
+ default: // p[i] == '/'
+ name = string(p[i+1:])
+ }
+
+ // TODO: fully test that name is a valid identifier.
+ if name == "" {
+ err = errors.Newf(pos, "empty package name in import path %q", p)
+ } else if strings.IndexByte(name, '.') >= 0 {
+ err = errors.Newf(pos,
+ "cannot determine package name for %q (set explicitly with ':')", p)
+ }
+
+ // Determine the directory.
+
+ sub := filepath.FromSlash(string(p))
+ switch hasPrefix := strings.HasPrefix(string(p), c.Module); {
+ case hasPrefix && len(sub) == len(c.Module):
+ absDir = c.ModuleRoot
+
+ case hasPrefix && p[len(c.Module)] == '/':
+ absDir = filepath.Join(c.ModuleRoot, sub[len(c.Module)+1:])
+
+ default:
+ absDir = filepath.Join(c.ModuleRoot, "pkg", sub)
+ }
+
+ return absDir, name, nil
+}
+
// Complete updates the configuration information. After calling complete,
// the following invariants hold:
// - c.ModuleRoot != ""
@@ -168,6 +335,8 @@
if err != nil {
return nil, err
}
+ } else if c.Dir, err = filepath.Abs(c.Dir); err != nil {
+ return nil, err
}
// TODO: we could populate this already with absolute file paths,
@@ -191,10 +360,6 @@
c.loader = &loader{cfg: &c}
- if c.Context == nil {
- c.Context = build.NewContext(build.Loader(c.loader.loadFunc(c.Dir)))
- }
-
if c.cache == "" {
c.cache = filepath.Join(home(), defaultDir)
}
@@ -225,6 +390,12 @@
}
}
+ c.loadFunc = c.loader.loadFunc()
+
+ if c.Context == nil {
+ c.Context = build.NewContext(build.Loader(c.loadFunc))
+ }
+
return &c, nil
}
diff --git a/cue/load/fs.go b/cue/load/fs.go
index 004866d..082efbb 100644
--- a/cue/load/fs.go
+++ b/cue/load/fs.go
@@ -68,10 +68,6 @@
// Organize overlay
for filename, src := range overlay {
- if !strings.HasSuffix(filename, ".cue") {
- return errors.Newf(token.NoPos, "overlay file %s not a .cue file", filename)
- }
-
// TODO: do we need to further clean the path or check that the
// specified files are within the root/ absolute files?
dir, base := filepath.Split(filename)
diff --git a/cue/load/import.go b/cue/load/import.go
index de14a70..b392804 100644
--- a/cue/load/import.go
+++ b/cue/load/import.go
@@ -17,7 +17,6 @@
import (
"bytes"
"log"
- pathpkg "path"
"path/filepath"
"sort"
"strconv"
@@ -67,35 +66,36 @@
// If an error occurs, importPkg sets the error in the returned instance,
// which then may contain partial information.
//
-func (l *loader) importPkg(pos token.Pos, path, srcDir string) *build.Instance {
- l.stk.Push(path)
+func (l *loader) importPkg(pos token.Pos, p *build.Instance) *build.Instance {
+ l.stk.Push(p.ImportPath)
defer l.stk.Pop()
cfg := l.cfg
ctxt := &cfg.fileSystem
- parentPath := path
- if isLocalImport(path) {
- parentPath = filepath.Join(srcDir, filepath.FromSlash(path))
- }
- p := cfg.Context.NewInstance(path, l.loadFunc(parentPath))
-
- if err := updateDirs(cfg, p, path, srcDir, 0); err != nil {
- p.ReportError(err)
+ if p.Err != nil {
return p
}
- if path != cleanImport(path) {
- report(p, l.errPkgf(nil,
- "non-canonical import path: %q should be %q", path, pathpkg.Clean(path)))
- p.Incomplete = true
+ info, err := ctxt.stat(p.Dir)
+ if err != nil || !info.IsDir() {
+ // package was not found
+ p.Err = errors.Newf(token.NoPos, "cannot find package %q", p.DisplayPath)
return p
}
fp := newFileProcessor(cfg, p)
- root := srcDir
+ root := cfg.Dir
+ // If we have an explicit package name, we can ignore other packages.
+ if p.PkgName != "" {
+ fp.ignoreOther = true
+ }
+
+ // TODO: remove: use the pre-determined module root.
+ // Also consider an additional mechanism that we may merge in packages
+ // from parents.
for dir := p.Dir; ctxt.isDir(dir); {
files, err := ctxt.readDir(dir)
if err != nil {
@@ -131,8 +131,14 @@
dir = parent
}
- if strings.HasPrefix(root, srcDir) {
- root = srcDir
+ impPath, err := addImportQualifier(importPath(p.ImportPath), p.PkgName)
+ p.ImportPath = string(impPath)
+ if err != nil {
+ p.ReportError(err)
+ }
+
+ if strings.HasPrefix(root, cfg.Dir) {
+ root = cfg.Dir
}
rewriteFiles(p, root, false)
@@ -158,85 +164,31 @@
}
// loadFunc creates a LoadFunc that can be used to create new build.Instances.
-func (l *loader) loadFunc(parentPath string) build.LoadFunc {
+func (l *loader) loadFunc() build.LoadFunc {
return func(pos token.Pos, path string) *build.Instance {
cfg := l.cfg
- if !isLocalImport(path) {
- // is it a builtin?
- if strings.IndexByte(strings.Split(path, "/")[0], '.') == -1 {
- if l.cfg.StdRoot != "" {
- return l.importPkg(pos, path, l.cfg.StdRoot)
- }
- return nil
+ impPath := importPath(path)
+ if isLocalImport(path) {
+ return cfg.newErrInstance(pos, impPath,
+ errors.Newf(pos, "relative import paths not allowed (%q)", path))
+ }
+
+ // is it a builtin?
+ if strings.IndexByte(strings.Split(path, "/")[0], '.') == -1 {
+ if l.cfg.StdRoot != "" {
+ p := cfg.newInstance(pos, impPath)
+ return l.importPkg(pos, p)
}
- if cfg.ModuleRoot == "" {
- i := cfg.newInstance(path)
- report(i, l.errPkgf(nil,
- "import %q not found in the pkg directory", path))
- return i
- }
- root := cfg.ModuleRoot
- if mod := cfg.Module; path == mod || strings.HasPrefix(path, mod+"/") {
- path = path[len(mod)+1:]
- } else {
- root = filepath.Join(root, "pkg")
- }
- return l.importPkg(pos, path, root)
+ return nil
}
- if strings.Contains(path, "@") {
- i := cfg.newInstance(path)
- report(i, l.errPkgf(nil,
- "can only use path@version syntax with 'cue get'"))
- return i
- }
-
- return l.importPkg(pos, path, parentPath)
+ p := cfg.newInstance(pos, impPath)
+ return l.importPkg(pos, p)
}
}
-func updateDirs(c *Config, p *build.Instance, path, srcDir string, mode importMode) errors.Error {
- p.DisplayPath = path
- p.Root = c.ModuleRoot
- p.Module = c.Module
-
- isLocal := isLocalImport(path)
- p.Local = isLocal
-
- ctxt := &c.fileSystem
-
- if path == "" {
- return errors.Newf(token.NoPos, "import %q: invalid import path", path)
- }
-
- if ctxt.isAbsPath(path) || strings.HasPrefix(path, "/") {
- return errors.Newf(token.NoPos, "absolute import path %q not allowed", path)
- }
-
- if isLocal {
- if c.Module != "" {
- p.ImportPath = filepath.Join(c.Module, path)
- }
-
- if srcDir == "" {
- return errors.Newf(token.NoPos, "import %q: import relative to unknown directory", path)
- }
- p.Dir = ctxt.joinPath(srcDir, path)
- return nil
- }
- dir := ctxt.joinPath(srcDir, path)
- info, err := ctxt.stat(dir)
- if err == nil && info.IsDir() {
- p.Dir = dir
- return nil
- }
-
- // package was not found
- return errors.Newf(token.NoPos, "cannot find package %q", path)
-}
-
func normPrefix(root, path string, isLocal bool) string {
root = filepath.Clean(root)
prefix := ""
@@ -366,16 +318,7 @@
return false // don't mark as added
}
- if fp.c.Package != "" {
- if pkg != fp.c.Package {
- if fp.ignoreOther {
- p.IgnoredCUEFiles = append(p.IgnoredCUEFiles, fullPath)
- return false
- }
- // TODO: package does not conform with requested.
- return badFile(errors.Newf(pos, "%s: found package %q; want %q", filename, pkg, fp.c.Package))
- }
- } else if fp.firstFile == "" {
+ if p.PkgName == "" {
p.PkgName = pkg
fp.firstFile = name
} else if pkg != p.PkgName {
diff --git a/cue/load/import_test.go b/cue/load/import_test.go
index 26caedc..1b7d570 100644
--- a/cue/load/import_test.go
+++ b/cue/load/import_test.go
@@ -16,47 +16,49 @@
import (
"os"
- "path/filepath"
"reflect"
"testing"
build "cuelang.org/go/cue/build"
"cuelang.org/go/cue/token"
+ "golang.org/x/xerrors"
)
const testdata = "./testdata/"
func getInst(pkg, cwd string) (*build.Instance, error) {
- c, _ := (&Config{}).complete()
+ c, _ := (&Config{Dir: cwd}).complete()
l := loader{cfg: c}
- p := l.importPkg(token.NoPos, pkg, cwd)
+ inst := c.newRelInstance(token.NoPos, pkg)
+ p := l.importPkg(token.NoPos, inst)
return p, p.Err
}
-func TestDotSlashImport(t *testing.T) {
- c, _ := (&Config{}).complete()
- l := loader{cfg: c}
- p := l.importPkg(token.NoPos, ".", testdata+"other")
- errl := p.Err
- if errl != nil {
- t.Fatal(errl)
- }
- if len(p.ImportPaths) != 1 || p.ImportPaths[0] != "./file" {
- t.Fatalf("testdata/other: Imports=%v, want [./file]", p.ImportPaths)
- }
+// Uncomment this test if we decide to allow relative imports again.
+// func TestDotSlashImport(t *testing.T) {
+// c, _ := (&Config{}).complete()
+// l := loader{cfg: c}
+// p := l.importPkg(token.NoPos, ".", testdata+"other")
+// errl := p.Err
+// if errl != nil {
+// t.Fatal(errl)
+// }
+// if len(p.ImportPaths) != 1 || p.ImportPaths[0] != "./file" {
+// t.Fatalf("testdata/other: Imports=%v, want [./file]", p.ImportPaths)
+// }
- p1, err := getInst("./file", testdata+"other")
- if err != nil {
- t.Fatal(err)
- }
- if p1.PkgName != "file" {
- t.Fatalf("./file: Name=%q, want %q", p1.PkgName, "file")
- }
- dir := filepath.Clean(testdata + "other/file") // Clean to use \ on Windows
- if p1.Dir != dir {
- t.Fatalf("./file: Dir=%q, want %q", p1.PkgName, dir)
- }
-}
+// p1, err := getInst("./file", testdata+"other")
+// if err != nil {
+// t.Fatal(err)
+// }
+// if p1.PkgName != "file" {
+// t.Fatalf("./file: Name=%q, want %q", p1.PkgName, "file")
+// }
+// dir := filepath.Clean(testdata + "other/file") // Clean to use \ on Windows
+// if p1.Dir != dir {
+// t.Fatalf("./file: Dir=%q, want %q", p1.PkgName, dir)
+// }
+// }
func TestEmptyImport(t *testing.T) {
p, err := getInst("", "")
@@ -80,7 +82,8 @@
func TestIgnoredCUEFilesImport(t *testing.T) {
_, err := getInst(".", testdata+"ignored")
- e, ok := err.(*noCUEError)
+ var e *noCUEError
+ ok := xerrors.As(err, &e)
if !ok {
t.Fatal(`Import("testdata/ignored") did not return NoCUEError.`)
}
@@ -95,8 +98,8 @@
if !ok {
t.Fatal(`Import("testdata/multi") did not return MultiplePackageError.`)
}
+ mpe.Dir = ""
want := &multiplePackageError{
- Dir: filepath.FromSlash("testdata/multi"),
Packages: []string{"main", "test_package"},
Files: []string{"file.cue", "file_appengine.cue"},
}
diff --git a/cue/load/loader.go b/cue/load/loader.go
index c831705..ed64913 100644
--- a/cue/load/loader.go
+++ b/cue/load/loader.go
@@ -42,7 +42,7 @@
}
newC, err := c.complete()
if err != nil {
- return []*build.Instance{c.newErrInstance(nil, "", err)}
+ return []*build.Instance{c.newErrInstance(token.NoPos, "", err)}
}
c = newC
@@ -55,13 +55,10 @@
return []*build.Instance{l.cueFilesPackage(args)}
}
- dummy := c.newInstance("user")
- dummy.Local = true
-
a := []*build.Instance{}
for _, m := range l.importPaths(args) {
if m.Err != nil {
- inst := c.newErrInstance(m, "", m.Err)
+ inst := c.newErrInstance(token.NoPos, "", m.Err)
a = append(a, inst)
continue
}
@@ -102,17 +99,17 @@
pos := token.NoPos
cfg := l.cfg
// ModInit() // TODO: support modules
- pkg := l.cfg.Context.NewInstance(cfg.Dir, l.loadFunc(cfg.Dir))
+ pkg := l.cfg.Context.NewInstance(cfg.Dir, l.loadFunc())
for _, f := range files {
if cfg.isDir(f) {
- return cfg.newErrInstance(nil, f,
+ return cfg.newErrInstance(token.NoPos, toImportPath(f),
errors.Newf(pos, "cannot mix files with directories %v", f))
}
ext := filepath.Ext(f)
enc := encoding.MapExtension(ext)
if enc == nil {
- return cfg.newErrInstance(nil, f,
+ return cfg.newErrInstance(token.NoPos, toImportPath(f),
errors.Newf(pos, "unrecognized extension %q", ext))
}
}
@@ -126,11 +123,11 @@
}
fi, err := cfg.fileSystem.stat(path)
if err != nil {
- return cfg.newErrInstance(nil, path,
+ return cfg.newErrInstance(pos, toImportPath(path),
errors.Wrapf(err, pos, "could not find dir %s", path))
}
if fi.IsDir() {
- return cfg.newErrInstance(nil, path,
+ return cfg.newErrInstance(pos, toImportPath(path),
errors.Newf(pos, "%s is a directory, should be a CUE file", file))
}
fp.add(pos, cfg.Dir, file, allowAnonymous)
@@ -139,7 +136,7 @@
// TODO: ModImportFromFiles(files)
_, err := filepath.Abs(cfg.Dir)
if err != nil {
- return cfg.newErrInstance(nil, cfg.Dir,
+ return cfg.newErrInstance(pos, toImportPath(cfg.Dir),
errors.Wrapf(err, pos, "could convert '%s' to absolute path", cfg.Dir))
}
pkg.Dir = cfg.Dir
diff --git a/cue/load/loader_test.go b/cue/load/loader_test.go
index 26625f6..e9150d0 100644
--- a/cue/load/loader_test.go
+++ b/cue/load/loader_test.go
@@ -55,6 +55,7 @@
module: example.org/test
root: $CWD/testdata
dir: $CWD/testdata
+display:.
files:
$CWD/testdata/test.cue
imports:
@@ -70,33 +71,23 @@
module: example.org/test
root: $CWD/testdata
dir: $CWD/testdata
+display:.
files:
$CWD/testdata/test.cue
imports:
example.org/test/sub: $CWD/testdata/sub/sub.cue`,
}, {
// TODO:
- // - paths are incorrect, should be example.org/test/other:main and
- // example.org/test/other/file, respectively.
- // - referenced import path of files is wrong.
+ // - path incorrect, should be example.org/test/other:main.
cfg: dirCfg,
args: args("./other/..."),
want: `
-path: example.org/test
+err: relative import paths not allowed ("./file")
+path: ""
module: example.org/test
-root: $CWD/testdata/
-dir: $CWD/testdata/other
-files:
- $CWD/testdata/other/main.cue
-imports:
- ./file: $CWD/testdata/other/file/file.cue
-
-path: example.org/test/file
-module: example.org/test
-root: $CWD/testdata/
-dir: $CWD/testdata/other/file
-files:
- $CWD/testdata/other/file/file.cue`,
+root: $CWD/testdata
+dir: $CWD/testdata/pkg
+display:`,
}, {
cfg: dirCfg,
args: args("./anon"),
@@ -105,38 +96,67 @@
path: example.org/test/anon
module: example.org/test
root: $CWD/testdata
-dir: $CWD/testdata/anon`,
+dir: $CWD/testdata/anon
+display:./anon`,
}, {
// TODO:
- // - paths are incorrect, should be example.org/test/other:main and
- // example.org/test/other/file, respectively.
+ // - paths are incorrect, should be example.org/test/other:main.
cfg: dirCfg,
args: args("./other"),
want: `
-path: example.org/test/other
+err: relative import paths not allowed ("./file")
+path: example.org/test/other:main
module: example.org/test
root: $CWD/testdata
dir: $CWD/testdata/other
+display:./other
files:
- $CWD/testdata/other/main.cue
-imports:
- ./file: $CWD/testdata/other/file/file.cue`,
+ $CWD/testdata/other/main.cue`,
}, {
// TODO:
// - incorrect path, should be example.org/test/hello:test
cfg: dirCfg,
args: args("./hello"),
want: `
-path: example.org/test/hello
+path: example.org/test/hello:test
module: example.org/test
root: $CWD/testdata
dir: $CWD/testdata/hello
+display:./hello
files:
$CWD/testdata/test.cue
$CWD/testdata/hello/test.cue
imports:
example.org/test/sub: $CWD/testdata/sub/sub.cue`,
}, {
+ // TODO:
+ // - incorrect path, should be example.org/test/hello:test
+ cfg: dirCfg,
+ args: args("example.org/test/hello:test"),
+ want: `
+path: example.org/test/hello:test
+module: example.org/test
+root: $CWD/testdata
+dir: $CWD/testdata/hello
+display:example.org/test/hello:test
+files:
+ $CWD/testdata/test.cue
+ $CWD/testdata/hello/test.cue
+imports:
+ example.org/test/sub: $CWD/testdata/sub/sub.cue`,
+ }, {
+ // TODO:
+ // - incorrect path, should be example.org/test/hello:test
+ cfg: dirCfg,
+ args: args("example.org/test/hello:nonexist"),
+ want: `
+err: build constraints exclude all CUE files in example.org/test/hello:nonexist
+path: example.org/test/hello:nonexist
+module: example.org/test
+root: $CWD/testdata
+dir: $CWD/testdata/hello
+display:example.org/test/hello:nonexist`,
+ }, {
cfg: dirCfg,
args: args("./anon.cue", "./other/anon.cue"),
want: `
@@ -144,6 +164,7 @@
module: ""
root: $CWD/testdata
dir: $CWD/testdata
+display:command-line-arguments
files:
$CWD/testdata/anon.cue
$CWD/testdata/other/anon.cue`,
@@ -156,6 +177,7 @@
module: ""
root: $CWD/testdata
dir: $CWD/testdata
+display:command-line-arguments
files:
$CWD/testdata/anon.cue`,
}, {
@@ -164,10 +186,11 @@
args: args("non-existing"),
want: `
err: cannot find package "non-existing"
-path: ""
+path: non-existing
module: example.org/test
root: $CWD/testdata
-dir: non-existing `,
+dir: $CWD/testdata/pkg/non-existing
+display:non-existing`,
}, {
cfg: dirCfg,
args: args("./empty"),
@@ -176,7 +199,8 @@
path: example.org/test/empty
module: example.org/test
root: $CWD/testdata
-dir: $CWD/testdata/empty`,
+dir: $CWD/testdata/empty
+display:./empty`,
}, {
cfg: dirCfg,
args: args("./imports"),
@@ -185,13 +209,17 @@
module: example.org/test
root: $CWD/testdata
dir: $CWD/testdata/imports
+display:./imports
files:
$CWD/testdata/imports/imports.cue
imports:
acme.com/catch: $CWD/testdata/pkg/acme.com/catch/catch.cue
- acme.com/helper: $CWD/testdata/pkg/acme.com/helper/helper.cue`,
+ acme.com/helper:helper1: $CWD/testdata/pkg/acme.com/helper/helper1.cue`,
}}
for i, tc := range testCases {
+ // if i != 5 {
+ // continue
+ // }
t.Run(strconv.Itoa(i)+"/"+strings.Join(tc.args, ":"), func(t *testing.T) {
pkgs := Instances(tc.args, tc.cfg)
@@ -223,6 +251,7 @@
module: {{if .Module}}{{.Module}}{{else}}""{{end}}
root: {{.Root}}
dir: {{.Dir}}
+display:{{.DisplayPath}}
{{if .Files -}}
files:
{{- range .Files}}
@@ -245,6 +274,9 @@
}
c := &Config{
Overlay: map[string]Source{
+ // Not necessary, but nice to add.
+ abs("cue.mod"): FromString(`module: acme.com`),
+
abs("dir/top.cue"): FromBytes([]byte(`
package top
msg: "Hello"
diff --git a/cue/load/search.go b/cue/load/search.go
index 3f2d0de..bd649d3 100644
--- a/cue/load/search.go
+++ b/cue/load/search.go
@@ -148,13 +148,14 @@
root := l.abs(dir)
- if c.ModuleRoot != "" {
- if !hasFilepathPrefix(root, c.ModuleRoot) {
- m.Err = errors.Newf(token.NoPos,
- "cue: pattern %s refers to dir %s, outside module root %s",
- pattern, root, c.ModuleRoot)
- return m
- }
+ // Find new module root from here or check there are no additional
+ // cue.mod files between here and the next module.
+
+ if !hasFilepathPrefix(root, c.ModuleRoot) {
+ m.Err = errors.Newf(token.NoPos,
+ "cue: pattern %s refers to dir %s, outside module root %s",
+ pattern, root, c.ModuleRoot)
+ return m
}
pkgDir := filepath.Join(root, "pkg")
@@ -192,7 +193,17 @@
// due to invalid CUE source files. This means that directories
// containing parse errors will be built (and fail) instead of being
// silently skipped as not matching the pattern.
- p := l.importPkg(token.NoPos, "."+path[len(root):], root)
+ // Do not take root, as we want to stay relative
+ // to one dir only.
+ dir, e := filepath.Rel(c.Dir, path)
+ if e != nil {
+ panic(err)
+ } else {
+ dir = "./" + dir
+ }
+ // TODO: consider not doing these checks here.
+ inst := c.newRelInstance(token.NoPos, dir)
+ p := l.importPkg(token.NoPos, inst)
if err := p.Err; err != nil && (p == nil || len(p.InvalidCUEFiles) == 0) {
switch err.(type) {
case nil:
@@ -332,7 +343,14 @@
continue
}
- pkg := l.importPkg(token.NoPos, a, l.cfg.Dir)
+ var p *build.Instance
+ if isLocalImport(a) {
+ p = l.cfg.newRelInstance(token.NoPos, a)
+ } else {
+ p = l.cfg.newInstance(token.NoPos, importPath(a))
+ }
+
+ pkg := l.importPkg(token.NoPos, p)
out = append(out, &match{Pattern: a, Literal: true, Pkgs: []*build.Instance{pkg}})
}
return out
diff --git a/cue/load/testdata/pkg/acme.com/catch/catch.cue b/cue/load/testdata/pkg/acme.com/catch/catch.cue
index 0b9beae..0cfbbe8 100644
--- a/cue/load/testdata/pkg/acme.com/catch/catch.cue
+++ b/cue/load/testdata/pkg/acme.com/catch/catch.cue
@@ -1,5 +1,5 @@
package catch
-import "acme.com/helper"
+import "acme.com/helper:helper1"
Method: "tnt" | "catapult" | "net" | helper.Gotcha
diff --git a/cue/load/testdata/pkg/acme.com/helper/helper1.cue b/cue/load/testdata/pkg/acme.com/helper/helper1.cue
new file mode 100644
index 0000000..62a8e6a
--- /dev/null
+++ b/cue/load/testdata/pkg/acme.com/helper/helper1.cue
@@ -0,0 +1,3 @@
+package helper1
+
+Gotcha: "gotcha"
diff --git a/cue/load/testdata/pkg/acme.com/helper/helper2.cue b/cue/load/testdata/pkg/acme.com/helper/helper2.cue
new file mode 100644
index 0000000..5c91907
--- /dev/null
+++ b/cue/load/testdata/pkg/acme.com/helper/helper2.cue
@@ -0,0 +1,3 @@
+package helper2
+
+Gotcha: "gotcha"
diff --git a/cue/parser/parser.go b/cue/parser/parser.go
index 43ef86e..840be6d 100644
--- a/cue/parser/parser.go
+++ b/cue/parser/parser.go
@@ -1221,6 +1221,9 @@
func isValidImport(lit string) bool {
const illegalChars = `!"#$%&'()*,:;<=>?[\]^{|}` + "`\uFFFD"
s, _ := literal.Unquote(lit) // go/scanner returns a legal string literal
+ if p := strings.LastIndexByte(s, ':'); p >= 0 {
+ s = s[:p]
+ }
for _, r := range s {
if !unicode.IsGraphic(r) || unicode.IsSpace(r) || strings.ContainsRune(illegalChars, r) {
return false