all: rework goimports environment, support GOMODCACHE

This CL got away from me a little.

For a number of reasons, the existing goimports API of passing in values
for various GO* values was not working. For one, the number of necessary
variables kept growing. For another, we tried to avoid calling `go env`
in GOPATH mode by using `build.Default`, but that turns out to be buggy;
see golang/go#39838. And finally, it created massive confusion about
whether the values were intended to be read from the OS environment, or
fully evaluated by the `go` command.

There are only two users of the internal imports API, so there really
shouldn't need to be more than two modes. For the command line tool, we
have to call `go env` to deal with the `go/build` bug. So we just do it.
Tests use that same path, but can augment the enviroment to set
themselves up. In contrast, `gopls` needs to fully specify the
environment. It can simply pass in the fully evaluated GO* values.

Finally, make the change I was actually here to make: propagate
GOMODCACHE and use it where appropriate.

Fixes golang/go#39761.

Change-Id: I720c69839d91d66d98e94dfc5f065ba0279c5542
Reviewed-on: https://go-review.googlesource.com/c/tools/+/239754
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/cmd/goimports/goimports.go b/cmd/goimports/goimports.go
index c195caa..f7fc3d5 100644
--- a/cmd/goimports/goimports.go
+++ b/cmd/goimports/goimports.go
@@ -10,7 +10,6 @@
 	"errors"
 	"flag"
 	"fmt"
-	"go/build"
 	"go/scanner"
 	"io"
 	"io/ioutil"
@@ -43,15 +42,6 @@
 		TabIndent: true,
 		Comments:  true,
 		Fragment:  true,
-		// This environment, and its caches, will be reused for the whole run.
-		Env: &imports.ProcessEnv{
-			GOPATH:      build.Default.GOPATH,
-			GOROOT:      build.Default.GOROOT,
-			GOFLAGS:     os.Getenv("GOFLAGS"),
-			GO111MODULE: os.Getenv("GO111MODULE"),
-			GOPROXY:     os.Getenv("GOPROXY"),
-			GOSUMDB:     os.Getenv("GOSUMDB"),
-		},
 	}
 	exitCode = 0
 )
diff --git a/imports/forward.go b/imports/forward.go
index dbe5b49..83f4e44 100644
--- a/imports/forward.go
+++ b/imports/forward.go
@@ -3,9 +3,7 @@
 package imports // import "golang.org/x/tools/imports"
 
 import (
-	"go/build"
 	"log"
-	"os"
 
 	intimp "golang.org/x/tools/internal/imports"
 )
@@ -42,12 +40,6 @@
 	}
 	intopt := &intimp.Options{
 		Env: &intimp.ProcessEnv{
-			GOPATH:      build.Default.GOPATH,
-			GOROOT:      build.Default.GOROOT,
-			GOFLAGS:     os.Getenv("GOFLAGS"),
-			GO111MODULE: os.Getenv("GO111MODULE"),
-			GOPROXY:     os.Getenv("GOPROXY"),
-			GOSUMDB:     os.Getenv("GOSUMDB"),
 			LocalPrefix: LocalPrefix,
 		},
 		AllErrors:  opt.AllErrors,
diff --git a/internal/gocommand/invoke.go b/internal/gocommand/invoke.go
index 83b654b..f516e17 100644
--- a/internal/gocommand/invoke.go
+++ b/internal/gocommand/invoke.go
@@ -185,7 +185,6 @@
 		cmd.Env = append(cmd.Env, "PWD="+i.WorkingDir)
 		cmd.Dir = i.WorkingDir
 	}
-
 	defer func(start time.Time) { log("%s for %v", time.Since(start), cmdDebugStr(cmd)) }(time.Now())
 
 	return runCmdContext(ctx, cmd)
diff --git a/internal/imports/fix.go b/internal/imports/fix.go
index 36292d7..2c70307 100644
--- a/internal/imports/fix.go
+++ b/internal/imports/fix.go
@@ -7,6 +7,7 @@
 import (
 	"bytes"
 	"context"
+	"encoding/json"
 	"fmt"
 	"go/ast"
 	"go/build"
@@ -598,7 +599,7 @@
 	// Start off with the standard library.
 	for importPath, exports := range stdlib {
 		p := &pkg{
-			dir:             filepath.Join(env.GOROOT, "src", importPath),
+			dir:             filepath.Join(env.goroot(), "src", importPath),
 			importPathShort: importPath,
 			packageName:     path.Base(importPath),
 			relevance:       MaxRelevance,
@@ -743,6 +744,8 @@
 	return getCandidatePkgs(ctx, callback, filename, filePkg, env)
 }
 
+var RequiredGoEnvVars = []string{"GO111MODULE", "GOFLAGS", "GOINSECURE", "GOMOD", "GOMODCACHE", "GONOPROXY", "GONOSUMDB", "GOPATH", "GOPROXY", "GOROOT", "GOSUMDB"}
+
 // ProcessEnv contains environment variables and settings that affect the use of
 // the go command, the go/build package, etc.
 type ProcessEnv struct {
@@ -752,10 +755,13 @@
 
 	BuildFlags []string
 
-	// If non-empty, these will be used instead of the
-	// process-wide values.
-	GOPATH, GOROOT, GO111MODULE, GOPROXY, GOFLAGS, GOSUMDB string
-	WorkingDir                                             string
+	// Env overrides the OS environment, and can be used to specify
+	// GOPROXY, GO111MODULE, etc. PATH cannot be set here, because
+	// exec.Command will not honor it.
+	// Specifying all of RequiredGoEnvVars avoids a call to `go env`.
+	Env map[string]string
+
+	WorkingDir string
 
 	// If Logf is non-nil, debug logging is enabled through this function.
 	Logf func(format string, args ...interface{})
@@ -763,6 +769,22 @@
 	resolver Resolver
 }
 
+func (e *ProcessEnv) goroot() string {
+	return e.mustGetEnv("GOROOT")
+}
+
+func (e *ProcessEnv) gopath() string {
+	return e.mustGetEnv("GOPATH")
+}
+
+func (e *ProcessEnv) mustGetEnv(k string) string {
+	v, ok := e.Env[k]
+	if !ok {
+		panic(fmt.Sprintf("%v not set in evaluated environment", k))
+	}
+	return v
+}
+
 // CopyConfig copies the env's configuration into a new env.
 func (e *ProcessEnv) CopyConfig() *ProcessEnv {
 	copy := *e
@@ -770,21 +792,40 @@
 	return &copy
 }
 
-func (e *ProcessEnv) env() []string {
-	env := os.Environ()
-	add := func(k, v string) {
-		if v != "" {
-			env = append(env, k+"="+v)
+func (e *ProcessEnv) init() error {
+	foundAllRequired := true
+	for _, k := range RequiredGoEnvVars {
+		if _, ok := e.Env[k]; !ok {
+			foundAllRequired = false
+			break
 		}
 	}
-	add("GOPATH", e.GOPATH)
-	add("GOROOT", e.GOROOT)
-	add("GO111MODULE", e.GO111MODULE)
-	add("GOPROXY", e.GOPROXY)
-	add("GOFLAGS", e.GOFLAGS)
-	add("GOSUMDB", e.GOSUMDB)
-	if e.WorkingDir != "" {
-		add("PWD", e.WorkingDir)
+	if foundAllRequired {
+		return nil
+	}
+
+	if e.Env == nil {
+		e.Env = map[string]string{}
+	}
+
+	goEnv := map[string]string{}
+	stdout, err := e.invokeGo(context.TODO(), "env", append([]string{"-json"}, RequiredGoEnvVars...)...)
+	if err != nil {
+		return err
+	}
+	if err := json.Unmarshal(stdout.Bytes(), &goEnv); err != nil {
+		return err
+	}
+	for k, v := range goEnv {
+		e.Env[k] = v
+	}
+	return nil
+}
+
+func (e *ProcessEnv) env() []string {
+	var env []string // the gocommand package will prepend os.Environ.
+	for k, v := range e.Env {
+		env = append(env, k+"="+v)
 	}
 	return env
 }
@@ -793,8 +834,7 @@
 	if e.resolver != nil {
 		return e.resolver
 	}
-	out, err := e.invokeGo(context.TODO(), "env", "GOMOD")
-	if err != nil || len(bytes.TrimSpace(out.Bytes())) == 0 {
+	if len(e.Env["GOMOD"]) == 0 {
 		e.resolver = newGopathResolver(e)
 		return e.resolver
 	}
@@ -804,8 +844,8 @@
 
 func (e *ProcessEnv) buildContext() *build.Context {
 	ctx := build.Default
-	ctx.GOROOT = e.GOROOT
-	ctx.GOPATH = e.GOPATH
+	ctx.GOROOT = e.goroot()
+	ctx.GOPATH = e.gopath()
 
 	// As of Go 1.14, build.Context has a Dir field
 	// (see golang.org/issue/34860).
@@ -839,7 +879,7 @@
 func addStdlibCandidates(pass *pass, refs references) {
 	add := func(pkg string) {
 		// Prevent self-imports.
-		if path.Base(pkg) == pass.f.Name.Name && filepath.Join(pass.env.GOROOT, "src", pkg) == pass.srcDir {
+		if path.Base(pkg) == pass.f.Name.Name && filepath.Join(pass.env.goroot(), "src", pkg) == pass.srcDir {
 			return
 		}
 		exports := copyExports(stdlib[pkg])
diff --git a/internal/imports/fix_test.go b/internal/imports/fix_test.go
index 1e78687..7800cce 100644
--- a/internal/imports/fix_test.go
+++ b/internal/imports/fix_test.go
@@ -8,7 +8,6 @@
 	"context"
 	"flag"
 	"fmt"
-	"go/build"
 	"log"
 	"path/filepath"
 	"reflect"
@@ -1562,12 +1561,13 @@
 
 	testConfig{
 		module: packagestest.Module{
-			Name: "ignored.com",
+			Name:  "ignored.com",
+			Files: fm{"x.go": "package x"},
 		},
 	}.test(t, func(t *goimportTest) {
 		// Run in GOROOT/src so that the std module shows up in go list -m all.
-		t.env.WorkingDir = filepath.Join(t.env.GOROOT, "src")
-		got, err := t.processNonModule(filepath.Join(t.env.GOROOT, "src/x.go"), []byte(input), nil)
+		t.env.WorkingDir = filepath.Join(t.env.goroot(), "src")
+		got, err := t.processNonModule(filepath.Join(t.env.goroot(), "src/x.go"), []byte(input), nil)
 		if err != nil {
 			t.Fatalf("Process() = %v", err)
 		}
@@ -1585,10 +1585,11 @@
 
 	testConfig{
 		module: packagestest.Module{
-			Name: "ignored.com",
+			Name:  "ignored.com",
+			Files: fm{"x.go": "package x"},
 		},
 	}.test(t, func(t *goimportTest) {
-		got, err := t.processNonModule(filepath.Join(t.env.GOROOT, "src/crypto/ecdsa/foo.go"), []byte(input), nil)
+		got, err := t.processNonModule(filepath.Join(t.env.goroot(), "src/crypto/ecdsa/foo.go"), []byte(input), nil)
 		if err != nil {
 			t.Fatalf("Process() = %v", err)
 		}
@@ -1623,33 +1624,26 @@
 			exported := packagestest.Export(t, exporter, c.modules)
 			defer exported.Cleanup()
 
-			env := make(map[string]string)
+			env := map[string]string{}
 			for _, kv := range exported.Config.Env {
-				split := strings.Split(kv, "=")
-				k, v := split[0], split[1]
-				env[k] = v
+				split := strings.SplitN(kv, "=", 2)
+				env[split[0]] = split[1]
 			}
-
 			it := &goimportTest{
 				T: t,
 				env: &ProcessEnv{
-					GOROOT:      env["GOROOT"],
-					GOPATH:      env["GOPATH"],
-					GO111MODULE: env["GO111MODULE"],
-					GOSUMDB:     env["GOSUMDB"],
+					Env:         env,
 					WorkingDir:  exported.Config.Dir,
 					GocmdRunner: &gocommand.Runner{},
 				},
 				exported: exported,
 			}
+			if err := it.env.init(); err != nil {
+				t.Fatal(err)
+			}
 			if *testDebug {
 				it.env.Logf = log.Printf
 			}
-			if it.env.GOROOT == "" {
-				// packagestest clears out GOROOT to work around https://golang.org/issue/32849,
-				// which isn't relevant here. Fill it back in so we can find the standard library.
-				it.env.GOROOT = build.Default.GOROOT
-			}
 			fn(it)
 		})
 	}
diff --git a/internal/imports/imports.go b/internal/imports/imports.go
index f43d6b2..04ecdfd 100644
--- a/internal/imports/imports.go
+++ b/internal/imports/imports.go
@@ -14,14 +14,12 @@
 	"context"
 	"fmt"
 	"go/ast"
-	"go/build"
 	"go/format"
 	"go/parser"
 	"go/printer"
 	"go/token"
 	"io"
 	"io/ioutil"
-	"os"
 	"regexp"
 	"strconv"
 	"strings"
@@ -146,19 +144,16 @@
 
 	// Set the env if the user has not provided it.
 	if opt.Env == nil {
-		opt.Env = &ProcessEnv{
-			GOPATH:      build.Default.GOPATH,
-			GOROOT:      build.Default.GOROOT,
-			GOFLAGS:     os.Getenv("GOFLAGS"),
-			GO111MODULE: os.Getenv("GO111MODULE"),
-			GOPROXY:     os.Getenv("GOPROXY"),
-			GOSUMDB:     os.Getenv("GOSUMDB"),
-		}
+		opt.Env = &ProcessEnv{}
 	}
 	// Set the gocmdRunner if the user has not provided it.
 	if opt.Env.GocmdRunner == nil {
 		opt.Env.GocmdRunner = &gocommand.Runner{}
 	}
+	if err := opt.Env.init(); err != nil {
+		return nil, nil, err
+	}
+
 	if src == nil {
 		b, err := ioutil.ReadFile(filename)
 		if err != nil {
diff --git a/internal/imports/imports_test.go b/internal/imports/imports_test.go
index 70f66ab..ad16622 100644
--- a/internal/imports/imports_test.go
+++ b/internal/imports/imports_test.go
@@ -1,7 +1,6 @@
 package imports
 
 import (
-	"go/build"
 	"os"
 	"testing"
 
@@ -32,8 +31,6 @@
 			name: "default",
 			opt: &Options{
 				Env: &ProcessEnv{
-					GOPATH:      build.Default.GOPATH,
-					GOROOT:      build.Default.GOROOT,
 					GocmdRunner: &gocommand.Runner{},
 				},
 				Comments:  true,
diff --git a/internal/imports/mod.go b/internal/imports/mod.go
index 4e816e8..664fbbf 100644
--- a/internal/imports/mod.go
+++ b/internal/imports/mod.go
@@ -79,7 +79,11 @@
 		r.initAllMods()
 	}
 
-	r.moduleCacheDir = filepath.Join(filepath.SplitList(r.env.GOPATH)[0], "/pkg/mod")
+	if gmc := r.env.Env["GOMODCACHE"]; gmc != "" {
+		r.moduleCacheDir = gmc
+	} else {
+		r.moduleCacheDir = filepath.Join(filepath.SplitList(r.env.gopath())[0], "/pkg/mod")
+	}
 
 	sort.Slice(r.modsByModPath, func(i, j int) bool {
 		count := func(x int) int {
@@ -95,7 +99,7 @@
 	})
 
 	r.roots = []gopathwalk.Root{
-		{filepath.Join(r.env.GOROOT, "/src"), gopathwalk.RootGOROOT},
+		{filepath.Join(r.env.goroot(), "/src"), gopathwalk.RootGOROOT},
 	}
 	if r.main != nil {
 		r.roots = append(r.roots, gopathwalk.Root{r.main.Dir, gopathwalk.RootCurrentModule})
diff --git a/internal/imports/mod_test.go b/internal/imports/mod_test.go
index fa1a9c6..9d26aff 100644
--- a/internal/imports/mod_test.go
+++ b/internal/imports/mod_test.go
@@ -4,7 +4,6 @@
 	"archive/zip"
 	"context"
 	"fmt"
-	"go/build"
 	"io/ioutil"
 	"log"
 	"os"
@@ -240,7 +239,7 @@
 
 	// Clear out the resolver's cache, since we've changed the environment.
 	mt.resolver = newModuleResolver(mt.env)
-	mt.env.GOFLAGS = "-mod=vendor"
+	mt.env.Env["GOFLAGS"] = "-mod=vendor"
 	mt.assertModuleFoundInDir("rsc.io/sampler", "sampler", `/vendor/`)
 }
 
@@ -686,17 +685,21 @@
 	}
 
 	env := &ProcessEnv{
-		GOROOT:      build.Default.GOROOT,
-		GOPATH:      filepath.Join(dir, "gopath"),
-		GO111MODULE: "on",
-		GOPROXY:     proxydir.ToURL(proxyDir),
-		GOSUMDB:     "off",
+		Env: map[string]string{
+			"GOPATH":      filepath.Join(dir, "gopath"),
+			"GO111MODULE": "on",
+			"GOSUMDB":     "off",
+			"GOPROXY":     proxydir.ToURL(proxyDir),
+		},
 		WorkingDir:  filepath.Join(mainDir, wd),
 		GocmdRunner: &gocommand.Runner{},
 	}
 	if *testDebug {
 		env.Logf = log.Printf
 	}
+	if err := env.init(); err != nil {
+		t.Fatal(err)
+	}
 
 	// go mod download gets mad if we don't have a go.mod, so make sure we do.
 	_, err = os.Stat(filepath.Join(mainDir, "go.mod"))
@@ -838,7 +841,7 @@
 import _ "rsc.io/quote"
 `, "")
 	defer mt.cleanup()
-	want := filepath.Join(mt.resolver.env.GOPATH, "pkg/mod", "rsc.io/quote@v1.5.2")
+	want := filepath.Join(mt.resolver.env.gopath(), "pkg/mod", "rsc.io/quote@v1.5.2")
 
 	found := mt.assertScanFinds("rsc.io/quote", "quote")
 	modDir, _ := mt.resolver.modInfo(found.dir)
@@ -864,13 +867,17 @@
 		t.Fatal(err)
 	}
 	env := &ProcessEnv{
-		GOROOT:      build.Default.GOROOT,
-		GOPATH:      filepath.Join(dir, "gopath"),
-		GO111MODULE: "on",
-		GOSUMDB:     "off",
+		Env: map[string]string{
+			"GOPATH":      filepath.Join(dir, "gopath"),
+			"GO111MODULE": "on",
+			"GOSUMDB":     "off",
+		},
 		GocmdRunner: &gocommand.Runner{},
 		WorkingDir:  dir,
 	}
+	if err := env.init(); err != nil {
+		t.Fatal(err)
+	}
 	resolver := newModuleResolver(env)
 	scanToSlice(resolver, nil)
 }
@@ -938,11 +945,12 @@
 func BenchmarkScanModCache(b *testing.B) {
 	testenv.NeedsGo1Point(b, 11)
 	env := &ProcessEnv{
-		GOPATH:      build.Default.GOPATH,
-		GOROOT:      build.Default.GOROOT,
 		GocmdRunner: &gocommand.Runner{},
 		Logf:        log.Printf,
 	}
+	if err := env.init(); err != nil {
+		b.Fatal(err)
+	}
 	exclude := []gopathwalk.RootType{gopathwalk.RootGOROOT}
 	scanToSlice(env.GetResolver(), exclude)
 	b.ResetTimer()
diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go
index 8faab5b..f78dbcd 100644
--- a/internal/lsp/cache/view.go
+++ b/internal/lsp/cache/view.go
@@ -114,8 +114,10 @@
 	// build system.
 	goCommand bool
 
-	// `go env` variables that need to be tracked.
-	gopath, gocache, goprivate string
+	// `go env` variables that need to be tracked by gopls.
+	gocache, gomodcache, gopath, goprivate string
+
+	goEnv map[string]string
 
 	// gocmdRunner guards go command calls from concurrency errors.
 	gocmdRunner *gocommand.Runner
@@ -464,13 +466,16 @@
 	cleanup = func() {}
 
 	v.optionsMu.Lock()
-	env, buildFlags := v.envLocked()
+	_, buildFlags := v.envLocked()
 	localPrefix, verboseOutput := v.options.LocalPrefix, v.options.VerboseOutput
 	v.optionsMu.Unlock()
 
 	pe := v.processEnv
-
+	pe.LocalPrefix = localPrefix
+	pe.GocmdRunner = v.gocmdRunner
 	pe.BuildFlags = buildFlags
+	pe.Env = v.goEnv
+	pe.WorkingDir = v.folder.Filename()
 
 	// Add -modfile to the build flags, if we are using it.
 	if modFH != nil {
@@ -482,46 +487,18 @@
 		pe.BuildFlags = append(pe.BuildFlags, fmt.Sprintf("-modfile=%s", tmpURI.Filename()))
 	}
 
-	pe.WorkingDir = v.folder.Filename()
-	pe.LocalPrefix = localPrefix
-	pe.GocmdRunner = v.gocmdRunner
-
 	if verboseOutput {
 		pe.Logf = func(format string, args ...interface{}) {
 			event.Log(ctx, fmt.Sprintf(format, args...))
 		}
 	}
-	for _, kv := range env {
-		split := strings.SplitN(kv, "=", 2)
-		if len(split) < 2 {
-			continue
-		}
-		switch split[0] {
-		case "GOPATH":
-			pe.GOPATH = split[1]
-		case "GOROOT":
-			pe.GOROOT = split[1]
-		case "GO111MODULE":
-			pe.GO111MODULE = split[1]
-		case "GOPROXY":
-			pe.GOPROXY = split[1]
-		case "GOFLAGS":
-			pe.GOFLAGS = split[1]
-		case "GOSUMDB":
-			pe.GOSUMDB = split[1]
-		}
-	}
-	if pe.GOPATH == "" {
-		return nil, fmt.Errorf("no GOPATH for view %s", v.folder)
-	}
 	return cleanup, nil
 }
 
 // envLocked returns the environment and build flags for the current view.
 // It assumes that the caller is holding the view's optionsMu.
 func (v *View) envLocked() ([]string, []string) {
-	env := []string{fmt.Sprintf("GOPATH=%s", v.gopath)}
-	env = append(env, v.options.Env...)
+	env := append([]string{}, v.options.Env...)
 	buildFlags := append([]string{}, v.options.BuildFlags...)
 	return env, buildFlags
 }
@@ -650,8 +627,7 @@
 		}
 	} else {
 		mainMod := filepath.Dir(v.modURI.Filename())
-		modCache := filepath.Join(filepath.SplitList(v.gopath)[0], "/pkg/mod")
-		prefixes = []string{mainMod, modCache}
+		prefixes = []string{mainMod, v.gomodcache}
 	}
 
 	for _, prefix := range prefixes {
@@ -747,11 +723,10 @@
 		return fmt.Errorf("invalid workspace configuration: %w", err)
 	}
 	// Make sure to get the `go env` before continuing with initialization.
-	gomod, err := v.getGoEnv(ctx, env)
+	modFile, err := v.setGoEnv(ctx, env)
 	if err != nil {
 		return err
 	}
-	modFile := strings.TrimSpace(gomod)
 	if modFile == os.DevNull {
 		return nil
 	}
@@ -813,80 +788,52 @@
 	return err == nil && !strings.HasPrefix(rel, "..")
 }
 
-// getGoEnv sets the view's build information's GOPATH, GOCACHE, GOPRIVATE, and
-// GOPACKAGESDRIVER values.  It also returns the view's GOMOD value, which need
-// not be cached.
-func (v *View) getGoEnv(ctx context.Context, env []string) (string, error) {
-	var gocache, gopath, gopackagesdriver, goprivate bool
-	isGoCommand := func(gopackagesdriver string) bool {
-		return gopackagesdriver == "" || gopackagesdriver == "off"
+// setGoEnv sets the view's various GO* values. It also returns the view's
+// GOMOD value, which need not be cached.
+func (v *View) setGoEnv(ctx context.Context, configEnv []string) (string, error) {
+	var gomod string
+	vars := map[string]*string{
+		"GOCACHE":    &v.gocache,
+		"GOPATH":     &v.gopath,
+		"GOPRIVATE":  &v.goprivate,
+		"GOMODCACHE": &v.gomodcache,
+		"GOMOD":      &gomod,
 	}
-	for _, e := range env {
-		split := strings.Split(e, "=")
-		if len(split) != 2 {
-			continue
-		}
-		switch split[0] {
-		case "GOCACHE":
-			v.gocache = split[1]
-			gocache = true
-		case "GOPATH":
-			v.gopath = split[1]
-			gopath = true
-		case "GOPRIVATE":
-			v.goprivate = split[1]
-			goprivate = true
-		case "GOPACKAGESDRIVER":
-			v.goCommand = isGoCommand(split[1])
-			gopackagesdriver = true
-		}
+	// We can save ~200 ms by requesting only the variables we care about.
+	args := append([]string{"-json"}, imports.RequiredGoEnvVars...)
+	for k := range vars {
+		args = append(args, k)
+	}
+
+	inv := gocommand.Invocation{
+		Verb:       "env",
+		Args:       args,
+		Env:        configEnv,
+		WorkingDir: v.Folder().Filename(),
 	}
 	// Don't go through runGoCommand, as we don't need a temporary -modfile to
 	// run `go env`.
-	inv := gocommand.Invocation{
-		Verb:       "env",
-		Args:       []string{"-json"},
-		Env:        env,
-		WorkingDir: v.Folder().Filename(),
-	}
 	stdout, err := v.gocmdRunner.Run(ctx, inv)
 	if err != nil {
 		return "", err
 	}
-	envMap := make(map[string]string)
-	decoder := json.NewDecoder(stdout)
-	if err := decoder.Decode(&envMap); err != nil {
+	if err := json.Unmarshal(stdout.Bytes(), &v.goEnv); err != nil {
 		return "", err
 	}
-	if !gopath {
-		if gopath, ok := envMap["GOPATH"]; ok {
-			v.gopath = gopath
-		} else {
-			return "", errors.New("unable to determine GOPATH")
-		}
-	}
-	if !gocache {
-		if gocache, ok := envMap["GOCACHE"]; ok {
-			v.gocache = gocache
-		} else {
-			return "", errors.New("unable to determine GOCACHE")
-		}
-	}
-	if !goprivate {
-		if goprivate, ok := envMap["GOPRIVATE"]; ok {
-			v.goprivate = goprivate
-		}
-		// No error here: GOPRIVATE is not essential.
-	}
-	// The value of GOPACKAGESDRIVER is not returned through the go command.
-	if !gopackagesdriver {
-		v.goCommand = isGoCommand(os.Getenv("GOPACKAGESDRIVER"))
-	}
-	if gomod, ok := envMap["GOMOD"]; ok {
-		return gomod, nil
-	}
-	return "", nil
 
+	for key, ptr := range vars {
+		*ptr = v.goEnv[key]
+	}
+
+	// Old versions of Go don't have GOMODCACHE, so emulate it.
+	if v.gomodcache == "" && v.gopath != "" {
+		v.gomodcache = filepath.Join(filepath.SplitList(v.gopath)[0], "pkg/mod")
+	}
+
+	// The value of GOPACKAGESDRIVER is not returned through the go command.
+	gopackagesdriver := os.Getenv("GOPACKAGESDRIVER")
+	v.goCommand = gopackagesdriver == "" || gopackagesdriver == "off"
+	return gomod, nil
 }
 
 func (v *View) IsGoPrivatePath(target string) bool {
diff --git a/internal/lsp/regtest/diagnostics_test.go b/internal/lsp/regtest/diagnostics_test.go
index 39eb67c..72a5313 100644
--- a/internal/lsp/regtest/diagnostics_test.go
+++ b/internal/lsp/regtest/diagnostics_test.go
@@ -418,7 +418,7 @@
 	})
 }
 
-// Tests golang/go#37984.
+// Tests golang/go#37984: GOPATH should be read from the go command.
 func TestNoGOPATH_Issue37984(t *testing.T) {
 	const missingImport = `
 -- main.go --
@@ -431,9 +431,8 @@
 	runner.Run(t, missingImport, func(t *testing.T, env *Env) {
 		env.OpenFile("main.go")
 		env.Await(env.DiagnosticAtRegexp("main.go", "fmt"))
-		if err := env.Editor.OrganizeImports(env.Ctx, "main.go"); err == nil {
-			t.Fatalf("organize imports should fail with an empty GOPATH")
-		}
+		env.SaveBuffer("main.go")
+		env.Await(EmptyDiagnostics("main.go"))
 	}, WithEditorConfig(fake.EditorConfig{Env: []string{"GOPATH="}}))
 }
 
diff --git a/internal/lsp/regtest/imports_test.go b/internal/lsp/regtest/imports_test.go
index fd35f34..0a66a21 100644
--- a/internal/lsp/regtest/imports_test.go
+++ b/internal/lsp/regtest/imports_test.go
@@ -1,10 +1,19 @@
 package regtest
 
 import (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
 	"testing"
+	"time"
+
+	"golang.org/x/tools/internal/lsp/fake"
+	"golang.org/x/tools/internal/testenv"
 )
 
-const needs = `
+func TestIssue38815(t *testing.T) {
+	const needs = `
 -- go.mod --
 module foo
 
@@ -12,12 +21,12 @@
 package main
 func f() {}
 `
-const ntest = `package main
+	const ntest = `package main
 func TestZ(t *testing.T) {
 	f()
 }
 `
-const want = `package main
+	const want = `package main
 
 import "testing"
 
@@ -26,7 +35,6 @@
 }
 `
 
-func TestIssue38815(t *testing.T) {
 	// it was returning
 	// "package main\nimport \"testing\"\npackage main..."
 	runner.Run(t, needs, func(t *testing.T, env *Env) {
@@ -39,7 +47,8 @@
 	})
 }
 
-const vim1 = `package main
+func TestVim1(t *testing.T) {
+	const vim1 = `package main
 
 import "fmt"
 
@@ -53,10 +62,9 @@
 }
 `
 
-func TestVim1(t *testing.T) {
 	// The file remains unchanged, but if there are any CodeActions returned, they confuse vim.
 	// Therefore check for no CodeActions
-	runner.Run(t, vim1, func(t *testing.T, env *Env) {
+	runner.Run(t, "", func(t *testing.T, env *Env) {
 		env.CreateBuffer("main.go", vim1)
 		env.OrganizeImports("main.go")
 		actions := env.CodeAction("main.go")
@@ -73,7 +81,8 @@
 	})
 }
 
-const vim2 = `package main
+func TestVim2(t *testing.T) {
+	const vim2 = `package main
 
 import (
 	"fmt"
@@ -88,8 +97,7 @@
 }
 `
 
-func TestVim2(t *testing.T) {
-	runner.Run(t, vim1, func(t *testing.T, env *Env) {
+	runner.Run(t, "", func(t *testing.T, env *Env) {
 		env.CreateBuffer("main.go", vim2)
 		env.OrganizeImports("main.go")
 		actions := env.CodeAction("main.go")
@@ -98,3 +106,51 @@
 		}
 	})
 }
+
+func TestGOMODCACHE(t *testing.T) {
+	const proxy = `
+-- example.com@v1.2.3/go.mod --
+module example.com
+
+go 1.12
+-- example.com@v1.2.3/x/x.go --
+package x
+
+const X = 1
+-- example.com@v1.2.3/y/y.go --
+package y
+
+const Y = 2
+`
+	const ws = `
+-- go.mod --
+module mod.com
+
+require example.com v1.2.3
+
+-- main.go --
+package main
+
+import "example.com/x"
+
+var _, _ = x.X, y.Y
+`
+	testenv.NeedsGo1Point(t, 15)
+
+	modcache, err := ioutil.TempDir("", "TestGOMODCACHE-modcache")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(modcache)
+
+	runner.Run(t, ws, func(t *testing.T, env *Env) {
+		env.OpenFile("main.go")
+		env.Await(env.DiagnosticAtRegexp("main.go", `y.Y`))
+		env.SaveBuffer("main.go")
+		env.Await(EmptyDiagnostics("main.go"))
+		path, _ := env.GoToDefinition("main.go", env.RegexpSearch("main.go", `y.(Y)`))
+		if !strings.HasPrefix(path, filepath.ToSlash(modcache)) {
+			t.Errorf("found module dependency outside of GOMODCACHE: got %v, wanted subdir of %v", path, filepath.ToSlash(modcache))
+		}
+	}, WithProxy(proxy), WithEditorConfig(fake.EditorConfig{Env: []string{"GOMODCACHE=" + modcache}}), WithTimeout(5*time.Second))
+}
diff --git a/internal/lsp/regtest/modfile_test.go b/internal/lsp/regtest/modfile_test.go
index 47a0ed2..188818e 100644
--- a/internal/lsp/regtest/modfile_test.go
+++ b/internal/lsp/regtest/modfile_test.go
@@ -45,7 +45,7 @@
 			env.DiagnosticAtRegexp("main.go", "\"example.com/blah\""),
 		)
 		if got := env.ReadWorkspaceFile("go.mod"); got != goModContent {
-			t.Fatalf("go.mod changed on disk:\n%s", tests.Diff(want, got))
+			t.Fatalf("go.mod changed on disk:\n%s", tests.Diff(goModContent, got))
 		}
 		// Save the buffer, which will format and organize imports.
 		// Confirm that the go.mod file still does not change.
@@ -54,7 +54,7 @@
 			env.DiagnosticAtRegexp("main.go", "\"example.com/blah\""),
 		)
 		if got := env.ReadWorkspaceFile("go.mod"); got != goModContent {
-			t.Fatalf("go.mod changed on disk:\n%s", tests.Diff(want, got))
+			t.Fatalf("go.mod changed on disk:\n%s", tests.Diff(goModContent, got))
 		}
 	}, WithProxy(proxy))
 }