Skip to content

Commit 5b96389

Browse files
committed
Let Starlark tests inherit env variables
Adds an inherited_environment field to testing.TestEnvironment to allow Starlark rules to implement the equivalent of the native rules' `env_inherit` attribute. Work towards bazelbuild#7364. To fully resolve that issue, it remains to handle executable non-test rules. RELNOTES: Starlark test rules can use the new inherited_environment parameter of testing.TestEnvironment to specify environment variables whose values should be inherited from the shell environment. Closes bazelbuild#14849. PiperOrigin-RevId: 439277689
1 parent af8316c commit 5b96389

18 files changed

+270
-71
lines changed

src/main/java/com/google/devtools/build/lib/actions/AbstractAction.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -701,7 +701,7 @@ public Dict<String, String> getExecutionInfoDict() {
701701

702702
@Override
703703
public Dict<String, String> getEnv() {
704-
return Dict.immutableCopyOf(env.getFixedEnv().toMap());
704+
return Dict.immutableCopyOf(env.getFixedEnv());
705705
}
706706

707707
@Override

src/main/java/com/google/devtools/build/lib/actions/ActionEnvironment.java

+88-47
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import com.google.common.collect.ImmutableMap;
1919
import com.google.common.collect.ImmutableSet;
2020
import com.google.devtools.build.lib.util.Fingerprint;
21-
import java.util.LinkedHashMap;
2221
import java.util.Map;
2322
import java.util.Set;
2423
import java.util.TreeMap;
@@ -45,23 +44,34 @@
4544
*/
4645
public final class ActionEnvironment {
4746

48-
/** A map of environment variables. */
47+
/**
48+
* A map of environment variables together with a list of variables to inherit from the shell
49+
* environment.
50+
*/
4951
public interface EnvironmentVariables {
5052

5153
/**
52-
* Returns the environment variables as a map.
54+
* Returns the fixed environment variables as a map.
5355
*
54-
* <p>WARNING: this allocations additional objects if the underlying implementation is a {@link
56+
* <p>WARNING: this allocates additional objects if the underlying implementation is a {@link
5557
* CompoundEnvironmentVariables}; use sparingly.
5658
*/
57-
ImmutableMap<String, String> toMap();
59+
ImmutableMap<String, String> getFixedEnvironment();
60+
61+
/**
62+
* Returns the inherited environment variables as a set.
63+
*
64+
* <p>WARNING: this allocates additional objects if the underlying implementation is a {@link
65+
* CompoundEnvironmentVariables}; use sparingly.
66+
*/
67+
ImmutableSet<String> getInheritedEnvironment();
5868

5969
default boolean isEmpty() {
60-
return toMap().isEmpty();
70+
return getFixedEnvironment().isEmpty() && getInheritedEnvironment().isEmpty();
6171
}
6272

6373
default int size() {
64-
return toMap().size();
74+
return getFixedEnvironment().size() + getInheritedEnvironment().size();
6575
}
6676
}
6777

@@ -70,11 +80,21 @@ default int size() {
7080
* allocation a new map.
7181
*/
7282
static class CompoundEnvironmentVariables implements EnvironmentVariables {
83+
84+
static EnvironmentVariables create(
85+
Map<String, String> fixedVars, Set<String> inheritedVars, EnvironmentVariables base) {
86+
if (fixedVars.isEmpty() && inheritedVars.isEmpty() && base.isEmpty()) {
87+
return EMPTY_ENVIRONMENT_VARIABLES;
88+
}
89+
return new CompoundEnvironmentVariables(fixedVars, inheritedVars, base);
90+
}
91+
7392
private final EnvironmentVariables current;
7493
private final EnvironmentVariables base;
7594

76-
CompoundEnvironmentVariables(Map<String, String> vars, EnvironmentVariables base) {
77-
this.current = new SimpleEnvironmentVariables(vars);
95+
private CompoundEnvironmentVariables(
96+
Map<String, String> fixedVars, Set<String> inheritedVars, EnvironmentVariables base) {
97+
this.current = SimpleEnvironmentVariables.create(fixedVars, inheritedVars);
7898
this.base = base;
7999
}
80100

@@ -84,48 +104,62 @@ public boolean isEmpty() {
84104
}
85105

86106
@Override
87-
public ImmutableMap<String, String> toMap() {
88-
Map<String, String> result = new LinkedHashMap<>();
89-
result.putAll(base.toMap());
90-
result.putAll(current.toMap());
91-
return ImmutableMap.copyOf(result);
107+
public ImmutableMap<String, String> getFixedEnvironment() {
108+
ImmutableMap.Builder<String, String> result = new ImmutableMap.Builder<>();
109+
result.putAll(base.getFixedEnvironment());
110+
result.putAll(current.getFixedEnvironment());
111+
return result.buildKeepingLast();
112+
}
113+
114+
@Override
115+
public ImmutableSet<String> getInheritedEnvironment() {
116+
ImmutableSet.Builder<String> result = new ImmutableSet.Builder<>();
117+
result.addAll(base.getInheritedEnvironment());
118+
result.addAll(current.getInheritedEnvironment());
119+
return result.build();
92120
}
93121
}
94122

95123
/** A simple {@link EnvironmentVariables}. */
96124
static class SimpleEnvironmentVariables implements EnvironmentVariables {
97125

98-
static EnvironmentVariables create(Map<String, String> vars) {
99-
if (vars.isEmpty()) {
126+
static EnvironmentVariables create(Map<String, String> fixedVars, Set<String> inheritedVars) {
127+
if (fixedVars.isEmpty() && inheritedVars.isEmpty()) {
100128
return EMPTY_ENVIRONMENT_VARIABLES;
101129
}
102-
return new SimpleEnvironmentVariables(vars);
130+
return new SimpleEnvironmentVariables(fixedVars, inheritedVars);
103131
}
104132

105-
private final ImmutableMap<String, String> vars;
133+
private final ImmutableMap<String, String> fixedVars;
134+
private final ImmutableSet<String> inheritedVars;
106135

107-
private SimpleEnvironmentVariables(Map<String, String> vars) {
108-
this.vars = ImmutableMap.copyOf(vars);
136+
private SimpleEnvironmentVariables(Map<String, String> fixedVars, Set<String> inheritedVars) {
137+
this.fixedVars = ImmutableMap.copyOf(fixedVars);
138+
this.inheritedVars = ImmutableSet.copyOf(inheritedVars);
109139
}
110140

111141
@Override
112-
public ImmutableMap<String, String> toMap() {
113-
return vars;
142+
public ImmutableMap<String, String> getFixedEnvironment() {
143+
return fixedVars;
144+
}
145+
146+
@Override
147+
public ImmutableSet<String> getInheritedEnvironment() {
148+
return inheritedVars;
114149
}
115150
}
116151

117152
/** An empty {@link EnvironmentVariables}. */
118153
public static final EnvironmentVariables EMPTY_ENVIRONMENT_VARIABLES =
119-
new SimpleEnvironmentVariables(ImmutableMap.of());
154+
new SimpleEnvironmentVariables(ImmutableMap.of(), ImmutableSet.of());
120155

121156
/**
122157
* An empty environment, mainly for testing. Production code should never use this, but instead
123158
* get the proper environment from the current configuration.
124159
*/
125160
// TODO(ulfjack): Migrate all production code to use the proper action environment, and then make
126161
// this @VisibleForTesting or rename it to clarify.
127-
public static final ActionEnvironment EMPTY =
128-
new ActionEnvironment(EMPTY_ENVIRONMENT_VARIABLES, ImmutableSet.of());
162+
public static final ActionEnvironment EMPTY = new ActionEnvironment(EMPTY_ENVIRONMENT_VARIABLES);
129163

130164
/**
131165
* Splits the given map into a map of variables with a fixed value, and a set of variables that
@@ -145,59 +179,66 @@ public static ActionEnvironment split(Map<String, String> env) {
145179
inheritedEnv.add(key);
146180
}
147181
}
148-
return create(new SimpleEnvironmentVariables(fixedEnv), ImmutableSet.copyOf(inheritedEnv));
182+
return create(SimpleEnvironmentVariables.create(fixedEnv, inheritedEnv));
149183
}
150184

151-
private final EnvironmentVariables fixedEnv;
152-
private final ImmutableSet<String> inheritedEnv;
185+
private final EnvironmentVariables vars;
153186

154-
private ActionEnvironment(EnvironmentVariables fixedEnv, ImmutableSet<String> inheritedEnv) {
155-
this.fixedEnv = fixedEnv;
156-
this.inheritedEnv = inheritedEnv;
187+
private ActionEnvironment(EnvironmentVariables vars) {
188+
this.vars = vars;
157189
}
158190

159191
/**
160192
* Creates a new action environment. The order in which the environments are combined is
161193
* undefined, so callers need to take care that the key set of the {@code fixedEnv} map and the
162194
* set of {@code inheritedEnv} elements are disjoint.
163195
*/
164-
private static ActionEnvironment create(
165-
EnvironmentVariables fixedEnv, ImmutableSet<String> inheritedEnv) {
166-
if (fixedEnv.isEmpty() && inheritedEnv.isEmpty()) {
196+
private static ActionEnvironment create(EnvironmentVariables vars) {
197+
if (vars.isEmpty()) {
167198
return EMPTY;
168199
}
169-
return new ActionEnvironment(fixedEnv, inheritedEnv);
200+
return new ActionEnvironment(vars);
170201
}
171202

172203
public static ActionEnvironment create(
173204
Map<String, String> fixedEnv, ImmutableSet<String> inheritedEnv) {
174-
return new ActionEnvironment(SimpleEnvironmentVariables.create(fixedEnv), inheritedEnv);
205+
return ActionEnvironment.create(SimpleEnvironmentVariables.create(fixedEnv, inheritedEnv));
175206
}
176207

177208
public static ActionEnvironment create(Map<String, String> fixedEnv) {
178-
return new ActionEnvironment(new SimpleEnvironmentVariables(fixedEnv), ImmutableSet.of());
209+
return ActionEnvironment.create(SimpleEnvironmentVariables.create(fixedEnv, ImmutableSet.of()));
179210
}
180211

181212
/**
182213
* Returns a copy of the environment with the given fixed variables added to it, <em>overwriting
183214
* any existing occurrences of those variables</em>.
184215
*/
185-
public ActionEnvironment addFixedVariables(Map<String, String> vars) {
186-
return new ActionEnvironment(new CompoundEnvironmentVariables(vars, fixedEnv), inheritedEnv);
216+
public ActionEnvironment addFixedVariables(Map<String, String> fixedVars) {
217+
return ActionEnvironment.create(
218+
CompoundEnvironmentVariables.create(fixedVars, ImmutableSet.of(), vars));
219+
}
220+
221+
/**
222+
* Returns a copy of the environment with the given fixed and inherited variables added to it,
223+
* <em>overwriting any existing occurrences of those variables</em>.
224+
*/
225+
public ActionEnvironment addVariables(Map<String, String> fixedVars, Set<String> inheritedVars) {
226+
return ActionEnvironment.create(
227+
CompoundEnvironmentVariables.create(fixedVars, inheritedVars, vars));
187228
}
188229

189230
/** Returns the combined size of the fixed and inherited environments. */
190231
public int size() {
191-
return fixedEnv.size() + inheritedEnv.size();
232+
return vars.size();
192233
}
193234

194235
/**
195236
* Returns the 'fixed' part of the environment, i.e., those environment variables that are set to
196237
* fixed values and their values. This should only be used for testing and to compute the cache
197238
* keys of actions. Use {@link #resolve} instead to get the complete environment.
198239
*/
199-
public EnvironmentVariables getFixedEnv() {
200-
return fixedEnv;
240+
public ImmutableMap<String, String> getFixedEnv() {
241+
return vars.getFixedEnvironment();
201242
}
202243

203244
/**
@@ -207,7 +248,7 @@ public EnvironmentVariables getFixedEnv() {
207248
* get the complete environment.
208249
*/
209250
public ImmutableSet<String> getInheritedEnv() {
210-
return inheritedEnv;
251+
return vars.getInheritedEnvironment();
211252
}
212253

213254
/**
@@ -218,8 +259,8 @@ public ImmutableSet<String> getInheritedEnv() {
218259
*/
219260
public void resolve(Map<String, String> result, Map<String, String> clientEnv) {
220261
checkNotNull(clientEnv);
221-
result.putAll(fixedEnv.toMap());
222-
for (String var : inheritedEnv) {
262+
result.putAll(vars.getFixedEnvironment());
263+
for (String var : vars.getInheritedEnvironment()) {
223264
String value = clientEnv.get(var);
224265
if (value != null) {
225266
result.put(var, value);
@@ -228,7 +269,7 @@ public void resolve(Map<String, String> result, Map<String, String> clientEnv) {
228269
}
229270

230271
public void addTo(Fingerprint f) {
231-
f.addStringMap(fixedEnv.toMap());
232-
f.addStrings(inheritedEnv);
272+
f.addStringMap(vars.getFixedEnvironment());
273+
f.addStrings(vars.getInheritedEnvironment());
233274
}
234275
}

src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java

+1
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,7 @@ private TestProvider initializeTestProvider(FilesToRunProvider filesToRunProvide
470470
providersBuilder.getProvider(TestEnvironmentInfo.PROVIDER.getKey());
471471
if (environmentProvider != null) {
472472
testActionBuilder.addExtraEnv(environmentProvider.getEnvironment());
473+
testActionBuilder.addExtraInheritedEnv(environmentProvider.getInheritedEnvironment());
473474
}
474475

475476
TestParams testParams =

src/main/java/com/google/devtools/build/lib/analysis/actions/SpawnAction.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@ public String describeKey() {
442442
StringBuilder message = new StringBuilder();
443443
message.append(getProgressMessage());
444444
message.append('\n');
445-
for (Map.Entry<String, String> entry : env.getFixedEnv().toMap().entrySet()) {
445+
for (Map.Entry<String, String> entry : env.getFixedEnv().entrySet()) {
446446
message.append(" Environment variable: ");
447447
message.append(ShellEscaper.escapeString(entry.getKey()));
448448
message.append('=');
@@ -551,7 +551,7 @@ public final ImmutableMap<String, String> getIncompleteEnvironmentForTesting() {
551551
// ActionEnvironment to avoid developers misunderstanding the purpose of this method. That
552552
// requires first updating all subclasses and callers to actually handle environments correctly,
553553
// so it's not a small change.
554-
return env.getFixedEnv().toMap();
554+
return env.getFixedEnv();
555555
}
556556

557557
/**

src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,7 @@ public boolean isSiblingRepositoryLayout() {
446446
*/
447447
@Override
448448
public ImmutableMap<String, String> getLocalShellEnvironment() {
449-
return actionEnv.getFixedEnv().toMap();
449+
return actionEnv.getFixedEnv();
450450
}
451451

452452
/**
@@ -583,7 +583,7 @@ public boolean legacyExternalRunfiles() {
583583
*/
584584
@Override
585585
public ImmutableMap<String, String> getTestEnv() {
586-
return testEnv.getFixedEnv().toMap();
586+
return testEnv.getFixedEnv();
587587
}
588588

589589
/**

src/main/java/com/google/devtools/build/lib/analysis/test/TestActionBuilder.java

+12-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@
5555
import com.google.devtools.build.lib.vfs.PathFragment;
5656
import java.util.List;
5757
import java.util.Map;
58+
import java.util.Set;
5859
import java.util.TreeMap;
60+
import java.util.TreeSet;
5961
import javax.annotation.Nullable;
6062

6163
/**
@@ -96,10 +98,12 @@ public NestedSet<PackageGroupContents> getPackageSpecifications() {
9698
private InstrumentedFilesInfo instrumentedFiles;
9799
private int explicitShardCount;
98100
private final Map<String, String> extraEnv;
101+
private final Set<String> extraInheritedEnv;
99102

100103
public TestActionBuilder(RuleContext ruleContext) {
101104
this.ruleContext = ruleContext;
102105
this.extraEnv = new TreeMap<>();
106+
this.extraInheritedEnv = new TreeSet<>();
103107
this.additionalTools = new ImmutableList.Builder<>();
104108
}
105109

@@ -154,6 +158,11 @@ public TestActionBuilder addExtraEnv(Map<String, String> extraEnv) {
154158
return this;
155159
}
156160

161+
public TestActionBuilder addExtraInheritedEnv(List<String> extraInheritedEnv) {
162+
this.extraInheritedEnv.addAll(extraInheritedEnv);
163+
return this;
164+
}
165+
157166
/**
158167
* Set the explicit shard count. Note that this may be overridden by the sharding strategy.
159168
*/
@@ -442,7 +451,9 @@ private TestParams createTestAction(int shards)
442451
coverageArtifact,
443452
coverageDirectory,
444453
testProperties,
445-
runfilesSupport.getActionEnvironment().addFixedVariables(extraTestEnv),
454+
runfilesSupport
455+
.getActionEnvironment()
456+
.addVariables(extraTestEnv, extraInheritedEnv),
446457
executionSettings,
447458
shard,
448459
run,

0 commit comments

Comments
 (0)