github.com/tiagovtristao/plz@v13.4.0+incompatible/src/core/build_env.go (about)

     1  package core
     2  
     3  import (
     4  	"encoding/base64"
     5  	"path"
     6  	"strings"
     7  
     8  	"github.com/thought-machine/please/src/fs"
     9  )
    10  
    11  // ExpandHomePath is an alias to the function in fs for compatibility.
    12  var ExpandHomePath func(string) string = fs.ExpandHomePath
    13  
    14  // A BuildEnv is a representation of the build environment that also knows how to log itself.
    15  type BuildEnv []string
    16  
    17  // GeneralBuildEnvironment creates the shell env vars used for a command, not based
    18  // on any specific target etc.
    19  func GeneralBuildEnvironment(config *Configuration) BuildEnv {
    20  	// TODO(peterebden): why is this not just config.GetBuildEnv()?
    21  	env := BuildEnv{
    22  		// Need this for certain tools, for example sass
    23  		"LANG=" + config.Build.Lang,
    24  		// Expose the requested build config. We might also want to expose
    25  		// the command that's actually running (although typically this is more useful,
    26  		// because targets using this want to avoid defining different commands).
    27  		"BUILD_CONFIG=" + config.Build.Config,
    28  	}
    29  	if config.Go.GoRoot != "" {
    30  		env = append(env, "GOROOT="+config.Go.GoRoot)
    31  	}
    32  	if config.Cpp.PkgConfigPath != "" {
    33  		env = append(env, "PKG_CONFIG_PATH="+config.Cpp.PkgConfigPath)
    34  	}
    35  	return append(env, config.GetBuildEnv()...)
    36  }
    37  
    38  // buildEnvironment returns the basic parts of the build environment.
    39  func buildEnvironment(state *BuildState, target *BuildTarget) BuildEnv {
    40  	return append(GeneralBuildEnvironment(state.Config),
    41  		"PKG="+target.Label.PackageName,
    42  		"PKG_DIR="+target.Label.PackageDir(),
    43  		"NAME="+target.Label.Name,
    44  		"CONFIG="+state.Config.Build.Config,
    45  	)
    46  }
    47  
    48  // BuildEnvironment creates the shell env vars to be passed into the exec.Command calls made by plz.
    49  // Note that we lie about the location of HOME in order to keep some tools happy.
    50  // We read this as being slightly more POSIX-compliant than not having it set at all...
    51  func BuildEnvironment(state *BuildState, target *BuildTarget) BuildEnv {
    52  	env := buildEnvironment(state, target)
    53  	sources := target.AllSourcePaths(state.Graph)
    54  	tmpDir := path.Join(RepoRoot, target.TmpDir())
    55  	outEnv := target.GetTmpOutputAll(target.Outputs())
    56  
    57  	env = append(env,
    58  		"TMP_DIR="+tmpDir,
    59  		"TMPDIR="+tmpDir,
    60  		"SRCS="+strings.Join(sources, " "),
    61  		"OUTS="+strings.Join(outEnv, " "),
    62  		"HOME="+tmpDir,
    63  		"TOOLS="+strings.Join(toolPaths(state, target.Tools), " "),
    64  		// Set a consistent hash seed for Python. Important for build determinism.
    65  		"PYTHONHASHSEED=42",
    66  	)
    67  	// The OUT variable is only available on rules that have a single output.
    68  	if len(outEnv) == 1 {
    69  		env = append(env, "OUT="+path.Join(RepoRoot, target.TmpDir(), outEnv[0]))
    70  	}
    71  	// The SRC variable is only available on rules that have a single source file.
    72  	if len(sources) == 1 {
    73  		env = append(env, "SRC="+sources[0])
    74  	}
    75  	// Similarly, TOOL is only available on rules with a single tool.
    76  	if len(target.Tools) == 1 {
    77  		env = append(env, "TOOL="+toolPath(state, target.Tools[0]))
    78  	}
    79  	// Named source groups if the target declared any.
    80  	for name, srcs := range target.NamedSources {
    81  		paths := target.SourcePaths(state.Graph, srcs)
    82  		env = append(env, "SRCS_"+strings.ToUpper(name)+"="+strings.Join(paths, " "))
    83  	}
    84  	// Named output groups similarly.
    85  	for name, outs := range target.DeclaredNamedOutputs() {
    86  		outs = target.GetTmpOutputAll(outs)
    87  		env = append(env, "OUTS_"+strings.ToUpper(name)+"="+strings.Join(outs, " "))
    88  	}
    89  	// Named tools as well.
    90  	for name, tools := range target.namedTools {
    91  		env = append(env, "TOOLS_"+strings.ToUpper(name)+"="+strings.Join(toolPaths(state, tools), " "))
    92  	}
    93  	// Secrets, again only if they declared any.
    94  	if len(target.Secrets) > 0 {
    95  		env = append(env, "SECRETS="+ExpandHomePath(strings.Join(target.Secrets, " ")))
    96  	}
    97  	if state.Config.Bazel.Compatibility {
    98  		// Obviously this is only a subset of the variables Bazel would expose, but there's
    99  		// no point populating ones that we literally have no clue what they should be.
   100  		// To be honest I don't terribly like these, I'm pretty sure that using $GENDIR in
   101  		// your genrule is not a good sign.
   102  		env = append(env, "GENDIR="+path.Join(RepoRoot, GenDir))
   103  		env = append(env, "BINDIR="+path.Join(RepoRoot, BinDir))
   104  	}
   105  	return env
   106  }
   107  
   108  // TestEnvironment creates the environment variables for a test.
   109  func TestEnvironment(state *BuildState, target *BuildTarget, testDir string) BuildEnv {
   110  	env := buildEnvironment(state, target)
   111  	resultsFile := path.Join(testDir, "test.results")
   112  	env = append(env,
   113  		"TEST_DIR="+testDir,
   114  		"TMP_DIR="+testDir,
   115  		"TMPDIR="+testDir,
   116  		"TEST_ARGS="+strings.Join(state.TestArgs, ","),
   117  		"RESULTS_FILE="+resultsFile,
   118  		// We shouldn't really have specific things like this here, but it really is just easier to set it.
   119  		"GTEST_OUTPUT=xml:"+resultsFile,
   120  	)
   121  	// Ideally we would set this to something useful even within a container, but it ends
   122  	// up being /tmp/test or something which just confuses matters.
   123  	if !target.Containerise {
   124  		env = append(env, "HOME="+testDir)
   125  	}
   126  	if state.NeedCoverage && !target.HasAnyLabel(state.Config.Test.DisableCoverage) {
   127  		env = append(env, "COVERAGE=true", "COVERAGE_FILE=test.coverage")
   128  	}
   129  	if len(target.Outputs()) > 0 {
   130  		// Bit of a hack; ideally we would be unaware of the sandbox here.
   131  		if target.TestSandbox {
   132  			env = append(env, "TEST="+path.Join(SandboxDir, target.Outputs()[0]))
   133  		} else {
   134  			env = append(env, "TEST="+path.Join(testDir, target.Outputs()[0]))
   135  		}
   136  	}
   137  	if len(target.Data) > 0 {
   138  		env = append(env, "DATA="+strings.Join(target.AllData(state.Graph), " "))
   139  	}
   140  	// Bit of a hack for gcov which needs access to its .gcno files.
   141  	if target.HasLabel("cc") {
   142  		env = append(env, "GCNO_DIR="+path.Join(RepoRoot, GenDir, target.Label.PackageName))
   143  	}
   144  	if state.DebugTests {
   145  		env = append(env, "DEBUG=true")
   146  	}
   147  	return env
   148  }
   149  
   150  // StampedBuildEnvironment returns the shell env vars to be passed into exec.Command.
   151  // Optionally includes a stamp if the target is marked as such.
   152  func StampedBuildEnvironment(state *BuildState, target *BuildTarget, stamp []byte) BuildEnv {
   153  	env := BuildEnvironment(state, target)
   154  	if target.Stamp {
   155  		return append(env, "STAMP="+base64.RawURLEncoding.EncodeToString(stamp))
   156  	}
   157  	return env
   158  }
   159  
   160  func toolPath(state *BuildState, tool BuildInput) string {
   161  	label := tool.Label()
   162  	if label != nil {
   163  		return state.Graph.TargetOrDie(*label).toolPath()
   164  	}
   165  	return tool.Paths(state.Graph)[0]
   166  }
   167  
   168  func toolPaths(state *BuildState, tools []BuildInput) []string {
   169  	ret := make([]string, len(tools))
   170  	for i, tool := range tools {
   171  		ret[i] = toolPath(state, tool)
   172  	}
   173  	return ret
   174  }
   175  
   176  // ReplaceEnvironment is a function suitable for passing to os.Expand to replace environment
   177  // variables from this BuildEnv.
   178  func (env BuildEnv) ReplaceEnvironment(s string) string {
   179  	for _, e := range env {
   180  		if strings.HasPrefix(e, s+"=") {
   181  			return e[len(s)+1:]
   182  		}
   183  	}
   184  	return ""
   185  }
   186  
   187  // Replace replaces the value of the given variable in this BuildEnv.
   188  func (env BuildEnv) Replace(key, value string) {
   189  	key = key + "="
   190  	for i, e := range env {
   191  		if strings.HasPrefix(e, key) {
   192  			env[i] = key + value
   193  		}
   194  	}
   195  }
   196  
   197  // Redacted implements the interface for our logging implementation.
   198  func (env BuildEnv) Redacted() interface{} {
   199  	r := make(BuildEnv, len(env))
   200  	for i, e := range env {
   201  		r[i] = e
   202  		split := strings.SplitN(e, "=", 2)
   203  		if len(split) == 2 && (strings.Contains(split[0], "SECRET") || strings.Contains(split[0], "PASSWORD")) {
   204  			r[i] = split[0] + "=" + "************"
   205  		}
   206  	}
   207  	return r
   208  }
   209  
   210  // String implements the fmt.Stringer interface
   211  func (env BuildEnv) String() string {
   212  	return strings.Join(env, "\n")
   213  }