diff options
author | Russ Cox <rsc@golang.org> | 2019-04-08 11:23:42 -0400 |
---|---|---|
committer | Russ Cox <rsc@golang.org> | 2019-04-23 00:58:08 +0000 |
commit | f0e97546962736fe4aa73b7c7ed590f0134515e1 (patch) | |
tree | d6903cc240c0cdef29bcd0d7fa3b059eab080a17 /src/cmd/go/internal/cfg/cfg.go | |
parent | e40dffe55ac0ec40fc325bf9ef03dde297fcc2c0 (diff) | |
download | go-f0e97546962736fe4aa73b7c7ed590f0134515e1.tar.gz go-f0e97546962736fe4aa73b7c7ed590f0134515e1.zip |
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>
Diffstat (limited to 'src/cmd/go/internal/cfg/cfg.go')
-rw-r--r-- | src/cmd/go/internal/cfg/cfg.go | 254 |
1 files changed, 230 insertions, 24 deletions
diff --git a/src/cmd/go/internal/cfg/cfg.go b/src/cmd/go/internal/cfg/cfg.go index 35f7f1a173..1060c8f6df 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 @@ 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) |