cue/load: allow cue.mod to be directory

pkg directory is moved in there as subdirectory,
but split into pkg, gen, and src.

The default is still "pkg". This change is planned
with a follow-up CL.

Change-Id: I99dd6b96c015371daf321949342efb36408b734a
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/3861
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/get_go.go b/cmd/cue/cmd/get_go.go
index 299d9d6..6c0194d 100644
--- a/cmd/cue/cmd/get_go.go
+++ b/cmd/cue/cmd/get_go.go
@@ -33,6 +33,7 @@
 
 	cueast "cuelang.org/go/cue/ast"
 	"cuelang.org/go/cue/format"
+	"cuelang.org/go/cue/load"
 	"cuelang.org/go/cue/parser"
 	cuetoken "cuelang.org/go/cue/token"
 	"cuelang.org/go/internal"
@@ -394,7 +395,7 @@
 	}
 
 	pkg := p.PkgPath
-	dir := filepath.Join(root, "pkg", filepath.FromSlash(pkg))
+	dir := filepath.Join(load.GenPath(root), filepath.FromSlash(pkg))
 	if err := os.MkdirAll(dir, 0755); err != nil {
 		return err
 	}
diff --git a/cue/load/config.go b/cue/load/config.go
index 820a7d4..7e81dce 100644
--- a/cue/load/config.go
+++ b/cue/load/config.go
@@ -29,9 +29,9 @@
 
 const (
 	cueSuffix  = ".cue"
-	defaultDir = "cue"
-	modFile    = "cue.mod"
-	pkgDir     = "pkg" // TODO: vendor, third_party, _imports?
+	modDir     = "cue.mod"
+	configFile = "module.cue"
+	pkgDir     = "pkg"
 )
 
 // FromArgsUsage is a partial usage message that applications calling
@@ -68,6 +68,17 @@
 A '--' argument terminates the list of packages.
 `
 
+// GenPath reports the directory in which to store generated
+// files.
+func GenPath(root string) string {
+	info, err := os.Stat(filepath.Join(root, modDir))
+	if err == nil && info.IsDir() {
+		// TODO(legacy): support legacy cue.mod file.
+		return filepath.Join(root, modDir, "gen")
+	}
+	return filepath.Join(root, "pkg")
+}
+
 // A Config configures load behavior.
 type Config struct {
 	// Context specifies the context for the load operation.
@@ -87,9 +98,6 @@
 	// the module field of an existing cue.mod file.
 	Module string
 
-	// cache specifies the package cache in which to look for packages.
-	cache string
-
 	// Package defines the name of the package to be loaded. In this is not set,
 	// the package must be uniquely defined from its context.
 	Package string
@@ -313,7 +321,7 @@
 		absDir = filepath.Join(c.ModuleRoot, sub[len(c.Module)+1:])
 
 	default:
-		absDir = filepath.Join(c.ModuleRoot, "pkg", sub)
+		absDir = filepath.Join(GenPath(c.ModuleRoot), sub)
 	}
 
 	return absDir, name, nil
@@ -364,14 +372,10 @@
 
 	c.loader = &loader{cfg: &c}
 
-	if c.cache == "" {
-		c.cache = filepath.Join(home(), defaultDir)
-	}
-
 	// TODO: also make this work if run from outside the module?
 	switch {
 	case true:
-		mod := filepath.Join(c.ModuleRoot, modFile)
+		mod := filepath.Join(c.ModuleRoot, modDir)
 		f, cerr := c.fileSystem.openFile(mod)
 		if cerr != nil {
 			break
@@ -412,7 +416,7 @@
 	}
 	abs := absDir
 	for {
-		info, err := fs.stat(filepath.Join(abs, modFile))
+		info, err := fs.stat(filepath.Join(abs, modDir))
 		if err == nil && !info.IsDir() {
 			return abs, nil
 		}
@@ -423,6 +427,8 @@
 		abs = d
 	}
 	abs = absDir
+
+	// TODO(legacy): remove this capability at some point.
 	for {
 		info, err := fs.stat(filepath.Join(abs, pkgDir))
 		if err == nil && info.IsDir() {
diff --git a/cue/load/import.go b/cue/load/import.go
index 47c5282..e2faeb8 100644
--- a/cue/load/import.go
+++ b/cue/load/import.go
@@ -86,49 +86,65 @@
 
 	fp := newFileProcessor(cfg, p)
 
-	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 {
-			p.ReportError(errors.Wrapf(err, pos, "import failed reading dir %v", dir))
-			return p
-		}
-		rootFound := false
-		for _, f := range files {
-			if f.IsDir() {
-				continue
-			}
-			if fp.add(pos, dir, f.Name(), importComment) {
-				root = dir
-			}
-			if f.Name() == "cue.mod" {
-				root = dir
-				rootFound = true
+	if !strings.HasPrefix(p.Dir, cfg.ModuleRoot) {
+		panic("")
+	}
+
+	var dirs [][2]string
+	genDir := GenPath(cfg.ModuleRoot)
+	if strings.HasPrefix(p.Dir, genDir) {
+		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"} {
+				rel, err := filepath.Rel(genDir, p.Dir)
+				if err != nil {
+					// should not happen
+					p.Err = errors.Wrapf(err, token.NoPos, "invalid path")
+					return p
+				}
+				base := filepath.Join(cfg.ModuleRoot, modDir, sub)
+				dir := filepath.Join(base, rel)
+				dirs = append(dirs, [2]string{base, dir})
 			}
 		}
+	} else {
+		dirs = append(dirs, [2]string{cfg.ModuleRoot, p.Dir})
+	}
 
-		if rootFound || filepath.Clean(dir) == l.cfg.ModuleRoot || fp.pkg.PkgName == "" {
-			break
+	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))
+				return p
+			}
+			for _, f := range files {
+				if f.IsDir() {
+					continue
+				}
+				fp.add(pos, dir, f.Name(), importComment)
+			}
+
+			if filepath.Clean(dir) == d[0] || fp.pkg.PkgName == "" {
+				break
+			}
+
+			// From now on we just ignore files that do not belong to the same
+			// package.
+			fp.ignoreOther = true
+
+			parent, _ := filepath.Split(filepath.Clean(dir))
+			if parent == dir {
+				break
+			}
+			dir = parent
 		}
-
-		// From now on we just ignore files that do not belong to the same
-		// package.
-		fp.ignoreOther = true
-
-		parent, _ := filepath.Split(filepath.Clean(dir))
-		if parent == dir {
-			break
-		}
-		dir = parent
 	}
 
 	impPath, err := addImportQualifier(importPath(p.ImportPath), p.PkgName)
@@ -137,11 +153,7 @@
 		p.ReportError(err)
 	}
 
-	if strings.HasPrefix(root, cfg.Dir) {
-		root = cfg.Dir
-	}
-
-	rewriteFiles(p, root, false)
+	rewriteFiles(p, cfg.ModuleRoot, false)
 	if errs := fp.finalize(); errs != nil {
 		for _, e := range errors.Errors(errs) {
 			p.ReportError(e)
@@ -151,7 +163,7 @@
 
 	for _, f := range p.CUEFiles {
 		if !ctxt.isAbsPath(f) {
-			f = ctxt.joinPath(root, f)
+			f = ctxt.joinPath(cfg.ModuleRoot, f)
 		}
 		r, err := ctxt.openFile(f)
 		if err != nil {
@@ -424,7 +436,7 @@
 	// expect package name
 	_, data = parseWord(data)
 
-	// now ready for import comment, a // or /* */ comment
+	// now ready for import comment, a // comment
 	// beginning and ending on the current line.
 	for len(data) > 0 && (data[0] == ' ' || data[0] == '\t' || data[0] == '\r') {
 		data = data[1:]
@@ -438,17 +450,6 @@
 			i = len(data)
 		}
 		comment = data[2:i]
-	case bytes.HasPrefix(data, slashStar):
-		data = data[2:]
-		i := bytes.Index(data, starSlash)
-		if i < 0 {
-			// malformed comment
-			return "", 0
-		}
-		comment = data[:i]
-		if bytes.Contains(comment, newline) {
-			return "", 0
-		}
 	}
 	comment = bytes.TrimSpace(comment)
 
@@ -464,8 +465,6 @@
 
 var (
 	slashSlash = []byte("//")
-	slashStar  = []byte("/*")
-	starSlash  = []byte("*/")
 	newline    = []byte("\n")
 )
 
@@ -485,15 +484,6 @@
 				data = data[i+1:]
 				continue
 			}
-			if bytes.HasPrefix(data, slashStar) {
-				data = data[2:]
-				i := bytes.Index(data, starSlash)
-				if i < 0 {
-					return nil
-				}
-				data = data[i+2:]
-				continue
-			}
 		}
 		break
 	}
diff --git a/cue/load/loader.go b/cue/load/loader.go
index deefd3d..5d8029a 100644
--- a/cue/load/loader.go
+++ b/cue/load/loader.go
@@ -115,7 +115,7 @@
 		}
 	}
 
-	// TODO: add fiedls directly?
+	// TODO: add fields directly?
 	fp := newFileProcessor(cfg, pkg)
 	for _, file := range files {
 		path := file
diff --git a/cue/load/read_test.go b/cue/load/read_test.go
index 372ebe8..826ed19 100644
--- a/cue/load/read_test.go
+++ b/cue/load/read_test.go
@@ -55,15 +55,13 @@
 		import "x"
 		import _ "x"
 		import a "x"
-		
-		/* comment */
-		
+
 		import (
-			"x" /* comment */
+			"x"
 			_ "x"
 			a "x" // comment
 			` + quote + `x` + quote + `
-			_ /*comment*/ ` + quote + `x` + quote + `
+			_ ` + quote + `x` + quote + `
 			a ` + quote + `x` + quote + `
 		)
 		import (
@@ -94,12 +92,6 @@
 	{
 		`// foo
 
-		/* bar */
-
-		/* quux */ // baz
-		
-		/*/ zot */
-
 		// asdf
 		â„™Hello, world`,
 		"",
diff --git a/cue/load/search.go b/cue/load/search.go
index bd649d3..18635a3 100644
--- a/cue/load/search.go
+++ b/cue/load/search.go
@@ -179,7 +179,7 @@
 
 		if !top {
 			// Ignore other modules found in subdirectories.
-			if _, err := c.fileSystem.stat(filepath.Join(path, modFile)); err == nil {
+			if _, err := c.fileSystem.stat(filepath.Join(path, modDir)); err == nil {
 				return skipDir
 			}
 		}
diff --git a/doc/tutorial/kubernetes/tut_test.go b/doc/tutorial/kubernetes/tut_test.go
index 9e21d89..4509fd3 100644
--- a/doc/tutorial/kubernetes/tut_test.go
+++ b/doc/tutorial/kubernetes/tut_test.go
@@ -28,6 +28,7 @@
 	"strings"
 	"testing"
 
+	"cuelang.org/go/cue/load"
 	"cuelang.org/go/internal/copy"
 	"cuelang.org/go/internal/cuetest"
 	"github.com/kylelemons/godebug/diff"
@@ -85,7 +86,7 @@
 		}
 	} else {
 		// We only fetch new kubernetes files with when updating.
-		err := copy.Dir(filepath.Join("quick", "pkg"), filepath.Join(dir, "pkg"))
+		err := copy.Dir(load.GenPath("quick"), load.GenPath(dir))
 		if err != nil {
 			t.Fatal(err)
 		}
diff --git a/encoding/protobuf/protobuf.go b/encoding/protobuf/protobuf.go
index ee3cde3..a52df13 100644
--- a/encoding/protobuf/protobuf.go
+++ b/encoding/protobuf/protobuf.go
@@ -95,6 +95,7 @@
 	"cuelang.org/go/cue/build"
 	"cuelang.org/go/cue/errors"
 	"cuelang.org/go/cue/format"
+	"cuelang.org/go/cue/load"
 	"cuelang.org/go/cue/parser"
 	"cuelang.org/go/cue/token"
 	"github.com/mpvl/unique"
@@ -314,7 +315,7 @@
 	dir := b.root
 	path := importPath
 	if !strings.HasPrefix(path, b.module) {
-		dir = filepath.Join(dir, "pkg", path)
+		dir = filepath.Join(load.GenPath(dir), path)
 	} else {
 		dir = filepath.Join(dir, path[len(b.module)+1:])
 		want := filepath.Dir(p.file.Filename)
diff --git a/encoding/protobuf/testdata/istio.io/api/pkg/github.com/gogo/protobuf/gogoproto/gogo_proto_gen.cue b/encoding/protobuf/testdata/istio.io/api/cue.mod/gen/github.com/gogo/protobuf/gogoproto/gogo_proto_gen.cue
similarity index 100%
rename from encoding/protobuf/testdata/istio.io/api/pkg/github.com/gogo/protobuf/gogoproto/gogo_proto_gen.cue
rename to encoding/protobuf/testdata/istio.io/api/cue.mod/gen/github.com/gogo/protobuf/gogoproto/gogo_proto_gen.cue
diff --git a/encoding/protobuf/testdata/istio.io/api/pkg/github.com/golang/protobuf/protoc-gen-go/descriptor/descriptor_proto_gen.cue b/encoding/protobuf/testdata/istio.io/api/cue.mod/gen/github.com/golang/protobuf/protoc-gen-go/descriptor/descriptor_proto_gen.cue
similarity index 100%
rename from encoding/protobuf/testdata/istio.io/api/pkg/github.com/golang/protobuf/protoc-gen-go/descriptor/descriptor_proto_gen.cue
rename to encoding/protobuf/testdata/istio.io/api/cue.mod/gen/github.com/golang/protobuf/protoc-gen-go/descriptor/descriptor_proto_gen.cue
diff --git a/encoding/protobuf/testdata/istio.io/api/pkg/google.golang.org/genproto/googleapis/rpc/status/status_proto_gen.cue b/encoding/protobuf/testdata/istio.io/api/cue.mod/gen/google.golang.org/genproto/googleapis/rpc/status/status_proto_gen.cue
similarity index 100%
rename from encoding/protobuf/testdata/istio.io/api/pkg/google.golang.org/genproto/googleapis/rpc/status/status_proto_gen.cue
rename to encoding/protobuf/testdata/istio.io/api/cue.mod/gen/google.golang.org/genproto/googleapis/rpc/status/status_proto_gen.cue
diff --git a/encoding/protobuf/testdata/istio.io/api/pkg/googleapis.com/acme/test/test/test_proto_gen.cue b/encoding/protobuf/testdata/istio.io/api/cue.mod/gen/googleapis.com/acme/test/test/test_proto_gen.cue
similarity index 100%
rename from encoding/protobuf/testdata/istio.io/api/pkg/googleapis.com/acme/test/test/test_proto_gen.cue
rename to encoding/protobuf/testdata/istio.io/api/cue.mod/gen/googleapis.com/acme/test/test/test_proto_gen.cue
diff --git a/encoding/protobuf/testdata/istio.io/api/pkg/googleapis.com/acme/test/test_proto_gen.cue b/encoding/protobuf/testdata/istio.io/api/cue.mod/gen/googleapis.com/acme/test/test_proto_gen.cue
similarity index 100%
rename from encoding/protobuf/testdata/istio.io/api/pkg/googleapis.com/acme/test/test_proto_gen.cue
rename to encoding/protobuf/testdata/istio.io/api/cue.mod/gen/googleapis.com/acme/test/test_proto_gen.cue