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