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