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