Skip to content

Commit 3a2c629

Browse files
comiusfmeum
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 bazelbuild#5980 Fixes bazelbuild#12049 Closes bazelbuild#18235. PiperOrigin-RevId: 559506535 Change-Id: I7ec6ae17b076bbca72fab8394f3a8b3e4f9ea9d8
1 parent 333e83f commit 3a2c629

File tree

4 files changed

+132
-12
lines changed

4 files changed

+132
-12
lines changed

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

Lines changed: 40 additions & 6 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;
@@ -762,12 +763,33 @@ public SpawnAction build(ActionOwner owner, BuildConfigurationValue configuratio
762763
result.addCommandLine(pair);
763764
}
764765
CommandLines commandLines = result.build();
765-
ActionEnvironment env =
766-
actionEnvironment != null
767-
? actionEnvironment
768-
: useDefaultShellEnvironment
769-
? configuration.getActionEnvironment()
770-
: ActionEnvironment.create(environment, inheritedEnvironment);
766+
ActionEnvironment env;
767+
if (actionEnvironment != null) {
768+
env = actionEnvironment;
769+
} else if (useDefaultShellEnvironment && environment != null) {
770+
// Inherited variables override fixed variables in ActionEnvironment. Since we want the
771+
// fixed part of the action-provided environment to override the inherited part of the
772+
// user-provided environment, we have to explicitly filter the inherited part.
773+
var userFilteredInheritedEnv =
774+
ImmutableSet.copyOf(
775+
Sets.difference(
776+
configuration.getActionEnvironment().getInheritedEnv(), environment.keySet()));
777+
// Do not create a new ActionEnvironment in the common case where no vars have been filtered
778+
// out.
779+
if (userFilteredInheritedEnv.equals(
780+
configuration.getActionEnvironment().getInheritedEnv())) {
781+
env = configuration.getActionEnvironment();
782+
} else {
783+
env =
784+
ActionEnvironment.create(
785+
configuration.getActionEnvironment().getFixedEnv(), userFilteredInheritedEnv);
786+
}
787+
env = env.withAdditionalFixedVariables(environment);
788+
} else if (useDefaultShellEnvironment) {
789+
env = configuration.getActionEnvironment();
790+
} else {
791+
env = ActionEnvironment.create(environment, inheritedEnvironment);
792+
}
771793
return buildSpawnAction(
772794
owner, commandLines, configuration.getCommandLineLimits(), configuration, env);
773795
}
@@ -1075,6 +1097,18 @@ public Builder executeUnconditionally() {
10751097
return this;
10761098
}
10771099

1100+
/**
1101+
* Same as {@link #useDefaultShellEnvironment()}, but additionally sets the provided fixed
1102+
* environment variables, which take precedence over the variables contained in the default
1103+
* shell environment.
1104+
*/
1105+
@CanIgnoreReturnValue
1106+
public Builder useDefaultShellEnvironmentWithOverrides(Map<String, String> environment) {
1107+
this.environment = ImmutableMap.copyOf(environment);
1108+
this.useDefaultShellEnvironment = true;
1109+
return this;
1110+
}
1111+
10781112
/**
10791113
* Sets the executable path; the path is interpreted relative to the execution root, unless it's
10801114
* 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
@@ -652,6 +652,19 @@ public final class BuildLanguageOptions extends OptionsBase {
652652
+ " specified through features configuration.")
653653
public boolean experimentalGetFixedConfiguredEnvironment;
654654

655+
@Option(
656+
name = "incompatible_merge_fixed_and_default_shell_env",
657+
defaultValue = "false",
658+
documentationCategory = OptionDocumentationCategory.STARLARK_SEMANTICS,
659+
effectTags = {OptionEffectTag.LOADING_AND_ANALYSIS},
660+
metadataTags = {OptionMetadataTag.INCOMPATIBLE_CHANGE},
661+
help =
662+
"If enabled, actions registered with ctx.actions.run and ctx.actions.run_shell with both"
663+
+ " 'env' and 'use_default_shell_env = True' specified will use an environment"
664+
+ " obtained from the default shell environment by overriding with the values passed"
665+
+ " in to 'env'. If disabled, the value of 'env' is completely ignored in this case.")
666+
public boolean incompatibleMergeFixedAndDefaultShellEnv;
667+
655668
/**
656669
* An interner to reduce the number of StarlarkSemantics instances. A single Blaze instance should
657670
* never accumulate a large number of these and being able to shortcut on object identity makes a
@@ -743,6 +756,9 @@ public StarlarkSemantics toStarlarkSemantics() {
743756
.setBool(
744757
EXPERIMENTAL_GET_FIXED_CONFIGURED_ACTION_ENV,
745758
experimentalGetFixedConfiguredEnvironment)
759+
.setBool(
760+
INCOMPATIBLE_MERGE_FIXED_AND_DEFAULT_SHELL_ENV,
761+
incompatibleMergeFixedAndDefaultShellEnv)
746762
.build();
747763
return INTERNER.intern(semantics);
748764
}
@@ -831,6 +847,8 @@ public StarlarkSemantics toStarlarkSemantics() {
831847
"-incompatible_disable_starlark_host_transitions";
832848
public static final String EXPERIMENTAL_GET_FIXED_CONFIGURED_ACTION_ENV =
833849
"-experimental_get_fixed_configured_action_env";
850+
public static final String INCOMPATIBLE_MERGE_FIXED_AND_DEFAULT_SHELL_ENV =
851+
"-experimental_merge_fixed_and_default_shell_env";
834852

835853
// non-booleans
836854
public static final StarlarkSemantics.Key<String> EXPERIMENTAL_BUILTINS_BZL_PATH =

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)