| // Copyright 2017 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // Package cfg holds configuration shared by multiple parts |
| // of the go command. |
| package cfg |
| |
| import ( |
| "bytes" |
| "fmt" |
| "go/build" |
| "internal/buildcfg" |
| "internal/cfg" |
| "io" |
| "os" |
| "os/exec" |
| "path/filepath" |
| "runtime" |
| "strings" |
| "sync" |
| |
| "cmd/go/internal/fsys" |
| ) |
| |
| // Global build parameters (used during package load) |
| var ( |
| Goos = envOr("GOOS", build.Default.GOOS) |
| Goarch = envOr("GOARCH", build.Default.GOARCH) |
| |
| ExeSuffix = exeSuffix() |
| |
| // ModulesEnabled specifies whether the go command is running |
| // in module-aware mode (as opposed to GOPATH mode). |
| // It is equal to modload.Enabled, but not all packages can import modload. |
| ModulesEnabled bool |
| ) |
| |
| func exeSuffix() string { |
| if Goos == "windows" { |
| return ".exe" |
| } |
| return "" |
| } |
| |
| // Configuration for tools installed to GOROOT/bin. |
| // Normally these match runtime.GOOS and runtime.GOARCH, |
| // but when testing a cross-compiled cmd/go they will |
| // indicate the GOOS and GOARCH of the installed cmd/go |
| // rather than the test binary. |
| var ( |
| installedGOOS string |
| installedGOARCH string |
| ) |
| |
| // ToolExeSuffix returns the suffix for executables installed |
| // in build.ToolDir. |
| func ToolExeSuffix() string { |
| if installedGOOS == "windows" { |
| return ".exe" |
| } |
| return "" |
| } |
| |
| // These are general "build flags" used by build and other commands. |
| var ( |
| BuildA bool // -a flag |
| BuildBuildmode string // -buildmode flag |
| BuildBuildvcs = "auto" // -buildvcs flag: "true", "false", or "auto" |
| BuildContext = defaultContext() |
| BuildMod string // -mod flag |
| BuildModExplicit bool // whether -mod was set explicitly |
| BuildModReason string // reason -mod was set, if set by default |
| BuildLinkshared bool // -linkshared flag |
| BuildMSan bool // -msan flag |
| BuildASan bool // -asan flag |
| BuildCover bool // -cover flag |
| BuildCoverMode string // -covermode flag |
| BuildCoverPkg []string // -coverpkg flag |
| BuildN bool // -n flag |
| BuildO string // -o flag |
| BuildP = runtime.GOMAXPROCS(0) // -p flag |
| BuildPGO string // -pgo flag |
| BuildPGOFile string // profile selected by -pgo flag, an absolute path (if not empty) |
| BuildPkgdir string // -pkgdir flag |
| BuildRace bool // -race flag |
| BuildToolexec []string // -toolexec flag |
| BuildToolchainName string |
| BuildToolchainCompiler func() string |
| BuildToolchainLinker func() string |
| BuildTrimpath bool // -trimpath flag |
| BuildV bool // -v flag |
| BuildWork bool // -work flag |
| BuildX bool // -x flag |
| |
| ModCacheRW bool // -modcacherw flag |
| ModFile string // -modfile flag |
| |
| CmdName string // "build", "install", "list", "mod tidy", etc. |
| |
| DebugActiongraph string // -debug-actiongraph flag (undocumented, unstable) |
| DebugTrace string // -debug-trace flag |
| |
| // GoPathError is set when GOPATH is not set. it contains an |
| // explanation why GOPATH is unset. |
| GoPathError string |
| ) |
| |
| func defaultContext() build.Context { |
| ctxt := build.Default |
| |
| ctxt.JoinPath = filepath.Join // back door to say "do not use go command" |
| |
| // Override defaults computed in go/build with defaults |
| // from go environment configuration file, if known. |
| ctxt.GOPATH = envOr("GOPATH", gopath(ctxt)) |
| ctxt.GOOS = Goos |
| ctxt.GOARCH = Goarch |
| |
| // Clear the GOEXPERIMENT-based tool tags, which we will recompute later. |
| var save []string |
| for _, tag := range ctxt.ToolTags { |
| if !strings.HasPrefix(tag, "goexperiment.") { |
| save = append(save, tag) |
| } |
| } |
| ctxt.ToolTags = save |
| |
| // The go/build rule for whether cgo is enabled is: |
| // 1. If $CGO_ENABLED is set, respect it. |
| // 2. Otherwise, if this is a cross-compile, disable cgo. |
| // 3. Otherwise, use built-in default for GOOS/GOARCH. |
| // Recreate that logic here with the new GOOS/GOARCH setting. |
| if v := Getenv("CGO_ENABLED"); v == "0" || v == "1" { |
| ctxt.CgoEnabled = v[0] == '1' |
| } else if ctxt.GOOS != runtime.GOOS || ctxt.GOARCH != runtime.GOARCH { |
| ctxt.CgoEnabled = false |
| } else { |
| // Use built-in default cgo setting for GOOS/GOARCH. |
| // Note that ctxt.GOOS/GOARCH are derived from the preference list |
| // (1) environment, (2) go/env file, (3) runtime constants, |
| // while go/build.Default.GOOS/GOARCH are derived from the preference list |
| // (1) environment, (2) runtime constants. |
| // We know ctxt.GOOS/GOARCH == runtime.GOOS/GOARCH; |
| // no matter how that happened, go/build.Default will make the |
| // same decision (either the environment variables are set explicitly |
| // to match the runtime constants, or else they are unset, in which |
| // case go/build falls back to the runtime constants), so |
| // go/build.Default.GOOS/GOARCH == runtime.GOOS/GOARCH. |
| // So ctxt.CgoEnabled (== go/build.Default.CgoEnabled) is correct |
| // as is and can be left unmodified. |
| // |
| // All that said, starting in Go 1.20 we layer one more rule |
| // on top of the go/build decision: if CC is unset and |
| // the default C compiler we'd look for is not in the PATH, |
| // we automatically default cgo to off. |
| // This makes go builds work automatically on systems |
| // without a C compiler installed. |
| if ctxt.CgoEnabled { |
| if os.Getenv("CC") == "" { |
| cc := DefaultCC(ctxt.GOOS, ctxt.GOARCH) |
| if _, err := exec.LookPath(cc); err != nil { |
| ctxt.CgoEnabled = false |
| } |
| } |
| } |
| } |
| |
| ctxt.OpenFile = func(path string) (io.ReadCloser, error) { |
| return fsys.Open(path) |
| } |
| ctxt.ReadDir = fsys.ReadDir |
| ctxt.IsDir = func(path string) bool { |
| isDir, err := fsys.IsDir(path) |
| return err == nil && isDir |
| } |
| |
| return ctxt |
| } |
| |
| func init() { |
| SetGOROOT(Getenv("GOROOT"), false) |
| BuildToolchainCompiler = func() string { return "missing-compiler" } |
| BuildToolchainLinker = func() string { return "missing-linker" } |
| } |
| |
| // SetGOROOT sets GOROOT and associated variables to the given values. |
| // |
| // If isTestGo is true, build.ToolDir is set based on the TESTGO_GOHOSTOS and |
| // TESTGO_GOHOSTARCH environment variables instead of runtime.GOOS and |
| // runtime.GOARCH. |
| func SetGOROOT(goroot string, isTestGo bool) { |
| BuildContext.GOROOT = goroot |
| |
| GOROOT = goroot |
| if goroot == "" { |
| GOROOTbin = "" |
| GOROOTpkg = "" |
| GOROOTsrc = "" |
| } else { |
| GOROOTbin = filepath.Join(goroot, "bin") |
| GOROOTpkg = filepath.Join(goroot, "pkg") |
| GOROOTsrc = filepath.Join(goroot, "src") |
| } |
| GOROOT_FINAL = findGOROOT_FINAL(goroot) |
| |
| installedGOOS = runtime.GOOS |
| installedGOARCH = runtime.GOARCH |
| if isTestGo { |
| if testOS := os.Getenv("TESTGO_GOHOSTOS"); testOS != "" { |
| installedGOOS = testOS |
| } |
| if testArch := os.Getenv("TESTGO_GOHOSTARCH"); testArch != "" { |
| installedGOARCH = testArch |
| } |
| } |
| |
| if runtime.Compiler != "gccgo" { |
| if goroot == "" { |
| build.ToolDir = "" |
| } else { |
| // Note that we must use the installed OS and arch here: the tool |
| // directory does not move based on environment variables, and even if we |
| // are testing a cross-compiled cmd/go all of the installed packages and |
| // tools would have been built using the native compiler and linker (and |
| // would spuriously appear stale if we used a cross-compiled compiler and |
| // linker). |
| // |
| // This matches the initialization of ToolDir in go/build, except for |
| // using ctxt.GOROOT and the installed GOOS and GOARCH rather than the |
| // GOROOT, GOOS, and GOARCH reported by the runtime package. |
| build.ToolDir = filepath.Join(GOROOTpkg, "tool", installedGOOS+"_"+installedGOARCH) |
| } |
| } |
| } |
| |
| // Experiment configuration. |
| var ( |
| // RawGOEXPERIMENT is the GOEXPERIMENT value set by the user. |
| RawGOEXPERIMENT = envOr("GOEXPERIMENT", buildcfg.DefaultGOEXPERIMENT) |
| // CleanGOEXPERIMENT is the minimal GOEXPERIMENT value needed to reproduce the |
| // experiments enabled by RawGOEXPERIMENT. |
| CleanGOEXPERIMENT = RawGOEXPERIMENT |
| |
| Experiment *buildcfg.ExperimentFlags |
| ExperimentErr error |
| ) |
| |
| func init() { |
| Experiment, ExperimentErr = buildcfg.ParseGOEXPERIMENT(Goos, Goarch, RawGOEXPERIMENT) |
| if ExperimentErr != nil { |
| return |
| } |
| |
| // GOEXPERIMENT is valid, so convert it to canonical form. |
| CleanGOEXPERIMENT = Experiment.String() |
| |
| // Add build tags based on the experiments in effect. |
| exps := Experiment.Enabled() |
| expTags := make([]string, 0, len(exps)+len(BuildContext.ToolTags)) |
| for _, exp := range exps { |
| expTags = append(expTags, "goexperiment."+exp) |
| } |
| BuildContext.ToolTags = append(expTags, BuildContext.ToolTags...) |
| } |
| |
| // An EnvVar is an environment variable Name=Value. |
| type EnvVar struct { |
| Name string |
| Value string |
| } |
| |
| // OrigEnv is the original environment of the program at startup. |
| var OrigEnv []string |
| |
| // CmdEnv is the new environment for running go tool commands. |
| // User binaries (during go test or go run) are run with OrigEnv, |
| // not CmdEnv. |
| var CmdEnv []EnvVar |
| |
| var envCache struct { |
| once sync.Once |
| m map[string]string |
| } |
| |
| // EnvFile returns the name of the Go environment configuration file. |
| func EnvFile() (string, error) { |
| if file := os.Getenv("GOENV"); file != "" { |
| if file == "off" { |
| return "", fmt.Errorf("GOENV=off") |
| } |
| return file, nil |
| } |
| dir, err := os.UserConfigDir() |
| if err != nil { |
| return "", err |
| } |
| if dir == "" { |
| return "", fmt.Errorf("missing user-config dir") |
| } |
| return filepath.Join(dir, "go/env"), nil |
| } |
| |
| func initEnvCache() { |
| envCache.m = make(map[string]string) |
| if file, _ := EnvFile(); file != "" { |
| readEnvFile(file, "user") |
| } |
| goroot := findGOROOT(envCache.m["GOROOT"]) |
| if goroot != "" { |
| readEnvFile(filepath.Join(goroot, "go.env"), "GOROOT") |
| } |
| |
| // Save the goroot for func init calling SetGOROOT, |
| // and also overwrite anything that might have been in go.env. |
| // It makes no sense for GOROOT/go.env to specify |
| // a different GOROOT. |
| envCache.m["GOROOT"] = goroot |
| } |
| |
| func readEnvFile(file string, source string) { |
| if file == "" { |
| return |
| } |
| data, err := os.ReadFile(file) |
| if err != nil { |
| return |
| } |
| |
| for len(data) > 0 { |
| // Get next line. |
| line := data |
| i := bytes.IndexByte(data, '\n') |
| if i >= 0 { |
| line, data = line[:i], data[i+1:] |
| } else { |
| data = nil |
| } |
| |
| i = bytes.IndexByte(line, '=') |
| if i < 0 || line[0] < 'A' || 'Z' < line[0] { |
| // Line is missing = (or empty) or a comment or not a valid env name. Ignore. |
| // This should not happen in the user file, since the file should be maintained almost |
| // exclusively by "go env -w", but better to silently ignore than to make |
| // the go command unusable just because somehow the env file has |
| // gotten corrupted. |
| // In the GOROOT/go.env file, we expect comments. |
| continue |
| } |
| key, val := line[:i], line[i+1:] |
| |
| if source == "GOROOT" { |
| // In the GOROOT/go.env file, do not overwrite fields loaded from the user's go/env file. |
| if _, ok := envCache.m[string(key)]; ok { |
| continue |
| } |
| } |
| envCache.m[string(key)] = string(val) |
| } |
| } |
| |
| // Getenv gets the value for the configuration key. |
| // It consults the operating system environment |
| // and then the go/env file. |
| // If Getenv is called for a key that cannot be set |
| // in the go/env file (for example GODEBUG), it panics. |
| // This ensures that CanGetenv is accurate, so that |
| // 'go env -w' stays in sync with what Getenv can retrieve. |
| func Getenv(key string) string { |
| if !CanGetenv(key) { |
| switch key { |
| case "CGO_TEST_ALLOW", "CGO_TEST_DISALLOW", "CGO_test_ALLOW", "CGO_test_DISALLOW": |
| // used by internal/work/security_test.go; allow |
| default: |
| panic("internal error: invalid Getenv " + key) |
| } |
| } |
| val := os.Getenv(key) |
| if val != "" { |
| return val |
| } |
| envCache.once.Do(initEnvCache) |
| return envCache.m[key] |
| } |
| |
| // CanGetenv reports whether key is a valid go/env configuration key. |
| func CanGetenv(key string) bool { |
| return strings.Contains(cfg.KnownEnv, "\t"+key+"\n") |
| } |
| |
| var ( |
| GOROOT string |
| |
| // Either empty or produced by filepath.Join(GOROOT, …). |
| GOROOTbin string |
| GOROOTpkg string |
| GOROOTsrc string |
| |
| GOROOT_FINAL string |
| |
| GOBIN = Getenv("GOBIN") |
| GOMODCACHE = envOr("GOMODCACHE", gopathDir("pkg/mod")) |
| |
| // Used in envcmd.MkEnv and build ID computations. |
| GOARM = envOr("GOARM", fmt.Sprint(buildcfg.GOARM)) |
| GO386 = envOr("GO386", buildcfg.GO386) |
| GOAMD64 = envOr("GOAMD64", fmt.Sprintf("%s%d", "v", buildcfg.GOAMD64)) |
| GOMIPS = envOr("GOMIPS", buildcfg.GOMIPS) |
| GOMIPS64 = envOr("GOMIPS64", buildcfg.GOMIPS64) |
| GOPPC64 = envOr("GOPPC64", fmt.Sprintf("%s%d", "power", buildcfg.GOPPC64)) |
| GOWASM = envOr("GOWASM", fmt.Sprint(buildcfg.GOWASM)) |
| |
| GOPROXY = envOr("GOPROXY", "") |
| GOSUMDB = envOr("GOSUMDB", "") |
| GOPRIVATE = Getenv("GOPRIVATE") |
| GONOPROXY = envOr("GONOPROXY", GOPRIVATE) |
| GONOSUMDB = envOr("GONOSUMDB", GOPRIVATE) |
| GOINSECURE = Getenv("GOINSECURE") |
| GOVCS = Getenv("GOVCS") |
| ) |
| |
| var SumdbDir = gopathDir("pkg/sumdb") |
| |
| // GetArchEnv returns the name and setting of the |
| // GOARCH-specific architecture environment variable. |
| // If the current architecture has no GOARCH-specific variable, |
| // GetArchEnv returns empty key and value. |
| func GetArchEnv() (key, val string) { |
| switch Goarch { |
| case "arm": |
| return "GOARM", GOARM |
| case "386": |
| return "GO386", GO386 |
| case "amd64": |
| return "GOAMD64", GOAMD64 |
| case "mips", "mipsle": |
| return "GOMIPS", GOMIPS |
| case "mips64", "mips64le": |
| return "GOMIPS64", GOMIPS64 |
| case "ppc64", "ppc64le": |
| return "GOPPC64", GOPPC64 |
| case "wasm": |
| return "GOWASM", GOWASM |
| } |
| return "", "" |
| } |
| |
| // envOr returns Getenv(key) if set, or else def. |
| func envOr(key, def string) string { |
| val := Getenv(key) |
| if val == "" { |
| val = def |
| } |
| return val |
| } |
| |
| // There is a copy of findGOROOT, isSameDir, and isGOROOT in |
| // x/tools/cmd/godoc/goroot.go. |
| // Try to keep them in sync for now. |
| |
| // findGOROOT returns the GOROOT value, using either an explicitly |
| // provided environment variable, a GOROOT that contains the current |
| // os.Executable value, or else the GOROOT that the binary was built |
| // with from runtime.GOROOT(). |
| // |
| // There is a copy of this code in x/tools/cmd/godoc/goroot.go. |
| func findGOROOT(env string) string { |
| if env == "" { |
| // Not using Getenv because findGOROOT is called |
| // to find the GOROOT/go.env file. initEnvCache |
| // has passed in the setting from the user go/env file. |
| env = os.Getenv("GOROOT") |
| } |
| if env != "" { |
| return filepath.Clean(env) |
| } |
| def := "" |
| if r := runtime.GOROOT(); r != "" { |
| def = filepath.Clean(r) |
| } |
| if runtime.Compiler == "gccgo" { |
| // gccgo has no real GOROOT, and it certainly doesn't |
| // depend on the executable's location. |
| return def |
| } |
| exe, err := os.Executable() |
| if err == nil { |
| exe, err = filepath.Abs(exe) |
| if err == nil { |
| if dir := filepath.Join(exe, "../.."); isGOROOT(dir) { |
| // If def (runtime.GOROOT()) and dir are the same |
| // directory, prefer the spelling used in def. |
| if isSameDir(def, dir) { |
| return def |
| } |
| return dir |
| } |
| exe, err = filepath.EvalSymlinks(exe) |
| if err == nil { |
| if dir := filepath.Join(exe, "../.."); isGOROOT(dir) { |
| if isSameDir(def, dir) { |
| return def |
| } |
| return dir |
| } |
| } |
| } |
| } |
| return def |
| } |
| |
| func findGOROOT_FINAL(goroot string) string { |
| // $GOROOT_FINAL is only for use during make.bash |
| // so it is not settable using go/env, so we use os.Getenv here. |
| def := goroot |
| if env := os.Getenv("GOROOT_FINAL"); env != "" { |
| def = filepath.Clean(env) |
| } |
| return def |
| } |
| |
| // isSameDir reports whether dir1 and dir2 are the same directory. |
| func isSameDir(dir1, dir2 string) bool { |
| if dir1 == dir2 { |
| return true |
| } |
| info1, err1 := os.Stat(dir1) |
| info2, err2 := os.Stat(dir2) |
| return err1 == nil && err2 == nil && os.SameFile(info1, info2) |
| } |
| |
| // isGOROOT reports whether path looks like a GOROOT. |
| // |
| // It does this by looking for the path/pkg/tool directory, |
| // which is necessary for useful operation of the cmd/go tool, |
| // and is not typically present in a GOPATH. |
| // |
| // There is a copy of this code in x/tools/cmd/godoc/goroot.go. |
| func isGOROOT(path string) bool { |
| stat, err := os.Stat(filepath.Join(path, "pkg", "tool")) |
| if err != nil { |
| return false |
| } |
| return stat.IsDir() |
| } |
| |
| func gopathDir(rel string) string { |
| list := filepath.SplitList(BuildContext.GOPATH) |
| if len(list) == 0 || list[0] == "" { |
| return "" |
| } |
| return filepath.Join(list[0], rel) |
| } |
| |
| func gopath(ctxt build.Context) string { |
| if len(ctxt.GOPATH) > 0 { |
| return ctxt.GOPATH |
| } |
| env := "HOME" |
| if runtime.GOOS == "windows" { |
| env = "USERPROFILE" |
| } else if runtime.GOOS == "plan9" { |
| env = "home" |
| } |
| if home := os.Getenv(env); home != "" { |
| def := filepath.Join(home, "go") |
| if filepath.Clean(def) == filepath.Clean(runtime.GOROOT()) { |
| GoPathError = "cannot set GOROOT as GOPATH" |
| } |
| return "" |
| } |
| GoPathError = fmt.Sprintf("%s is not set", env) |
| return "" |
| } |