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