Black Lives Matter. Support the Equal Justice Initiative.

Source file src/cmd/go/internal/envcmd/env.go

Documentation: cmd/go/internal/envcmd

     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.
     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 // break init cycle
    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  // ExtraEnvVars returns environment variables that should not leak into child processes.
   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  // ExtraEnvVarsCostly returns environment variables that should not leak into child processes
   159  // but are costly to evaluate.
   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  		// 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.
   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  	// 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  
   275  func 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  
   319  func 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.
   341  func 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.
   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  	// 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  
   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  	// 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.
   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  // 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.)
   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