Skip to content

Commit 6052b8b

Browse files
fmeumcomiusiancha1992
authored
[6.4.0] Add --incompatible_merge_fixed_and_default_shell_env (#19319)
With the new flag, Starlark actions that specify both `env` and `use_default_shell_env` will no longer have `env` ignored. Instead, the values of `env` will override the default shell environment. This allows Starlark actions to both pick up user-configured variables such as `PATH` from the shell environment as well as set variables to fixed values required for the action, e.g., variables provided by the C++ toolchain. Rationale for having `env` override the default shell env: The rules know best which values they have to set specific environment variables to in order to successfully execute an action, so a situation where users break an action by a globally applied `--action_env` is prevented. If users really do have to be able to modify an environment variables fixed by the rule, the rule can always make this configurable via an attribute. Work towards #5980 Fixes #12049 Closes #18235. Commit d1fdc53 PiperOrigin-RevId: 559506535 Change-Id: I7ec6ae17b076bbca72fab8394f3a8b3e4f9ea9d8 Fixes #19312 --------- Co-authored-by: Ivo List <[email protected]> Co-authored-by: Ian (Hee) Cha <[email protected]>
1 parent cb187a0 commit 6052b8b

File tree

4 files changed

+130
-21
lines changed

4 files changed

+130
-21
lines changed

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

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import com.google.common.collect.Interner;
2929
import com.google.common.collect.Iterables;
3030
import com.google.common.util.concurrent.ListenableFuture;
31+
import com.google.common.collect.Sets;
3132
import com.google.devtools.build.lib.actions.AbstractAction;
3233
import com.google.devtools.build.lib.actions.ActionContinuationOrResult;
3334
import com.google.devtools.build.lib.actions.ActionEnvironment;
@@ -683,7 +684,6 @@ public static class Builder {
683684
private final List<Artifact> outputs = new ArrayList<>();
684685
private final List<RunfilesSupplier> inputRunfilesSuppliers = new ArrayList<>();
685686
private ResourceSetOrBuilder resourceSetOrBuilder = AbstractAction.DEFAULT_RESOURCE_SET;
686-
private ActionEnvironment actionEnvironment = null;
687687
private ImmutableMap<String, String> environment = ImmutableMap.of();
688688
private ImmutableSet<String> inheritedEnvironment = ImmutableSet.of();
689689
private ImmutableMap<String, String> executionInfo = ImmutableMap.of();
@@ -717,7 +717,6 @@ public Builder(Builder other) {
717717
this.outputs.addAll(other.outputs);
718718
this.inputRunfilesSuppliers.addAll(other.inputRunfilesSuppliers);
719719
this.resourceSetOrBuilder = other.resourceSetOrBuilder;
720-
this.actionEnvironment = other.actionEnvironment;
721720
this.environment = other.environment;
722721
this.executionInfo = other.executionInfo;
723722
this.isShellCommand = other.isShellCommand;
@@ -762,12 +761,31 @@ public SpawnAction build(ActionOwner owner, BuildConfigurationValue configuratio
762761
result.addCommandLine(pair);
763762
}
764763
CommandLines commandLines = result.build();
765-
ActionEnvironment env =
766-
actionEnvironment != null
767-
? actionEnvironment
768-
: useDefaultShellEnvironment
769-
? configuration.getActionEnvironment()
770-
: ActionEnvironment.create(environment, inheritedEnvironment);
764+
ActionEnvironment env;
765+
if (useDefaultShellEnvironment && environment != null) {
766+
// Inherited variables override fixed variables in ActionEnvironment. Since we want the
767+
// fixed part of the action-provided environment to override the inherited part of the
768+
// user-provided environment, we have to explicitly filter the inherited part.
769+
var userFilteredInheritedEnv =
770+
ImmutableSet.copyOf(
771+
Sets.difference(
772+
configuration.getActionEnvironment().getInheritedEnv(), environment.keySet()));
773+
// Do not create a new ActionEnvironment in the common case where no vars have been filtered
774+
// out.
775+
if (userFilteredInheritedEnv.equals(
776+
configuration.getActionEnvironment().getInheritedEnv())) {
777+
env = configuration.getActionEnvironment();
778+
} else {
779+
env =
780+
ActionEnvironment.create(
781+
configuration.getActionEnvironment().getFixedEnv(), userFilteredInheritedEnv);
782+
}
783+
env = env.withAdditionalFixedVariables(environment);
784+
} else if (useDefaultShellEnvironment) {
785+
env = configuration.getActionEnvironment();
786+
} else {
787+
env = ActionEnvironment.create(environment, inheritedEnvironment);
788+
}
771789
return buildSpawnAction(
772790
owner, commandLines, configuration.getCommandLineLimits(), configuration, env);
773791
}
@@ -984,13 +1002,6 @@ public Builder setResources(ResourceSetOrBuilder resourceSetOrBuilder) {
9841002
return this;
9851003
}
9861004

987-
/** Sets the action environment. */
988-
@CanIgnoreReturnValue
989-
public Builder setEnvironment(ActionEnvironment actionEnvironment) {
990-
this.actionEnvironment = actionEnvironment;
991-
return this;
992-
}
993-
9941005
/**
9951006
* Sets the map of environment variables. Do not use! This makes the builder ignore the 'default
9961007
* shell environment', which is computed from the --action_env command line option.
@@ -1075,6 +1086,18 @@ public Builder executeUnconditionally() {
10751086
return this;
10761087
}
10771088

1089+
/**
1090+
* Same as {@link #useDefaultShellEnvironment()}, but additionally sets the provided fixed
1091+
* environment variables, which take precedence over the variables contained in the default
1092+
* shell environment.
1093+
*/
1094+
@CanIgnoreReturnValue
1095+
public Builder useDefaultShellEnvironmentWithOverrides(Map<String, String> environment) {
1096+
this.environment = ImmutableMap.copyOf(environment);
1097+
this.useDefaultShellEnvironment = true;
1098+
return this;
1099+
}
1100+
10781101
/**
10791102
* Sets the executable path; the path is interpreted relative to the execution root, unless it's
10801103
* a bare file name.

src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkActionFactory.java

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -671,15 +671,24 @@ private void registerStarlarkAction(
671671
} catch (IllegalArgumentException e) {
672672
throw Starlark.errorf("%s", e.getMessage());
673673
}
674-
if (envUnchecked != Starlark.NONE) {
675-
builder.setEnvironment(
676-
ImmutableMap.copyOf(Dict.cast(envUnchecked, String.class, String.class, "env")));
677-
}
678674
if (progressMessage != Starlark.NONE) {
679675
builder.setProgressMessageFromStarlark((String) progressMessage);
680676
}
677+
678+
ImmutableMap<String, String> env = null;
679+
if (envUnchecked != Starlark.NONE) {
680+
env = ImmutableMap.copyOf(Dict.cast(envUnchecked, String.class, String.class, "env"));
681+
}
681682
if (Starlark.truth(useDefaultShellEnv)) {
682-
builder.useDefaultShellEnvironment();
683+
if (env != null
684+
&& getSemantics()
685+
.getBool(BuildLanguageOptions.INCOMPATIBLE_MERGE_FIXED_AND_DEFAULT_SHELL_ENV)) {
686+
builder.useDefaultShellEnvironmentWithOverrides(env);
687+
} else {
688+
builder.useDefaultShellEnvironment();
689+
}
690+
} else if (env != null) {
691+
builder.setEnvironment(env);
683692
}
684693

685694
RuleContext ruleContext = getRuleContext();

src/main/java/com/google/devtools/build/lib/packages/semantics/BuildLanguageOptions.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,19 @@ public final class BuildLanguageOptions extends OptionsBase {
662662
help = "If enabled, targets that have unknown attributes set to None fail.")
663663
public boolean incompatibleFailOnUnknownAttributes;
664664

665+
@Option(
666+
name = "incompatible_merge_fixed_and_default_shell_env",
667+
defaultValue = "false",
668+
documentationCategory = OptionDocumentationCategory.STARLARK_SEMANTICS,
669+
effectTags = {OptionEffectTag.LOADING_AND_ANALYSIS},
670+
metadataTags = {OptionMetadataTag.INCOMPATIBLE_CHANGE},
671+
help =
672+
"If enabled, actions registered with ctx.actions.run and ctx.actions.run_shell with both"
673+
+ " 'env' and 'use_default_shell_env = True' specified will use an environment"
674+
+ " obtained from the default shell environment by overriding with the values passed"
675+
+ " in to 'env'. If disabled, the value of 'env' is completely ignored in this case.")
676+
public boolean incompatibleMergeFixedAndDefaultShellEnv;
677+
665678
@Option(
666679
name = "incompatible_disable_objc_library_transition",
667680
defaultValue = "false",
@@ -765,6 +778,9 @@ public StarlarkSemantics toStarlarkSemantics() {
765778
EXPERIMENTAL_GET_FIXED_CONFIGURED_ACTION_ENV,
766779
experimentalGetFixedConfiguredEnvironment)
767780
.setBool(INCOMPATIBLE_FAIL_ON_UNKNOWN_ATTRIBUTES, incompatibleFailOnUnknownAttributes)
781+
.setBool(
782+
INCOMPATIBLE_MERGE_FIXED_AND_DEFAULT_SHELL_ENV,
783+
incompatibleMergeFixedAndDefaultShellEnv)
768784
.setBool(
769785
INCOMPATIBLE_DISABLE_OBJC_LIBRARY_TRANSITION,
770786
incompatibleDisableObjcLibraryTransition)
@@ -858,6 +874,8 @@ public StarlarkSemantics toStarlarkSemantics() {
858874
"-experimental_get_fixed_configured_action_env";
859875
public static final String INCOMPATIBLE_FAIL_ON_UNKNOWN_ATTRIBUTES =
860876
"-incompatible_fail_on_unknown_attributes";
877+
public static final String INCOMPATIBLE_MERGE_FIXED_AND_DEFAULT_SHELL_ENV =
878+
"-experimental_merge_fixed_and_default_shell_env";
861879
public static final String INCOMPATIBLE_DISABLE_OBJC_LIBRARY_TRANSITION =
862880
"-incompatible_disable_objc_library_transition";
863881

src/test/shell/integration/action_env_test.sh

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,15 @@ load("//pkg:build.bzl", "environ")
3939
4040
environ(name = "no_default_env", env = 0)
4141
environ(name = "with_default_env", env = 1)
42+
environ(
43+
name = "with_default_and_fixed_env",
44+
env = 1,
45+
fixed_env = {
46+
"ACTION_FIXED": "action",
47+
"ACTION_AND_CLIENT_FIXED": "action",
48+
"ACTION_AND_CLIENT_INHERITED": "action",
49+
},
50+
)
4251
4352
sh_test(
4453
name = "test_env_foo",
@@ -72,12 +81,16 @@ def _impl(ctx):
7281
ctx.actions.run_shell(
7382
inputs=[],
7483
outputs=[output],
84+
env = ctx.attr.fixed_env,
7585
use_default_shell_env = ctx.attr.env,
7686
command="env > %s" % output.path)
7787
7888
environ = rule(
7989
implementation=_impl,
80-
attrs={"env": attr.bool(default=True)},
90+
attrs={
91+
"env": attr.bool(default=True),
92+
"fixed_env": attr.string_dict(),
93+
},
8194
outputs={"out": "%{name}.env"},
8295
)
8396
EOF
@@ -222,6 +235,52 @@ function test_use_default_shell_env {
222235
&& fail "dynamic action_env used, even though requested not to") || true
223236
}
224237

238+
function test_use_default_shell_env_and_fixed_env {
239+
ACTION_AND_CLIENT_INHERITED=client CLIENT_INHERITED=client \
240+
bazel build \
241+
--noincompatible_merge_fixed_and_default_shell_env \
242+
--action_env=ACTION_AND_CLIENT_FIXED=client \
243+
--action_env=ACTION_AND_CLIENT_INHERITED \
244+
--action_env=CLIENT_FIXED=client \
245+
--action_env=CLIENT_INHERITED \
246+
//pkg:with_default_and_fixed_env
247+
echo
248+
cat bazel-bin/pkg/with_default_and_fixed_env.env
249+
echo
250+
grep -q ACTION_AND_CLIENT_FIXED=client bazel-bin/pkg/with_default_and_fixed_env.env \
251+
|| fail "static action environment not honored"
252+
grep -q ACTION_AND_CLIENT_INHERITED=client bazel-bin/pkg/with_default_and_fixed_env.env \
253+
|| fail "dynamic action environment not honored"
254+
grep -q ACTION_FIXED bazel-bin/pkg/with_default_and_fixed_env.env \
255+
&& fail "fixed env provided by action should have been ignored"
256+
grep -q CLIENT_FIXED=client bazel-bin/pkg/with_default_and_fixed_env.env \
257+
|| fail "static action environment not honored"
258+
grep -q CLIENT_INHERITED=client bazel-bin/pkg/with_default_and_fixed_env.env \
259+
|| fail "dynamic action environment not honored"
260+
261+
ACTION_AND_CLIENT_INHERITED=client CLIENT_INHERITED=client \
262+
bazel build \
263+
--incompatible_merge_fixed_and_default_shell_env \
264+
--action_env=ACTION_AND_CLIENT_FIXED=client \
265+
--action_env=ACTION_AND_CLIENT_INHERITED \
266+
--action_env=CLIENT_FIXED=client \
267+
--action_env=CLIENT_INHERITED \
268+
//pkg:with_default_and_fixed_env
269+
echo
270+
cat bazel-bin/pkg/with_default_and_fixed_env.env
271+
echo
272+
grep -q ACTION_AND_CLIENT_FIXED=action bazel-bin/pkg/with_default_and_fixed_env.env \
273+
|| fail "action-provided env should have overridden static --action_env"
274+
grep -q ACTION_AND_CLIENT_INHERITED=action bazel-bin/pkg/with_default_and_fixed_env.env \
275+
|| fail "action-provided env should have overridden dynamic --action_env"
276+
grep -q ACTION_FIXED=action bazel-bin/pkg/with_default_and_fixed_env.env \
277+
|| fail "action-provided env should have been honored"
278+
grep -q CLIENT_FIXED=client bazel-bin/pkg/with_default_and_fixed_env.env \
279+
|| fail "static action environment not honored"
280+
grep -q CLIENT_INHERITED=client bazel-bin/pkg/with_default_and_fixed_env.env \
281+
|| fail "dynamic action environment not honored"
282+
}
283+
225284
function test_action_env_changes_honored {
226285
# Verify that changes to the explicitly specified action_env in honored in
227286
# tests. Regression test for #3265.

0 commit comments

Comments
 (0)