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