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 }