| // Copyright 2025 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.common.base.Preconditions.checkArgument; | |
| import static com.google.common.base.Preconditions.checkNotNull; | |
| import static com.google.common.base.Preconditions.checkPositionIndex; | |
| import static com.google.common.base.Preconditions.checkState; | |
| import com.google.common.collect.Interner; | |
| import com.google.common.collect.Iterators; | |
| import com.google.devtools.build.lib.cmdline.Label; | |
| import com.google.devtools.build.lib.concurrent.BlazeInterners; | |
| import com.google.devtools.build.lib.packages.Package.Declarations; | |
| import com.google.devtools.build.lib.util.HashCodes; | |
| import java.util.Arrays; | |
| import java.util.BitSet; | |
| import java.util.Collection; | |
| import java.util.List; | |
| import javax.annotation.Nullable; | |
| /** | |
| * Represents a rule or macro instance. | |
| * | |
| * <p>This encompasses the shared logic between {@link Rule} and {@link MacroInstance}. | |
| */ | |
| public abstract class RuleOrMacroInstance implements DependencyFilter.AttributeInfoProvider { | |
| private static final String NAME = RuleClass.NAME_ATTRIBUTE.getName(); | |
| static final String GENERATOR_NAME = "generator_name"; | |
| static final String GENERATOR_FUNCTION = "generator_function"; | |
| static final String GENERATOR_LOCATION = "generator_location"; | |
| private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; | |
| private static final int[] EMPTY_INT_ARRAY = new int[0]; | |
| private static final BitSet emptyBitSet = new BitSet(0); | |
| /** | |
| * Stores attribute values, taking on one of two shapes: | |
| * | |
| * <ol> | |
| * <li>While the rule or macro instance is mutable, the array length is equal to the number of | |
| * attributes. Each array slot holds the attribute value for the corresponding index or null | |
| * if not set. | |
| * <li>After {@link #freeze}, the array is compacted to store only necessary values. Nulls and | |
| * values that match {@link Attribute#getDefaultValue} are omitted to save space. Ordering | |
| * of attributes by their index is preserved. | |
| * </ol> | |
| */ | |
| private Object[] attrValues; | |
| /** | |
| * Holds metadata about attributes, taking on one of two shapes: | |
| * | |
| * <ol> | |
| * <li>While the rule or macro instance is mutable, contains only a {@link BitSet} tracking | |
| * which attribute indices were set explicitly. | |
| * <li>After {@link #freeze}, additionally contains an {@linkplain AttributeMetadata#index | |
| * index} of which attributes are stored in {@link #attrValues}. | |
| * </ol> | |
| */ | |
| private AttributeMetadata attrMetadata; | |
| final Label label; | |
| RuleOrMacroInstance(Label label, int attrCount) { | |
| this.label = checkNotNull(label); | |
| this.attrValues = new Object[attrCount]; | |
| this.attrMetadata = AttributeMetadata.mutable(attrCount); | |
| } | |
| /** | |
| * Returns true if the subset of this object's fields which are defined in this class equal those | |
| * of {@code other}. Intended for use by {@code equals()} implementations in subclasses. | |
| */ | |
| boolean equalsHelper(RuleOrMacroInstance other) { | |
| return Arrays.equals(attrValues, other.attrValues) | |
| && attrMetadata.equals(other.attrMetadata) | |
| && label.equals(other.label); | |
| } | |
| /** | |
| * Returns hash code of the subset of this object's fields which are defined in this class. | |
| * Intended for use by {@code hashCode()} implementations in subclasses. | |
| */ | |
| int hashCodeHelper() { | |
| return label.hashCode() | |
| + HashCodes.MULTIPLIER | |
| * (Arrays.hashCode(attrValues) + HashCodes.MULTIPLIER * attrMetadata.hashCode()); | |
| } | |
| /** | |
| * Returns the label of the rule or macro instance for error messaging. | |
| * | |
| * <p>For symbolic macros, this may not be unique, because macros can create macros that create | |
| * macros.... that create a single target all with the same name. | |
| */ | |
| // TODO: steinman - This should be the macro ID, not the label. | |
| public Label getLabel() { | |
| return label; | |
| } | |
| /** Returns the {@link AttributeProvider} for this rule or macro's parent class. */ | |
| public abstract AttributeProvider getAttributeProvider(); | |
| /** | |
| * Returns the name part of the label of the target. | |
| * | |
| * <p>Equivalent to {@code getLabel().getName()}. | |
| */ | |
| public String getName() { | |
| return label.getName(); | |
| } | |
| /** | |
| * Returns an (unmodifiable, unordered) collection containing all the Attribute definitions for | |
| * this kind of rule or macro. (Note, this doesn't include the <i>values</i> of the attributes, | |
| * merely the schema. Call get[Type]Attr() methods to access the actual values.) | |
| */ | |
| public Collection<Attribute> getAttributes() { | |
| return getAttributeProvider().getAttributes(); | |
| } | |
| /** | |
| * Returns true iff the {@link AttributeProvider} has an attribute with the given name and type. | |
| * | |
| * <p>Note: RuleContext also has isAttrDefined(), which takes Aspects into account. Whenever | |
| * possible, use RuleContext.isAttrDefined() instead of this method. | |
| */ | |
| public boolean isAttrDefined(String attrName, Type<?> type) { | |
| return getAttributeProvider().hasAttr(attrName, type); | |
| } | |
| void setAttributeValue(Attribute attribute, Object value, boolean explicit) { | |
| checkState(!isFrozen(), "Already frozen: %s", this); | |
| String attrName = attribute.getName(); | |
| if (attrName.equals(NAME)) { | |
| // Avoid unnecessarily storing the name in attrValues - it's stored in the label. | |
| return; | |
| } | |
| Integer attrIndex = getAttributeProvider().getAttributeIndex(attrName); | |
| checkArgument( | |
| attrIndex != null, | |
| "Attribute %s is not valid for this %s", | |
| attrName, | |
| isRuleInstance() ? "rule" : "macro"); | |
| if (explicit) { | |
| checkState( | |
| !attrMetadata.getExplicit(attrIndex), "Attribute %s already explicitly set", attrName); | |
| attrMetadata.setExplicit(attrIndex); | |
| } | |
| attrValues[attrIndex] = value; | |
| } | |
| /** | |
| * Returns the value of the given attribute for this rule or macro. Returns null for invalid | |
| * attributes and default value if attribute was not set. | |
| * | |
| * @param attrName the name of the attribute to lookup. | |
| */ | |
| @Nullable | |
| public Object getAttr(String attrName) { | |
| if (attrName.equals(NAME)) { | |
| return getName(); | |
| } | |
| Integer attrIndex = getAttributeProvider().getAttributeIndex(attrName); | |
| return attrIndex == null ? null : getAttrWithIndex(attrIndex); | |
| } | |
| /** | |
| * Returns the value of the given attribute if it has the right type. | |
| * | |
| * @throws IllegalArgumentException if the attribute does not have the expected type. | |
| */ | |
| @Nullable | |
| public <T> Object getAttr(String attrName, Type<T> type) { | |
| if (attrName.equals(NAME)) { | |
| checkAttrType(attrName, type, RuleClass.NAME_ATTRIBUTE); | |
| return getName(); | |
| } | |
| Integer index = getAttributeProvider().getAttributeIndex(attrName); | |
| if (index == null) { | |
| throw new IllegalArgumentException( | |
| "No such attribute " | |
| + attrName | |
| + " in " | |
| + getAttributeProvider() | |
| + (isRuleInstance() ? " rule " : " macro ") | |
| + label); | |
| } | |
| checkAttrType(attrName, type, getAttributeProvider().getAttribute(index)); | |
| return getAttrWithIndex(index); | |
| } | |
| /** | |
| * Returns the value of the attribute with the given index. Returns null, if no such attribute | |
| * exists OR no value was set. | |
| */ | |
| @Nullable | |
| Object getAttrWithIndex(int attrIndex) { | |
| Object value = getAttrIfStored(attrIndex); | |
| if (value != null) { | |
| return value; | |
| } | |
| Attribute attr = getAttributeProvider().getAttribute(attrIndex); | |
| return attr.getDefaultValueUnchecked(); | |
| } | |
| /** | |
| * Returns the attribute value at the specified index if stored in this rule or macro, otherwise | |
| * {@code null}. | |
| * | |
| * <p>Unlike {@link #getAttr}, does not fall back to the default value. | |
| */ | |
| @Nullable | |
| Object getAttrIfStored(int attrIndex) { | |
| int attrCount = getAttributeProvider().getAttributeCount(); | |
| checkPositionIndex(attrIndex, attrCount - 1); | |
| if (!isFrozen()) { | |
| return attrValues[attrIndex]; | |
| } | |
| int index = Arrays.binarySearch(attrMetadata.index, attrIndex); | |
| if (index >= 0) { | |
| return attrValues[index]; | |
| } | |
| if (attrMetadata.getExplicit(attrIndex)) { | |
| Attribute attr = getAttributeProvider().getAttribute(attrIndex); | |
| return attr.getDefaultValueUnchecked(); | |
| } | |
| return null; | |
| } | |
| /** | |
| * Returns raw attribute values stored by this rule or macro. | |
| * | |
| * <p>The indices of attribute values in the returned list are not guaranteed to be consistent | |
| * with the other methods of this class. If this is important, which is generally the case, avoid | |
| * this method. | |
| * | |
| * <p>The returned iterable may contain null values. Its {@link Iterable#iterator} is | |
| * unmodifiable. | |
| */ | |
| Iterable<Object> getRawAttrValues() { | |
| return () -> Iterators.forArray(attrValues); | |
| } | |
| /** See {@link #isAttributeValueExplicitlySpecified(String)} */ | |
| @Override | |
| public boolean isAttributeValueExplicitlySpecified(Attribute attribute) { | |
| return isAttributeValueExplicitlySpecified(attribute.getName()); | |
| } | |
| /** | |
| * Returns true iff the value of the specified attribute is explicitly set in the BUILD file. This | |
| * returns true also if the value explicitly specified in the BUILD file is the same as the | |
| * attribute's default value. In addition, this method return false if the rule or macro has no | |
| * attribute with the given name. | |
| */ | |
| public boolean isAttributeValueExplicitlySpecified(String attrName) { | |
| if (attrName.equals(NAME)) { | |
| return true; | |
| } | |
| if ((attrName.equals(GENERATOR_FUNCTION) || attrName.equals(GENERATOR_LOCATION)) | |
| && isRuleInstance()) { | |
| return isRuleCreatedInMacro(); | |
| } | |
| Integer attrIndex = getAttributeProvider().getAttributeIndex(attrName); | |
| if (attrIndex == null) { | |
| return false; | |
| } | |
| return attrMetadata.getExplicit(attrIndex); | |
| } | |
| /* Returns true iff this is a rule instance (v. macro). */ | |
| public abstract boolean isRuleInstance(); | |
| /** | |
| * Returns whether this is a rule (v. macro) that was created by a legacy or symbolic macro. | |
| * Always false for macro instances; sometimes true for rules. | |
| */ | |
| public abstract boolean isRuleCreatedInMacro(); | |
| private void checkAttrType(String attrName, Type<?> requestedType, Attribute attr) { | |
| if (requestedType != attr.getType()) { | |
| throw new IllegalArgumentException( | |
| "Attribute " | |
| + attrName | |
| + " is of type " | |
| + attr.getType() | |
| + " and not of type " | |
| + requestedType | |
| + " in " | |
| + getAttributeProvider() | |
| + (isRuleInstance() ? " rule " : " macro ") | |
| + label); | |
| } | |
| } | |
| /** | |
| * Returns {@code true} if this rule or macro's attributes are immutable. | |
| * | |
| * <p>Frozen instances optimize for space by omitting storage for attribute values that match the | |
| * {@link Attribute} default. If {@link #getAttrIfStored} returns {@code null}, the value should | |
| * be taken from either {@link Attribute#getLateBoundDefault} for late-bound defaults or {@link | |
| * Attribute#getDefaultValue} for all other attributes (including computed defaults). | |
| * | |
| * <p>Mutable instances have no such optimization. During rule creation, this allows for | |
| * distinguishing whether a computed default (which may depend on other unset attributes) is | |
| * available. | |
| */ | |
| boolean isFrozen() { | |
| return attrMetadata.index != null; | |
| } | |
| /** Makes this rule or macro's attributes immutable and compacts their representation. */ | |
| void freeze() { | |
| if (isFrozen()) { | |
| return; | |
| } | |
| AttributeProvider provider = getAttributeProvider(); | |
| int numToStore = 0; | |
| for (int i = 0; i < attrValues.length; i++) { | |
| Object value = attrValues[i]; | |
| if (value == null) { | |
| continue; | |
| } | |
| if (value.equals(provider.getAttribute(i).getDefaultValueUnchecked())) { | |
| attrValues[i] = null; | |
| continue; | |
| } | |
| numToStore++; | |
| } | |
| if (numToStore == 0) { | |
| this.attrValues = EMPTY_OBJECT_ARRAY; | |
| this.attrMetadata = AttributeMetadata.frozen(EMPTY_INT_ARRAY, attrMetadata.explicit); | |
| } else { | |
| Object[] compactValues = new Object[numToStore]; | |
| int[] index = new int[numToStore]; | |
| int destIdx = 0; | |
| for (int i = 0; i < attrValues.length; i++) { | |
| Object value = attrValues[i]; | |
| if (value != null) { | |
| index[destIdx] = i; | |
| compactValues[destIdx] = value; | |
| destIdx++; | |
| } | |
| } | |
| this.attrValues = compactValues; | |
| this.attrMetadata = AttributeMetadata.frozen(index, attrMetadata.explicit); | |
| } | |
| // Sanity check to ensure mutable vs frozen is distinguishable. | |
| checkState(isFrozen(), "Freeze unsuccessful"); | |
| } | |
| /** | |
| * Encapsulates attribute metadata (index mapping of non-default attribute values and tracking of | |
| * which attributes were explicitly set). | |
| * | |
| * <p>Target declarations across a codebase frequently follow uniform usage patterns (e.g., | |
| * setting the same common subsets of explicit and non-default attributes for a given rule class), | |
| * so the duplication rate among frozen {@code AttributeMetadata} instances is high. Interning | |
| * these instances achieves significant heap savings. | |
| */ | |
| private static final class AttributeMetadata { | |
| private static final Interner<AttributeMetadata> interner = BlazeInterners.newWeakInterner(); | |
| static AttributeMetadata mutable(int attrCount) { | |
| return new AttributeMetadata(null, new BitSet(attrCount)); | |
| } | |
| static AttributeMetadata frozen(int[] index, BitSet explicit) { | |
| if (explicit.isEmpty()) { | |
| explicit = emptyBitSet; | |
| } else if (explicit.size() - explicit.length() >= Long.SIZE) { | |
| // More words allocated than necessary. Copy to a more compact BitSet. | |
| BitSet compacted = new BitSet(explicit.length()); | |
| compacted.or(explicit); | |
| explicit = compacted; | |
| } | |
| return interner.intern(new AttributeMetadata(index, explicit)); | |
| } | |
| /** | |
| * When mutable, this is {@code null}. When frozen, this is a sorted array of the attribute | |
| * indices with non-default values. The corresponding values are stored in {@link #attrValues} | |
| * at the exact same array offset (i.e., {@code attrValues[i]} is the value for attribute index | |
| * {@code index[i]}). | |
| */ | |
| @Nullable private final int[] index; | |
| /** Tracks the indices of attributes which were explicitly specified. */ | |
| private final BitSet explicit; | |
| AttributeMetadata(@Nullable int[] index, BitSet explicit) { | |
| this.index = index; | |
| this.explicit = checkNotNull(explicit); | |
| } | |
| @Override | |
| public boolean equals(Object obj) { | |
| if (this == obj) { | |
| return true; | |
| } | |
| if (!(obj instanceof AttributeMetadata other)) { | |
| return false; | |
| } | |
| return Arrays.equals(index, other.index) && explicit.equals(other.explicit); | |
| } | |
| @Override | |
| public int hashCode() { | |
| return Arrays.hashCode(index) + HashCodes.MULTIPLIER * explicit.hashCode(); | |
| } | |
| boolean getExplicit(int attrIndex) { | |
| return explicit.get(attrIndex); | |
| } | |
| void setExplicit(int attrIndex) { | |
| checkState(index == null, "Cannot mutate explicit attributes when frozen"); | |
| explicit.set(attrIndex); | |
| } | |
| } | |
| /** | |
| * Returns a {@link BuildType.SelectorList} for the given attribute if the attribute is | |
| * configurable for this rule or macro, null otherwise. | |
| */ | |
| @Nullable | |
| @SuppressWarnings("unchecked") | |
| public <T> BuildType.SelectorList<T> getSelectorList(String attributeName, Type<T> type) { | |
| Integer index = getAttributeProvider().getAttributeIndex(attributeName); | |
| if (index == null) { | |
| return null; | |
| } | |
| Object attrValue = getAttrIfStored(index); | |
| if (!(attrValue instanceof BuildType.SelectorList<?> selectorList)) { | |
| return null; | |
| } | |
| if (selectorList.getOriginalType() != type) { | |
| throw new IllegalArgumentException( | |
| "Attribute " | |
| + attributeName | |
| + " is not of type " | |
| + type | |
| + " in " | |
| + getAttributeProvider() | |
| + " rule " | |
| + label); | |
| } | |
| return (BuildType.SelectorList<T>) selectorList; | |
| } | |
| /** | |
| * Retrieves the package's default visibility, or for certain rule classes, injects a different | |
| * default visibility. | |
| */ | |
| public abstract RuleVisibility getDefaultVisibility(); | |
| @Nullable | |
| @SuppressWarnings("unchecked") | |
| private List<Label> getRawVisibilityLabels() { | |
| Integer visibilityIndex = getAttributeProvider().getAttributeIndex("visibility"); | |
| if (visibilityIndex == null) { | |
| return null; | |
| } | |
| return (List<Label>) getAttrIfStored(visibilityIndex); | |
| } | |
| /** | |
| * Returns the declared labels of the visibility attribute, or the default visibility if the | |
| * attribute is not set. | |
| */ | |
| public List<Label> getVisibilityDeclaredLabels() { | |
| List<Label> rawLabels = getRawVisibilityLabels(); | |
| return rawLabels != null ? rawLabels : getDefaultVisibility().getDeclaredLabels(); | |
| } | |
| /** Returns the metadata of the package where this target or macro instance lives. */ | |
| public abstract Package.Metadata getPackageMetadata(); | |
| abstract Declarations getPackageDeclarations(); | |
| /** | |
| * Returns the innermost symbolic macro that declared this target or macro instance, or null if it | |
| * was declared outside any symbolic macro (i.e. directly in a BUILD file or only in one or more | |
| * legacy macros). | |
| */ | |
| @Nullable | |
| public abstract MacroInstance getDeclaringMacro(); | |
| @Nullable | |
| public PackageArgs getPackageArgs() { | |
| return getPackageDeclarations().getPackageArgs(); | |
| } | |
| abstract void reportError(String message, TargetDefinitionContext targetDefinitionContext); | |
| } |