| // 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.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 getName() { | |
| return ruleLabel.getName(); | |
| } | |
| @Override | |
| public Label getLabel() { | |
| return ruleLabel; | |
| } | |
| @Override | |
| public String getRuleClassName() { | |
| return ruleClass.getName(); | |
| } | |
| @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(); | |
| } 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; | |
| } | |
| } | |
| @Override | |
| public Iterable<String> getAttributeNames() { | |
| ImmutableList.Builder<String> names = ImmutableList.builder(); | |
| for (Attribute a : ruleClass.getAttributes()) { | |
| names.add(a.getName()); | |
| } | |
| return names.build(); | |
| } | |
| @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 String getPackageDefaultHdrsCheck() { | |
| return rule.getPackage().getDefaultHdrsCheck(); | |
| } | |
| @Override | |
| public boolean isPackageDefaultHdrsCheckSet() { | |
| return rule.getPackage().isDefaultHdrsCheckSet(); | |
| } | |
| @Override | |
| public Boolean getPackageDefaultTestOnly() { | |
| return rule.getPackage().getDefaultTestOnly(); | |
| } | |
| @Override | |
| public String getPackageDefaultDeprecation() { | |
| return rule.getPackage().getDefaultDeprecation(); | |
| } | |
| @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) { | |
| Attribute attrDef = getAttributeDefinition(attributeName); | |
| return attrDef != null && getSelectorList(attributeName, attrDef.getType()) != null; | |
| } | |
| public static <T> boolean isConfigurable(Rule rule, String attributeName, Type<T> type) { | |
| SelectorList<T> selectorMaybe = rule.getSelectorList(attributeName, type); | |
| return selectorMaybe != 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; | |
| } | |
| } |