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

View as plain text