cue/load: allow cue.mod to be directory

(Part 2, merged wrong version)

Change-Id: I7f9a8778f5d68e0e2dbb171a174db45c06b8032f

Closes #167
https://github.com/cuelang/cue/pull/167

GitOrigin-RevId: c175b2ef4d3b0bb145122b6f494d071f20a9d16a
Change-Id: I0645b24e7d143ae87de23ef6e9bf575dd2f984fb
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/3880
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/load/config.go b/cue/load/config.go
index 7e81dce..09cbb60 100644
--- a/cue/load/config.go
+++ b/cue/load/config.go
@@ -242,6 +242,14 @@
 
 	pkg := filepath.ToSlash(dir[len(c.ModuleRoot):])
 	switch {
+	case strings.HasPrefix(pkg, "/cue.mod/"):
+		pkg = pkg[len("/cue.mod/"):]
+		if pkg == "" {
+			return "", errors.Newf(token.NoPos,
+				"invalid package %q (root of %s)", key, modDir)
+		}
+
+		// TODO(legacy): remove.
 	case strings.HasPrefix(pkg, "/pkg/"):
 		pkg = pkg[len("/pkg/"):]
 		if pkg == "" {
@@ -376,6 +384,13 @@
 	switch {
 	case true:
 		mod := filepath.Join(c.ModuleRoot, modDir)
+		info, cerr := c.fileSystem.stat(mod)
+		if cerr != nil {
+			break
+		}
+		if info.IsDir() {
+			mod = filepath.Join(mod, configFile)
+		}
 		f, cerr := c.fileSystem.openFile(mod)
 		if cerr != nil {
 			break
@@ -416,8 +431,10 @@
 	}
 	abs := absDir
 	for {
-		info, err := fs.stat(filepath.Join(abs, modDir))
-		if err == nil && !info.IsDir() {
+		_, err := fs.stat(filepath.Join(abs, modDir))
+		if err == nil {
+			// Note: cue.mod used to be a file. We still allow
+			// both to match.
 			return abs, nil
 		}
 		d := filepath.Dir(abs)
diff --git a/cue/load/import.go b/cue/load/import.go
index e2faeb8..01bfe30 100644
--- a/cue/load/import.go
+++ b/cue/load/import.go
@@ -17,6 +17,7 @@
 import (
 	"bytes"
 	"log"
+	"os"
 	"path/filepath"
 	"sort"
 	"strconv"
@@ -77,10 +78,8 @@
 		return p
 	}
 
-	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)
+	if !strings.HasPrefix(p.Dir, cfg.ModuleRoot) {
+		p.Err = errors.Newf(token.NoPos, "module root not defined", p.DisplayPath)
 		return p
 	}
 
@@ -101,7 +100,7 @@
 		dirs = append(dirs, [2]string{genDir, p.Dir})
 		// TODO(legacy): don't support "pkg"
 		if filepath.Base(genDir) != "pkg" {
-			for _, sub := range []string{"pkg", "src"} {
+			for _, sub := range []string{"pkg", "usr"} {
 				rel, err := filepath.Rel(genDir, p.Dir)
 				if err != nil {
 					// should not happen
@@ -117,11 +116,25 @@
 		dirs = append(dirs, [2]string{cfg.ModuleRoot, p.Dir})
 	}
 
+	found := false
+	for _, d := range dirs {
+		info, err := ctxt.stat(d[1])
+		if err == nil && info.IsDir() {
+			found = true
+			break
+		}
+	}
+
+	if !found {
+		p.Err = errors.Newf(token.NoPos, "cannot find package %q", p.DisplayPath)
+		return p
+	}
+
 	for _, d := range dirs {
 		for dir := d[1]; ctxt.isDir(dir); {
 			files, err := ctxt.readDir(dir)
-			if err != nil {
-				p.ReportError(errors.Wrapf(err, pos, "import failed reading dir %v", dir))
+			if err != nil && !os.IsNotExist(err) {
+				p.ReportError(errors.Wrapf(err, pos, "import failed reading dir %v", dirs[0][1]))
 				return p
 			}
 			for _, f := range files {
diff --git a/cue/load/import_test.go b/cue/load/import_test.go
index 1b7d570..4ed9a56 100644
--- a/cue/load/import_test.go
+++ b/cue/load/import_test.go
@@ -34,32 +34,6 @@
 	return p, p.Err
 }
 
-// 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)
-// 	}
-// }
-
 func TestEmptyImport(t *testing.T) {
 	p, err := getInst("", "")
 	if err == nil {
diff --git a/cue/load/search.go b/cue/load/search.go
index 18635a3..edba60e 100644
--- a/cue/load/search.go
+++ b/cue/load/search.go
@@ -158,13 +158,15 @@
 		return m
 	}
 
-	pkgDir := filepath.Join(root, "pkg")
+	pkgDir := filepath.Join(root, modDir)
+	// TODO(legacy): remove
+	pkgDir2 := filepath.Join(root, "pkg")
 
 	_ = c.fileSystem.walk(root, func(path string, fi os.FileInfo, err errors.Error) errors.Error {
 		if err != nil || !fi.IsDir() {
 			return nil
 		}
-		if path == pkgDir {
+		if path == pkgDir || path == pkgDir2 {
 			return skipDir
 		}