summaryrefslogtreecommitdiffstats
path: root/src/cmd/go/internal/envcmd/env.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/go/internal/envcmd/env.go')
-rw-r--r--src/cmd/go/internal/envcmd/env.go537
1 files changed, 537 insertions, 0 deletions
diff --git a/src/cmd/go/internal/envcmd/env.go b/src/cmd/go/internal/envcmd/env.go
new file mode 100644
index 0000000..6937187
--- /dev/null
+++ b/src/cmd/go/internal/envcmd/env.go
@@ -0,0 +1,537 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package envcmd implements the ``go env'' command.
+package envcmd
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "go/build"
+ "os"
+ "path/filepath"
+ "runtime"
+ "sort"
+ "strings"
+ "unicode/utf8"
+
+ "cmd/go/internal/base"
+ "cmd/go/internal/cache"
+ "cmd/go/internal/cfg"
+ "cmd/go/internal/fsys"
+ "cmd/go/internal/load"
+ "cmd/go/internal/modload"
+ "cmd/go/internal/work"
+)
+
+var CmdEnv = &base.Command{
+ UsageLine: "go env [-json] [-u] [-w] [var ...]",
+ Short: "print Go environment information",
+ Long: `
+Env prints Go environment information.
+
+By default env prints information as a shell script
+(on Windows, a batch file). If one or more variable
+names is given as arguments, env prints the value of
+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'.
+ `,
+}
+
+func init() {
+ CmdEnv.Run = runEnv // break init cycle
+}
+
+var (
+ envJson = CmdEnv.Flag.Bool("json", false, "")
+ envU = CmdEnv.Flag.Bool("u", false, "")
+ envW = CmdEnv.Flag.Bool("w", false, "")
+)
+
+func MkEnv() []cfg.EnvVar {
+ envFile, _ := cfg.EnvFile()
+ env := []cfg.EnvVar{
+ {Name: "GO111MODULE", Value: cfg.Getenv("GO111MODULE")},
+ {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: cfg.Getenv("GOFLAGS")},
+ {Name: "GOHOSTARCH", Value: runtime.GOARCH},
+ {Name: "GOHOSTOS", Value: runtime.GOOS},
+ {Name: "GOINSECURE", Value: cfg.GOINSECURE},
+ {Name: "GOMODCACHE", Value: cfg.GOMODCACHE},
+ {Name: "GONOPROXY", Value: cfg.GONOPROXY},
+ {Name: "GONOSUMDB", Value: cfg.GONOSUMDB},
+ {Name: "GOOS", Value: cfg.Goos},
+ {Name: "GOPATH", Value: cfg.BuildContext.GOPATH},
+ {Name: "GOPRIVATE", Value: cfg.GOPRIVATE},
+ {Name: "GOPROXY", Value: cfg.GOPROXY},
+ {Name: "GOROOT", Value: cfg.GOROOT},
+ {Name: "GOSUMDB", Value: cfg.GOSUMDB},
+ {Name: "GOTMPDIR", Value: cfg.Getenv("GOTMPDIR")},
+ {Name: "GOTOOLDIR", Value: base.ToolDir},
+ {Name: "GOVCS", Value: cfg.GOVCS},
+ {Name: "GOVERSION", Value: runtime.Version()},
+ }
+
+ if work.GccgoBin != "" {
+ env = append(env, cfg.EnvVar{Name: "GCCGO", Value: work.GccgoBin})
+ } else {
+ env = append(env, cfg.EnvVar{Name: "GCCGO", Value: work.GccgoName})
+ }
+
+ 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(cfg.Getenv("CC")); len(env) > 0 {
+ cc = env[0]
+ }
+ cxx := cfg.DefaultCXX(cfg.Goos, cfg.Goarch)
+ 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})
+
+ if cfg.BuildContext.CgoEnabled {
+ env = append(env, cfg.EnvVar{Name: "CGO_ENABLED", Value: "1"})
+ } else {
+ env = append(env, cfg.EnvVar{Name: "CGO_ENABLED", Value: "0"})
+ }
+
+ 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 {
+ return e.Value
+ }
+ }
+ return ""
+}
+
+// ExtraEnvVars returns environment variables that should not leak into child processes.
+func ExtraEnvVars() []cfg.EnvVar {
+ gomod := ""
+ if modload.HasModRoot() {
+ gomod = filepath.Join(modload.ModRoot(), "go.mod")
+ } else if modload.Enabled() {
+ gomod = os.DevNull
+ }
+ return []cfg.EnvVar{
+ {Name: "GOMOD", Value: gomod},
+ }
+}
+
+// ExtraEnvVarsCostly returns environment variables that should not leak into child processes
+// but are costly to evaluate.
+func ExtraEnvVarsCostly() []cfg.EnvVar {
+ var b work.Builder
+ b.Init()
+ cppflags, cflags, cxxflags, fflags, ldflags, err := b.CFlags(&load.Package{})
+ if err != nil {
+ // Should not happen - b.CFlags was given an empty package.
+ fmt.Fprintf(os.Stderr, "go: invalid cflags: %v\n", err)
+ return nil
+ }
+ cmd := b.GccCmd(".", "")
+
+ return []cfg.EnvVar{
+ // Note: Update the switch in runEnv below when adding to this list.
+ {Name: "CGO_CFLAGS", Value: strings.Join(cflags, " ")},
+ {Name: "CGO_CPPFLAGS", Value: strings.Join(cppflags, " ")},
+ {Name: "CGO_CXXFLAGS", Value: strings.Join(cxxflags, " ")},
+ {Name: "CGO_FFLAGS", Value: strings.Join(fflags, " ")},
+ {Name: "CGO_LDFLAGS", Value: strings.Join(ldflags, " ")},
+ {Name: "PKG_CONFIG", Value: b.PkgconfigCmd()},
+ {Name: "GOGCCFLAGS", Value: strings.Join(cmd[3:], " ")},
+ }
+}
+
+// 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(ctx context.Context, 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()...)
+
+ if err := fsys.Init(base.Cwd); err != nil {
+ base.Fatalf("go: %v", err)
+ }
+
+ // Do we need to call ExtraEnvVarsCostly, which is a bit expensive?
+ needCostly := false
+ if *envU || *envW {
+ // We're overwriting or removing default settings,
+ // so it doesn't really matter what the existing settings are.
+ //
+ // Moreover, we haven't validated the new settings yet, so it is
+ // important that we NOT perform any actions based on them,
+ // such as initializing the builder to compute other variables.
+ } else if len(args) == 0 {
+ // We're listing all environment variables ("go env"),
+ // including the expensive ones.
+ needCostly = true
+ } else {
+ needCostly = false
+ checkCostly:
+ for _, arg := range args {
+ switch argKey(arg) {
+ case "CGO_CFLAGS",
+ "CGO_CPPFLAGS",
+ "CGO_CXXFLAGS",
+ "CGO_FFLAGS",
+ "CGO_LDFLAGS",
+ "PKG_CONFIG",
+ "GOGCCFLAGS":
+ needCostly = true
+ break checkCostly
+ }
+ }
+ }
+ if needCostly {
+ 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); 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)
+ }
+ }
+
+ goos, okGOOS := add["GOOS"]
+ goarch, okGOARCH := add["GOARCH"]
+ if okGOOS || okGOARCH {
+ if !okGOOS {
+ goos = cfg.Goos
+ }
+ if !okGOARCH {
+ goarch = cfg.Goarch
+ }
+ if err := work.CheckGOOSARCHPair(goos, goarch); err != nil {
+ base.Fatalf("go env -w: %v", err)
+ }
+ }
+
+ gotmp, okGOTMP := add["GOTMPDIR"]
+ if okGOTMP {
+ if !filepath.IsAbs(gotmp) && gotmp != "" {
+ base.Fatalf("go env -w: GOTMPDIR must be an absolute path")
+ }
+ }
+
+ 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, ""); err != nil {
+ base.Fatalf("go env -u: %v", err)
+ }
+ del[arg] = true
+ }
+ if del["GOOS"] || del["GOARCH"] {
+ goos, goarch := cfg.Goos, cfg.Goarch
+ if del["GOOS"] {
+ goos = getOrigEnv("GOOS")
+ if goos == "" {
+ goos = build.Default.GOOS
+ }
+ }
+ if del["GOARCH"] {
+ goarch = getOrigEnv("GOARCH")
+ if goarch == "" {
+ goarch = build.Default.GOARCH
+ }
+ }
+ if err := work.CheckGOOSARCHPair(goos, goarch); err != nil {
+ base.Fatalf("go env -u: %v", err)
+ }
+ }
+ updateEnvFile(nil, del)
+ return
+ }
+
+ if len(args) > 0 {
+ if *envJson {
+ var es []cfg.EnvVar
+ for _, name := range args {
+ e := cfg.EnvVar{Name: name, Value: findEnv(env, name)}
+ es = append(es, e)
+ }
+ printEnvAsJSON(es)
+ } else {
+ for _, name := range args {
+ fmt.Printf("%s\n", findEnv(env, name))
+ }
+ }
+ return
+ }
+
+ if *envJson {
+ printEnvAsJSON(env)
+ return
+ }
+
+ for _, e := range env {
+ if e.Name != "TERM" {
+ switch runtime.GOOS {
+ default:
+ fmt.Printf("%s=\"%s\"\n", e.Name, e.Value)
+ case "plan9":
+ if strings.IndexByte(e.Value, '\x00') < 0 {
+ fmt.Printf("%s='%s'\n", e.Name, strings.ReplaceAll(e.Value, "'", "''"))
+ } else {
+ v := strings.Split(e.Value, "\x00")
+ fmt.Printf("%s=(", e.Name)
+ for x, s := range v {
+ if x > 0 {
+ fmt.Printf(" ")
+ }
+ fmt.Printf("%s", s)
+ }
+ fmt.Printf(")\n")
+ }
+ case "windows":
+ fmt.Printf("set %s=%s\n", e.Name, e.Value)
+ }
+ }
+ }
+}
+
+func printEnvAsJSON(env []cfg.EnvVar) {
+ m := make(map[string]string)
+ for _, e := range env {
+ if e.Name == "TERM" {
+ continue
+ }
+ m[e.Name] = e.Value
+ }
+ enc := json.NewEncoder(os.Stdout)
+ enc.SetIndent("", "\t")
+ if err := enc.Encode(m); err != nil {
+ base.Fatalf("go env -json: %s", err)
+ }
+}
+
+func getOrigEnv(key string) string {
+ for _, v := range cfg.OrigEnv {
+ if strings.HasPrefix(v, key+"=") {
+ return strings.TrimPrefix(v, key+"=")
+ }
+ }
+ return ""
+}
+
+func checkEnvWrite(key, val string) error {
+ switch key {
+ case "GOEXE", "GOGCCFLAGS", "GOHOSTARCH", "GOHOSTOS", "GOMOD", "GOTOOLDIR", "GOVERSION":
+ 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)
+ }
+
+ // Some variables can only have one of a few valid values. If set to an
+ // invalid value, the next cmd/go invocation might fail immediately,
+ // even 'go env -w' itself.
+ switch key {
+ case "GO111MODULE":
+ switch val {
+ case "", "auto", "on", "off":
+ default:
+ return fmt.Errorf("invalid %s value %q", key, val)
+ }
+ case "GOPATH":
+ if strings.HasPrefix(val, "~") {
+ return fmt.Errorf("GOPATH entry cannot start with shell metacharacter '~': %q", val)
+ }
+ if !filepath.IsAbs(val) && val != "" {
+ return fmt.Errorf("GOPATH entry is relative; must be absolute path: %q", val)
+ }
+ // Make sure CC and CXX are absolute paths
+ case "CC", "CXX":
+ if !filepath.IsAbs(val) && val != "" && val != filepath.Base(val) {
+ return fmt.Errorf("%s entry is relative; must be absolute path: %q", key, val)
+ }
+ }
+
+ 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 := os.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 = os.WriteFile(file, data, 0666)
+ if err != nil {
+ // Try creating directory.
+ os.MkdirAll(filepath.Dir(file), 0777)
+ err = os.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 keys which are GOx where x is an ASCII
+// character smaller than = sort after GO=.
+// (There are no such keys currently. It used to matter for GO386 which was
+// removed in Go 1.16.)
+func sortKeyValues(lines []string) {
+ sort.Slice(lines, func(i, j int) bool {
+ return lineToKey(lines[i]) < lineToKey(lines[j])
+ })
+}