blob: a6ad7390efe40847e86b66114a62fa9905c31da8 [file] [log] [blame]
// 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 ""
}