1
2
3
4
5
6 package envcmd
7
8 import (
9 "bytes"
10 "context"
11 "encoding/json"
12 "fmt"
13 "go/build"
14 "internal/buildcfg"
15 "io"
16 "os"
17 "path/filepath"
18 "runtime"
19 "sort"
20 "strings"
21 "unicode"
22 "unicode/utf8"
23
24 "cmd/go/internal/base"
25 "cmd/go/internal/cache"
26 "cmd/go/internal/cfg"
27 "cmd/go/internal/fsys"
28 "cmd/go/internal/load"
29 "cmd/go/internal/modload"
30 "cmd/go/internal/work"
31 "cmd/internal/quoted"
32 "cmd/internal/telemetry"
33 )
34
35 var CmdEnv = &base.Command{
36 UsageLine: "go env [-json] [-changed] [-u] [-w] [var ...]",
37 Short: "print Go environment information",
38 Long: `
39 Env prints Go environment information.
40
41 By default env prints information as a shell script
42 (on Windows, a batch file). If one or more variable
43 names is given as arguments, env prints the value of
44 each named variable on its own line.
45
46 The -json flag prints the environment in JSON format
47 instead of as a shell script.
48
49 The -u flag requires one or more arguments and unsets
50 the default setting for the named environment variables,
51 if one has been set with 'go env -w'.
52
53 The -w flag requires one or more arguments of the
54 form NAME=VALUE and changes the default settings
55 of the named environment variables to the given values.
56
57 The -changed flag prints only those settings whose effective
58 value differs from the default value that would be obtained in
59 an empty environment with no prior uses of the -w flag.
60
61 For more about environment variables, see 'go help environment'.
62 `,
63 }
64
65 func init() {
66 CmdEnv.Run = runEnv
67 base.AddChdirFlag(&CmdEnv.Flag)
68 base.AddBuildFlagsNX(&CmdEnv.Flag)
69 }
70
71 var (
72 envJson = CmdEnv.Flag.Bool("json", false, "")
73 envU = CmdEnv.Flag.Bool("u", false, "")
74 envW = CmdEnv.Flag.Bool("w", false, "")
75 envChanged = CmdEnv.Flag.Bool("changed", false, "")
76 )
77
78 func MkEnv() []cfg.EnvVar {
79 envFile, envFileChanged, _ := cfg.EnvFile()
80 env := []cfg.EnvVar{
81 {Name: "GO111MODULE", Value: cfg.Getenv("GO111MODULE")},
82 {Name: "GOARCH", Value: cfg.Goarch, Changed: cfg.Goarch != runtime.GOARCH},
83 {Name: "GOBIN", Value: cfg.GOBIN},
84 {Name: "GOCACHE"},
85 {Name: "GOENV", Value: envFile, Changed: envFileChanged},
86 {Name: "GOEXE", Value: cfg.ExeSuffix},
87
88
89
90
91
92
93 {Name: "GOEXPERIMENT", Value: cfg.RawGOEXPERIMENT},
94
95 {Name: "GOFLAGS", Value: cfg.Getenv("GOFLAGS")},
96 {Name: "GOHOSTARCH", Value: runtime.GOARCH},
97 {Name: "GOHOSTOS", Value: runtime.GOOS},
98 {Name: "GOINSECURE", Value: cfg.GOINSECURE},
99 {Name: "GOMODCACHE", Value: cfg.GOMODCACHE, Changed: cfg.GOMODCACHEChanged},
100 {Name: "GONOPROXY", Value: cfg.GONOPROXY, Changed: cfg.GONOPROXYChanged},
101 {Name: "GONOSUMDB", Value: cfg.GONOSUMDB, Changed: cfg.GONOSUMDBChanged},
102 {Name: "GOOS", Value: cfg.Goos, Changed: cfg.Goos != runtime.GOOS},
103 {Name: "GOPATH", Value: cfg.BuildContext.GOPATH, Changed: cfg.GOPATHChanged},
104 {Name: "GOPRIVATE", Value: cfg.GOPRIVATE},
105 {Name: "GOPROXY", Value: cfg.GOPROXY, Changed: cfg.GOPROXYChanged},
106 {Name: "GOROOT", Value: cfg.GOROOT},
107 {Name: "GOSUMDB", Value: cfg.GOSUMDB, Changed: cfg.GOSUMDBChanged},
108 {Name: "GOTMPDIR", Value: cfg.Getenv("GOTMPDIR")},
109 {Name: "GOTOOLCHAIN", Value: cfg.Getenv("GOTOOLCHAIN")},
110 {Name: "GOTOOLDIR", Value: build.ToolDir},
111 {Name: "GOVCS", Value: cfg.GOVCS},
112 {Name: "GOVERSION", Value: runtime.Version()},
113 {Name: "GODEBUG", Value: os.Getenv("GODEBUG")},
114 {Name: "GOTELEMETRY", Value: telemetry.Mode()},
115 {Name: "GOTELEMETRYDIR", Value: telemetry.Dir()},
116 }
117
118 for i := range env {
119 switch env[i].Name {
120 case "GO111MODULE":
121 if env[i].Value != "on" && env[i].Value != "" {
122 env[i].Changed = true
123 }
124 case "GOBIN", "GOEXPERIMENT", "GOFLAGS", "GOINSECURE", "GOPRIVATE", "GOTMPDIR", "GOVCS":
125 if env[i].Value != "" {
126 env[i].Changed = true
127 }
128 case "GOCACHE":
129 env[i].Value, env[i].Changed = cache.DefaultDir()
130 case "GOTOOLCHAIN":
131 if env[i].Value != "auto" {
132 env[i].Changed = true
133 }
134 case "GODEBUG":
135 env[i].Changed = env[i].Value != ""
136 }
137 }
138
139 if work.GccgoBin != "" {
140 env = append(env, cfg.EnvVar{Name: "GCCGO", Value: work.GccgoBin, Changed: true})
141 } else {
142 env = append(env, cfg.EnvVar{Name: "GCCGO", Value: work.GccgoName})
143 }
144
145 goarch, val, changed := cfg.GetArchEnv()
146 if goarch != "" {
147 env = append(env, cfg.EnvVar{Name: goarch, Value: val, Changed: changed})
148 }
149
150 cc := cfg.Getenv("CC")
151 ccChanged := true
152 if cc == "" {
153 ccChanged = false
154 cc = cfg.DefaultCC(cfg.Goos, cfg.Goarch)
155 }
156 cxx := cfg.Getenv("CXX")
157 cxxChanged := true
158 if cxx == "" {
159 cxxChanged = false
160 cxx = cfg.DefaultCXX(cfg.Goos, cfg.Goarch)
161 }
162 ar, arChanged := cfg.EnvOrAndChanged("AR", "ar")
163 env = append(env, cfg.EnvVar{Name: "AR", Value: ar, Changed: arChanged})
164 env = append(env, cfg.EnvVar{Name: "CC", Value: cc, Changed: ccChanged})
165 env = append(env, cfg.EnvVar{Name: "CXX", Value: cxx, Changed: cxxChanged})
166
167 if cfg.BuildContext.CgoEnabled {
168 env = append(env, cfg.EnvVar{Name: "CGO_ENABLED", Value: "1", Changed: cfg.CGOChanged})
169 } else {
170 env = append(env, cfg.EnvVar{Name: "CGO_ENABLED", Value: "0", Changed: cfg.CGOChanged})
171 }
172
173 return env
174 }
175
176 func findEnv(env []cfg.EnvVar, name string) string {
177 for _, e := range env {
178 if e.Name == name {
179 return e.Value
180 }
181 }
182 if cfg.CanGetenv(name) {
183 return cfg.Getenv(name)
184 }
185 return ""
186 }
187
188
189 func ExtraEnvVars() []cfg.EnvVar {
190 gomod := ""
191 modload.Init()
192 if modload.HasModRoot() {
193 gomod = modload.ModFilePath()
194 } else if modload.Enabled() {
195 gomod = os.DevNull
196 }
197 modload.InitWorkfile()
198 gowork := modload.WorkFilePath()
199
200 if cfg.Getenv("GOWORK") == "off" {
201 gowork = "off"
202 }
203 return []cfg.EnvVar{
204 {Name: "GOMOD", Value: gomod},
205 {Name: "GOWORK", Value: gowork},
206 }
207 }
208
209
210
211 func ExtraEnvVarsCostly() []cfg.EnvVar {
212 b := work.NewBuilder("")
213 defer func() {
214 if err := b.Close(); err != nil {
215 base.Fatal(err)
216 }
217 }()
218
219 cppflags, cflags, cxxflags, fflags, ldflags, err := b.CFlags(&load.Package{})
220 if err != nil {
221
222 fmt.Fprintf(os.Stderr, "go: invalid cflags: %v\n", err)
223 return nil
224 }
225 cmd := b.GccCmd(".", "")
226
227 join := func(s []string) string {
228 q, err := quoted.Join(s)
229 if err != nil {
230 return strings.Join(s, " ")
231 }
232 return q
233 }
234
235 ret := []cfg.EnvVar{
236
237 {Name: "CGO_CFLAGS", Value: join(cflags)},
238 {Name: "CGO_CPPFLAGS", Value: join(cppflags)},
239 {Name: "CGO_CXXFLAGS", Value: join(cxxflags)},
240 {Name: "CGO_FFLAGS", Value: join(fflags)},
241 {Name: "CGO_LDFLAGS", Value: join(ldflags)},
242 {Name: "PKG_CONFIG", Value: b.PkgconfigCmd()},
243 {Name: "GOGCCFLAGS", Value: join(cmd[3:])},
244 }
245
246 for i := range ret {
247 ev := &ret[i]
248 switch ev.Name {
249 case "GOGCCFLAGS":
250 case "CGO_CPPFLAGS":
251 ev.Changed = ev.Value != ""
252 case "PKG_CONFIG":
253 ev.Changed = ev.Value != cfg.DefaultPkgConfig
254 case "CGO_CXXFLAGS", "CGO_CFLAGS", "CGO_FFLAGS", "GGO_LDFLAGS":
255 ev.Changed = ev.Value != work.DefaultCFlags
256 }
257 }
258
259 return ret
260 }
261
262
263 func argKey(arg string) string {
264 i := strings.Index(arg, "=")
265 if i < 0 {
266 return arg
267 }
268 return arg[:i]
269 }
270
271 func runEnv(ctx context.Context, cmd *base.Command, args []string) {
272 if *envJson && *envU {
273 base.Fatalf("go: cannot use -json with -u")
274 }
275 if *envJson && *envW {
276 base.Fatalf("go: cannot use -json with -w")
277 }
278 if *envU && *envW {
279 base.Fatalf("go: cannot use -u with -w")
280 }
281
282
283
284 if *envW {
285 runEnvW(args)
286 return
287 }
288
289 if *envU {
290 runEnvU(args)
291 return
292 }
293
294 buildcfg.Check()
295 if cfg.ExperimentErr != nil {
296 base.Fatal(cfg.ExperimentErr)
297 }
298
299 for _, arg := range args {
300 if strings.Contains(arg, "=") {
301 base.Fatalf("go: invalid variable name %q (use -w to set variable)", arg)
302 }
303 }
304
305 env := cfg.CmdEnv
306 env = append(env, ExtraEnvVars()...)
307
308 if err := fsys.Init(base.Cwd()); err != nil {
309 base.Fatal(err)
310 }
311
312
313 needCostly := false
314 if len(args) == 0 {
315
316
317 needCostly = true
318 } else {
319 needCostly = false
320 checkCostly:
321 for _, arg := range args {
322 switch argKey(arg) {
323 case "CGO_CFLAGS",
324 "CGO_CPPFLAGS",
325 "CGO_CXXFLAGS",
326 "CGO_FFLAGS",
327 "CGO_LDFLAGS",
328 "PKG_CONFIG",
329 "GOGCCFLAGS":
330 needCostly = true
331 break checkCostly
332 }
333 }
334 }
335 if needCostly {
336 work.BuildInit()
337 env = append(env, ExtraEnvVarsCostly()...)
338 }
339
340 if len(args) > 0 {
341
342 if !*envChanged {
343 if *envJson {
344 var es []cfg.EnvVar
345 for _, name := range args {
346 e := cfg.EnvVar{Name: name, Value: findEnv(env, name)}
347 es = append(es, e)
348 }
349 env = es
350 } else {
351
352 for _, name := range args {
353 fmt.Printf("%s\n", findEnv(env, name))
354 }
355 return
356 }
357 } else {
358
359 var es []cfg.EnvVar
360 for _, name := range args {
361 for _, e := range env {
362 if e.Name == name {
363 es = append(es, e)
364 break
365 }
366 }
367 }
368 env = es
369 }
370 }
371
372
373 if *envJson {
374 printEnvAsJSON(env, *envChanged)
375 } else {
376 PrintEnv(os.Stdout, env, *envChanged)
377 }
378 }
379
380 func runEnvW(args []string) {
381
382 if len(args) == 0 {
383 base.Fatalf("go: no KEY=VALUE arguments given")
384 }
385 osEnv := make(map[string]string)
386 for _, e := range cfg.OrigEnv {
387 if i := strings.Index(e, "="); i >= 0 {
388 osEnv[e[:i]] = e[i+1:]
389 }
390 }
391 add := make(map[string]string)
392 for _, arg := range args {
393 key, val, found := strings.Cut(arg, "=")
394 if !found {
395 base.Fatalf("go: arguments must be KEY=VALUE: invalid argument: %s", arg)
396 }
397 if err := checkEnvWrite(key, val); err != nil {
398 base.Fatal(err)
399 }
400 if _, ok := add[key]; ok {
401 base.Fatalf("go: multiple values for key: %s", key)
402 }
403 add[key] = val
404 if osVal := osEnv[key]; osVal != "" && osVal != val {
405 fmt.Fprintf(os.Stderr, "warning: go env -w %s=... does not override conflicting OS environment variable\n", key)
406 }
407 }
408
409 if err := checkBuildConfig(add, nil); err != nil {
410 base.Fatal(err)
411 }
412
413 gotmp, okGOTMP := add["GOTMPDIR"]
414 if okGOTMP {
415 if !filepath.IsAbs(gotmp) && gotmp != "" {
416 base.Fatalf("go: GOTMPDIR must be an absolute path")
417 }
418 }
419
420 updateEnvFile(add, nil)
421 }
422
423 func runEnvU(args []string) {
424
425 if len(args) == 0 {
426 base.Fatalf("go: 'go env -u' requires an argument")
427 }
428 del := make(map[string]bool)
429 for _, arg := range args {
430 if err := checkEnvWrite(arg, ""); err != nil {
431 base.Fatal(err)
432 }
433 del[arg] = true
434 }
435
436 if err := checkBuildConfig(nil, del); err != nil {
437 base.Fatal(err)
438 }
439
440 updateEnvFile(nil, del)
441 }
442
443
444
445 func checkBuildConfig(add map[string]string, del map[string]bool) error {
446
447
448
449
450 get := func(key, cur, def string) (string, bool) {
451 if val, ok := add[key]; ok {
452 return val, true
453 }
454 if del[key] {
455 val := getOrigEnv(key)
456 if val == "" {
457 val = def
458 }
459 return val, true
460 }
461 return cur, false
462 }
463
464 goos, okGOOS := get("GOOS", cfg.Goos, build.Default.GOOS)
465 goarch, okGOARCH := get("GOARCH", cfg.Goarch, build.Default.GOARCH)
466 if okGOOS || okGOARCH {
467 if err := work.CheckGOOSARCHPair(goos, goarch); err != nil {
468 return err
469 }
470 }
471
472 goexperiment, okGOEXPERIMENT := get("GOEXPERIMENT", cfg.RawGOEXPERIMENT, buildcfg.DefaultGOEXPERIMENT)
473 if okGOEXPERIMENT {
474 if _, err := buildcfg.ParseGOEXPERIMENT(goos, goarch, goexperiment); err != nil {
475 return err
476 }
477 }
478
479 return nil
480 }
481
482
483 func PrintEnv(w io.Writer, env []cfg.EnvVar, onlyChanged bool) {
484 for _, e := range env {
485 if e.Name != "TERM" {
486 if runtime.GOOS != "plan9" && bytes.Contains([]byte(e.Value), []byte{0}) {
487 base.Fatalf("go: internal error: encountered null byte in environment variable %s on non-plan9 platform", e.Name)
488 }
489 if onlyChanged && !e.Changed {
490 continue
491 }
492 switch runtime.GOOS {
493 default:
494 fmt.Fprintf(w, "%s=%s\n", e.Name, shellQuote(e.Value))
495 case "plan9":
496 if strings.IndexByte(e.Value, '\x00') < 0 {
497 fmt.Fprintf(w, "%s='%s'\n", e.Name, strings.ReplaceAll(e.Value, "'", "''"))
498 } else {
499 v := strings.Split(e.Value, "\x00")
500 fmt.Fprintf(w, "%s=(", e.Name)
501 for x, s := range v {
502 if x > 0 {
503 fmt.Fprintf(w, " ")
504 }
505 fmt.Fprintf(w, "'%s'", strings.ReplaceAll(s, "'", "''"))
506 }
507 fmt.Fprintf(w, ")\n")
508 }
509 case "windows":
510 if hasNonGraphic(e.Value) {
511 base.Errorf("go: stripping unprintable or unescapable characters from %%%q%%", e.Name)
512 }
513 fmt.Fprintf(w, "set %s=%s\n", e.Name, batchEscape(e.Value))
514 }
515 }
516 }
517 }
518
519 func hasNonGraphic(s string) bool {
520 for _, c := range []byte(s) {
521 if c == '\r' || c == '\n' || (!unicode.IsGraphic(rune(c)) && !unicode.IsSpace(rune(c))) {
522 return true
523 }
524 }
525 return false
526 }
527
528 func shellQuote(s string) string {
529 var b bytes.Buffer
530 b.WriteByte('\'')
531 for _, x := range []byte(s) {
532 if x == '\'' {
533
534
535 b.WriteString(`'\''`)
536 } else {
537 b.WriteByte(x)
538 }
539 }
540 b.WriteByte('\'')
541 return b.String()
542 }
543
544 func batchEscape(s string) string {
545 var b bytes.Buffer
546 for _, x := range []byte(s) {
547 if x == '\r' || x == '\n' || (!unicode.IsGraphic(rune(x)) && !unicode.IsSpace(rune(x))) {
548 b.WriteRune(unicode.ReplacementChar)
549 continue
550 }
551 switch x {
552 case '%':
553 b.WriteString("%%")
554 case '<', '>', '|', '&', '^':
555
556
557 b.WriteByte('^')
558 b.WriteByte(x)
559 default:
560 b.WriteByte(x)
561 }
562 }
563 return b.String()
564 }
565
566 func printEnvAsJSON(env []cfg.EnvVar, onlyChanged bool) {
567 m := make(map[string]string)
568 for _, e := range env {
569 if e.Name == "TERM" {
570 continue
571 }
572 if onlyChanged && !e.Changed {
573 continue
574 }
575 m[e.Name] = e.Value
576 }
577 enc := json.NewEncoder(os.Stdout)
578 enc.SetIndent("", "\t")
579 if err := enc.Encode(m); err != nil {
580 base.Fatalf("go: %s", err)
581 }
582 }
583
584 func getOrigEnv(key string) string {
585 for _, v := range cfg.OrigEnv {
586 if v, found := strings.CutPrefix(v, key+"="); found {
587 return v
588 }
589 }
590 return ""
591 }
592
593 func checkEnvWrite(key, val string) error {
594 switch key {
595 case "GOEXE", "GOGCCFLAGS", "GOHOSTARCH", "GOHOSTOS", "GOMOD", "GOWORK", "GOTOOLDIR", "GOVERSION":
596 return fmt.Errorf("%s cannot be modified", key)
597 case "GOENV", "GODEBUG":
598 return fmt.Errorf("%s can only be set using the OS environment", key)
599 }
600
601
602
603 if !cfg.CanGetenv(key) {
604 return fmt.Errorf("unknown go command variable %s", key)
605 }
606
607
608
609
610 switch key {
611 case "GO111MODULE":
612 switch val {
613 case "", "auto", "on", "off":
614 default:
615 return fmt.Errorf("invalid %s value %q", key, val)
616 }
617 case "GOPATH":
618 if strings.HasPrefix(val, "~") {
619 return fmt.Errorf("GOPATH entry cannot start with shell metacharacter '~': %q", val)
620 }
621 if !filepath.IsAbs(val) && val != "" {
622 return fmt.Errorf("GOPATH entry is relative; must be absolute path: %q", val)
623 }
624 case "GOMODCACHE":
625 if !filepath.IsAbs(val) && val != "" {
626 return fmt.Errorf("GOMODCACHE entry is relative; must be absolute path: %q", val)
627 }
628 case "CC", "CXX":
629 if val == "" {
630 break
631 }
632 args, err := quoted.Split(val)
633 if err != nil {
634 return fmt.Errorf("invalid %s: %v", key, err)
635 }
636 if len(args) == 0 {
637 return fmt.Errorf("%s entry cannot contain only space", key)
638 }
639 if !filepath.IsAbs(args[0]) && args[0] != filepath.Base(args[0]) {
640 return fmt.Errorf("%s entry is relative; must be absolute path: %q", key, args[0])
641 }
642 }
643
644 if !utf8.ValidString(val) {
645 return fmt.Errorf("invalid UTF-8 in %s=... value", key)
646 }
647 if strings.Contains(val, "\x00") {
648 return fmt.Errorf("invalid NUL in %s=... value", key)
649 }
650 if strings.ContainsAny(val, "\v\r\n") {
651 return fmt.Errorf("invalid newline in %s=... value", key)
652 }
653 return nil
654 }
655
656 func readEnvFileLines(mustExist bool) []string {
657 file, _, err := cfg.EnvFile()
658 if file == "" {
659 if mustExist {
660 base.Fatalf("go: cannot find go env config: %v", err)
661 }
662 return nil
663 }
664 data, err := os.ReadFile(file)
665 if err != nil && (!os.IsNotExist(err) || mustExist) {
666 base.Fatalf("go: reading go env config: %v", err)
667 }
668 lines := strings.SplitAfter(string(data), "\n")
669 if lines[len(lines)-1] == "" {
670 lines = lines[:len(lines)-1]
671 } else {
672 lines[len(lines)-1] += "\n"
673 }
674 return lines
675 }
676
677 func updateEnvFile(add map[string]string, del map[string]bool) {
678 lines := readEnvFileLines(len(add) == 0)
679
680
681
682 prev := make(map[string]int)
683 for l, line := range lines {
684 if key := lineToKey(line); key != "" {
685 if p, ok := prev[key]; ok {
686 lines[p] = ""
687 }
688 prev[key] = l
689 }
690 }
691
692
693 for key, val := range add {
694 if p, ok := prev[key]; ok {
695 lines[p] = key + "=" + val + "\n"
696 delete(add, key)
697 }
698 }
699 for key, val := range add {
700 lines = append(lines, key+"="+val+"\n")
701 }
702
703
704 for key := range del {
705 if p, ok := prev[key]; ok {
706 lines[p] = ""
707 }
708 }
709
710
711
712
713 start := 0
714 for i := 0; i <= len(lines); i++ {
715 if i == len(lines) || lineToKey(lines[i]) == "" {
716 sortKeyValues(lines[start:i])
717 start = i + 1
718 }
719 }
720
721 file, _, err := cfg.EnvFile()
722 if file == "" {
723 base.Fatalf("go: cannot find go env config: %v", err)
724 }
725 data := []byte(strings.Join(lines, ""))
726 err = os.WriteFile(file, data, 0666)
727 if err != nil {
728
729 os.MkdirAll(filepath.Dir(file), 0777)
730 err = os.WriteFile(file, data, 0666)
731 if err != nil {
732 base.Fatalf("go: writing go env config: %v", err)
733 }
734 }
735 }
736
737
738 func lineToKey(line string) string {
739 i := strings.Index(line, "=")
740 if i < 0 || strings.Contains(line[:i], "#") {
741 return ""
742 }
743 return line[:i]
744 }
745
746
747
748 func sortKeyValues(lines []string) {
749 sort.Slice(lines, func(i, j int) bool {
750 return lineToKey(lines[i]) < lineToKey(lines[j])
751 })
752 }
753
View as plain text