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