blob: a912704353d838dd9fa19224bdda131aa6a7d910 [file]
// 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);
}