| // 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.annotations.VisibleForTesting; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Lists; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.packages.BuildType.SelectorList; |
| import java.util.List; |
| import java.util.function.BiConsumer; |
| import java.util.function.Consumer; |
| import javax.annotation.Nullable; |
| |
| /** |
| * Base {@link AttributeMap} implementation providing direct, unmanipulated access to |
| * underlying attribute data as stored within the Rule. |
| * |
| * <p>Any instantiable subclass should define a clear policy of what it does with this |
| * data before exposing it to consumers. |
| */ |
| public abstract class AbstractAttributeMapper implements AttributeMap { |
| final RuleClass ruleClass; |
| final Rule rule; |
| private final Label ruleLabel; |
| |
| protected AbstractAttributeMapper(Rule rule) { |
| this.ruleClass = rule.getRuleClassObject(); |
| this.ruleLabel = rule.getLabel(); |
| this.rule = rule; |
| } |
| |
| @Override |
| public String describeRule() { |
| return String.format("%s %s", this.rule.getRuleClass(), getLabel()); |
| } |
| |
| @Override |
| public Label getLabel() { |
| return ruleLabel; |
| } |
| |
| @Nullable |
| @Override |
| public <T> T get(String attributeName, Type<T> type) { |
| return getFromRawAttributeValue(rule.getAttr(attributeName, type), attributeName, type); |
| } |
| |
| @SuppressWarnings("unchecked") |
| final <T> T getFromRawAttributeValue(Object value, String attributeName, Type<T> type) { |
| if (value instanceof Attribute.ComputedDefault) { |
| value = ((Attribute.ComputedDefault) value).getDefault(this); |
| } else if (value instanceof Attribute.LateBoundDefault) { |
| value = ((Attribute.LateBoundDefault<?, ?>) value).getDefault(rule); |
| } else if (value instanceof MaterializingDefault) { |
| value = ((MaterializingDefault<?, ?>) value).getDefault(); |
| } else if (value instanceof SelectorList) { |
| throw new IllegalArgumentException( |
| String.format( |
| "Unexpected configurable attribute \"%s\" in %s rule %s: expected %s, is %s", |
| attributeName, ruleClass, ruleLabel, type, value)); |
| } |
| |
| // Hot code path - avoid the overhead of calling type.cast(value). The rule would have already |
| // failed on construction if one of its attributes was of the wrong type (including computed |
| // defaults). |
| return (T) value; |
| } |
| |
| /** |
| * Returns the given attribute if it's a computed default, null otherwise. |
| * |
| * @throws IllegalArgumentException if the given attribute doesn't exist with the specified |
| * type. This happens whether or not it's a computed default. |
| */ |
| @VisibleForTesting // Should be protected |
| @Nullable |
| public <T> Attribute.ComputedDefault getComputedDefault(String attributeName, Type<T> type) { |
| Object value = rule.getAttr(attributeName, type); |
| if (value instanceof Attribute.ComputedDefault) { |
| return (Attribute.ComputedDefault) value; |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Returns the given attribute if it's a {@link Attribute.LateBoundDefault}, null otherwise. |
| * |
| * @throws IllegalArgumentException if the given attribute doesn't exist with the specified |
| * type. This happens whether or not it's a late bound default. |
| */ |
| @Nullable |
| @SuppressWarnings("unchecked") |
| public <T> Attribute.LateBoundDefault<?, T> getLateBoundDefault( |
| String attributeName, Type<T> type) { |
| Object value = rule.getAttr(attributeName, type); |
| if (value instanceof Attribute.LateBoundDefault) { |
| return (Attribute.LateBoundDefault<?, T>) value; |
| } else { |
| return null; |
| } |
| } |
| |
| @Nullable |
| @SuppressWarnings("unchecked") |
| public <T> MaterializingDefault<?, T> getMaterializer(String attributeName, Type<T> type) { |
| Object value = rule.getAttr(attributeName, type); |
| if (value instanceof MaterializingDefault<?, ?>) { |
| return (MaterializingDefault<?, T>) value; |
| } else { |
| return null; |
| } |
| } |
| |
| @Override |
| public Iterable<String> getAttributeNames() { |
| return Lists.transform(ruleClass.getAttributes(), Attribute::getName); |
| } |
| |
| @Nullable |
| @Override |
| public Type<?> getAttributeType(String attrName) { |
| Attribute attr = getAttributeDefinition(attrName); |
| return attr == null ? null : attr.getType(); |
| } |
| |
| @Nullable |
| @Override |
| public Attribute getAttributeDefinition(String attrName) { |
| return ruleClass.getAttributeByNameMaybe(attrName); |
| } |
| |
| @Override |
| public boolean isAttributeValueExplicitlySpecified(String attributeName) { |
| return rule.isAttributeValueExplicitlySpecified(attributeName); |
| } |
| |
| @Override |
| public PackageArgs getPackageArgs() { |
| return rule.getPackage().getPackageArgs(); |
| } |
| |
| @Override |
| public final void visitAllLabels(BiConsumer<Attribute, Label> consumer) { |
| visitLabels(DependencyFilter.ALL_DEPS, consumer); |
| } |
| |
| @Override |
| public final void visitLabels(String attributeName, Consumer<Label> consumer) { |
| visitLabels( |
| ImmutableList.of(ruleClass.getAttributeByName(attributeName)), |
| DependencyFilter.ALL_DEPS, |
| (attr, label) -> consumer.accept(label)); |
| } |
| |
| @Override |
| public void visitLabels(DependencyFilter filter, BiConsumer<Attribute, Label> consumer) { |
| visitLabels(ruleClass.getAttributes(), filter, consumer); |
| } |
| |
| private void visitLabels( |
| List<Attribute> attributes, DependencyFilter filter, BiConsumer<Attribute, Label> consumer) { |
| Type.LabelVisitor visitor = |
| (label, attribute) -> { |
| if (label != null) { |
| consumer.accept(attribute, label); |
| } |
| }; |
| for (Attribute attribute : attributes) { |
| Type<?> type = attribute.getType(); |
| // TODO(bazel-team): clean up the typing / visitation interface so we don't have to |
| // special-case these types. |
| if (type != BuildType.OUTPUT |
| && type != BuildType.OUTPUT_LIST |
| && type != BuildType.NODEP_LABEL |
| && type != BuildType.NODEP_LABEL_LIST |
| && filter.test(rule, attribute)) { |
| visitLabels(attribute, type, visitor); |
| } |
| } |
| } |
| |
| /** Visits all labels reachable from the given attribute. */ |
| <T> void visitLabels(Attribute attribute, Type<T> type, Type.LabelVisitor visitor) { |
| T value = get(attribute.getName(), type); |
| if (value != null) { // null values are particularly possible for computed defaults. |
| type.visitLabels(visitor, value, attribute); |
| } |
| } |
| |
| @Override |
| public final boolean isConfigurable(String attributeName) { |
| return isConfigurable(rule, attributeName); |
| } |
| |
| /** |
| * Check if an attribute is configurable (uses select) or, if it's a computed default, if any of |
| * its inputs are configurable. |
| */ |
| public static boolean isConfigurable(Rule rule, String attributeName) { |
| Object attr = rule.getAttr(attributeName); |
| if (attr instanceof Attribute.ComputedDefault) { |
| for (String dep : ((Attribute.ComputedDefault) attr).dependencies()) { |
| if (isConfigurable(rule, dep)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| Attribute attrDef = rule.getRuleClassObject().getAttributeByNameMaybe(attributeName); |
| return attrDef != null && rule.getSelectorList(attributeName, attrDef.getType()) != null; |
| } |
| |
| /** |
| * Returns a {@link SelectorList} for the given attribute if the attribute is configurable |
| * for this rule, null otherwise. |
| * |
| * @return a {@link SelectorList} if the attribute takes the form |
| * "attrName = { 'a': value1_of_type_T, 'b': value2_of_type_T }") for this rule, null |
| * if it takes the form "attrName = value_of_type_T", null if it doesn't exist |
| * @throws IllegalArgumentException if the attribute is configurable but of the wrong type |
| */ |
| @Nullable |
| public final <T> SelectorList<T> getSelectorList(String attributeName, Type<T> type) { |
| return rule.getSelectorList(attributeName, type); |
| } |
| |
| /** |
| * Helper routine that just checks the given attribute has the given type for this rule and throws |
| * an IllegalException if not. |
| */ |
| void checkType(String attrName, Type<?> type) { |
| Integer index = ruleClass.getAttributeIndex(attrName); |
| if (index == null) { |
| throw new IllegalArgumentException( |
| "No such attribute " + attrName + " in " + ruleClass + " rule " + ruleLabel); |
| } |
| Attribute attr = ruleClass.getAttribute(index); |
| if (attr.getType() != type) { |
| throw new IllegalArgumentException( |
| "Attribute " + attrName + " is of type " + attr.getType() + " and not of type " + type |
| + " in " + ruleClass + " rule " + ruleLabel); |
| } |
| } |
| |
| |
| @Override |
| public boolean has(String attrName) { |
| Attribute attribute = ruleClass.getAttributeByNameMaybe(attrName); |
| return attribute != null; |
| } |
| |
| @Override |
| public <T> boolean has(String attrName, Type<T> type) { |
| return getAttributeType(attrName) == type; |
| } |
| } |