diff options
Diffstat (limited to 'src/cmd/go/internal/cfg/cfg.go')
-rw-r--r-- | src/cmd/go/internal/cfg/cfg.go | 618 |
1 files changed, 618 insertions, 0 deletions
diff --git a/src/cmd/go/internal/cfg/cfg.go b/src/cmd/go/internal/cfg/cfg.go new file mode 100644 index 0000000..a8daa2d --- /dev/null +++ b/src/cmd/go/internal/cfg/cfg.go @@ -0,0 +1,618 @@ +// 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" + "context" + "fmt" + "go/build" + "internal/buildcfg" + "internal/cfg" + "io" + "os" + "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 + 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 + DebugRuntimeTrace string // -debug-runtime-trace flag (undocumented, unstable) + + // 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 := 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 { + envCache.once.Do(initEnvCache) + if _, ok := envCache.m[key]; ok { + // Assume anything in the user file or go.env file is valid. + return true + } + 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 + } + + // canonical returns a directory path that represents + // the same directory as dir, + // preferring the spelling in def if the two are the same. + canonical := func(dir string) string { + if isSameDir(def, dir) { + return def + } + return dir + } + + exe, err := os.Executable() + if err == nil { + exe, err = filepath.Abs(exe) + if err == nil { + // cmd/go may be installed in GOROOT/bin or GOROOT/bin/GOOS_GOARCH, + // depending on whether it was cross-compiled with a different + // GOHOSTOS (see https://go.dev/issue/62119). Try both. + if dir := filepath.Join(exe, "../.."); isGOROOT(dir) { + return canonical(dir) + } + if dir := filepath.Join(exe, "../../.."); isGOROOT(dir) { + return canonical(dir) + } + + // Depending on what was passed on the command line, it is possible + // that os.Executable is a symlink (like /usr/local/bin/go) referring + // to a binary installed in a real GOROOT elsewhere + // (like /usr/lib/go/bin/go). + // Try to find that GOROOT by resolving the symlinks. + exe, err = filepath.EvalSymlinks(exe) + if err == nil { + if dir := filepath.Join(exe, "../.."); isGOROOT(dir) { + return canonical(dir) + } + if dir := filepath.Join(exe, "../../.."); isGOROOT(dir) { + return canonical(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 "" +} + +// WithBuildXWriter returns a Context in which BuildX output is written +// to given io.Writer. +func WithBuildXWriter(ctx context.Context, xLog io.Writer) context.Context { + return context.WithValue(ctx, buildXContextKey{}, xLog) +} + +type buildXContextKey struct{} + +// BuildXWriter returns nil if BuildX is false, or +// the writer to which BuildX output should be written otherwise. +func BuildXWriter(ctx context.Context) (io.Writer, bool) { + if !BuildX { + return nil, false + } + if v := ctx.Value(buildXContextKey{}); v != nil { + return v.(io.Writer), true + } + return os.Stderr, true +} |