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