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 }