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