1// Copyright 2012 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Package envcmd implements the ``go env'' command.
6package envcmd
7
8import (
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
32var CmdEnv = &base.Command{
33	UsageLine: "go env [-json] [-u] [-w] [var ...]",
34	Short:     "print Go environment information",
35	Long: `
36Env prints Go environment information.
37
38By default env prints information as a shell script
39(on Windows, a batch file). If one or more variable
40names is given as arguments, env prints the value of
41each named variable on its own line.
42
43The -json flag prints the environment in JSON format
44instead of as a shell script.
45
46The -u flag requires one or more arguments and unsets
47the default setting for the named environment variables,
48if one has been set with 'go env -w'.
49
50The -w flag requires one or more arguments of the
51form NAME=VALUE and changes the default settings
52of the named environment variables to the given values.
53
54For more about environment variables, see 'go help environment'.
55	`,
56}
57
58func init() {
59	CmdEnv.Run = runEnv // break init cycle
60}
61
62var (
63	envJson = CmdEnv.Flag.Bool("json", false, "")
64	envU    = CmdEnv.Flag.Bool("u", false, "")
65	envW    = CmdEnv.Flag.Bool("w", false, "")
66)
67
68func 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
129func envOr(name, def string) string {
130	val := cfg.Getenv(name)
131	if val != "" {
132		return val
133	}
134	return def
135}
136
137func 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// ExtraEnvVars returns environment variables that should not leak into child processes.
147func 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	return []cfg.EnvVar{
158		{Name: "GOMOD", Value: gomod},
159		{Name: "GOWORK", Value: gowork},
160	}
161}
162
163// ExtraEnvVarsCostly returns environment variables that should not leak into child processes
164// but are costly to evaluate.
165func ExtraEnvVarsCostly() []cfg.EnvVar {
166	var b work.Builder
167	b.Init()
168	cppflags, cflags, cxxflags, fflags, ldflags, err := b.CFlags(&load.Package{})
169	if err != nil {
170		// Should not happen - b.CFlags was given an empty package.
171		fmt.Fprintf(os.Stderr, "go: invalid cflags: %v\n", err)
172		return nil
173	}
174	cmd := b.GccCmd(".", "")
175
176	return []cfg.EnvVar{
177		// Note: Update the switch in runEnv below when adding to this list.
178		{Name: "CGO_CFLAGS", Value: strings.Join(cflags, " ")},
179		{Name: "CGO_CPPFLAGS", Value: strings.Join(cppflags, " ")},
180		{Name: "CGO_CXXFLAGS", Value: strings.Join(cxxflags, " ")},
181		{Name: "CGO_FFLAGS", Value: strings.Join(fflags, " ")},
182		{Name: "CGO_LDFLAGS", Value: strings.Join(ldflags, " ")},
183		{Name: "PKG_CONFIG", Value: b.PkgconfigCmd()},
184		{Name: "GOGCCFLAGS", Value: strings.Join(cmd[3:], " ")},
185	}
186}
187
188// argKey returns the KEY part of the arg KEY=VAL, or else arg itself.
189func argKey(arg string) string {
190	i := strings.Index(arg, "=")
191	if i < 0 {
192		return arg
193	}
194	return arg[:i]
195}
196
197func runEnv(ctx context.Context, cmd *base.Command, args []string) {
198	if *envJson && *envU {
199		base.Fatalf("go: cannot use -json with -u")
200	}
201	if *envJson && *envW {
202		base.Fatalf("go: cannot use -json with -w")
203	}
204	if *envU && *envW {
205		base.Fatalf("go: cannot use -u with -w")
206	}
207
208	// Handle 'go env -w' and 'go env -u' before calling buildcfg.Check,
209	// so they can be used to recover from an invalid configuration.
210	if *envW {
211		runEnvW(args)
212		return
213	}
214
215	if *envU {
216		runEnvU(args)
217		return
218	}
219
220	buildcfg.Check()
221
222	env := cfg.CmdEnv
223	env = append(env, ExtraEnvVars()...)
224
225	if err := fsys.Init(base.Cwd()); err != nil {
226		base.Fatalf("go: %v", err)
227	}
228
229	// Do we need to call ExtraEnvVarsCostly, which is a bit expensive?
230	needCostly := false
231	if len(args) == 0 {
232		// We're listing all environment variables ("go env"),
233		// including the expensive ones.
234		needCostly = true
235	} else {
236		needCostly = false
237	checkCostly:
238		for _, arg := range args {
239			switch argKey(arg) {
240			case "CGO_CFLAGS",
241				"CGO_CPPFLAGS",
242				"CGO_CXXFLAGS",
243				"CGO_FFLAGS",
244				"CGO_LDFLAGS",
245				"PKG_CONFIG",
246				"GOGCCFLAGS":
247				needCostly = true
248				break checkCostly
249			}
250		}
251	}
252	if needCostly {
253		env = append(env, ExtraEnvVarsCostly()...)
254	}
255
256	if len(args) > 0 {
257		if *envJson {
258			var es []cfg.EnvVar
259			for _, name := range args {
260				e := cfg.EnvVar{Name: name, Value: findEnv(env, name)}
261				es = append(es, e)
262			}
263			printEnvAsJSON(es)
264		} else {
265			for _, name := range args {
266				fmt.Printf("%s\n", findEnv(env, name))
267			}
268		}
269		return
270	}
271
272	if *envJson {
273		printEnvAsJSON(env)
274		return
275	}
276
277	PrintEnv(os.Stdout, env)
278}
279
280func runEnvW(args []string) {
281	// Process and sanity-check command line.
282	if len(args) == 0 {
283		base.Fatalf("go: no KEY=VALUE arguments given")
284	}
285	osEnv := make(map[string]string)
286	for _, e := range cfg.OrigEnv {
287		if i := strings.Index(e, "="); i >= 0 {
288			osEnv[e[:i]] = e[i+1:]
289		}
290	}
291	add := make(map[string]string)
292	for _, arg := range args {
293		i := strings.Index(arg, "=")
294		if i < 0 {
295			base.Fatalf("go: arguments must be KEY=VALUE: invalid argument: %s", arg)
296		}
297		key, val := arg[:i], arg[i+1:]
298		if err := checkEnvWrite(key, val); err != nil {
299			base.Fatalf("go: %v", err)
300		}
301		if _, ok := add[key]; ok {
302			base.Fatalf("go: multiple values for key: %s", key)
303		}
304		add[key] = val
305		if osVal := osEnv[key]; osVal != "" && osVal != val {
306			fmt.Fprintf(os.Stderr, "warning: go env -w %s=... does not override conflicting OS environment variable\n", key)
307		}
308	}
309
310	if err := checkBuildConfig(add, nil); err != nil {
311		base.Fatalf("go: %v", err)
312	}
313
314	gotmp, okGOTMP := add["GOTMPDIR"]
315	if okGOTMP {
316		if !filepath.IsAbs(gotmp) && gotmp != "" {
317			base.Fatalf("go: GOTMPDIR must be an absolute path")
318		}
319	}
320
321	updateEnvFile(add, nil)
322}
323
324func runEnvU(args []string) {
325	// Process and sanity-check command line.
326	if len(args) == 0 {
327		base.Fatalf("go: 'go env -u' requires an argument")
328	}
329	del := make(map[string]bool)
330	for _, arg := range args {
331		if err := checkEnvWrite(arg, ""); err != nil {
332			base.Fatalf("go: %v", err)
333		}
334		del[arg] = true
335	}
336
337	if err := checkBuildConfig(nil, del); err != nil {
338		base.Fatalf("go: %v", err)
339	}
340
341	updateEnvFile(nil, del)
342}
343
344// checkBuildConfig checks whether the build configuration is valid
345// after the specified configuration environment changes are applied.
346func checkBuildConfig(add map[string]string, del map[string]bool) error {
347	// get returns the value for key after applying add and del and
348	// reports whether it changed. cur should be the current value
349	// (i.e., before applying changes) and def should be the default
350	// value (i.e., when no environment variables are provided at all).
351	get := func(key, cur, def string) (string, bool) {
352		if val, ok := add[key]; ok {
353			return val, true
354		}
355		if del[key] {
356			val := getOrigEnv(key)
357			if val == "" {
358				val = def
359			}
360			return val, true
361		}
362		return cur, false
363	}
364
365	goos, okGOOS := get("GOOS", cfg.Goos, build.Default.GOOS)
366	goarch, okGOARCH := get("GOARCH", cfg.Goarch, build.Default.GOARCH)
367	if okGOOS || okGOARCH {
368		if err := work.CheckGOOSARCHPair(goos, goarch); err != nil {
369			return err
370		}
371	}
372
373	goexperiment, okGOEXPERIMENT := get("GOEXPERIMENT", buildcfg.GOEXPERIMENT(), "")
374	if okGOEXPERIMENT {
375		if _, _, err := buildcfg.ParseGOEXPERIMENT(goos, goarch, goexperiment); err != nil {
376			return err
377		}
378	}
379
380	return nil
381}
382
383// PrintEnv prints the environment variables to w.
384func PrintEnv(w io.Writer, env []cfg.EnvVar) {
385	for _, e := range env {
386		if e.Name != "TERM" {
387			switch runtime.GOOS {
388			default:
389				fmt.Fprintf(w, "%s=\"%s\"\n", e.Name, e.Value)
390			case "plan9":
391				if strings.IndexByte(e.Value, '\x00') < 0 {
392					fmt.Fprintf(w, "%s='%s'\n", e.Name, strings.ReplaceAll(e.Value, "'", "''"))
393				} else {
394					v := strings.Split(e.Value, "\x00")
395					fmt.Fprintf(w, "%s=(", e.Name)
396					for x, s := range v {
397						if x > 0 {
398							fmt.Fprintf(w, " ")
399						}
400						fmt.Fprintf(w, "%s", s)
401					}
402					fmt.Fprintf(w, ")\n")
403				}
404			case "windows":
405				fmt.Fprintf(w, "set %s=%s\n", e.Name, e.Value)
406			}
407		}
408	}
409}
410
411func printEnvAsJSON(env []cfg.EnvVar) {
412	m := make(map[string]string)
413	for _, e := range env {
414		if e.Name == "TERM" {
415			continue
416		}
417		m[e.Name] = e.Value
418	}
419	enc := json.NewEncoder(os.Stdout)
420	enc.SetIndent("", "\t")
421	if err := enc.Encode(m); err != nil {
422		base.Fatalf("go: %s", err)
423	}
424}
425
426func getOrigEnv(key string) string {
427	for _, v := range cfg.OrigEnv {
428		if strings.HasPrefix(v, key+"=") {
429			return strings.TrimPrefix(v, key+"=")
430		}
431	}
432	return ""
433}
434
435func checkEnvWrite(key, val string) error {
436	switch key {
437	case "GOEXE", "GOGCCFLAGS", "GOHOSTARCH", "GOHOSTOS", "GOMOD", "GOWORK", "GOTOOLDIR", "GOVERSION":
438		return fmt.Errorf("%s cannot be modified", key)
439	case "GOENV":
440		return fmt.Errorf("%s can only be set using the OS environment", key)
441	}
442
443	// To catch typos and the like, check that we know the variable.
444	if !cfg.CanGetenv(key) {
445		return fmt.Errorf("unknown go command variable %s", key)
446	}
447
448	// Some variables can only have one of a few valid values. If set to an
449	// invalid value, the next cmd/go invocation might fail immediately,
450	// even 'go env -w' itself.
451	switch key {
452	case "GO111MODULE":
453		switch val {
454		case "", "auto", "on", "off":
455		default:
456			return fmt.Errorf("invalid %s value %q", key, val)
457		}
458	case "GOPATH":
459		if strings.HasPrefix(val, "~") {
460			return fmt.Errorf("GOPATH entry cannot start with shell metacharacter '~': %q", val)
461		}
462		if !filepath.IsAbs(val) && val != "" {
463			return fmt.Errorf("GOPATH entry is relative; must be absolute path: %q", val)
464		}
465	case "GOMODCACHE":
466		if !filepath.IsAbs(val) && val != "" {
467			return fmt.Errorf("GOMODCACHE entry is relative; must be absolute path: %q", val)
468		}
469	case "CC", "CXX":
470		if val == "" {
471			break
472		}
473		args, err := quoted.Split(val)
474		if err != nil {
475			return fmt.Errorf("invalid %s: %v", key, err)
476		}
477		if len(args) == 0 {
478			return fmt.Errorf("%s entry cannot contain only space", key)
479		}
480		if !filepath.IsAbs(args[0]) && args[0] != filepath.Base(args[0]) {
481			return fmt.Errorf("%s entry is relative; must be absolute path: %q", key, args[0])
482		}
483	}
484
485	if !utf8.ValidString(val) {
486		return fmt.Errorf("invalid UTF-8 in %s=... value", key)
487	}
488	if strings.Contains(val, "\x00") {
489		return fmt.Errorf("invalid NUL in %s=... value", key)
490	}
491	if strings.ContainsAny(val, "\v\r\n") {
492		return fmt.Errorf("invalid newline in %s=... value", key)
493	}
494	return nil
495}
496
497func updateEnvFile(add map[string]string, del map[string]bool) {
498	file, err := cfg.EnvFile()
499	if file == "" {
500		base.Fatalf("go: cannot find go env config: %v", err)
501	}
502	data, err := os.ReadFile(file)
503	if err != nil && (!os.IsNotExist(err) || len(add) == 0) {
504		base.Fatalf("go: reading go env config: %v", err)
505	}
506
507	lines := strings.SplitAfter(string(data), "\n")
508	if lines[len(lines)-1] == "" {
509		lines = lines[:len(lines)-1]
510	} else {
511		lines[len(lines)-1] += "\n"
512	}
513
514	// Delete all but last copy of any duplicated variables,
515	// since the last copy is the one that takes effect.
516	prev := make(map[string]int)
517	for l, line := range lines {
518		if key := lineToKey(line); key != "" {
519			if p, ok := prev[key]; ok {
520				lines[p] = ""
521			}
522			prev[key] = l
523		}
524	}
525
526	// Add variables (go env -w). Update existing lines in file if present, add to end otherwise.
527	for key, val := range add {
528		if p, ok := prev[key]; ok {
529			lines[p] = key + "=" + val + "\n"
530			delete(add, key)
531		}
532	}
533	for key, val := range add {
534		lines = append(lines, key+"="+val+"\n")
535	}
536
537	// Delete requested variables (go env -u).
538	for key := range del {
539		if p, ok := prev[key]; ok {
540			lines[p] = ""
541		}
542	}
543
544	// Sort runs of KEY=VALUE lines
545	// (that is, blocks of lines where blocks are separated
546	// by comments, blank lines, or invalid lines).
547	start := 0
548	for i := 0; i <= len(lines); i++ {
549		if i == len(lines) || lineToKey(lines[i]) == "" {
550			sortKeyValues(lines[start:i])
551			start = i + 1
552		}
553	}
554
555	data = []byte(strings.Join(lines, ""))
556	err = os.WriteFile(file, data, 0666)
557	if err != nil {
558		// Try creating directory.
559		os.MkdirAll(filepath.Dir(file), 0777)
560		err = os.WriteFile(file, data, 0666)
561		if err != nil {
562			base.Fatalf("go: writing go env config: %v", err)
563		}
564	}
565}
566
567// lineToKey returns the KEY part of the line KEY=VALUE or else an empty string.
568func lineToKey(line string) string {
569	i := strings.Index(line, "=")
570	if i < 0 || strings.Contains(line[:i], "#") {
571		return ""
572	}
573	return line[:i]
574}
575
576// sortKeyValues sorts a sequence of lines by key.
577// It differs from sort.Strings in that keys which are GOx where x is an ASCII
578// character smaller than = sort after GO=.
579// (There are no such keys currently. It used to matter for GO386 which was
580// removed in Go 1.16.)
581func sortKeyValues(lines []string) {
582	sort.Slice(lines, func(i, j int) bool {
583		return lineToKey(lines[i]) < lineToKey(lines[j])
584	})
585}
586