cmd/go: add env -w and env -u to set and unset default env vars

Setting environment variables for go command configuration
is too difficult and system-specific. This CL adds go env -w,
to change the default settings more easily, in a portable way.
It also adds go env -u, to unset those changes.

See https://golang.org/design/30411-env for details.

Fixes #30411.

Change-Id: I36e83f55b666459f8f7f482432a4a6ee015da71d
Reviewed-on: https://go-review.googlesource.com/c/go/+/171137
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
diff --git a/src/cmd/go/alldocs.go b/src/cmd/go/alldocs.go
index 2cc00f2..d012235 100644
--- a/src/cmd/go/alldocs.go
+++ b/src/cmd/go/alldocs.go
@@ -368,7 +368,7 @@
 //
 // Usage:
 //
-// 	go env [-json] [var ...]
+// 	go env [-json] [-u] [-w] [var ...]
 //
 // Env prints Go environment information.
 //
@@ -380,6 +380,14 @@
 // The -json flag prints the environment in JSON format
 // instead of as a shell script.
 //
+// The -u flag requires one or more arguments and unsets
+// the default setting for the named environment variables,
+// if one has been set with 'go env -w'.
+//
+// The -w flag requires one or more arguments of the
+// form NAME=VALUE and changes the default settings
+// of the named environment variables to the given values.
+//
 // For more about environment variables, see 'go help environment'.
 //
 //
@@ -1485,10 +1493,17 @@
 //
 // Environment variables
 //
-// The go command, and the tools it invokes, examine a few different
-// environment variables. For many of these, you can see the default
-// value of on your system by running 'go env NAME', where NAME is the
-// name of the variable.
+// The go command and the tools it invokes consult environment variables
+// for configuration. If an environment variable is unset, the go command
+// uses a sensible default setting. To see the effective setting of the
+// variable <NAME>, run 'go env <NAME>'. To change the default setting,
+// run 'go env -w <NAME>=<VALUE>'. Defaults changed using 'go env -w'
+// are recorded in a Go environment configuration file stored in the
+// per-user configuration directory, as reported by os.UserConfigDir.
+// The location of the configuration file can be changed by setting
+// the environment variable GOENV, and 'go env GOENV' prints the
+// effective location, but 'go env -w' cannot change the default location.
+// See 'go help env' for details.
 //
 // General-purpose environment variables:
 //
@@ -1502,10 +1517,15 @@
 // 	GOCACHE
 // 		The directory where the go command will store cached
 // 		information for reuse in future builds.
+// 	GOENV
+// 		The location of the Go environment configuration file.
+// 		Cannot be set using 'go env -w'.
 // 	GOFLAGS
 // 		A space-separated list of -flag=value settings to apply
 // 		to go commands by default, when the given flag is known by
-// 		the current command. Flags listed on the command line
+// 		the current command. Each entry must be a standalone flag.
+// 		Because the entries are space-separated, flag values must
+// 		not contain spaces. Flags listed on the command line
 // 		are applied after this list and therefore override it.
 // 	GOOS
 // 		The operating system for which to compile code.
@@ -1514,21 +1534,18 @@
 // 		For more details see: 'go help gopath'.
 // 	GOPROXY
 // 		URL of Go module proxy. See 'go help goproxy'.
-// 	GORACE
-// 		Options for the race detector.
-// 		See https://golang.org/doc/articles/race_detector.html.
 // 	GOROOT
 // 		The root of the go tree.
 // 	GOTMPDIR
 // 		The directory where the go command will write
 // 		temporary source files, packages, and binaries.
 //
-// Each entry in the GOFLAGS list must be a standalone flag.
-// Because the entries are space-separated, flag values must
-// not contain spaces.
-//
 // Environment variables for use with cgo:
 //
+// 	AR
+// 		The command to use to manipulate library archives when
+// 		building with the gccgo compiler.
+// 		The default is 'ar'.
 // 	CC
 // 		The command to use to compile C code.
 // 	CGO_ENABLED
@@ -1558,12 +1575,10 @@
 // 		but for the linker.
 // 	CXX
 // 		The command to use to compile C++ code.
+// 	FC
+// 		The command to use to compile Fortran code.
 // 	PKG_CONFIG
 // 		Path to pkg-config tool.
-// 	AR
-// 		The command to use to manipulate library archives when
-// 		building with the gccgo compiler.
-// 		The default is 'ar'.
 //
 // Architecture-specific environment variables:
 //
@@ -1598,9 +1613,11 @@
 // 		when using -linkmode=auto with code that uses cgo.
 // 		Set to 0 to disable external linking mode, 1 to enable it.
 // 	GIT_ALLOW_PROTOCOL
-// 		Defined by Git. A colon-separated list of schemes that are allowed to be used
-// 		with git fetch/clone. If set, any scheme not explicitly mentioned will be
-// 		considered insecure by 'go get'.
+// 		Defined by Git. A colon-separated list of schemes that are allowed
+// 		to be used with git fetch/clone. If set, any scheme not explicitly
+// 		mentioned will be considered insecure by 'go get'.
+// 		Because the variable is defined by Git, the default value cannot
+// 		be set using 'go env -w'.
 //
 // Additional information available from 'go env' but not read from the environment:
 //
diff --git a/src/cmd/go/go_test.go b/src/cmd/go/go_test.go
index 473f62c..5ec02d8 100644
--- a/src/cmd/go/go_test.go
+++ b/src/cmd/go/go_test.go
@@ -6,8 +6,6 @@
 
 import (
 	"bytes"
-	"cmd/go/internal/cache"
-	"cmd/internal/sys"
 	"context"
 	"debug/elf"
 	"debug/macho"
@@ -28,6 +26,10 @@
 	"strings"
 	"testing"
 	"time"
+
+	"cmd/go/internal/cache"
+	"cmd/go/internal/cfg"
+	"cmd/internal/sys"
 )
 
 var (
@@ -119,6 +121,8 @@
 // The TestMain function creates a go command for testing purposes and
 // deletes it after the tests have been run.
 func TestMain(m *testing.M) {
+	// $GO_GCFLAGS a compiler debug flag known to cmd/dist, make.bash, etc.
+	// It is not a standard go command flag; use os.Getenv, not cfg.Getenv.
 	if os.Getenv("GO_GCFLAGS") != "" {
 		fmt.Fprintf(os.Stderr, "testing: warning: no tests to run\n") // magic string for cmd/go
 		fmt.Printf("cmd/go test is not compatible with $GO_GCFLAGS being set\n")
@@ -256,6 +260,7 @@
 		}
 	}
 	// Don't let these environment variables confuse the test.
+	os.Setenv("GOENV", "off")
 	os.Unsetenv("GOBIN")
 	os.Unsetenv("GOPATH")
 	os.Unsetenv("GIT_ALLOW_PROTOCOL")
@@ -264,7 +269,7 @@
 	// Setting HOME to a non-existent directory will break
 	// those systems. Disable ccache and use real compiler. Issue 17668.
 	os.Setenv("CCACHE_DISABLE", "1")
-	if os.Getenv("GOCACHE") == "" {
+	if cfg.Getenv("GOCACHE") == "" {
 		os.Setenv("GOCACHE", testGOCACHE) // because $HOME is gone
 	}
 
@@ -5258,7 +5263,7 @@
 	if strings.Contains(os.Getenv("GODEBUG"), "gocacheverify") {
 		t.Skip("GODEBUG gocacheverify")
 	}
-	if os.Getenv("GOCACHE") == "off" {
+	if cfg.Getenv("GOCACHE") == "off" {
 		tooSlow(t)
 		tg.makeTempdir()
 		tg.setenv("GOCACHE", tg.path("cache"))
diff --git a/src/cmd/go/internal/base/goflags.go b/src/cmd/go/internal/base/goflags.go
index 2f50b50..187c2a1 100644
--- a/src/cmd/go/internal/base/goflags.go
+++ b/src/cmd/go/internal/base/goflags.go
@@ -7,7 +7,6 @@
 import (
 	"flag"
 	"fmt"
-	"os"
 	"runtime"
 	"strings"
 
@@ -62,7 +61,7 @@
 	// (Both will show the GOFLAGS setting if let succeed.)
 	hideErrors := cfg.CmdName == "env" || cfg.CmdName == "bug"
 
-	goflags = strings.Fields(os.Getenv("GOFLAGS"))
+	goflags = strings.Fields(cfg.Getenv("GOFLAGS"))
 	if goflags == nil {
 		goflags = []string{} // avoid work on later InitGOFLAGS call
 	}
diff --git a/src/cmd/go/internal/cache/default.go b/src/cmd/go/internal/cache/default.go
index 7d389c3..9f8dd8a 100644
--- a/src/cmd/go/internal/cache/default.go
+++ b/src/cmd/go/internal/cache/default.go
@@ -12,6 +12,7 @@
 	"sync"
 
 	"cmd/go/internal/base"
+	"cmd/go/internal/cfg"
 )
 
 // Default returns the default cache to use, or nil if no cache should be used.
@@ -73,7 +74,7 @@
 	// otherwise distinguish between an explicit "off" and a UserCacheDir error.
 
 	defaultDirOnce.Do(func() {
-		defaultDir = os.Getenv("GOCACHE")
+		defaultDir = cfg.Getenv("GOCACHE")
 		if filepath.IsAbs(defaultDir) || defaultDir == "off" {
 			return
 		}
diff --git a/src/cmd/go/internal/cfg/cfg.go b/src/cmd/go/internal/cfg/cfg.go
index 35f7f1a..1060c8f 100644
--- a/src/cmd/go/internal/cfg/cfg.go
+++ b/src/cmd/go/internal/cfg/cfg.go
@@ -7,11 +7,15 @@
 package cfg
 
 import (
+	"bytes"
 	"fmt"
 	"go/build"
+	"io/ioutil"
 	"os"
 	"path/filepath"
 	"runtime"
+	"strings"
+	"sync"
 
 	"cmd/internal/objabi"
 )
@@ -46,6 +50,50 @@
 func defaultContext() build.Context {
 	ctxt := build.Default
 	ctxt.JoinPath = filepath.Join // back door to say "do not use go command"
+
+	ctxt.GOROOT = findGOROOT()
+	if runtime.Compiler != "gccgo" {
+		// Note that we must use runtime.GOOS and runtime.GOARCH here,
+		// as the tool directory does not move based on environment
+		// variables. This matches the initialization of ToolDir in
+		// go/build, except for using ctxt.GOROOT rather than
+		// runtime.GOROOT.
+		build.ToolDir = filepath.Join(ctxt.GOROOT, "pkg/tool/"+runtime.GOOS+"_"+runtime.GOARCH)
+	}
+
+	ctxt.GOPATH = envOr("GOPATH", ctxt.GOPATH)
+
+	// Override defaults computed in go/build with defaults
+	// from go environment configuration file, if known.
+	ctxt.GOOS = envOr("GOOS", ctxt.GOOS)
+	ctxt.GOARCH = envOr("GOARCH", ctxt.GOARCH)
+
+	// The go/build rule for whether cgo is enabled is:
+	//	1. If $CGO_ENABLED is set, respect it.
+	//	2. Otherwise, if this is a cross-compile, disable cgo.
+	//	3. Otherwise, use built-in default for GOOS/GOARCH.
+	// Recreate that logic here with the new GOOS/GOARCH setting.
+	if v := Getenv("CGO_ENABLED"); v == "0" || v == "1" {
+		ctxt.CgoEnabled = v[0] == '1'
+	} else if ctxt.GOOS != runtime.GOOS || ctxt.GOARCH != runtime.GOARCH {
+		ctxt.CgoEnabled = false
+	} else {
+		// Use built-in default cgo setting for GOOS/GOARCH.
+		// Note that ctxt.GOOS/GOARCH are derived from the preference list
+		// (1) environment, (2) go/env file, (3) runtime constants,
+		// while go/build.Default.GOOS/GOARCH are derived from the preference list
+		// (1) environment, (2) runtime constants.
+		// We know ctxt.GOOS/GOARCH == runtime.GOOS/GOARCH;
+		// no matter how that happened, go/build.Default will make the
+		// same decision (either the environment variables are set explicitly
+		// to match the runtime constants, or else they are unset, in which
+		// case go/build falls back to the runtime constants), so
+		// go/build.Default.GOOS/GOARCH == runtime.GOOS/GOARCH.
+		// So ctxt.CgoEnabled (== go/build.Default.CgoEnabled) is correct
+		// as is and can be left unmodified.
+		// Nothing to do here.
+	}
+
 	return ctxt
 }
 
@@ -70,9 +118,10 @@
 
 // Global build parameters (used during package load)
 var (
-	Goarch    = BuildContext.GOARCH
-	Goos      = BuildContext.GOOS
-	ExeSuffix string
+	Goarch = BuildContext.GOARCH
+	Goos   = BuildContext.GOOS
+
+	ExeSuffix = exeSuffix()
 
 	// ModulesEnabled specifies whether the go command is running
 	// in module-aware mode (as opposed to GOPATH mode).
@@ -85,40 +134,195 @@
 	GoModInGOPATH string
 )
 
-func init() {
+func exeSuffix() string {
 	if Goos == "windows" {
-		ExeSuffix = ".exe"
+		return ".exe"
+	}
+	return ""
+}
+
+var envCache struct {
+	once sync.Once
+	m    map[string]string
+}
+
+// EnvFile returns the name of the Go environment configuration file.
+func EnvFile() (string, error) {
+	if file := os.Getenv("GOENV"); file != "" {
+		if file == "off" {
+			return "", fmt.Errorf("GOENV=off")
+		}
+		return file, nil
+	}
+	dir, err := os.UserConfigDir()
+	if err != nil {
+		return "", err
+	}
+	if dir == "" {
+		return "", fmt.Errorf("missing user-config dir")
+	}
+	return filepath.Join(dir, "go/env"), nil
+}
+
+func initEnvCache() {
+	envCache.m = make(map[string]string)
+	file, _ := EnvFile()
+	if file == "" {
+		return
+	}
+	data, err := ioutil.ReadFile(file)
+	if err != nil {
+		return
+	}
+
+	for len(data) > 0 {
+		// Get next line.
+		line := data
+		i := bytes.IndexByte(data, '\n')
+		if i >= 0 {
+			line, data = line[:i], data[i+1:]
+		} else {
+			data = nil
+		}
+
+		i = bytes.IndexByte(line, '=')
+		if i < 0 || line[0] < 'A' || 'Z' < line[0] {
+			// Line is missing = (or empty) or a comment or not a valid env name. Ignore.
+			// (This should not happen, since the file should be maintained almost
+			// exclusively by "go env -w", but better to silently ignore than to make
+			// the go command unusable just because somehow the env file has
+			// gotten corrupted.)
+			continue
+		}
+		key, val := line[:i], line[i+1:]
+		envCache.m[string(key)] = string(val)
 	}
 }
 
+// Getenv gets the value for the configuration key.
+// It consults the operating system environment
+// and then the go/env file.
+// If Getenv is called for a key that cannot be set
+// in the go/env file (for example GODEBUG), it panics.
+// This ensures that CanGetenv is accurate, so that
+// 'go env -w' stays in sync with what Getenv can retrieve.
+func Getenv(key string) string {
+	if !CanGetenv(key) {
+		switch key {
+		case "CGO_TEST_ALLOW", "CGO_TEST_DISALLOW", "CGO_test_ALLOW", "CGO_test_DISALLOW":
+			// used by internal/work/security_test.go; allow
+		default:
+			panic("internal error: invalid Getenv " + key)
+		}
+	}
+	val := os.Getenv(key)
+	if val != "" {
+		return val
+	}
+	envCache.once.Do(initEnvCache)
+	return envCache.m[key]
+}
+
+// CanGetenv reports whether key is a valid go/env configuration key.
+func CanGetenv(key string) bool {
+	return strings.Contains(knownEnv, "\t"+key+"\n")
+}
+
+var knownEnv = `
+	AR
+	CC
+	CGO_CFLAGS
+	CGO_CFLAGS_ALLOW
+	CGO_CFLAGS_DISALLOW
+	CGO_CPPFLAGS
+	CGO_CPPFLAGS_ALLOW
+	CGO_CPPFLAGS_DISALLOW
+	CGO_CXXFLAGS
+	CGO_CXXFLAGS_ALLOW
+	CGO_CXXFLAGS_DISALLOW
+	CGO_ENABLED
+	CGO_FFLAGS
+	CGO_FFLAGS_ALLOW
+	CGO_FFLAGS_DISALLOW
+	CGO_LDFLAGS
+	CGO_LDFLAGS_ALLOW
+	CGO_LDFLAGS_DISALLOW
+	CXX
+	FC
+	GCCGO
+	GO111MODULE
+	GO386
+	GOARCH
+	GOARM
+	GOBIN
+	GOCACHE
+	GOENV
+	GOEXE
+	GOFLAGS
+	GOGCCFLAGS
+	GOHOSTARCH
+	GOHOSTOS
+	GOMIPS
+	GOMIPS64
+	GONOVERIFY
+	GOOS
+	GOPATH
+	GOPPC64
+	GOPROXY
+	GOROOT
+	GOTMPDIR
+	GOTOOLDIR
+	GOWASM
+	GO_EXTLINK_ENABLED
+	PKG_CONFIG
+`
+
 var (
-	GOROOT       = findGOROOT()
-	GOBIN        = os.Getenv("GOBIN")
+	GOROOT       = BuildContext.GOROOT
+	GOBIN        = Getenv("GOBIN")
 	GOROOTbin    = filepath.Join(GOROOT, "bin")
 	GOROOTpkg    = filepath.Join(GOROOT, "pkg")
 	GOROOTsrc    = filepath.Join(GOROOT, "src")
 	GOROOT_FINAL = findGOROOT_FINAL()
 
 	// Used in envcmd.MkEnv and build ID computations.
-	GOARM    = fmt.Sprint(objabi.GOARM)
-	GO386    = objabi.GO386
-	GOMIPS   = objabi.GOMIPS
-	GOMIPS64 = objabi.GOMIPS64
-	GOPPC64  = fmt.Sprintf("%s%d", "power", objabi.GOPPC64)
-	GOWASM   = objabi.GOWASM
+	GOARM    = envOr("GOARM", fmt.Sprint(objabi.GOARM))
+	GO386    = envOr("GO386", objabi.GO386)
+	GOMIPS   = envOr("GOMIPS", objabi.GOMIPS)
+	GOMIPS64 = envOr("GOMIPS64", objabi.GOMIPS64)
+	GOPPC64  = envOr("GOPPC64", fmt.Sprintf("%s%d", "power", objabi.GOPPC64))
+	GOWASM   = envOr("GOWASM", fmt.Sprint(objabi.GOWASM))
 )
 
-// Update build context to use our computed GOROOT.
-func init() {
-	BuildContext.GOROOT = GOROOT
-	if runtime.Compiler != "gccgo" {
-		// Note that we must use runtime.GOOS and runtime.GOARCH here,
-		// as the tool directory does not move based on environment
-		// variables. This matches the initialization of ToolDir in
-		// go/build, except for using GOROOT rather than
-		// runtime.GOROOT.
-		build.ToolDir = filepath.Join(GOROOT, "pkg/tool/"+runtime.GOOS+"_"+runtime.GOARCH)
+// GetArchEnv returns the name and setting of the
+// GOARCH-specific architecture environment variable.
+// If the current architecture has no GOARCH-specific variable,
+// GetArchEnv returns empty key and value.
+func GetArchEnv() (key, val string) {
+	switch Goarch {
+	case "arm":
+		return "GOARM", GOARM
+	case "386":
+		return "GO386", GO386
+	case "mips", "mipsle":
+		return "GOMIPS", GOMIPS
+	case "mips64", "mips64le":
+		return "GOMIPS64", GOMIPS64
+	case "ppc64", "ppc64le":
+		return "GOPPC64", GOPPC64
+	case "wasm":
+		return "GOWASM", GOWASM
 	}
+	return "", ""
+}
+
+// envOr returns Getenv(key) if set, or else def.
+func envOr(key, def string) string {
+	val := Getenv(key)
+	if val == "" {
+		val = def
+	}
+	return val
 }
 
 // There is a copy of findGOROOT, isSameDir, and isGOROOT in
@@ -132,7 +336,7 @@
 //
 // There is a copy of this code in x/tools/cmd/godoc/goroot.go.
 func findGOROOT() string {
-	if env := os.Getenv("GOROOT"); env != "" {
+	if env := Getenv("GOROOT"); env != "" {
 		return filepath.Clean(env)
 	}
 	def := filepath.Clean(runtime.GOROOT())
@@ -168,6 +372,8 @@
 }
 
 func findGOROOT_FINAL() string {
+	// $GOROOT_FINAL is only for use during make.bash
+	// so it is not settable using go/env, so we use os.Getenv here.
 	def := GOROOT
 	if env := os.Getenv("GOROOT_FINAL"); env != "" {
 		def = filepath.Clean(env)
diff --git a/src/cmd/go/internal/envcmd/env.go b/src/cmd/go/internal/envcmd/env.go
index 645f832..f03eeca 100644
--- a/src/cmd/go/internal/envcmd/env.go
+++ b/src/cmd/go/internal/envcmd/env.go
@@ -10,6 +10,9 @@
 	"fmt"
 	"os"
 	"path/filepath"
+	"unicode/utf8"
+	"io/ioutil"
+	"sort"
 	"runtime"
 	"strings"
 
@@ -22,7 +25,7 @@
 )
 
 var CmdEnv = &base.Command{
-	UsageLine: "go env [-json] [var ...]",
+	UsageLine: "go env [-json] [-u] [-w] [var ...]",
 	Short:     "print Go environment information",
 	Long: `
 Env prints Go environment information.
@@ -35,6 +38,14 @@
 The -json flag prints the environment in JSON format
 instead of as a shell script.
 
+The -u flag requires one or more arguments and unsets
+the default setting for the named environment variables,
+if one has been set with 'go env -w'.
+
+The -w flag requires one or more arguments of the
+form NAME=VALUE and changes the default settings
+of the named environment variables to the given values.
+
 For more about environment variables, see 'go help environment'.
 	`,
 }
@@ -43,26 +54,31 @@
 	CmdEnv.Run = runEnv // break init cycle
 }
 
-var envJson = CmdEnv.Flag.Bool("json", false, "")
+var (
+	envJson = CmdEnv.Flag.Bool("json", false, "")
+	envU = CmdEnv.Flag.Bool("u", false, "")
+	envW    = CmdEnv.Flag.Bool("w", false, "")
+)
 
 func MkEnv() []cfg.EnvVar {
 	var b work.Builder
 	b.Init()
 
+	envFile, _ := cfg.EnvFile()
 	env := []cfg.EnvVar{
 		{Name: "GOARCH", Value: cfg.Goarch},
 		{Name: "GOBIN", Value: cfg.GOBIN},
 		{Name: "GOCACHE", Value: cache.DefaultDir()},
+		{Name: "GOENV", Value: envFile},
 		{Name: "GOEXE", Value: cfg.ExeSuffix},
-		{Name: "GOFLAGS", Value: os.Getenv("GOFLAGS")},
+		{Name: "GOFLAGS", Value: cfg.Getenv("GOFLAGS")},
 		{Name: "GOHOSTARCH", Value: runtime.GOARCH},
 		{Name: "GOHOSTOS", Value: runtime.GOOS},
 		{Name: "GOOS", Value: cfg.Goos},
 		{Name: "GOPATH", Value: cfg.BuildContext.GOPATH},
-		{Name: "GOPROXY", Value: os.Getenv("GOPROXY")},
-		{Name: "GORACE", Value: os.Getenv("GORACE")},
+		{Name: "GOPROXY", Value: cfg.Getenv("GOPROXY")},
 		{Name: "GOROOT", Value: cfg.GOROOT},
-		{Name: "GOTMPDIR", Value: os.Getenv("GOTMPDIR")},
+		{Name: "GOTMPDIR", Value: cfg.Getenv("GOTMPDIR")},
 		{Name: "GOTOOLDIR", Value: base.ToolDir},
 	}
 
@@ -72,29 +88,20 @@
 		env = append(env, cfg.EnvVar{Name: "GCCGO", Value: work.GccgoName})
 	}
 
-	switch cfg.Goarch {
-	case "arm":
-		env = append(env, cfg.EnvVar{Name: "GOARM", Value: cfg.GOARM})
-	case "386":
-		env = append(env, cfg.EnvVar{Name: "GO386", Value: cfg.GO386})
-	case "mips", "mipsle":
-		env = append(env, cfg.EnvVar{Name: "GOMIPS", Value: cfg.GOMIPS})
-	case "mips64", "mips64le":
-		env = append(env, cfg.EnvVar{Name: "GOMIPS64", Value: cfg.GOMIPS64})
-	case "ppc64", "ppc64le":
-		env = append(env, cfg.EnvVar{Name: "GOPPC64", Value: cfg.GOPPC64})
-	case "wasm":
-		env = append(env, cfg.EnvVar{Name: "GOWASM", Value: cfg.GOWASM.String()})
+	key, val := cfg.GetArchEnv()
+	if key != "" {
+		env = append(env, cfg.EnvVar{Name: key, Value: val})
 	}
 
 	cc := cfg.DefaultCC(cfg.Goos, cfg.Goarch)
-	if env := strings.Fields(os.Getenv("CC")); len(env) > 0 {
+	if env := strings.Fields(cfg.Getenv("CC")); len(env) > 0 {
 		cc = env[0]
 	}
 	cxx := cfg.DefaultCXX(cfg.Goos, cfg.Goarch)
-	if env := strings.Fields(os.Getenv("CXX")); len(env) > 0 {
+	if env := strings.Fields(cfg.Getenv("CXX")); len(env) > 0 {
 		cxx = env[0]
 	}
+	env = append(env, cfg.EnvVar{Name: "AR", Value: envOr("AR", "ar")})
 	env = append(env, cfg.EnvVar{Name: "CC", Value: cc})
 	env = append(env, cfg.EnvVar{Name: "CXX", Value: cxx})
 
@@ -107,6 +114,14 @@
 	return env
 }
 
+func envOr(name, def string) string {
+	val := cfg.Getenv(name)
+	if val != "" {
+		return val
+	}
+	return def
+}
+
 func findEnv(env []cfg.EnvVar, name string) string {
 	for _, e := range env {
 		if e.Name == name {
@@ -154,7 +169,25 @@
 	}
 }
 
+// argKey returns the KEY part of the arg KEY=VAL, or else arg itself.
+func argKey(arg string) string {
+	i := strings.Index(arg, "=")
+	if i < 0 {
+		return arg
+	}
+	return arg[:i]
+}
+
 func runEnv(cmd *base.Command, args []string) {
+	if *envJson && *envU {
+		base.Fatalf("go env: cannot use -json with -u")
+	}
+	if *envJson && *envW {
+		base.Fatalf("go env: cannot use -json with -w")
+	}
+	if *envU && *envW {
+		base.Fatalf("go env: cannot use -u with -w")
+	}
 	env := cfg.CmdEnv
 	env = append(env, ExtraEnvVars()...)
 
@@ -165,7 +198,7 @@
 	if len(args) > 0 {
 		needCostly = false
 		for _, arg := range args {
-			switch arg {
+			switch argKey(arg) {
 			case "CGO_CFLAGS",
 				"CGO_CPPFLAGS",
 				"CGO_CXXFLAGS",
@@ -181,6 +214,55 @@
 		env = append(env, ExtraEnvVarsCostly()...)
 	}
 
+	if *envW {
+		// Process and sanity-check command line.
+		if len(args) == 0 {
+			base.Fatalf("go env -w: no KEY=VALUE arguments given")
+		}
+		osEnv := make(map[string]string)
+		for _, e := range cfg.OrigEnv {
+			if i := strings.Index(e, "="); i >= 0 {
+				osEnv[e[:i]] = e[i+1:]
+			}
+		}
+		add := make(map[string]string)
+		for _, arg := range args {
+			i := strings.Index(arg, "=")
+			if i < 0 {
+				base.Fatalf("go env -w: arguments must be KEY=VALUE: invalid argument: %s", arg)
+			}
+			key, val := arg[:i], arg[i+1:]
+			if err := checkEnvWrite(key, val, env); err != nil {
+				base.Fatalf("go env -w: %v", err)
+			}
+			if _, ok := add[key]; ok {
+				base.Fatalf("go env -w: multiple values for key: %s", key)
+			}
+			add[key] = val
+			if osVal := osEnv[key]; osVal != "" && osVal != val {
+				fmt.Fprintf(os.Stderr, "warning: go env -w %s=... does not override conflicting OS environment variable\n", key)
+			}
+		}
+		updateEnvFile(add, nil)
+		return
+	}
+
+	if *envU {
+		// Process and sanity-check command line.
+		if len(args) == 0 {
+			base.Fatalf("go env -u: no arguments given")
+		}
+		del := make(map[string]bool)
+		for _, arg := range args {
+			if err := checkEnvWrite(arg, "", env); err != nil {
+				base.Fatalf("go env -u: %v", err)
+			}
+			del[arg] = true
+		}
+		updateEnvFile(nil, del)
+		return
+	}
+
 	if len(args) > 0 {
 		if *envJson {
 			var es []cfg.EnvVar
@@ -239,6 +321,118 @@
 	enc := json.NewEncoder(os.Stdout)
 	enc.SetIndent("", "\t")
 	if err := enc.Encode(m); err != nil {
-		base.Fatalf("%s", err)
+		base.Fatalf("go env -json: %s", err)
 	}
 }
+
+func checkEnvWrite(key, val string, env []cfg.EnvVar) error {
+	switch key {
+	case "GOEXE", "GOGCCFLAGS", "GOHOSTARCH", "GOHOSTOS", "GOMOD", "GOTOOLDIR":
+		return fmt.Errorf("%s cannot be modified", key)
+	case "GOENV":
+		return fmt.Errorf("%s can only be set using the OS environment", key)
+	}
+
+	// To catch typos and the like, check that we know the variable.
+	if !cfg.CanGetenv(key) {
+		return fmt.Errorf("unknown go command variable %s", key)
+	}
+
+	if !utf8.ValidString(val) {
+		return fmt.Errorf("invalid UTF-8 in %s=... value", key)
+	}
+	if strings.Contains(val, "\x00") {
+		return fmt.Errorf("invalid NUL in %s=... value", key)
+	}
+	if strings.ContainsAny(val, "\v\r\n") {
+		return fmt.Errorf("invalid newline in %s=... value", key)
+	}
+	return nil
+}
+
+func updateEnvFile(add map[string]string, del map[string]bool) {
+	file, err := cfg.EnvFile()
+	if file == "" {
+		base.Fatalf("go env: cannot find go env config: %v", err)
+	}
+	data, err := ioutil.ReadFile(file)
+	if err != nil && (!os.IsNotExist(err) || len(add) == 0) {
+		base.Fatalf("go env: reading go env config: %v", err)
+	}
+
+	lines := strings.SplitAfter(string(data), "\n")
+	if lines[len(lines)-1] == "" {
+		lines = lines[:len(lines)-1]
+	} else {
+		lines[len(lines)-1] += "\n"
+	}
+
+	// Delete all but last copy of any duplicated variables,
+	// since the last copy is the one that takes effect.
+	prev := make(map[string]int)
+	for l, line := range lines {
+		if key := lineToKey(line); key != "" {
+			if p, ok := prev[key]; ok {
+				lines[p] = ""
+			}
+			prev[key] = l
+		}
+	}
+
+	// Add variables (go env -w). Update existing lines in file if present, add to end otherwise.
+	for key, val := range add {
+		if p, ok := prev[key]; ok {
+			lines[p] = key + "=" + val + "\n"
+			delete(add, key)
+		}
+	}
+	for key, val := range add {
+		lines = append(lines, key + "=" + val + "\n")
+	}
+
+	// Delete requested variables (go env -u).
+	for key := range del {
+		if p, ok := prev[key]; ok {
+			lines[p] = ""
+		}
+	}
+
+	// Sort runs of KEY=VALUE lines
+	// (that is, blocks of lines where blocks are separated
+	// by comments, blank lines, or invalid lines).
+	start := 0
+	for i := 0; i <= len(lines); i++ {
+		if i == len(lines) || lineToKey(lines[i]) == "" {
+			sortKeyValues(lines[start:i])
+			start = i+1
+		}
+	}
+
+	data = []byte(strings.Join(lines, ""))
+	err = ioutil.WriteFile(file, data, 0666)
+	if err != nil {
+		// Try creating directory.
+		os.MkdirAll(filepath.Dir(file), 0777)
+		err = ioutil.WriteFile(file, data, 0666)
+		if err != nil {
+			base.Fatalf("go env: writing go env config: %v", err)
+		}
+	}
+}
+
+// lineToKey returns the KEY part of the line KEY=VALUE or else an empty string.
+func lineToKey(line string) string {
+	i := strings.Index(line, "=")
+	if i < 0 || strings.Contains(line[:i], "#") {
+		return ""
+	}
+	return line[:i]
+}
+
+// sortKeyValues sorts a sequence of lines by key.
+// It differs from sort.Strings in that GO386= sorts after GO=.
+func sortKeyValues(lines []string) {
+	sort.Slice(lines, func(i, j int) bool {
+		return lineToKey(lines[i]) < lineToKey(lines[j])
+	})
+}
diff --git a/src/cmd/go/internal/help/helpdoc.go b/src/cmd/go/internal/help/helpdoc.go
index 98d4bd0..43ad57f 100644
--- a/src/cmd/go/internal/help/helpdoc.go
+++ b/src/cmd/go/internal/help/helpdoc.go
@@ -469,10 +469,17 @@
 	Short:     "environment variables",
 	Long: `
 
-The go command, and the tools it invokes, examine a few different
-environment variables. For many of these, you can see the default
-value of on your system by running 'go env NAME', where NAME is the
-name of the variable.
+The go command and the tools it invokes consult environment variables
+for configuration. If an environment variable is unset, the go command
+uses a sensible default setting. To see the effective setting of the
+variable <NAME>, run 'go env <NAME>'. To change the default setting,
+run 'go env -w <NAME>=<VALUE>'. Defaults changed using 'go env -w'
+are recorded in a Go environment configuration file stored in the
+per-user configuration directory, as reported by os.UserConfigDir.
+The location of the configuration file can be changed by setting
+the environment variable GOENV, and 'go env GOENV' prints the
+effective location, but 'go env -w' cannot change the default location.
+See 'go help env' for details.
 
 General-purpose environment variables:
 
@@ -486,10 +493,15 @@
 	GOCACHE
 		The directory where the go command will store cached
 		information for reuse in future builds.
+	GOENV
+		The location of the Go environment configuration file.
+		Cannot be set using 'go env -w'.
 	GOFLAGS
 		A space-separated list of -flag=value settings to apply
 		to go commands by default, when the given flag is known by
-		the current command. Flags listed on the command line
+		the current command. Each entry must be a standalone flag.
+		Because the entries are space-separated, flag values must
+		not contain spaces. Flags listed on the command line
 		are applied after this list and therefore override it.
 	GOOS
 		The operating system for which to compile code.
@@ -498,21 +510,18 @@
 		For more details see: 'go help gopath'.
 	GOPROXY
 		URL of Go module proxy. See 'go help goproxy'.
-	GORACE
-		Options for the race detector.
-		See https://golang.org/doc/articles/race_detector.html.
 	GOROOT
 		The root of the go tree.
 	GOTMPDIR
 		The directory where the go command will write
 		temporary source files, packages, and binaries.
 
-Each entry in the GOFLAGS list must be a standalone flag.
-Because the entries are space-separated, flag values must
-not contain spaces.
-
 Environment variables for use with cgo:
 
+	AR
+		The command to use to manipulate library archives when
+		building with the gccgo compiler.
+		The default is 'ar'.
 	CC
 		The command to use to compile C code.
 	CGO_ENABLED
@@ -542,12 +551,10 @@
 		but for the linker.
 	CXX
 		The command to use to compile C++ code.
+	FC
+		The command to use to compile Fortran code.
 	PKG_CONFIG
 		Path to pkg-config tool.
-	AR
-		The command to use to manipulate library archives when
-		building with the gccgo compiler.
-		The default is 'ar'.
 
 Architecture-specific environment variables:
 
@@ -582,9 +589,11 @@
 		when using -linkmode=auto with code that uses cgo.
 		Set to 0 to disable external linking mode, 1 to enable it.
 	GIT_ALLOW_PROTOCOL
-		Defined by Git. A colon-separated list of schemes that are allowed to be used
-		with git fetch/clone. If set, any scheme not explicitly mentioned will be
-		considered insecure by 'go get'.
+		Defined by Git. A colon-separated list of schemes that are allowed
+		to be used with git fetch/clone. If set, any scheme not explicitly
+		mentioned will be considered insecure by 'go get'.
+		Because the variable is defined by Git, the default value cannot
+		be set using 'go env -w'.
 
 Additional information available from 'go env' but not read from the environment:
 
diff --git a/src/cmd/go/internal/modfetch/notary.go b/src/cmd/go/internal/modfetch/notary.go
index 9514958..dea37ff 100644
--- a/src/cmd/go/internal/modfetch/notary.go
+++ b/src/cmd/go/internal/modfetch/notary.go
@@ -6,11 +6,11 @@
 
 import (
 	"fmt"
-	"os"
 	pathpkg "path"
 	"strings"
 
 	"cmd/go/internal/base"
+	"cmd/go/internal/cfg"
 	"cmd/go/internal/get"
 	"cmd/go/internal/module"
 )
@@ -67,7 +67,7 @@
 	if get.Insecure {
 		return false
 	}
-	wantNotary, err := notaryShouldVerify(mod.Path, os.Getenv("GONOVERIFY"))
+	wantNotary, err := notaryShouldVerify(mod.Path, cfg.Getenv("GONOVERIFY"))
 	if err != nil {
 		base.Fatalf("%v", err)
 	}
diff --git a/src/cmd/go/internal/modfetch/proxy.go b/src/cmd/go/internal/modfetch/proxy.go
index 3d4d2be..cbf476d 100644
--- a/src/cmd/go/internal/modfetch/proxy.go
+++ b/src/cmd/go/internal/modfetch/proxy.go
@@ -9,11 +9,11 @@
 	"fmt"
 	"io"
 	"net/url"
-	"os"
 	"strings"
 	"time"
 
 	"cmd/go/internal/base"
+	"cmd/go/internal/cfg"
 	"cmd/go/internal/modfetch/codehost"
 	"cmd/go/internal/module"
 	"cmd/go/internal/semver"
@@ -85,7 +85,7 @@
 `,
 }
 
-var proxyURL = os.Getenv("GOPROXY")
+var proxyURL = cfg.Getenv("GOPROXY")
 
 // SetProxy sets the proxy to use when fetching modules.
 // It accepts the same syntax as the GOPROXY environment variable,
diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go
index bc05417..eaf4407 100644
--- a/src/cmd/go/internal/modload/init.go
+++ b/src/cmd/go/internal/modload/init.go
@@ -6,6 +6,18 @@
 
 import (
 	"bytes"
+	"encoding/json"
+	"fmt"
+	"go/build"
+	"internal/lazyregexp"
+	"io/ioutil"
+	"os"
+	"path"
+	"path/filepath"
+	"runtime/debug"
+	"strconv"
+	"strings"
+
 	"cmd/go/internal/base"
 	"cmd/go/internal/cache"
 	"cmd/go/internal/cfg"
@@ -18,17 +30,6 @@
 	"cmd/go/internal/mvs"
 	"cmd/go/internal/renameio"
 	"cmd/go/internal/search"
-	"encoding/json"
-	"fmt"
-	"go/build"
-	"internal/lazyregexp"
-	"io/ioutil"
-	"os"
-	"path"
-	"path/filepath"
-	"runtime/debug"
-	"strconv"
-	"strings"
 )
 
 var (
@@ -90,7 +91,7 @@
 	}
 	initialized = true
 
-	env := os.Getenv("GO111MODULE")
+	env := cfg.Getenv("GO111MODULE")
 	switch env {
 	default:
 		base.Fatalf("go: unknown environment setting GO111MODULE=%s", env)
@@ -294,7 +295,7 @@
 	if printStackInDie {
 		debug.PrintStack()
 	}
-	if os.Getenv("GO111MODULE") == "off" {
+	if cfg.Getenv("GO111MODULE") == "off" {
 		base.Fatalf("go: modules disabled by GO111MODULE=off; see 'go help modules'")
 	}
 	if inGOPATH && !mustUseModules {
diff --git a/src/cmd/go/internal/work/action.go b/src/cmd/go/internal/work/action.go
index 1134b1f..7a74b1b 100644
--- a/src/cmd/go/internal/work/action.go
+++ b/src/cmd/go/internal/work/action.go
@@ -226,7 +226,7 @@
 	if cfg.BuildN {
 		b.WorkDir = "$WORK"
 	} else {
-		tmp, err := ioutil.TempDir(os.Getenv("GOTMPDIR"), "go-build")
+		tmp, err := ioutil.TempDir(cfg.Getenv("GOTMPDIR"), "go-build")
 		if err != nil {
 			base.Fatalf("go: creating work dir: %v", err)
 		}
diff --git a/src/cmd/go/internal/work/exec.go b/src/cmd/go/internal/work/exec.go
index 14d13f8..5d2659c 100644
--- a/src/cmd/go/internal/work/exec.go
+++ b/src/cmd/go/internal/work/exec.go
@@ -224,12 +224,16 @@
 		if len(p.SFiles) > 0 {
 			fmt.Fprintf(h, "asm %q %q %q\n", b.toolID("asm"), forcedAsmflags, p.Internal.Asmflags)
 		}
+
 		// GO386, GOARM, GOMIPS, etc.
-		baseArch := strings.TrimSuffix(cfg.BuildContext.GOARCH, "le")
-		fmt.Fprintf(h, "GO$GOARCH=%s\n", os.Getenv("GO"+strings.ToUpper(baseArch)))
+		key, val := cfg.GetArchEnv()
+		fmt.Fprintf(h, "%s=%s\n", key, val)
 
 		// TODO(rsc): Convince compiler team not to add more magic environment variables,
 		// or perhaps restrict the environment variables passed to subprocesses.
+		// Because these are clumsy, undocumented special-case hacks
+		// for debugging the compiler, they are not settable using 'go env -w',
+		// and so here we use os.Getenv, not cfg.Getenv.
 		magic := []string{
 			"GOCLOBBERDEADHASH",
 			"GOSSAFUNC",
@@ -1115,21 +1119,16 @@
 		if p != nil {
 			fmt.Fprintf(h, "linkflags %q\n", p.Internal.Ldflags)
 		}
-		fmt.Fprintf(h, "GO$GOARCH=%s\n", os.Getenv("GO"+strings.ToUpper(cfg.BuildContext.GOARCH))) // GO386, GOARM, etc
+
+		// GO386, GOARM, GOMIPS, etc.
+		key, val := cfg.GetArchEnv()
+		fmt.Fprintf(h, "%s=%s\n", key, val)
 
 		// The linker writes source file paths that say GOROOT_FINAL.
 		fmt.Fprintf(h, "GOROOT=%s\n", cfg.GOROOT_FINAL)
 
-		// TODO(rsc): Convince linker team not to add more magic environment variables,
-		// or perhaps restrict the environment variables passed to subprocesses.
-		magic := []string{
-			"GO_EXTLINK_ENABLED",
-		}
-		for _, env := range magic {
-			if x := os.Getenv(env); x != "" {
-				fmt.Fprintf(h, "magic %s=%s\n", env, x)
-			}
-		}
+		// GO_EXTLINK_ENABLED controls whether the external linker is used.
+		fmt.Fprintf(h, "GO_EXTLINK_ENABLED=%s\n", cfg.Getenv("GO_EXTLINK_ENABLED"))
 
 		// TODO(rsc): Do cgo settings and flags need to be included?
 		// Or external linker settings and flags?
@@ -2192,8 +2191,8 @@
 
 // Grab these before main helpfully overwrites them.
 var (
-	origCC  = os.Getenv("CC")
-	origCXX = os.Getenv("CXX")
+	origCC  = cfg.Getenv("CC")
+	origCXX = cfg.Getenv("CXX")
 )
 
 // gccCmd returns a gcc command line prefix
@@ -2225,7 +2224,7 @@
 
 // fcExe returns the FC compiler setting without all the extra flags we add implicitly.
 func (b *Builder) fcExe() []string {
-	return b.compilerExe(os.Getenv("FC"), "gfortran")
+	return b.compilerExe(cfg.Getenv("FC"), "gfortran")
 }
 
 // compilerExe returns the compiler to use given an
@@ -2391,7 +2390,7 @@
 // envList returns the value of the given environment variable broken
 // into fields, using the default value when the variable is empty.
 func envList(key, def string) []string {
-	v := os.Getenv(key)
+	v := cfg.Getenv(key)
 	if v == "" {
 		v = def
 	}
@@ -2448,7 +2447,7 @@
 	// Support gfortran out of the box and let others pass the correct link options
 	// via CGO_LDFLAGS
 	if len(ffiles) > 0 {
-		fc := os.Getenv("FC")
+		fc := cfg.Getenv("FC")
 		if fc == "" {
 			fc = "gfortran"
 		}
diff --git a/src/cmd/go/internal/work/gccgo.go b/src/cmd/go/internal/work/gccgo.go
index 0ba690f..ff2621e 100644
--- a/src/cmd/go/internal/work/gccgo.go
+++ b/src/cmd/go/internal/work/gccgo.go
@@ -26,7 +26,7 @@
 var gccgoErr error
 
 func init() {
-	GccgoName = os.Getenv("GCCGO")
+	GccgoName = cfg.Getenv("GCCGO")
 	if GccgoName == "" {
 		GccgoName = "gccgo"
 	}
@@ -44,7 +44,7 @@
 }
 
 func (gccgoToolchain) ar() string {
-	ar := os.Getenv("AR")
+	ar := cfg.Getenv("AR")
 	if ar == "" {
 		ar = "ar"
 	}
@@ -479,7 +479,7 @@
 			ldflags = append(ldflags, "-lobjc")
 		}
 		if fortran {
-			fc := os.Getenv("FC")
+			fc := cfg.Getenv("FC")
 			if fc == "" {
 				fc = "gfortran"
 			}
diff --git a/src/cmd/go/internal/work/security.go b/src/cmd/go/internal/work/security.go
index 8351e4c..ecfb9df 100644
--- a/src/cmd/go/internal/work/security.go
+++ b/src/cmd/go/internal/work/security.go
@@ -30,12 +30,13 @@
 package work
 
 import (
-	"cmd/go/internal/load"
 	"fmt"
 	"internal/lazyregexp"
-	"os"
 	"regexp"
 	"strings"
+
+	"cmd/go/internal/cfg"
+	"cmd/go/internal/load"
 )
 
 var re = lazyregexp.New
@@ -229,14 +230,14 @@
 		allow    *regexp.Regexp
 		disallow *regexp.Regexp
 	)
-	if env := os.Getenv("CGO_" + name + "_ALLOW"); env != "" {
+	if env := cfg.Getenv("CGO_" + name + "_ALLOW"); env != "" {
 		r, err := regexp.Compile(env)
 		if err != nil {
 			return fmt.Errorf("parsing $CGO_%s_ALLOW: %v", name, err)
 		}
 		allow = r
 	}
-	if env := os.Getenv("CGO_" + name + "_DISALLOW"); env != "" {
+	if env := cfg.Getenv("CGO_" + name + "_DISALLOW"); env != "" {
 		r, err := regexp.Compile(env)
 		if err != nil {
 			return fmt.Errorf("parsing $CGO_%s_DISALLOW: %v", name, err)
diff --git a/src/cmd/go/main.go b/src/cmd/go/main.go
index 35a5076..0207862 100644
--- a/src/cmd/go/main.go
+++ b/src/cmd/go/main.go
@@ -122,8 +122,14 @@
 				os.Exit(2)
 			}
 			if !filepath.IsAbs(p) {
-				fmt.Fprintf(os.Stderr, "go: GOPATH entry is relative; must be absolute path: %q.\nFor more details see: 'go help gopath'\n", p)
-				os.Exit(2)
+				if cfg.Getenv("GOPATH") == "" {
+					// We inferred $GOPATH from $HOME and did a bad job at it.
+					// Instead of dying, uninfer it.
+					cfg.BuildContext.GOPATH = ""
+				} else {
+					fmt.Fprintf(os.Stderr, "go: GOPATH entry is relative; must be absolute path: %q.\nFor more details see: 'go help gopath'\n", p)
+					os.Exit(2)
+				}
 			}
 		}
 	}
diff --git a/src/cmd/go/testdata/script/env_write.txt b/src/cmd/go/testdata/script/env_write.txt
new file mode 100644
index 0000000..bdc348c
--- /dev/null
+++ b/src/cmd/go/testdata/script/env_write.txt
@@ -0,0 +1,87 @@
+env GO111MODULE=off
+
+# go env should default to the right places
+env AppData=$HOME/windowsappdata
+env home=$HOME/plan9home
+go env GOENV
+[aix] stdout $HOME/.config/go/env
+[darwin] stdout $HOME/Library/Preferences/go/env
+[freebsd] stdout $HOME/.config/go/env
+[linux] stdout $HOME/.config/go/env
+[netbsd] stdout $HOME/.config/go/env
+[openbsd] stdout $HOME/.config/go/env
+[plan9] stdout $HOME/plan9home/lib/go/env
+[windows] stdout $HOME\\windowsappdata\\go\\env
+
+# Now override it to something writable.
+env GOENV=$WORK/envdir/go/env
+go env GOENV
+stdout envdir[\\/]go[\\/]env
+
+# go env shows all variables
+go env
+stdout GOARCH=
+stdout GOOS=
+stdout GOROOT=
+
+# go env -w changes default setting
+env root=
+[windows] env root=c:
+env GOPATH=
+go env -w GOPATH=$root/non-exist/gopath
+! stderr .+
+grep GOPATH=$root/non-exist/gopath $WORK/envdir/go/env
+go env GOPATH
+stdout /non-exist/gopath
+
+# go env -w does not override OS environment, and warns about that
+env GOPATH=$root/other
+go env -w GOPATH=$root/non-exist/gopath2
+stderr 'warning: go env -w GOPATH=... does not override conflicting OS environment variable'
+go env GOPATH
+stdout $root/other
+
+# but go env -w does do the update, and unsetting the env var exposes the change
+env GOPATH=
+go env GOPATH
+stdout $root/non-exist/gopath2
+
+# unsetting with go env -u does not warn about OS environment overrides,
+# nor does it warn about variables that haven't been set by go env -w.
+env GOPATH=$root/other
+go env -u GOPATH
+! stderr .+
+go env -u GOPATH
+! stderr .+
+
+# go env -w rejects unknown or bad variables
+! go env -w GODEBUG=gctrace=1
+stderr 'unknown go command variable GODEBUG'
+! go env -w GOEXE=.bat
+stderr 'GOEXE cannot be modified'
+! go env -w GOENV=/env
+stderr 'GOENV can only be set using the OS environment'
+
+# go env -w can set multiple variables
+env CC=
+go env CC
+! stdout ^xyc$
+go env -w GOOS=$GOOS CC=xyc
+grep CC=xyc $GOENV
+# file is maintained in sorted order
+grep 'CC=xyc\nGOOS=' $GOENV
+go env CC
+stdout ^xyc$
+
+# go env -u unsets effect of go env -w.
+go env -u CC
+go env CC
+! stdout ^xyc$
+
+# go env -w rejects double-set variables
+! go env -w GOOS=$GOOS GOOS=$GOOS
+stderr 'multiple values for key: GOOS'
+
+# go env -w rejects missing variables
+! go env -w GOOS
+stderr 'arguments must be KEY=VALUE: invalid argument: GOOS'
diff --git a/src/cmd/internal/objabi/util.go b/src/cmd/internal/objabi/util.go
index 57f19f2..9e41b87 100644
--- a/src/cmd/internal/objabi/util.go
+++ b/src/cmd/internal/objabi/util.go
@@ -87,7 +87,7 @@
 	SatConv bool
 }
 
-func (f *gowasmFeatures) String() string {
+func (f gowasmFeatures) String() string {
 	var flags []string
 	if f.SatConv {
 		flags = append(flags, "satconv")
diff --git a/src/make.bash b/src/make.bash
index 2883f47..92d1481 100755
--- a/src/make.bash
+++ b/src/make.bash
@@ -65,6 +65,7 @@
 
 set -e
 
+export GOENV=off
 unset GOBIN # Issue 14340
 unset GOFLAGS
 unset GO111MODULE
diff --git a/src/make.bat b/src/make.bat
index d22cb30..d18cd87 100644
--- a/src/make.bat
+++ b/src/make.bat
@@ -46,13 +46,14 @@
 setlocal
 :nolocal
 
+set GOENV=off
 set GOBUILDFAIL=0
 set GOFLAGS=
 set GO111MODULE=
 
 if exist make.bat goto ok
 echo Must run make.bat from Go src directory.
-goto fail 
+goto fail
 :ok
 
 :: Clean old generated file that will cause problems in the build.
diff --git a/src/make.rc b/src/make.rc
index f055ff8..f5e57e9 100755
--- a/src/make.rc
+++ b/src/make.rc
@@ -47,6 +47,7 @@
 	shift
 }
 
+GOENV=off
 GOFLAGS=()
 GO111MODULE=()
 GOROOT = `{cd .. && pwd}
diff --git a/src/runtime/extern.go b/src/runtime/extern.go
index e308dd3..2917efe 100644
--- a/src/runtime/extern.go
+++ b/src/runtime/extern.go
@@ -136,6 +136,9 @@
 the GOMAXPROCS limit. This package's GOMAXPROCS function queries and changes
 the limit.
 
+The GORACE variable configures the race detector, for programs built using -race.
+See https://golang.org/doc/articles/race_detector.html for details.
+
 The GOTRACEBACK variable controls the amount of output generated when a Go
 program fails due to an unrecovered panic or an unexpected runtime condition.
 By default, a failure prints a stack trace for the current goroutine,