github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/go/cfg/cfg.go (about) 1 // Copyright 2017 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 cfg holds configuration shared by multiple parts 6 // of the go command. 7 package cfg 8 9 import ( 10 "bytes" 11 "context" 12 "fmt" 13 "go/build" 14 "io" 15 "os" 16 "path/filepath" 17 "runtime" 18 "strings" 19 "sync" 20 21 "github.com/go-asm/go/buildcfg" 22 "github.com/go-asm/go/cfg" 23 24 "github.com/go-asm/go/cmd/go/fsys" 25 ) 26 27 // Global build parameters (used during package load) 28 var ( 29 Goos = envOr("GOOS", build.Default.GOOS) 30 Goarch = envOr("GOARCH", build.Default.GOARCH) 31 32 ExeSuffix = exeSuffix() 33 34 // ModulesEnabled specifies whether the go command is running 35 // in module-aware mode (as opposed to GOPATH mode). 36 // It is equal to modload.Enabled, but not all packages can import modload. 37 ModulesEnabled bool 38 ) 39 40 func exeSuffix() string { 41 if Goos == "windows" { 42 return ".exe" 43 } 44 return "" 45 } 46 47 // Configuration for tools installed to GOROOT/bin. 48 // Normally these match runtime.GOOS and runtime.GOARCH, 49 // but when testing a cross-compiled cmd/go they will 50 // indicate the GOOS and GOARCH of the installed cmd/go 51 // rather than the test binary. 52 var ( 53 installedGOOS string 54 installedGOARCH string 55 ) 56 57 // ToolExeSuffix returns the suffix for executables installed 58 // in build.ToolDir. 59 func ToolExeSuffix() string { 60 if installedGOOS == "windows" { 61 return ".exe" 62 } 63 return "" 64 } 65 66 // These are general "build flags" used by build and other commands. 67 var ( 68 BuildA bool // -a flag 69 BuildBuildmode string // -buildmode flag 70 BuildBuildvcs = "auto" // -buildvcs flag: "true", "false", or "auto" 71 BuildContext = defaultContext() 72 BuildMod string // -mod flag 73 BuildModExplicit bool // whether -mod was set explicitly 74 BuildModReason string // reason -mod was set, if set by default 75 BuildLinkshared bool // -linkshared flag 76 BuildMSan bool // -msan flag 77 BuildASan bool // -asan flag 78 BuildCover bool // -cover flag 79 BuildCoverMode string // -covermode flag 80 BuildCoverPkg []string // -coverpkg flag 81 BuildN bool // -n flag 82 BuildO string // -o flag 83 BuildP = runtime.GOMAXPROCS(0) // -p flag 84 BuildPGO string // -pgo flag 85 BuildPkgdir string // -pkgdir flag 86 BuildRace bool // -race flag 87 BuildToolexec []string // -toolexec flag 88 BuildToolchainName string 89 BuildToolchainCompiler func() string 90 BuildToolchainLinker func() string 91 BuildTrimpath bool // -trimpath flag 92 BuildV bool // -v flag 93 BuildWork bool // -work flag 94 BuildX bool // -x flag 95 96 ModCacheRW bool // -modcacherw flag 97 ModFile string // -modfile flag 98 99 CmdName string // "build", "install", "list", "mod tidy", etc. 100 101 DebugActiongraph string // -debug-actiongraph flag (undocumented, unstable) 102 DebugTrace string // -debug-trace flag 103 DebugRuntimeTrace string // -debug-runtime-trace flag (undocumented, unstable) 104 105 // GoPathError is set when GOPATH is not set. it contains an 106 // explanation why GOPATH is unset. 107 GoPathError string 108 ) 109 110 func defaultContext() build.Context { 111 ctxt := build.Default 112 113 ctxt.JoinPath = filepath.Join // back door to say "do not use go command" 114 115 // Override defaults computed in go/build with defaults 116 // from go environment configuration file, if known. 117 ctxt.GOPATH = envOr("GOPATH", gopath(ctxt)) 118 ctxt.GOOS = Goos 119 ctxt.GOARCH = Goarch 120 121 // Clear the GOEXPERIMENT-based tool tags, which we will recompute later. 122 var save []string 123 for _, tag := range ctxt.ToolTags { 124 if !strings.HasPrefix(tag, "goexperiment.") { 125 save = append(save, tag) 126 } 127 } 128 ctxt.ToolTags = save 129 130 // The go/build rule for whether cgo is enabled is: 131 // 1. If $CGO_ENABLED is set, respect it. 132 // 2. Otherwise, if this is a cross-compile, disable cgo. 133 // 3. Otherwise, use built-in default for GOOS/GOARCH. 134 // Recreate that logic here with the new GOOS/GOARCH setting. 135 if v := Getenv("CGO_ENABLED"); v == "0" || v == "1" { 136 ctxt.CgoEnabled = v[0] == '1' 137 } else if ctxt.GOOS != runtime.GOOS || ctxt.GOARCH != runtime.GOARCH { 138 ctxt.CgoEnabled = false 139 } else { 140 // Use built-in default cgo setting for GOOS/GOARCH. 141 // Note that ctxt.GOOS/GOARCH are derived from the preference list 142 // (1) environment, (2) go/env file, (3) runtime constants, 143 // while go/build.Default.GOOS/GOARCH are derived from the preference list 144 // (1) environment, (2) runtime constants. 145 // 146 // We know ctxt.GOOS/GOARCH == runtime.GOOS/GOARCH; 147 // no matter how that happened, go/build.Default will make the 148 // same decision (either the environment variables are set explicitly 149 // to match the runtime constants, or else they are unset, in which 150 // case go/build falls back to the runtime constants), so 151 // go/build.Default.GOOS/GOARCH == runtime.GOOS/GOARCH. 152 // So ctxt.CgoEnabled (== go/build.Default.CgoEnabled) is correct 153 // as is and can be left unmodified. 154 // 155 // All that said, starting in Go 1.20 we layer one more rule 156 // on top of the go/build decision: if CC is unset and 157 // the default C compiler we'd look for is not in the PATH, 158 // we automatically default cgo to off. 159 // This makes go builds work automatically on systems 160 // without a C compiler installed. 161 if ctxt.CgoEnabled { 162 if os.Getenv("CC") == "" { 163 cc := DefaultCC(ctxt.GOOS, ctxt.GOARCH) 164 if _, err := LookPath(cc); err != nil { 165 ctxt.CgoEnabled = false 166 } 167 } 168 } 169 } 170 171 ctxt.OpenFile = func(path string) (io.ReadCloser, error) { 172 return fsys.Open(path) 173 } 174 ctxt.ReadDir = fsys.ReadDir 175 ctxt.IsDir = func(path string) bool { 176 isDir, err := fsys.IsDir(path) 177 return err == nil && isDir 178 } 179 180 return ctxt 181 } 182 183 func init() { 184 SetGOROOT(Getenv("GOROOT"), false) 185 BuildToolchainCompiler = func() string { return "missing-compiler" } 186 BuildToolchainLinker = func() string { return "missing-linker" } 187 } 188 189 // SetGOROOT sets GOROOT and associated variables to the given values. 190 // 191 // If isTestGo is true, build.ToolDir is set based on the TESTGO_GOHOSTOS and 192 // TESTGO_GOHOSTARCH environment variables instead of runtime.GOOS and 193 // runtime.GOARCH. 194 func SetGOROOT(goroot string, isTestGo bool) { 195 BuildContext.GOROOT = goroot 196 197 GOROOT = goroot 198 if goroot == "" { 199 GOROOTbin = "" 200 GOROOTpkg = "" 201 GOROOTsrc = "" 202 } else { 203 GOROOTbin = filepath.Join(goroot, "bin") 204 GOROOTpkg = filepath.Join(goroot, "pkg") 205 GOROOTsrc = filepath.Join(goroot, "src") 206 } 207 GOROOT_FINAL = findGOROOT_FINAL(goroot) 208 209 installedGOOS = runtime.GOOS 210 installedGOARCH = runtime.GOARCH 211 if isTestGo { 212 if testOS := os.Getenv("TESTGO_GOHOSTOS"); testOS != "" { 213 installedGOOS = testOS 214 } 215 if testArch := os.Getenv("TESTGO_GOHOSTARCH"); testArch != "" { 216 installedGOARCH = testArch 217 } 218 } 219 220 if runtime.Compiler != "gccgo" { 221 if goroot == "" { 222 build.ToolDir = "" 223 } else { 224 // Note that we must use the installed OS and arch here: the tool 225 // directory does not move based on environment variables, and even if we 226 // are testing a cross-compiled cmd/go all of the installed packages and 227 // tools would have been built using the native compiler and linker (and 228 // would spuriously appear stale if we used a cross-compiled compiler and 229 // linker). 230 // 231 // This matches the initialization of ToolDir in go/build, except for 232 // using ctxt.GOROOT and the installed GOOS and GOARCH rather than the 233 // GOROOT, GOOS, and GOARCH reported by the runtime package. 234 build.ToolDir = filepath.Join(GOROOTpkg, "tool", installedGOOS+"_"+installedGOARCH) 235 } 236 } 237 } 238 239 // Experiment configuration. 240 var ( 241 // RawGOEXPERIMENT is the GOEXPERIMENT value set by the user. 242 RawGOEXPERIMENT = envOr("GOEXPERIMENT", buildcfg.DefaultGOEXPERIMENT) 243 // CleanGOEXPERIMENT is the minimal GOEXPERIMENT value needed to reproduce the 244 // experiments enabled by RawGOEXPERIMENT. 245 CleanGOEXPERIMENT = RawGOEXPERIMENT 246 247 Experiment *buildcfg.ExperimentFlags 248 ExperimentErr error 249 ) 250 251 func init() { 252 Experiment, ExperimentErr = buildcfg.ParseGOEXPERIMENT(Goos, Goarch, RawGOEXPERIMENT) 253 if ExperimentErr != nil { 254 return 255 } 256 257 // GOEXPERIMENT is valid, so convert it to canonical form. 258 CleanGOEXPERIMENT = Experiment.String() 259 260 // Add build tags based on the experiments in effect. 261 exps := Experiment.Enabled() 262 expTags := make([]string, 0, len(exps)+len(BuildContext.ToolTags)) 263 for _, exp := range exps { 264 expTags = append(expTags, "goexperiment."+exp) 265 } 266 BuildContext.ToolTags = append(expTags, BuildContext.ToolTags...) 267 } 268 269 // An EnvVar is an environment variable Name=Value. 270 type EnvVar struct { 271 Name string 272 Value string 273 } 274 275 // OrigEnv is the original environment of the program at startup. 276 var OrigEnv []string 277 278 // CmdEnv is the new environment for running go tool commands. 279 // User binaries (during go test or go run) are run with OrigEnv, 280 // not CmdEnv. 281 var CmdEnv []EnvVar 282 283 var envCache struct { 284 once sync.Once 285 m map[string]string 286 } 287 288 // EnvFile returns the name of the Go environment configuration file. 289 func EnvFile() (string, error) { 290 if file := os.Getenv("GOENV"); file != "" { 291 if file == "off" { 292 return "", fmt.Errorf("GOENV=off") 293 } 294 return file, nil 295 } 296 dir, err := os.UserConfigDir() 297 if err != nil { 298 return "", err 299 } 300 if dir == "" { 301 return "", fmt.Errorf("missing user-config dir") 302 } 303 return filepath.Join(dir, "go/env"), nil 304 } 305 306 func initEnvCache() { 307 envCache.m = make(map[string]string) 308 if file, _ := EnvFile(); file != "" { 309 readEnvFile(file, "user") 310 } 311 goroot := findGOROOT(envCache.m["GOROOT"]) 312 if goroot != "" { 313 readEnvFile(filepath.Join(goroot, "go.env"), "GOROOT") 314 } 315 316 // Save the goroot for func init calling SetGOROOT, 317 // and also overwrite anything that might have been in go.env. 318 // It makes no sense for GOROOT/go.env to specify 319 // a different GOROOT. 320 envCache.m["GOROOT"] = goroot 321 } 322 323 func readEnvFile(file string, source string) { 324 if file == "" { 325 return 326 } 327 data, err := os.ReadFile(file) 328 if err != nil { 329 return 330 } 331 332 for len(data) > 0 { 333 // Get next line. 334 line := data 335 i := bytes.IndexByte(data, '\n') 336 if i >= 0 { 337 line, data = line[:i], data[i+1:] 338 } else { 339 data = nil 340 } 341 342 i = bytes.IndexByte(line, '=') 343 if i < 0 || line[0] < 'A' || 'Z' < line[0] { 344 // Line is missing = (or empty) or a comment or not a valid env name. Ignore. 345 // This should not happen in the user file, since the file should be maintained almost 346 // exclusively by "go env -w", but better to silently ignore than to make 347 // the go command unusable just because somehow the env file has 348 // gotten corrupted. 349 // In the GOROOT/go.env file, we expect comments. 350 continue 351 } 352 key, val := line[:i], line[i+1:] 353 354 if source == "GOROOT" { 355 // In the GOROOT/go.env file, do not overwrite fields loaded from the user's go/env file. 356 if _, ok := envCache.m[string(key)]; ok { 357 continue 358 } 359 } 360 envCache.m[string(key)] = string(val) 361 } 362 } 363 364 // Getenv gets the value for the configuration key. 365 // It consults the operating system environment 366 // and then the go/env file. 367 // If Getenv is called for a key that cannot be set 368 // in the go/env file (for example GODEBUG), it panics. 369 // This ensures that CanGetenv is accurate, so that 370 // 'go env -w' stays in sync with what Getenv can retrieve. 371 func Getenv(key string) string { 372 if !CanGetenv(key) { 373 switch key { 374 case "CGO_TEST_ALLOW", "CGO_TEST_DISALLOW", "CGO_test_ALLOW", "CGO_test_DISALLOW": 375 // used by github.com/go-asm/go/work/security_test.go; allow 376 default: 377 panic("internal error: invalid Getenv " + key) 378 } 379 } 380 val := os.Getenv(key) 381 if val != "" { 382 return val 383 } 384 envCache.once.Do(initEnvCache) 385 return envCache.m[key] 386 } 387 388 // CanGetenv reports whether key is a valid go/env configuration key. 389 func CanGetenv(key string) bool { 390 envCache.once.Do(initEnvCache) 391 if _, ok := envCache.m[key]; ok { 392 // Assume anything in the user file or go.env file is valid. 393 return true 394 } 395 return strings.Contains(cfg.KnownEnv, "\t"+key+"\n") 396 } 397 398 var ( 399 GOROOT string 400 401 // Either empty or produced by filepath.Join(GOROOT, …). 402 GOROOTbin string 403 GOROOTpkg string 404 GOROOTsrc string 405 406 GOROOT_FINAL string 407 408 GOBIN = Getenv("GOBIN") 409 GOMODCACHE = envOr("GOMODCACHE", gopathDir("pkg/mod")) 410 411 // Used in envcmd.MkEnv and build ID computations. 412 GOARM = envOr("GOARM", fmt.Sprint(buildcfg.GOARM)) 413 GO386 = envOr("GO386", buildcfg.GO386) 414 GOAMD64 = envOr("GOAMD64", fmt.Sprintf("%s%d", "v", buildcfg.GOAMD64)) 415 GOMIPS = envOr("GOMIPS", buildcfg.GOMIPS) 416 GOMIPS64 = envOr("GOMIPS64", buildcfg.GOMIPS64) 417 GOPPC64 = envOr("GOPPC64", fmt.Sprintf("%s%d", "power", buildcfg.GOPPC64)) 418 GOWASM = envOr("GOWASM", fmt.Sprint(buildcfg.GOWASM)) 419 420 GOPROXY = envOr("GOPROXY", "") 421 GOSUMDB = envOr("GOSUMDB", "") 422 GOPRIVATE = Getenv("GOPRIVATE") 423 GONOPROXY = envOr("GONOPROXY", GOPRIVATE) 424 GONOSUMDB = envOr("GONOSUMDB", GOPRIVATE) 425 GOINSECURE = Getenv("GOINSECURE") 426 GOVCS = Getenv("GOVCS") 427 ) 428 429 var SumdbDir = gopathDir("pkg/sumdb") 430 431 // GetArchEnv returns the name and setting of the 432 // GOARCH-specific architecture environment variable. 433 // If the current architecture has no GOARCH-specific variable, 434 // GetArchEnv returns empty key and value. 435 func GetArchEnv() (key, val string) { 436 switch Goarch { 437 case "arm": 438 return "GOARM", GOARM 439 case "386": 440 return "GO386", GO386 441 case "amd64": 442 return "GOAMD64", GOAMD64 443 case "mips", "mipsle": 444 return "GOMIPS", GOMIPS 445 case "mips64", "mips64le": 446 return "GOMIPS64", GOMIPS64 447 case "ppc64", "ppc64le": 448 return "GOPPC64", GOPPC64 449 case "wasm": 450 return "GOWASM", GOWASM 451 } 452 return "", "" 453 } 454 455 // envOr returns Getenv(key) if set, or else def. 456 func envOr(key, def string) string { 457 val := Getenv(key) 458 if val == "" { 459 val = def 460 } 461 return val 462 } 463 464 // There is a copy of findGOROOT, isSameDir, and isGOROOT in 465 // x/tools/cmd/godoc/goroot.go. 466 // Try to keep them in sync for now. 467 468 // findGOROOT returns the GOROOT value, using either an explicitly 469 // provided environment variable, a GOROOT that contains the current 470 // os.Executable value, or else the GOROOT that the binary was built 471 // with from runtime.GOROOT(). 472 // 473 // There is a copy of this code in x/tools/cmd/godoc/goroot.go. 474 func findGOROOT(env string) string { 475 if env == "" { 476 // Not using Getenv because findGOROOT is called 477 // to find the GOROOT/go.env file. initEnvCache 478 // has passed in the setting from the user go/env file. 479 env = os.Getenv("GOROOT") 480 } 481 if env != "" { 482 return filepath.Clean(env) 483 } 484 def := "" 485 if r := runtime.GOROOT(); r != "" { 486 def = filepath.Clean(r) 487 } 488 if runtime.Compiler == "gccgo" { 489 // gccgo has no real GOROOT, and it certainly doesn't 490 // depend on the executable's location. 491 return def 492 } 493 494 // canonical returns a directory path that represents 495 // the same directory as dir, 496 // preferring the spelling in def if the two are the same. 497 canonical := func(dir string) string { 498 if isSameDir(def, dir) { 499 return def 500 } 501 return dir 502 } 503 504 exe, err := os.Executable() 505 if err == nil { 506 exe, err = filepath.Abs(exe) 507 if err == nil { 508 // cmd/go may be installed in GOROOT/bin or GOROOT/bin/GOOS_GOARCH, 509 // depending on whether it was cross-compiled with a different 510 // GOHOSTOS (see https://go.dev/issue/62119). Try both. 511 if dir := filepath.Join(exe, "../.."); isGOROOT(dir) { 512 return canonical(dir) 513 } 514 if dir := filepath.Join(exe, "../../.."); isGOROOT(dir) { 515 return canonical(dir) 516 } 517 518 // Depending on what was passed on the command line, it is possible 519 // that os.Executable is a symlink (like /usr/local/bin/go) referring 520 // to a binary installed in a real GOROOT elsewhere 521 // (like /usr/lib/go/bin/go). 522 // Try to find that GOROOT by resolving the symlinks. 523 exe, err = filepath.EvalSymlinks(exe) 524 if err == nil { 525 if dir := filepath.Join(exe, "../.."); isGOROOT(dir) { 526 return canonical(dir) 527 } 528 if dir := filepath.Join(exe, "../../.."); isGOROOT(dir) { 529 return canonical(dir) 530 } 531 } 532 } 533 } 534 return def 535 } 536 537 func findGOROOT_FINAL(goroot string) string { 538 // $GOROOT_FINAL is only for use during make.bash 539 // so it is not settable using go/env, so we use os.Getenv here. 540 def := goroot 541 if env := os.Getenv("GOROOT_FINAL"); env != "" { 542 def = filepath.Clean(env) 543 } 544 return def 545 } 546 547 // isSameDir reports whether dir1 and dir2 are the same directory. 548 func isSameDir(dir1, dir2 string) bool { 549 if dir1 == dir2 { 550 return true 551 } 552 info1, err1 := os.Stat(dir1) 553 info2, err2 := os.Stat(dir2) 554 return err1 == nil && err2 == nil && os.SameFile(info1, info2) 555 } 556 557 // isGOROOT reports whether path looks like a GOROOT. 558 // 559 // It does this by looking for the path/pkg/tool directory, 560 // which is necessary for useful operation of the cmd/go tool, 561 // and is not typically present in a GOPATH. 562 // 563 // There is a copy of this code in x/tools/cmd/godoc/goroot.go. 564 func isGOROOT(path string) bool { 565 stat, err := os.Stat(filepath.Join(path, "pkg", "tool")) 566 if err != nil { 567 return false 568 } 569 return stat.IsDir() 570 } 571 572 func gopathDir(rel string) string { 573 list := filepath.SplitList(BuildContext.GOPATH) 574 if len(list) == 0 || list[0] == "" { 575 return "" 576 } 577 return filepath.Join(list[0], rel) 578 } 579 580 func gopath(ctxt build.Context) string { 581 if len(ctxt.GOPATH) > 0 { 582 return ctxt.GOPATH 583 } 584 env := "HOME" 585 if runtime.GOOS == "windows" { 586 env = "USERPROFILE" 587 } else if runtime.GOOS == "plan9" { 588 env = "home" 589 } 590 if home := os.Getenv(env); home != "" { 591 def := filepath.Join(home, "go") 592 if filepath.Clean(def) == filepath.Clean(runtime.GOROOT()) { 593 GoPathError = "cannot set GOROOT as GOPATH" 594 } 595 return "" 596 } 597 GoPathError = fmt.Sprintf("%s is not set", env) 598 return "" 599 } 600 601 // WithBuildXWriter returns a Context in which BuildX output is written 602 // to given io.Writer. 603 func WithBuildXWriter(ctx context.Context, xLog io.Writer) context.Context { 604 return context.WithValue(ctx, buildXContextKey{}, xLog) 605 } 606 607 type buildXContextKey struct{} 608 609 // BuildXWriter returns nil if BuildX is false, or 610 // the writer to which BuildX output should be written otherwise. 611 func BuildXWriter(ctx context.Context) (io.Writer, bool) { 612 if !BuildX { 613 return nil, false 614 } 615 if v := ctx.Value(buildXContextKey{}); v != nil { 616 return v.(io.Writer), true 617 } 618 return os.Stderr, true 619 }