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

     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  	"bytes"
    10  	"context"
    11  	"encoding/json"
    12  	"fmt"
    13  	"go/build"
    14  	"internal/buildcfg"
    15  	"io"
    16  	"os"
    17  	"path/filepath"
    18  	"runtime"
    19  	"slices"
    20  	"sort"
    21  	"strings"
    22  	"unicode"
    23  	"unicode/utf8"
    24  
    25  	"cmd/go/internal/base"
    26  	"cmd/go/internal/cache"
    27  	"cmd/go/internal/cfg"
    28  	"cmd/go/internal/fsys"
    29  	"cmd/go/internal/load"
    30  	"cmd/go/internal/modload"
    31  	"cmd/go/internal/work"
    32  	"cmd/internal/quoted"
    33  	"cmd/internal/telemetry"
    34  )
    35  
    36  var CmdEnv = &base.Command{
    37  	UsageLine: "go env [-json] [-changed] [-u] [-w] [var ...]",
    38  	Short:     "print Go environment information",
    39  	Long: `
    40  Env prints Go environment information.
    41  
    42  By default env prints information as a shell script
    43  (on Windows, a batch file). If one or more variable
    44  names is given as arguments, env prints the value of
    45  each named variable on its own line.
    46  
    47  The -json flag prints the environment in JSON format
    48  instead of as a shell script.
    49  
    50  The -u flag requires one or more arguments and unsets
    51  the default setting for the named environment variables,
    52  if one has been set with 'go env -w'.
    53  
    54  The -w flag requires one or more arguments of the
    55  form NAME=VALUE and changes the default settings
    56  of the named environment variables to the given values.
    57  
    58  The -changed flag prints only those settings whose effective
    59  value differs from the default value that would be obtained in
    60  an empty environment with no prior uses of the -w flag.
    61  
    62  For more about environment variables, see 'go help environment'.
    63  	`,
    64  }
    65  
    66  func init() {
    67  	CmdEnv.Run = runEnv // break init cycle
    68  	base.AddChdirFlag(&CmdEnv.Flag)
    69  	base.AddBuildFlagsNX(&CmdEnv.Flag)
    70  }
    71  
    72  var (
    73  	envJson    = CmdEnv.Flag.Bool("json", false, "")
    74  	envU       = CmdEnv.Flag.Bool("u", false, "")
    75  	envW       = CmdEnv.Flag.Bool("w", false, "")
    76  	envChanged = CmdEnv.Flag.Bool("changed", false, "")
    77  )
    78  
    79  func MkEnv() []cfg.EnvVar {
    80  	envFile, envFileChanged, _ := cfg.EnvFile()
    81  	env := []cfg.EnvVar{
    82  		// NOTE: Keep this list (and in general, all lists in source code) sorted by name.
    83  		{Name: "GO111MODULE", Value: cfg.Getenv("GO111MODULE")},
    84  		{Name: "GOARCH", Value: cfg.Goarch, Changed: cfg.Goarch != runtime.GOARCH},
    85  		{Name: "GOAUTH", Value: cfg.GOAUTH, Changed: cfg.GOAUTHChanged},
    86  		{Name: "GOCACHE"},
    87  		{Name: "GOCACHEPROG", Value: cfg.GOCACHEPROG, Changed: cfg.GOCACHEPROGChanged},
    88  		{Name: "GODEBUG", Value: os.Getenv("GODEBUG")},
    89  		{Name: "GOENV", Value: envFile, Changed: envFileChanged},
    90  		{Name: "GOEXE", Value: cfg.ExeSuffix},
    91  
    92  		// List the raw value of GOEXPERIMENT, not the cleaned one.
    93  		// The set of default experiments may change from one release
    94  		// to the next, so a GOEXPERIMENT setting that is redundant
    95  		// with the current toolchain might actually be relevant with
    96  		// a different version (for example, when bisecting a regression).
    97  		{Name: "GOEXPERIMENT", Value: cfg.RawGOEXPERIMENT},
    98  
    99  		{Name: "GOFIPS140", Value: cfg.GOFIPS140, Changed: cfg.GOFIPS140Changed},
   100  		{Name: "GOFLAGS", Value: cfg.Getenv("GOFLAGS")},
   101  		{Name: "GOHOSTARCH", Value: runtime.GOARCH},
   102  		{Name: "GOHOSTOS", Value: runtime.GOOS},
   103  		{Name: "GOINSECURE", Value: cfg.GOINSECURE},
   104  		{Name: "GOMODCACHE", Value: cfg.GOMODCACHE, Changed: cfg.GOMODCACHEChanged},
   105  		{Name: "GONOPROXY", Value: cfg.GONOPROXY, Changed: cfg.GONOPROXYChanged},
   106  		{Name: "GONOSUMDB", Value: cfg.GONOSUMDB, Changed: cfg.GONOSUMDBChanged},
   107  		{Name: "GOOS", Value: cfg.Goos, Changed: cfg.Goos != runtime.GOOS},
   108  
   109  		// GOPACKAGESDRIVER isn't read or used by cmd/go, so it can only
   110  		// be sourced from environment variables.
   111  		// We include it for bug reports.
   112  		// go.dev/issue/75930
   113  		{Name: "GOPACKAGESDRIVER", Value: os.Getenv("GOPACKAGESDRIVER")},
   114  
   115  		{Name: "GOPATH", Value: cfg.BuildContext.GOPATH, Changed: cfg.GOPATHChanged},
   116  		{Name: "GOPRIVATE", Value: cfg.GOPRIVATE},
   117  		{Name: "GOPROXY", Value: cfg.GOPROXY, Changed: cfg.GOPROXYChanged},
   118  		{Name: "GOROOT", Value: cfg.GOROOT},
   119  		{Name: "GOSUMDB", Value: cfg.GOSUMDB, Changed: cfg.GOSUMDBChanged},
   120  		{Name: "GOTELEMETRY", Value: telemetry.Mode()},
   121  		{Name: "GOTELEMETRYDIR", Value: telemetry.Dir()},
   122  		{Name: "GOTMPDIR", Value: cfg.Getenv("GOTMPDIR")},
   123  		{Name: "GOTOOLCHAIN"},
   124  		{Name: "GOTOOLDIR", Value: build.ToolDir},
   125  		{Name: "GOVCS", Value: cfg.GOVCS},
   126  		{Name: "GOVERSION", Value: runtime.Version()},
   127  	}
   128  
   129  	for i := range env {
   130  		switch env[i].Name {
   131  		case "GO111MODULE":
   132  			if env[i].Value != "on" && env[i].Value != "" {
   133  				env[i].Changed = true
   134  			}
   135  		case "GOEXPERIMENT", "GOFLAGS", "GOINSECURE", "GOPACKAGESDRIVER", "GOPRIVATE", "GOTMPDIR", "GOVCS":
   136  			if env[i].Value != "" {
   137  				env[i].Changed = true
   138  			}
   139  		case "GOCACHE":
   140  			env[i].Value, env[i].Changed, _ = cache.DefaultDir()
   141  		case "GOTOOLCHAIN":
   142  			env[i].Value, env[i].Changed = cfg.EnvOrAndChanged("GOTOOLCHAIN", "")
   143  		case "GODEBUG":
   144  			env[i].Changed = env[i].Value != ""
   145  		}
   146  	}
   147  
   148  	if work.GccgoBin != "" {
   149  		env = append(env, cfg.EnvVar{Name: "GCCGO", Value: work.GccgoBin, Changed: true})
   150  	} else {
   151  		env = append(env, cfg.EnvVar{Name: "GCCGO", Value: work.GccgoName, Changed: work.GccgoChanged})
   152  	}
   153  
   154  	goarch, val, changed := cfg.GetArchEnv()
   155  	if goarch != "" {
   156  		env = append(env, cfg.EnvVar{Name: goarch, Value: val, Changed: changed})
   157  	}
   158  
   159  	cc := cfg.Getenv("CC")
   160  	ccChanged := true
   161  	if cc == "" {
   162  		ccChanged = false
   163  		cc = cfg.DefaultCC(cfg.Goos, cfg.Goarch)
   164  	}
   165  	cxx := cfg.Getenv("CXX")
   166  	cxxChanged := true
   167  	if cxx == "" {
   168  		cxxChanged = false
   169  		cxx = cfg.DefaultCXX(cfg.Goos, cfg.Goarch)
   170  	}
   171  	ar, arChanged := cfg.EnvOrAndChanged("AR", "ar")
   172  	env = append(env, cfg.EnvVar{Name: "AR", Value: ar, Changed: arChanged})
   173  	env = append(env, cfg.EnvVar{Name: "CC", Value: cc, Changed: ccChanged})
   174  	env = append(env, cfg.EnvVar{Name: "CXX", Value: cxx, Changed: cxxChanged})
   175  
   176  	if cfg.BuildContext.CgoEnabled {
   177  		env = append(env, cfg.EnvVar{Name: "CGO_ENABLED", Value: "1", Changed: cfg.CGOChanged})
   178  	} else {
   179  		env = append(env, cfg.EnvVar{Name: "CGO_ENABLED", Value: "0", Changed: cfg.CGOChanged})
   180  	}
   181  
   182  	return env
   183  }
   184  
   185  func findEnv(env []cfg.EnvVar, name string) string {
   186  	for _, e := range env {
   187  		if e.Name == name {
   188  			return e.Value
   189  		}
   190  	}
   191  	if cfg.CanGetenv(name) {
   192  		return cfg.Getenv(name)
   193  	}
   194  	return ""
   195  }
   196  
   197  // ExtraEnvVars returns environment variables that should not leak into child processes.
   198  func ExtraEnvVars(ld *modload.Loader) []cfg.EnvVar {
   199  	gomod := ""
   200  	modload.Init(ld)
   201  	if ld.HasModRoot() {
   202  		gomod = ld.ModFilePath()
   203  	} else if ld.Enabled() {
   204  		gomod = os.DevNull
   205  	}
   206  	ld.InitWorkfile()
   207  	gowork := modload.WorkFilePath(ld)
   208  	// As a special case, if a user set off explicitly, report that in GOWORK.
   209  	if cfg.Getenv("GOWORK") == "off" {
   210  		gowork = "off"
   211  	}
   212  	gobin := cfg.GOBIN
   213  	if gobin == "" && cfg.ModulesEnabled {
   214  		gobin = modload.BinDir(ld)
   215  	} else if gobin == "" {
   216  		// Best effort guess of where the binary will be installed.
   217  		// go.dev/issue/23439
   218  		gopaths := filepath.SplitList(cfg.BuildContext.GOPATH)
   219  		wd, err := os.Getwd()
   220  		if err == nil && len(gopaths) > 0 {
   221  			gopath := gopaths[0]
   222  			for _, p := range gopaths {
   223  				if strings.HasPrefix(wd, p) {
   224  					gopath = p
   225  					break
   226  				}
   227  			}
   228  			gobin = filepath.Join(gopath, "bin")
   229  		}
   230  	}
   231  
   232  	return []cfg.EnvVar{
   233  		{Name: "GOMOD", Value: gomod},
   234  		{Name: "GOWORK", Value: gowork},
   235  		{Name: "GOBIN", Value: gobin, Changed: cfg.GOBINChanged},
   236  	}
   237  }
   238  
   239  // ExtraEnvVarsCostly returns environment variables that should not leak into child processes
   240  // but are costly to evaluate.
   241  func ExtraEnvVarsCostly(ld *modload.Loader) []cfg.EnvVar {
   242  	b := work.NewBuilder("", ld.VendorDirOrEmpty)
   243  	defer func() {
   244  		if err := b.Close(); err != nil {
   245  			base.Fatal(err)
   246  		}
   247  	}()
   248  
   249  	cppflags, cflags, cxxflags, fflags, ldflags, err := b.CFlags(&load.Package{})
   250  	if err != nil {
   251  		// Should not happen - b.CFlags was given an empty package.
   252  		fmt.Fprintf(os.Stderr, "go: invalid cflags: %v\n", err)
   253  		return nil
   254  	}
   255  	cmd := b.GccCmd(".", "")
   256  
   257  	join := func(s []string) string {
   258  		q, err := quoted.Join(s)
   259  		if err != nil {
   260  			return strings.Join(s, " ")
   261  		}
   262  		return q
   263  	}
   264  
   265  	ret := []cfg.EnvVar{
   266  		// Note: Update the switch in runEnv below when adding to this list.
   267  		{Name: "CGO_CFLAGS", Value: join(cflags)},
   268  		{Name: "CGO_CPPFLAGS", Value: join(cppflags)},
   269  		{Name: "CGO_CXXFLAGS", Value: join(cxxflags)},
   270  		{Name: "CGO_FFLAGS", Value: join(fflags)},
   271  		{Name: "CGO_LDFLAGS", Value: join(ldflags)},
   272  		{Name: "PKG_CONFIG", Value: b.PkgconfigCmd()},
   273  		{Name: "GOGCCFLAGS", Value: join(cmd[3:])},
   274  	}
   275  
   276  	for i := range ret {
   277  		ev := &ret[i]
   278  		switch ev.Name {
   279  		case "GOGCCFLAGS": // GOGCCFLAGS cannot be modified
   280  		case "CGO_CPPFLAGS":
   281  			ev.Changed = ev.Value != ""
   282  		case "PKG_CONFIG":
   283  			ev.Changed = ev.Value != cfg.DefaultPkgConfig
   284  		case "CGO_CXXFLAGS", "CGO_CFLAGS", "CGO_FFLAGS", "CGO_LDFLAGS":
   285  			ev.Changed = ev.Value != work.DefaultCFlags
   286  		}
   287  	}
   288  
   289  	return ret
   290  }
   291  
   292  // argKey returns the KEY part of the arg KEY=VAL, or else arg itself.
   293  func argKey(arg string) string {
   294  	i := strings.Index(arg, "=")
   295  	if i < 0 {
   296  		return arg
   297  	}
   298  	return arg[:i]
   299  }
   300  
   301  func runEnv(ctx context.Context, cmd *base.Command, args []string) {
   302  	moduleLoader := modload.NewLoader()
   303  	if *envJson && *envU {
   304  		base.Fatalf("go: cannot use -json with -u")
   305  	}
   306  	if *envJson && *envW {
   307  		base.Fatalf("go: cannot use -json with -w")
   308  	}
   309  	if *envU && *envW {
   310  		base.Fatalf("go: cannot use -u with -w")
   311  	}
   312  
   313  	// Handle 'go env -w' and 'go env -u' before calling buildcfg.Check,
   314  	// so they can be used to recover from an invalid configuration.
   315  	if *envW {
   316  		runEnvW(args)
   317  		return
   318  	}
   319  
   320  	if *envU {
   321  		runEnvU(args)
   322  		return
   323  	}
   324  
   325  	buildcfg.Check()
   326  	if cfg.ExperimentErr != nil {
   327  		base.Fatal(cfg.ExperimentErr)
   328  	}
   329  
   330  	for _, arg := range args {
   331  		if strings.Contains(arg, "=") {
   332  			base.Fatalf("go: invalid variable name %q (use -w to set variable)", arg)
   333  		}
   334  	}
   335  
   336  	env := cfg.CmdEnv
   337  	env = append(env, ExtraEnvVars(moduleLoader)...)
   338  
   339  	if err := fsys.Init(); err != nil {
   340  		base.Fatal(err)
   341  	}
   342  
   343  	// Do we need to call ExtraEnvVarsCostly, which is a bit expensive?
   344  	needCostly := false
   345  	if len(args) == 0 {
   346  		// We're listing all environment variables ("go env"),
   347  		// including the expensive ones.
   348  		needCostly = true
   349  	} else {
   350  		needCostly = false
   351  	checkCostly:
   352  		for _, arg := range args {
   353  			switch argKey(arg) {
   354  			case "CGO_CFLAGS",
   355  				"CGO_CPPFLAGS",
   356  				"CGO_CXXFLAGS",
   357  				"CGO_FFLAGS",
   358  				"CGO_LDFLAGS",
   359  				"PKG_CONFIG",
   360  				"GOGCCFLAGS":
   361  				needCostly = true
   362  				break checkCostly
   363  			}
   364  		}
   365  	}
   366  	if needCostly {
   367  		work.BuildInit(moduleLoader)
   368  		env = append(env, ExtraEnvVarsCostly(moduleLoader)...)
   369  	}
   370  
   371  	if len(args) > 0 {
   372  		// Show only the named vars.
   373  		if !*envChanged {
   374  			if *envJson {
   375  				es := make([]cfg.EnvVar, 0, len(args))
   376  				for _, name := range args {
   377  					e := cfg.EnvVar{Name: name, Value: findEnv(env, name)}
   378  					es = append(es, e)
   379  				}
   380  				env = es
   381  			} else {
   382  				// Print just the values, without names.
   383  				for _, name := range args {
   384  					fmt.Printf("%s\n", findEnv(env, name))
   385  				}
   386  				return
   387  			}
   388  		} else {
   389  			// Show only the changed, named vars.
   390  			var es []cfg.EnvVar
   391  			for _, name := range args {
   392  				for _, e := range env {
   393  					if e.Name == name {
   394  						es = append(es, e)
   395  						break
   396  					}
   397  				}
   398  			}
   399  			env = es
   400  		}
   401  	}
   402  
   403  	// print
   404  	if *envJson {
   405  		printEnvAsJSON(env, *envChanged)
   406  	} else {
   407  		PrintEnv(os.Stdout, env, *envChanged)
   408  	}
   409  }
   410  
   411  func runEnvW(args []string) {
   412  	// Process and sanity-check command line.
   413  	if len(args) == 0 {
   414  		base.Fatalf("go: no KEY=VALUE arguments given")
   415  	}
   416  	osEnv := make(map[string]string)
   417  	for _, e := range cfg.OrigEnv {
   418  		if i := strings.Index(e, "="); i >= 0 {
   419  			osEnv[e[:i]] = e[i+1:]
   420  		}
   421  	}
   422  	add := make(map[string]string)
   423  	for _, arg := range args {
   424  		key, val, found := strings.Cut(arg, "=")
   425  		if !found {
   426  			base.Fatalf("go: arguments must be KEY=VALUE: invalid argument: %s", arg)
   427  		}
   428  		if err := checkEnvWrite(key, val); err != nil {
   429  			base.Fatal(err)
   430  		}
   431  		if _, ok := add[key]; ok {
   432  			base.Fatalf("go: multiple values for key: %s", key)
   433  		}
   434  		add[key] = val
   435  		if osVal := osEnv[key]; osVal != "" && osVal != val {
   436  			fmt.Fprintf(os.Stderr, "warning: go env -w %s=... does not override conflicting OS environment variable\n", key)
   437  		}
   438  	}
   439  
   440  	if err := checkBuildConfig(add, nil); err != nil {
   441  		base.Fatal(err)
   442  	}
   443  
   444  	gotmp, okGOTMP := add["GOTMPDIR"]
   445  	if okGOTMP {
   446  		if !filepath.IsAbs(gotmp) && gotmp != "" {
   447  			base.Fatalf("go: GOTMPDIR must be an absolute path")
   448  		}
   449  	}
   450  
   451  	updateEnvFile(add, nil)
   452  }
   453  
   454  func runEnvU(args []string) {
   455  	// Process and sanity-check command line.
   456  	if len(args) == 0 {
   457  		base.Fatalf("go: 'go env -u' requires an argument")
   458  	}
   459  	del := make(map[string]bool)
   460  	for _, arg := range args {
   461  		if err := checkEnvWrite(arg, ""); err != nil {
   462  			base.Fatal(err)
   463  		}
   464  		del[arg] = true
   465  	}
   466  
   467  	if err := checkBuildConfig(nil, del); err != nil {
   468  		base.Fatal(err)
   469  	}
   470  
   471  	updateEnvFile(nil, del)
   472  }
   473  
   474  // checkBuildConfig checks whether the build configuration is valid
   475  // after the specified configuration environment changes are applied.
   476  func checkBuildConfig(add map[string]string, del map[string]bool) error {
   477  	// get returns the value for key after applying add and del and
   478  	// reports whether it changed. cur should be the current value
   479  	// (i.e., before applying changes) and def should be the default
   480  	// value (i.e., when no environment variables are provided at all).
   481  	get := func(key, cur, def string) (string, bool) {
   482  		if val, ok := add[key]; ok {
   483  			return val, true
   484  		}
   485  		if del[key] {
   486  			val := getOrigEnv(key)
   487  			if val == "" {
   488  				val = def
   489  			}
   490  			return val, true
   491  		}
   492  		return cur, false
   493  	}
   494  
   495  	goos, okGOOS := get("GOOS", cfg.Goos, build.Default.GOOS)
   496  	goarch, okGOARCH := get("GOARCH", cfg.Goarch, build.Default.GOARCH)
   497  	if okGOOS || okGOARCH {
   498  		if err := work.CheckGOOSARCHPair(goos, goarch); err != nil {
   499  			return err
   500  		}
   501  	}
   502  
   503  	goexperiment, okGOEXPERIMENT := get("GOEXPERIMENT", cfg.RawGOEXPERIMENT, buildcfg.DefaultGOEXPERIMENT)
   504  	if okGOEXPERIMENT {
   505  		if _, err := buildcfg.ParseGOEXPERIMENT(goos, goarch, goexperiment); err != nil {
   506  			return err
   507  		}
   508  	}
   509  
   510  	return nil
   511  }
   512  
   513  // PrintEnv prints the environment variables to w.
   514  func PrintEnv(w io.Writer, env []cfg.EnvVar, onlyChanged bool) {
   515  	env = slices.Clone(env)
   516  	slices.SortFunc(env, func(x, y cfg.EnvVar) int { return strings.Compare(x.Name, y.Name) })
   517  
   518  	for _, e := range env {
   519  		if e.Name != "TERM" {
   520  			if runtime.GOOS != "plan9" && bytes.Contains([]byte(e.Value), []byte{0}) {
   521  				base.Fatalf("go: internal error: encountered null byte in environment variable %s on non-plan9 platform", e.Name)
   522  			}
   523  			if onlyChanged && !e.Changed {
   524  				continue
   525  			}
   526  			switch runtime.GOOS {
   527  			default:
   528  				fmt.Fprintf(w, "%s=%s\n", e.Name, shellQuote(e.Value))
   529  			case "plan9":
   530  				if strings.IndexByte(e.Value, '\x00') < 0 {
   531  					fmt.Fprintf(w, "%s='%s'\n", e.Name, strings.ReplaceAll(e.Value, "'", "''"))
   532  				} else {
   533  					v := strings.Split(e.Value, "\x00")
   534  					fmt.Fprintf(w, "%s=(", e.Name)
   535  					for x, s := range v {
   536  						if x > 0 {
   537  							fmt.Fprintf(w, " ")
   538  						}
   539  						fmt.Fprintf(w, "'%s'", strings.ReplaceAll(s, "'", "''"))
   540  					}
   541  					fmt.Fprintf(w, ")\n")
   542  				}
   543  			case "windows":
   544  				if hasNonGraphic(e.Value) {
   545  					base.Errorf("go: stripping unprintable or unescapable characters from %%%q%%", e.Name)
   546  				}
   547  				fmt.Fprintf(w, "set %s=%s\n", e.Name, batchEscape(e.Value))
   548  			}
   549  		}
   550  	}
   551  }
   552  
   553  // isWindowsUnquotableRune reports whether r can't be quoted in a
   554  // Windows "set" command.
   555  // These runes will be replaced by the Unicode replacement character.
   556  func isWindowsUnquotableRune(r rune) bool {
   557  	if r == '\r' || r == '\n' {
   558  		return true
   559  	}
   560  	return !unicode.IsGraphic(r) && !unicode.IsSpace(r)
   561  }
   562  
   563  func hasNonGraphic(s string) bool {
   564  	return strings.ContainsFunc(s, isWindowsUnquotableRune)
   565  }
   566  
   567  func shellQuote(s string) string {
   568  	var sb strings.Builder
   569  	sb.WriteByte('\'')
   570  	for _, r := range s {
   571  		if r == '\'' {
   572  			// Close the single quoted string, add an escaped single quote,
   573  			// and start another single quoted string.
   574  			sb.WriteString(`'\''`)
   575  		} else {
   576  			sb.WriteRune(r)
   577  		}
   578  	}
   579  	sb.WriteByte('\'')
   580  	return sb.String()
   581  }
   582  
   583  func batchEscape(s string) string {
   584  	var sb strings.Builder
   585  	for _, r := range s {
   586  		if isWindowsUnquotableRune(r) {
   587  			sb.WriteRune(unicode.ReplacementChar)
   588  			continue
   589  		}
   590  		switch r {
   591  		case '%':
   592  			sb.WriteString("%%")
   593  		case '<', '>', '|', '&', '^':
   594  			// These are special characters that need to be escaped with ^. See
   595  			// https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/set_1.
   596  			sb.WriteByte('^')
   597  			sb.WriteRune(r)
   598  		default:
   599  			sb.WriteRune(r)
   600  		}
   601  	}
   602  	return sb.String()
   603  }
   604  
   605  func printEnvAsJSON(env []cfg.EnvVar, onlyChanged bool) {
   606  	m := make(map[string]string)
   607  	for _, e := range env {
   608  		if e.Name == "TERM" {
   609  			continue
   610  		}
   611  		if onlyChanged && !e.Changed {
   612  			continue
   613  		}
   614  		m[e.Name] = e.Value
   615  	}
   616  	enc := json.NewEncoder(os.Stdout)
   617  	enc.SetIndent("", "\t")
   618  	if err := enc.Encode(m); err != nil {
   619  		base.Fatalf("go: %s", err)
   620  	}
   621  }
   622  
   623  func getOrigEnv(key string) string {
   624  	for _, v := range cfg.OrigEnv {
   625  		if v, found := strings.CutPrefix(v, key+"="); found {
   626  			return v
   627  		}
   628  	}
   629  	return ""
   630  }
   631  
   632  func checkEnvWrite(key, val string) error {
   633  	switch key {
   634  	case "GOEXE",
   635  		"GOGCCFLAGS",
   636  		"GOHOSTARCH",
   637  		"GOHOSTOS",
   638  		"GOMOD",
   639  		"GOROOT",
   640  		"GOTELEMETRY",
   641  		"GOTELEMETRYDIR",
   642  		"GOTOOLDIR",
   643  		"GOVERSION",
   644  		"GOWORK":
   645  		return fmt.Errorf("%s cannot be modified", key)
   646  	case "GOENV", "GODEBUG":
   647  		return fmt.Errorf("%s can only be set using the OS environment", key)
   648  	}
   649  
   650  	// To catch typos and the like, check that we know the variable.
   651  	// If it's already in the env file, we assume it's known.
   652  	if !cfg.CanGetenv(key) {
   653  		return fmt.Errorf("unknown go command variable %s", key)
   654  	}
   655  
   656  	// Some variables can only have one of a few valid values. If set to an
   657  	// invalid value, the next cmd/go invocation might fail immediately,
   658  	// even 'go env -w' itself.
   659  	switch key {
   660  	case "GO111MODULE":
   661  		switch val {
   662  		case "", "auto", "on", "off":
   663  		default:
   664  			return fmt.Errorf("invalid %s value %q", key, val)
   665  		}
   666  	case "GOPATH":
   667  		if strings.HasPrefix(val, "~") {
   668  			return fmt.Errorf("GOPATH entry cannot start with shell metacharacter '~': %q", val)
   669  		}
   670  		if !filepath.IsAbs(val) && val != "" {
   671  			return fmt.Errorf("GOPATH entry is relative; must be absolute path: %q", val)
   672  		}
   673  	case "GOMODCACHE":
   674  		if !filepath.IsAbs(val) && val != "" {
   675  			return fmt.Errorf("GOMODCACHE entry is relative; must be absolute path: %q", val)
   676  		}
   677  	case "CC", "CXX":
   678  		if val == "" {
   679  			break
   680  		}
   681  		args, err := quoted.Split(val)
   682  		if err != nil {
   683  			return fmt.Errorf("invalid %s: %v", key, err)
   684  		}
   685  		if len(args) == 0 {
   686  			return fmt.Errorf("%s entry cannot contain only space", key)
   687  		}
   688  		if !filepath.IsAbs(args[0]) && args[0] != filepath.Base(args[0]) {
   689  			return fmt.Errorf("%s entry is relative; must be absolute path: %q", key, args[0])
   690  		}
   691  	}
   692  
   693  	if !utf8.ValidString(val) {
   694  		return fmt.Errorf("invalid UTF-8 in %s=... value", key)
   695  	}
   696  	if strings.Contains(val, "\x00") {
   697  		return fmt.Errorf("invalid NUL in %s=... value", key)
   698  	}
   699  	if strings.ContainsAny(val, "\v\r\n") {
   700  		return fmt.Errorf("invalid newline in %s=... value", key)
   701  	}
   702  	return nil
   703  }
   704  
   705  func readEnvFileLines(mustExist bool) []string {
   706  	file, _, err := cfg.EnvFile()
   707  	if file == "" {
   708  		if mustExist {
   709  			base.Fatalf("go: cannot find go env config: %v", err)
   710  		}
   711  		return nil
   712  	}
   713  	data, err := os.ReadFile(file)
   714  	if err != nil && (!os.IsNotExist(err) || mustExist) {
   715  		base.Fatalf("go: reading go env config: %v", err)
   716  	}
   717  	lines := strings.SplitAfter(string(data), "\n")
   718  	if lines[len(lines)-1] == "" {
   719  		lines = lines[:len(lines)-1]
   720  	} else {
   721  		lines[len(lines)-1] += "\n"
   722  	}
   723  	return lines
   724  }
   725  
   726  func updateEnvFile(add map[string]string, del map[string]bool) {
   727  	lines := readEnvFileLines(len(add) == 0)
   728  
   729  	// Delete all but last copy of any duplicated variables,
   730  	// since the last copy is the one that takes effect.
   731  	prev := make(map[string]int)
   732  	for l, line := range lines {
   733  		if key := lineToKey(line); key != "" {
   734  			if p, ok := prev[key]; ok {
   735  				lines[p] = ""
   736  			}
   737  			prev[key] = l
   738  		}
   739  	}
   740  
   741  	// Add variables (go env -w). Update existing lines in file if present, add to end otherwise.
   742  	for key, val := range add {
   743  		if p, ok := prev[key]; ok {
   744  			lines[p] = key + "=" + val + "\n"
   745  			delete(add, key)
   746  		}
   747  	}
   748  	for key, val := range add {
   749  		lines = append(lines, key+"="+val+"\n")
   750  	}
   751  
   752  	// Delete requested variables (go env -u).
   753  	for key := range del {
   754  		if p, ok := prev[key]; ok {
   755  			lines[p] = ""
   756  		}
   757  	}
   758  
   759  	// Sort runs of KEY=VALUE lines
   760  	// (that is, blocks of lines where blocks are separated
   761  	// by comments, blank lines, or invalid lines).
   762  	start := 0
   763  	for i := 0; i <= len(lines); i++ {
   764  		if i == len(lines) || lineToKey(lines[i]) == "" {
   765  			sortKeyValues(lines[start:i])
   766  			start = i + 1
   767  		}
   768  	}
   769  
   770  	file, _, err := cfg.EnvFile()
   771  	if file == "" {
   772  		base.Fatalf("go: cannot find go env config: %v", err)
   773  	}
   774  	data := []byte(strings.Join(lines, ""))
   775  	err = os.WriteFile(file, data, 0666)
   776  	if err != nil {
   777  		// Try creating directory.
   778  		os.MkdirAll(filepath.Dir(file), 0777)
   779  		err = os.WriteFile(file, data, 0666)
   780  		if err != nil {
   781  			base.Fatalf("go: writing go env config: %v", err)
   782  		}
   783  	}
   784  }
   785  
   786  // lineToKey returns the KEY part of the line KEY=VALUE or else an empty string.
   787  func lineToKey(line string) string {
   788  	i := strings.Index(line, "=")
   789  	if i < 0 || strings.Contains(line[:i], "#") {
   790  		return ""
   791  	}
   792  	return line[:i]
   793  }
   794  
   795  // sortKeyValues sorts a sequence of lines by key.
   796  // It differs from sort.Strings in that GO386= sorts after GO=.
   797  func sortKeyValues(lines []string) {
   798  	sort.Slice(lines, func(i, j int) bool {
   799  		return lineToKey(lines[i]) < lineToKey(lines[j])
   800  	})
   801  }
   802  

View as plain text