...

Source file src/pkg/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		"encoding/json"
    10		"fmt"
    11		"io/ioutil"
    12		"os"
    13		"path/filepath"
    14		"runtime"
    15		"sort"
    16		"strings"
    17		"unicode/utf8"
    18	
    19		"cmd/go/internal/base"
    20		"cmd/go/internal/cache"
    21		"cmd/go/internal/cfg"
    22		"cmd/go/internal/load"
    23		"cmd/go/internal/modload"
    24		"cmd/go/internal/work"
    25	)
    26	
    27	var CmdEnv = &base.Command{
    28		UsageLine: "go env [-json] [-u] [-w] [var ...]",
    29		Short:     "print Go environment information",
    30		Long: `
    31	Env prints Go environment information.
    32	
    33	By default env prints information as a shell script
    34	(on Windows, a batch file). If one or more variable
    35	names is given as arguments, env prints the value of
    36	each named variable on its own line.
    37	
    38	The -json flag prints the environment in JSON format
    39	instead of as a shell script.
    40	
    41	The -u flag requires one or more arguments and unsets
    42	the default setting for the named environment variables,
    43	if one has been set with 'go env -w'.
    44	
    45	The -w flag requires one or more arguments of the
    46	form NAME=VALUE and changes the default settings
    47	of the named environment variables to the given values.
    48	
    49	For more about environment variables, see 'go help environment'.
    50		`,
    51	}
    52	
    53	func init() {
    54		CmdEnv.Run = runEnv // break init cycle
    55	}
    56	
    57	var (
    58		envJson = CmdEnv.Flag.Bool("json", false, "")
    59		envU    = CmdEnv.Flag.Bool("u", false, "")
    60		envW    = CmdEnv.Flag.Bool("w", false, "")
    61	)
    62	
    63	func MkEnv() []cfg.EnvVar {
    64		var b work.Builder
    65		b.Init()
    66	
    67		envFile, _ := cfg.EnvFile()
    68		env := []cfg.EnvVar{
    69			{Name: "GO111MODULE", Value: cfg.Getenv("GO111MODULE")},
    70			{Name: "GOARCH", Value: cfg.Goarch},
    71			{Name: "GOBIN", Value: cfg.GOBIN},
    72			{Name: "GOCACHE", Value: cache.DefaultDir()},
    73			{Name: "GOENV", Value: envFile},
    74			{Name: "GOEXE", Value: cfg.ExeSuffix},
    75			{Name: "GOFLAGS", Value: cfg.Getenv("GOFLAGS")},
    76			{Name: "GOHOSTARCH", Value: runtime.GOARCH},
    77			{Name: "GOHOSTOS", Value: runtime.GOOS},
    78			{Name: "GONOPROXY", Value: cfg.GONOPROXY},
    79			{Name: "GONOSUMDB", Value: cfg.GONOSUMDB},
    80			{Name: "GOOS", Value: cfg.Goos},
    81			{Name: "GOPATH", Value: cfg.BuildContext.GOPATH},
    82			{Name: "GOPRIVATE", Value: cfg.GOPRIVATE},
    83			{Name: "GOPROXY", Value: cfg.GOPROXY},
    84			{Name: "GOROOT", Value: cfg.GOROOT},
    85			{Name: "GOSUMDB", Value: cfg.GOSUMDB},
    86			{Name: "GOTMPDIR", Value: cfg.Getenv("GOTMPDIR")},
    87			{Name: "GOTOOLDIR", Value: base.ToolDir},
    88		}
    89	
    90		if work.GccgoBin != "" {
    91			env = append(env, cfg.EnvVar{Name: "GCCGO", Value: work.GccgoBin})
    92		} else {
    93			env = append(env, cfg.EnvVar{Name: "GCCGO", Value: work.GccgoName})
    94		}
    95	
    96		key, val := cfg.GetArchEnv()
    97		if key != "" {
    98			env = append(env, cfg.EnvVar{Name: key, Value: val})
    99		}
   100	
   101		cc := cfg.DefaultCC(cfg.Goos, cfg.Goarch)
   102		if env := strings.Fields(cfg.Getenv("CC")); len(env) > 0 {
   103			cc = env[0]
   104		}
   105		cxx := cfg.DefaultCXX(cfg.Goos, cfg.Goarch)
   106		if env := strings.Fields(cfg.Getenv("CXX")); len(env) > 0 {
   107			cxx = env[0]
   108		}
   109		env = append(env, cfg.EnvVar{Name: "AR", Value: envOr("AR", "ar")})
   110		env = append(env, cfg.EnvVar{Name: "CC", Value: cc})
   111		env = append(env, cfg.EnvVar{Name: "CXX", Value: cxx})
   112	
   113		if cfg.BuildContext.CgoEnabled {
   114			env = append(env, cfg.EnvVar{Name: "CGO_ENABLED", Value: "1"})
   115		} else {
   116			env = append(env, cfg.EnvVar{Name: "CGO_ENABLED", Value: "0"})
   117		}
   118	
   119		return env
   120	}
   121	
   122	func envOr(name, def string) string {
   123		val := cfg.Getenv(name)
   124		if val != "" {
   125			return val
   126		}
   127		return def
   128	}
   129	
   130	func findEnv(env []cfg.EnvVar, name string) string {
   131		for _, e := range env {
   132			if e.Name == name {
   133				return e.Value
   134			}
   135		}
   136		return ""
   137	}
   138	
   139	// ExtraEnvVars returns environment variables that should not leak into child processes.
   140	func ExtraEnvVars() []cfg.EnvVar {
   141		gomod := ""
   142		if modload.HasModRoot() {
   143			gomod = filepath.Join(modload.ModRoot(), "go.mod")
   144		} else if modload.Enabled() {
   145			gomod = os.DevNull
   146		}
   147		return []cfg.EnvVar{
   148			{Name: "GOMOD", Value: gomod},
   149		}
   150	}
   151	
   152	// ExtraEnvVarsCostly returns environment variables that should not leak into child processes
   153	// but are costly to evaluate.
   154	func ExtraEnvVarsCostly() []cfg.EnvVar {
   155		var b work.Builder
   156		b.Init()
   157		cppflags, cflags, cxxflags, fflags, ldflags, err := b.CFlags(&load.Package{})
   158		if err != nil {
   159			// Should not happen - b.CFlags was given an empty package.
   160			fmt.Fprintf(os.Stderr, "go: invalid cflags: %v\n", err)
   161			return nil
   162		}
   163		cmd := b.GccCmd(".", "")
   164	
   165		return []cfg.EnvVar{
   166			// Note: Update the switch in runEnv below when adding to this list.
   167			{Name: "CGO_CFLAGS", Value: strings.Join(cflags, " ")},
   168			{Name: "CGO_CPPFLAGS", Value: strings.Join(cppflags, " ")},
   169			{Name: "CGO_CXXFLAGS", Value: strings.Join(cxxflags, " ")},
   170			{Name: "CGO_FFLAGS", Value: strings.Join(fflags, " ")},
   171			{Name: "CGO_LDFLAGS", Value: strings.Join(ldflags, " ")},
   172			{Name: "PKG_CONFIG", Value: b.PkgconfigCmd()},
   173			{Name: "GOGCCFLAGS", Value: strings.Join(cmd[3:], " ")},
   174		}
   175	}
   176	
   177	// argKey returns the KEY part of the arg KEY=VAL, or else arg itself.
   178	func argKey(arg string) string {
   179		i := strings.Index(arg, "=")
   180		if i < 0 {
   181			return arg
   182		}
   183		return arg[:i]
   184	}
   185	
   186	func runEnv(cmd *base.Command, args []string) {
   187		if *envJson && *envU {
   188			base.Fatalf("go env: cannot use -json with -u")
   189		}
   190		if *envJson && *envW {
   191			base.Fatalf("go env: cannot use -json with -w")
   192		}
   193		if *envU && *envW {
   194			base.Fatalf("go env: cannot use -u with -w")
   195		}
   196		env := cfg.CmdEnv
   197		env = append(env, ExtraEnvVars()...)
   198	
   199		// Do we need to call ExtraEnvVarsCostly, which is a bit expensive?
   200		// Only if we're listing all environment variables ("go env")
   201		// or the variables being requested are in the extra list.
   202		needCostly := true
   203		if len(args) > 0 {
   204			needCostly = false
   205			for _, arg := range args {
   206				switch argKey(arg) {
   207				case "CGO_CFLAGS",
   208					"CGO_CPPFLAGS",
   209					"CGO_CXXFLAGS",
   210					"CGO_FFLAGS",
   211					"CGO_LDFLAGS",
   212					"PKG_CONFIG",
   213					"GOGCCFLAGS":
   214					needCostly = true
   215				}
   216			}
   217		}
   218		if needCostly {
   219			env = append(env, ExtraEnvVarsCostly()...)
   220		}
   221	
   222		if *envW {
   223			// Process and sanity-check command line.
   224			if len(args) == 0 {
   225				base.Fatalf("go env -w: no KEY=VALUE arguments given")
   226			}
   227			osEnv := make(map[string]string)
   228			for _, e := range cfg.OrigEnv {
   229				if i := strings.Index(e, "="); i >= 0 {
   230					osEnv[e[:i]] = e[i+1:]
   231				}
   232			}
   233			add := make(map[string]string)
   234			for _, arg := range args {
   235				i := strings.Index(arg, "=")
   236				if i < 0 {
   237					base.Fatalf("go env -w: arguments must be KEY=VALUE: invalid argument: %s", arg)
   238				}
   239				key, val := arg[:i], arg[i+1:]
   240				if err := checkEnvWrite(key, val, env); err != nil {
   241					base.Fatalf("go env -w: %v", err)
   242				}
   243				if _, ok := add[key]; ok {
   244					base.Fatalf("go env -w: multiple values for key: %s", key)
   245				}
   246				add[key] = val
   247				if osVal := osEnv[key]; osVal != "" && osVal != val {
   248					fmt.Fprintf(os.Stderr, "warning: go env -w %s=... does not override conflicting OS environment variable\n", key)
   249				}
   250			}
   251			updateEnvFile(add, nil)
   252			return
   253		}
   254	
   255		if *envU {
   256			// Process and sanity-check command line.
   257			if len(args) == 0 {
   258				base.Fatalf("go env -u: no arguments given")
   259			}
   260			del := make(map[string]bool)
   261			for _, arg := range args {
   262				if err := checkEnvWrite(arg, "", env); err != nil {
   263					base.Fatalf("go env -u: %v", err)
   264				}
   265				del[arg] = true
   266			}
   267			updateEnvFile(nil, del)
   268			return
   269		}
   270	
   271		if len(args) > 0 {
   272			if *envJson {
   273				var es []cfg.EnvVar
   274				for _, name := range args {
   275					e := cfg.EnvVar{Name: name, Value: findEnv(env, name)}
   276					es = append(es, e)
   277				}
   278				printEnvAsJSON(es)
   279			} else {
   280				for _, name := range args {
   281					fmt.Printf("%s\n", findEnv(env, name))
   282				}
   283			}
   284			return
   285		}
   286	
   287		if *envJson {
   288			printEnvAsJSON(env)
   289			return
   290		}
   291	
   292		for _, e := range env {
   293			if e.Name != "TERM" {
   294				switch runtime.GOOS {
   295				default:
   296					fmt.Printf("%s=\"%s\"\n", e.Name, e.Value)
   297				case "plan9":
   298					if strings.IndexByte(e.Value, '\x00') < 0 {
   299						fmt.Printf("%s='%s'\n", e.Name, strings.ReplaceAll(e.Value, "'", "''"))
   300					} else {
   301						v := strings.Split(e.Value, "\x00")
   302						fmt.Printf("%s=(", e.Name)
   303						for x, s := range v {
   304							if x > 0 {
   305								fmt.Printf(" ")
   306							}
   307							fmt.Printf("%s", s)
   308						}
   309						fmt.Printf(")\n")
   310					}
   311				case "windows":
   312					fmt.Printf("set %s=%s\n", e.Name, e.Value)
   313				}
   314			}
   315		}
   316	}
   317	
   318	func printEnvAsJSON(env []cfg.EnvVar) {
   319		m := make(map[string]string)
   320		for _, e := range env {
   321			if e.Name == "TERM" {
   322				continue
   323			}
   324			m[e.Name] = e.Value
   325		}
   326		enc := json.NewEncoder(os.Stdout)
   327		enc.SetIndent("", "\t")
   328		if err := enc.Encode(m); err != nil {
   329			base.Fatalf("go env -json: %s", err)
   330		}
   331	}
   332	
   333	func checkEnvWrite(key, val string, env []cfg.EnvVar) error {
   334		switch key {
   335		case "GOEXE", "GOGCCFLAGS", "GOHOSTARCH", "GOHOSTOS", "GOMOD", "GOTOOLDIR":
   336			return fmt.Errorf("%s cannot be modified", key)
   337		case "GOENV":
   338			return fmt.Errorf("%s can only be set using the OS environment", key)
   339		}
   340	
   341		// To catch typos and the like, check that we know the variable.
   342		if !cfg.CanGetenv(key) {
   343			return fmt.Errorf("unknown go command variable %s", key)
   344		}
   345	
   346		if !utf8.ValidString(val) {
   347			return fmt.Errorf("invalid UTF-8 in %s=... value", key)
   348		}
   349		if strings.Contains(val, "\x00") {
   350			return fmt.Errorf("invalid NUL in %s=... value", key)
   351		}
   352		if strings.ContainsAny(val, "\v\r\n") {
   353			return fmt.Errorf("invalid newline in %s=... value", key)
   354		}
   355		return nil
   356	}
   357	
   358	func updateEnvFile(add map[string]string, del map[string]bool) {
   359		file, err := cfg.EnvFile()
   360		if file == "" {
   361			base.Fatalf("go env: cannot find go env config: %v", err)
   362		}
   363		data, err := ioutil.ReadFile(file)
   364		if err != nil && (!os.IsNotExist(err) || len(add) == 0) {
   365			base.Fatalf("go env: reading go env config: %v", err)
   366		}
   367	
   368		lines := strings.SplitAfter(string(data), "\n")
   369		if lines[len(lines)-1] == "" {
   370			lines = lines[:len(lines)-1]
   371		} else {
   372			lines[len(lines)-1] += "\n"
   373		}
   374	
   375		// Delete all but last copy of any duplicated variables,
   376		// since the last copy is the one that takes effect.
   377		prev := make(map[string]int)
   378		for l, line := range lines {
   379			if key := lineToKey(line); key != "" {
   380				if p, ok := prev[key]; ok {
   381					lines[p] = ""
   382				}
   383				prev[key] = l
   384			}
   385		}
   386	
   387		// Add variables (go env -w). Update existing lines in file if present, add to end otherwise.
   388		for key, val := range add {
   389			if p, ok := prev[key]; ok {
   390				lines[p] = key + "=" + val + "\n"
   391				delete(add, key)
   392			}
   393		}
   394		for key, val := range add {
   395			lines = append(lines, key+"="+val+"\n")
   396		}
   397	
   398		// Delete requested variables (go env -u).
   399		for key := range del {
   400			if p, ok := prev[key]; ok {
   401				lines[p] = ""
   402			}
   403		}
   404	
   405		// Sort runs of KEY=VALUE lines
   406		// (that is, blocks of lines where blocks are separated
   407		// by comments, blank lines, or invalid lines).
   408		start := 0
   409		for i := 0; i <= len(lines); i++ {
   410			if i == len(lines) || lineToKey(lines[i]) == "" {
   411				sortKeyValues(lines[start:i])
   412				start = i + 1
   413			}
   414		}
   415	
   416		data = []byte(strings.Join(lines, ""))
   417		err = ioutil.WriteFile(file, data, 0666)
   418		if err != nil {
   419			// Try creating directory.
   420			os.MkdirAll(filepath.Dir(file), 0777)
   421			err = ioutil.WriteFile(file, data, 0666)
   422			if err != nil {
   423				base.Fatalf("go env: writing go env config: %v", err)
   424			}
   425		}
   426	}
   427	
   428	// lineToKey returns the KEY part of the line KEY=VALUE or else an empty string.
   429	func lineToKey(line string) string {
   430		i := strings.Index(line, "=")
   431		if i < 0 || strings.Contains(line[:i], "#") {
   432			return ""
   433		}
   434		return line[:i]
   435	}
   436	
   437	// sortKeyValues sorts a sequence of lines by key.
   438	// It differs from sort.Strings in that GO386= sorts after GO=.
   439	func sortKeyValues(lines []string) {
   440		sort.Slice(lines, func(i, j int) bool {
   441			return lineToKey(lines[i]) < lineToKey(lines[j])
   442		})
   443	}
   444	

View as plain text