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

View as plain text