| // Copyright 2018 The Bazel Authors. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| package com.google.devtools.build.lib.exec; |
| |
| import static java.util.stream.Collectors.joining; |
| |
| import com.google.auto.value.AutoValue; |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.base.Joiner; |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableListMultimap; |
| import com.google.common.collect.ImmutableMultimap; |
| import com.google.common.collect.LinkedListMultimap; |
| import com.google.common.collect.ListMultimap; |
| import com.google.common.collect.Lists; |
| import com.google.common.flogger.GoogleLogger; |
| import com.google.devtools.build.lib.actions.ActionContext; |
| import com.google.devtools.build.lib.actions.ActionExecutionMetadata; |
| import com.google.devtools.build.lib.actions.DynamicStrategyRegistry; |
| import com.google.devtools.build.lib.actions.SandboxedSpawnStrategy; |
| import com.google.devtools.build.lib.actions.Spawn; |
| import com.google.devtools.build.lib.actions.SpawnStrategy; |
| import com.google.devtools.build.lib.events.Event; |
| import com.google.devtools.build.lib.events.EventHandler; |
| import com.google.devtools.build.lib.events.Reporter; |
| import com.google.devtools.build.lib.server.FailureDetails; |
| import com.google.devtools.build.lib.server.FailureDetails.ExecutionOptions.Code; |
| import com.google.devtools.build.lib.server.FailureDetails.FailureDetail; |
| import com.google.devtools.build.lib.util.AbruptExitException; |
| import com.google.devtools.build.lib.util.DetailedExitCode; |
| import com.google.devtools.build.lib.util.RegexFilter; |
| import com.google.errorprone.annotations.CanIgnoreReturnValue; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import javax.annotation.Nullable; |
| |
| /** |
| * Registry that collects spawn strategies and rules about their applicability and makes them |
| * available for querying through various registry interfaces. |
| * |
| * <p>An instance of this registry can be created using its {@linkplain Builder builder}, which is |
| * available to Blaze modules during server startup. |
| */ |
| public final class SpawnStrategyRegistry |
| implements DynamicStrategyRegistry, ActionContext, RemoteLocalFallbackRegistry { |
| private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); |
| |
| private final ImmutableListMultimap<String, SpawnStrategy> mnemonicToStrategies; |
| private final ImmutableListMultimap<RegexFilter, SpawnStrategy> filterToStrategies; |
| private final ImmutableList<? extends SpawnStrategy> defaultStrategies; |
| private final ImmutableMultimap<String, SandboxedSpawnStrategy> mnemonicToRemoteDynamicStrategies; |
| private final ImmutableMultimap<String, SandboxedSpawnStrategy> mnemonicToLocalDynamicStrategies; |
| @Nullable private final AbstractSpawnStrategy remoteLocalFallbackStrategy; |
| |
| private SpawnStrategyRegistry( |
| ImmutableListMultimap<String, SpawnStrategy> mnemonicToStrategies, |
| ImmutableListMultimap<RegexFilter, SpawnStrategy> filterToStrategies, |
| ImmutableList<? extends SpawnStrategy> defaultStrategies, |
| ImmutableMultimap<String, SandboxedSpawnStrategy> mnemonicToRemoteDynamicStrategies, |
| ImmutableMultimap<String, SandboxedSpawnStrategy> mnemonicToLocalDynamicStrategies, |
| @Nullable AbstractSpawnStrategy remoteLocalFallbackStrategy) { |
| this.mnemonicToStrategies = mnemonicToStrategies; |
| this.filterToStrategies = filterToStrategies; |
| this.defaultStrategies = defaultStrategies; |
| this.mnemonicToRemoteDynamicStrategies = mnemonicToRemoteDynamicStrategies; |
| this.mnemonicToLocalDynamicStrategies = mnemonicToLocalDynamicStrategies; |
| this.remoteLocalFallbackStrategy = remoteLocalFallbackStrategy; |
| logger.atInfo().log("Default strategies: %s", defaultStrategies); |
| logger.atInfo().log("Filter strategies: %s", filterToStrategies); |
| logger.atInfo().log("Mnemonic strategies: %s", mnemonicToStrategies); |
| logger.atInfo().log("Remote strategies: %s", mnemonicToRemoteDynamicStrategies); |
| logger.atInfo().log("Local strategies: %s", mnemonicToLocalDynamicStrategies); |
| logger.atInfo().log("Fallback strategies: %s", remoteLocalFallbackStrategy); |
| } |
| |
| /** |
| * Returns the strategies applying to the given spawn, in priority order. |
| * |
| * <p>Which strategies are returned is based on the precedence as documented on the construction |
| * methods of {@linkplain Builder this registry's builder}. |
| * |
| * <p>If the reason for selecting the context is worth mentioning to the user, logs a message |
| * using the given {@link Reporter}. |
| */ |
| @VisibleForTesting |
| public List<? extends SpawnStrategy> getStrategies(Spawn spawn, EventHandler reporter) { |
| return getStrategies(spawn.getResourceOwner(), spawn.getMnemonic(), reporter); |
| } |
| |
| public List<? extends SpawnStrategy> getStrategies( |
| @Nullable ActionExecutionMetadata resourceOwner, |
| String mnemonic, |
| @Nullable EventHandler reporter) { |
| // Don't override test strategies by --strategy_regexp for backwards compatibility. |
| if (resourceOwner != null && !"TestRunner".equals(mnemonic)) { |
| String description = resourceOwner.getProgressMessage(); |
| if (description != null) { |
| for (Map.Entry<RegexFilter, Collection<SpawnStrategy>> filterStrategies : |
| filterToStrategies.asMap().entrySet()) { |
| if (filterStrategies.getKey().isIncluded(description)) { |
| // TODO(schmitt): Why is this done here and not after running canExec? |
| if (reporter != null) { |
| reporter.handle( |
| Event.progress(description + " with context " + filterStrategies.getValue())); |
| } |
| return ImmutableList.copyOf(filterStrategies.getValue()); |
| } |
| } |
| } |
| } |
| if (mnemonicToStrategies.containsKey(mnemonic)) { |
| return mnemonicToStrategies.get(mnemonic); |
| } |
| return defaultStrategies; |
| } |
| |
| @Override |
| public void notifyUsedDynamic(ActionContext.ActionContextRegistry actionContextRegistry) { |
| for (SandboxedSpawnStrategy strategy : mnemonicToLocalDynamicStrategies.values()) { |
| strategy.usedContext(actionContextRegistry); |
| } |
| for (SandboxedSpawnStrategy strategy : mnemonicToRemoteDynamicStrategies.values()) { |
| strategy.usedContext(actionContextRegistry); |
| } |
| } |
| |
| @Override |
| public List<SandboxedSpawnStrategy> getDynamicSpawnActionContexts( |
| Spawn spawn, DynamicMode dynamicMode) { |
| ImmutableMultimap<String, SandboxedSpawnStrategy> mnemonicToDynamicStrategies = |
| dynamicMode == DynamicStrategyRegistry.DynamicMode.REMOTE |
| ? mnemonicToRemoteDynamicStrategies |
| : mnemonicToLocalDynamicStrategies; |
| return ImmutableList.<SandboxedSpawnStrategy>builder() |
| .addAll(mnemonicToDynamicStrategies.get(spawn.getMnemonic())) |
| .addAll(mnemonicToDynamicStrategies.get("")) |
| .build(); |
| } |
| |
| @Nullable |
| @Override |
| public AbstractSpawnStrategy getRemoteLocalFallbackStrategy() { |
| return remoteLocalFallbackStrategy; |
| } |
| |
| /** |
| * Notifies all (non-dynamic) strategies stored in this registry that they are {@linkplain |
| * SpawnStrategy#usedContext used}. |
| */ |
| public void notifyUsed(ActionContext.ActionContextRegistry actionContextRegistry) { |
| for (SpawnStrategy strategy : filterToStrategies.values()) { |
| strategy.usedContext(actionContextRegistry); |
| } |
| for (SpawnStrategy strategy : mnemonicToStrategies.values()) { |
| strategy.usedContext(actionContextRegistry); |
| } |
| for (SpawnStrategy strategy : defaultStrategies) { |
| strategy.usedContext(actionContextRegistry); |
| } |
| if (remoteLocalFallbackStrategy != null) { |
| remoteLocalFallbackStrategy.usedContext(actionContextRegistry); |
| } |
| } |
| |
| /** |
| * Records the list of all spawn strategies that can be returned by the various query methods of |
| * this registry to the given reporter. |
| */ |
| void writeSpawnStrategiesTo(Reporter reporter) { |
| for (Map.Entry<String, Collection<SpawnStrategy>> entry : |
| mnemonicToStrategies.asMap().entrySet()) { |
| reporter.handle( |
| Event.info( |
| String.format( |
| "MnemonicToStrategyImplementations: \"%s\" = [%s]", |
| entry.getKey(), toImplementationNames(entry.getValue())))); |
| } |
| |
| for (Map.Entry<RegexFilter, Collection<SpawnStrategy>> entry : |
| filterToStrategies.asMap().entrySet()) { |
| Collection<SpawnStrategy> value = entry.getValue(); |
| reporter.handle( |
| Event.info( |
| String.format( |
| "FilterToStrategyImplementations: \"%s\" = [%s]", |
| entry.getKey(), toImplementationNames(value)))); |
| } |
| |
| reporter.handle( |
| Event.info( |
| String.format( |
| "DefaultStrategyImplementations: [%s]", toImplementationNames(defaultStrategies)))); |
| |
| if (remoteLocalFallbackStrategy != null) { |
| reporter.handle( |
| Event.info( |
| String.format( |
| "RemoteLocalFallbackImplementation: [%s]", |
| remoteLocalFallbackStrategy.getClass().getSimpleName()))); |
| } |
| |
| for (Map.Entry<String, Collection<SandboxedSpawnStrategy>> entry : |
| mnemonicToRemoteDynamicStrategies.asMap().entrySet()) { |
| reporter.handle( |
| Event.info( |
| String.format( |
| "MnemonicToRemoteDynamicStrategyImplementations: \"%s\" = [%s]", |
| entry.getKey(), toImplementationNames(entry.getValue())))); |
| } |
| |
| for (Map.Entry<String, Collection<SandboxedSpawnStrategy>> entry : |
| mnemonicToLocalDynamicStrategies.asMap().entrySet()) { |
| reporter.handle( |
| Event.info( |
| String.format( |
| "MnemonicToLocalDynamicStrategyImplementations: \"%s\" = [%s]", |
| entry.getKey(), toImplementationNames(entry.getValue())))); |
| } |
| } |
| |
| private static String toImplementationNames(Collection<?> strategies) { |
| return strategies.stream() |
| .map(strategy -> strategy.getClass().getSimpleName()) |
| .collect(joining(", ")); |
| } |
| |
| /** Returns a new {@link Builder} suitable for creating instances of SpawnStrategyRegistry. */ |
| public static Builder builder() { |
| return new Builder(); |
| } |
| |
| /** |
| * Builder collecting the strategies and restrictions thereon for a {@link SpawnStrategyRegistry}. |
| * |
| * <p>To {@linkplain SpawnStrategyRegistry#getStrategies match a strategy to a spawn} it needs to |
| * be both {@linkplain #registerStrategy registered} and its registered command-line identifier |
| * has to match {@linkplain #addDescriptionFilter a filter on the spawn's progress message}, |
| * {@linkplain #addMnemonicFilter a filter on the spawn's mnemonic} or be part of the default |
| * strategies (see below). |
| * |
| * <p><strong>Default strategies</strong> are either {@linkplain #setDefaultStrategies set |
| * explicitly} or, if {@link #setDefaultStrategies} is not called on this builder, comprised of |
| * all registered strategies, in registration order (i.e. the earliest strategy registered will be |
| * first in the list of strategies returned by {@link SpawnStrategyRegistry#getStrategies}). |
| */ |
| public static final class Builder { |
| |
| private final HashMap<String, SpawnStrategy> identifierToStrategy = new HashMap<>(); |
| private final ArrayList<SpawnStrategy> strategiesInRegistrationOrder = new ArrayList<>(); |
| |
| private ImmutableList<String> explicitDefaultStrategies = ImmutableList.of(); |
| |
| // TODO(schmitt): Using a list and autovalue so as to be able to reverse order while legacy sort |
| // is supported. Can be converted to same as mnemonics once legacy behavior is removed. |
| private final List<FilterAndIdentifiers> filterAndIdentifiers = new ArrayList<>(); |
| // Using List values here rather than multimaps as there is no need for the latter's |
| // functionality: The values are always replaced as a whole, no adding/creation required. |
| private final HashMap<String, List<String>> mnemonicToIdentifiers = new HashMap<>(); |
| private final HashMap<String, List<String>> mnemonicToRemoteDynamicIdentifiers = |
| new HashMap<>(); |
| private final HashMap<String, List<String>> mnemonicToLocalDynamicIdentifiers = new HashMap<>(); |
| |
| @Nullable private String remoteLocalFallbackStrategyIdentifier; |
| |
| /** |
| * Adds a filter limiting any spawn whose {@linkplain |
| * com.google.devtools.build.lib.actions.ActionExecutionMetadata#getProgressMessage() owner's |
| * progress message} matches the regular expression to only use strategies with the given |
| * command-line identifiers, in order. |
| * |
| * <p>If multiple filters match the same spawn (including an identical filter) the order of last |
| * applicable filter registered by this method will be used. |
| */ |
| @CanIgnoreReturnValue |
| public Builder addDescriptionFilter(RegexFilter filter, List<String> identifiers) { |
| filterAndIdentifiers.add( |
| new AutoValue_SpawnStrategyRegistry_FilterAndIdentifiers( |
| filter, ImmutableList.copyOf(identifiers))); |
| return this; |
| } |
| |
| /** |
| * Adds a filter limiting any spawn whose {@linkplain Spawn#getMnemonic() mnemonic} |
| * (case-sensitively) matches the given mnemonic to only use strategies with the given |
| * command-line identifiers, in order. |
| * |
| * <p>If the same mnemonic is registered multiple times the last such call will take precedence. |
| * |
| * <p>Note that if a spawn matches a {@linkplain #addDescriptionFilter registered description |
| * filter} that filter will take precedence over any mnemonic-based filters. |
| */ |
| // last one wins |
| @CanIgnoreReturnValue |
| public Builder addMnemonicFilter(String mnemonic, List<String> identifiers) { |
| mnemonicToIdentifiers.put(mnemonic, identifiers); |
| return this; |
| } |
| |
| /** |
| * Registers a strategy implementation with this collector, distinguishing it from other |
| * strategies with the given command-line identifiers (of which at least one is required). |
| * |
| * <p>If multiple strategies are registered with the same command-line identifier the last one |
| * so registered will take precedence. |
| */ |
| @CanIgnoreReturnValue |
| public Builder registerStrategy(SpawnStrategy strategy, String... commandlineIdentifiers) { |
| Preconditions.checkArgument( |
| commandlineIdentifiers.length >= 1, "At least one commandLineIdentifier must be given"); |
| for (String identifier : commandlineIdentifiers) { |
| identifierToStrategy.put(identifier, strategy); |
| } |
| strategiesInRegistrationOrder.add(strategy); |
| return this; |
| } |
| |
| /** |
| * Explicitly sets the identifiers of default strategies to use if a spawn matches no filters. |
| * |
| * <p>Note that if this method is not called on the builder, all registered strategies are |
| * considered default strategies, in registration order. See also the {@linkplain Builder class |
| * documentation}. |
| */ |
| @CanIgnoreReturnValue |
| public Builder setDefaultStrategies(List<String> defaultStrategies) { |
| // Ensure there are actual strategies and the contents are not empty. |
| Preconditions.checkArgument(!defaultStrategies.isEmpty()); |
| Preconditions.checkArgument( |
| defaultStrategies.stream().anyMatch(strategy -> !"".equals(strategy))); |
| this.explicitDefaultStrategies = ImmutableList.copyOf(defaultStrategies); |
| return this; |
| } |
| |
| /** |
| * Reset the default strategies (see {@link #setDefaultStrategies}) to the reverse of the order |
| * they were registered in. |
| */ |
| @CanIgnoreReturnValue |
| public Builder resetDefaultStrategies() { |
| this.explicitDefaultStrategies = ImmutableList.of(); |
| return this; |
| } |
| |
| /** |
| * Sets the strategy names to use in the remote branch of dynamic execution for a set of action |
| * mnemonics. |
| * |
| * <p>During execution, each strategy is {@linkplain SpawnStrategy#canExec(Spawn, |
| * ActionContextRegistry) asked} whether it can execute a given Spawn. The first strategy in the |
| * list that says so will get the job. |
| */ |
| @CanIgnoreReturnValue |
| public Builder addDynamicRemoteStrategies(Map<String, List<String>> strategies) { |
| mnemonicToRemoteDynamicIdentifiers.putAll(strategies); |
| return this; |
| } |
| |
| /** |
| * Sets the strategy names to use in the local branch of dynamic execution for a number of |
| * action mnemonics. |
| * |
| * <p>During execution, each strategy is {@linkplain SpawnStrategy#canExec(Spawn, |
| * ActionContextRegistry) asked} whether it can execute a given Spawn. The first strategy in the |
| * list that says so will get the job. |
| */ |
| @CanIgnoreReturnValue |
| public Builder addDynamicLocalStrategies(Map<String, List<String>> strategies) { |
| mnemonicToLocalDynamicIdentifiers.putAll(strategies); |
| return this; |
| } |
| |
| /** |
| * Sets the commandline identifier of the strategy to be used when falling back from remote to |
| * local execution. |
| * |
| * <p>Note that this is an optional setting, if not provided {@link |
| * SpawnStrategyRegistry#getRemoteLocalFallbackStrategy()} will return {@code null}. If the |
| * value <b>is</b> provided it must match the commandline identifier of a registered strategy |
| * (at {@linkplain #build build} time). |
| */ |
| @CanIgnoreReturnValue |
| public Builder setRemoteLocalFallbackStrategyIdentifier(String commandlineIdentifier) { |
| this.remoteLocalFallbackStrategyIdentifier = commandlineIdentifier; |
| return this; |
| } |
| |
| /** |
| * Finalizes the construction of the registry. |
| * |
| * @throws AbruptExitException if a strategy command-line identifier was used in a filter or the |
| * default strategies but no strategy for that identifier was registered |
| */ |
| public SpawnStrategyRegistry build() throws AbruptExitException { |
| List<FilterAndIdentifiers> orderedFilterAndIdentifiers = Lists.reverse(filterAndIdentifiers); |
| |
| ListMultimap<RegexFilter, SpawnStrategy> filterToStrategies = LinkedListMultimap.create(); |
| for (FilterAndIdentifiers filterAndIdentifier : orderedFilterAndIdentifiers) { |
| RegexFilter filter = filterAndIdentifier.filter(); |
| if (!filterToStrategies.containsKey(filter)) { |
| filterToStrategies.putAll( |
| filter, toStrategies(filterAndIdentifier.identifiers(), filter)); |
| } |
| } |
| |
| ImmutableListMultimap.Builder<String, SpawnStrategy> mnemonicToStrategies = |
| new ImmutableListMultimap.Builder<>(); |
| for (Map.Entry<String, List<String>> entry : mnemonicToIdentifiers.entrySet()) { |
| mnemonicToStrategies.putAll( |
| entry.getKey(), toStrategies(entry.getValue(), "mnemonic " + entry.getKey())); |
| } |
| |
| ImmutableListMultimap.Builder<String, SandboxedSpawnStrategy> mnemonicToLocalStrategies = |
| new ImmutableListMultimap.Builder<>(); |
| for (Map.Entry<String, List<String>> entry : mnemonicToLocalDynamicIdentifiers.entrySet()) { |
| mnemonicToLocalStrategies.putAll( |
| entry.getKey(), |
| toSandboxedStrategies(entry.getValue(), "local mnemonic " + entry.getKey())); |
| } |
| |
| ImmutableListMultimap.Builder<String, SandboxedSpawnStrategy> mnemonicToRemoteStrategies = |
| new ImmutableListMultimap.Builder<>(); |
| for (Map.Entry<String, List<String>> entry : mnemonicToRemoteDynamicIdentifiers.entrySet()) { |
| mnemonicToRemoteStrategies.putAll( |
| entry.getKey(), |
| toSandboxedStrategies(entry.getValue(), "remote mnemonic " + entry.getKey())); |
| } |
| |
| AbstractSpawnStrategy remoteLocalFallbackStrategy = null; |
| if (remoteLocalFallbackStrategyIdentifier != null) { |
| SpawnStrategy strategy = |
| toStrategy(remoteLocalFallbackStrategyIdentifier, "remote fallback strategy"); |
| if (!(strategy instanceof AbstractSpawnStrategy)) { |
| // TODO(schmitt): Check if all strategies can use the same base and remove check if so. |
| throw createExitException( |
| String.format( |
| "'%s' was requested for the remote fallback strategy but is not an" |
| + " abstract spawn strategy (which is required for remote" |
| + " fallback execution).", |
| strategy.getClass().getSimpleName()), |
| Code.REMOTE_FALLBACK_STRATEGY_NOT_ABSTRACT_SPAWN); |
| } |
| |
| remoteLocalFallbackStrategy = (AbstractSpawnStrategy) strategy; |
| } |
| |
| ImmutableList<? extends SpawnStrategy> defaultStrategies; |
| if (explicitDefaultStrategies.isEmpty()) { |
| // Use the strategies as registered, in reverse order. |
| defaultStrategies = ImmutableList.copyOf(Lists.reverse(strategiesInRegistrationOrder)); |
| } else { |
| defaultStrategies = toStrategies(explicitDefaultStrategies, "default strategies"); |
| } |
| |
| return new SpawnStrategyRegistry( |
| mnemonicToStrategies.build(), |
| ImmutableListMultimap.copyOf(filterToStrategies), |
| defaultStrategies, |
| mnemonicToRemoteStrategies.build(), |
| mnemonicToLocalStrategies.build(), |
| remoteLocalFallbackStrategy); |
| } |
| |
| private ImmutableList<? extends SpawnStrategy> toStrategies( |
| List<String> identifiers, Object requestName) throws AbruptExitException { |
| ImmutableList.Builder<SpawnStrategy> strategies = ImmutableList.builder(); |
| for (String identifier : identifiers) { |
| if (identifier.isEmpty()) { |
| continue; |
| } |
| strategies.add(toStrategy(identifier, requestName)); |
| } |
| return strategies.build(); |
| } |
| |
| @VisibleForTesting |
| public SpawnStrategy toStrategy(String identifier, Object requestName) |
| throws AbruptExitException { |
| SpawnStrategy strategy = identifierToStrategy.get(identifier); |
| if (strategy == null) { |
| throw createExitException( |
| String.format( |
| "'%s' was requested for %s but no strategy with that identifier was registered. " |
| + "Valid values are: [%s]", |
| identifier, requestName, Joiner.on(", ").join(identifierToStrategy.keySet())), |
| Code.STRATEGY_NOT_FOUND); |
| } |
| return strategy; |
| } |
| |
| private Iterable<? extends SandboxedSpawnStrategy> toSandboxedStrategies( |
| List<String> identifiers, Object requestName) throws AbruptExitException { |
| Iterable<? extends SpawnStrategy> strategies = toStrategies(identifiers, requestName); |
| for (SpawnStrategy strategy : strategies) { |
| if (!(strategy instanceof SandboxedSpawnStrategy)) { |
| throw createExitException( |
| String.format( |
| "'%s' was requested for %s but is not a sandboxed strategy (which is required for" |
| + " dynamic execution).", |
| strategy.getClass().getSimpleName(), requestName), |
| Code.DYNAMIC_STRATEGY_NOT_SANDBOXED); |
| } |
| } |
| |
| @SuppressWarnings("unchecked") // Each element of the iterable was checked to fulfil this. |
| Iterable<? extends SandboxedSpawnStrategy> sandboxedStrategies = |
| (Iterable<? extends SandboxedSpawnStrategy>) strategies; |
| return sandboxedStrategies; |
| } |
| } |
| |
| public ImmutableListMultimap<String, SpawnStrategy> getMnemonicToStrategies() { |
| return mnemonicToStrategies; |
| } |
| |
| private static AbruptExitException createExitException(String message, Code detailedCode) { |
| return new AbruptExitException( |
| DetailedExitCode.of( |
| FailureDetail.newBuilder() |
| .setMessage(message) |
| .setExecutionOptions( |
| FailureDetails.ExecutionOptions.newBuilder().setCode(detailedCode)) |
| .build())); |
| } |
| |
| @AutoValue |
| abstract static class FilterAndIdentifiers { |
| |
| abstract RegexFilter filter(); |
| |
| abstract ImmutableList<String> identifiers(); |
| } |
| } |