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