1 // Copyright 2012 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 // Package envcmd implements the “go env” command.
24 "cmd/go/internal/base"
25 "cmd/go/internal/cache"
27 "cmd/go/internal/fsys"
28 "cmd/go/internal/load"
29 "cmd/go/internal/modload"
30 "cmd/go/internal/work"
34 var CmdEnv = &base.Command{
35 UsageLine: "go env [-json] [-u] [-w] [var ...]",
36 Short: "print Go environment information",
38 Env prints Go environment information.
40 By default env prints information as a shell script
41 (on Windows, a batch file). If one or more variable
42 names is given as arguments, env prints the value of
43 each named variable on its own line.
45 The -json flag prints the environment in JSON format
46 instead of as a shell script.
48 The -u flag requires one or more arguments and unsets
49 the default setting for the named environment variables,
50 if one has been set with 'go env -w'.
52 The -w flag requires one or more arguments of the
53 form NAME=VALUE and changes the default settings
54 of the named environment variables to the given values.
56 For more about environment variables, see 'go help environment'.
61 CmdEnv.Run = runEnv // break init cycle
62 base.AddChdirFlag(&CmdEnv.Flag)
63 base.AddBuildFlagsNX(&CmdEnv.Flag)
67 envJson = CmdEnv.Flag.Bool("json", false, "")
68 envU = CmdEnv.Flag.Bool("u", false, "")
69 envW = CmdEnv.Flag.Bool("w", false, "")
72 func MkEnv() []cfg.EnvVar {
73 envFile, _ := cfg.EnvFile()
75 {Name: "GO111MODULE", Value: cfg.Getenv("GO111MODULE")},
76 {Name: "GOARCH", Value: cfg.Goarch},
77 {Name: "GOBIN", Value: cfg.GOBIN},
78 {Name: "GOCACHE", Value: cache.DefaultDir()},
79 {Name: "GOENV", Value: envFile},
80 {Name: "GOEXE", Value: cfg.ExeSuffix},
82 // List the raw value of GOEXPERIMENT, not the cleaned one.
83 // The set of default experiments may change from one release
84 // to the next, so a GOEXPERIMENT setting that is redundant
85 // with the current toolchain might actually be relevant with
86 // a different version (for example, when bisecting a regression).
87 {Name: "GOEXPERIMENT", Value: cfg.RawGOEXPERIMENT},
89 {Name: "GOFLAGS", Value: cfg.Getenv("GOFLAGS")},
90 {Name: "GOHOSTARCH", Value: runtime.GOARCH},
91 {Name: "GOHOSTOS", Value: runtime.GOOS},
92 {Name: "GOINSECURE", Value: cfg.GOINSECURE},
93 {Name: "GOMODCACHE", Value: cfg.GOMODCACHE},
94 {Name: "GONOPROXY", Value: cfg.GONOPROXY},
95 {Name: "GONOSUMDB", Value: cfg.GONOSUMDB},
96 {Name: "GOOS", Value: cfg.Goos},
97 {Name: "GOPATH", Value: cfg.BuildContext.GOPATH},
98 {Name: "GOPRIVATE", Value: cfg.GOPRIVATE},
99 {Name: "GOPROXY", Value: cfg.GOPROXY},
100 {Name: "GOROOT", Value: cfg.GOROOT},
101 {Name: "GOSUMDB", Value: cfg.GOSUMDB},
102 {Name: "GOTMPDIR", Value: cfg.Getenv("GOTMPDIR")},
103 {Name: "GOTOOLDIR", Value: build.ToolDir},
104 {Name: "GOVCS", Value: cfg.GOVCS},
105 {Name: "GOVERSION", Value: runtime.Version()},
108 if work.GccgoBin != "" {
109 env = append(env, cfg.EnvVar{Name: "GCCGO", Value: work.GccgoBin})
111 env = append(env, cfg.EnvVar{Name: "GCCGO", Value: work.GccgoName})
114 key, val := cfg.GetArchEnv()
116 env = append(env, cfg.EnvVar{Name: key, Value: val})
119 cc := cfg.Getenv("CC")
121 cc = cfg.DefaultCC(cfg.Goos, cfg.Goarch)
123 cxx := cfg.Getenv("CXX")
125 cxx = cfg.DefaultCXX(cfg.Goos, cfg.Goarch)
127 env = append(env, cfg.EnvVar{Name: "AR", Value: envOr("AR", "ar")})
128 env = append(env, cfg.EnvVar{Name: "CC", Value: cc})
129 env = append(env, cfg.EnvVar{Name: "CXX", Value: cxx})
131 if cfg.BuildContext.CgoEnabled {
132 env = append(env, cfg.EnvVar{Name: "CGO_ENABLED", Value: "1"})
134 env = append(env, cfg.EnvVar{Name: "CGO_ENABLED", Value: "0"})
140 func envOr(name, def string) string {
141 val := cfg.Getenv(name)
148 func findEnv(env []cfg.EnvVar, envFile map[string]string, name string) string {
149 for _, e := range env {
157 // ExtraEnvVars returns environment variables that should not leak into child processes.
158 func ExtraEnvVars() []cfg.EnvVar {
161 if modload.HasModRoot() {
162 gomod = modload.ModFilePath()
163 } else if modload.Enabled() {
166 modload.InitWorkfile()
167 gowork := modload.WorkFilePath()
168 // As a special case, if a user set off explicitly, report that in GOWORK.
169 if cfg.Getenv("GOWORK") == "off" {
173 {Name: "GOMOD", Value: gomod},
174 {Name: "GOWORK", Value: gowork},
178 // ExtraEnvVarsCostly returns environment variables that should not leak into child processes
179 // but are costly to evaluate.
180 func ExtraEnvVarsCostly() []cfg.EnvVar {
181 b := work.NewBuilder("")
183 if err := b.Close(); err != nil {
184 base.Fatalf("go: %v", err)
188 cppflags, cflags, cxxflags, fflags, ldflags, err := b.CFlags(&load.Package{})
190 // Should not happen - b.CFlags was given an empty package.
191 fmt.Fprintf(os.Stderr, "go: invalid cflags: %v\n", err)
194 cmd := b.GccCmd(".", "")
196 join := func(s []string) string {
197 q, err := quoted.Join(s)
199 return strings.Join(s, " ")
205 // Note: Update the switch in runEnv below when adding to this list.
206 {Name: "CGO_CFLAGS", Value: join(cflags)},
207 {Name: "CGO_CPPFLAGS", Value: join(cppflags)},
208 {Name: "CGO_CXXFLAGS", Value: join(cxxflags)},
209 {Name: "CGO_FFLAGS", Value: join(fflags)},
210 {Name: "CGO_LDFLAGS", Value: join(ldflags)},
211 {Name: "PKG_CONFIG", Value: b.PkgconfigCmd()},
212 {Name: "GOGCCFLAGS", Value: join(cmd[3:])},
216 // argKey returns the KEY part of the arg KEY=VAL, or else arg itself.
217 func argKey(arg string) string {
218 i := strings.Index(arg, "=")
225 func runEnv(ctx context.Context, cmd *base.Command, args []string) {
226 if *envJson && *envU {
227 base.Fatalf("go: cannot use -json with -u")
229 if *envJson && *envW {
230 base.Fatalf("go: cannot use -json with -w")
233 base.Fatalf("go: cannot use -u with -w")
236 // Handle 'go env -w' and 'go env -u' before calling buildcfg.Check,
237 // so they can be used to recover from an invalid configuration.
249 if cfg.ExperimentErr != nil {
250 base.Fatalf("go: %v", cfg.ExperimentErr)
254 env = append(env, ExtraEnvVars()...)
255 envFile := readEnvFile()
257 if err := fsys.Init(base.Cwd()); err != nil {
258 base.Fatalf("go: %v", err)
261 // Do we need to call ExtraEnvVarsCostly, which is a bit expensive?
264 // We're listing all environment variables ("go env"),
265 // including the expensive ones.
270 for _, arg := range args {
286 env = append(env, ExtraEnvVarsCostly()...)
292 for _, name := range args {
293 e := cfg.EnvVar{Name: name, Value: findEnv(env, envFile, name)}
298 for _, name := range args {
299 fmt.Printf("%s\n", findEnv(env, envFile, name))
310 PrintEnv(os.Stdout, env)
313 func runEnvW(args []string) {
314 // Process and sanity-check command line.
316 base.Fatalf("go: no KEY=VALUE arguments given")
318 osEnv := make(map[string]string)
319 for _, e := range cfg.OrigEnv {
320 if i := strings.Index(e, "="); i >= 0 {
321 osEnv[e[:i]] = e[i+1:]
324 add := make(map[string]string)
325 envFile := readEnvFile()
326 for _, arg := range args {
327 key, val, found := strings.Cut(arg, "=")
329 base.Fatalf("go: arguments must be KEY=VALUE: invalid argument: %s", arg)
331 if err := checkEnvWrite(key, val, envFile, 'w'); err != nil {
332 base.Fatalf("go: %v", err)
334 if _, ok := add[key]; ok {
335 base.Fatalf("go: multiple values for key: %s", key)
338 if osVal := osEnv[key]; osVal != "" && osVal != val {
339 fmt.Fprintf(os.Stderr, "warning: go env -w %s=... does not override conflicting OS environment variable\n", key)
343 if err := checkBuildConfig(add, nil); err != nil {
344 base.Fatalf("go: %v", err)
347 gotmp, okGOTMP := add["GOTMPDIR"]
349 if !filepath.IsAbs(gotmp) && gotmp != "" {
350 base.Fatalf("go: GOTMPDIR must be an absolute path")
354 updateEnvFile(add, nil)
357 func runEnvU(args []string) {
358 // Process and sanity-check command line.
360 base.Fatalf("go: 'go env -u' requires an argument")
362 envFile := readEnvFile()
363 del := make(map[string]bool)
364 for _, arg := range args {
365 if err := checkEnvWrite(arg, "", envFile, 'u'); err != nil {
366 base.Fatalf("go: %v", err)
371 if err := checkBuildConfig(nil, del); err != nil {
372 base.Fatalf("go: %v", err)
375 updateEnvFile(nil, del)
378 // checkBuildConfig checks whether the build configuration is valid
379 // after the specified configuration environment changes are applied.
380 func checkBuildConfig(add map[string]string, del map[string]bool) error {
381 // get returns the value for key after applying add and del and
382 // reports whether it changed. cur should be the current value
383 // (i.e., before applying changes) and def should be the default
384 // value (i.e., when no environment variables are provided at all).
385 get := func(key, cur, def string) (string, bool) {
386 if val, ok := add[key]; ok {
390 val := getOrigEnv(key)
399 goos, okGOOS := get("GOOS", cfg.Goos, build.Default.GOOS)
400 goarch, okGOARCH := get("GOARCH", cfg.Goarch, build.Default.GOARCH)
401 if okGOOS || okGOARCH {
402 if err := work.CheckGOOSARCHPair(goos, goarch); err != nil {
407 goexperiment, okGOEXPERIMENT := get("GOEXPERIMENT", cfg.RawGOEXPERIMENT, buildcfg.DefaultGOEXPERIMENT)
409 if _, err := buildcfg.ParseGOEXPERIMENT(goos, goarch, goexperiment); err != nil {
417 // PrintEnv prints the environment variables to w.
418 func PrintEnv(w io.Writer, env []cfg.EnvVar) {
419 for _, e := range env {
420 if e.Name != "TERM" {
421 if runtime.GOOS != "plan9" && bytes.Contains([]byte(e.Value), []byte{0}) {
422 base.Fatalf("go: internal error: encountered null byte in environment variable %s on non-plan9 platform", e.Name)
424 switch runtime.GOOS {
426 fmt.Fprintf(w, "%s=%s\n", e.Name, shellQuote(e.Value))
428 if strings.IndexByte(e.Value, '\x00') < 0 {
429 fmt.Fprintf(w, "%s='%s'\n", e.Name, strings.ReplaceAll(e.Value, "'", "''"))
431 v := strings.Split(e.Value, "\x00")
432 fmt.Fprintf(w, "%s=(", e.Name)
433 for x, s := range v {
437 // TODO(#59979): Does this need to be quoted like above?
438 fmt.Fprintf(w, "%s", s)
440 fmt.Fprintf(w, ")\n")
443 if hasNonGraphic(e.Value) {
444 base.Errorf("go: stripping unprintable or unescapable characters from %%%q%%", e.Name)
446 fmt.Fprintf(w, "set %s=%s\n", e.Name, batchEscape(e.Value))
452 func hasNonGraphic(s string) bool {
453 for _, c := range []byte(s) {
454 if c == '\r' || c == '\n' || (!unicode.IsGraphic(rune(c)) && !unicode.IsSpace(rune(c))) {
461 func shellQuote(s string) string {
464 for _, x := range []byte(s) {
466 // Close the single quoted string, add an escaped single quote,
467 // and start another single quoted string.
468 b.WriteString(`'\''`)
477 func batchEscape(s string) string {
479 for _, x := range []byte(s) {
480 if x == '\r' || x == '\n' || (!unicode.IsGraphic(rune(x)) && !unicode.IsSpace(rune(x))) {
481 b.WriteRune(unicode.ReplacementChar)
487 case '<', '>', '|', '&', '^':
488 // These are special characters that need to be escaped with ^. See
489 // https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/set_1.
499 func printEnvAsJSON(env []cfg.EnvVar) {
500 m := make(map[string]string)
501 for _, e := range env {
502 if e.Name == "TERM" {
507 enc := json.NewEncoder(os.Stdout)
508 enc.SetIndent("", "\t")
509 if err := enc.Encode(m); err != nil {
510 base.Fatalf("go: %s", err)
514 func getOrigEnv(key string) string {
515 for _, v := range cfg.OrigEnv {
516 if v, found := strings.CutPrefix(v, key+"="); found {
523 func checkEnvWrite(key, val string, envFile map[string]string, op rune) error {
524 _, inEnvFile := envFile[key]
526 // Always OK to delete something in the env file; maybe a different toolchain put it there.
527 if op == 'u' && inEnvFile {
532 case "GOEXE", "GOGCCFLAGS", "GOHOSTARCH", "GOHOSTOS", "GOMOD", "GOWORK", "GOTOOLDIR", "GOVERSION":
533 return fmt.Errorf("%s cannot be modified", key)
535 return fmt.Errorf("%s can only be set using the OS environment", key)
538 // To catch typos and the like, check that we know the variable.
539 // If it's already in the env file, we assume it's known.
540 if !inEnvFile && !cfg.CanGetenv(key) {
541 if _, ok := envFile[key]; !ok {
542 return fmt.Errorf("unknown go command variable %s", key)
546 // Some variables can only have one of a few valid values. If set to an
547 // invalid value, the next cmd/go invocation might fail immediately,
548 // even 'go env -w' itself.
552 case "", "auto", "on", "off":
554 return fmt.Errorf("invalid %s value %q", key, val)
557 if strings.HasPrefix(val, "~") {
558 return fmt.Errorf("GOPATH entry cannot start with shell metacharacter '~': %q", val)
560 if !filepath.IsAbs(val) && val != "" {
561 return fmt.Errorf("GOPATH entry is relative; must be absolute path: %q", val)
564 if !filepath.IsAbs(val) && val != "" {
565 return fmt.Errorf("GOMODCACHE entry is relative; must be absolute path: %q", val)
571 args, err := quoted.Split(val)
573 return fmt.Errorf("invalid %s: %v", key, err)
576 return fmt.Errorf("%s entry cannot contain only space", key)
578 if !filepath.IsAbs(args[0]) && args[0] != filepath.Base(args[0]) {
579 return fmt.Errorf("%s entry is relative; must be absolute path: %q", key, args[0])
583 if !utf8.ValidString(val) {
584 return fmt.Errorf("invalid UTF-8 in %s=... value", key)
586 if strings.Contains(val, "\x00") {
587 return fmt.Errorf("invalid NUL in %s=... value", key)
589 if strings.ContainsAny(val, "\v\r\n") {
590 return fmt.Errorf("invalid newline in %s=... value", key)
595 func readEnvFile() map[string]string {
596 lines := readEnvFileLines(false)
597 m := make(map[string]string)
598 for _, line := range lines {
599 key := lineToKey(line)
603 m[key] = string(line[len(key)+len("="):])
608 func readEnvFileLines(mustExist bool) []string {
609 file, err := cfg.EnvFile()
612 base.Fatalf("go: cannot find go env config: %v", err)
616 data, err := os.ReadFile(file)
617 if err != nil && (!os.IsNotExist(err) || mustExist) {
618 base.Fatalf("go: reading go env config: %v", err)
620 lines := strings.SplitAfter(string(data), "\n")
621 if lines[len(lines)-1] == "" {
622 lines = lines[:len(lines)-1]
624 lines[len(lines)-1] += "\n"
629 func updateEnvFile(add map[string]string, del map[string]bool) {
630 lines := readEnvFileLines(len(add) == 0)
632 // Delete all but last copy of any duplicated variables,
633 // since the last copy is the one that takes effect.
634 prev := make(map[string]int)
635 for l, line := range lines {
636 if key := lineToKey(line); key != "" {
637 if p, ok := prev[key]; ok {
644 // Add variables (go env -w). Update existing lines in file if present, add to end otherwise.
645 for key, val := range add {
646 if p, ok := prev[key]; ok {
647 lines[p] = key + "=" + val + "\n"
651 for key, val := range add {
652 lines = append(lines, key+"="+val+"\n")
655 // Delete requested variables (go env -u).
656 for key := range del {
657 if p, ok := prev[key]; ok {
662 // Sort runs of KEY=VALUE lines
663 // (that is, blocks of lines where blocks are separated
664 // by comments, blank lines, or invalid lines).
666 for i := 0; i <= len(lines); i++ {
667 if i == len(lines) || lineToKey(lines[i]) == "" {
668 sortKeyValues(lines[start:i])
673 file, err := cfg.EnvFile()
675 base.Fatalf("go: cannot find go env config: %v", err)
677 data := []byte(strings.Join(lines, ""))
678 err = os.WriteFile(file, data, 0666)
680 // Try creating directory.
681 os.MkdirAll(filepath.Dir(file), 0777)
682 err = os.WriteFile(file, data, 0666)
684 base.Fatalf("go: writing go env config: %v", err)
689 // lineToKey returns the KEY part of the line KEY=VALUE or else an empty string.
690 func lineToKey(line string) string {
691 i := strings.Index(line, "=")
692 if i < 0 || strings.Contains(line[:i], "#") {
698 // sortKeyValues sorts a sequence of lines by key.
699 // It differs from sort.Strings in that keys which are GOx where x is an ASCII
700 // character smaller than = sort after GO=.
701 // (There are no such keys currently. It used to matter for GO386 which was
702 // removed in Go 1.16.)
703 func sortKeyValues(lines []string) {
704 sort.Slice(lines, func(i, j int) bool {
705 return lineToKey(lines[i]) < lineToKey(lines[j])