Skip to content

Commit bb08f5b

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. 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.
1 parent b3baae9 commit bb08f5b

File tree

9 files changed

+176
-9
lines changed

9 files changed

+176
-9
lines changed

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,23 @@ public ActionEnvironment addFixedVariables(Map<String, String> vars) {
186186
return new ActionEnvironment(new CompoundEnvironmentVariables(vars, fixedEnv), inheritedEnv);
187187
}
188188

189+
/**
190+
* Returns a copy of the environment with the given fixed and inherited variables added to it,
191+
* <em>overwriting any existing occurrences of those variables</em>.
192+
*/
193+
public ActionEnvironment addVariables(Map<String, String> vars, Set<String> inheritedVars) {
194+
if (inheritedVars.isEmpty()) {
195+
return addFixedVariables(vars);
196+
} else {
197+
// TODO: inheritedEnv is currently not optimized for allocation avoidance in the same way as
198+
// fixedEnv.
199+
ImmutableSet<String> newInheritedEnv = ImmutableSet.<String>builder().addAll(inheritedEnv)
200+
.addAll(inheritedVars).build();
201+
return new ActionEnvironment(new CompoundEnvironmentVariables(vars, fixedEnv),
202+
newInheritedEnv);
203+
}
204+
}
205+
189206
/** Returns the combined size of the fixed and inherited environments. */
190207
public int size() {
191208
return fixedEnv.size() + inheritedEnv.size();

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,7 @@ private TestProvider initializeTestProvider(FilesToRunProvider filesToRunProvide
476476
providersBuilder.getProvider(TestEnvironmentInfo.PROVIDER.getKey());
477477
if (environmentProvider != null) {
478478
testActionBuilder.addExtraEnv(environmentProvider.getEnvironment());
479+
testActionBuilder.addExtraInheritedEnv(environmentProvider.getInheritedEnvironment());
479480
}
480481

481482
TestParams testParams =

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

Lines changed: 10 additions & 1 deletion
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,7 @@ private TestParams createTestAction(int shards)
442451
coverageArtifact,
443452
coverageDirectory,
444453
testProperties,
445-
runfilesSupport.getActionEnvironment().addFixedVariables(extraTestEnv),
454+
runfilesSupport.getActionEnvironment().addVariables(extraTestEnv, extraInheritedEnv),
446455
executionSettings,
447456
shard,
448457
run,

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.google.devtools.build.lib.packages.BuiltinProvider;
2020
import com.google.devtools.build.lib.packages.NativeInfo;
2121
import com.google.devtools.build.lib.starlarkbuildapi.test.TestEnvironmentInfoApi;
22+
import java.util.List;
2223
import java.util.Map;
2324

2425
/** Provider containing any additional environment variables for use in the test action. */
@@ -30,10 +31,12 @@ public final class TestEnvironmentInfo extends NativeInfo implements TestEnviron
3031
new BuiltinProvider<TestEnvironmentInfo>("TestEnvironment", TestEnvironmentInfo.class) {};
3132

3233
private final Map<String, String> environment;
34+
private final List<String> inheritedEnvironment;
3335

34-
/** Constructs a new provider with the given variable name to variable value mapping. */
35-
public TestEnvironmentInfo(Map<String, String> environment) {
36+
/** Constructs a new provider with the given fixed and inherited environment variables. */
37+
public TestEnvironmentInfo(Map<String, String> environment, List<String> inheritedEnvironment) {
3638
this.environment = Preconditions.checkNotNull(environment);
39+
this.inheritedEnvironment = Preconditions.checkNotNull(inheritedEnvironment);
3740
}
3841

3942
@Override
@@ -48,4 +51,12 @@ public BuiltinProvider<TestEnvironmentInfo> getProvider() {
4851
public Map<String, String> getEnvironment() {
4952
return environment;
5053
}
54+
55+
/**
56+
* Returns names of environment variables whose value should be obtained from the environment.
57+
*/
58+
@Override
59+
public List<String> getInheritedEnvironment() {
60+
return inheritedEnvironment;
61+
}
5162
}

src/main/java/com/google/devtools/build/lib/rules/test/StarlarkTestingModule.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import com.google.devtools.build.lib.starlarkbuildapi.test.TestingModuleApi;
1919
import net.starlark.java.eval.Dict;
2020
import net.starlark.java.eval.EvalException;
21+
import net.starlark.java.eval.Sequence;
22+
import net.starlark.java.eval.StarlarkList;
2123

2224
/** A class that exposes testing infrastructure to Starlark. */
2325
public class StarlarkTestingModule implements TestingModuleApi {
@@ -29,9 +31,12 @@ public ExecutionInfo executionInfo(Dict<?, ?> requirements /* <String, String> *
2931
}
3032

3133
@Override
32-
public TestEnvironmentInfo testEnvironment(Dict<?, ?> environment /* <String, String> */)
34+
public TestEnvironmentInfo testEnvironment(Dict<?, ?> environment /* <String, String> */,
35+
Sequence<?> inheritedEnvironment /* <String> */)
3336
throws EvalException {
3437
return new TestEnvironmentInfo(
35-
Dict.cast(environment, String.class, String.class, "environment"));
38+
Dict.cast(environment, String.class, String.class, "environment"),
39+
StarlarkList.immutableCopyOf(
40+
Sequence.cast(inheritedEnvironment, String.class, "inherited_environment")));
3641
}
3742
}

src/main/java/com/google/devtools/build/lib/starlarkbuildapi/test/TestEnvironmentInfoApi.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package com.google.devtools.build.lib.starlarkbuildapi.test;
1616

1717
import com.google.devtools.build.lib.starlarkbuildapi.core.StructApi;
18+
import java.util.List;
1819
import java.util.Map;
1920
import net.starlark.java.annot.StarlarkBuiltin;
2021
import net.starlark.java.annot.StarlarkMethod;
@@ -28,4 +29,10 @@ public interface TestEnvironmentInfoApi extends StructApi {
2829
doc = "A dict containing environment variables which should be set on the test action.",
2930
structField = true)
3031
Map<String, String> getEnvironment();
32+
33+
@StarlarkMethod(
34+
name = "inherited_environment",
35+
doc = "A list of variables that the test action should inherit from the environment.",
36+
structField = true)
37+
List<String> getInheritedEnvironment();
3138
}

src/main/java/com/google/devtools/build/lib/starlarkbuildapi/test/TestingModuleApi.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@
1515
package com.google.devtools.build.lib.starlarkbuildapi.test;
1616

1717
import net.starlark.java.annot.Param;
18+
import net.starlark.java.annot.ParamType;
1819
import net.starlark.java.annot.StarlarkBuiltin;
1920
import net.starlark.java.annot.StarlarkMethod;
2021
import net.starlark.java.eval.Dict;
2122
import net.starlark.java.eval.EvalException;
23+
import net.starlark.java.eval.Sequence;
2224
import net.starlark.java.eval.StarlarkValue;
2325

2426
/** Helper module for accessing test infrastructure. */
@@ -56,12 +58,22 @@ ExecutionInfoApi executionInfo(Dict<?, ?> requirements // <String, String> expec
5658
parameters = {
5759
@Param(
5860
name = "environment",
59-
named = false,
61+
named = true,
6062
positional = true,
6163
doc =
6264
"A map of string keys and values that represent environment variables and their"
63-
+ " values. These will be made available during the test execution.")
65+
+ " values. These will be made available during the test execution."),
66+
@Param(
67+
name = "inherited_environment",
68+
allowedTypes = {@ParamType(type = Sequence.class, generic1 = String.class)},
69+
defaultValue = "[]",
70+
named = true,
71+
positional = true,
72+
doc =
73+
"A sequence of string keys that represent environment variables. These variables"
74+
+ "will be made available during the test execution with their current value"
75+
+ " taken from the environment.")
6476
})
65-
TestEnvironmentInfoApi testEnvironment(Dict<?, ?> environment // <String, String> expected
66-
) throws EvalException;
77+
TestEnvironmentInfoApi testEnvironment(Dict<?, ?> environment, // <String, String> expected
78+
Sequence<?> inheritedEnvironment /* <String> expected */) throws EvalException;
6779
}

src/test/java/com/google/devtools/build/lib/rules/test/StarlarkTestingModuleTest.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,39 @@ public void testStarlarkRulePropagatesTestEnvironmentProvider() throws Exception
7979
assertThat(provider.getEnvironment().get("XCODE_VERSION_OVERRIDE")).isEqualTo("7.3.1");
8080
}
8181

82+
@Test
83+
public void testStarlarkRulePropagatesTestEnvironmentProviderWithInheritedEnv() throws Exception {
84+
scratch.file("examples/rule/BUILD");
85+
scratch.file(
86+
"examples/rule/apple_rules.bzl",
87+
"def my_rule_impl(ctx):",
88+
" test_env = testing.TestEnvironment(",
89+
" {'XCODE_VERSION_OVERRIDE': '7.3.1'},",
90+
" [",
91+
" 'DEVELOPER_DIR',",
92+
" 'XCODE_VERSION_OVERRIDE',",
93+
" ]",
94+
")",
95+
" return [test_env]",
96+
"my_rule = rule(implementation = my_rule_impl,",
97+
" attrs = {},",
98+
")");
99+
scratch.file(
100+
"examples/apple_starlark/BUILD",
101+
"package(default_visibility = ['//visibility:public'])",
102+
"load('//examples/rule:apple_rules.bzl', 'my_rule')",
103+
"my_rule(",
104+
" name = 'my_target',",
105+
")");
106+
107+
ConfiguredTarget starlarkTarget = getConfiguredTarget("//examples/apple_starlark:my_target");
108+
TestEnvironmentInfo provider = starlarkTarget.get(TestEnvironmentInfo.PROVIDER);
109+
110+
assertThat(provider.getEnvironment().get("XCODE_VERSION_OVERRIDE")).isEqualTo("7.3.1");
111+
assertThat(provider.getInheritedEnvironment()).contains("DEVELOPER_DIR");
112+
assertThat(provider.getInheritedEnvironment()).contains("XCODE_VERSION_OVERRIDE");
113+
}
114+
82115
@Test
83116
public void testExecutionInfoProviderCanMarkTestAsLocal() throws Exception {
84117
scratch.file("examples/rule/BUILD");

src/test/shell/bazel/bazel_rules_test.sh

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,4 +593,76 @@ EOF
593593
assert_contains "hello world" bazel-bin/pkg/hello_gen.txt
594594
}
595595

596+
function test_starlark_test_with_test_environment() {
597+
mkdir pkg
598+
cat >pkg/BUILD <<'EOF'
599+
load(":rules.bzl", "my_test")
600+
my_test(
601+
name = "my_test",
602+
)
603+
EOF
604+
605+
# On Windows this file needs to be acceptable by CreateProcessW(), rather
606+
# than a Bourne script.
607+
if "$is_windows"; then
608+
cat >pkg/rules.bzl <<'EOF'
609+
_SCRIPT_EXT = ".bat"
610+
_SCRIPT_CONTENT = """@ECHO OFF
611+
if "%FIXED_ONLY%" != "fixed" {
612+
exit /B 1
613+
}
614+
if "%FIXED_AND_INHERITED%" != "fixed" {
615+
exit /B 1
616+
}
617+
if "%INHERITED_ONLY%" != "inherited" {
618+
exit /B 1
619+
}
620+
"""
621+
EOF
622+
else
623+
cat >pkg/rules.bzl <<'EOF'
624+
_SCRIPT_EXT = ".sh"
625+
_SCRIPT_CONTENT = """#!/bin/bash
626+
env
627+
[[ "$FIXED_ONLY" == "fixed" && "$FIXED_AND_INHERITED" = "fixed" && "$INHERITED_ONLY" == "inherited" ]]
628+
"""
629+
EOF
630+
fi
631+
632+
cat >>pkg/rules.bzl <<'EOF'
633+
def _my_test_impl(ctx):
634+
test_sh = ctx.actions.declare_file(ctx.attr.name + _SCRIPT_EXT)
635+
ctx.actions.write(
636+
output = test_sh,
637+
content = _SCRIPT_CONTENT,
638+
is_executable = True,
639+
)
640+
test_env = testing.TestEnvironment(
641+
{
642+
"FIXED_AND_INHERITED": "fixed",
643+
"FIXED_ONLY": "fixed",
644+
},
645+
[
646+
"FIXED_AND_INHERITED",
647+
"INHERITED_ONLY",
648+
]
649+
)
650+
return [
651+
DefaultInfo(
652+
executable = test_sh,
653+
),
654+
test_env
655+
]
656+
657+
my_test = rule(
658+
implementation = _my_test_impl,
659+
attrs = {},
660+
test = True,
661+
)
662+
EOF
663+
664+
FIXED_AND_INHERITED=inherited INHERITED_ONLY=inherited \
665+
bazel test //pkg:my_test --test_output=streamed || fail "Test should pass"
666+
}
667+
596668
run_suite "rules test"

0 commit comments

Comments
 (0)