blob: 770b7e04fcbdc1e6a0540a71a90588e710428975 [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.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.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.rules.cpp.CcToolchainFeatures.Variables.VariableValue;
import com.google.devtools.build.lib.util.Pair;
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.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.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.
*
* <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 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.
*
* <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 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
private static class StringLiteralChunk implements StringChunk, Serializable {
private final String text;
private StringLiteralChunk(String text) {
this.text = text;
}
@Override
public void expand(Variables 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(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));
}
}
/**
* 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 {
/**
* Expands the current expandable under the given {@code view}, adding new flags to {@code
* commandLine}.
*
* <p>The {@code variables} controls which variables are visible during the expansion and allows
* to recursively expand nested flag groups.
*/
void expand(Variables variables, 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 variables, List<String> commandLine) {
StringBuilder flag = new StringBuilder();
for (StringChunk chunk : chunks) {
chunk.expand(variables, flag);
}
commandLine.add(flag.toString());
}
}
/**
* 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 EnvEntry(CToolchain.EnvEntry envEntry) throws InvalidConfigurationException {
this.key = envEntry.getKey();
StringValueParser parser = new StringValueParser(envEntry.getValue());
this.valueChunks = parser.getChunks();
}
/**
* 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) {
StringBuilder value = new StringBuilder();
for (StringChunk chunk : valueChunks) {
chunk.expand(variables, value);
}
envBuilder.put(key, value.toString());
}
}
@Immutable
private static class VariableWithValue {
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
private static class FlagGroup implements Serializable, Expandable {
private final ImmutableList<Expandable> expandables;
private String iterateOverVariable;
private final ImmutableSet<String> expandIfAllAvailable;
private final ImmutableSet<String> expandIfNoneAvailable;
private final String expandIfTrue;
private final String expandIfFalse;
private final VariableWithValue expandIfEqual;
private FlagGroup(CToolchain.FlagGroup flagGroup) throws InvalidConfigurationException {
ImmutableList.Builder<Expandable> expandables = ImmutableList.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()));
}
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;
}
}
@Override
public void expand(Variables variables, final List<String> commandLine) {
if (!canBeExpanded(variables)) {
return;
}
if (iterateOverVariable != null) {
for (VariableValue variableValue : variables.getSequenceVariable(iterateOverVariable)) {
Variables nestedVariables = new Variables(variables, iterateOverVariable, variableValue);
for (Expandable expandable : expandables) {
expandable.expand(nestedVariables, commandLine);
}
}
} else {
for (Expandable expandable : expandables) {
expandable.expand(variables, commandLine);
}
}
}
private boolean canBeExpanded(Variables variables) {
for (String variable : expandIfAllAvailable) {
if (!variables.isAvailable(variable)) {
return false;
}
}
for (String variable : expandIfNoneAvailable) {
if (variables.isAvailable(variable)) {
return false;
}
}
if (expandIfTrue != null
&& (!variables.isAvailable(expandIfTrue)
|| !variables.getVariable(expandIfTrue).isTruthy())) {
return false;
}
if (expandIfFalse != null
&& (!variables.isAvailable(expandIfFalse)
|| variables.getVariable(expandIfFalse).isTruthy())) {
return false;
}
if (expandIfEqual != null
&& (!variables.isAvailable(expandIfEqual.variable)
|| !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}.
*
* <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 'iterate_over' variable specified
* (preferred, explicit way), or
* <li>for each element in the sequence, if there is only one sequence variable used in the
* body of the flag_group (deprecated, implicit way). Having more than a single variable
* of sequence type in a single flag group with implicit iteration is not supported. Use
* explicit 'iterate_over' instead.
* </ul>
*/
private void expandCommandLine(Variables variables, final List<String> commandLine) {
expand(variables, commandLine);
}
}
private static boolean isWithFeaturesSatisfied(
Set<CToolchain.FeatureSet> withFeatureSets, Set<String> enabledFeatureNames) {
if (withFeatureSets.isEmpty()) {
return true;
}
for (CToolchain.FeatureSet featureSet : withFeatureSets) {
if (enabledFeatureNames.containsAll(featureSet.getFeatureList())) {
return true;
}
}
return false;
}
/**
* 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 ImmutableSet<CToolchain.FeatureSet> withFeatureSets;
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());
this.withFeatureSets = ImmutableSet.copyOf(flagSet.getWithFeatureList());
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,
Set<String> enabledFeatureNames,
List<String> commandLine) {
for (String variable : expandIfAllAvailable) {
if (!variables.isAvailable(variable)) {
return;
}
}
if (!isWithFeaturesSatisfied(withFeatureSets, enabledFeatureNames)) {
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,
Set<String> enabledFeatureNames,
List<String> commandLine) {
for (FlagSet flagSet : flagSets) {
flagSet.expandCommandLine(action, variables, enabledFeatureNames, 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,
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, Set<String> enabledFeatureNames, List<String> commandLine) {
for (FlagSet flagSet : flagSets) {
flagSet.expandCommandLine(actionName, variables, enabledFeatureNames, commandLine);
}
}
}
/** A description of how artifacts of a certain type are named. */
@Immutable
private static class ArtifactNamePattern {
private final ArtifactCategory artifactCategory;
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.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 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.
*
* <p>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
public static class Variables {
/** 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.
*
* <p>Throws {@link ExpansionException} when the variable is not a {@link StringSequence}.
*/
public static final ImmutableList<String> 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.
*
* <p>{@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.
*
* <p>Implementations must be immutable and without any side-effects. They will be expanded and
* queried multiple times.
*/
interface VariableValue {
/**
* 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<? extends VariableValue> 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);
/** 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) {
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<? extends VariableValue> 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<String> 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<VariableValue> 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<String, VariableValue> 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<String> 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}).
*/
private static final class LazyStringSequence extends VariableValueAdapter {
private final Supplier<ImmutableList<String>> supplier;
private LazyStringSequence(Supplier<ImmutableList<String>> supplier) {
this.supplier = Preconditions.checkNotNull(supplier);
}
@Override
public Iterable<? extends VariableValue> 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
public static class LibraryToLinkValue extends VariableValueAdapter {
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)";
private 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 ImmutableList<String> objectFiles;
private final boolean isWholeArchive;
private final Type type;
public static LibraryToLinkValue forDynamicLibrary(String name) {
return new LibraryToLinkValue(
Preconditions.checkNotNull(name), null, false, Type.DYNAMIC_LIBRARY);
}
public static LibraryToLinkValue forVersionedDynamicLibrary(
String name) {
return new LibraryToLinkValue(
Preconditions.checkNotNull(name), null, false, Type.VERSIONED_DYNAMIC_LIBRARY);
}
public static LibraryToLinkValue forInterfaceLibrary(String name) {
return new LibraryToLinkValue(
Preconditions.checkNotNull(name), null, false, Type.INTERFACE_LIBRARY);
}
public static LibraryToLinkValue forStaticLibrary(String name, boolean isWholeArchive) {
return new LibraryToLinkValue(
Preconditions.checkNotNull(name), null, isWholeArchive, Type.STATIC_LIBRARY);
}
public static LibraryToLinkValue forObjectFile(String name, boolean isWholeArchive) {
return new LibraryToLinkValue(
Preconditions.checkNotNull(name), null, isWholeArchive, Type.OBJECT_FILE);
}
public static LibraryToLinkValue forObjectFileGroup(
ImmutableList<String> objects, boolean isWholeArchive) {
Preconditions.checkNotNull(objects);
Preconditions.checkArgument(!objects.isEmpty());
return new LibraryToLinkValue(null, objects, isWholeArchive, Type.OBJECT_FILE_GROUP);
}
private LibraryToLinkValue(
String name, ImmutableList<String> objectFiles, boolean isWholeArchive, Type type) {
this.name = name;
this.objectFiles = objectFiles;
this.isWholeArchive = isWholeArchive;
this.type = type;
}
@Override
public VariableValue getFieldValue(String variableName, String field) {
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)) {
return new StringSequence(objectFiles);
} 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
private static final class Sequence extends VariableValueAdapter {
private static final String SEQUENCE_VARIABLE_TYPE_NAME = "sequence";
private final ImmutableList<VariableValue> values;
public Sequence(ImmutableList<VariableValue> values) {
this.values = values;
}
@Override
public Iterable<? extends VariableValue> 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
private static final class StructureSequence extends VariableValueAdapter {
private final ImmutableList<ImmutableMap<String, VariableValue>> values;
private StructureSequence(ImmutableList<ImmutableMap<String, VariableValue>> values) {
Preconditions.checkNotNull(values);
this.values = values;
}
@Override
public Iterable<? extends VariableValue> getSequenceValue(String variableName) {
final ImmutableList.Builder<VariableValue> sequences = ImmutableList.builder();
for (ImmutableMap<String, VariableValue> 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
static final class StringSequence extends VariableValueAdapter {
private final Iterable<String> values;
public StringSequence(Iterable<String> values) {
Preconditions.checkNotNull(values);
this.values = values;
}
@Override
public Iterable<? extends VariableValue> getSequenceValue(String variableName) {
final ImmutableList.Builder<VariableValue> 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
private static final class StructureValue extends VariableValueAdapter {
private static final String STRUCTURE_VARIABLE_TYPE_NAME = "structure";
private final ImmutableMap<String, VariableValue> value;
public StructureValue(ImmutableMap<String, VariableValue> value) {
this.value = value;
}
@Override
public VariableValue getFieldValue(String variableName, String field) {
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
private static final class StringValue extends VariableValueAdapter {
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
static final class IntegerValue extends VariableValueAdapter {
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<String, VariableValue> variablesMap = new LinkedHashMap<>();
private final Map<String, String> 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<ImmutableList<String>> 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}.
*
* <p>Accepts values as ImmutableSet. As ImmutableList has smaller memory footprint, we copy
* the values into a new list.
*/
public Builder addStringSequenceVariable(String name, ImmutableSet<String> values) {
checkVariableNotPresentAlready(name);
Preconditions.checkNotNull(values, "Cannot set null as a value for variable '%s'", name);
ImmutableList.Builder<String> 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}.
*
* <p>Accepts values as NestedSet. Nested set is stored directly, not cloned, not flattened.
*/
public Builder addStringSequenceVariable(String name, NestedSet<String> 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}.
*
* <p>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<String> 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<ImmutableList<String>> 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<String, String> 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<String> intersection =
Sets.intersection(variables.variablesMap.keySet(), variablesMap.keySet());
SetView<String> 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<String, VariableValue> variablesMap;
private final ImmutableMap<String, String> stringVariablesMap;
private final Variables parent;
private Variables(
Variables parent,
ImmutableMap<String, VariableValue> variablesMap,
ImmutableMap<String, String> 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);
}
/**
* Lookup a variable named @param name or return a reason why the variable was not found.
* Supports accessing fields in structures.
*
* @return Pair<VariableValue, String> returns either (variable value, null) or (null, string
* reason why variable was not found)
*/
private VariableValue lookupVariable(String name, boolean throwOnMissingVariable) {
VariableValue nonStructuredVariable = getNonStructuredVariable(name);
if (nonStructuredVariable != null) {
return nonStructuredVariable;
}
VariableValue structuredVariable = getStructureVariable(name, throwOnMissingVariable);
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) {
if (!name.contains(".")) {
return null;
}
Stack<String> 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);
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).getStringValue(variableName);
}
public Iterable<? extends VariableValue> getSequenceVariable(String variableName) {
return getVariable(variableName).getSequenceValue(variableName);
}
/** Returns whether {@code variable} is set. */
boolean isAvailable(String variable) {
return lookupVariable(variable, false) != null;
}
}
/**
* Captures the set of enabled features and action configs for a rule.
*/
@Immutable
public static class FeatureConfiguration {
private final FeatureSpecification featureSpecification;
private final ImmutableSet<String> enabledFeatureNames;
private final Iterable<Feature> enabledFeatures;
private final ImmutableSet<String> enabledActionConfigActionNames;
private final ImmutableMap<String, ActionConfig> 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(
FeatureSpecification.EMPTY,
ImmutableList.of(),
ImmutableList.of(),
ImmutableMap.of());
}
private FeatureConfiguration(
FeatureSpecification featureSpecification,
Iterable<Feature> enabledFeatures,
Iterable<ActionConfig> enabledActionConfigs,
ImmutableMap<String, ActionConfig> actionConfigByActionName) {
this.featureSpecification = featureSpecification;
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 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<String> getCommandLine(String action, Variables variables) {
List<String> commandLine = new ArrayList<>();
if (actionIsConfigured(action)) {
actionConfigByActionName
.get(action)
.expandCommandLine(variables, enabledFeatureNames, commandLine);
}
for (Feature feature : enabledFeatures) {
feature.expandCommandLine(action, variables, enabledFeatureNames, commandLine);
}
return commandLine;
}
/** @return the flags expanded for the given {@code action} in per-feature buckets. */
public ImmutableList<Pair<String, List<String>>> getPerFeatureExpansions(
String action, Variables variables) {
ImmutableList.Builder<Pair<String, List<String>>> perFeatureExpansions =
ImmutableList.builder();
if (actionIsConfigured(action)) {
List<String> commandLine = new ArrayList<>();
ActionConfig actionConfig = actionConfigByActionName.get(action);
actionConfig.expandCommandLine(variables, enabledFeatureNames, commandLine);
perFeatureExpansions.add(Pair.of(actionConfig.getName(), commandLine));
}
for (Feature feature : enabledFeatures) {
List<String> commandLine = new ArrayList<>();
feature.expandCommandLine(action, variables, enabledFeatureNames, commandLine);
perFeatureExpansions.add(Pair.of(feature.getName(), commandLine));
}
return perFeatureExpansions.build();
}
/** @return the environment variables (key/value pairs) for the given {@code action}. */
ImmutableMap<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);
}
public FeatureSpecification getFeatureSpecification() {
return featureSpecification;
}
}
/** 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 string to the set of selectables that 'provide' it.
*/
private final ImmutableMultimap<String, CrosstoolSelectable> provides;
/**
* 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;
private final ImmutableList<String> defaultFeatures;
/**
* A cache of feature selection results, so we do not recalculate the feature selection for all
* actions.
*/
private transient LoadingCache<FeatureSpecification, 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();
ImmutableList.Builder<String> defaultFeaturesBuilder = ImmutableList.builder();
for (CToolchain.Feature toolchainFeature : toolchain.getFeatureList()) {
Feature feature = new Feature(toolchainFeature);
selectablesBuilder.add(feature);
selectablesByName.put(feature.getName(), feature);
if (toolchainFeature.getEnabled()) {
defaultFeaturesBuilder.add(feature.getName());
}
}
this.defaultFeatures = defaultFeaturesBuilder.build();
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', 'requires', and 'provides' edges.
ImmutableMultimap.Builder<CrosstoolSelectable, CrosstoolSelectable> implies =
ImmutableMultimap.builder();
ImmutableMultimap.Builder<CrosstoolSelectable, ImmutableSet<CrosstoolSelectable>> requires =
ImmutableMultimap.builder();
ImmutableMultimap.Builder<CrosstoolSelectable, String> provides = 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 (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<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<FeatureSpecification, FeatureConfiguration> buildConfigurationCache() {
return CacheBuilder.newBuilder()
// TODO(klimek): Benchmark and tweak once we support a larger configuration.
.maximumSize(10000)
.build(
new CacheLoader<FeatureSpecification, FeatureConfiguration>() {
@Override
public FeatureConfiguration load(FeatureSpecification featureSpecification)
throws CollidingProvidesException {
return computeFeatureConfiguration(featureSpecification);
}
});
}
/**
* 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(FeatureSpecification featureSpecification)
throws CollidingProvidesException {
try {
return configurationCache.get(featureSpecification);
} 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.
*
* <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 computeFeatureConfiguration(FeatureSpecification featureSpecification)
throws CollidingProvidesException {
// Command line flags will be output in the order in which they are specified in the toolchain
// configuration.
return new FeatureSelection(featureSpecification).run();
}
/** Returns the list of features that specify themselves as enabled by default. */
public ImmutableList<String> getDefaultFeatures() {
return defaultFeatures;
}
/**
* @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 = PathFragment.create(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 final FeatureSpecification featureSpecification;
private FeatureSelection(FeatureSpecification featureSpecification) {
this.featureSpecification = featureSpecification;
ImmutableSet.Builder<CrosstoolSelectable> builder = ImmutableSet.builder();
for (String name : featureSpecification.getRequestedFeatures()) {
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<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);
for (String provided : provides.keys()) {
List<String> 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)));
}
}
return new FeatureConfiguration(
featureSpecification,
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;
}
}
}