// 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.Joiner; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.base.Supplier; import com.google.common.base.Throwables; 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.Sets; import com.google.common.collect.Sets.SetView; import com.google.common.collect.Streams; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander; import com.google.devtools.build.lib.analysis.RuleContext; import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException; import com.google.devtools.build.lib.collect.nestedset.NestedSet; import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; import com.google.devtools.build.lib.skyframe.serialization.InjectingObjectCodec; import com.google.devtools.build.lib.skyframe.serialization.ObjectCodec; import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec; import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.Strategy; import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization; import com.google.devtools.build.lib.util.Pair; import com.google.devtools.build.lib.vfs.FileSystemProvider; import com.google.devtools.build.lib.vfs.PathFragment; import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.CToolchain; import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.CToolchain.WithFeatureSet; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Queue; import java.util.Set; import java.util.Stack; import java.util.concurrent.ExecutionException; import javax.annotation.Nullable; /** * Provides access to features supported by a specific toolchain. * *

This class can be generated from the CToolchain protocol buffer. * *

TODO(bazel-team): Implement support for specifying the toolchain configuration directly from * the BUILD file. * *

TODO(bazel-team): Find a place to put the public-facing documentation and link to it from * here. * *

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. * *

This happens for example when a flag references a variable that is not provided by the * action, or when a flag group implicitly references multiple variables of sequence type. */ public static class ExpansionException extends RuntimeException { ExpansionException(String message) { super(message); } } /** Thrown when multiple features provide the same string symbol. */ public static class CollidingProvidesException extends Exception { CollidingProvidesException(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"; /** Error message thrown when a toolchain enables two features that provide the same string. */ @VisibleForTesting static final String COLLIDING_PROVIDES_ERROR = "Symbol %s is provided by all of the following features: %s"; /** * A piece of a single string value. * *

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. */ @AutoCodec(strategy = Strategy.POLYMORPHIC) interface StringChunk { public static final ObjectCodec CODEC = new CcToolchainFeatures_StringChunk_AutoCodec(); /** * Expands this chunk. * * @param variables binding of variable names to their values for a single flag expansion. * @param flag the flag content to append to. */ void expand(Variables variables, StringBuilder flag); } /** A plain text chunk of a string (containing no variables). */ @Immutable @AutoCodec @VisibleForSerialization static class StringLiteralChunk implements StringChunk, Serializable { public static final ObjectCodec CODEC = new CcToolchainFeatures_StringLiteralChunk_AutoCodec(); private final String text; @VisibleForSerialization StringLiteralChunk(String text) { this.text = text; } @Override public void expand(Variables variables, StringBuilder flag) { flag.append(text); } @Override public boolean equals(@Nullable Object object) { if (this == object) { return true; } if (object instanceof StringLiteralChunk) { StringLiteralChunk that = (StringLiteralChunk) object; return text.equals(that.text); } return false; } @Override public int hashCode() { return Objects.hash(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(Variables 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. stringBuilder.append(variables.getStringVariable(variableName)); } @Override public boolean equals(@Nullable Object object) { if (this == object) { return true; } if (object instanceof VariableChunk) { VariableChunk that = (VariableChunk) object; return variableName.equals(that.variableName); } return false; } @Override public int hashCode() { return Objects.hash(variableName); } } /** * Parser for toolchain string values. * *

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. * *

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]. * *

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}. * *

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 chunks = ImmutableList.builder(); private final ImmutableSet.Builder usedVariables = ImmutableSet.builder(); StringValueParser(String value) throws InvalidConfigurationException { this.value = value; parse(); } /** @return the parsed chunks for this string. */ ImmutableList getChunks() { return chunks.build(); } /** @return all variable names needed to expand this string. */ ImmutableSet 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. */ @AutoCodec(strategy = Strategy.POLYMORPHIC) interface Expandable { public static final ObjectCodec CODEC = new CcToolchainFeatures_Expandable_AutoCodec(); /** * Expands the current expandable under the given {@code view}, adding new flags to {@code * commandLine}. * *

The {@code variables} controls which variables are visible during the expansion and allows * to recursively expand nested flag groups. */ void expand(Variables variables, @Nullable ArtifactExpander expander, List commandLine); } /** * A single flag to be expanded under a set of variables. * *

TODO(bazel-team): Consider specializing Flag for the simple case that a flag is just a bit * of text. */ @Immutable @AutoCodec @VisibleForSerialization static class Flag implements Serializable, Expandable { public static final ObjectCodec CODEC = new CcToolchainFeatures_Flag_AutoCodec(); private final ImmutableList chunks; @VisibleForSerialization Flag(ImmutableList chunks) { this.chunks = chunks; } /** Expand this flag into a single new entry in {@code commandLine}. */ @Override public void expand( Variables variables, @Nullable ArtifactExpander expander, List commandLine) { StringBuilder flag = new StringBuilder(); for (StringChunk chunk : chunks) { chunk.expand(variables, flag); } commandLine.add(flag.toString()); } @Override public boolean equals(@Nullable Object object) { if (this == object) { return true; } if (object instanceof Flag) { Flag that = (Flag) object; return Iterables.elementsEqual(chunks, that.chunks); } return false; } @Override public int hashCode() { return Objects.hash(chunks); } } /** A single environment key/value pair to be expanded under a set of variables. */ @Immutable @AutoCodec @VisibleForSerialization static class EnvEntry implements Serializable { public static final ObjectCodec CODEC = new CcToolchainFeatures_EnvEntry_AutoCodec(); private final String key; private final ImmutableList valueChunks; private EnvEntry(CToolchain.EnvEntry envEntry) throws InvalidConfigurationException { this.key = envEntry.getKey(); StringValueParser parser = new StringValueParser(envEntry.getValue()); this.valueChunks = parser.getChunks(); } @AutoCodec.Instantiator @VisibleForSerialization EnvEntry(String key, ImmutableList valueChunks) { this.key = key; this.valueChunks = valueChunks; } /** * 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 envBuilder) { StringBuilder value = new StringBuilder(); for (StringChunk chunk : valueChunks) { chunk.expand(variables, value); } envBuilder.put(key, value.toString()); } @Override public boolean equals(@Nullable Object object) { if (this == object) { return true; } if (object instanceof EnvEntry) { EnvEntry that = (EnvEntry) object; return Objects.equals(key, that.key) && Iterables.elementsEqual(valueChunks, that.valueChunks); } return false; } @Override public int hashCode() { return Objects.hash(key, valueChunks); } } @Immutable @AutoCodec @VisibleForSerialization static class VariableWithValue { public static final ObjectCodec CODEC = new CcToolchainFeatures_VariableWithValue_AutoCodec(); public final String variable; public final String value; public VariableWithValue(String variable, String value) { this.variable = variable; this.value = value; } } /** * A group of flags. When iterateOverVariable is specified, we assume the variable is a sequence * and the flag_group will be expanded repeatedly for every value in the sequence. */ @Immutable @AutoCodec @VisibleForSerialization static class FlagGroup implements Serializable, Expandable { public static final ObjectCodec CODEC = new CcToolchainFeatures_FlagGroup_AutoCodec(); private final ImmutableList expandables; private String iterateOverVariable; private final ImmutableSet expandIfAllAvailable; private final ImmutableSet expandIfNoneAvailable; private final String expandIfTrue; private final String expandIfFalse; private final VariableWithValue expandIfEqual; private FlagGroup(CToolchain.FlagGroup flagGroup) throws InvalidConfigurationException { ImmutableList.Builder expandables = ImmutableList.builder(); Collection flags = flagGroup.getFlagList(); Collection 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())); } for (CToolchain.FlagGroup group : groups) { FlagGroup subgroup = new FlagGroup(group); expandables.add(subgroup); } if (flagGroup.hasIterateOver()) { this.iterateOverVariable = flagGroup.getIterateOver(); } this.expandables = expandables.build(); this.expandIfAllAvailable = ImmutableSet.copyOf(flagGroup.getExpandIfAllAvailableList()); this.expandIfNoneAvailable = ImmutableSet.copyOf(flagGroup.getExpandIfNoneAvailableList()); this.expandIfTrue = Strings.emptyToNull(flagGroup.getExpandIfTrue()); this.expandIfFalse = Strings.emptyToNull(flagGroup.getExpandIfFalse()); if (flagGroup.hasExpandIfEqual()) { this.expandIfEqual = new VariableWithValue( flagGroup.getExpandIfEqual().getVariable(), flagGroup.getExpandIfEqual().getValue()); } else { this.expandIfEqual = null; } } @AutoCodec.Instantiator @VisibleForSerialization public FlagGroup( ImmutableList expandables, String iterateOverVariable, ImmutableSet expandIfAllAvailable, ImmutableSet expandIfNoneAvailable, String expandIfTrue, String expandIfFalse, VariableWithValue expandIfEqual) { this.expandables = expandables; this.iterateOverVariable = iterateOverVariable; this.expandIfAllAvailable = expandIfAllAvailable; this.expandIfNoneAvailable = expandIfNoneAvailable; this.expandIfTrue = expandIfTrue; this.expandIfFalse = expandIfFalse; this.expandIfEqual = expandIfEqual; } @Override public void expand( Variables variables, @Nullable ArtifactExpander expander, final List commandLine) { if (!canBeExpanded(variables, expander)) { return; } if (iterateOverVariable != null) { for (Variables.VariableValue variableValue : variables.getSequenceVariable(iterateOverVariable, expander)) { Variables nestedVariables = new Variables(variables, iterateOverVariable, variableValue); for (Expandable expandable : expandables) { expandable.expand(nestedVariables, expander, commandLine); } } } else { for (Expandable expandable : expandables) { expandable.expand(variables, expander, commandLine); } } } private boolean canBeExpanded(Variables variables, @Nullable ArtifactExpander expander) { for (String variable : expandIfAllAvailable) { if (!variables.isAvailable(variable, expander)) { return false; } } for (String variable : expandIfNoneAvailable) { if (variables.isAvailable(variable, expander)) { return false; } } if (expandIfTrue != null && (!variables.isAvailable(expandIfTrue, expander) || !variables.getVariable(expandIfTrue).isTruthy())) { return false; } if (expandIfFalse != null && (!variables.isAvailable(expandIfFalse, expander) || variables.getVariable(expandIfFalse).isTruthy())) { return false; } if (expandIfEqual != null && (!variables.isAvailable(expandIfEqual.variable, expander) || !variables .getVariable(expandIfEqual.variable) .getStringValue(expandIfEqual.variable) .equals(expandIfEqual.value))) { return false; } return true; } /** * Expands all flags in this group and adds them to {@code commandLine}. * *

The flags of the group will be expanded either: * *

*/ private void expandCommandLine( Variables variables, @Nullable ArtifactExpander expander, final List commandLine) { expand(variables, expander, commandLine); } @Override public boolean equals(@Nullable Object object) { if (this == object) { return true; } if (object instanceof FlagGroup) { FlagGroup that = (FlagGroup) object; return Iterables.elementsEqual(expandables, that.expandables) && Objects.equals(iterateOverVariable, that.iterateOverVariable) && Iterables.elementsEqual(expandIfAllAvailable, that.expandIfAllAvailable) && Iterables.elementsEqual(expandIfNoneAvailable, that.expandIfNoneAvailable) && Objects.equals(expandIfTrue, that.expandIfTrue) && Objects.equals(expandIfFalse, that.expandIfFalse) && Objects.equals(expandIfEqual, that.expandIfEqual); } return false; } @Override public int hashCode() { return Objects.hash( expandables, iterateOverVariable, expandIfAllAvailable, expandIfNoneAvailable, expandIfTrue, expandIfFalse, expandIfEqual); } } private static boolean isWithFeaturesSatisfied( Collection withFeatureSets, Set enabledFeatureNames) { if (withFeatureSets.isEmpty()) { return true; } for (CToolchain.WithFeatureSet featureSet : withFeatureSets) { boolean negativeMatch = featureSet .getNotFeatureList() .stream() .anyMatch(notFeature -> enabledFeatureNames.contains(notFeature)); boolean positiveMatch = enabledFeatureNames.containsAll(featureSet.getFeatureList()); if (!negativeMatch && positiveMatch) { return true; } } return false; } /** Groups a set of flags to apply for certain actions. */ @Immutable @AutoCodec @VisibleForSerialization static class FlagSet implements Serializable { public static final ObjectCodec CODEC = new CcToolchainFeatures_FlagSet_AutoCodec(); private final ImmutableSet actions; private final ImmutableSet expandIfAllAvailable; private final ImmutableSet withFeatureSets; private final ImmutableList 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 actions) throws InvalidConfigurationException { this.actions = actions; this.expandIfAllAvailable = ImmutableSet.copyOf(flagSet.getExpandIfAllAvailableList()); this.withFeatureSets = ImmutableSet.copyOf(flagSet.getWithFeatureList()); ImmutableList.Builder builder = ImmutableList.builder(); for (CToolchain.FlagGroup flagGroup : flagSet.getFlagGroupList()) { builder.add(new FlagGroup(flagGroup)); } this.flagGroups = builder.build(); } @AutoCodec.Instantiator @VisibleForSerialization FlagSet( ImmutableSet actions, ImmutableSet expandIfAllAvailable, ImmutableSet withFeatureSets, ImmutableList flagGroups) { this.actions = actions; this.expandIfAllAvailable = expandIfAllAvailable; this.withFeatureSets = withFeatureSets; this.flagGroups = flagGroups; } /** Adds the flags that apply to the given {@code action} to {@code commandLine}. */ private void expandCommandLine( String action, Variables variables, Set enabledFeatureNames, @Nullable ArtifactExpander expander, List commandLine) { for (String variable : expandIfAllAvailable) { if (!variables.isAvailable(variable, expander)) { return; } } if (!isWithFeaturesSatisfied(withFeatureSets, enabledFeatureNames)) { return; } if (!actions.contains(action)) { return; } for (FlagGroup flagGroup : flagGroups) { flagGroup.expandCommandLine(variables, expander, commandLine); } } @Override public boolean equals(@Nullable Object object) { if (object instanceof FlagSet) { FlagSet that = (FlagSet) object; return Iterables.elementsEqual(actions, that.actions) && Iterables.elementsEqual(expandIfAllAvailable, that.expandIfAllAvailable) && Iterables.elementsEqual(withFeatureSets, that.withFeatureSets) && Iterables.elementsEqual(flagGroups, that.flagGroups); } return false; } @Override public int hashCode() { return Objects.hash(actions, expandIfAllAvailable, withFeatureSets, flagGroups); } } /** Groups a set of environment variables to apply for certain actions. */ @Immutable @AutoCodec @VisibleForSerialization static class EnvSet implements Serializable { public static final ObjectCodec CODEC = new CcToolchainFeatures_EnvSet_AutoCodec(); private final ImmutableSet actions; private final ImmutableList envEntries; private final ImmutableSet withFeatureSets; private EnvSet(CToolchain.EnvSet envSet) throws InvalidConfigurationException { this.actions = ImmutableSet.copyOf(envSet.getActionList()); ImmutableList.Builder builder = ImmutableList.builder(); for (CToolchain.EnvEntry envEntry : envSet.getEnvEntryList()) { builder.add(new EnvEntry(envEntry)); } this.envEntries = builder.build(); this.withFeatureSets = ImmutableSet.copyOf(envSet.getWithFeatureList()); } @AutoCodec.Instantiator @VisibleForSerialization EnvSet( ImmutableSet actions, ImmutableList envEntries, ImmutableSet withFeatureSets) { this.actions = actions; this.envEntries = envEntries; this.withFeatureSets = withFeatureSets; } /** * Adds the environment key/value pairs that apply to the given {@code action} to * {@code envBuilder}. */ private void expandEnvironment( String action, Variables variables, Set enabledFeatureNames, ImmutableMap.Builder envBuilder) { if (!actions.contains(action)) { return; } if (!isWithFeaturesSatisfied(withFeatureSets, enabledFeatureNames)) { return; } for (EnvEntry envEntry : envEntries) { envEntry.addEnvEntry(variables, envBuilder); } } @Override public boolean equals(@Nullable Object object) { if (this == object) { return true; } if (object instanceof EnvSet) { EnvSet that = (EnvSet) object; return Iterables.elementsEqual(actions, that.actions) && Iterables.elementsEqual(envEntries, that.envEntries) && Iterables.elementsEqual(withFeatureSets, that.withFeatureSets); } return false; } @Override public int hashCode() { return Objects.hash(actions, envEntries, withFeatureSets); } } /** * An interface for classes representing crosstool messages that can activate eachother * using 'requires' and 'implies' semantics. * *

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 @AutoCodec @VisibleForSerialization static class Feature implements Serializable, CrosstoolSelectable { public static final ObjectCodec CODEC = new CcToolchainFeatures_Feature_AutoCodec(); private final String name; private final ImmutableList flagSets; private final ImmutableList envSets; private Feature(CToolchain.Feature feature) throws InvalidConfigurationException { this.name = feature.getName(); ImmutableList.Builder flagSetBuilder = ImmutableList.builder(); for (CToolchain.FlagSet flagSet : feature.getFlagSetList()) { flagSetBuilder.add(new FlagSet(flagSet)); } this.flagSets = flagSetBuilder.build(); ImmutableList.Builder envSetBuilder = ImmutableList.builder(); for (CToolchain.EnvSet flagSet : feature.getEnvSetList()) { envSetBuilder.add(new EnvSet(flagSet)); } this.envSets = envSetBuilder.build(); } @AutoCodec.Instantiator @VisibleForSerialization Feature(String name, ImmutableList flagSets, ImmutableList envSets) { this.name = name; this.flagSets = flagSets; this.envSets = envSets; } @Override public String getName() { return name; } /** Adds environment variables for the given action to the provided builder. */ private void expandEnvironment( String action, Variables variables, Set enabledFeatureNames, ImmutableMap.Builder envBuilder) { for (EnvSet envSet : envSets) { envSet.expandEnvironment(action, variables, enabledFeatureNames, envBuilder); } } /** Adds the flags that apply to the given {@code action} to {@code commandLine}. */ private void expandCommandLine( String action, Variables variables, Set enabledFeatureNames, @Nullable ArtifactExpander expander, List commandLine) { for (FlagSet flagSet : flagSets) { flagSet.expandCommandLine(action, variables, enabledFeatureNames, expander, commandLine); } } @Override public boolean equals(@Nullable Object object) { if (this == object) { return true; } if (object instanceof Feature) { Feature that = (Feature) object; return name.equals(that.name) && Iterables.elementsEqual(flagSets, that.flagSets) && Iterables.elementsEqual(envSets, that.envSets); } return false; } @Override public int hashCode() { return Objects.hash(name, flagSets, envSets); } } /** * 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 executionRequirements; private Tool(CToolchain.Tool tool) { toolPathString = tool.getToolPath(); executionRequirements = ImmutableSet.copyOf(tool.getExecutionRequirementList()); } @VisibleForTesting public Tool(String toolPathString, ImmutableSet 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 getExecutionRequirements() { return executionRequirements; } } /** * A container for information on a particular blaze action. * *

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. * *

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. * *

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 @AutoCodec static class ActionConfig implements Serializable, CrosstoolSelectable { public static final ObjectCodec CODEC = new CcToolchainFeatures_ActionConfig_AutoCodec(); 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 tools; private final ImmutableList flagSets; private ActionConfig(CToolchain.ActionConfig actionConfig) throws InvalidConfigurationException { this.configName = actionConfig.getConfigName(); this.actionName = actionConfig.getActionName(); this.tools = actionConfig.getToolList(); ImmutableList.Builder 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(); } @AutoCodec.Instantiator @VisibleForSerialization ActionConfig( String configName, String actionName, List tools, ImmutableList flagSets) { this.configName = configName; this.actionName = actionName; this.tools = tools; this.flagSets = flagSets; } @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 enabledFeatureNames) { Optional tool = Iterables.tryFind( tools, input -> { return isWithFeaturesSatisfied(input.getWithFeatureList(), enabledFeatureNames); }); 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, Set enabledFeatureNames, @Nullable ArtifactExpander expander, List commandLine) { for (FlagSet flagSet : flagSets) { flagSet.expandCommandLine( actionName, variables, enabledFeatureNames, expander, commandLine); } } } /** A description of how artifacts of a certain type are named. */ @Immutable private static class ArtifactNamePattern { private final ArtifactCategory artifactCategory; private final ImmutableList 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 InvalidConfigurationException( String.format( "Invalid toolchain configuration: Artifact category %s not recognized", artifactNamePattern.getCategoryName())); } this.artifactCategory = foundCategory; StringValueParser parser = new StringValueParser(artifactNamePattern.getPattern()); 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 variables) { StringBuilder resultBuilder = new StringBuilder(); Variables artifactNameVariables = new Variables.Builder().addAllStringVariables(variables).build(); for (StringChunk chunk : chunks) { chunk.expand(artifactNameVariables, resultBuilder); } String result = resultBuilder.toString(); return result.charAt(0) == '/' ? result.substring(1) : result; } } /** * Configured build variables usable by the toolchain configuration. * *

TODO(b/32655571): Investigate cleanup once implicit iteration is not needed. Variables * instance could serve as a top level View used to expand all flag_groups. */ @Immutable @AutoCodec(dependency = FileSystemProvider.class) public static class Variables { public static final InjectingObjectCodec CODEC = new CcToolchainFeatures_Variables_AutoCodec(); /** An empty variables instance. */ public static final Variables EMPTY = new Variables.Builder().build(); /** * Retrieves a {@link StringSequence} variable named {@code variableName} from {@code variables} * and converts it into a list of plain strings. * *

Throws {@link ExpansionException} when the variable is not a {@link StringSequence}. */ public static final ImmutableList toStringList( CcToolchainFeatures.Variables variables, String variableName) { return Streams .stream(variables.getSequenceVariable(variableName)) .map(variable -> variable.getStringValue(variableName)) .collect(ImmutableList.toImmutableList()); } public Variables getParent() { return parent; } /** * Value of a build variable exposed to the CROSSTOOL used for flag expansion. * *

{@link VariableValue} represent either primitive values or an arbitrarily deeply nested * recursive structures or sequences. Since there are builds with millions of values, some * implementations might exist only to optimize memory usage. * *

Implementations must be immutable and without any side-effects. They will be expanded and * queried multiple times. */ @AutoCodec(strategy = Strategy.POLYMORPHIC, dependency = FileSystemProvider.class) interface VariableValue { public static final InjectingObjectCodec CODEC = new CcToolchainFeatures_Variables_VariableValue_AutoCodec(); /** * Returns string value of the variable, if the variable type can be converted to string (e.g. * StringValue), or throw exception if it cannot (e.g. Sequence). * * @param variableName name of the variable value at hand, for better exception message. */ String getStringValue(String variableName); /** * Returns Iterable value of the variable, if the variable type can be converted to a Iterable * (e.g. Sequence), or throw exception if it cannot (e.g. StringValue). * * @param variableName name of the variable value at hand, for better exception message. */ Iterable getSequenceValue(String variableName); /** * Returns value of the field, if the variable is of struct type or throw exception if it is * not or no such field exists. * * @param variableName name of the variable value at hand, for better exception message. */ VariableValue getFieldValue(String variableName, String field); VariableValue getFieldValue( String variableName, String field, @Nullable ArtifactExpander expander); /** Returns true if the variable is truthy */ boolean isTruthy(); } /** * Adapter for {@link VariableValue} predefining error handling methods. Override {@link * #getVariableTypeName()}, {@link #isTruthy()}, and one of {@link #getFieldValue(String, * String)}, {@link #getSequenceValue(String)}, or {@link #getStringValue(String)}, and you'll * get error handling for the other methods for free. */ abstract static class VariableValueAdapter implements VariableValue { /** Returns human-readable variable type name to be used in error messages. */ public abstract String getVariableTypeName(); @Override public abstract boolean isTruthy(); @Override public VariableValue getFieldValue(String variableName, String field) { return getFieldValue(variableName, field, null); } @Override public VariableValue getFieldValue( String variableName, String field, @Nullable ArtifactExpander expander) { throw new ExpansionException( String.format( "Invalid toolchain configuration: Cannot expand variable '%s.%s': variable '%s' is " + "%s, expected structure", variableName, field, variableName, getVariableTypeName())); } @Override public String getStringValue(String variableName) { throw new ExpansionException( String.format( "Invalid toolchain configuration: Cannot expand variable '%s': expected string, " + "found %s", variableName, getVariableTypeName())); } @Override public Iterable getSequenceValue(String variableName) { throw new ExpansionException( String.format( "Invalid toolchain configuration: Cannot expand variable '%s': expected sequence, " + "found %s", variableName, getVariableTypeName())); } } /** Interface for VariableValue builders */ public interface VariableValueBuilder { VariableValue build(); } /** Builder for StringSequence. */ public static class StringSequenceBuilder implements VariableValueBuilder { private final ImmutableList.Builder values = ImmutableList.builder(); /** Adds a value to the sequence. */ public StringSequenceBuilder addValue(String value) { values.add(value); return this; } /** Returns an immutable string sequence. */ @Override public StringSequence build() { return new StringSequence(values.build()); } } /** Builder for Sequence. */ public static class SequenceBuilder implements VariableValueBuilder { private final ImmutableList.Builder values = ImmutableList.builder(); /** Adds a value to the sequence. */ public SequenceBuilder addValue(VariableValue value) { values.add(value); return this; } /** Adds a value to the sequence. */ public SequenceBuilder addValue(VariableValueBuilder value) { Preconditions.checkArgument(value != null, "Cannot use null builder for a sequence value"); values.add(value.build()); return this; } /** Returns an immutable sequence. */ @Override public Sequence build() { return new Sequence(values.build()); } } /** Builder for StructureValue. */ public static class StructureBuilder implements VariableValueBuilder { private final ImmutableMap.Builder fields = ImmutableMap.builder(); /** Adds a field to the structure. */ public StructureBuilder addField(String name, VariableValue value) { fields.put(name, value); return this; } /** Adds a field to the structure. */ public StructureBuilder addField(String name, VariableValueBuilder valueBuilder) { Preconditions.checkArgument( valueBuilder != null, "Cannot use null builder to get a field value for field '%s'", name); fields.put(name, valueBuilder.build()); return this; } /** Adds a field to the structure. */ public StructureBuilder addField(String name, String value) { fields.put(name, new StringValue(value)); return this; } /** Adds a field to the structure. */ public StructureBuilder addField(String name, ImmutableList values) { fields.put(name, new StringSequence(values)); return this; } /** Returns an immutable structure. */ @Override public StructureValue build() { return new StructureValue(fields.build()); } } /** * Lazily computed string sequence. Exists as a memory optimization. Make sure the {@param * supplier} doesn't capture anything that shouldn't outlive analysis phase (e.g. {@link * RuleContext}). */ @AutoCodec @VisibleForSerialization static final class LazyStringSequence extends VariableValueAdapter { public static final ObjectCodec CODEC = new CcToolchainFeatures_Variables_LazyStringSequence_AutoCodec(); private final Supplier> supplier; @VisibleForSerialization LazyStringSequence(Supplier> supplier) { this.supplier = Preconditions.checkNotNull(supplier); } @Override public Iterable getSequenceValue(String variableName) { return supplier .get() .stream() .map(flag -> new StringValue(flag)) .collect(ImmutableList.toImmutableList()); } @Override public String getVariableTypeName() { return Sequence.SEQUENCE_VARIABLE_TYPE_NAME; } @Override public boolean isTruthy() { return !supplier.get().isEmpty(); } } /** * A sequence of structure values. Exists as a memory optimization - a typical build can contain * millions of feature values, so getting rid of the overhead of {@code StructureValue} objects * significantly reduces memory overhead. */ @Immutable @AutoCodec(dependency = FileSystemProvider.class) public static class LibraryToLinkValue extends VariableValueAdapter { public static final InjectingObjectCodec CODEC = new CcToolchainFeatures_Variables_LibraryToLinkValue_AutoCodec(); public static final String OBJECT_FILES_FIELD_NAME = "object_files"; public static final String NAME_FIELD_NAME = "name"; public static final String TYPE_FIELD_NAME = "type"; public static final String IS_WHOLE_ARCHIVE_FIELD_NAME = "is_whole_archive"; private static final String LIBRARY_TO_LINK_VARIABLE_TYPE_NAME = "structure (LibraryToLink)"; @VisibleForSerialization enum Type { OBJECT_FILE("object_file"), OBJECT_FILE_GROUP("object_file_group"), INTERFACE_LIBRARY("interface_library"), STATIC_LIBRARY("static_library"), DYNAMIC_LIBRARY("dynamic_library"), VERSIONED_DYNAMIC_LIBRARY("versioned_dynamic_library"); private final String name; Type(String name) { this.name = name; } } private final String name; private final Artifact directory; private final ImmutableList objectFiles; private final boolean isWholeArchive; private final Type type; public static LibraryToLinkValue forDynamicLibrary(String name) { return new LibraryToLinkValue( Preconditions.checkNotNull(name), null, null, false, Type.DYNAMIC_LIBRARY); } public static LibraryToLinkValue forVersionedDynamicLibrary( String name) { return new LibraryToLinkValue( Preconditions.checkNotNull(name), null, null, false, Type.VERSIONED_DYNAMIC_LIBRARY); } public static LibraryToLinkValue forInterfaceLibrary(String name) { return new LibraryToLinkValue( Preconditions.checkNotNull(name), null, null, false, Type.INTERFACE_LIBRARY); } public static LibraryToLinkValue forStaticLibrary(String name, boolean isWholeArchive) { return new LibraryToLinkValue( Preconditions.checkNotNull(name), null, null, isWholeArchive, Type.STATIC_LIBRARY); } public static LibraryToLinkValue forObjectFile(String name, boolean isWholeArchive) { return new LibraryToLinkValue( Preconditions.checkNotNull(name), null, null, isWholeArchive, Type.OBJECT_FILE); } public static LibraryToLinkValue forObjectFileGroup( ImmutableList objects, boolean isWholeArchive) { Preconditions.checkNotNull(objects); Preconditions.checkArgument(!objects.isEmpty()); return new LibraryToLinkValue(null, null, objects, isWholeArchive, Type.OBJECT_FILE_GROUP); } public static LibraryToLinkValue forObjectDirectory( Artifact directory, boolean isWholeArchive) { Preconditions.checkNotNull(directory); Preconditions.checkArgument(directory.isTreeArtifact()); return new LibraryToLinkValue( null, directory, null, isWholeArchive, Type.OBJECT_FILE_GROUP); } @VisibleForSerialization LibraryToLinkValue( String name, Artifact directory, ImmutableList objectFiles, boolean isWholeArchive, Type type) { this.name = name; this.directory = directory; this.objectFiles = objectFiles; this.isWholeArchive = isWholeArchive; this.type = type; } @Override public VariableValue getFieldValue( String variableName, String field, @Nullable ArtifactExpander expander) { Preconditions.checkNotNull(field); if (NAME_FIELD_NAME.equals(field) && !type.equals(Type.OBJECT_FILE_GROUP)) { return new StringValue(name); } else if (OBJECT_FILES_FIELD_NAME.equals(field) && type.equals(Type.OBJECT_FILE_GROUP)) { ImmutableList.Builder expandedObjectFiles = ImmutableList.builder(); if (objectFiles != null) { expandedObjectFiles.addAll(objectFiles); } else if (directory != null) { if (expander != null) { List artifacts = new ArrayList<>(); expander.expand(directory, artifacts); expandedObjectFiles.addAll( Iterables.transform(artifacts, artifact -> artifact.getExecPathString())); } else { expandedObjectFiles.add(directory.getExecPathString()); } } return new StringSequence(expandedObjectFiles.build()); } else if (TYPE_FIELD_NAME.equals(field)) { return new StringValue(type.name); } else if (IS_WHOLE_ARCHIVE_FIELD_NAME.equals(field)) { return new IntegerValue(isWholeArchive ? 1 : 0); } else { return null; } } @Override public String getVariableTypeName() { return LIBRARY_TO_LINK_VARIABLE_TYPE_NAME; } @Override public boolean isTruthy() { return true; } } /** Sequence of arbitrary VariableValue objects. */ @Immutable @AutoCodec(dependency = FileSystemProvider.class) @VisibleForSerialization static final class Sequence extends VariableValueAdapter { public static final InjectingObjectCodec CODEC = new CcToolchainFeatures_Variables_Sequence_AutoCodec(); private static final String SEQUENCE_VARIABLE_TYPE_NAME = "sequence"; private final ImmutableList values; public Sequence(ImmutableList values) { this.values = values; } @Override public Iterable getSequenceValue(String variableName) { return values; } @Override public String getVariableTypeName() { return SEQUENCE_VARIABLE_TYPE_NAME; } @Override public boolean isTruthy() { return values.isEmpty(); } } /** * A sequence of structure values. Exists as a memory optimization - a typical build can contain * millions of feature values, so getting rid of the overhead of {@code StructureValue} objects * significantly reduces memory overhead. */ @Immutable @AutoCodec(dependency = FileSystemProvider.class) @VisibleForSerialization static final class StructureSequence extends VariableValueAdapter { public static final InjectingObjectCodec CODEC = new CcToolchainFeatures_Variables_StructureSequence_AutoCodec(); private final ImmutableList> values; @VisibleForSerialization StructureSequence(ImmutableList> values) { Preconditions.checkNotNull(values); this.values = values; } @Override public Iterable getSequenceValue(String variableName) { final ImmutableList.Builder sequences = ImmutableList.builder(); for (ImmutableMap value : values) { sequences.add(new StructureValue(value)); } return sequences.build(); } @Override public String getVariableTypeName() { return Sequence.SEQUENCE_VARIABLE_TYPE_NAME; } @Override public boolean isTruthy() { return !values.isEmpty(); } } /** * 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 StringValue} * objects significantly reduces memory overhead. */ @Immutable @AutoCodec static final class StringSequence extends VariableValueAdapter { public static final ObjectCodec CODEC = new CcToolchainFeatures_Variables_StringSequence_AutoCodec(); private final Iterable values; public StringSequence(Iterable values) { Preconditions.checkNotNull(values); this.values = values; } @Override public Iterable getSequenceValue(String variableName) { final ImmutableList.Builder sequences = ImmutableList.builder(); for (String value : values) { sequences.add(new StringValue(value)); } return sequences.build(); } @Override public String getVariableTypeName() { return Sequence.SEQUENCE_VARIABLE_TYPE_NAME; } @Override public boolean isTruthy() { return !Iterables.isEmpty(values); } } /** * Single structure value. Be careful not to create sequences of single structures, as the * memory overhead is prohibitively big. Use optimized {@link StructureSequence} instead. */ @Immutable @AutoCodec(dependency = FileSystemProvider.class) @VisibleForSerialization static final class StructureValue extends VariableValueAdapter { public static final InjectingObjectCodec CODEC = new CcToolchainFeatures_Variables_StructureValue_AutoCodec(); private static final String STRUCTURE_VARIABLE_TYPE_NAME = "structure"; private final ImmutableMap value; public StructureValue(ImmutableMap value) { this.value = value; } @Override public VariableValue getFieldValue( String variableName, String field, @Nullable ArtifactExpander expander) { if (value.containsKey(field)) { return value.get(field); } else { return null; } } @Override public String getVariableTypeName() { return STRUCTURE_VARIABLE_TYPE_NAME; } @Override public boolean isTruthy() { return !value.isEmpty(); } } /** * The leaves in the variable sequence node tree are simple string values. Note that this should * never live outside of {@code expand}, as the object overhead is prohibitively expensive. */ @Immutable @AutoCodec @VisibleForSerialization static final class StringValue extends VariableValueAdapter { public static final ObjectCodec CODEC = new CcToolchainFeatures_Variables_StringValue_AutoCodec(); private static final String STRING_VARIABLE_TYPE_NAME = "string"; private final String value; public StringValue(String value) { Preconditions.checkNotNull(value, "Cannot create StringValue from null"); this.value = value; } @Override public String getStringValue(String variableName) { return value; } @Override public String getVariableTypeName() { return STRING_VARIABLE_TYPE_NAME; } @Override public boolean isTruthy() { return !value.isEmpty(); } } /** * The leaves in the variable sequence node tree are simple integer values. Note that this * should never live outside of {@code expand}, as the object overhead is prohibitively * expensive. */ @Immutable @AutoCodec static final class IntegerValue extends VariableValueAdapter { public static final ObjectCodec CODEC = new CcToolchainFeatures_Variables_IntegerValue_AutoCodec(); private static final String INTEGER_VALUE_TYPE_NAME = "integer"; private final int value; public IntegerValue(int value) { this.value = value; } @Override public String getStringValue(String variableName) { return Integer.toString(value); } @Override public String getVariableTypeName() { return INTEGER_VALUE_TYPE_NAME; } @Override public boolean isTruthy() { return value != 0; } } /** * Builder for {@code Variables}. */ // TODO(b/65472725): Forbid sequences with empty string in them. public static class Builder { private final Map variablesMap = new LinkedHashMap<>(); private final Map stringVariablesMap = new LinkedHashMap<>(); private final Variables parent; public Builder() { parent = null; } public Builder(@Nullable Variables parent) { this.parent = parent; } /** Add an integer variable that expands {@code name} to {@code value}. */ public Builder addIntegerVariable(String name, int value) { variablesMap.put(name, new IntegerValue(value)); return this; } /** Add a string variable that expands {@code name} to {@code value}. */ public Builder addStringVariable(String name, String value) { checkVariableNotPresentAlready(name); Preconditions.checkNotNull( value, "Cannot set null as a value for variable '%s'", name); stringVariablesMap.put(name, value); return this; } /** Overrides a variable to expands {@code name} to {@code value} instead. */ public Builder overrideStringVariable(String name, String value) { Preconditions.checkNotNull( value, "Cannot set null as a value for variable '%s'", name); stringVariablesMap.put(name, value); return this; } /** Overrides a variable to expands {@code name} to {@code value} instead. */ public Builder overrideLazyStringSequenceVariable( String name, Supplier> supplier) { Preconditions.checkNotNull(supplier, "Cannot set null as a value for variable '%s'", name); variablesMap.put(name, new LazyStringSequence(supplier)); return this; } /** * Add a sequence variable that expands {@code name} to {@code values}. * *

Accepts values as ImmutableSet. As ImmutableList has smaller memory footprint, we copy * the values into a new list. */ public Builder addStringSequenceVariable(String name, ImmutableSet values) { checkVariableNotPresentAlready(name); Preconditions.checkNotNull(values, "Cannot set null as a value for variable '%s'", name); ImmutableList.Builder builder = ImmutableList.builder(); builder.addAll(values); variablesMap.put(name, new StringSequence(builder.build())); return this; } /** * Add a sequence variable that expands {@code name} to {@code values}. * *

Accepts values as NestedSet. Nested set is stored directly, not cloned, not flattened. */ public Builder addStringSequenceVariable(String name, NestedSet values) { checkVariableNotPresentAlready(name); Preconditions.checkNotNull(values, "Cannot set null as a value for variable '%s'", name); variablesMap.put(name, new StringSequence(values)); return this; } /** * Add a sequence variable that expands {@code name} to {@code values}. * *

Accepts values as Iterable. The iterable is stored directly, not cloned, not iterated. * Be mindful of memory consumption of the particular Iterable. Prefer ImmutableList, or * be sure that the iterable always returns the same elements in the same order, without any * side effects. */ public Builder addStringSequenceVariable(String name, Iterable values) { checkVariableNotPresentAlready(name); Preconditions.checkNotNull(values, "Cannot set null as a value for variable '%s'", name); variablesMap.put(name, new StringSequence(values)); return this; } public Builder addLazyStringSequenceVariable( String name, Supplier> supplier) { checkVariableNotPresentAlready(name); Preconditions.checkNotNull(supplier, "Cannot set null as a value for variable '%s'", name); variablesMap.put(name, new LazyStringSequence(supplier)); return this; } /** * Add a variable built using {@code VariableValueBuilder} api that expands {@code name} to * the value returned by the {@code builder}. */ public Builder addCustomBuiltVariable(String name, Variables.VariableValueBuilder builder) { checkVariableNotPresentAlready(name); Preconditions.checkNotNull( builder, "Cannot use null builder to get variable value for variable '%s'", name); variablesMap.put(name, builder.build()); return this; } /** Add all string variables in a map. */ public Builder addAllStringVariables(Map variables) { for (String name : variables.keySet()) { checkVariableNotPresentAlready(name); } stringVariablesMap.putAll(variables); return this; } private void checkVariableNotPresentAlready(String name) { Preconditions.checkNotNull(name); Preconditions.checkArgument( !variablesMap.containsKey(name), "Cannot overwrite variable '%s'", name); Preconditions.checkArgument( !stringVariablesMap.containsKey(name), "Cannot overwrite variable '%s'", name); } /** * Adds all variables to this builder. Cannot override already added variables. Does not add * variables defined in the {@code parent} variables. */ public Builder addAllNonTransitive(Variables variables) { SetView intersection = Sets.intersection(variables.variablesMap.keySet(), variablesMap.keySet()); SetView stringIntersection = Sets.intersection(variables.stringVariablesMap.keySet(), stringVariablesMap.keySet()); Preconditions.checkArgument( intersection.isEmpty(), "Cannot overwrite existing variables: %s", intersection); Preconditions.checkArgument( stringIntersection.isEmpty(), "Cannot overwrite existing variables: %s", stringIntersection); this.variablesMap.putAll(variables.variablesMap); this.stringVariablesMap.putAll(variables.stringVariablesMap); return this; } /** * Add all variables to this builder, possibly overriding variables already present in the * builder. Use cautiously, prefer {@code addAllNonTransitive} if possible. * TODO(b/32893861) Clean 'module_files' to be registered only once and remove this method. */ Builder addAndOverwriteAll(Variables overwrittenVariables) { this.variablesMap.putAll(overwrittenVariables.variablesMap); this.stringVariablesMap.putAll(overwrittenVariables.stringVariablesMap); return this; } /** @return a new {@Variables} object. */ public Variables build() { return new Variables( parent, ImmutableMap.copyOf(variablesMap), ImmutableMap.copyOf(stringVariablesMap)); } } /** * A group of extra {@code Variable} instances, packaged as logic for adding to a * {@code Builder} */ public interface VariablesExtension { void addVariables(Builder builder); } private final ImmutableMap variablesMap; private final ImmutableMap stringVariablesMap; private final Variables parent; @AutoCodec.Instantiator @VisibleForSerialization Variables( Variables parent, ImmutableMap variablesMap, ImmutableMap stringVariablesMap) { this.variablesMap = variablesMap; this.stringVariablesMap = stringVariablesMap; this.parent = parent; } /** * Creates a variables instance nested under the @param parent, and binds variable named @param * name to @param value */ private Variables(Variables parent, String name, VariableValue value) { this.variablesMap = ImmutableMap.of(name, value); this.stringVariablesMap = ImmutableMap.of(); this.parent = parent; } /** * Get a variable value named @param name. Supports accessing fields in structures (e.g. * 'libraries_to_link.interface_libraries') * * @throws ExpansionException when no such variable or no such field are present, or when * accessing a field of non-structured variable */ public VariableValue getVariable(String name) { return lookupVariable(name, true, null); } public VariableValue getVariable(String name, @Nullable ArtifactExpander expander) { return lookupVariable(name, true, expander); } /** * Lookup a variable named @param name or return a reason why the variable was not found. * Supports accessing fields in structures. * * @return Pair returns either (variable value, null) or (null, string * reason why variable was not found) */ private VariableValue lookupVariable( String name, boolean throwOnMissingVariable, @Nullable ArtifactExpander expander) { VariableValue nonStructuredVariable = getNonStructuredVariable(name); if (nonStructuredVariable != null) { return nonStructuredVariable; } VariableValue structuredVariable = getStructureVariable(name, throwOnMissingVariable, expander); if (structuredVariable != null) { return structuredVariable; } else if (throwOnMissingVariable) { throw new ExpansionException( String.format( "Invalid toolchain configuration: Cannot find variable named '%s'.", name)); } else { return null; } } private VariableValue getNonStructuredVariable(String name) { if (variablesMap.containsKey(name)) { return variablesMap.get(name); } if (stringVariablesMap.containsKey(name)) { return new StringValue(stringVariablesMap.get(name)); } if (parent != null) { return parent.getNonStructuredVariable(name); } return null; } private VariableValue getStructureVariable( String name, boolean throwOnMissingVariable, @Nullable ArtifactExpander expander) { if (!name.contains(".")) { return null; } Stack fieldsToAccess = new Stack<>(); String structPath = name; VariableValue variable; do { fieldsToAccess.push(structPath.substring(structPath.lastIndexOf('.') + 1)); structPath = structPath.substring(0, structPath.lastIndexOf('.')); variable = getNonStructuredVariable(structPath); } while (variable == null && structPath.contains(".")); if (variable == null) { return null; } while (!fieldsToAccess.empty()) { String field = fieldsToAccess.pop(); variable = variable.getFieldValue(structPath, field, expander); if (variable == null) { if (throwOnMissingVariable) { throw new ExpansionException( String.format( "Invalid toolchain configuration: Cannot expand variable '%s.%s': structure %s " + "doesn't have a field named '%s'", structPath, field, structPath, field)); } else { return null; } } } return variable; } public String getStringVariable(String variableName) { return getVariable(variableName, null).getStringValue(variableName); } public String getStringVariable(String variableName, @Nullable ArtifactExpander expander) { return getVariable(variableName, expander).getStringValue(variableName); } public Iterable getSequenceVariable(String variableName) { return getVariable(variableName, null).getSequenceValue(variableName); } public Iterable getSequenceVariable( String variableName, @Nullable ArtifactExpander expander) { return getVariable(variableName, expander).getSequenceValue(variableName); } /** Returns whether {@code variable} is set. */ boolean isAvailable(String variable) { return isAvailable(variable, null); } boolean isAvailable(String variable, @Nullable ArtifactExpander expander) { return lookupVariable(variable, false, expander) != null; } } /** Captures the set of enabled features and action configs for a rule. */ @Immutable @AutoCodec public static class FeatureConfiguration { public static final ObjectCodec CODEC = new CcToolchainFeatures_FeatureConfiguration_AutoCodec(); private final ImmutableSet enabledFeatureNames; private final ImmutableList enabledFeatures; private final ImmutableSet enabledActionConfigActionNames; private final ImmutableMap actionConfigByActionName; /** * {@link FeatureConfiguration} instance that doesn't produce any command lines. This is to be * used when creation of the real {@link FeatureConfiguration} failed, the rule error was * reported, but the analysis continues to collect more rule errors. */ public static final FeatureConfiguration EMPTY = new FeatureConfiguration(); protected FeatureConfiguration() { this(ImmutableList.of(), ImmutableSet.of(), ImmutableMap.of()); } @AutoCodec.Instantiator FeatureConfiguration( ImmutableList enabledFeatures, ImmutableSet enabledActionConfigActionNames, ImmutableMap actionConfigByActionName) { this.enabledFeatures = enabledFeatures; this.actionConfigByActionName = actionConfigByActionName; ImmutableSet.Builder featureBuilder = ImmutableSet.builder(); for (Feature feature : enabledFeatures) { featureBuilder.add(feature.getName()); } this.enabledFeatureNames = featureBuilder.build(); this.enabledActionConfigActionNames = enabledActionConfigActionNames; } /** * @return whether the given {@code feature} is enabled. */ public boolean isEnabled(String feature) { return enabledFeatureNames.contains(feature); } /** @return true if tool_path in action_config points to a real tool, not a dummy placeholder */ public boolean hasConfiguredLinkerPathInActionConfig() { return isEnabled("has_configured_linker_path"); } /** @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}. */ public List getCommandLine(String action, Variables variables) { return getCommandLine(action, variables, null); } public List getCommandLine( String action, Variables variables, @Nullable ArtifactExpander expander) { List commandLine = new ArrayList<>(); if (actionIsConfigured(action)) { actionConfigByActionName .get(action) .expandCommandLine(variables, enabledFeatureNames, expander, commandLine); } for (Feature feature : enabledFeatures) { feature.expandCommandLine(action, variables, enabledFeatureNames, expander, commandLine); } return commandLine; } /** @return the flags expanded for the given {@code action} in per-feature buckets. */ public ImmutableList>> getPerFeatureExpansions( String action, Variables variables) { return getPerFeatureExpansions(action, variables, null); } public ImmutableList>> getPerFeatureExpansions( String action, Variables variables, @Nullable ArtifactExpander expander) { ImmutableList.Builder>> perFeatureExpansions = ImmutableList.builder(); if (actionIsConfigured(action)) { List commandLine = new ArrayList<>(); ActionConfig actionConfig = actionConfigByActionName.get(action); actionConfig.expandCommandLine(variables, enabledFeatureNames, expander, commandLine); perFeatureExpansions.add(Pair.of(actionConfig.getName(), commandLine)); } for (Feature feature : enabledFeatures) { List commandLine = new ArrayList<>(); feature.expandCommandLine(action, variables, enabledFeatureNames, expander, commandLine); perFeatureExpansions.add(Pair.of(feature.getName(), commandLine)); } return perFeatureExpansions.build(); } /** @return the environment variables (key/value pairs) for the given {@code action}. */ ImmutableMap getEnvironmentVariables(String action, Variables variables) { ImmutableMap.Builder envBuilder = ImmutableMap.builder(); for (Feature feature : enabledFeatures) { feature.expandEnvironment(action, variables, enabledFeatureNames, 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); } @Override public boolean equals(Object object) { if (object == this) { return true; } if (object instanceof FeatureConfiguration) { FeatureConfiguration that = (FeatureConfiguration) object; return Objects.equals(actionConfigByActionName, that.actionConfigByActionName) && Iterables.elementsEqual( enabledActionConfigActionNames, that.enabledActionConfigActionNames) && Iterables.elementsEqual(enabledFeatureNames, that.enabledFeatureNames) && Iterables.elementsEqual(enabledFeatures, that.enabledFeatures); } return false; } @Override public int hashCode() { return Objects.hash( actionConfigByActionName, enabledActionConfigActionNames, enabledFeatureNames, enabledFeatures); } } /** All artifact name patterns defined in this feature configuration. */ private final ImmutableList artifactNamePatterns; /** * All features and action configs in the order in which they were specified in the configuration. * *

We guarantee the command line to be in the order in which the flags were specified in the * configuration. */ private final ImmutableList selectables; /** * Maps the selectables's name to the selectable. */ private final ImmutableMap selectablesByName; /** * Maps an action's name to the ActionConfig. */ private final ImmutableMap actionConfigsByActionName; /** * Maps from a selectable to a set of all the selectables it has a direct 'implies' edge to. */ private final ImmutableMultimap implies; /** * Maps from a selectable to all features that have an direct 'implies' edge to this * selectable. */ private final ImmutableMultimap impliedBy; /** * Maps from a selectable to a set of selecatable sets, where: *

    *
  • a selectable set satisfies the 'requires' condition, if all selectables in the * selectable set are enabled
  • *
  • the 'requires' condition is satisfied, if at least one of the selectable sets satisfies * the 'requires' condition.
  • *
*/ private final ImmutableMultimap> requires; /** * Maps from a string to the set of selectables that 'provide' it. */ private final ImmutableMultimap provides; /** * Maps from a selectable to all selectables that have a requirement referencing it. * *

This will be used to determine which selectables need to be re-checked after a selectable * was disabled. */ private final ImmutableMultimap requiredBy; private final ImmutableList defaultSelectables; /** * A cache of feature selection results, so we do not recalculate the feature selection for all * actions. */ private transient LoadingCache, 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 selectablesBuilder = ImmutableList.builder(); HashMap selectablesByName = new HashMap<>(); // Also build a map from action -> action_config, for use in tool lookups ImmutableMap.Builder actionConfigsByActionName = ImmutableMap.builder(); ImmutableList.Builder defaultSelectablesBuilder = ImmutableList.builder(); for (CToolchain.Feature toolchainFeature : toolchain.getFeatureList()) { Feature feature = new Feature(toolchainFeature); selectablesBuilder.add(feature); selectablesByName.put(feature.getName(), feature); if (toolchainFeature.getEnabled()) { defaultSelectablesBuilder.add(feature.getName()); } } for (CToolchain.ActionConfig toolchainActionConfig : toolchain.getActionConfigList()) { ActionConfig actionConfig = new ActionConfig(toolchainActionConfig); selectablesBuilder.add(actionConfig); selectablesByName.put(actionConfig.getName(), actionConfig); actionConfigsByActionName.put(actionConfig.getActionName(), actionConfig); if (toolchainActionConfig.getEnabled()) { defaultSelectablesBuilder.add(actionConfig.getName()); } } this.defaultSelectables = defaultSelectablesBuilder.build(); this.selectables = selectablesBuilder.build(); this.selectablesByName = ImmutableMap.copyOf(selectablesByName); checkForActionNameDups(toolchain.getActionConfigList()); checkForActivatableDups(this.selectables); this.actionConfigsByActionName = actionConfigsByActionName.build(); ImmutableList.Builder 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', 'requires', and 'provides' edges. ImmutableMultimap.Builder implies = ImmutableMultimap.builder(); ImmutableMultimap.Builder> requires = ImmutableMultimap.builder(); ImmutableMultimap.Builder provides = ImmutableMultimap.builder(); // We also store the reverse 'implied by' and 'required by' edges during this pass. ImmutableMultimap.Builder impliedBy = ImmutableMultimap.builder(); ImmutableMultimap.Builder 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 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 (String providesName : toolchainFeature.getProvidesList()) { provides.put(selectable, providesName); } } 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.provides = provides.build().inverse(); this.impliedBy = impliedBy.build(); this.requiredBy = requiredBy.build(); } private static void checkForActivatableDups(Iterable selectables) throws InvalidConfigurationException { Collection 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 actionConfigs) throws InvalidConfigurationException { Collection 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, FeatureConfiguration> buildConfigurationCache() { return CacheBuilder.newBuilder() // TODO(klimek): Benchmark and tweak once we support a larger configuration. .maximumSize(10000) .build( new CacheLoader, FeatureConfiguration>() { @Override public FeatureConfiguration load(ImmutableSet requestedFeatures) throws CollidingProvidesException { return computeFeatureConfiguration(requestedFeatures); } }); } /** * Given a list of {@code requestedSelectables}, returns all features that are enabled by the * toolchain configuration. * *

A requested feature will not be enabled if the toolchain does not support it (which may * depend on other requested features). * *

Additional features will be enabled if the toolchain supports them and they are implied by * requested features. */ public FeatureConfiguration getFeatureConfiguration(ImmutableSet requestedSelectables) throws CollidingProvidesException { try { return configurationCache.get(requestedSelectables); } catch (ExecutionException e) { Throwables.throwIfInstanceOf(e.getCause(), CollidingProvidesException.class); Throwables.throwIfUnchecked(e.getCause()); throw new IllegalStateException("Unexpected checked exception encountered", e); } } /** * Given {@code featureSpecification}, returns a FeatureConfiguration with all requested features * enabled. * *

A requested feature will not be enabled if the toolchain does not support it (which may * depend on other requested features). * *

Additional features will be enabled if the toolchain supports them and they are implied by * requested features. */ public FeatureConfiguration computeFeatureConfiguration(ImmutableSet requestedSelectables) throws CollidingProvidesException { // Command line flags will be output in the order in which they are specified in the toolchain // configuration. return new FeatureSelection(requestedSelectables).run(); } public ImmutableList getDefaultFeaturesAndActionConfigs() { return defaultSelectables; } /** * @return the selectable with the given {@code name}.s * * @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 getActivatableNames() { Collection 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. * * @throws InvalidConfigurationException if the category is not supported by the action config. */ String getArtifactNameForCategory(ArtifactCategory artifactCategory, String outputName) throws InvalidConfigurationException { PathFragment output = PathFragment.create(outputName); ArtifactNamePattern patternForCategory = null; for (ArtifactNamePattern artifactNamePattern : artifactNamePatterns) { if (artifactNamePattern.getArtifactCategory() == artifactCategory) { patternForCategory = artifactNamePattern; } } if (patternForCategory == null) { throw new InvalidConfigurationException( 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. * *

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 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 enabled = new HashSet<>(); private FeatureSelection(ImmutableSet requestedFeatures) { ImmutableSet.Builder builder = ImmutableSet.builder(); for (String name : requestedFeatures) { 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() throws CollidingProvidesException { for (CrosstoolSelectable selectable : requestedSelectables) { enableAllImpliedBy(selectable); } disableUnsupportedActivatables(); ImmutableList.Builder enabledActivatablesInOrderBuilder = ImmutableList.builder(); for (CrosstoolSelectable selectable : selectables) { if (enabled.contains(selectable)) { enabledActivatablesInOrderBuilder.add(selectable); } } ImmutableList enabledActivatablesInOrder = enabledActivatablesInOrderBuilder.build(); ImmutableList enabledFeaturesInOrder = enabledActivatablesInOrder .stream() .filter(a -> a instanceof Feature) .map(f -> (Feature) f) .collect(ImmutableList.toImmutableList()); Iterable enabledActionConfigsInOrder = Iterables.filter(enabledActivatablesInOrder, ActionConfig.class); for (String provided : provides.keys()) { List conflicts = new ArrayList<>(); for (CrosstoolSelectable selectableProvidingString : provides.get(provided)) { if (enabledActivatablesInOrder.contains(selectableProvidingString)) { conflicts.add(selectableProvidingString.getName()); } } if (conflicts.size() > 1) { throw new CollidingProvidesException(String.format(COLLIDING_PROVIDES_ERROR, provided, Joiner.on(" ").join(conflicts))); } } ImmutableSet.Builder enabledActionConfigNames = ImmutableSet.builder(); for (ActionConfig actionConfig : enabledActionConfigsInOrder) { enabledActionConfigNames.add(actionConfig.actionName); } return new FeatureConfiguration( enabledFeaturesInOrder, enabledActionConfigNames.build(), 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 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. * *

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. * *

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 requiresAllOf : requires.get(feature)) { boolean requirementMet = true; for (CrosstoolSelectable required : requiresAllOf) { if (!enabled.contains(required)) { requirementMet = false; break; } } if (requirementMet) { return true; } } return false; } } }