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