Skip to content

Commit 27786ba

Browse files
Filter spawn strategies by execution platform
1 parent 20cf927 commit 27786ba

File tree

14 files changed

+264
-21
lines changed

14 files changed

+264
-21
lines changed

src/main/java/com/google/devtools/build/lib/analysis/platform/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ java_library(
8585
name = "platform_utils",
8686
srcs = ["PlatformUtils.java"],
8787
deps = [
88+
":platform",
8889
"//src/main/java/com/google/devtools/build/lib/actions",
8990
"//src/main/java/com/google/devtools/build/lib/remote/options",
9091
"//src/main/protobuf:failure_details_java_proto",

src/main/java/com/google/devtools/build/lib/analysis/platform/PlatformUtils.java

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,29 @@ public static Platform getPlatformProto(Spawn spawn, @Nullable RemoteOptions rem
6767
return getPlatformProto(spawn, remoteOptions, ImmutableMap.of());
6868
}
6969

70+
private static boolean shouldProducePlatformProto(
71+
Spawn spawn, SortedMap<String, String> defaultExecProperties, Map<String, String> additionalProperties) {
72+
PlatformInfo executionPlatform = spawn.getExecutionPlatform();
73+
if (executionPlatform != null) {
74+
if (!executionPlatform.execProperties().isEmpty()) {
75+
return true;
76+
}
77+
if (!executionPlatform.remoteExecutionProperties().isEmpty()) {
78+
return true;
79+
}
80+
}
81+
if (!spawn.getCombinedExecProperties().isEmpty()) {
82+
return true;
83+
}
84+
if (!defaultExecProperties.isEmpty()) {
85+
return true;
86+
}
87+
if (!additionalProperties.isEmpty()) {
88+
return true;
89+
}
90+
return false;
91+
}
92+
7093
@Nullable
7194
public static Platform getPlatformProto(
7295
Spawn spawn, @Nullable RemoteOptions remoteOptions, Map<String, String> additionalProperties)
@@ -76,10 +99,8 @@ public static Platform getPlatformProto(
7699
? remoteOptions.getRemoteDefaultExecProperties()
77100
: ImmutableSortedMap.of();
78101

79-
if (spawn.getExecutionPlatform() == null
80-
&& spawn.getCombinedExecProperties().isEmpty()
81-
&& defaultExecProperties.isEmpty()
82-
&& additionalProperties.isEmpty()) {
102+
if (!shouldProducePlatformProto(spawn, defaultExecProperties, additionalProperties)) {
103+
// Execution platform is null or functionally empty
83104
return null;
84105
}
85106

src/main/java/com/google/devtools/build/lib/bazel/rules/BazelStrategyModule.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import com.google.devtools.build.lib.analysis.actions.FileWriteActionContext;
1919
import com.google.devtools.build.lib.analysis.actions.TemplateExpansionContext;
2020
import com.google.devtools.build.lib.buildtool.BuildRequest;
21+
import com.google.devtools.build.lib.cmdline.Label;
2122
import com.google.devtools.build.lib.exec.ExecutionOptions;
2223
import com.google.devtools.build.lib.exec.ModuleActionContextRegistry;
2324
import com.google.devtools.build.lib.exec.SpawnCache;
@@ -90,5 +91,9 @@ public void registerSpawnStrategies(
9091
for (Map.Entry<RegexFilter, List<String>> entry : options.strategyByRegexp) {
9192
registryBuilder.addDescriptionFilter(entry.getKey(), entry.getValue());
9293
}
94+
95+
for (Map.Entry<Label, List<String>> strategy : options.allowedStrategiesByExecPlatform) {
96+
registryBuilder.addExecPlatformFilter(strategy.getKey(), strategy.getValue());
97+
}
9398
}
9499
}

src/main/java/com/google/devtools/build/lib/exec/BUILD

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,9 @@ java_library(
9191
deps = [
9292
":regex_filter_assignment_converter",
9393
"//src/main/java/com/google/devtools/build/lib/actions",
94+
"//src/main/java/com/google/devtools/build/lib/analysis/config:core_option_converters",
9495
"//src/main/java/com/google/devtools/build/lib/analysis/config:per_label_options",
96+
"//src/main/java/com/google/devtools/build/lib/cmdline",
9597
"//src/main/java/com/google/devtools/build/lib/util",
9698
"//src/main/java/com/google/devtools/build/lib/util:cpu_resource_converter",
9799
"//src/main/java/com/google/devtools/build/lib/util:ram_resource_converter",
@@ -353,6 +355,7 @@ java_library(
353355
":remote_local_fallback_registry",
354356
":spawn_strategy_policy",
355357
"//src/main/java/com/google/devtools/build/lib/actions",
358+
"//src/main/java/com/google/devtools/build/lib/cmdline",
356359
"//src/main/java/com/google/devtools/build/lib/events",
357360
"//src/main/java/com/google/devtools/build/lib/util",
358361
"//src/main/java/com/google/devtools/build/lib/util:abrupt_exit_exception",

src/main/java/com/google/devtools/build/lib/exec/ExecutionOptions.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import com.google.devtools.build.lib.actions.ActionExecutionContext;
1818
import com.google.devtools.build.lib.actions.ActionExecutionContext.ShowSubcommands;
1919
import com.google.devtools.build.lib.analysis.config.PerLabelOptions;
20+
import com.google.devtools.build.lib.cmdline.Label;
21+
import com.google.devtools.build.lib.analysis.config.CoreOptionConverters.LabelConverter;
2022
import com.google.devtools.build.lib.util.CpuResourceConverter;
2123
import com.google.devtools.build.lib.util.OptionsUtils;
2224
import com.google.devtools.build.lib.util.RamResourceConverter;
@@ -25,6 +27,7 @@
2527
import com.google.devtools.build.lib.vfs.PathFragment;
2628
import com.google.devtools.common.options.BoolOrEnumConverter;
2729
import com.google.devtools.common.options.Converters;
30+
import com.google.devtools.common.options.Converters.AssignmentToListOfValuesConverter;
2831
import com.google.devtools.common.options.Converters.CommaSeparatedNonEmptyOptionListConverter;
2932
import com.google.devtools.common.options.EnumConverter;
3033
import com.google.devtools.common.options.Option;
@@ -118,6 +121,31 @@ public class ExecutionOptions extends OptionsBase {
118121
+ "the 'local' strategy, but reversing the order would run it with 'sandboxed'. ")
119122
public List<Map.Entry<RegexFilter, List<String>>> strategyByRegexp;
120123

124+
@Option(
125+
name = "allowed_strategies_by_exec_platform",
126+
allowMultiple = true,
127+
converter = LabelToStringListConverter.class,
128+
defaultValue = "null",
129+
documentationCategory = OptionDocumentationCategory.EXECUTION_STRATEGY,
130+
effectTags = {OptionEffectTag.EXECUTION},
131+
help =
132+
"""
133+
Filters spawn strategies by the execution platform without affecting order.
134+
For example:
135+
```
136+
common --spawn_strategy=remote,sandboxed,worker,local
137+
common --strategy=Genrule=local
138+
common --allowed_strategies_by_exec_platform=@platforms//host:host=local,sandboxed,worker
139+
common --allowed_strategies_by_exec_platform=//:linux_amd64=remote
140+
```
141+
With the above options;
142+
- Actions configured for the host platform will be given `remote,sandboxed,worker`.
143+
- Actions configured for the `//:linux_amd64` platform will be given `remote`.
144+
- Actions configured for the `//:linux_amd64` platform with mnemonic `Genrule` will be
145+
given no strategies and fail to spawn.
146+
""")
147+
public List<Map.Entry<Label, List<String>>> allowedStrategiesByExecPlatform;
148+
121149
@Option(
122150
name = "materialize_param_files",
123151
defaultValue = "false",
@@ -641,4 +669,16 @@ public ShowSubcommandsConverter() {
641669
ShowSubcommands.class, "subcommand option", ShowSubcommands.TRUE, ShowSubcommands.FALSE);
642670
}
643671
}
672+
673+
private static class LabelToStringListConverter extends AssignmentToListOfValuesConverter<Label, String> {
674+
675+
public LabelToStringListConverter() {
676+
super(new LabelConverter(), new Converters.StringConverter(), AllowEmptyKeys.NO);
677+
}
678+
679+
@Override
680+
public String getTypeDescription() {
681+
return "a '<Label>=value[,value]' assignment";
682+
}
683+
}
644684
}

src/main/java/com/google/devtools/build/lib/exec/RemoteLocalFallbackRegistry.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package com.google.devtools.build.lib.exec;
1616

1717
import com.google.devtools.build.lib.actions.ActionContext;
18+
import com.google.devtools.build.lib.actions.Spawn;
1819
import javax.annotation.Nullable;
1920

2021
/**
@@ -29,5 +30,5 @@ public interface RemoteLocalFallbackRegistry extends ActionContext {
2930
* @return remote fallback strategy or {@code null} if none was registered
3031
*/
3132
@Nullable
32-
AbstractSpawnStrategy getRemoteLocalFallbackStrategy();
33+
AbstractSpawnStrategy getRemoteLocalFallbackStrategy(Spawn spawn);
3334
}

src/main/java/com/google/devtools/build/lib/exec/SpawnStrategyRegistry.java

Lines changed: 101 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.google.common.collect.ImmutableCollection;
2323
import com.google.common.collect.ImmutableList;
2424
import com.google.common.collect.ImmutableListMultimap;
25+
import com.google.common.collect.ImmutableMap;
2526
import com.google.common.collect.ImmutableMultimap;
2627
import com.google.common.collect.LinkedListMultimap;
2728
import com.google.common.collect.ListMultimap;
@@ -34,6 +35,7 @@
3435
import com.google.devtools.build.lib.actions.SandboxedSpawnStrategy;
3536
import com.google.devtools.build.lib.actions.Spawn;
3637
import com.google.devtools.build.lib.actions.SpawnStrategy;
38+
import com.google.devtools.build.lib.cmdline.Label;
3739
import com.google.devtools.build.lib.events.Event;
3840
import com.google.devtools.build.lib.events.EventHandler;
3941
import com.google.devtools.build.lib.events.Reporter;
@@ -69,6 +71,7 @@ public final class SpawnStrategyRegistry
6971

7072
private final ImmutableListMultimap<String, SpawnStrategy> mnemonicToStrategies;
7173
private final StrategyRegexFilter strategyRegexFilter;
74+
private final StrategyPlatformFilter strategyPlatformFilter;
7275
private final ImmutableList<? extends SpawnStrategy> defaultStrategies;
7376
private final ImmutableMultimap<String, SandboxedSpawnStrategy> mnemonicToRemoteDynamicStrategies;
7477
private final ImmutableMultimap<String, SandboxedSpawnStrategy> mnemonicToLocalDynamicStrategies;
@@ -77,18 +80,21 @@ public final class SpawnStrategyRegistry
7780
private SpawnStrategyRegistry(
7881
ImmutableListMultimap<String, SpawnStrategy> mnemonicToStrategies,
7982
StrategyRegexFilter strategyRegexFilter,
83+
StrategyPlatformFilter strategyPlatformFilter,
8084
ImmutableList<? extends SpawnStrategy> defaultStrategies,
8185
ImmutableMultimap<String, SandboxedSpawnStrategy> mnemonicToRemoteDynamicStrategies,
8286
ImmutableMultimap<String, SandboxedSpawnStrategy> mnemonicToLocalDynamicStrategies,
8387
@Nullable AbstractSpawnStrategy remoteLocalFallbackStrategy) {
8488
this.mnemonicToStrategies = mnemonicToStrategies;
8589
this.strategyRegexFilter = strategyRegexFilter;
90+
this.strategyPlatformFilter = strategyPlatformFilter;
8691
this.defaultStrategies = defaultStrategies;
8792
this.mnemonicToRemoteDynamicStrategies = mnemonicToRemoteDynamicStrategies;
8893
this.mnemonicToLocalDynamicStrategies = mnemonicToLocalDynamicStrategies;
8994
this.remoteLocalFallbackStrategy = remoteLocalFallbackStrategy;
9095
logger.atInfo().log("Default strategies: %s", defaultStrategies);
91-
logger.atInfo().log("Filter strategies: %s", strategyRegexFilter);
96+
logger.atInfo().log("Regex filter strategies: %s", strategyRegexFilter);
97+
logger.atInfo().log("Platform filter strategies: %s", strategyPlatformFilter);
9298
logger.atInfo().log("Mnemonic strategies: %s", mnemonicToStrategies);
9399
logger.atInfo().log("Remote strategies: %s", mnemonicToRemoteDynamicStrategies);
94100
logger.atInfo().log("Local strategies: %s", mnemonicToLocalDynamicStrategies);
@@ -105,7 +111,8 @@ private SpawnStrategyRegistry(
105111
* using the given {@link Reporter}.
106112
*/
107113
public List<? extends SpawnStrategy> getStrategies(Spawn spawn, @Nullable EventHandler reporter) {
108-
return getStrategies(spawn.getResourceOwner(), spawn.getMnemonic(), reporter);
114+
return strategyPlatformFilter.getStrategies(
115+
spawn, getStrategies(spawn.getResourceOwner(), spawn.getMnemonic(), reporter));
109116
}
110117

111118
/**
@@ -116,6 +123,8 @@ public List<? extends SpawnStrategy> getStrategies(Spawn spawn, @Nullable EventH
116123
*
117124
* <p>If the reason for selecting the context is worth mentioning to the user, logs a message
118125
* using the given {@link Reporter}.
126+
*
127+
* NOTE: This method is public for Blaze, `getStrategies(Spawn, EventHandler)` must not be used in Bazel.
119128
*/
120129
public List<? extends SpawnStrategy> getStrategies(
121130
ActionExecutionMetadata resourceOwner, String mnemonic, @Nullable EventHandler reporter) {
@@ -154,18 +163,22 @@ public ImmutableCollection<SandboxedSpawnStrategy> getDynamicSpawnActionContexts
154163
? mnemonicToRemoteDynamicStrategies
155164
: mnemonicToLocalDynamicStrategies;
156165
if (mnemonicToDynamicStrategies.containsKey(spawn.getMnemonic())) {
157-
return mnemonicToDynamicStrategies.get(spawn.getMnemonic());
166+
return strategyPlatformFilter.getStrategies(spawn, mnemonicToDynamicStrategies.get(spawn.getMnemonic()));
158167
}
159168
if (mnemonicToDynamicStrategies.containsKey("")) {
160-
return mnemonicToDynamicStrategies.get("");
169+
return strategyPlatformFilter.getStrategies(spawn, mnemonicToDynamicStrategies.get(""));
161170
}
162171
return ImmutableList.of();
163172
}
164173

165174
@Nullable
166175
@Override
167-
public AbstractSpawnStrategy getRemoteLocalFallbackStrategy() {
168-
return remoteLocalFallbackStrategy;
176+
public AbstractSpawnStrategy getRemoteLocalFallbackStrategy(Spawn spawn) {
177+
var strategies = strategyPlatformFilter.getStrategies(spawn, Lists.newArrayList(remoteLocalFallbackStrategy));
178+
if (strategies.isEmpty()) {
179+
return null;
180+
}
181+
return strategies.getFirst();
169182
}
170183

171184
/**
@@ -203,9 +216,16 @@ void logSpawnStrategies() {
203216
strategyRegexFilter.getFilterToStrategies().asMap().entrySet()) {
204217
Collection<SpawnStrategy> value = entry.getValue();
205218
logger.atInfo().log(
206-
"FilterToStrategyImplementations: \"%s\" = [%s]",
219+
"FilterDescriptionToStrategyImplementations: \"%s\" = [%s]",
207220
entry.getKey(), toImplementationNames(value));
208221
}
222+
for (Map.Entry<Label, Collection<SpawnStrategy>> entry :
223+
strategyPlatformFilter.getFilterToStrategies().asMap().entrySet()) {
224+
Collection<SpawnStrategy> value = entry.getValue();
225+
logger.atInfo().log(
226+
"FilterPlatformToStrategyImplementations: \"%s\" = [%s]",
227+
entry.getKey().getCanonicalForm(), toImplementationNames(value));
228+
}
209229

210230
logger.atInfo().log(
211231
"DefaultStrategyImplementations: [%s]", toImplementationNames(defaultStrategies));
@@ -287,6 +307,7 @@ public static final class Builder {
287307
private final HashMap<String, List<String>> mnemonicToRemoteDynamicIdentifiers =
288308
new HashMap<>();
289309
private final HashMap<String, List<String>> mnemonicToLocalDynamicIdentifiers = new HashMap<>();
310+
private final HashMap<Label, List<String>> execPlatformFilters = new HashMap<>();
290311

291312
@Nullable private String remoteLocalFallbackStrategyIdentifier;
292313

@@ -314,6 +335,12 @@ public Builder addDescriptionFilter(RegexFilter filter, List<String> identifiers
314335
return this;
315336
}
316337

338+
@CanIgnoreReturnValue
339+
public Builder addExecPlatformFilter(Label execPlatform, List<String> identifiers) {
340+
this.execPlatformFilters.put(execPlatform, identifiers);
341+
return this;
342+
}
343+
317344
/**
318345
* Adds a filter limiting any spawn whose {@linkplain Spawn#getMnemonic() mnemonic}
319346
* (case-sensitively) matches the given mnemonic to only use strategies with the given
@@ -444,6 +471,14 @@ public SpawnStrategyRegistry build() throws AbruptExitException {
444471
}
445472
}
446473

474+
ImmutableListMultimap.Builder<Label, SpawnStrategy> platformToStrategies = ImmutableListMultimap.builder();
475+
for (Map.Entry<Label, List<String>> entry : execPlatformFilters.entrySet()) {
476+
Label platform = entry.getKey();
477+
platformToStrategies.putAll(
478+
platform,
479+
strategyMapper.toStrategies(entry.getValue(), "platform " + platform.getCanonicalForm()));
480+
}
481+
447482
ImmutableListMultimap.Builder<String, SpawnStrategy> mnemonicToStrategies =
448483
new ImmutableListMultimap.Builder<>();
449484
for (Map.Entry<String, List<String>> entry : mnemonicToIdentifiers.entrySet()) {
@@ -514,6 +549,7 @@ public SpawnStrategyRegistry build() throws AbruptExitException {
514549
mnemonicToStrategies.build(),
515550
new StrategyRegexFilter(
516551
strategyMapper, strategyPolicy, filterToIdentifiers, filterToStrategies),
552+
new StrategyPlatformFilter(strategyMapper, platformToStrategies.build()),
517553
defaultStrategies,
518554
mnemonicToRemoteStrategies.build(),
519555
mnemonicToLocalStrategies.build(),
@@ -595,6 +631,64 @@ public String toString() {
595631
}
596632
}
597633

634+
private static class StrategyPlatformFilter {
635+
private final StrategyMapper strategyMapper;
636+
private final ImmutableListMultimap<Label, SpawnStrategy> platformToStrategies;
637+
638+
private StrategyPlatformFilter(
639+
StrategyMapper strategyMapper,
640+
ImmutableListMultimap<Label, SpawnStrategy> platformToStrategies) {
641+
this.strategyMapper = strategyMapper;
642+
this.platformToStrategies = platformToStrategies;
643+
}
644+
645+
/**
646+
* Gets strategies for the given spawn that are allowed by the execution platform.
647+
* @param spawn Spawn to pick strategies for. Must have an execution platform.
648+
* @param candidateStrategies Strategies ordered by priority to pick from. The contents of this
649+
* list vary depending on the context but are always the result of
650+
* an initial strategy selection pass.
651+
* e.g. {@link SpawnStrategyRegistry#getStrategies(Spawn, EventHandler)},
652+
* {@link SpawnStrategyRegistry#getDynamicSpawnActionContexts(Spawn, DynamicMode)}
653+
* and {@link SpawnStrategyRegistry#getRemoteLocalFallbackStrategy(Spawn)}.
654+
* @return A subset of {@code candidateStrategies} that are allowed by the spawn's execution
655+
* platform or all if no restrictions are in place.
656+
* Order from {@code candidateStrategies} is preserved.
657+
*/
658+
public <T extends SpawnStrategy> List<T> getStrategies(
659+
Spawn spawn, List<T> candidateStrategies) {
660+
var platformLabel = spawn.getExecutionPlatformLabel();
661+
Preconditions.checkNotNull(platformLabel, "Attempting to spawn action without an execution platform.");
662+
663+
if (platformToStrategies.containsKey(platformLabel)) {
664+
var allowedStrategies = platformToStrategies.get(platformLabel);
665+
List<T> filteredStrategies = new ArrayList<>();
666+
for (var strategy : candidateStrategies) {
667+
if (allowedStrategies.contains(strategy)) {
668+
filteredStrategies.add(strategy);
669+
}
670+
}
671+
return filteredStrategies;
672+
}
673+
674+
return candidateStrategies;
675+
}
676+
677+
public <T extends SpawnStrategy> ImmutableCollection<T> getStrategies(
678+
Spawn spawn, ImmutableCollection<T> candidateStrategies) {
679+
return ImmutableList.copyOf(getStrategies(spawn, Lists.newCopyOnWriteArrayList(candidateStrategies)));
680+
}
681+
682+
ImmutableListMultimap<Label, SpawnStrategy> getFilterToStrategies() {
683+
return platformToStrategies;
684+
}
685+
686+
@Override
687+
public String toString() {
688+
return platformToStrategies.toString();
689+
}
690+
}
691+
598692
/* Maps the strategy identifier (e.g. "local", "worker"..) to the real strategy. */
599693
private static class StrategyMapper {
600694

0 commit comments

Comments
 (0)