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