// 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.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.Rule; import com.google.devtools.build.lib.packages.Type; import com.google.devtools.build.lib.syntax.EvalException; import com.google.devtools.build.lib.syntax.Label; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; /** * {@link AttributeMap} implementation that binds a rule's attribute as follows: * *
    *
  1. 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. *
  2. If the attribute is not selectable (i.e. its value is static), returns that value with * no additional processing. *
* *

Example usage: *

 *   Label fooLabel = ConfiguredAttributeMapper.of(ruleConfiguredTarget).get("foo", Type.LABEL);
 * 
*/ public class ConfiguredAttributeMapper extends AbstractAttributeMapper { private final Map configConditions; private Rule rule; private ConfiguredAttributeMapper(Rule rule, Set configConditions) { super(Preconditions.checkNotNull(rule).getPackage(), rule.getRuleClassObject(), rule.getLabel(), rule.getAttributeContainer()); ImmutableMap.Builder 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. * *

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 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 getAndValidate(String attributeName, Type type) throws EvalException { Type.SelectorList selectorList = getSelectorList(attributeName, type); if (selectorList == null) { // This is a normal attribute. return super.get(attributeName, type); } List resolvedList = new ArrayList<>(); for (Type.Selector selector : selectorList.getSelectors()) { resolvedList.add(resolveSelector(attributeName, selector)); } return resolvedList.size() == 1 ? resolvedList.get(0) : type.concat(resolvedList); } private T resolveSelector(String attributeName, Type.Selector selector) throws EvalException { ConfigMatchingProvider matchingCondition = null; T matchingValue = null; // Find the matching condition and record its value (checking for duplicates). for (Map.Entry entry : selector.getEntries().entrySet()) { Label selectorKey = entry.getKey(); if (Type.Selector.isReservedLabel(selectorKey)) { continue; } ConfigMatchingProvider curCondition = Verify.verifyNotNull(configConditions.get(selectorKey)); 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?)"); } matchingValue = selector.getDefault(); } return matchingValue; } @Override public T get(String attributeName, Type 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()); } } }