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)
30
31var CmdEnv = &base.Command{
32	UsageLine: "go env [-json] [-u] [-w] [var ...]",
33	Short:     "print Go environment information",
34	Long: `
35Env prints Go environment information.
36
37By default env prints information as a shell script
38(on Windows, a batch file). If one or more variable
39names is given as arguments, env prints the value of
40each named variable on its own line.
41
42The -json flag prints the environment in JSON format
43instead of as a shell script.
44
45The -u flag requires one or more arguments and unsets
46the default setting for the named environment variables,
47if one has been set with 'go env -w'.
48
49The -w flag requires one or more arguments of the
50form NAME=VALUE and changes the default settings
51of the named environment variables to the given values.
52
53For more about environment variables, see 'go help environment'.
54	`,
55}
56
57func init() {
58	CmdEnv.Run = runEnv // break init cycle
59}
60
61var (
62	envJson = CmdEnv.Flag.Bool("json", false, "")
63	envU    = CmdEnv.Flag.Bool("u", false, "")
64	envW    = CmdEnv.Flag.Bool("w", false, "")
65)
66
67func 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
128func envOr(name, def string) string {
129	val := cfg.Getenv(name)
130	if val != "" {
131		return val
132	}
133	return def
134}
135
136func 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// ExtraEnvVars returns environment variables that should not leak into child processes.
146func 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// ExtraEnvVarsCostly returns environment variables that should not leak into child processes
159// but are costly to evaluate.
160func 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		// Should not happen - b.CFlags was given an empty package.
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		// Note: Update the switch in runEnv below when adding to this list.
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// argKey returns the KEY part of the arg KEY=VAL, or else arg itself.
184func argKey(arg string) string {
185	i := strings.Index(arg, "=")
186	if i < 0 {
187		return arg
188	}
189	return arg[:i]
190}
191
192func 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	// Handle 'go env -w' and 'go env -u' before calling buildcfg.Check,
204	// so they can be used to recover from an invalid configuration.
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	// Do we need to call ExtraEnvVarsCostly, which is a bit expensive?
225	needCostly := false
226	if len(args) == 0 {
227		// We're listing all environment variables ("go env"),
228		// including the expensive ones.
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
275func runEnvW(args []string) {
276	// Process and sanity-check command line.
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
319func runEnvU(args []string) {
320	// Process and sanity-check command line.
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// checkBuildConfig checks whether the build configuration is valid
340// after the specified configuration environment changes are applied.
341func checkBuildConfig(add map[string]string, del map[string]bool) error {
342	// get returns the value for key after applying add and del and
343	// reports whether it changed. cur should be the current value
344	// (i.e., before applying changes) and def should be the default
345	// value (i.e., when no environment variables are provided at all).
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// PrintEnv prints the environment variables to w.
379func 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
406func 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
421func 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
430func 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	// To catch typos and the like, check that we know the variable.
439	if !cfg.CanGetenv(key) {
440		return fmt.Errorf("unknown go command variable %s", key)
441	}
442
443	// Some variables can only have one of a few valid values. If set to an
444	// invalid value, the next cmd/go invocation might fail immediately,
445	// even 'go env -w' itself.
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	// Make sure CC and CXX are absolute paths
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
479func 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	// Delete all but last copy of any duplicated variables,
497	// since the last copy is the one that takes effect.
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	// Add variables (go env -w). Update existing lines in file if present, add to end otherwise.
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	// Delete requested variables (go env -u).
520	for key := range del {
521		if p, ok := prev[key]; ok {
522			lines[p] = ""
523		}
524	}
525
526	// Sort runs of KEY=VALUE lines
527	// (that is, blocks of lines where blocks are separated
528	// by comments, blank lines, or invalid lines).
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		// Try creating directory.
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// lineToKey returns the KEY part of the line KEY=VALUE or else an empty string.
550func 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// sortKeyValues sorts a sequence of lines by key.
559// It differs from sort.Strings in that keys which are GOx where x is an ASCII
560// character smaller than = sort after GO=.
561// (There are no such keys currently. It used to matter for GO386 which was
562// removed in Go 1.16.)
563func sortKeyValues(lines []string) {
564	sort.Slice(lines, func(i, j int) bool {
565		return lineToKey(lines[i]) < lineToKey(lines[j])
566	})
567}
568