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