blob: 799be8b9981792bcc12b6719fe803cd613affe25 [file] [log] [blame]
// Copyright 2015 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.rules.cpp;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.CToolchain;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.Stack;
/**
* Provides access to features supported by a specific toolchain.
*
* <p>This class can be generated from the CToolchain protocol buffer.
*
* <p>TODO(bazel-team): Implement support for specifying the toolchain configuration directly from
* the BUILD file.
*
* <p>TODO(bazel-team): Find a place to put the public-facing documentation and link to it from
* here.
*
* <p>TODO(bazel-team): Split out Feature as CcToolchainFeature, which will modularize the
* crosstool configuration into one part that is about handling a set of features (including feature
* selection) and one part that is about how to apply a single feature (parsing flags and expanding
* them from build variables).
*/
@Immutable
public class CcToolchainFeatures implements Serializable {
/**
* Thrown when a flag value cannot be expanded under a set of build variables.
*
* <p>This happens for example when a flag references a variable that is not provided by the
* action, or when a flag group references multiple variables of sequence type.
*/
public static class ExpansionException extends RuntimeException {
ExpansionException(String message) {
super(message);
}
}
/** Error message thrown when a toolchain does not provide a required artifact_name_pattern. */
public static final String MISSING_ARTIFACT_NAME_PATTERN_ERROR_TEMPLATE =
"Toolchain must provide artifact_name_pattern for category %s";
/**
* A piece of a single string value.
*
* <p>A single value can contain a combination of text and variables (for example "-f
* %{var1}/%{var2}"). We split the string into chunks, where each chunk represents either a text
* snippet, or a variable that is to be replaced.
*/
interface StringChunk {
/**
* Expands this chunk.
*
* @param variables variable names mapped to their values for a single flag expansion.
* @param flag the flag content to append to.
*/
void expand(Map<String, String> variables, StringBuilder flag);
}
/**
* A plain text chunk of a string (containing no variables).
*/
@Immutable
private static class StringLiteralChunk implements StringChunk, Serializable {
private final String text;
private StringLiteralChunk(String text) {
this.text = text;
}
@Override
public void expand(Map<String, String> variables, StringBuilder flag) {
flag.append(text);
}
}
/**
* A chunk of a string value into which a variable should be expanded.
*/
@Immutable
private static class VariableChunk implements StringChunk, Serializable {
private final String variableName;
private VariableChunk(String variableName) {
this.variableName = variableName;
}
@Override
public void expand(Map<String, String> variables, StringBuilder stringBuilder) {
// We check all variables in FlagGroup.expandCommandLine.
// If we arrive here with the variable not being available, the variable was provided, but
// the nesting level of the NestedSequence was deeper than the nesting level of the flag
// groups.
if (!variables.containsKey(variableName)) {
throw new ExpansionException(
"Invalid toolchain configuration: the flag group referencing '"
+ variableName
+ "' is not nested deeply enough to fully expand it.");
}
String value = variables.get(variableName);
if (value == null) {
throw new ExpansionException(
"Internal blaze error: build variable '" + variableName + "'was set to 'null'.");
}
stringBuilder.append(variables.get(variableName));
}
}
/**
* Parser for toolchain string values.
*
* <p>A string value contains a snippet of text supporting variable expansion. For example, a
* string value "-f %{var1}/%{var2}" will expand the values of the variables "var1" and "var2" in
* the corresponding places in the string.
*
* <p>The {@code StringValueParser} takes a string and parses it into a list of {@link
* StringChunk} objects, where each chunk represents either a snippet of text or a variable to be
* expanded. In the above example, the resulting chunks would be ["-f ", var1, "/", var2].
*
* <p>In addition to the list of chunks, the {@link StringValueParser} also provides the set of
* variables necessary for the expansion of this flag via {@link #getUsedVariables}.
*
* <p>To get a literal percent character, "%%" can be used in the string.
*/
static class StringValueParser {
private final String value;
/**
* The current position in {@value} during parsing.
*/
private int current = 0;
private final ImmutableList.Builder<StringChunk> chunks = ImmutableList.builder();
private final ImmutableSet.Builder<String> usedVariables = ImmutableSet.builder();
StringValueParser(String value) throws InvalidConfigurationException {
this.value = value;
parse();
}
/** @return the parsed chunks for this string. */
ImmutableList<StringChunk> getChunks() {
return chunks.build();
}
/** @return all variable names needed to expand this string. */
ImmutableSet<String> getUsedVariables() {
return usedVariables.build();
}
/**
* Parses the string.
*
* @throws InvalidConfigurationException if there is a parsing error.
*/
private void parse() throws InvalidConfigurationException {
while (current < value.length()) {
if (atVariableStart()) {
parseVariableChunk();
} else {
parseStringChunk();
}
}
}
/**
* @return whether the current position is the start of a variable.
*/
private boolean atVariableStart() {
// We parse a variable when value starts with '%', but not '%%'.
return value.charAt(current) == '%'
&& (current + 1 >= value.length() || value.charAt(current + 1) != '%');
}
/**
* Parses a chunk of text until the next '%', which indicates either an escaped literal '%'
* or a variable.
*/
private void parseStringChunk() {
int start = current;
// We only parse string chunks starting with '%' if they also start with '%%'.
// In that case, we want to have a single '%' in the string, so we start at the second
// character.
// Note that for strings like "abc%%def" this will lead to two string chunks, the first
// referencing the subtring "abc", and a second referencing the substring "%def".
if (value.charAt(current) == '%') {
current = current + 1;
start = current;
}
current = value.indexOf('%', current + 1);
if (current == -1) {
current = value.length();
}
final String text = value.substring(start, current);
chunks.add(new StringLiteralChunk(text));
}
/**
* Parses a variable to be expanded.
*
* @throws InvalidConfigurationException if there is a parsing error.
*/
private void parseVariableChunk() throws InvalidConfigurationException {
current = current + 1;
if (current >= value.length() || value.charAt(current) != '{') {
abort("expected '{'");
}
current = current + 1;
if (current >= value.length() || value.charAt(current) == '}') {
abort("expected variable name");
}
int end = value.indexOf('}', current);
final String name = value.substring(current, end);
usedVariables.add(name);
chunks.add(new VariableChunk(name));
current = end + 1;
}
/**
* @throws InvalidConfigurationException with the given error text, adding information about
* the current position in the string.
*/
private void abort(String error) throws InvalidConfigurationException {
throw new InvalidConfigurationException("Invalid toolchain configuration: " + error
+ " at position " + current + " while parsing a flag containing '" + value + "'");
}
}
/**
* A flag or flag group that can be expanded under a set of variables.
*/
interface Expandable {
/**
* Returns the set of variables that can control the expansion of this expandable.
* Returns null if the expandable is a simple flag that will only be expanded once.
*/
Set<String> getControllingVariables();
/**
* Expands the current expandable under the given {@code view}, adding new flags to
* {@code commandLine}.
*
* The {@code view} controls which variables are visible during the expansion and allows
* to recursively expand nested flag groups.
*/
void expand(Variables.View view, List<String> commandLine);
}
/**
* A single flag to be expanded under a set of variables.
*
* <p>TODO(bazel-team): Consider specializing Flag for the simple case that a flag is just a bit
* of text.
*/
@Immutable
private static class Flag implements Serializable, Expandable {
private final ImmutableList<StringChunk> chunks;
private Flag(ImmutableList<StringChunk> chunks) {
this.chunks = chunks;
}
/**
* Expand this flag into a single new entry in {@code commandLine}.
*/
@Override
public void expand(Variables.View view, List<String> commandLine) {
StringBuilder flag = new StringBuilder();
for (StringChunk chunk : chunks) {
chunk.expand(view.getVariables(), flag);
}
commandLine.add(flag.toString());
}
@Override
public Set<String> getControllingVariables() {
// A simple flag will only ever be expanded once.
return null;
}
}
/**
* A single environment key/value pair to be expanded under a set of variables.
*/
@Immutable
private static class EnvEntry implements Serializable {
private final String key;
private final ImmutableList<StringChunk> valueChunks;
private final ImmutableSet<String> usedVariables;
private EnvEntry(CToolchain.EnvEntry envEntry) throws InvalidConfigurationException {
this.key = envEntry.getKey();
StringValueParser parser = new StringValueParser(envEntry.getValue());
this.valueChunks = parser.getChunks();
this.usedVariables = parser.getUsedVariables();
}
/**
* Adds the key/value pair this object represents to the given map of environment variables.
* The value of the entry is expanded with the given {@code variables}.
*/
public void addEnvEntry(Variables variables, ImmutableMap.Builder<String, String> envBuilder) {
Variables.View view = variables.getView(usedVariables);
StringBuilder value = new StringBuilder();
for (StringChunk chunk : valueChunks) {
chunk.expand(view.getVariables(), value);
}
envBuilder.put(key, value.toString());
}
}
/**
* A group of flags.
*/
@Immutable
private static class FlagGroup implements Serializable, Expandable {
private final ImmutableList<Expandable> expandables;
private final ImmutableSet<String> usedVariables;
private FlagGroup(CToolchain.FlagGroup flagGroup) throws InvalidConfigurationException {
ImmutableList.Builder<Expandable> expandables = ImmutableList.builder();
ImmutableSet.Builder<String> usedVariables = ImmutableSet.builder();
Collection<String> flags = flagGroup.getFlagList();
Collection<CToolchain.FlagGroup> groups = flagGroup.getFlagGroupList();
if (!flags.isEmpty() && !groups.isEmpty()) {
// If both flags and flag_groups are available, the original order is not preservable.
throw new ExpansionException(
"Invalid toolchain configuration: a flag_group must not contain both a flag "
+ "and another flag_group.");
}
for (String flag : flags) {
StringValueParser parser = new StringValueParser(flag);
expandables.add(new Flag(parser.getChunks()));
usedVariables.addAll(parser.getUsedVariables());
}
for (CToolchain.FlagGroup group : groups) {
FlagGroup subgroup = new FlagGroup(group);
expandables.add(subgroup);
usedVariables.addAll(subgroup.getControllingVariables());
}
this.expandables = expandables.build();
this.usedVariables = usedVariables.build();
}
@Override
public void expand(Variables.View view, final List<String> commandLine) {
for (Expandable expandable : expandables) {
view.expand(expandable, commandLine);
}
}
@Override
public Set<String> getControllingVariables() {
// Any of the used variables can be used to control this flag group's expansion.
return usedVariables;
}
/**
* Expands all flags in this group and adds them to {@code commandLine}.
*
* <p>The flags of the group will be expanded either:
*
* <ul>
* <li>once, if there is no variable of sequence type in any of the group's flags, or
* <li>for each element in the sequence, if there is one variable of sequence type within the
* flags.
* </ul>
*
* <p>Having more than a single variable of sequence type in a single flag group is not
* supported.
*/
private void expandCommandLine(Variables variables, final List<String> commandLine) {
Variables.View view = variables.getView(getControllingVariables());
view.expand(this, commandLine);
}
}
/**
* Groups a set of flags to apply for certain actions.
*/
@Immutable
private static class FlagSet implements Serializable {
private final ImmutableSet<String> actions;
private final ImmutableSet<String> expandIfAllAvailable;
private final ImmutableList<FlagGroup> flagGroups;
private FlagSet(CToolchain.FlagSet flagSet) throws InvalidConfigurationException {
this(flagSet, ImmutableSet.copyOf(flagSet.getActionList()));
}
/**
* Constructs a FlagSet for the given set of actions.
*/
private FlagSet(CToolchain.FlagSet flagSet, ImmutableSet<String> actions)
throws InvalidConfigurationException {
this.actions = actions;
this.expandIfAllAvailable = ImmutableSet.copyOf(flagSet.getExpandIfAllAvailableList());
ImmutableList.Builder<FlagGroup> builder = ImmutableList.builder();
for (CToolchain.FlagGroup flagGroup : flagSet.getFlagGroupList()) {
builder.add(new FlagGroup(flagGroup));
}
this.flagGroups = builder.build();
}
/**
* Adds the flags that apply to the given {@code action} to {@code commandLine}.
*/
private void expandCommandLine(String action, Variables variables, List<String> commandLine) {
for (String variable : expandIfAllAvailable) {
if (!variables.isAvailable(variable)) {
return;
}
}
if (!actions.contains(action)) {
return;
}
for (FlagGroup flagGroup : flagGroups) {
flagGroup.expandCommandLine(variables, commandLine);
}
}
}
/**
* Groups a set of environment variables to apply for certain actions.
*/
@Immutable
private static class EnvSet implements Serializable {
private final ImmutableSet<String> actions;
private final ImmutableList<EnvEntry> envEntries;
private EnvSet(CToolchain.EnvSet envSet) throws InvalidConfigurationException {
this.actions = ImmutableSet.copyOf(envSet.getActionList());
ImmutableList.Builder<EnvEntry> builder = ImmutableList.builder();
for (CToolchain.EnvEntry envEntry : envSet.getEnvEntryList()) {
builder.add(new EnvEntry(envEntry));
}
this.envEntries = builder.build();
}
/**
* Adds the environment key/value pairs that apply to the given {@code action} to
* {@code envBuilder}.
*/
private void expandEnvironment(String action, Variables variables,
ImmutableMap.Builder<String, String> envBuilder) {
if (!actions.contains(action)) {
return;
}
for (EnvEntry envEntry : envEntries) {
envEntry.addEnvEntry(variables, envBuilder);
}
}
}
/**
* An interface for classes representing crosstool messages that can activate eachother
* using 'requires' and 'implies' semantics.
*
* <p>Currently there are two types of CrosstoolActivatable: Feature and ActionConfig.
*/
private interface CrosstoolSelectable {
/**
* Returns the name of this selectable.
*/
String getName();
}
/**
* Contains flags for a specific feature.
*/
@Immutable
private static class Feature implements Serializable, CrosstoolSelectable {
private final String name;
private final ImmutableList<FlagSet> flagSets;
private final ImmutableList<EnvSet> envSets;
private Feature(CToolchain.Feature feature) throws InvalidConfigurationException {
this.name = feature.getName();
ImmutableList.Builder<FlagSet> flagSetBuilder = ImmutableList.builder();
for (CToolchain.FlagSet flagSet : feature.getFlagSetList()) {
flagSetBuilder.add(new FlagSet(flagSet));
}
this.flagSets = flagSetBuilder.build();
ImmutableList.Builder<EnvSet> envSetBuilder = ImmutableList.builder();
for (CToolchain.EnvSet flagSet : feature.getEnvSetList()) {
envSetBuilder.add(new EnvSet(flagSet));
}
this.envSets = envSetBuilder.build();
}
@Override
public String getName() {
return name;
}
/**
* Adds environment variables for the given action to the provided builder.
*/
private void expandEnvironment(
String action, Variables variables, ImmutableMap.Builder<String, String> envBuilder) {
for (EnvSet envSet : envSets) {
envSet.expandEnvironment(action, variables, envBuilder);
}
}
/**
* Adds the flags that apply to the given {@code action} to {@code commandLine}.
*/
private void expandCommandLine(String action, Variables variables,
List<String> commandLine) {
for (FlagSet flagSet : flagSets) {
flagSet.expandCommandLine(action, variables, commandLine);
}
}
}
/**
* An executable to be invoked by a blaze action. Can carry information on its platform
* restrictions.
*/
@Immutable
static class Tool {
private final String toolPathString;
private final ImmutableSet<String> executionRequirements;
private Tool(CToolchain.Tool tool) {
toolPathString = tool.getToolPath();
executionRequirements = ImmutableSet.copyOf(tool.getExecutionRequirementList());
}
@VisibleForTesting
public Tool(String toolPathString, ImmutableSet<String> executionRequirements) {
this.toolPathString = toolPathString;
this.executionRequirements = executionRequirements;
}
/**
* Returns the path to this action's tool relative to the provided crosstool path.
*/
PathFragment getToolPath(PathFragment crosstoolTopPathFragment) {
return crosstoolTopPathFragment.getRelative(toolPathString);
}
/**
* Returns a list of requirement hints that apply to the execution of this tool.
*/
ImmutableSet<String> getExecutionRequirements() {
return executionRequirements;
}
}
/**
* A container for information on a particular blaze action.
*
* <p>An ActionConfig can select a tool for its blaze action based on the set of active
* features. Internally, an ActionConfig maintains an ordered list (the order being that of the
* list of tools in the crosstool action_config message) of such tools and the feature sets for
* which they are valid. For a given feature configuration, the ActionConfig will consider the
* first tool in that list with a feature set that matches the configuration to be the tool for
* its blaze action.
*
* <p>ActionConfigs can be activated by features. That is, a particular feature can cause an
* ActionConfig to be applied in its "implies" field. Blaze may include certain actions in
* the action graph only if a corresponding ActionConfig is activated in the toolchain - this
* provides the crosstool with a mechanism for adding certain actions to the action graph based
* on feature configuration.
*
* <p>It is invalid for a toolchain to contain two action configs for the same blaze action. In
* that case, blaze will throw an error when it consumes the crosstool.
*/
@Immutable
static class ActionConfig implements Serializable, CrosstoolSelectable {
public static final String FLAG_SET_WITH_ACTION_ERROR =
"action_config %s specifies actions. An action_config's flag sets automatically apply "
+ "to the configured action. Thus, you must not specify action lists in an "
+ "action_config's flag set.";
private final String configName;
private final String actionName;
private final List<CToolchain.Tool> tools;
private final ImmutableList<FlagSet> flagSets;
private ActionConfig(CToolchain.ActionConfig actionConfig)
throws InvalidConfigurationException {
this.configName = actionConfig.getConfigName();
this.actionName = actionConfig.getActionName();
this.tools = actionConfig.getToolList();
ImmutableList.Builder<FlagSet> flagSetBuilder = ImmutableList.builder();
for (CToolchain.FlagSet flagSet : actionConfig.getFlagSetList()) {
if (!flagSet.getActionList().isEmpty()) {
throw new InvalidConfigurationException(
String.format(FLAG_SET_WITH_ACTION_ERROR, configName));
}
flagSetBuilder.add(new FlagSet(flagSet, ImmutableSet.of(actionName)));
}
this.flagSets = flagSetBuilder.build();
}
@Override
public String getName() {
return configName;
}
/**
* Returns the name of the blaze action this action config applies to.
*/
private String getActionName() {
return actionName;
}
/**
* Returns the path to this action's tool relative to the provided crosstool path given a set
* of enabled features.
*/
private Tool getTool(final Set<String> enabledFeatureNames) {
Optional<CToolchain.Tool> tool =
Iterables.tryFind(
tools,
new Predicate<CToolchain.Tool>() {
// We select the first listed tool for which all specified features are activated
// in this configuration
@Override
public boolean apply(CToolchain.Tool input) {
Collection<String> featureNamesForTool = input.getWithFeature().getFeatureList();
return enabledFeatureNames.containsAll(featureNamesForTool);
}
});
if (tool.isPresent()) {
return new Tool(tool.get());
} else {
throw new IllegalArgumentException(
"Matching tool for action "
+ getActionName()
+ " not "
+ "found for given feature configuration");
}
}
/**
* Adds the flags that apply to this action to {@code commandLine}.
*/
private void expandCommandLine(Variables variables, List<String> commandLine) {
for (FlagSet flagSet : flagSets) {
flagSet.expandCommandLine(actionName, variables, commandLine);
}
}
}
/** A description of how artifacts of a certain type are named. */
@Immutable
private static class ArtifactNamePattern {
private final ArtifactCategory artifactCategory;
private final ImmutableSet<String> variables;
private final ImmutableList<StringChunk> chunks;
private ArtifactNamePattern(CToolchain.ArtifactNamePattern artifactNamePattern)
throws InvalidConfigurationException {
ArtifactCategory foundCategory = null;
for (ArtifactCategory artifactCategory : ArtifactCategory.values()) {
if (artifactNamePattern.getCategoryName().equals(artifactCategory.getCategoryName())) {
foundCategory = artifactCategory;
}
}
if (foundCategory == null) {
throw new ExpansionException(
String.format(
"Artifact category %s not recognized", artifactNamePattern.getCategoryName()));
}
this.artifactCategory = foundCategory;
StringValueParser parser = new StringValueParser(artifactNamePattern.getPattern());
this.variables = parser.getUsedVariables();
this.chunks = parser.getChunks();
}
/** Returns the ArtifactCategory for this ArtifactNamePattern. */
ArtifactCategory getArtifactCategory() {
return this.artifactCategory;
}
/**
* Returns the artifact name that this pattern selects.
*/
public String getArtifactName(Map<String, String> variables) {
StringBuilder resultBuilder = new StringBuilder();
Variables.View artifactNameVariables =
new Variables.Builder()
.addAllVariables(variables)
.build()
.getView(this.variables);
for (StringChunk chunk : chunks) {
chunk.expand(artifactNameVariables.getVariables(), resultBuilder);
}
String result = resultBuilder.toString();
return result.charAt(0) == '/' ? result.substring(1) : result;
}
}
/**
* Configured build variables usable by the toolchain configuration.
*/
@Immutable
public static class Variables {
/** An empty variables instance. */
public static final Variables EMPTY = new Variables.Builder().build();
/**
* Variables can be set as an arbitrarily deeply nested recursive sequence, which we represent
* as a tree of {@code Sequence} nodes. The nodes are {@code NestedSequence} objects, while the
* leafs are {@code ValueSequence} objects. We do not allow {@code Value} objects in the tree,
* as the object memory overhead is too large when we have millions of values. If we find single
* element {@code ValueSequence} in memory profiles in the future, we can introduce another
* special case type.
*/
interface Sequence {
/**
* Expands {@code expandable} under the given nested {@code view}, appending flags to
* {@code commandLine}.
*/
void expand(NestedView view, Expandable expandable, List<String> commandLine);
}
/**
* A sequence of simple string values.
* Exists as a memory optimization - a typical build can contain millions of feature values,
* so getting rid of the overhead of {@code Value} objects significantly reduces memory
* overhead.
*/
public static class ValueSequence implements Sequence {
private final List<String> values;
/** Builder for value sequences. */
public static class Builder {
private final ImmutableList.Builder<String> values = ImmutableList.builder();
/** Adds a value to the sequence. */
public Builder addValue(String value) {
values.add(value);
return this;
}
/** Returns an immutable value sequence. */
public ValueSequence build() {
return new ValueSequence(values.build());
}
}
private ValueSequence(List<String> values) {
this.values = values;
}
@Override
public void expand(NestedView view, Expandable expandable, List<String> commandLine) {
final ImmutableList.Builder<Sequence> sequences = ImmutableList.builder();
for (String value : values) {
sequences.add(new Value(value));
}
view.expandSequence(sequences.build(), expandable, commandLine);
}
/**
* The leaves in the variable sequence node tree are simple values. Note that this should
* never live outside of {@code expand}, as the object overhead is prohibitively expensive.
*/
private static class Value implements Sequence {
private final String value;
private Value(String value) {
this.value = value;
}
@Override
public void expand(NestedView view, Expandable expandable, List<String> commandLine) {
view.expandValue(value, expandable, commandLine);
}
}
}
/** An internal node in the sequence node tree. */
static class NestedSequence implements Sequence {
/**
* Builder for nested sequences.
*/
static class Builder {
private final ImmutableList.Builder<Sequence> sequences = ImmutableList.builder();
/**
* Adds a sub-sequence to the sequence.
*/
Builder addSequence(Sequence sequence) {
sequences.add(sequence);
return this;
}
/**
* Returns an immutable nested sequence.
*/
NestedSequence build() {
return new NestedSequence(sequences.build());
}
}
private final List<Sequence> sequences;
private NestedSequence(List<Sequence> expandables) {
this.sequences = expandables;
}
@Override
public void expand(NestedView view, Expandable expandable, List<String> commandLine) {
view.expandSequence(sequences, expandable, commandLine);
}
}
/**
* Builder for {@code Variables}.
*/
public static class Builder {
private final Map<String, String> variables = Maps.newLinkedHashMap();
private final Map<String, Sequence> expandables = Maps.newLinkedHashMap();
/**
* Add a variable that expands {@code name} to {@code value}.
*/
public Builder addVariable(String name, String value) {
variables.put(name, value);
return this;
}
/**
* Add all variables in a map.
*/
public Builder addAllVariables(Map<String, String> variableMap) {
variables.putAll(variableMap);
return this;
}
/**
* Add a nested sequence that expands {@code name} recursively.
*/
public Builder addSequence(String name, Sequence sequence) {
expandables.put(name, sequence);
return this;
}
/**
* Adds all variables to this builder.
*/
public Builder addAll(Variables variables) {
this.variables.putAll(variables.variables);
this.expandables.putAll(variables.sequenceVariables);
return this;
}
/**
* Add a variable that expands a flag group containing a reference to {@code name} for each
* entry in {@code values}.
*/
public Builder addSequenceVariable(String name, Collection<String> values) {
ValueSequence.Builder sequenceBuilder = new ValueSequence.Builder();
for (String value : values) {
sequenceBuilder.addValue(value);
}
return addSequence(name, sequenceBuilder.build());
}
/**
* @return a new {@Variables} object.
*/
Variables build() {
return new Variables(ImmutableMap.copyOf(variables), ImmutableMap.copyOf(expandables));
}
}
/**
* A group of extra {@code Variable} instances, packaged as logic for adding to a
* {@code Builder}
*/
public interface VariablesExtension {
void addVariables(Builder builder);
}
/**
* Interface for a set of variables visible during an expansion of a variable.
*/
interface View {
/**
* Returns all bound variables in the current view.
*/
Map<String, String> getVariables();
/**
* Expands the given {@code expandable} under the current view, adding flags to
* {@code commandLine}.
*/
void expand(Expandable expandable, List<String> commandLine);
}
/**
* An simple view that contains a fixed mapping.
*/
private static class FixedView implements View {
private final Map<String, String> viewMap;
FixedView(Map<String, String> viewMap) {
this.viewMap = viewMap;
}
@Override
public Map<String, String> getVariables() {
return viewMap;
}
@Override
public void expand(Expandable expandable, List<String> commandLine) {
expandable.expand(this, commandLine);
}
}
/**
* A nested view that is controlled by a nested build variable and expanded recursively.
*/
private static class NestedView implements View {
/**
* The view map will contain all mapped variables when a leaf node is reached.
* During traversal it will not contain the control variable.
*/
private final Map<String, String> viewMap;
/**
* The name of the control variable (a variable of nested structure) that controls
* the recursive expansion.
*/
String controlVariable;
/**
* The stack of sequences that are currently being expanded. Each level represents
* one level in the control variables nesting.
*/
Stack<Sequence> sequenceStack = new Stack<>();
NestedView(Map<String, String> viewMap, String controlVariable, Sequence sequence) {
this.viewMap = viewMap;
this.controlVariable = controlVariable;
this.sequenceStack.push(sequence);
}
@Override
public Map<String, String> getVariables() {
return viewMap;
}
@Override
public void expand(Expandable expandable, List<String> commandLine) {
Sequence sequence = sequenceStack.peek();
sequence.expand(this, expandable, commandLine);
}
/**
* Expands {@code expandable} with the control variable set to {@code value}.
*/
void expandValue(String value, Expandable expandable, List<String> commandLine) {
viewMap.put(controlVariable, value);
expandable.expand(this, commandLine);
viewMap.remove(controlVariable);
}
/**
* Expands {@code expandable}. If the {@code expandable} is controlled by the current view's
* control variable, it will be expanded for each given sequence. Otherwise it will be
* expanded once.
*/
void expandSequence(
Collection<Sequence> sequences, Expandable expandable, List<String> commandLine) {
if (!controls(expandable)) {
// If an expandable does not reference the control variable, we only want to expand
// it once.
expandable.expand(this, commandLine);
return;
}
// Expandables that are controlled by the control variable will be expanded for each
// sequence at the current nesting level of the control variable's content.
for (Sequence sequence : sequences) {
sequenceStack.push(sequence);
expandable.expand(this, commandLine);
sequenceStack.pop();
}
}
/**
* Returns whether the expansion of {@code expandable} is controlled by the controlling
* variable of the current view.
*/
boolean controls(Expandable expandable) {
if (expandable.getControllingVariables() == null) {
return false;
}
return expandable.getControllingVariables().contains(controlVariable);
}
}
private final ImmutableMap<String, String> variables;
private final ImmutableMap<String, Sequence> sequenceVariables;
private Variables(
ImmutableMap<String, String> variables, ImmutableMap<String, Sequence> sequenceVariables) {
this.variables = variables;
this.sequenceVariables = sequenceVariables;
}
/**
* Returns a view of the current variables under the given {@code controllingVariables}.
* Verifies that all controlling variables are available.
*/
View getView(Collection<String> controllingVariables) {
Map<String, String> viewMap = new HashMap<>();
String sequenceName = null;
for (String name : controllingVariables) {
if (sequenceVariables.containsKey(name)) {
if (variables.containsKey(name)) {
throw new ExpansionException("Internal error: variable '" + name
+ "' provided both as sequence and standard variable.");
} else if (sequenceName != null) {
throw new ExpansionException(
"Invalid toolchain configuration: trying to expand two variable list in one "
+ "flag group: '" + sequenceName + "' and '" + name + "'");
} else {
sequenceName = name;
}
} else if (variables.containsKey(name)) {
viewMap.put(name, variables.get(name));
} else {
throw new ExpansionException("Invalid toolchain configuration: unknown variable '" + name
+ "' can not be expanded.");
}
}
if (sequenceName == null) {
return new FixedView(viewMap);
}
return new NestedView(viewMap, sequenceName, sequenceVariables.get(sequenceName));
}
/** Returns whether {@code variable} is set. */
boolean isAvailable(String variable) {
return variables.containsKey(variable) || sequenceVariables.containsKey(variable);
}
}
/**
* Captures the set of enabled features and action configs for a rule.
*/
@Immutable
public static class FeatureConfiguration {
private final ImmutableSet<String> enabledFeatureNames;
private final Iterable<Feature> enabledFeatures;
private final ImmutableSet<String> enabledActionConfigActionNames;
private final ImmutableMap<String, ActionConfig> actionConfigByActionName;
public FeatureConfiguration() {
this(
ImmutableList.<Feature>of(),
ImmutableList.<ActionConfig>of(),
ImmutableMap.<String, ActionConfig>of());
}
private FeatureConfiguration(
Iterable<Feature> enabledFeatures,
Iterable<ActionConfig> enabledActionConfigs,
ImmutableMap<String, ActionConfig> actionConfigByActionName) {
this.enabledFeatures = enabledFeatures;
this.actionConfigByActionName = actionConfigByActionName;
ImmutableSet.Builder<String> featureBuilder = ImmutableSet.builder();
for (Feature feature : enabledFeatures) {
featureBuilder.add(feature.getName());
}
this.enabledFeatureNames = featureBuilder.build();
ImmutableSet.Builder<String> actionConfigBuilder = ImmutableSet.builder();
for (ActionConfig actionConfig : enabledActionConfigs) {
actionConfigBuilder.add(actionConfig.getActionName());
}
this.enabledActionConfigActionNames = actionConfigBuilder.build();
}
/**
* @return whether the given {@code feature} is enabled.
*/
public boolean isEnabled(String feature) {
return enabledFeatureNames.contains(feature);
}
/**
* @return whether an action config for the blaze action with the given name is enabled.
*/
boolean actionIsConfigured(String actionName) {
return enabledActionConfigActionNames.contains(actionName);
}
/**
* @return the command line for the given {@code action}.
*/
List<String> getCommandLine(String action, Variables variables) {
List<String> commandLine = new ArrayList<>();
for (Feature feature : enabledFeatures) {
feature.expandCommandLine(action, variables, commandLine);
}
if (actionIsConfigured(action)) {
actionConfigByActionName.get(action).expandCommandLine(variables, commandLine);
}
return commandLine;
}
/**
* @return the environment variables (key/value pairs) for the given {@code action}.
*/
Map<String, String> getEnvironmentVariables(String action, Variables variables) {
ImmutableMap.Builder<String, String> envBuilder = ImmutableMap.builder();
for (Feature feature : enabledFeatures) {
feature.expandEnvironment(action, variables, envBuilder);
}
return envBuilder.build();
}
/**
* Returns a given action's tool under this FeatureConfiguration.
*/
Tool getToolForAction(String actionName) {
Preconditions.checkArgument(
actionConfigByActionName.containsKey(actionName),
"Action %s does not have an enabled configuration in the toolchain.",
actionName);
ActionConfig actionConfig = actionConfigByActionName.get(actionName);
return actionConfig.getTool(enabledFeatureNames);
}
}
/** All artifact name patterns defined in this feature configuration. */
private final ImmutableList<ArtifactNamePattern> artifactNamePatterns;
/**
* All features and action configs in the order in which they were specified in the configuration.
*
* <p>We guarantee the command line to be in the order in which the flags were specified in the
* configuration.
*/
private final ImmutableList<CrosstoolSelectable> selectables;
/**
* Maps the selectables's name to the selectable.
*/
private final ImmutableMap<String, CrosstoolSelectable> selectablesByName;
/**
* Maps an action's name to the ActionConfig.
*/
private final ImmutableMap<String, ActionConfig> actionConfigsByActionName;
/**
* Maps from a selectable to a set of all the selectables it has a direct 'implies' edge to.
*/
private final ImmutableMultimap<CrosstoolSelectable, CrosstoolSelectable> implies;
/**
* Maps from a selectable to all features that have an direct 'implies' edge to this
* selectable.
*/
private final ImmutableMultimap<CrosstoolSelectable, CrosstoolSelectable> impliedBy;
/**
* Maps from a selectable to a set of selecatable sets, where:
* <ul>
* <li>a selectable set satisfies the 'requires' condition, if all selectables in the
* selectable set are enabled</li>
* <li>the 'requires' condition is satisfied, if at least one of the selectable sets satisfies
* the 'requires' condition.</li>
* </ul>
*/
private final ImmutableMultimap<CrosstoolSelectable, ImmutableSet<CrosstoolSelectable>>
requires;
/**
* Maps from a selectable to all selectables that have a requirement referencing it.
*
* <p>This will be used to determine which selectables need to be re-checked after a selectable
* was disabled.
*/
private final ImmutableMultimap<CrosstoolSelectable, CrosstoolSelectable> requiredBy;
/**
* A cache of feature selection results, so we do not recalculate the feature selection for
* all actions.
*/
private transient LoadingCache<Collection<String>, FeatureConfiguration>
configurationCache = buildConfigurationCache();
/**
* Constructs the feature configuration from a {@code CToolchain} protocol buffer.
*
* @param toolchain the toolchain configuration as specified by the user.
* @throws InvalidConfigurationException if the configuration has logical errors.
*/
@VisibleForTesting
public CcToolchainFeatures(CToolchain toolchain) throws InvalidConfigurationException {
// Build up the feature/action config graph. We refer to features/action configs as
// 'selectables'.
// First, we build up the map of name -> selectables in one pass, so that earlier selectables
// can reference later features in their configuration.
ImmutableList.Builder<CrosstoolSelectable> selectablesBuilder = ImmutableList.builder();
HashMap<String, CrosstoolSelectable> selectablesByName = new HashMap<>();
// Also build a map from action -> action_config, for use in tool lookups
ImmutableMap.Builder<String, ActionConfig> actionConfigsByActionName = ImmutableMap.builder();
for (CToolchain.Feature toolchainFeature : toolchain.getFeatureList()) {
Feature feature = new Feature(toolchainFeature);
selectablesBuilder.add(feature);
selectablesByName.put(feature.getName(), feature);
}
for (CToolchain.ActionConfig toolchainActionConfig : toolchain.getActionConfigList()) {
ActionConfig actionConfig = new ActionConfig(toolchainActionConfig);
selectablesBuilder.add(actionConfig);
selectablesByName.put(actionConfig.getName(), actionConfig);
actionConfigsByActionName.put(actionConfig.getActionName(), actionConfig);
}
this.selectables = selectablesBuilder.build();
this.selectablesByName = ImmutableMap.copyOf(selectablesByName);
checkForActionNameDups(toolchain.getActionConfigList());
checkForActivatableDups(this.selectables);
this.actionConfigsByActionName = actionConfigsByActionName.build();
ImmutableList.Builder<ArtifactNamePattern> artifactNamePatternsBuilder =
ImmutableList.builder();
for (CToolchain.ArtifactNamePattern artifactNamePattern :
toolchain.getArtifactNamePatternList()) {
artifactNamePatternsBuilder.add(new ArtifactNamePattern(artifactNamePattern));
}
this.artifactNamePatterns = artifactNamePatternsBuilder.build();
// Next, we build up all forward references for 'implies' and 'requires' edges.
ImmutableMultimap.Builder<CrosstoolSelectable, CrosstoolSelectable> implies =
ImmutableMultimap.builder();
ImmutableMultimap.Builder<CrosstoolSelectable, ImmutableSet<CrosstoolSelectable>> requires =
ImmutableMultimap.builder();
// We also store the reverse 'implied by' and 'required by' edges during this pass.
ImmutableMultimap.Builder<CrosstoolSelectable, CrosstoolSelectable> impliedBy =
ImmutableMultimap.builder();
ImmutableMultimap.Builder<CrosstoolSelectable, CrosstoolSelectable> requiredBy =
ImmutableMultimap.builder();
for (CToolchain.Feature toolchainFeature : toolchain.getFeatureList()) {
String name = toolchainFeature.getName();
CrosstoolSelectable selectable = selectablesByName.get(name);
for (CToolchain.FeatureSet requiredFeatures : toolchainFeature.getRequiresList()) {
ImmutableSet.Builder<CrosstoolSelectable> allOf = ImmutableSet.builder();
for (String requiredName : requiredFeatures.getFeatureList()) {
CrosstoolSelectable required = getActivatableOrFail(requiredName, name);
allOf.add(required);
requiredBy.put(required, selectable);
}
requires.put(selectable, allOf.build());
}
for (String impliedName : toolchainFeature.getImpliesList()) {
CrosstoolSelectable implied = getActivatableOrFail(impliedName, name);
impliedBy.put(implied, selectable);
implies.put(selectable, implied);
}
}
for (CToolchain.ActionConfig toolchainActionConfig : toolchain.getActionConfigList()) {
String name = toolchainActionConfig.getConfigName();
CrosstoolSelectable selectable = selectablesByName.get(name);
for (String impliedName : toolchainActionConfig.getImpliesList()) {
CrosstoolSelectable implied = getActivatableOrFail(impliedName, name);
impliedBy.put(implied, selectable);
implies.put(selectable, implied);
}
}
this.implies = implies.build();
this.requires = requires.build();
this.impliedBy = impliedBy.build();
this.requiredBy = requiredBy.build();
}
private static void checkForActivatableDups(Iterable<CrosstoolSelectable> selectables)
throws InvalidConfigurationException {
Collection<String> names = new HashSet<>();
for (CrosstoolSelectable selectable : selectables) {
if (!names.add(selectable.getName())) {
throw new InvalidConfigurationException(
"Invalid toolchain configuration: feature or "
+ "action config '"
+ selectable.getName()
+ "' was specified multiple times.");
}
}
}
private static void checkForActionNameDups(Iterable<CToolchain.ActionConfig> actionConfigs)
throws InvalidConfigurationException {
Collection<String> actionNames = new HashSet<>();
for (CToolchain.ActionConfig actionConfig : actionConfigs) {
if (!actionNames.add(actionConfig.getActionName())) {
throw new InvalidConfigurationException(
"Invalid toolchain configuration: multiple action "
+ "configs for action '"
+ actionConfig.getActionName()
+ "'");
}
}
}
/**
* Assign an empty cache after default-deserializing all non-transient members.
*/
private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException {
in.defaultReadObject();
this.configurationCache = buildConfigurationCache();
}
/**
* @return an empty {@code FeatureConfiguration} cache.
*/
private LoadingCache<Collection<String>, FeatureConfiguration> buildConfigurationCache() {
return CacheBuilder.newBuilder()
// TODO(klimek): Benchmark and tweak once we support a larger configuration.
.maximumSize(10000)
.build(new CacheLoader<Collection<String>, FeatureConfiguration>() {
@Override
public FeatureConfiguration load(Collection<String> requestedFeatures) {
return computeFeatureConfiguration(requestedFeatures);
}
});
}
/**
* Given a list of {@code requestedFeatures}, returns all features that are enabled by the
* toolchain configuration.
*
* <p>A requested feature will not be enabled if the toolchain does not support it (which may
* depend on other requested features).
*
* <p>Additional features will be enabled if the toolchain supports them and they are implied by
* requested features.
*/
public FeatureConfiguration getFeatureConfiguration(Collection<String> requestedFeatures) {
return configurationCache.getUnchecked(requestedFeatures);
}
private FeatureConfiguration computeFeatureConfiguration(Collection<String> requestedFeatures) {
// Command line flags will be output in the order in which they are specified in the toolchain
// configuration.
return new FeatureSelection(requestedFeatures).run();
}
/**
* Given a list of {@code requestedFeatures}, returns all features that are enabled by the
* toolchain configuration.
*
* <p>A requested feature will not be enabled if the toolchain does not support it (which may
* depend on other requested features).
*
* <p>Additional features will be enabled if the toolchain supports them and they are implied by
* requested features.
*/
public FeatureConfiguration getFeatureConfiguration(String... requestedFeatures) {
return getFeatureConfiguration(Arrays.asList(requestedFeatures));
}
/**
* @return the selectable with the given {@code name}.
*
* @throws InvalidConfigurationException if no selectable with the given name was configured.
*/
private CrosstoolSelectable getActivatableOrFail(String name, String reference)
throws InvalidConfigurationException {
if (!selectablesByName.containsKey(name)) {
throw new InvalidConfigurationException("Invalid toolchain configuration: feature '" + name
+ "', which is referenced from feature '" + reference + "', is not defined.");
}
return selectablesByName.get(name);
}
@VisibleForTesting
Collection<String> getActivatableNames() {
Collection<String> featureNames = new HashSet<>();
for (CrosstoolSelectable selectable : selectables) {
featureNames.add(selectable.getName());
}
return featureNames;
}
/**
* Returns the artifact selected by the toolchain for the given action type and action category,
* or null if the category is not supported by the action config.
*/
String getArtifactNameForCategory(ArtifactCategory artifactCategory, String outputName)
throws ExpansionException {
PathFragment output = new PathFragment(outputName);
ArtifactNamePattern patternForCategory = null;
for (ArtifactNamePattern artifactNamePattern : artifactNamePatterns) {
if (artifactNamePattern.getArtifactCategory() == artifactCategory) {
patternForCategory = artifactNamePattern;
}
}
if (patternForCategory == null) {
throw new ExpansionException(
String.format(
MISSING_ARTIFACT_NAME_PATTERN_ERROR_TEMPLATE, artifactCategory.getCategoryName()));
}
return patternForCategory.getArtifactName(ImmutableMap.of(
"output_name", outputName,
"base_name", output.getBaseName(),
"output_directory", output.getParentDirectory().getPathString()));
}
/** Returns true if the toolchain defines an ArtifactNamePattern for the given category. */
boolean hasPatternForArtifactCategory(ArtifactCategory artifactCategory) {
for (ArtifactNamePattern artifactNamePattern : artifactNamePatterns) {
if (artifactNamePattern.getArtifactCategory() == artifactCategory) {
return true;
}
}
return false;
}
/**
* Implements the feature selection algorithm.
*
* <p>Feature selection is done by first enabling all features reachable by an 'implies' edge, and
* then iteratively pruning features that have unmet requirements.
*/
private class FeatureSelection {
/**
* The selectables Bazel would like to enable; either because they are supported and generally
* useful, or because the user required them (for example through the command line).
*/
private final ImmutableSet<CrosstoolSelectable> requestedSelectables;
/**
* The currently enabled selectable; during feature selection, we first put all selectables
* reachable via an 'implies' edge into the enabled selectable set, and than prune that set
* from selectables that have unmet requirements.
*/
private final Set<CrosstoolSelectable> enabled = new HashSet<>();
private FeatureSelection(Collection<String> requestedSelectables) {
ImmutableSet.Builder<CrosstoolSelectable> builder = ImmutableSet.builder();
for (String name : requestedSelectables) {
if (selectablesByName.containsKey(name)) {
builder.add(selectablesByName.get(name));
}
}
this.requestedSelectables = builder.build();
}
/**
* @return a {@code FeatureConfiguration} that reflects the set of activated features and
* action configs.
*/
private FeatureConfiguration run() {
for (CrosstoolSelectable selectable : requestedSelectables) {
enableAllImpliedBy(selectable);
}
disableUnsupportedActivatables();
ImmutableList.Builder<CrosstoolSelectable> enabledActivatablesInOrderBuilder =
ImmutableList.builder();
for (CrosstoolSelectable selectable : selectables) {
if (enabled.contains(selectable)) {
enabledActivatablesInOrderBuilder.add(selectable);
}
}
ImmutableList<CrosstoolSelectable> enabledActivatablesInOrder =
enabledActivatablesInOrderBuilder.build();
Iterable<Feature> enabledFeaturesInOrder =
Iterables.filter(enabledActivatablesInOrder, Feature.class);
Iterable<ActionConfig> enabledActionConfigsInOrder =
Iterables.filter(enabledActivatablesInOrder, ActionConfig.class);
return new FeatureConfiguration(
enabledFeaturesInOrder, enabledActionConfigsInOrder, actionConfigsByActionName);
}
/**
* Transitively and unconditionally enable all selectables implied by the given selectable
* and the selectable itself to the enabled selectable set.
*/
private void enableAllImpliedBy(CrosstoolSelectable selectable) {
if (enabled.contains(selectable)) {
return;
}
enabled.add(selectable);
for (CrosstoolSelectable implied : implies.get(selectable)) {
enableAllImpliedBy(implied);
}
}
/**
* Remove all unsupported features from the enabled feature set.
*/
private void disableUnsupportedActivatables() {
Queue<CrosstoolSelectable> check = new ArrayDeque<>(enabled);
while (!check.isEmpty()) {
checkActivatable(check.poll());
}
}
/**
* Check if the given selectable is still satisfied within the set of currently enabled
* selectables.
*
* <p>If it is not, remove the selectable from the set of enabled selectables, and re-check
* all selectables that may now also become disabled.
*/
private void checkActivatable(CrosstoolSelectable selectable) {
if (!enabled.contains(selectable) || isSatisfied(selectable)) {
return;
}
enabled.remove(selectable);
// Once we disable a selectable, we have to re-check all selectables that can be affected
// by that removal.
// 1. A selectable that implied the current selectable is now going to be disabled.
for (CrosstoolSelectable impliesCurrent : impliedBy.get(selectable)) {
checkActivatable(impliesCurrent);
}
// 2. A selectable that required the current selectable may now be disabled, depending on
// whether the requirement was optional.
for (CrosstoolSelectable requiresCurrent : requiredBy.get(selectable)) {
checkActivatable(requiresCurrent);
}
// 3. A selectable that this selectable implied may now be disabled if no other selectables
// also implies it.
for (CrosstoolSelectable implied : implies.get(selectable)) {
checkActivatable(implied);
}
}
/**
* @return whether all requirements of the selectable are met in the set of currently enabled
* selectables.
*/
private boolean isSatisfied(CrosstoolSelectable selectable) {
return (requestedSelectables.contains(selectable)
|| isImpliedByEnabledActivatable(selectable))
&& allImplicationsEnabled(selectable)
&& allRequirementsMet(selectable);
}
/**
* @return whether a currently enabled selectable implies the given selectable.
*/
private boolean isImpliedByEnabledActivatable(CrosstoolSelectable selectable) {
return !Collections.disjoint(impliedBy.get(selectable), enabled);
}
/**
* @return whether all implications of the given feature are enabled.
*/
private boolean allImplicationsEnabled(CrosstoolSelectable selectable) {
for (CrosstoolSelectable implied : implies.get(selectable)) {
if (!enabled.contains(implied)) {
return false;
}
}
return true;
}
/**
* @return whether all requirements are enabled.
*
* <p>This implies that for any of the selectable sets all of the specified selectable
* are enabled.
*/
private boolean allRequirementsMet(CrosstoolSelectable feature) {
if (!requires.containsKey(feature)) {
return true;
}
for (ImmutableSet<CrosstoolSelectable> requiresAllOf : requires.get(feature)) {
boolean requirementMet = true;
for (CrosstoolSelectable required : requiresAllOf) {
if (!enabled.contains(required)) {
requirementMet = false;
break;
}
}
if (requirementMet) {
return true;
}
}
return false;
}
}
}