github.com/knieriem/gointernal@v0.2.0-pre2/cmd/go/envcmd/env.go (about)

     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  	"io"
    13  	"os"
    14  	"path/filepath"
    15  	"runtime"
    16  	"sort"
    17  	"strings"
    18  	"unicode/utf8"
    19  
    20  	"github.com/knieriem/gointernal/cmd/go/base"
    21  	"github.com/knieriem/gointernal/cmd/go/cfg"
    22  )
    23  
    24  var CmdEnv = &base.Command{
    25  	UsageLine: "<prog> env [-json] [-u] [-w] [var ...]",
    26  	Short:     "print environment information",
    27  	Long: `
    28  Env prints environment information.
    29  
    30  By default env prints information as a shell script
    31  (on Windows, a batch file). If one or more variable
    32  names is given as arguments, env prints the value of
    33  each named variable on its own line.
    34  
    35  The -json flag prints the environment in JSON format
    36  instead of as a shell script.
    37  
    38  The -u flag requires one or more arguments and unsets
    39  the default setting for the named environment variables,
    40  if one has been set with '<prog> env -w'.
    41  
    42  The -w flag requires one or more arguments of the
    43  form NAME=VALUE and changes the default settings
    44  of the named environment variables to the given values.
    45  
    46  For more about environment variables, see 'go help environment'.
    47  	`,
    48  }
    49  
    50  func init() {
    51  	CmdEnv.Run = runEnv // break init cycle
    52  }
    53  
    54  var (
    55  	envJson = CmdEnv.Flag.Bool("json", false, "")
    56  	envU    = CmdEnv.Flag.Bool("u", false, "")
    57  	envW    = CmdEnv.Flag.Bool("w", false, "")
    58  )
    59  
    60  func findEnv(env []cfg.EnvVar, name string) string {
    61  	for _, e := range env {
    62  		if e.Name == name {
    63  			return e.Value
    64  		}
    65  	}
    66  	return ""
    67  }
    68  
    69  // ExtraEnvVars returns environment variables that should not leak into child processes.
    70  func ExtraEnvVars() []cfg.EnvVar {
    71  	return nil
    72  }
    73  
    74  // ExtraEnvVarsCostly returns environment variables that should not leak into child processes
    75  // but are costly to evaluate.
    76  func ExtraEnvVarsCostly() []cfg.EnvVar {
    77  	return nil
    78  }
    79  
    80  // argKey returns the KEY part of the arg KEY=VAL, or else arg itself.
    81  func argKey(arg string) string {
    82  	i := strings.Index(arg, "=")
    83  	if i < 0 {
    84  		return arg
    85  	}
    86  	return arg[:i]
    87  }
    88  
    89  func runEnv(ctx context.Context, cmd *base.Command, args []string) {
    90  	if *envJson && *envU {
    91  		base.Fatalf("go: cannot use -json with -u")
    92  	}
    93  	if *envJson && *envW {
    94  		base.Fatalf("go: cannot use -json with -w")
    95  	}
    96  	if *envU && *envW {
    97  		base.Fatalf("go: cannot use -u with -w")
    98  	}
    99  
   100  	// Handle 'go env -w' and 'go env -u' before calling buildcfg.Check,
   101  	// so they can be used to recover from an invalid configuration.
   102  	if *envW {
   103  		runEnvW(args)
   104  		return
   105  	}
   106  
   107  	if *envU {
   108  		runEnvU(args)
   109  		return
   110  	}
   111  
   112  	env := cfg.CmdEnv
   113  	env = append(env, ExtraEnvVars()...)
   114  
   115  	// Do we need to call ExtraEnvVarsCostly, which is a bit expensive?
   116  	needCostly := false
   117  	if len(args) == 0 {
   118  		// We're listing all environment variables ("go env"),
   119  		// including the expensive ones.
   120  		needCostly = true
   121  	} else {
   122  		needCostly = false
   123  	checkCostly:
   124  		for _, arg := range args {
   125  			switch argKey(arg) {
   126  			case "CGO_CFLAGS",
   127  				"CGO_CPPFLAGS",
   128  				"CGO_CXXFLAGS",
   129  				"CGO_FFLAGS",
   130  				"CGO_LDFLAGS",
   131  				"PKG_CONFIG",
   132  				"GOGCCFLAGS":
   133  				needCostly = true
   134  				break checkCostly
   135  			}
   136  		}
   137  	}
   138  	if needCostly {
   139  		env = append(env, ExtraEnvVarsCostly()...)
   140  	}
   141  
   142  	if len(args) > 0 {
   143  		if *envJson {
   144  			var es []cfg.EnvVar
   145  			for _, name := range args {
   146  				e := cfg.EnvVar{Name: name, Value: findEnv(env, name)}
   147  				es = append(es, e)
   148  			}
   149  			printEnvAsJSON(es)
   150  		} else {
   151  			for _, name := range args {
   152  				fmt.Printf("%s\n", findEnv(env, name))
   153  			}
   154  		}
   155  		return
   156  	}
   157  
   158  	if *envJson {
   159  		printEnvAsJSON(env)
   160  		return
   161  	}
   162  
   163  	PrintEnv(os.Stdout, env)
   164  }
   165  
   166  func runEnvW(args []string) {
   167  	// Process and sanity-check command line.
   168  	if len(args) == 0 {
   169  		base.Fatalf("go: no KEY=VALUE arguments given")
   170  	}
   171  	osEnv := make(map[string]string)
   172  	for _, e := range cfg.OrigEnv {
   173  		if i := strings.Index(e, "="); i >= 0 {
   174  			osEnv[e[:i]] = e[i+1:]
   175  		}
   176  	}
   177  	add := make(map[string]string)
   178  	for _, arg := range args {
   179  		i := strings.Index(arg, "=")
   180  		if i < 0 {
   181  			base.Fatalf("go: arguments must be KEY=VALUE: invalid argument: %s", arg)
   182  		}
   183  		key, val := arg[:i], arg[i+1:]
   184  		if err := checkEnvWrite(key, val); err != nil {
   185  			base.Fatalf("go: %v", err)
   186  		}
   187  		if _, ok := add[key]; ok {
   188  			base.Fatalf("go: multiple values for key: %s", key)
   189  		}
   190  		add[key] = val
   191  		if osVal := osEnv[key]; osVal != "" && osVal != val {
   192  			fmt.Fprintf(os.Stderr, "warning: go env -w %s=... does not override conflicting OS environment variable\n", key)
   193  		}
   194  	}
   195  
   196  	gotmp, okGOTMP := add["GOTMPDIR"]
   197  	if okGOTMP {
   198  		if !filepath.IsAbs(gotmp) && gotmp != "" {
   199  			base.Fatalf("go: GOTMPDIR must be an absolute path")
   200  		}
   201  	}
   202  
   203  	updateEnvFile(add, nil)
   204  }
   205  
   206  func runEnvU(args []string) {
   207  	// Process and sanity-check command line.
   208  	if len(args) == 0 {
   209  		base.Fatalf("go: 'go env -u' requires an argument")
   210  	}
   211  	del := make(map[string]bool)
   212  	for _, arg := range args {
   213  		if err := checkEnvWrite(arg, ""); err != nil {
   214  			base.Fatalf("go: %v", err)
   215  		}
   216  		del[arg] = true
   217  	}
   218  
   219  	updateEnvFile(nil, del)
   220  }
   221  
   222  // PrintEnv prints the environment variables to w.
   223  func PrintEnv(w io.Writer, env []cfg.EnvVar) {
   224  	for _, e := range env {
   225  		if e.Name != "TERM" {
   226  			switch runtime.GOOS {
   227  			default:
   228  				fmt.Fprintf(w, "%s=\"%s\"\n", e.Name, e.Value)
   229  			case "plan9":
   230  				if strings.IndexByte(e.Value, '\x00') < 0 {
   231  					fmt.Fprintf(w, "%s='%s'\n", e.Name, strings.ReplaceAll(e.Value, "'", "''"))
   232  				} else {
   233  					v := strings.Split(e.Value, "\x00")
   234  					fmt.Fprintf(w, "%s=(", e.Name)
   235  					for x, s := range v {
   236  						if x > 0 {
   237  							fmt.Fprintf(w, " ")
   238  						}
   239  						fmt.Fprintf(w, "%s", s)
   240  					}
   241  					fmt.Fprintf(w, ")\n")
   242  				}
   243  			case "windows":
   244  				fmt.Fprintf(w, "set %s=%s\n", e.Name, e.Value)
   245  			}
   246  		}
   247  	}
   248  }
   249  
   250  func printEnvAsJSON(env []cfg.EnvVar) {
   251  	m := make(map[string]string)
   252  	for _, e := range env {
   253  		if e.Name == "TERM" {
   254  			continue
   255  		}
   256  		m[e.Name] = e.Value
   257  	}
   258  	enc := json.NewEncoder(os.Stdout)
   259  	enc.SetIndent("", "\t")
   260  	if err := enc.Encode(m); err != nil {
   261  		base.Fatalf("go: %s", err)
   262  	}
   263  }
   264  
   265  func getOrigEnv(key string) string {
   266  	for _, v := range cfg.OrigEnv {
   267  		if strings.HasPrefix(v, key+"=") {
   268  			return strings.TrimPrefix(v, key+"=")
   269  		}
   270  	}
   271  	return ""
   272  }
   273  
   274  func checkEnvWrite(key, val string) error {
   275  	// To catch typos and the like, check that we know the variable.
   276  	if !cfg.CanGetenv(key) {
   277  		return fmt.Errorf("unknown go command variable %s", key)
   278  	}
   279  
   280  	// Some variables can only have one of a few valid values. If set to an
   281  	// invalid value, the next cmd/go invocation might fail immediately,
   282  	// even 'go env -w' itself.
   283  	switch key {
   284  	}
   285  
   286  	if !utf8.ValidString(val) {
   287  		return fmt.Errorf("invalid UTF-8 in %s=... value", key)
   288  	}
   289  	if strings.Contains(val, "\x00") {
   290  		return fmt.Errorf("invalid NUL in %s=... value", key)
   291  	}
   292  	if strings.ContainsAny(val, "\v\r\n") {
   293  		return fmt.Errorf("invalid newline in %s=... value", key)
   294  	}
   295  	return nil
   296  }
   297  
   298  func updateEnvFile(add map[string]string, del map[string]bool) {
   299  	file, err := cfg.EnvFile()
   300  	if file == "" {
   301  		base.Fatalf("go: cannot find go env config: %v", err)
   302  	}
   303  	data, err := os.ReadFile(file)
   304  	if err != nil && (!os.IsNotExist(err) || len(add) == 0) {
   305  		base.Fatalf("go: reading go env config: %v", err)
   306  	}
   307  
   308  	lines := strings.SplitAfter(string(data), "\n")
   309  	if lines[len(lines)-1] == "" {
   310  		lines = lines[:len(lines)-1]
   311  	} else {
   312  		lines[len(lines)-1] += "\n"
   313  	}
   314  
   315  	// Delete all but last copy of any duplicated variables,
   316  	// since the last copy is the one that takes effect.
   317  	prev := make(map[string]int)
   318  	for l, line := range lines {
   319  		if key := lineToKey(line); key != "" {
   320  			if p, ok := prev[key]; ok {
   321  				lines[p] = ""
   322  			}
   323  			prev[key] = l
   324  		}
   325  	}
   326  
   327  	// Add variables (go env -w). Update existing lines in file if present, add to end otherwise.
   328  	for key, val := range add {
   329  		if p, ok := prev[key]; ok {
   330  			lines[p] = key + "=" + val + "\n"
   331  			delete(add, key)
   332  		}
   333  	}
   334  	for key, val := range add {
   335  		lines = append(lines, key+"="+val+"\n")
   336  	}
   337  
   338  	// Delete requested variables (go env -u).
   339  	for key := range del {
   340  		if p, ok := prev[key]; ok {
   341  			lines[p] = ""
   342  		}
   343  	}
   344  
   345  	// Sort runs of KEY=VALUE lines
   346  	// (that is, blocks of lines where blocks are separated
   347  	// by comments, blank lines, or invalid lines).
   348  	start := 0
   349  	for i := 0; i <= len(lines); i++ {
   350  		if i == len(lines) || lineToKey(lines[i]) == "" {
   351  			sortKeyValues(lines[start:i])
   352  			start = i + 1
   353  		}
   354  	}
   355  
   356  	data = []byte(strings.Join(lines, ""))
   357  	err = os.WriteFile(file, data, 0666)
   358  	if err != nil {
   359  		// Try creating directory.
   360  		os.MkdirAll(filepath.Dir(file), 0777)
   361  		err = os.WriteFile(file, data, 0666)
   362  		if err != nil {
   363  			base.Fatalf("go: writing go env config: %v", err)
   364  		}
   365  	}
   366  }
   367  
   368  // lineToKey returns the KEY part of the line KEY=VALUE or else an empty string.
   369  func lineToKey(line string) string {
   370  	i := strings.Index(line, "=")
   371  	if i < 0 || strings.Contains(line[:i], "#") {
   372  		return ""
   373  	}
   374  	return line[:i]
   375  }
   376  
   377  // sortKeyValues sorts a sequence of lines by key.
   378  // It differs from sort.Strings in that keys which are GOx where x is an ASCII
   379  // character smaller than = sort after GO=.
   380  // (There are no such keys currently. It used to matter for GO386 which was
   381  // removed in Go 1.16.)
   382  func sortKeyValues(lines []string) {
   383  	sort.Slice(lines, func(i, j int) bool {
   384  		return lineToKey(lines[i]) < lineToKey(lines[j])
   385  	})
   386  }