// 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 com.google.common.base.Preconditions; import com.google.common.base.Verify; import com.google.common.collect.ImmutableList; 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.events.Location; import com.google.devtools.build.lib.packages.Attribute.ComputationLimiter; 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.syntax.Type.LabelVisitor; import com.google.devtools.build.lib.syntax.Type.ListType; 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.Set; import java.util.concurrent.atomic.AtomicInteger; 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 { private final Rule rule; private AggregatingAttributeMapper(Rule rule) { super(rule.getPackage(), rule.getRuleClassObject(), rule.getLabel(), rule.getAttributeContainer()); this.rule = rule; } public static AggregatingAttributeMapper of(Rule rule) { return new AggregatingAttributeMapper(rule); } /** * Returns 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 List getNonConfigurableAttributes() { return rule.getRuleClassObject().getNonConfigurableAttributes(); } /** * Override that also visits the rule's configurable attribute keys (which are themselves labels). * *

This method directly parses each selector, vs. calling {@link #visitAttribute} to iterate * over all possible values. The latter has dangerous efficiency consequences, as discussed in * {@link #visitAttribute}'s documentation. So we want to avoid that code path when possible. */ @Override protected void visitLabels(Attribute attribute, Type.LabelVisitor visitor) throws InterruptedException { visitLabels(attribute, true, visitor); } private void visitLabels( Attribute attribute, boolean includeSelectKeys, Type.LabelVisitor visitor) throws InterruptedException { 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) { type.visitLabels(visitor, value, attribute); } } } else { super.visitLabels(attribute, visitor); } } else { for (Selector selector : selectorList.getSelectors()) { for (Map.Entry selectorEntry : selector.getEntries().entrySet()) { if (includeSelectKeys && !BuildType.Selector.isReservedLabel(selectorEntry.getKey())) { visitor.visit(selectorEntry.getKey(), attribute); } Object value = selector.isValueSet(selectorEntry.getKey()) ? selectorEntry.getValue() : attribute.getDefaultValue(null); type.visitLabels(visitor, value, attribute); } } } } /** * Returns all labels reachable via the given attribute, with duplicate instances removed. * *

Use this interface over @link #visitAttribute} whenever possible, since the latter has * efficiency problems discussed in that method's documentation. * * @param includeSelectKeys whether to include config_setting keys for configurable attributes */ public Set

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. * *

EFFICIENCY WARNING: Do not use this method unless you really need every single value * the attribute might take. See {@link #visitAttribute}'s documentation for details. */ 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())); } /** * If the attribute is a selector list of list type, then this method returns a list with number * of elements equal to the number of select statements in the selector list. Each element of this * list is equal to concatenating every possible attribute value in a single select statement. * The conditions themselves in the select statements are completely ignored. Returns {@code null} * if the attribute isn't of the desired format. * * As an example, if we have select({a: ["a"], b: ["a", "b"]}) + select({a: ["c", "d"], c: ["e"]) * The output will be [["a", "a", "b"], ["c", "d", "e"]]. The idea behind this structure is that * at least some of the structure in the original selector list is preserved and we know any * possible attribute value is the result of concatenating some sublist of each element. */ @Nullable public Iterable getConcatenatedSelectorListsOfListType( String attributeName, Type type) { SelectorList selectorList = getSelectorList(attributeName, type); if (selectorList != null && type instanceof ListType) { List selectList = new ArrayList<>(); for (Selector selector : selectorList.getSelectors()) { selectList.add(type.concat(selector.getEntries().values())); } return ImmutableList.copyOf(selectList); } return null; } /** * Returns a list of all possible values an attribute can take for this rule. * *

EFFICIENCY WARNING: Do not use this method unless you really need every single value * the attribute might take. * *

This is dangerous because it's easy to write attributes with an exponential number of * possible values: * *

   *   foo = select({a: 1, b: 2} + select({c: 3, d: 4}) + select({e: 5, f: 6})
   * 
* *

Possible values: [135, 136, 145, 146, 235, 236, 245, 246] (i.e. 2^3). * *

This is true not just for attributes with multiple selects, but also * {@link Attribute.ComputedDefault}s depending on such attributes. * *

More often than not, calling code doesn't really need every value, but really just wants to * know, e.g., which labels might appear in a dependency list. For such cases, merging methods * like {@link #getReachableLabels} work just as well without the efficiency hit. Use those * whenever possible. */ 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) { return computedDefault.getPossibleValues(type, rule); } // 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 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()) { if (currentValueSoFar != null) { // Null values arise when a None is used as the value of a Selector for a type without a // default value. // TODO(gregce): visitAttribute should probably convey that an unset attribute is possible. // Therefore we need to actually handle null values here. valuesBuilder.add(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