blob: 4afc9f9c26dd11b7685f0735a5ae3a8ea6fa71a0 [file] [log] [blame]
// Copyright 2014 Google Inc. 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.analysis;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Verify;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.analysis.config.ConfigMatchingProvider;
import com.google.devtools.build.lib.packages.AbstractAttributeMapper;
import com.google.devtools.build.lib.packages.AttributeMap;
import com.google.devtools.build.lib.packages.BuildType;
import com.google.devtools.build.lib.packages.BuildType.Selector;
import com.google.devtools.build.lib.packages.BuildType.SelectorList;
import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.lib.syntax.Label;
import com.google.devtools.build.lib.syntax.Type;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* {@link AttributeMap} implementation that binds a rule's attribute as follows:
*
* <ol>
* <li>If the attribute is selectable (i.e. its BUILD declaration is of the form
* "attr = { config1: "value1", "config2: "value2", ... }", returns the subset of values
* chosen by the current configuration in accordance with Bazel's documented policy on
* configurable attribute selection.
* <li>If the attribute is not selectable (i.e. its value is static), returns that value with
* no additional processing.
* </ol>
*
* <p>Example usage:
* <pre>
* Label fooLabel = ConfiguredAttributeMapper.of(ruleConfiguredTarget).get("foo", Type.LABEL);
* </pre>
*/
public class ConfiguredAttributeMapper extends AbstractAttributeMapper {
private final Map<Label, ConfigMatchingProvider> configConditions;
private Rule rule;
private ConfiguredAttributeMapper(Rule rule, Set<ConfigMatchingProvider> configConditions) {
super(Preconditions.checkNotNull(rule).getPackage(), rule.getRuleClassObject(), rule.getLabel(),
rule.getAttributeContainer());
ImmutableMap.Builder<Label, ConfigMatchingProvider> builder = ImmutableMap.builder();
for (ConfigMatchingProvider configCondition : configConditions) {
builder.put(configCondition.label(), configCondition);
}
this.configConditions = builder.build();
this.rule = rule;
}
/**
* "Do-it-all" constructor that just needs a {@link RuleConfiguredTarget}.
*/
public static ConfiguredAttributeMapper of(RuleConfiguredTarget ct) {
return new ConfiguredAttributeMapper(ct.getTarget(), ct.getConfigConditions());
}
/**
* "Manual" constructor that requires the caller to pass the set of configurability conditions
* that trigger this rule's configurable attributes.
*
* <p>If you don't know how to do this, you really want to use one of the "do-it-all"
* constructors.
*/
@VisibleForTesting
public static ConfiguredAttributeMapper of(
Rule rule, Set<ConfigMatchingProvider> configConditions) {
return new ConfiguredAttributeMapper(rule, configConditions);
}
/**
* Checks that all attributes can be mapped to their configured values. This is
* useful for checking that the configuration space in a configured attribute doesn't
* contain unresolvable contradictions.
*
* @throws EvalException if any attribute's value can't be resolved under this mapper
*/
public void validateAttributes() throws EvalException {
for (String attrName : getAttributeNames()) {
getAndValidate(attrName, getAttributeType(attrName));
}
}
/**
* Variation of {@link #get} that throws an informative exception if the attribute
* can't be resolved due to intrinsic contradictions in the configuration.
*/
private <T> T getAndValidate(String attributeName, Type<T> type) throws EvalException {
SelectorList<T> selectorList = getSelectorList(attributeName, type);
if (selectorList == null) {
// This is a normal attribute.
return super.get(attributeName, type);
}
List<T> resolvedList = new ArrayList<>();
for (Selector<T> selector : selectorList.getSelectors()) {
resolvedList.add(resolveSelector(attributeName, selector));
}
return resolvedList.size() == 1 ? resolvedList.get(0) : type.concat(resolvedList);
}
private <T> T resolveSelector(String attributeName, Selector<T> selector)
throws EvalException {
ConfigMatchingProvider matchingCondition = null;
Set<Label> conditionLabels = new LinkedHashSet<>();
T matchingValue = null;
// Find the matching condition and record its value (checking for duplicates).
for (Map.Entry<Label, T> entry : selector.getEntries().entrySet()) {
Label selectorKey = entry.getKey();
if (BuildType.Selector.isReservedLabel(selectorKey)) {
continue;
}
ConfigMatchingProvider curCondition = Verify.verifyNotNull(configConditions.get(selectorKey));
conditionLabels.add(curCondition.label());
if (curCondition.matches()) {
if (matchingCondition == null || curCondition.refines(matchingCondition)) {
// A match is valid if either this is the *only* condition that matches or this is a
// more "precise" specification of another matching condition (in which case we choose
// the most precise one).
matchingCondition = curCondition;
matchingValue = entry.getValue();
} else if (matchingCondition.refines(curCondition)) {
// The originally matching conditions is more precise, so keep that one.
} else {
throw new EvalException(rule.getAttributeLocation(attributeName),
"Both " + matchingCondition.label() + " and " + curCondition.label()
+ " match configurable attribute \"" + attributeName + "\" in " + getLabel()
+ ". Multiple matches are not allowed unless one is a specialization of the other");
}
}
}
// If nothing matched, choose the default condition.
if (matchingCondition == null) {
if (!selector.hasDefault()) {
throw new EvalException(rule.getAttributeLocation(attributeName),
"Configurable attribute \"" + attributeName + "\" doesn't match this "
+ "configuration (would a default condition help?).\nConditions checked:\n "
+ Joiner.on("\n ").join(conditionLabels));
}
matchingValue = selector.getDefault();
}
return matchingValue;
}
@Override
public <T> T get(String attributeName, Type<T> type) {
try {
return getAndValidate(attributeName, type);
} catch (EvalException e) {
// Callers that reach this branch should explicitly validate the attribute through an
// appropriate call and handle the exception directly. This method assumes
// pre-validated attributes.
throw new IllegalStateException(
"lookup failed on attribute " + attributeName + ": " + e.getMessage());
}
}
}