blob: 63189d6edf696a72d84be6e1e4e87997e16ced8f [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.Preconditions;
import com.google.common.base.Strings;
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.Interner;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander;
import com.google.devtools.build.lib.concurrent.BlazeInterners;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.Expandable;
import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.SingleVariables;
import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.StringChunk;
import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.StringValueParser;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization;
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.lib.syntax.Starlark;
import com.google.devtools.build.lib.util.Pair;
import com.google.devtools.build.lib.util.StringUtil;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.CToolchain;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
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 {
/**
* 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 EvalException {
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. */
public static final String COLLIDING_PROVIDES_ERROR =
"Symbol %s is provided by all of the following features: %s";
/** A single flag to be expanded under a set of variables. */
@Immutable
@AutoCodec
@VisibleForSerialization
public static class Flag implements Expandable {
private final ImmutableList<StringChunk> chunks;
public Flag(ImmutableList<StringChunk> chunks) {
this.chunks = chunks;
}
String getString() {
return Joiner.on("")
.join(
chunks.stream()
.map(chunk -> chunk.getString())
.collect(ImmutableList.toImmutableList()));
}
/** Expand this flag into a single new entry in {@code commandLine}. */
@Override
public void expand(
CcToolchainVariables variables,
@Nullable ArtifactExpander expander,
List<String> commandLine)
throws ExpansionException {
StringBuilder flag = new StringBuilder();
for (StringChunk chunk : chunks) {
flag.append(chunk.expand(variables));
}
commandLine.add(flag.toString());
}
@Override
public boolean equals(@Nullable Object object) {
if (this == object) {
return true;
}
if (object instanceof Flag) {
Flag that = (Flag) object;
return Iterables.elementsEqual(chunks, that.chunks);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(chunks);
}
/** A single environment key/value pair to be expanded under a set of variables. */
public static Expandable create(ImmutableList<StringChunk> chunks) {
if (chunks.size() == 1) {
return new SingleChunkFlag(chunks.get(0));
}
return new Flag(chunks);
}
/** Optimization for single-chunk case */
@Immutable
@AutoCodec
@VisibleForSerialization
static class SingleChunkFlag implements Expandable {
private final StringChunk chunk;
@VisibleForSerialization
SingleChunkFlag(StringChunk chunk) {
this.chunk = chunk;
}
@Override
public void expand(
CcToolchainVariables variables,
@Nullable ArtifactExpander artifactExpander,
List<String> commandLine)
throws ExpansionException {
commandLine.add(chunk.expand(variables));
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
SingleChunkFlag that = (SingleChunkFlag) o;
return chunk.equals(that.chunk);
}
String getString() {
return chunk.getString();
}
@Override
public int hashCode() {
return chunk.hashCode();
}
}
}
/** A single environment key/value pair to be expanded under a set of variables. */
@Immutable
public static class EnvEntry {
private final String key;
private final ImmutableList<StringChunk> valueChunks;
private EnvEntry(CToolchain.EnvEntry envEntry) throws EvalException {
this.key = envEntry.getKey();
StringValueParser parser = new StringValueParser(envEntry.getValue());
this.valueChunks = parser.getChunks();
}
EnvEntry(String key, ImmutableList<StringChunk> valueChunks) {
this.key = key;
this.valueChunks = valueChunks;
}
String getKey() {
return key;
}
String getValue() {
return Joiner.on("")
.join(
valueChunks.stream()
.map(stringChunk -> stringChunk.getString())
.collect(ImmutableList.toImmutableList()));
}
/**
* 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(
CcToolchainVariables variables, ImmutableMap.Builder<String, String> envBuilder)
throws ExpansionException {
StringBuilder value = new StringBuilder();
for (StringChunk chunk : valueChunks) {
value.append(chunk.expand(variables));
}
envBuilder.put(key, value.toString());
}
@Override
public boolean equals(@Nullable Object object) {
if (this == object) {
return true;
}
if (object instanceof EnvEntry) {
EnvEntry that = (EnvEntry) object;
return Objects.equals(key, that.key)
&& Iterables.elementsEqual(valueChunks, that.valueChunks);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(key, valueChunks);
}
}
/** Used for equality check between a variable and a specific value. */
@Immutable
@AutoCodec
public static class VariableWithValue {
public final String variable;
public final String value;
public VariableWithValue(String variable, String value) {
this.variable = variable;
this.value = value;
}
String getVariable() {
return variable;
}
String getValue() {
return 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
static class FlagGroup implements 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 EvalException {
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(Flag.create(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;
}
}
FlagGroup(
ImmutableList<Expandable> expandables,
String iterateOverVariable,
ImmutableSet<String> expandIfAllAvailable,
ImmutableSet<String> expandIfNoneAvailable,
String expandIfTrue,
String expandIfFalse,
VariableWithValue expandIfEqual) {
this.expandables = expandables;
this.iterateOverVariable = iterateOverVariable;
this.expandIfAllAvailable = expandIfAllAvailable;
this.expandIfNoneAvailable = expandIfNoneAvailable;
this.expandIfTrue = expandIfTrue;
this.expandIfFalse = expandIfFalse;
this.expandIfEqual = expandIfEqual;
}
@Override
public void expand(
CcToolchainVariables variables,
@Nullable ArtifactExpander expander,
final List<String> commandLine)
throws ExpansionException {
if (!canBeExpanded(variables, expander)) {
return;
}
if (iterateOverVariable != null) {
for (CcToolchainVariables.VariableValue variableValue :
variables.getSequenceVariable(iterateOverVariable, expander)) {
CcToolchainVariables nestedVariables =
new SingleVariables(variables, iterateOverVariable, variableValue);
for (Expandable expandable : expandables) {
expandable.expand(nestedVariables, expander, commandLine);
}
}
} else {
for (Expandable expandable : expandables) {
expandable.expand(variables, expander, commandLine);
}
}
}
private boolean canBeExpanded(
CcToolchainVariables variables, @Nullable ArtifactExpander expander)
throws ExpansionException {
for (String variable : expandIfAllAvailable) {
if (!variables.isAvailable(variable, expander)) {
return false;
}
}
for (String variable : expandIfNoneAvailable) {
if (variables.isAvailable(variable, expander)) {
return false;
}
}
if (expandIfTrue != null
&& (!variables.isAvailable(expandIfTrue, expander)
|| !variables.getVariable(expandIfTrue).isTruthy())) {
return false;
}
if (expandIfFalse != null
&& (!variables.isAvailable(expandIfFalse, expander)
|| variables.getVariable(expandIfFalse).isTruthy())) {
return false;
}
if (expandIfEqual != null
&& (!variables.isAvailable(expandIfEqual.variable, expander)
|| !variables
.getVariable(expandIfEqual.variable)
.getStringValue(expandIfEqual.variable)
.equals(expandIfEqual.value))) {
return false;
}
return true;
}
/**
* Expands all flags in this group and adds them to {@code commandLine}.
*
* <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(
CcToolchainVariables variables,
@Nullable ArtifactExpander expander,
final List<String> commandLine)
throws ExpansionException {
expand(variables, expander, commandLine);
}
@Override
public boolean equals(@Nullable Object object) {
if (this == object) {
return true;
}
if (object instanceof FlagGroup) {
FlagGroup that = (FlagGroup) object;
return Iterables.elementsEqual(expandables, that.expandables)
&& Objects.equals(iterateOverVariable, that.iterateOverVariable)
&& Iterables.elementsEqual(expandIfAllAvailable, that.expandIfAllAvailable)
&& Iterables.elementsEqual(expandIfNoneAvailable, that.expandIfNoneAvailable)
&& Objects.equals(expandIfTrue, that.expandIfTrue)
&& Objects.equals(expandIfFalse, that.expandIfFalse)
&& Objects.equals(expandIfEqual, that.expandIfEqual);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(
expandables,
iterateOverVariable,
expandIfAllAvailable,
expandIfNoneAvailable,
expandIfTrue,
expandIfFalse,
expandIfEqual);
}
ImmutableList<Expandable> getExpandables() {
return expandables;
}
String getIterateOverVariable() {
return iterateOverVariable;
}
ImmutableSet<String> getExpandIfAllAvailable() {
return expandIfAllAvailable;
}
ImmutableSet<String> getExpandIfNoneAvailable() {
return expandIfNoneAvailable;
}
String getExpandIfTrue() {
return expandIfTrue;
}
String getExpandIfFalse() {
return expandIfFalse;
}
VariableWithValue getExpandIfEqual() {
return expandIfEqual;
}
}
private static boolean isWithFeaturesSatisfied(
Collection<WithFeatureSet> withFeatureSets, Set<String> enabledFeatureNames) {
if (withFeatureSets.isEmpty()) {
return true;
}
for (WithFeatureSet featureSet : withFeatureSets) {
boolean negativeMatch =
featureSet
.getNotFeatures()
.stream()
.anyMatch(notFeature -> enabledFeatureNames.contains(notFeature));
boolean positiveMatch = enabledFeatureNames.containsAll(featureSet.getFeatures());
if (!negativeMatch && positiveMatch) {
return true;
}
}
return false;
}
/** Groups a set of flags to apply for certain actions. */
@Immutable
public static class FlagSet {
private final ImmutableSet<String> actions;
private final ImmutableSet<String> expandIfAllAvailable;
private final ImmutableSet<WithFeatureSet> withFeatureSets;
private final ImmutableList<FlagGroup> flagGroups;
private FlagSet(CToolchain.FlagSet flagSet) throws EvalException {
this(flagSet, ImmutableSet.copyOf(flagSet.getActionList()));
}
/** Constructs a FlagSet for the given set of actions. */
private FlagSet(CToolchain.FlagSet flagSet, ImmutableSet<String> actions) throws EvalException {
this.actions = actions;
this.expandIfAllAvailable = ImmutableSet.copyOf(flagSet.getExpandIfAllAvailableList());
ImmutableSet.Builder<WithFeatureSet> featureSetBuilder = ImmutableSet.builder();
for (CToolchain.WithFeatureSet withFeatureSet : flagSet.getWithFeatureList()) {
featureSetBuilder.add(new WithFeatureSet(withFeatureSet));
}
this.withFeatureSets = featureSetBuilder.build();
ImmutableList.Builder<FlagGroup> builder = ImmutableList.builder();
for (CToolchain.FlagGroup flagGroup : flagSet.getFlagGroupList()) {
builder.add(new FlagGroup(flagGroup));
}
this.flagGroups = builder.build();
}
FlagSet(
ImmutableSet<String> actions,
ImmutableSet<String> expandIfAllAvailable,
ImmutableSet<WithFeatureSet> withFeatureSets,
ImmutableList<FlagGroup> flagGroups) {
this.actions = actions;
this.expandIfAllAvailable = expandIfAllAvailable;
this.withFeatureSets = withFeatureSets;
this.flagGroups = flagGroups;
}
/** Adds the flags that apply to the given {@code action} to {@code commandLine}. */
private void expandCommandLine(
String action,
CcToolchainVariables variables,
Set<String> enabledFeatureNames,
@Nullable ArtifactExpander expander,
List<String> commandLine)
throws ExpansionException {
for (String variable : expandIfAllAvailable) {
if (!variables.isAvailable(variable, expander)) {
return;
}
}
if (!isWithFeaturesSatisfied(withFeatureSets, enabledFeatureNames)) {
return;
}
if (!actions.contains(action)) {
return;
}
for (FlagGroup flagGroup : flagGroups) {
flagGroup.expandCommandLine(variables, expander, commandLine);
}
}
@Override
public boolean equals(@Nullable Object object) {
if (object instanceof FlagSet) {
FlagSet that = (FlagSet) object;
return Iterables.elementsEqual(actions, that.actions)
&& Iterables.elementsEqual(expandIfAllAvailable, that.expandIfAllAvailable)
&& Iterables.elementsEqual(withFeatureSets, that.withFeatureSets)
&& Iterables.elementsEqual(flagGroups, that.flagGroups);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(actions, expandIfAllAvailable, withFeatureSets, flagGroups);
}
ImmutableSet<String> getActions() {
return actions;
}
ImmutableSet<String> getExpandIfAllAvailable() {
return expandIfAllAvailable;
}
ImmutableSet<WithFeatureSet> getWithFeatureSets() {
return withFeatureSets;
}
ImmutableList<FlagGroup> getFlagGroups() {
return flagGroups;
}
}
/**
* A set of positive and negative features. This stanza will evaluate to true when every 'feature'
* is enabled, and every 'not_feature' is not enabled.
*/
@Immutable
public static class WithFeatureSet {
private final ImmutableSet<String> features;
private final ImmutableSet<String> notFeatures;
private WithFeatureSet(CToolchain.WithFeatureSet withFeatureSet) {
this.features = ImmutableSet.copyOf(withFeatureSet.getFeatureList());
this.notFeatures = ImmutableSet.copyOf(withFeatureSet.getNotFeatureList());
}
WithFeatureSet(ImmutableSet<String> features, ImmutableSet<String> notFeatures) {
this.features = features;
this.notFeatures = notFeatures;
}
public ImmutableSet<String> getFeatures() {
return features;
}
public ImmutableSet<String> getNotFeatures() {
return notFeatures;
}
@Override
public boolean equals(@Nullable Object object) {
if (this == object) {
return true;
}
if (object instanceof WithFeatureSet) {
WithFeatureSet that = (WithFeatureSet) object;
return Iterables.elementsEqual(features, that.features)
&& Iterables.elementsEqual(notFeatures, that.notFeatures);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(features, notFeatures);
}
}
/** Groups a set of environment variables to apply for certain actions. */
@Immutable
public static class EnvSet {
private final ImmutableSet<String> actions;
private final ImmutableList<EnvEntry> envEntries;
private final ImmutableSet<WithFeatureSet> withFeatureSets;
private EnvSet(CToolchain.EnvSet envSet) throws EvalException {
this.actions = ImmutableSet.copyOf(envSet.getActionList());
ImmutableList.Builder<EnvEntry> builder = ImmutableList.builder();
for (CToolchain.EnvEntry envEntry : envSet.getEnvEntryList()) {
builder.add(new EnvEntry(envEntry));
}
ImmutableSet.Builder<WithFeatureSet> withFeatureSetsBuilder = ImmutableSet.builder();
for (CToolchain.WithFeatureSet withFeatureSet : envSet.getWithFeatureList()) {
withFeatureSetsBuilder.add(new WithFeatureSet(withFeatureSet));
}
this.envEntries = builder.build();
this.withFeatureSets = withFeatureSetsBuilder.build();
}
EnvSet(
ImmutableSet<String> actions,
ImmutableList<EnvEntry> envEntries,
ImmutableSet<WithFeatureSet> withFeatureSets) {
this.actions = actions;
this.envEntries = envEntries;
this.withFeatureSets = withFeatureSets;
}
ImmutableSet<String> getActions() {
return actions;
}
ImmutableList<EnvEntry> getEnvEntries() {
return envEntries;
}
ImmutableSet<WithFeatureSet> getWithFeatureSets() {
return withFeatureSets;
}
/**
* Adds the environment key/value pairs that apply to the given {@code action} to {@code
* envBuilder}.
*/
private void expandEnvironment(
String action,
CcToolchainVariables variables,
Set<String> enabledFeatureNames,
ImmutableMap.Builder<String, String> envBuilder)
throws ExpansionException {
if (!actions.contains(action)) {
return;
}
if (!isWithFeaturesSatisfied(withFeatureSets, enabledFeatureNames)) {
return;
}
for (EnvEntry envEntry : envEntries) {
envEntry.addEnvEntry(variables, envBuilder);
}
}
@Override
public boolean equals(@Nullable Object object) {
if (this == object) {
return true;
}
if (object instanceof EnvSet) {
EnvSet that = (EnvSet) object;
return Iterables.elementsEqual(actions, that.actions)
&& Iterables.elementsEqual(envEntries, that.envEntries)
&& Iterables.elementsEqual(withFeatureSets, that.withFeatureSets);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(actions, envEntries, withFeatureSets);
}
}
/**
* An interface for classes representing crosstool messages that can activate each other using
* 'requires' and 'implies' semantics.
*
* <p>Currently there are two types of CrosstoolActivatable: Feature and ActionConfig.
*/
interface CrosstoolSelectable {
/**
* Returns the name of this selectable.
*/
String getName();
}
/** Contains flags for a specific feature. */
@Immutable
@AutoCodec
@VisibleForSerialization
public static class Feature implements CrosstoolSelectable {
private static final Interner<Feature> FEATURE_INTERNER = BlazeInterners.newWeakInterner();
private final String name;
private final ImmutableList<FlagSet> flagSets;
private final ImmutableList<EnvSet> envSets;
private final boolean enabled;
private final ImmutableList<ImmutableSet<String>> requires;
private final ImmutableList<String> implies;
private final ImmutableList<String> provides;
Feature(CToolchain.Feature feature) throws EvalException {
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();
this.enabled = feature.getEnabled();
ImmutableList.Builder<ImmutableSet<String>> requiresBuilder = ImmutableList.builder();
for (CToolchain.FeatureSet requiresFeatureSet : feature.getRequiresList()) {
ImmutableSet<String> featureSet = ImmutableSet.copyOf(requiresFeatureSet.getFeatureList());
requiresBuilder.add(featureSet);
}
this.requires = requiresBuilder.build();
this.implies = ImmutableList.copyOf(feature.getImpliesList());
this.provides = ImmutableList.copyOf(feature.getProvidesList());
}
public Feature(
String name,
ImmutableList<FlagSet> flagSets,
ImmutableList<EnvSet> envSets,
boolean enabled,
ImmutableList<ImmutableSet<String>> requires,
ImmutableList<String> implies,
ImmutableList<String> provides) {
this.name = name;
this.flagSets = flagSets;
this.envSets = envSets;
this.enabled = enabled;
this.requires = requires;
this.implies = implies;
this.provides = provides;
}
@AutoCodec.Instantiator
@VisibleForSerialization
static Feature createFeatureForSerialization(
String name,
ImmutableList<FlagSet> flagSets,
ImmutableList<EnvSet> envSets,
boolean enabled,
ImmutableList<ImmutableSet<String>> requires,
ImmutableList<String> implies,
ImmutableList<String> provides) {
return FEATURE_INTERNER.intern(
new Feature(name, flagSets, envSets, enabled, requires, implies, provides));
}
@Override
public String getName() {
return name;
}
/** Adds environment variables for the given action to the provided builder. */
private void expandEnvironment(
String action,
CcToolchainVariables variables,
Set<String> enabledFeatureNames,
ImmutableMap.Builder<String, String> envBuilder)
throws ExpansionException {
for (EnvSet envSet : envSets) {
envSet.expandEnvironment(action, variables, enabledFeatureNames, envBuilder);
}
}
/** Adds the flags that apply to the given {@code action} to {@code commandLine}. */
private void expandCommandLine(
String action,
CcToolchainVariables variables,
Set<String> enabledFeatureNames,
@Nullable ArtifactExpander expander,
List<String> commandLine)
throws ExpansionException {
for (FlagSet flagSet : flagSets) {
flagSet.expandCommandLine(action, variables, enabledFeatureNames, expander, commandLine);
}
}
ImmutableList<FlagSet> getFlagSets() {
return flagSets;
}
ImmutableList<EnvSet> getEnvSets() {
return envSets;
}
@Override
public boolean equals(@Nullable Object object) {
if (this == object) {
return true;
}
if (object instanceof Feature) {
Feature that = (Feature) object;
return name.equals(that.name)
&& Iterables.elementsEqual(flagSets, that.flagSets)
&& Iterables.elementsEqual(envSets, that.envSets)
&& Iterables.elementsEqual(requires, that.requires)
&& Iterables.elementsEqual(implies, that.implies)
&& Iterables.elementsEqual(provides, that.provides)
&& enabled == that.enabled;
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(name, flagSets, envSets, requires, implies, provides, enabled);
}
boolean isEnabled() {
return enabled;
}
public ImmutableList<ImmutableSet<String>> getRequires() {
return requires;
}
public ImmutableList<String> getImplies() {
return implies;
}
public ImmutableList<String> getProvides() {
return provides;
}
}
/**
* An executable to be invoked by a blaze action. Can carry information on its platform
* restrictions.
*/
@Immutable
public static class Tool {
private final PathFragment toolPathFragment;
private final CToolchain.Tool.PathOrigin toolPathOrigin;
private final ImmutableSet<String> executionRequirements;
private final ImmutableSet<WithFeatureSet> withFeatureSetSets;
private Tool(CToolchain.Tool tool, ImmutableSet<WithFeatureSet> withFeatureSetSets)
throws EvalException {
this(
PathFragment.create(tool.getToolPath()),
tool.getToolPathOrigin(),
ImmutableSet.copyOf(tool.getExecutionRequirementList()),
withFeatureSetSets);
}
@VisibleForTesting
public Tool(
PathFragment toolPathFragment,
CToolchain.Tool.PathOrigin toolPathOrigin,
ImmutableSet<String> executionRequirements,
ImmutableSet<WithFeatureSet> withFeatureSetSets)
throws EvalException {
checkToolPath(toolPathFragment, toolPathOrigin);
this.toolPathFragment = toolPathFragment;
this.toolPathOrigin = toolPathOrigin;
this.executionRequirements = executionRequirements;
this.withFeatureSetSets = withFeatureSetSets;
}
@Deprecated
@VisibleForTesting
public Tool(
PathFragment toolPathFragment,
ImmutableSet<String> executionRequirements,
ImmutableSet<WithFeatureSet> withFeatureSetSets)
throws EvalException {
this(
toolPathFragment,
CToolchain.Tool.PathOrigin.CROSSTOOL_PACKAGE,
executionRequirements,
withFeatureSetSets);
}
private static void checkToolPath(PathFragment toolPath, CToolchain.Tool.PathOrigin origin)
throws EvalException {
switch (origin) {
case CROSSTOOL_PACKAGE:
// For legacy reasons, we allow absolute and relative paths here.
return;
case FILESYSTEM_ROOT:
if (!toolPath.isAbsolute()) {
throw Starlark.errorf(
"Tool-path with origin FILESYSTEM_ROOT must be absolute, got '%s'.",
toolPath.getPathString());
}
return;
case WORKSPACE_ROOT:
if (toolPath.isAbsolute()) {
throw Starlark.errorf(
"Tool-path with origin WORKSPACE_ROOT must be relative, got '%s'.",
toolPath.getPathString());
}
return;
}
// Unreached.
throw new IllegalStateException();
}
/** Returns the path to this action's tool relative to the provided crosstool path. */
String getToolPathString(PathFragment ccToolchainPath) {
switch (toolPathOrigin) {
case CROSSTOOL_PACKAGE:
// Legacy behavior.
return ccToolchainPath.getRelative(toolPathFragment).getSafePathString();
case FILESYSTEM_ROOT: // fallthrough.
case WORKSPACE_ROOT:
return toolPathFragment.getSafePathString();
}
// Unreached.
throw new IllegalStateException();
}
/**
* Returns a list of requirement hints that apply to the execution of this tool.
*/
ImmutableSet<String> getExecutionRequirements() {
return executionRequirements;
}
/**
* Returns a set of {@link WithFeatureSet} instances used to decide whether to use this tool
* given a set of enabled features.
*/
ImmutableSet<WithFeatureSet> getWithFeatureSetSets() {
return withFeatureSetSets;
}
PathFragment getToolPathFragment() {
return toolPathFragment;
}
CToolchain.Tool.PathOrigin getToolPathOrigin() {
return toolPathOrigin;
}
}
/**
* 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
@AutoCodec
public static class ActionConfig implements CrosstoolSelectable {
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 static final Interner<ActionConfig> ACTION_CONFIG_INTERNER =
BlazeInterners.newWeakInterner();
private final String configName;
private final String actionName;
private final ImmutableList<Tool> tools;
private final ImmutableList<FlagSet> flagSets;
private final boolean enabled;
private final ImmutableList<String> implies;
ActionConfig(CToolchain.ActionConfig actionConfig) throws EvalException {
this.configName = actionConfig.getConfigName();
this.actionName = actionConfig.getActionName();
ImmutableList.Builder<Tool> tools = ImmutableList.builder();
for (CToolchain.Tool tool : actionConfig.getToolList()) {
ImmutableSet<WithFeatureSet> withFeatureSetSets =
tool.getWithFeatureList().stream()
.map(f -> new WithFeatureSet(f))
.collect(ImmutableSet.toImmutableSet());
tools.add(new Tool(tool, withFeatureSetSets));
}
this.tools = tools.build();
ImmutableList.Builder<FlagSet> flagSetBuilder = ImmutableList.builder();
for (CToolchain.FlagSet flagSet : actionConfig.getFlagSetList()) {
if (!flagSet.getActionList().isEmpty()) {
throw Starlark.errorf(FLAG_SET_WITH_ACTION_ERROR, configName);
}
flagSetBuilder.add(new FlagSet(flagSet, ImmutableSet.of(actionName)));
}
this.flagSets = flagSetBuilder.build();
this.enabled = actionConfig.getEnabled();
this.implies = ImmutableList.copyOf(actionConfig.getImpliesList());
}
public ActionConfig(
String configName,
String actionName,
ImmutableList<Tool> tools,
ImmutableList<FlagSet> flagSets,
boolean enabled,
ImmutableList<String> implies) {
this.configName = configName;
this.actionName = actionName;
this.tools = tools;
this.flagSets = flagSets;
this.enabled = enabled;
this.implies = implies;
}
@AutoCodec.Instantiator
@VisibleForSerialization
static ActionConfig createForSerialization(
String configName,
String actionName,
ImmutableList<Tool> tools,
ImmutableList<FlagSet> flagSets,
boolean enabled,
ImmutableList<String> implies) {
return ACTION_CONFIG_INTERNER.intern(
new ActionConfig(configName, actionName, tools, flagSets, enabled, implies));
}
@Override
public String getName() {
return configName;
}
/**
* Returns the name of the blaze action this action config applies to.
*/
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<Tool> tool =
tools
.stream()
.filter(t -> isWithFeaturesSatisfied(t.getWithFeatureSetSets(), enabledFeatureNames))
.findFirst();
if (tool.isPresent()) {
return 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(
CcToolchainVariables variables,
Set<String> enabledFeatureNames,
@Nullable ArtifactExpander expander,
List<String> commandLine)
throws ExpansionException {
for (FlagSet flagSet : flagSets) {
flagSet.expandCommandLine(
actionName, variables, enabledFeatureNames, expander, commandLine);
}
}
boolean isEnabled() {
return enabled;
}
public ImmutableList<String> getImplies() {
return implies;
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (!(other instanceof ActionConfig)) {
return false;
}
ActionConfig that = (ActionConfig) other;
return Objects.equals(configName, that.configName)
&& Objects.equals(actionName, that.actionName)
&& enabled == that.enabled
&& Iterables.elementsEqual(tools, that.tools)
&& Iterables.elementsEqual(flagSets, that.flagSets)
&& Iterables.elementsEqual(implies, that.implies);
}
@Override
public int hashCode() {
return Objects.hash(configName, actionName, enabled, tools, flagSets, implies);
}
ImmutableList<Tool> getTools() {
return tools;
}
ImmutableList<FlagSet> getFlagSets() {
return flagSets;
}
}
/** A description of how artifacts of a certain type are named. */
@Immutable
public static class ArtifactNamePattern {
private final ArtifactCategory artifactCategory;
private final String prefix;
private final String extension;
ArtifactNamePattern(CToolchain.ArtifactNamePattern artifactNamePattern) throws EvalException {
ArtifactCategory foundCategory = null;
for (ArtifactCategory artifactCategory : ArtifactCategory.values()) {
if (artifactNamePattern.getCategoryName().equals(artifactCategory.getCategoryName())) {
foundCategory = artifactCategory;
}
}
if (foundCategory == null) {
throw Starlark.errorf(
"Invalid toolchain configuration: Artifact category %s not recognized",
artifactNamePattern.getCategoryName());
}
String extension = artifactNamePattern.getExtension();
if (!foundCategory.getAllowedExtensions().contains(extension)) {
throw Starlark.errorf(
"Unrecognized file extension '%s', allowed extensions are %s,"
+ " please check artifact_name_pattern configuration for %s in your CROSSTOOL.",
extension,
StringUtil.joinEnglishList(foundCategory.getAllowedExtensions(), "or", "'"),
foundCategory.getCategoryName());
}
this.artifactCategory = foundCategory;
this.prefix = artifactNamePattern.getPrefix();
this.extension = artifactNamePattern.getExtension();
}
public ArtifactNamePattern(ArtifactCategory artifactCategory, String prefix, String extension) {
this.artifactCategory = artifactCategory;
this.prefix = prefix;
this.extension = extension;
}
/** Returns the ArtifactCategory for this ArtifactNamePattern. */
ArtifactCategory getArtifactCategory() {
return this.artifactCategory;
}
public String getPrefix() {
return this.prefix;
}
public String getExtension() {
return this.extension;
}
/** Returns the artifact name that this pattern selects. */
public String getArtifactName(String baseName) {
return prefix + baseName + extension;
}
}
/** Captures the set of enabled features and action configs for a rule. */
@Immutable
@AutoCodec
@SuppressWarnings("InconsistentHashCode") // enabledFeatureNames, see definition of equals().
public static class FeatureConfiguration {
private static final Interner<FeatureConfiguration> FEATURE_CONFIGURATION_INTERNER =
BlazeInterners.newWeakInterner();
private final ImmutableSet<String> requestedFeatures;
private final ImmutableSet<String> enabledFeatureNames;
private final ImmutableList<Feature> enabledFeatures;
private final ImmutableSet<String> enabledActionConfigActionNames;
private final ImmutableMap<String, ActionConfig> actionConfigByActionName;
private final PathFragment ccToolchainPath;
/**
* {@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 =
FEATURE_CONFIGURATION_INTERNER.intern(new FeatureConfiguration());
protected FeatureConfiguration() {
this(
/* requestedFeatures= */ ImmutableSet.of(),
/* enabledFeatures= */ ImmutableList.of(),
/* enabledActionConfigActionNames= */ ImmutableSet.of(),
/* actionConfigByActionName= */ ImmutableMap.of(),
/* ccToolchainPath= */ PathFragment.EMPTY_FRAGMENT);
}
@AutoCodec.Instantiator
static FeatureConfiguration createForSerialization(
ImmutableSet<String> requestedFeatures,
ImmutableList<Feature> enabledFeatures,
ImmutableSet<String> enabledActionConfigActionNames,
ImmutableMap<String, ActionConfig> actionConfigByActionName,
PathFragment ccToolchainPath) {
return FEATURE_CONFIGURATION_INTERNER.intern(
new FeatureConfiguration(
requestedFeatures,
enabledFeatures,
enabledActionConfigActionNames,
actionConfigByActionName,
ccToolchainPath));
}
FeatureConfiguration(
ImmutableSet<String> requestedFeatures,
ImmutableList<Feature> enabledFeatures,
ImmutableSet<String> enabledActionConfigActionNames,
ImmutableMap<String, ActionConfig> actionConfigByActionName,
PathFragment ccToolchainPath) {
this.requestedFeatures = requestedFeatures;
this.enabledFeatures = enabledFeatures;
this.actionConfigByActionName = actionConfigByActionName;
ImmutableSet.Builder<String> featureBuilder = ImmutableSet.builder();
for (Feature feature : enabledFeatures) {
featureBuilder.add(feature.getName());
}
this.enabledFeatureNames = featureBuilder.build();
this.enabledActionConfigActionNames = enabledActionConfigActionNames;
this.ccToolchainPath = ccToolchainPath;
}
/**
* @return whether the given {@code feature} is enabled.
*/
public boolean isEnabled(String feature) {
return enabledFeatureNames.contains(feature);
}
/** The list of requested features, even if they do not exist in CROSSTOOLs. */
public ImmutableSet<String> getRequestedFeatures() {
return requestedFeatures;
}
/** @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, CcToolchainVariables variables)
throws ExpansionException {
return getCommandLine(action, variables, /* expander= */ null);
}
public List<String> getCommandLine(
String action, CcToolchainVariables variables, @Nullable ArtifactExpander expander)
throws ExpansionException {
List<String> commandLine = new ArrayList<>();
if (actionIsConfigured(action)) {
actionConfigByActionName
.get(action)
.expandCommandLine(variables, enabledFeatureNames, expander, commandLine);
}
for (Feature feature : enabledFeatures) {
feature.expandCommandLine(action, variables, enabledFeatureNames, expander, commandLine);
}
return commandLine;
}
/** @return the flags expanded for the given {@code action} in per-feature buckets. */
public ImmutableList<Pair<String, List<String>>> getPerFeatureExpansions(
String action, CcToolchainVariables variables) throws ExpansionException {
return getPerFeatureExpansions(action, variables, null);
}
public ImmutableList<Pair<String, List<String>>> getPerFeatureExpansions(
String action, CcToolchainVariables variables, @Nullable ArtifactExpander expander)
throws ExpansionException {
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, expander, commandLine);
perFeatureExpansions.add(Pair.of(actionConfig.getName(), commandLine));
}
for (Feature feature : enabledFeatures) {
List<String> commandLine = new ArrayList<>();
feature.expandCommandLine(action, variables, enabledFeatureNames, expander, commandLine);
perFeatureExpansions.add(Pair.of(feature.getName(), commandLine));
}
return perFeatureExpansions.build();
}
/** @return the environment variables (key/value pairs) for the given {@code action}. */
public ImmutableMap<String, String> getEnvironmentVariables(
String action, CcToolchainVariables variables) throws ExpansionException {
ImmutableMap.Builder<String, String> envBuilder = ImmutableMap.builder();
for (Feature feature : enabledFeatures) {
feature.expandEnvironment(action, variables, enabledFeatureNames, envBuilder);
}
return envBuilder.build();
}
public String getToolPathForAction(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).getToolPathString(ccToolchainPath);
}
ImmutableSet<String> getToolRequirementsForAction(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).getExecutionRequirements();
}
@Override
public boolean equals(Object object) {
if (object == this) {
return true;
}
if (object instanceof FeatureConfiguration) {
FeatureConfiguration that = (FeatureConfiguration) object;
// Only compare actionConfigByActionName, enabledActionConfigActionnames and enabledFeatures
// because enabledFeatureNames is based on the list of Features.
return Objects.equals(actionConfigByActionName, that.actionConfigByActionName)
&& Iterables.elementsEqual(
enabledActionConfigActionNames, that.enabledActionConfigActionNames)
&& Iterables.elementsEqual(enabledFeatures, that.enabledFeatures);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(
actionConfigByActionName,
enabledActionConfigActionNames,
enabledFeatureNames,
enabledFeatures);
}
ImmutableSet<String> getEnabledFeatureNames() {
return 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 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> defaultSelectables;
/**
* A cache of feature selection results, so we do not recalculate the feature selection for all
* actions. This may not be initialized on deserialization.
*/
private transient LoadingCache<ImmutableSet<String>, FeatureConfiguration> configurationCache =
buildConfigurationCache();
private PathFragment ccToolchainPath;
/**
* Constructs the feature configuration from a {@link CcToolchainConfigInfo}.
*
* @param ccToolchainConfigInfo the toolchain information as specified by the user.
* @param ccToolchainPath location of the cc_toolchain.
* @throws EvalException if the configuration has logical errors.
*/
@VisibleForTesting
public CcToolchainFeatures(
CcToolchainConfigInfo ccToolchainConfigInfo, PathFragment ccToolchainPath)
throws EvalException {
// 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> defaultSelectablesBuilder = ImmutableList.builder();
for (Feature feature : ccToolchainConfigInfo.getFeatures()) {
selectablesBuilder.add(feature);
selectablesByName.put(feature.getName(), feature);
if (feature.isEnabled()) {
defaultSelectablesBuilder.add(feature.getName());
}
}
for (ActionConfig actionConfig : ccToolchainConfigInfo.getActionConfigs()) {
selectablesBuilder.add(actionConfig);
selectablesByName.put(actionConfig.getName(), actionConfig);
actionConfigsByActionName.put(actionConfig.getActionName(), actionConfig);
if (actionConfig.isEnabled()) {
defaultSelectablesBuilder.add(actionConfig.getName());
}
}
this.defaultSelectables = defaultSelectablesBuilder.build();
this.selectables = selectablesBuilder.build();
this.selectablesByName = ImmutableMap.copyOf(selectablesByName);
checkForActionNameDups(ccToolchainConfigInfo.getActionConfigs());
checkForActivatableDups(this.selectables);
this.actionConfigsByActionName = actionConfigsByActionName.build();
this.artifactNamePatterns = ccToolchainConfigInfo.getArtifactNamePatterns();
// 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 (Feature feature : ccToolchainConfigInfo.getFeatures()) {
String name = feature.getName();
CrosstoolSelectable selectable = selectablesByName.get(name);
for (ImmutableSet<String> requiredFeatures : feature.getRequires()) {
ImmutableSet.Builder<CrosstoolSelectable> allOf = ImmutableSet.builder();
for (String requiredName : requiredFeatures) {
CrosstoolSelectable required = getActivatableOrFail(requiredName, name);
allOf.add(required);
requiredBy.put(required, selectable);
}
requires.put(selectable, allOf.build());
}
for (String impliedName : feature.getImplies()) {
CrosstoolSelectable implied = getActivatableOrFail(impliedName, name);
impliedBy.put(implied, selectable);
implies.put(selectable, implied);
}
for (String providesName : feature.getProvides()) {
provides.put(selectable, providesName);
}
}
for (ActionConfig actionConfig : ccToolchainConfigInfo.getActionConfigs()) {
String name = actionConfig.getName();
CrosstoolSelectable selectable = selectablesByName.get(name);
for (String impliedName : actionConfig.getImplies()) {
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();
this.ccToolchainPath = ccToolchainPath;
}
private static void checkForActivatableDups(Iterable<CrosstoolSelectable> selectables)
throws EvalException {
Collection<String> names = new HashSet<>();
for (CrosstoolSelectable selectable : selectables) {
if (!names.add(selectable.getName())) {
throw Starlark.errorf(
"Invalid toolchain configuration: feature or action config '%s' was specified multiple"
+ " times.",
selectable.getName());
}
}
}
private static void checkForActionNameDups(Iterable<ActionConfig> actionConfigs)
throws EvalException {
Collection<String> actionNames = new HashSet<>();
for (ActionConfig actionConfig : actionConfigs) {
if (!actionNames.add(actionConfig.getActionName())) {
throw Starlark.errorf(
"Invalid toolchain configuration: multiple action configs for action '%s'",
actionConfig.getActionName());
}
}
}
/** @return an empty {@code FeatureConfiguration} cache. */
private LoadingCache<ImmutableSet<String>, FeatureConfiguration> buildConfigurationCache() {
return CacheBuilder.newBuilder()
// TODO(klimek): Benchmark and tweak once we support a larger configuration.
.maximumSize(10000)
.build(
new CacheLoader<ImmutableSet<String>, FeatureConfiguration>() {
@Override
public FeatureConfiguration load(ImmutableSet<String> requestedFeatures)
throws CollidingProvidesException {
return computeFeatureConfiguration(requestedFeatures);
}
});
}
/**
* Given a list of {@code requestedSelectables}, 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.
*
* <p>If multiple threads call this method we may do additional work in initializing the cache.
* This reinitialization is benign.
*/
public FeatureConfiguration getFeatureConfiguration(ImmutableSet<String> requestedSelectables)
throws CollidingProvidesException {
try {
if (configurationCache == null) {
configurationCache = buildConfigurationCache();
}
return configurationCache.get(requestedSelectables);
} catch (ExecutionException e) {
Throwables.throwIfInstanceOf(e.getCause(), CollidingProvidesException.class);
Throwables.throwIfUnchecked(e.getCause());
throw new IllegalStateException("Unexpected checked exception encountered", e);
}
}
/**
* Given {@code featureSpecification}, returns a FeatureConfiguration with all requested features
* enabled.
*
* <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(ImmutableSet<String> requestedSelectables)
throws CollidingProvidesException {
// Command line flags will be output in the order in which they are specified in the toolchain
// configuration.
return new FeatureSelection(
requestedSelectables,
selectablesByName,
selectables,
provides,
implies,
impliedBy,
requires,
requiredBy,
actionConfigsByActionName,
ccToolchainPath)
.run();
}
public ImmutableList<String> getDefaultFeaturesAndActionConfigs() {
return defaultSelectables;
}
/**
* @return the selectable with the given {@code name}.s
* @throws EvalException if no selectable with the given name was configured.
*/
private CrosstoolSelectable getActivatableOrFail(String name, String reference)
throws EvalException {
if (!selectablesByName.containsKey(name)) {
throw Starlark.errorf(
"Invalid toolchain configuration: feature '%s', which is referenced from feature '%s',"
+ " is not defined.",
name, reference);
}
return selectablesByName.get(name);
}
@VisibleForTesting
Collection<String> getActivatableNames() {
return selectablesByName.keySet();
}
/**
* Returns the artifact selected by the toolchain for the given action type and action category.
*
* @throws EvalException if the category is not supported by the action config.
*/
String getArtifactNameForCategory(ArtifactCategory artifactCategory, String outputName)
throws EvalException {
PathFragment output = PathFragment.create(outputName);
ArtifactNamePattern patternForCategory = null;
for (ArtifactNamePattern artifactNamePattern : artifactNamePatterns) {
if (artifactNamePattern.getArtifactCategory() == artifactCategory) {
patternForCategory = artifactNamePattern;
}
}
if (patternForCategory == null) {
throw Starlark.errorf(
MISSING_ARTIFACT_NAME_PATTERN_ERROR_TEMPLATE, artifactCategory.getCategoryName());
}
return output.getParentDirectory()
.getChild(patternForCategory.getArtifactName(output.getBaseName())).getPathString();
}
/**
* Returns the artifact name extension selected by the toolchain for the given artifact category.
*
* @throws EvalException if the category is not supported by the action config.
*/
String getArtifactNameExtensionForCategory(ArtifactCategory artifactCategory)
throws EvalException {
ArtifactNamePattern patternForCategory = null;
for (ArtifactNamePattern artifactNamePattern : artifactNamePatterns) {
if (artifactNamePattern.getArtifactCategory() == artifactCategory) {
patternForCategory = artifactNamePattern;
}
}
if (patternForCategory == null) {
throw Starlark.errorf(
MISSING_ARTIFACT_NAME_PATTERN_ERROR_TEMPLATE, artifactCategory.getCategoryName());
}
return patternForCategory.getExtension();
}
/** 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;
}
}