]> Cypherpunks.ru repositories - gostls13.git/commitdiff
cmd/go: add env -w and env -u to set and unset default env vars
authorRuss Cox <rsc@golang.org>
Mon, 8 Apr 2019 15:23:42 +0000 (11:23 -0400)
committerRuss Cox <rsc@golang.org>
Tue, 23 Apr 2019 00:58:08 +0000 (00:58 +0000)
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>
21 files changed:
src/cmd/go/alldocs.go
src/cmd/go/go_test.go
src/cmd/go/internal/base/goflags.go
src/cmd/go/internal/cache/default.go
src/cmd/go/internal/cfg/cfg.go
src/cmd/go/internal/envcmd/env.go
src/cmd/go/internal/help/helpdoc.go
src/cmd/go/internal/modfetch/notary.go
src/cmd/go/internal/modfetch/proxy.go
src/cmd/go/internal/modload/init.go
src/cmd/go/internal/work/action.go
src/cmd/go/internal/work/exec.go
src/cmd/go/internal/work/gccgo.go
src/cmd/go/internal/work/security.go
src/cmd/go/main.go
src/cmd/go/testdata/script/env_write.txt [new file with mode: 0644]
src/cmd/internal/objabi/util.go
src/make.bash
src/make.bat
src/make.rc
src/runtime/extern.go

index 2cc00f29b1e271e333e648b5727d613def179931..d012235b819f22c032706e6db6b2de97280ceba1 100644 (file)
 //
 // Usage:
 //
-//     go env [-json] [var ...]
+//     go env [-json] [-u] [-w] [var ...]
 //
 // Env prints Go environment information.
 //
 // 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'.
 //
 //
 //
 // 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:
 //
 //     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.
 //             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
 //             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:
 //
 //             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:
 //
index 473f62ca5bfe2b94d32f7039e23bc496bc78070f..5ec02d8e493d9ab7493b0a6ae82af70c867c2c50 100644 (file)
@@ -6,8 +6,6 @@ package main_test
 
 import (
        "bytes"
-       "cmd/go/internal/cache"
-       "cmd/internal/sys"
        "context"
        "debug/elf"
        "debug/macho"
@@ -28,6 +26,10 @@ import (
        "strings"
        "testing"
        "time"
+
+       "cmd/go/internal/cache"
+       "cmd/go/internal/cfg"
+       "cmd/internal/sys"
 )
 
 var (
@@ -119,6 +121,8 @@ var testCtx = context.Background()
 // 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 @@ func TestMain(m *testing.M) {
                }
        }
        // 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 @@ func TestMain(m *testing.M) {
        // 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 @@ func TestCacheVet(t *testing.T) {
        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"))
index 2f50b50bfcf368d1c1d911759b76ff1c398c38fd..187c2a1472734ca3a6cb0a162c187e165304c9a1 100644 (file)
@@ -7,7 +7,6 @@ package base
 import (
        "flag"
        "fmt"
-       "os"
        "runtime"
        "strings"
 
@@ -62,7 +61,7 @@ func InitGOFLAGS() {
        // (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
        }
index 7d389c3c1af9f13b16429480924f6d1709b9aa42..9f8dd8af4b6b2dc02586c7bc19debab92c5ff6d2 100644 (file)
@@ -12,6 +12,7 @@ import (
        "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 @@ func DefaultDir() string {
        // 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
                }
index 35f7f1a17399830c2a5e0ae3d8aa915e83c9e427..1060c8f6df538794e313bf82d901ab5f848cddfb 100644 (file)
@@ -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 @@ var (
 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 @@ var CmdEnv []EnvVar
 
 // 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 @@ var (
        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 @@ func init() {
 //
 // 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() string {
 }
 
 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)
index 645f83246a620bf16818a9b30bb1363fdcf35ec7..f03eeca9ff43dd072ba42af82889f7986bb839b9 100644 (file)
@@ -10,6 +10,9 @@ import (
        "fmt"
        "os"
        "path/filepath"
+       "unicode/utf8"
+       "io/ioutil"
+       "sort"
        "runtime"
        "strings"
 
@@ -22,7 +25,7 @@ import (
 )
 
 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 @@ each named variable on its own line.
 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 @@ func init() {
        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 @@ func MkEnv() []cfg.EnvVar {
                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 @@ func MkEnv() []cfg.EnvVar {
        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 @@ func ExtraEnvVarsCostly() []cfg.EnvVar {
        }
 }
 
+// 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 @@ func runEnv(cmd *base.Command, args []string) {
        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 @@ func runEnv(cmd *base.Command, args []string) {
                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 @@ func printEnvAsJSON(env []cfg.EnvVar) {
        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])
+       })
 }
index 98d4bd0382a042943ef0c431752905f416825fb8..43ad57f2c0e15bb28b8bbea66c8eadda20a2e404 100644 (file)
@@ -469,10 +469,17 @@ var HelpEnvironment = &base.Command{
        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 @@ General-purpose environment variables:
        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 @@ General-purpose environment variables:
                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 @@ Environment variables for use with cgo:
                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 @@ Special-purpose environment variables:
                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:
 
index 9514958817e1ea7072bdc46a124c2e81b37fd945..dea37ff4500e3dd788ff93e9555e9c76376d10b4 100644 (file)
@@ -6,11 +6,11 @@ package modfetch
 
 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 @@ func useNotary(mod module.Version) bool {
        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)
        }
index 3d4d2becf40346d66599e9946e46d89f9dff1bd9..cbf476d1e49945d00bd5a0c26ef33aa706892708 100644 (file)
@@ -9,11 +9,11 @@ import (
        "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 @@ cached module versions with GOPROXY=https://example.com/proxy.
 `,
 }
 
-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,
index bc0541705f1f04df5c9edc2ffd87f59de0251bc8..eaf4407529579cac12da0fa2a3cb85a9798775d0 100644 (file)
@@ -6,6 +6,18 @@ package modload
 
 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 @@ import (
        "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 @@ func Init() {
        }
        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 @@ func die() {
        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 {
index 1134b1f35bfc304bbd8a1d99deefe153f21f54f5..7a74b1bb0d185238235113fb294b8c8da57e222e 100644 (file)
@@ -226,7 +226,7 @@ func (b *Builder) Init() {
        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)
                }
index 14d13f83d390c02b5cf9046ff67b50a3c489b796..5d2659cef5c43a8b558f84ba6b33dcba2e33e9f1 100644 (file)
@@ -224,12 +224,16 @@ func (b *Builder) buildActionID(a *Action) cache.ActionID {
                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 @@ func (b *Builder) printLinkerConfig(h io.Writer, p *load.Package) {
                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 @@ func (b *Builder) gccld(p *load.Package, objdir, outfile string, flags []string,
 
 // 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 @@ func (b *Builder) cxxExe() []string {
 
 // 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 @@ func (b *Builder) gccArchArgs() []string {
 // 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 @@ func (b *Builder) cgo(a *Action, cgoExe, objdir string, pcCFLAGS, pcLDFLAGS, cgo
        // 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"
                }
index 0ba690fd62caf7d3710c621139c6defe13367dc9..ff2621e1c4963f0c5f33ab083851e9e364992e37 100644 (file)
@@ -26,7 +26,7 @@ var GccgoName, GccgoBin string
 var gccgoErr error
 
 func init() {
-       GccgoName = os.Getenv("GCCGO")
+       GccgoName = cfg.Getenv("GCCGO")
        if GccgoName == "" {
                GccgoName = "gccgo"
        }
@@ -44,7 +44,7 @@ func (gccgoToolchain) linker() string {
 }
 
 func (gccgoToolchain) ar() string {
-       ar := os.Getenv("AR")
+       ar := cfg.Getenv("AR")
        if ar == "" {
                ar = "ar"
        }
@@ -479,7 +479,7 @@ func (tools gccgoToolchain) link(b *Builder, root *Action, out, importcfg string
                        ldflags = append(ldflags, "-lobjc")
                }
                if fortran {
-                       fc := os.Getenv("FC")
+                       fc := cfg.Getenv("FC")
                        if fc == "" {
                                fc = "gfortran"
                        }
index 8351e4c731756c16c76a6d20ae0f025292e65caa..ecfb9df1b219742b35bdb70752f0a68c78ac6e0c 100644 (file)
 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 @@ func checkFlags(name, source string, list []string, valid []*lazyregexp.Regexp,
                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)
index 35a507680f19086f7804cd3ac82dc982e16364b8..0207862d0b0601055ecbdffffaa12966c4ed7f79 100644 (file)
@@ -122,8 +122,14 @@ func main() {
                                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 (file)
index 0000000..bdc348c
--- /dev/null
@@ -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'
index 57f19f2e3c26b5cfbef424355e628cfdea4dc8ef..9e41b87aa4609b120af516f6ab12a515adf756c8 100644 (file)
@@ -87,7 +87,7 @@ type gowasmFeatures struct {
        SatConv bool
 }
 
-func (f *gowasmFeatures) String() string {
+func (f gowasmFeatures) String() string {
        var flags []string
        if f.SatConv {
                flags = append(flags, "satconv")
index 2883f47c124471e5c52d8536acbfe34f15b8de35..92d148110a62e0c2f57ec904051f6ef15b24b0da 100755 (executable)
@@ -65,6 +65,7 @@
 
 set -e
 
+export GOENV=off
 unset GOBIN # Issue 14340
 unset GOFLAGS
 unset GO111MODULE
index d22cb30ab228227c791e218ed65fa20cc53bcfa3..d18cd87d48465d93a340b9a401a80ba1f1fdd054 100644 (file)
@@ -46,13 +46,14 @@ if x%4==x--no-local goto nolocal
 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.
index f055ff8e144c2dde288dc86bdb574bb6c8dd52b0..f5e57e975561839c3e3c1ac7bd24e5d03b2219b6 100755 (executable)
@@ -47,6 +47,7 @@ if(~ $1 -v) {
        shift
 }
 
+GOENV=off
 GOFLAGS=()
 GO111MODULE=()
 GOROOT = `{cd .. && pwd}
index e308dd38b1bc76da5f98f7aaa4530c0008972855..2917efefa61ad057baedc52222a6fcc26c79c8d3 100644 (file)
@@ -136,6 +136,9 @@ that can be blocked in system calls on behalf of Go code; those do not count aga
 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,