Skip to content

Commit d1fdc53

Browse files
comiuscopybara-github
authored andcommitted
Add --incompatible_merge_fixed_and_default_shell_env
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. PiperOrigin-RevId: 559506535 Change-Id: I7ec6ae17b076bbca72fab8394f3a8b3e4f9ea9d8
1 parent 1efe7ec commit d1fdc53

File tree

4 files changed

+130
-10
lines changed

4 files changed

+130
-10
lines changed

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

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.google.common.collect.ImmutableSortedMap;
3030
import com.google.common.collect.Interner;
3131
import com.google.common.collect.Iterables;
32+
import com.google.common.collect.Sets;
3233
import com.google.devtools.build.lib.actions.AbstractAction;
3334
import com.google.devtools.build.lib.actions.ActionEnvironment;
3435
import com.google.devtools.build.lib.actions.ActionExecutionContext;
@@ -649,10 +650,31 @@ public SpawnAction build(ActionOwner owner, BuildConfigurationValue configuratio
649650
result.addCommandLine(pair);
650651
}
651652
CommandLines commandLines = result.build();
652-
ActionEnvironment env =
653-
useDefaultShellEnvironment
654-
? configuration.getActionEnvironment()
655-
: ActionEnvironment.create(environment);
653+
ActionEnvironment env;
654+
if (useDefaultShellEnvironment && environment != null) {
655+
// Inherited variables override fixed variables in ActionEnvironment. Since we want the
656+
// fixed part of the action-provided environment to override the inherited part of the
657+
// user-provided environment, we have to explicitly filter the inherited part.
658+
var userFilteredInheritedEnv =
659+
ImmutableSet.copyOf(
660+
Sets.difference(
661+
configuration.getActionEnvironment().getInheritedEnv(), environment.keySet()));
662+
// Do not create a new ActionEnvironment in the common case where no vars have been filtered
663+
// out.
664+
if (userFilteredInheritedEnv.equals(
665+
configuration.getActionEnvironment().getInheritedEnv())) {
666+
env = configuration.getActionEnvironment();
667+
} else {
668+
env =
669+
ActionEnvironment.create(
670+
configuration.getActionEnvironment().getFixedEnv(), userFilteredInheritedEnv);
671+
}
672+
env = env.withAdditionalFixedVariables(environment);
673+
} else if (useDefaultShellEnvironment) {
674+
env = configuration.getActionEnvironment();
675+
} else {
676+
env = ActionEnvironment.create(environment);
677+
}
656678
return buildSpawnAction(owner, commandLines, configuration, env);
657679
}
658680

@@ -894,6 +916,18 @@ public Builder useDefaultShellEnvironment() {
894916
return this;
895917
}
896918

919+
/**
920+
* Same as {@link #useDefaultShellEnvironment()}, but additionally sets the provided fixed
921+
* environment variables, which take precedence over the variables contained in the default
922+
* shell environment.
923+
*/
924+
@CanIgnoreReturnValue
925+
public Builder useDefaultShellEnvironmentWithOverrides(Map<String, String> environment) {
926+
this.environment = ImmutableMap.copyOf(environment);
927+
this.useDefaultShellEnvironment = true;
928+
return this;
929+
}
930+
897931
/**
898932
* Sets the executable path; the path is interpreted relative to the execution root, unless it's
899933
* 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
@@ -777,15 +777,24 @@ private void registerStarlarkAction(
777777
} catch (IllegalArgumentException e) {
778778
throw Starlark.errorf("%s", e.getMessage());
779779
}
780-
if (envUnchecked != Starlark.NONE) {
781-
builder.setEnvironment(
782-
ImmutableMap.copyOf(Dict.cast(envUnchecked, String.class, String.class, "env")));
783-
}
784780
if (progressMessage != Starlark.NONE) {
785781
builder.setProgressMessageFromStarlark((String) progressMessage);
786782
}
783+
784+
ImmutableMap<String, String> env = null;
785+
if (envUnchecked != Starlark.NONE) {
786+
env = ImmutableMap.copyOf(Dict.cast(envUnchecked, String.class, String.class, "env"));
787+
}
787788
if (Starlark.truth(useDefaultShellEnv)) {
788-
builder.useDefaultShellEnvironment();
789+
if (env != null
790+
&& getSemantics()
791+
.getBool(BuildLanguageOptions.INCOMPATIBLE_MERGE_FIXED_AND_DEFAULT_SHELL_ENV)) {
792+
builder.useDefaultShellEnvironmentWithOverrides(env);
793+
} else {
794+
builder.useDefaultShellEnvironment();
795+
}
796+
} else if (env != null) {
797+
builder.setEnvironment(env);
789798
}
790799

791800
ImmutableMap<String, String> executionInfo =

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
@@ -679,6 +679,19 @@ public final class BuildLanguageOptions extends OptionsBase {
679679
+ " specified through features configuration.")
680680
public boolean experimentalGetFixedConfiguredEnvironment;
681681

682+
@Option(
683+
name = "incompatible_merge_fixed_and_default_shell_env",
684+
defaultValue = "false",
685+
documentationCategory = OptionDocumentationCategory.STARLARK_SEMANTICS,
686+
effectTags = {OptionEffectTag.LOADING_AND_ANALYSIS},
687+
metadataTags = {OptionMetadataTag.INCOMPATIBLE_CHANGE},
688+
help =
689+
"If enabled, actions registered with ctx.actions.run and ctx.actions.run_shell with both"
690+
+ " 'env' and 'use_default_shell_env = True' specified will use an environment"
691+
+ " obtained from the default shell environment by overriding with the values passed"
692+
+ " in to 'env'. If disabled, the value of 'env' is completely ignored in this case.")
693+
public boolean incompatibleMergeFixedAndDefaultShellEnv;
694+
682695
@Option(
683696
name = "incompatible_objc_provider_remove_linking_info",
684697
defaultValue = "false",
@@ -781,6 +794,9 @@ public StarlarkSemantics toStarlarkSemantics() {
781794
.setBool(
782795
EXPERIMENTAL_GET_FIXED_CONFIGURED_ACTION_ENV,
783796
experimentalGetFixedConfiguredEnvironment)
797+
.setBool(
798+
INCOMPATIBLE_MERGE_FIXED_AND_DEFAULT_SHELL_ENV,
799+
incompatibleMergeFixedAndDefaultShellEnv)
784800
.setBool(
785801
INCOMPATIBLE_OBJC_PROVIDER_REMOVE_LINKING_INFO,
786802
incompatibleObjcProviderRemoveLinkingInfo)
@@ -870,6 +886,8 @@ public StarlarkSemantics toStarlarkSemantics() {
870886
"-incompatible_disable_starlark_host_transitions";
871887
public static final String EXPERIMENTAL_GET_FIXED_CONFIGURED_ACTION_ENV =
872888
"-experimental_get_fixed_configured_action_env";
889+
public static final String INCOMPATIBLE_MERGE_FIXED_AND_DEFAULT_SHELL_ENV =
890+
"-experimental_merge_fixed_and_default_shell_env";
873891
public static final String INCOMPATIBLE_OBJC_PROVIDER_REMOVE_LINKING_INFO =
874892
"-incompatible_objc_provider_remove_linking_info";
875893

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",
@@ -65,12 +74,16 @@ def _impl(ctx):
6574
ctx.actions.run_shell(
6675
inputs=[],
6776
outputs=[output],
77+
env = ctx.attr.fixed_env,
6878
use_default_shell_env = ctx.attr.env,
6979
command="env > %s" % output.path)
7080
7181
environ = rule(
7282
implementation=_impl,
73-
attrs={"env": attr.bool(default=True)},
83+
attrs={
84+
"env": attr.bool(default=True),
85+
"fixed_env": attr.string_dict(),
86+
},
7487
outputs={"out": "%{name}.env"},
7588
)
7689
EOF
@@ -215,6 +228,52 @@ function test_use_default_shell_env {
215228
&& fail "dynamic action_env used, even though requested not to") || true
216229
}
217230

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

0 commit comments

Comments
 (0)