Skip to content

Commit 8a87064

Browse files
committed
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
1 parent f75d14b commit 8a87064

File tree

4 files changed

+129
-10
lines changed

4 files changed

+129
-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

@@ -879,6 +901,18 @@ public Builder useDefaultShellEnvironment() {
879901
return this;
880902
}
881903

904+
/**
905+
* Same as {@link #useDefaultShellEnvironment()}, but additionally sets the provided fixed
906+
* environment variables, which take precedence over the variables contained in the default
907+
* shell environment.
908+
*/
909+
@CanIgnoreReturnValue
910+
public Builder useDefaultShellEnvironmentWithOverrides(Map<String, String> environment) {
911+
this.environment = ImmutableMap.copyOf(environment);
912+
this.useDefaultShellEnvironment = true;
913+
return this;
914+
}
915+
882916
/**
883917
* Sets the executable path; the path is interpreted relative to the execution root, unless it's
884918
* 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
@@ -49,6 +49,7 @@
4949
import com.google.devtools.build.lib.analysis.actions.ParameterFileWriteAction;
5050
import com.google.devtools.build.lib.analysis.actions.SpawnAction;
5151
import com.google.devtools.build.lib.analysis.actions.StarlarkAction;
52+
import com.google.devtools.build.lib.analysis.actions.StarlarkAction.Builder;
5253
import com.google.devtools.build.lib.analysis.actions.Substitution;
5354
import com.google.devtools.build.lib.analysis.actions.SymlinkAction;
5455
import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction;
@@ -763,15 +764,23 @@ private void registerStarlarkAction(
763764
} catch (IllegalArgumentException e) {
764765
throw Starlark.errorf("%s", e.getMessage());
765766
}
766-
if (envUnchecked != Starlark.NONE) {
767-
builder.setEnvironment(
768-
ImmutableMap.copyOf(Dict.cast(envUnchecked, String.class, String.class, "env")));
769-
}
770767
if (progressMessage != Starlark.NONE) {
771768
builder.setProgressMessageFromStarlark((String) progressMessage);
772769
}
770+
771+
ImmutableMap<String, String> env = null;
772+
if (envUnchecked != Starlark.NONE) {
773+
env = ImmutableMap.copyOf(Dict.cast(envUnchecked, String.class, String.class, "env"));
774+
}
773775
if (Starlark.truth(useDefaultShellEnv)) {
774-
builder.useDefaultShellEnvironment();
776+
if (env != null && getSemantics().getBool(
777+
BuildLanguageOptions.INCOMPATIBLE_MERGE_FIXED_AND_DEFAULT_SHELL_ENV)) {
778+
builder.useDefaultShellEnvironmentWithOverrides(env);
779+
} else {
780+
builder.useDefaultShellEnvironment();
781+
}
782+
} else if (env != null) {
783+
builder.setEnvironment(env);
775784
}
776785

777786
ImmutableMap<String, String> executionInfo =

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,18 @@ public final class BuildLanguageOptions extends OptionsBase {
658658
+ " specified through features configuration.")
659659
public boolean experimentalGetFixedConfiguredEnvironment;
660660

661+
@Option(
662+
name = "incompatible_merge_fixed_and_default_shell_env",
663+
defaultValue = "false",
664+
documentationCategory = OptionDocumentationCategory.STARLARK_SEMANTICS,
665+
effectTags = {OptionEffectTag.LOADING_AND_ANALYSIS},
666+
metadataTags = {OptionMetadataTag.INCOMPATIBLE_CHANGE},
667+
help = "If enabled, actions registered with ctx.actions.run and ctx.actions.run_shell with"
668+
+ " both 'env' and 'use_default_shell_env = True' specified will use an environment"
669+
+ " obtained from the default shell environment by overriding with the values passed in"
670+
+ " to 'env'. If disabled, the value of 'env' is completely ignored in this case.")
671+
public boolean incompatibleMergeFixedAndDefaultShellEnv;
672+
661673
/**
662674
* An interner to reduce the number of StarlarkSemantics instances. A single Blaze instance should
663675
* never accumulate a large number of these and being able to shortcut on object identity makes a
@@ -750,6 +762,9 @@ public StarlarkSemantics toStarlarkSemantics() {
750762
.setBool(
751763
EXPERIMENTAL_GET_FIXED_CONFIGURED_ACTION_ENV,
752764
experimentalGetFixedConfiguredEnvironment)
765+
.setBool(
766+
INCOMPATIBLE_MERGE_FIXED_AND_DEFAULT_SHELL_ENV,
767+
incompatibleMergeFixedAndDefaultShellEnv)
753768
.build();
754769
return INTERNER.intern(semantics);
755770
}
@@ -838,6 +853,8 @@ public StarlarkSemantics toStarlarkSemantics() {
838853
"-incompatible_disable_starlark_host_transitions";
839854
public static final String EXPERIMENTAL_GET_FIXED_CONFIGURED_ACTION_ENV =
840855
"-experimental_get_fixed_configured_action_env";
856+
public static final String INCOMPATIBLE_MERGE_FIXED_AND_DEFAULT_SHELL_ENV =
857+
"-experimental_merge_fixed_and_default_shell_env";
841858

842859
// non-booleans
843860
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)