cmd/go: fix error for empty packages referenced with relative paths

'go build' now reports a more useful error when a relative path on the
command line points to a directory that doesn't exist or a directory
without .go files. Errors are generated by go/build.Context.ImportDir
instead of a vague call to base.Fatalf in modload.

Fixes #35414

Change-Id: I2642230c5e409107b98bb6d6c3a484d8d25b4147
Reviewed-on: https://go-review.googlesource.com/c/go/+/206902
Run-TryBot: Jay Conrod <jayconrod@google.com>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
diff --git a/src/cmd/go/internal/load/pkg.go b/src/cmd/go/internal/load/pkg.go
index 6a6f77e..8fc33e3 100644
--- a/src/cmd/go/internal/load/pkg.go
+++ b/src/cmd/go/internal/load/pkg.go
@@ -670,6 +670,11 @@
 	// we create from the full directory to the package.
 	// Otherwise it is the usual import path.
 	// For vendored imports, it is the expanded form.
+	//
+	// Note that when modules are enabled, local import paths are normally
+	// canonicalized by modload.ImportPaths before now. However, if there's an
+	// error resolving a local path, it will be returned untransformed
+	// so that 'go list -e' reports something useful.
 	importKey := importSpec{
 		path:        path,
 		parentPath:  parentPath,
diff --git a/src/cmd/go/internal/modload/build.go b/src/cmd/go/internal/modload/build.go
index 352ec73..5a281a9 100644
--- a/src/cmd/go/internal/modload/build.go
+++ b/src/cmd/go/internal/modload/build.go
@@ -45,11 +45,19 @@
 	return ""
 }
 
+// PackageModuleInfo returns information about the module that provides
+// a given package. If modules are not enabled or if the package is in the
+// standard library or if the package was not successfully loaded with
+// ImportPaths or a similar loading function, nil is returned.
 func PackageModuleInfo(pkgpath string) *modinfo.ModulePublic {
 	if isStandardImportPath(pkgpath) || !Enabled() {
 		return nil
 	}
-	return moduleInfo(findModule(pkgpath, pkgpath), true)
+	m, ok := findModule(pkgpath)
+	if !ok {
+		return nil
+	}
+	return moduleInfo(m, true)
 }
 
 func ModuleInfo(path string) *modinfo.ModulePublic {
@@ -199,12 +207,11 @@
 	if isStandardImportPath(path) || !Enabled() {
 		return ""
 	}
-
-	target := findModule(path, path)
+	target := mustFindModule(path, path)
 	mdeps := make(map[module.Version]bool)
 	for _, dep := range deps {
 		if !isStandardImportPath(dep) {
-			mdeps[findModule(path, dep)] = true
+			mdeps[mustFindModule(path, dep)] = true
 		}
 	}
 	var mods []module.Version
@@ -239,9 +246,12 @@
 	return buf.String()
 }
 
-// findModule returns the module containing the package at path,
-// needed to build the package at target.
-func findModule(target, path string) module.Version {
+// mustFindModule is like findModule, but it calls base.Fatalf if the
+// module can't be found.
+//
+// TODO(jayconrod): remove this. Callers should use findModule and return
+// errors instead of relying on base.Fatalf.
+func mustFindModule(target, path string) module.Version {
 	pkg, ok := loaded.pkgCache.Get(path).(*loadPkg)
 	if ok {
 		if pkg.err != nil {
@@ -261,6 +271,20 @@
 	panic("unreachable")
 }
 
+// findModule searches for the module that contains the package at path.
+// If the package was loaded with ImportPaths or one of the other loading
+// functions, its containing module and true are returned. Otherwise,
+// module.Version{} and false are returend.
+func findModule(path string) (module.Version, bool) {
+	if pkg, ok := loaded.pkgCache.Get(path).(*loadPkg); ok {
+		return pkg.mod, pkg.mod != module.Version{}
+	}
+	if path == "command-line-arguments" {
+		return Target, true
+	}
+	return module.Version{}, false
+}
+
 func ModInfoProg(info string, isgccgo bool) []byte {
 	// Inject a variable with the debug information as runtime.modinfo,
 	// but compile it in package main so that it is specific to the binary.
diff --git a/src/cmd/go/internal/modload/load.go b/src/cmd/go/internal/modload/load.go
index ca6c260..2df7bd0 100644
--- a/src/cmd/go/internal/modload/load.go
+++ b/src/cmd/go/internal/modload/load.go
@@ -94,11 +94,11 @@
 				pkgs := m.Pkgs
 				m.Pkgs = m.Pkgs[:0]
 				for _, pkg := range pkgs {
-					dir := pkg
-					if !filepath.IsAbs(dir) {
+					var dir string
+					if !filepath.IsAbs(pkg) {
 						dir = filepath.Join(base.Cwd, pkg)
 					} else {
-						dir = filepath.Clean(dir)
+						dir = filepath.Clean(pkg)
 					}
 
 					// golang.org/issue/32917: We should resolve a relative path to a
diff --git a/src/cmd/go/testdata/script/mod_empty_err.txt b/src/cmd/go/testdata/script/mod_empty_err.txt
new file mode 100644
index 0000000..729f848
--- /dev/null
+++ b/src/cmd/go/testdata/script/mod_empty_err.txt
@@ -0,0 +1,36 @@
+# This test checks error messages for non-existant packages in module mode.
+# Veries golang.org/issue/35414
+env GO111MODULE=on
+cd $WORK
+
+go list -e -f {{.Error}} .
+stdout 'package \.: no Go files in \$WORK'
+
+go list -e -f {{.Error}} ./empty
+stdout 'package \./empty: no Go files in \$WORK[/\\]empty'
+
+go list -e -f {{.Error}} ./exclude
+stdout 'package \./exclude: build constraints exclude all Go files in \$WORK[/\\]exclude'
+
+go list -e -f {{.Error}} ./missing
+stdout 'package \./missing: cannot find package "." in:\s*\$WORK[/\\]missing'
+
+# use 'go build -n' because 'go list' reports no error.
+! go build -n ./testonly
+stderr 'example.com/m/testonly: no non-test Go files in \$WORK[/\\]testonly'
+
+-- $WORK/go.mod --
+module example.com/m
+
+go 1.14
+
+-- $WORK/empty/empty.txt --
+-- $WORK/exclude/exclude.go --
+// +build exclude
+
+package exclude
+-- $WORK/testonly/testonly_test.go --
+package testonly_test
+-- $WORK/excluded-stdout --
+package ./excluded: cannot find package "." in:
+	$WORK/excluded