// Copyright 2014 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.packages; import static com.google.devtools.build.lib.packages.BuildType.DISTRIBUTIONS; import static com.google.devtools.build.lib.packages.BuildType.FILESET_ENTRY_LIST; import static com.google.devtools.build.lib.packages.BuildType.LABEL; import static com.google.devtools.build.lib.packages.BuildType.LABEL_DICT_UNARY; import static com.google.devtools.build.lib.packages.BuildType.LABEL_LIST; import static com.google.devtools.build.lib.packages.BuildType.LABEL_LIST_DICT; import static com.google.devtools.build.lib.packages.BuildType.LICENSE; import static com.google.devtools.build.lib.packages.BuildType.NODEP_LABEL; import static com.google.devtools.build.lib.packages.BuildType.NODEP_LABEL_LIST; import static com.google.devtools.build.lib.packages.BuildType.OUTPUT; import static com.google.devtools.build.lib.packages.BuildType.OUTPUT_LIST; import static com.google.devtools.build.lib.packages.BuildType.TRISTATE; import static com.google.devtools.build.lib.syntax.Type.BOOLEAN; import static com.google.devtools.build.lib.syntax.Type.INTEGER; import static com.google.devtools.build.lib.syntax.Type.INTEGER_LIST; import static com.google.devtools.build.lib.syntax.Type.STRING; import static com.google.devtools.build.lib.syntax.Type.STRING_DICT; import static com.google.devtools.build.lib.syntax.Type.STRING_DICT_UNARY; import static com.google.devtools.build.lib.syntax.Type.STRING_LIST; import static com.google.devtools.build.lib.syntax.Type.STRING_LIST_DICT; import com.google.common.base.Verify; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.collect.CollectionUtils; import com.google.devtools.build.lib.packages.BuildType.Selector; import com.google.devtools.build.lib.packages.BuildType.SelectorList; import com.google.devtools.build.lib.syntax.Type; import com.google.devtools.build.lib.util.Preconditions; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.annotation.Nullable; /** * {@link AttributeMap} implementation that provides the ability to retrieve all possible * values an attribute might take. */ public class AggregatingAttributeMapper extends AbstractAttributeMapper { @SuppressWarnings("unchecked") private static final ImmutableSet> scalarTypes = ImmutableSet.of(INTEGER, STRING, LABEL, NODEP_LABEL, OUTPUT, BOOLEAN, TRISTATE, LICENSE); /** * Store for all of this rule's attributes that are non-configurable. These are * unconditionally available to computed defaults no matter what dependencies * they've declared. */ private final List nonConfigurableAttributes; private AggregatingAttributeMapper(Rule rule) { super(rule.getPackage(), rule.getRuleClassObject(), rule.getLabel(), rule.getAttributeContainer()); nonConfigurableAttributes = rule.getRuleClassObject().getNonConfigurableAttributes(); } public static AggregatingAttributeMapper of(Rule rule) { return new AggregatingAttributeMapper(rule); } /** * Override that also visits the rule's configurable attribute keys (which are * themselves labels). * *

Note that we directly parse the selectors rather than just calling {@link #visitAttribute} * to iterate over all possible values. That's because {@link #visitAttribute} can grow * exponentially with respect to the number of selects (e.g. if an attribute uses three selects * with three conditions each, it can take nine possible values). So we want to avoid that code * path whenever actual value iteration isn't specifically needed. */ @Override protected void visitLabels(Attribute attribute, AcceptsLabelAttribute observer) { visitLabels(attribute, true, observer); } private void visitLabels(Attribute attribute, boolean includeSelectKeys, AcceptsLabelAttribute observer) { Type type = attribute.getType(); SelectorList selectorList = getSelectorList(attribute.getName(), type); if (selectorList == null) { if (getComputedDefault(attribute.getName(), attribute.getType()) != null) { // Computed defaults are a special pain: we have no choice but to iterate through their // (computed) values and look for labels. for (Object value : visitAttribute(attribute.getName(), attribute.getType())) { if (value != null) { for (Label label : extractLabels(type, value)) { observer.acceptLabelAttribute(getLabel().resolveRepositoryRelative(label), attribute); } } } } else { super.visitLabels(attribute, observer); } } else { for (Selector selector : selectorList.getSelectors()) { for (Map.Entry selectorEntry : selector.getEntries().entrySet()) { if (includeSelectKeys && !BuildType.Selector.isReservedLabel(selectorEntry.getKey())) { observer.acceptLabelAttribute( getLabel().resolveRepositoryRelative(selectorEntry.getKey()), attribute); } for (Label value : extractLabels(type, selectorEntry.getValue())) { observer.acceptLabelAttribute(getLabel().resolveRepositoryRelative(value), attribute); } } } } } /** * Returns all labels reachable via the given attribute. If a label is listed multiple times, * each instance appears in the returned list. * * @param includeSelectKeys whether to include config_setting keys for configurable attributes */ public List

If the attribute's value is a simple value, then this returns a singleton list of that * value. * *

If the attribute's value is an expression containing one or many {@code select(...)} * expressions, then this returns a list of all values that expression may evaluate to. * *

If the attribute does not have an explicit value for this rule, and the rule provides a * computed default, the computed default function is evaluated given the rule's other attribute * values as inputs and the output is returned in a singleton list. * *

If the attribute does not have an explicit value for this rule, and the rule provides a * computed default, and the computed default function depends on other attributes whose values * contain {@code select(...)} expressions, then the computed default function is evaluated for * every possible combination of input values, and the list of outputs is returned. */ public Iterable getPossibleAttributeValues(Rule rule, Attribute attr) { // Values may be null, so use normal collections rather than immutable collections. // This special case for the visibility attribute is needed because its value is replaced // with an empty list during package loading if it is public or private in order not to visit // the package called 'visibility'. if (attr.getName().equals("visibility")) { List result = new ArrayList<>(1); result.add(rule.getVisibility().getDeclaredLabels()); return result; } return Lists.newArrayList(visitAttribute(attr.getName(), attr.getType())); } /** * Coerces the list {@param possibleValues} of values of type {@param attrType} to a single * value of that type, in the following way: * *

If the list contains a single value, return that value. * *

If the list contains zero or multiple values and the type is a scalar type, return {@code * null}. * *

If the list contains zero or multiple values and the type is a collection or map type, * merge the collections/maps in the list and return the merged collection/map. */ @Nullable @SuppressWarnings("unchecked") public static Object flattenAttributeValues(Type attrType, Iterable possibleValues) { // If there is only one possible value, return it. if (Iterables.size(possibleValues) == 1) { return Iterables.getOnlyElement(possibleValues); } // Otherwise, there are multiple possible values. To conform to the message shape expected by // query output's clients, we must transform the list of possible values. This transformation // will be lossy, but this is the best we can do. // If the attribute's type is not a collection type, return null. Query output's clients do // not support list values for scalar attributes. if (scalarTypes.contains(attrType)) { return null; } // If the attribute's type is a collection type, merge the list of collections into a single // collection. This is a sensible solution for query output's clients, which are happy to get // the union of possible values. if (attrType == STRING_LIST || attrType == LABEL_LIST || attrType == NODEP_LABEL_LIST || attrType == OUTPUT_LIST || attrType == DISTRIBUTIONS || attrType == INTEGER_LIST || attrType == FILESET_ENTRY_LIST) { Builder builder = ImmutableList.builder(); for (Object possibleValue : possibleValues) { Collection collection = (Collection) possibleValue; for (Object o : collection) { builder.add(o); } } return builder.build(); } // Same for maps as for collections. if (attrType == STRING_DICT || attrType == STRING_DICT_UNARY || attrType == STRING_LIST_DICT || attrType == LABEL_DICT_UNARY || attrType == LABEL_LIST_DICT) { Map mergedDict = new HashMap<>(); for (Object possibleValue : possibleValues) { Map stringDict = (Map) possibleValue; for (Entry entry : stringDict.entrySet()) { mergedDict.put(entry.getKey(), entry.getValue()); } } return mergedDict; } throw new AssertionError("Unknown type: " + attrType); } /** * Returns a list of all possible values an attribute can take for this rule. * *

Note that when an attribute uses multiple selects, it can potentially take on many * values. So be cautious about unnecessarily relying on this method. */ public Iterable visitAttribute(String attributeName, Type type) { // If this attribute value is configurable, visit all possible values. SelectorList selectorList = getSelectorList(attributeName, type); if (selectorList != null) { ImmutableList.Builder builder = ImmutableList.builder(); visitConfigurableAttribute(selectorList.getSelectors(), new BoundSelectorPaths(), type, null, builder); return builder.build(); } // If this attribute is a computed default, feed it all possible value combinations of // its declared dependencies and return all computed results. For example, if this default // uses attributes x and y, x can configurably be x1 or x2, and y can configurably be y1 // or y1, then compute default values for the (x1,y1), (x1,y2), (x2,y1), and (x2,y2) cases. Attribute.ComputedDefault computedDefault = getComputedDefault(attributeName, type); if (computedDefault != null) { // This will hold every (value1, value2, ..) combination of the declared dependencies. List> depMaps = new LinkedList<>(); // Collect those combinations. mapDepsForComputedDefault(computedDefault.dependencies(), depMaps, ImmutableMap.of()); List possibleValues = new ArrayList<>(); // Not ImmutableList.Builder: values may be null. // For each combination, call getDefault on a specialized AttributeMap providing those values. for (Map depMap : depMaps) { possibleValues.add(type.cast(computedDefault.getDefault(mapBackedAttributeMap(depMap)))); } return possibleValues; } // For any other attribute, just return its direct value. T value = get(attributeName, type); return value == null ? ImmutableList.of() : ImmutableList.of(value); } /** * Determines all possible values a configurable attribute can take. Do not call this method * unless really necessary (see TODO comment inside). * * @param selectors the selectors that make up this attribute assignment (in order) * @param boundSelectorPaths paths that have already been chosen from previous selectors in an * earlier recursive call of this method. For example, given *

cmd = select({':a': 'w', ':b': 'x'}) + select({':a': 'y', ':b': 'z'})
* the only possible values for cmd are "wy" and "xz". * This is because the selects have the same conditions, so whatever matches the first also * matches the second. Note that this doesn't work for selects with overlapping but * different key sets. That's because of key specialization (see * {@link com.google.devtools.build.lib.analysis.ConfiguredAttributeMapper} - if the * second select also included a condition ':c' that includes both the flags * in ':a' and ':b', ':c' would be chosen over * them both. * @param type the type of this attribute * @param currentValueSoFar the partial value produced so far from earlier calls to this method * @param valuesBuilder output container for full values this attribute can take */ private void visitConfigurableAttribute(List> selectors, BoundSelectorPaths boundSelectorPaths, Type type, T currentValueSoFar, ImmutableList.Builder valuesBuilder) { // TODO(bazel-team): minimize or eliminate uses of this interface. It necessarily grows // exponentially with the number of selects in the attribute. Is that always necessary? // For example, dependency resolution just needs to know every possible label an attribute // might reference, but it doesn't need to know the exact combination of labels that make // up a value. This may be even less important for non-label values (e.g. strings), which // have no impact on the dependency structure. if (selectors.isEmpty()) { valuesBuilder.add(Preconditions.checkNotNull(currentValueSoFar)); } else { Selector firstSelector = selectors.get(0); List> remainingSelectors = selectors.subList(1, selectors.size()); Map firstSelectorEntries = firstSelector.getEntries(); Label boundKey = boundSelectorPaths.getChosenKey(firstSelectorEntries.keySet()); if (boundKey != null) { // If we've already followed some path from a previous selector with the same exact // conditions as this one, we only need to visit that path (since the same key will // match both selectors). T boundValue = firstSelectorEntries.get(boundKey); visitConfigurableAttribute(remainingSelectors, boundSelectorPaths, type, currentValueSoFar == null ? boundValue : type.concat(ImmutableList.of(currentValueSoFar, boundValue)), valuesBuilder); } else { // Otherwise, we need to iterate over all possible paths. for (Map.Entry selectorBranch : firstSelectorEntries.entrySet()) { // Bind this particular path for later selectors using the same conditions. boundSelectorPaths.bind(firstSelectorEntries.keySet(), selectorBranch.getKey()); visitConfigurableAttribute(remainingSelectors, boundSelectorPaths, type, currentValueSoFar == null ? selectorBranch.getValue() : type.concat(ImmutableList.of(currentValueSoFar, selectorBranch.getValue())), valuesBuilder); // Unbind the path (so when we pop back up the recursive stack we can rebind it to new // values if we visit this selector again). boundSelectorPaths.unbind(firstSelectorEntries.keySet()); } } } } /** * Helper class for {@link #visitConfigurableAttribute}. See that method's comments for more * details. */ private static class BoundSelectorPaths { private final Map, Label> bindings = new HashMap<>(); /** * Binds the given config key set to the specified path. There should be no previous binding * for this key set. */ public void bind(Set