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