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 func hasNonGraphic(s string) bool {
526 for _, c := range []byte(s) {
527 if c == '\r' || c == '\n' || (!unicode.IsGraphic(rune(c)) && !unicode.IsSpace(rune(c))) {
528 return true
529 }
530 }
531 return false
532 }
533
534 func shellQuote(s string) string {
535 var b bytes.Buffer
536 b.WriteByte('\'')
537 for _, x := range []byte(s) {
538 if x == '\'' {
539
540
541 b.WriteString(`'\''`)
542 } else {
543 b.WriteByte(x)
544 }
545 }
546 b.WriteByte('\'')
547 return b.String()
548 }
549
550 func batchEscape(s string) string {
551 var b bytes.Buffer
552 for _, x := range []byte(s) {
553 if x == '\r' || x == '\n' || (!unicode.IsGraphic(rune(x)) && !unicode.IsSpace(rune(x))) {
554 b.WriteRune(unicode.ReplacementChar)
555 continue
556 }
557 switch x {
558 case '%':
559 b.WriteString("%%")
560 case '<', '>', '|', '&', '^':
561
562
563 b.WriteByte('^')
564 b.WriteByte(x)
565 default:
566 b.WriteByte(x)
567 }
568 }
569 return b.String()
570 }
571
572 func printEnvAsJSON(env []cfg.EnvVar, onlyChanged bool) {
573 m := make(map[string]string)
574 for _, e := range env {
575 if e.Name == "TERM" {
576 continue
577 }
578 if onlyChanged && !e.Changed {
579 continue
580 }
581 m[e.Name] = e.Value
582 }
583 enc := json.NewEncoder(os.Stdout)
584 enc.SetIndent("", "\t")
585 if err := enc.Encode(m); err != nil {
586 base.Fatalf("go: %s", err)
587 }
588 }
589
590 func getOrigEnv(key string) string {
591 for _, v := range cfg.OrigEnv {
592 if v, found := strings.CutPrefix(v, key+"="); found {
593 return v
594 }
595 }
596 return ""
597 }
598
599 func checkEnvWrite(key, val string) error {
600 switch key {
601 case "GOEXE", "GOGCCFLAGS", "GOHOSTARCH", "GOHOSTOS", "GOMOD", "GOWORK", "GOTOOLDIR", "GOVERSION", "GOTELEMETRY", "GOTELEMETRYDIR":
602 return fmt.Errorf("%s cannot be modified", key)
603 case "GOENV", "GODEBUG":
604 return fmt.Errorf("%s can only be set using the OS environment", key)
605 }
606
607
608
609 if !cfg.CanGetenv(key) {
610 return fmt.Errorf("unknown go command variable %s", key)
611 }
612
613
614
615
616 switch key {
617 case "GO111MODULE":
618 switch val {
619 case "", "auto", "on", "off":
620 default:
621 return fmt.Errorf("invalid %s value %q", key, val)
622 }
623 case "GOPATH":
624 if strings.HasPrefix(val, "~") {
625 return fmt.Errorf("GOPATH entry cannot start with shell metacharacter '~': %q", val)
626 }
627 if !filepath.IsAbs(val) && val != "" {
628 return fmt.Errorf("GOPATH entry is relative; must be absolute path: %q", val)
629 }
630 case "GOMODCACHE":
631 if !filepath.IsAbs(val) && val != "" {
632 return fmt.Errorf("GOMODCACHE entry is relative; must be absolute path: %q", val)
633 }
634 case "CC", "CXX":
635 if val == "" {
636 break
637 }
638 args, err := quoted.Split(val)
639 if err != nil {
640 return fmt.Errorf("invalid %s: %v", key, err)
641 }
642 if len(args) == 0 {
643 return fmt.Errorf("%s entry cannot contain only space", key)
644 }
645 if !filepath.IsAbs(args[0]) && args[0] != filepath.Base(args[0]) {
646 return fmt.Errorf("%s entry is relative; must be absolute path: %q", key, args[0])
647 }
648 }
649
650 if !utf8.ValidString(val) {
651 return fmt.Errorf("invalid UTF-8 in %s=... value", key)
652 }
653 if strings.Contains(val, "\x00") {
654 return fmt.Errorf("invalid NUL in %s=... value", key)
655 }
656 if strings.ContainsAny(val, "\v\r\n") {
657 return fmt.Errorf("invalid newline in %s=... value", key)
658 }
659 return nil
660 }
661
662 func readEnvFileLines(mustExist bool) []string {
663 file, _, err := cfg.EnvFile()
664 if file == "" {
665 if mustExist {
666 base.Fatalf("go: cannot find go env config: %v", err)
667 }
668 return nil
669 }
670 data, err := os.ReadFile(file)
671 if err != nil && (!os.IsNotExist(err) || mustExist) {
672 base.Fatalf("go: reading go env config: %v", err)
673 }
674 lines := strings.SplitAfter(string(data), "\n")
675 if lines[len(lines)-1] == "" {
676 lines = lines[:len(lines)-1]
677 } else {
678 lines[len(lines)-1] += "\n"
679 }
680 return lines
681 }
682
683 func updateEnvFile(add map[string]string, del map[string]bool) {
684 lines := readEnvFileLines(len(add) == 0)
685
686
687
688 prev := make(map[string]int)
689 for l, line := range lines {
690 if key := lineToKey(line); key != "" {
691 if p, ok := prev[key]; ok {
692 lines[p] = ""
693 }
694 prev[key] = l
695 }
696 }
697
698
699 for key, val := range add {
700 if p, ok := prev[key]; ok {
701 lines[p] = key + "=" + val + "\n"
702 delete(add, key)
703 }
704 }
705 for key, val := range add {
706 lines = append(lines, key+"="+val+"\n")
707 }
708
709
710 for key := range del {
711 if p, ok := prev[key]; ok {
712 lines[p] = ""
713 }
714 }
715
716
717
718
719 start := 0
720 for i := 0; i <= len(lines); i++ {
721 if i == len(lines) || lineToKey(lines[i]) == "" {
722 sortKeyValues(lines[start:i])
723 start = i + 1
724 }
725 }
726
727 file, _, err := cfg.EnvFile()
728 if file == "" {
729 base.Fatalf("go: cannot find go env config: %v", err)
730 }
731 data := []byte(strings.Join(lines, ""))
732 err = os.WriteFile(file, data, 0666)
733 if err != nil {
734
735 os.MkdirAll(filepath.Dir(file), 0777)
736 err = os.WriteFile(file, data, 0666)
737 if err != nil {
738 base.Fatalf("go: writing go env config: %v", err)
739 }
740 }
741 }
742
743
744 func lineToKey(line string) string {
745 i := strings.Index(line, "=")
746 if i < 0 || strings.Contains(line[:i], "#") {
747 return ""
748 }
749 return line[:i]
750 }
751
752
753
754 func sortKeyValues(lines []string) {
755 sort.Slice(lines, func(i, j int) bool {
756 return lineToKey(lines[i]) < lineToKey(lines[j])
757 })
758 }
759
View as plain text