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