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