Skip to content

Commit eeb2e04

Browse files
susinmotioncopybara-github
authored andcommitted
Decide which sh path to used based on the platform the action will be executed on, instead of the host platform.
This makes it possible to execute sh actions on platforms other than the host platform. Note that --shell_executable (Bazel only) now only affects actions configured for host. RELNOTES: Bazel now selects sh path based on execution platform instead of host platform, making it possible to execute sh actions in multiplatform builds. --shell_executable now only applies to actions configured for host. PiperOrigin-RevId: 453946322 Change-Id: Ibc811939f20c5fac7789d631f36933da6e8bcb35
1 parent 442155f commit eeb2e04

File tree

16 files changed

+223
-124
lines changed

16 files changed

+223
-124
lines changed

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

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,48 +14,58 @@
1414

1515
package com.google.devtools.build.lib.analysis;
1616

17+
import com.google.common.base.Preconditions;
1718
import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue;
19+
import com.google.devtools.build.lib.analysis.platform.PlatformInfo;
20+
import com.google.devtools.build.lib.util.OS;
1821
import com.google.devtools.build.lib.vfs.PathFragment;
1922

2023
/** Class to work with the shell toolchain, e.g. get the shell interpreter's path. */
2124
public final class ShToolchain {
2225

26+
private static PathFragment getHostOrDefaultPath() {
27+
OS current = OS.getCurrent();
28+
if (!ShellConfiguration.getShellExecutables().containsKey(current)) {
29+
current = OS.UNKNOWN;
30+
}
31+
Preconditions.checkState(
32+
ShellConfiguration.getShellExecutables().containsKey(current),
33+
"shellExecutableFinder should set a value with key '%s'",
34+
current);
35+
36+
return ShellConfiguration.getShellExecutables().get(current);
37+
}
38+
2339
/**
24-
* Returns the shell executable's path, or an empty path if not set.
40+
* Returns the default shell executable's path for the host OS.
2541
*
2642
* <p>This method checks the configuration's {@link ShellConfiguration} fragment.
2743
*/
28-
public static PathFragment getPath(BuildConfigurationValue config) {
29-
PathFragment result = PathFragment.EMPTY_FRAGMENT;
30-
31-
ShellConfiguration configFragment =
32-
(ShellConfiguration) config.getFragment(ShellConfiguration.class);
44+
public static PathFragment getPathForHost(BuildConfigurationValue config) {
45+
ShellConfiguration configFragment = config.getFragment(ShellConfiguration.class);
3346
if (configFragment != null) {
34-
PathFragment path = configFragment.getShellExecutable();
35-
if (path != null) {
36-
result = path;
47+
if (configFragment.getOptionsBasedDefault() != null) {
48+
return configFragment.getOptionsBasedDefault();
49+
} else {
50+
return getHostOrDefaultPath();
3751
}
3852
}
39-
40-
return result;
53+
return PathFragment.EMPTY_FRAGMENT;
4154
}
4255

4356
/**
44-
* Returns the shell executable's path, or reports a rule error if the path is empty.
45-
*
46-
* <p>This method checks the rule's configuration's {@link ShellConfiguration} fragment for the
47-
* shell executable's path. If null or empty, the method reports an error against the rule.
57+
* Returns the shell executable's path for the provided platform. If none is present, return the
58+
* path for the host platform. Otherwise, return the default.
4859
*/
49-
public static PathFragment getPathOrError(RuleContext ctx) {
50-
PathFragment result = getPath(ctx.getConfiguration());
51-
52-
if (result.isEmpty()) {
53-
ctx.ruleError(
54-
"This rule needs a shell interpreter. Use the --shell_executable=<path> flag to specify"
55-
+ " the interpreter's path, e.g. --shell_executable=/usr/local/bin/bash");
60+
public static PathFragment getPathOrError(PlatformInfo platformInfo) {
61+
for (OS os : ShellConfiguration.getShellExecutables().keySet()) {
62+
if (platformInfo
63+
.constraints()
64+
.hasConstraintValue(ShellConfiguration.OS_TO_CONSTRAINTS.get(os))) {
65+
return ShellConfiguration.getShellExecutables().get(os);
66+
}
5667
}
57-
58-
return result;
68+
return getHostOrDefaultPath();
5969
}
6070

6171
private ShToolchain() {}

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

Lines changed: 67 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -18,49 +18,100 @@
1818
import com.google.devtools.build.lib.analysis.config.Fragment;
1919
import com.google.devtools.build.lib.analysis.config.FragmentOptions;
2020
import com.google.devtools.build.lib.analysis.config.RequiresOptions;
21+
import com.google.devtools.build.lib.analysis.platform.ConstraintSettingInfo;
22+
import com.google.devtools.build.lib.analysis.platform.ConstraintValueInfo;
23+
import com.google.devtools.build.lib.cmdline.Label;
2124
import com.google.devtools.build.lib.util.OS;
2225
import com.google.devtools.build.lib.util.OptionsUtils.PathFragmentConverter;
2326
import com.google.devtools.build.lib.vfs.PathFragment;
2427
import com.google.devtools.common.options.Option;
2528
import com.google.devtools.common.options.OptionDocumentationCategory;
2629
import com.google.devtools.common.options.OptionEffectTag;
2730
import com.google.devtools.common.options.OptionMetadataTag;
31+
import java.util.Map;
2832
import java.util.function.Function;
2933

3034
/** A configuration fragment that tells where the shell is. */
3135
@RequiresOptions(options = {ShellConfiguration.Options.class})
3236
public class ShellConfiguration extends Fragment {
33-
private static final ImmutableMap<OS, PathFragment> OS_SPECIFIC_SHELL =
34-
ImmutableMap.<OS, PathFragment>builder()
35-
.put(OS.WINDOWS, PathFragment.create("c:/tools/msys64/usr/bin/bash.exe"))
36-
.put(OS.FREEBSD, PathFragment.create("/usr/local/bin/bash"))
37-
.put(OS.OPENBSD, PathFragment.create("/usr/local/bin/bash"))
38-
.buildOrThrow();
3937

40-
private static Function<BuildOptions, PathFragment> shellExecutableFinder;
38+
private static Map<OS, PathFragment> shellExecutables;
39+
40+
private static final ConstraintSettingInfo OS_CONSTRAINT_SETTING =
41+
ConstraintSettingInfo.create(
42+
Label.parseAbsoluteUnchecked("@platforms//os:os"));
43+
44+
private static Function<Options, PathFragment> optionsBasedDefault;
4145

4246
/**
43-
* Injects a function for finding the shell executable path, given the current configuration's
44-
* {@link BuildOptions} and whatever system-specific logic the provider wishes to use.
47+
* Injects a function for retrieving the default sh path from build options, and a map for
48+
* locating the correct sh executable given a set of target constraints.
4549
*/
46-
public static void injectShellExecutableFinder(Function<BuildOptions, PathFragment> finder) {
50+
public static void injectShellExecutableFinder(
51+
Function<Options, PathFragment> shellFromOptionsFinder, Map<OS, PathFragment> osToShellMap) {
4752
// It'd be nice not to have to set a global static field. But there are so many disparate calls
48-
// to getShellExecutable() (in both the build's analysis phase and in the run command) that
53+
// to getShellExecutables() (in both the build's analysis phase and in the run command) that
4954
// feeding this through instance variables is unwieldy. Fortunately this info is a function of
5055
// the Blaze implementation and not something that might change between builds.
51-
shellExecutableFinder = finder;
56+
optionsBasedDefault = shellFromOptionsFinder;
57+
shellExecutables = osToShellMap;
58+
}
59+
60+
/**
61+
* Injects a map for locating the correct sh executable given a set of target constraints. Assumes
62+
* no options-based default shell.
63+
*/
64+
public static void injectShellExecutableFinder(Map<OS, PathFragment> osToShellMap) {
65+
optionsBasedDefault = (options) -> null;
66+
shellExecutables = osToShellMap;
5267
}
68+
// Standard mapping between OS and the corresponding platform constraints.
69+
static final ImmutableMap<OS, ConstraintValueInfo> OS_TO_CONSTRAINTS =
70+
ImmutableMap.<OS, ConstraintValueInfo>builder()
71+
.put(
72+
OS.DARWIN,
73+
ConstraintValueInfo.create(
74+
OS_CONSTRAINT_SETTING,
75+
Label.parseAbsoluteUnchecked("@platforms//os:osx")))
76+
.put(
77+
OS.WINDOWS,
78+
ConstraintValueInfo.create(
79+
OS_CONSTRAINT_SETTING,
80+
Label.parseAbsoluteUnchecked("@platforms//os:windows")))
81+
.put(
82+
OS.FREEBSD,
83+
ConstraintValueInfo.create(
84+
OS_CONSTRAINT_SETTING,
85+
Label.parseAbsoluteUnchecked("@platforms//os:freebsd")))
86+
.put(
87+
OS.OPENBSD,
88+
ConstraintValueInfo.create(
89+
OS_CONSTRAINT_SETTING,
90+
Label.parseAbsoluteUnchecked("@platforms//os:openbsd")))
91+
.put(
92+
OS.UNKNOWN,
93+
ConstraintValueInfo.create(
94+
OS_CONSTRAINT_SETTING,
95+
Label.parseAbsoluteUnchecked("@platforms//os:none")))
96+
.buildOrThrow();
5397

54-
private final PathFragment shellExecutable;
5598
private final boolean useShBinaryStubScript;
5699

100+
private final PathFragment defaultShellExecutableFromOptions;
101+
57102
public ShellConfiguration(BuildOptions buildOptions) {
58-
this.shellExecutable = shellExecutableFinder.apply(buildOptions);
103+
this.defaultShellExecutableFromOptions =
104+
optionsBasedDefault.apply(buildOptions.get(Options.class));
59105
this.useShBinaryStubScript = buildOptions.get(Options.class).useShBinaryStubScript;
60106
}
61107

62-
public PathFragment getShellExecutable() {
63-
return shellExecutable;
108+
public static Map<OS, PathFragment> getShellExecutables() {
109+
return shellExecutables;
110+
}
111+
112+
/* Returns a function for retrieving the default shell from build options. */
113+
public PathFragment getOptionsBasedDefault() {
114+
return defaultShellExecutableFromOptions;
64115
}
65116

66117
public boolean useShBinaryStubScript() {
@@ -103,22 +154,4 @@ public Options getHost() {
103154
return host;
104155
}
105156
}
106-
107-
public static PathFragment determineShellExecutable(
108-
OS os, Options options, PathFragment defaultShell) {
109-
if (options.shellExecutable != null) {
110-
return options.shellExecutable;
111-
}
112-
113-
// Honor BAZEL_SH env variable for backwards compatibility.
114-
String path = System.getenv("BAZEL_SH");
115-
if (path != null) {
116-
return PathFragment.create(path);
117-
}
118-
// TODO(ulfjack): instead of using the OS Bazel runs on, we need to use the exec platform,
119-
// which may be different for remote execution. For now, this can be overridden with
120-
// --shell_executable, so at least there's a workaround.
121-
PathFragment result = OS_SPECIFIC_SHELL.get(os);
122-
return result != null ? result : defaultShell;
123-
}
124157
}

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

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,13 @@
4848
import com.google.devtools.build.lib.analysis.actions.Substitution;
4949
import com.google.devtools.build.lib.analysis.actions.SymlinkAction;
5050
import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction;
51+
import com.google.devtools.build.lib.analysis.platform.PlatformInfo;
5152
import com.google.devtools.build.lib.collect.nestedset.Depset;
5253
import com.google.devtools.build.lib.collect.nestedset.Depset.TypeException;
5354
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
5455
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
5556
import com.google.devtools.build.lib.collect.nestedset.Order;
57+
import com.google.devtools.build.lib.packages.ExecGroup;
5658
import com.google.devtools.build.lib.packages.TargetUtils;
5759
import com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions;
5860
import com.google.devtools.build.lib.server.FailureDetails;
@@ -440,6 +442,24 @@ private StarlarkSemantics getSemantics() {
440442
return context.getStarlarkSemantics();
441443
}
442444

445+
private void verifyExecGroup(Object execGroupUnchecked, RuleContext ctx) throws EvalException {
446+
String execGroup = (String) execGroupUnchecked;
447+
if (!StarlarkExecGroupCollection.isValidGroupName(execGroup)
448+
|| !ctx.hasToolchainContext(execGroup)) {
449+
throw Starlark.errorf("Action declared for non-existent exec group '%s'.", execGroup);
450+
}
451+
}
452+
453+
private PlatformInfo getExecutionPlatform(Object execGroupUnchecked, RuleContext ctx)
454+
throws EvalException {
455+
if (execGroupUnchecked == Starlark.NONE) {
456+
return ctx.getExecutionPlatform(ExecGroup.DEFAULT_EXEC_GROUP_NAME);
457+
} else {
458+
verifyExecGroup(execGroupUnchecked, ctx);
459+
return ctx.getExecutionPlatform((String) execGroupUnchecked);
460+
}
461+
}
462+
443463
@Override
444464
public void runShell(
445465
Sequence<?> outputs,
@@ -464,11 +484,12 @@ public void runShell(
464484
buildCommandLine(builder, arguments);
465485

466486
if (commandUnchecked instanceof String) {
467-
Map<String, String> executionInfo =
487+
ImmutableMap<String, String> executionInfo =
468488
ImmutableMap.copyOf(TargetUtils.getExecutionInfo(ruleContext.getRule()));
469489
String helperScriptSuffix = String.format(".run_shell_%d.sh", runShellOutputCounter++);
470490
String command = (String) commandUnchecked;
471-
PathFragment shExecutable = ShToolchain.getPathOrError(ruleContext);
491+
PathFragment shExecutable =
492+
ShToolchain.getPathOrError(getExecutionPlatform(execGroupUnchecked, ruleContext));
472493
BashCommandConstructor constructor =
473494
CommandHelper.buildBashCommandConstructor(
474495
executionInfo, shExecutable, helperScriptSuffix);
@@ -662,13 +683,11 @@ private void registerStarlarkAction(
662683
}
663684
}
664685

665-
if (execGroupUnchecked != Starlark.NONE) {
666-
String execGroup = (String) execGroupUnchecked;
667-
if (!StarlarkExecGroupCollection.isValidGroupName(execGroup)
668-
|| !ruleContext.hasToolchainContext(execGroup)) {
669-
throw Starlark.errorf("Action declared for non-existent exec group '%s'.", execGroup);
670-
}
671-
builder.setExecGroup(execGroup);
686+
if (execGroupUnchecked == Starlark.NONE) {
687+
builder.setExecGroup(ExecGroup.DEFAULT_EXEC_GROUP_NAME);
688+
} else {
689+
verifyExecGroup(execGroupUnchecked, ruleContext);
690+
builder.setExecGroup((String) execGroupUnchecked);
672691
}
673692

674693
if (shadowedActionUnchecked != Starlark.NONE) {

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1018,7 +1018,9 @@ public Tuple resolveCommand(
10181018
String.class,
10191019
String.class,
10201020
"execution_requirements"));
1021-
PathFragment shExecutable = ShToolchain.getPathOrError(ruleContext);
1021+
// TODO(b/234923262): Take exec_group into consideration instead of using the default
1022+
// exec_group.
1023+
PathFragment shExecutable = ShToolchain.getPathOrError(ruleContext.getExecutionPlatform());
10221024

10231025
BashCommandConstructor constructor =
10241026
CommandHelper.buildBashCommandConstructor(

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,7 @@ private TestParams createTestAction(int shards)
398398
&& testConfiguration.cancelConcurrentTests();
399399

400400
boolean splitCoveragePostProcessing = testConfiguration.splitCoveragePostProcessing();
401+
// TODO(b/234923262): Take exec_group into consideration when selecting sh tools
401402
TestRunnerAction testRunnerAction =
402403
new TestRunnerAction(
403404
getOwner(),
@@ -421,7 +422,7 @@ private TestParams createTestAction(int shards)
421422
ruleContext.getWorkspaceName(),
422423
(!isUsingTestWrapperInsteadOfTestSetupScript
423424
|| executionSettings.needsShell(isExecutedOnWindows))
424-
? ShToolchain.getPathOrError(ruleContext)
425+
? ShToolchain.getPathOrError(ruleContext.getExecutionPlatform())
425426
: null,
426427
cancelConcurrentTests,
427428
splitCoveragePostProcessing,

0 commit comments

Comments
 (0)