| // Copyright 2024 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.auto.value.AutoValue; |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.cmdline.PackageIdentifier; |
| import com.google.devtools.build.lib.packages.BuildType.SelectorList; |
| import com.google.devtools.build.lib.packages.Type.ConversionException; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.function.Consumer; |
| import javax.annotation.Nullable; |
| import net.starlark.java.eval.EvalException; |
| import net.starlark.java.eval.Starlark; |
| |
| /** |
| * Represents a use of a symbolic macro in a package. |
| * |
| * <p>There is one {@code MacroInstance} for each call to a {@link |
| * StarlarkRuleClassFunctions#MacroFunction} that is executed during a package's evaluation. Just as |
| * a {@link MacroClass} is analogous to a {@link RuleClass}, {@code MacroInstance} is analogous to a |
| * {@link Rule} (i.e. a rule target). |
| * |
| * <p>Macro instance names are not guaranteed to be unique within a package; see {@link #getId}. |
| */ |
| public final class MacroInstance { |
| |
| // TODO: #19922 - If we want to save the cost of a field here, we can merge pkg and parent into a |
| // single field of type Object, and walk up the parent hierarchy to answer getPackage() queries. |
| private final Package pkg; |
| |
| @Nullable private final MacroInstance parent; |
| |
| private final MacroClass macroClass; |
| |
| private final int sameNameDepth; |
| |
| // Order isn't guaranteed, sort before dumping. You can use the schema map |
| // MacroClass#getAttributes for a guaranteed order. |
| private final ImmutableMap<String, Object> attrValues; |
| |
| // TODO(#19922): Consider switching to more optimized, indexed representation for attributes, as |
| // in Rule. |
| |
| /** |
| * Instantiates the given macro class with the given attribute values. |
| * |
| * <p>{@code attrValues} must have already been normalized based on the types of the attributes; |
| * see {@link MacroClass#instantiateMacro}. Values for the {@code "name"} and {@code "visibility"} |
| * attributes must exist with the correct types, and the {@code "visibility"} value must satisfy |
| * {@link RuleVisibility#validate}. |
| * |
| * <p>{@code sameNameDepth} is the number of macro instances that this one is inside of that share |
| * its name. For most instances it is 1, but for the main submacro of a parent macro it is one |
| * more than the parent's depth. |
| * |
| * @throws EvalException if there's a problem with the attribute values (currently, only thrown if |
| * the {@code visibility} value is invalid) |
| */ |
| // TODO: #19922 - Better encapsulate the invariant around attrValues, by either transitioning to |
| // storing internal-typed values (the way Rules do) instead of Starlark-typed values, or else just |
| // making the constructor private and moving instantiateMacro() to this class. |
| public MacroInstance( |
| Package pkg, |
| @Nullable MacroInstance parent, |
| MacroClass macroClass, |
| Map<String, Object> attrValues, |
| int sameNameDepth) |
| throws EvalException { |
| this.pkg = pkg; |
| this.parent = parent; |
| this.macroClass = macroClass; |
| this.attrValues = ImmutableMap.copyOf(attrValues); |
| Preconditions.checkArgument(sameNameDepth > 0); |
| this.sameNameDepth = sameNameDepth; |
| Preconditions.checkArgument(macroClass.getAttributes().keySet().equals(attrValues.keySet())); |
| } |
| |
| /** Returns the package this instance was created in. */ |
| public Package getPackage() { |
| return pkg; |
| } |
| |
| /** |
| * Returns the macro instance that instantiated this one, or null if this was created directly |
| * during BUILD evaluation. |
| */ |
| @Nullable |
| public MacroInstance getParent() { |
| return parent; |
| } |
| |
| /** Returns the {@link MacroClass} (i.e. schema info) that this instance parameterizes. */ |
| public MacroClass getMacroClass() { |
| return macroClass; |
| } |
| |
| /** |
| * The depth of this macro instance in a chain of nested macros having the same name. |
| * |
| * <p>1 for any macro that is not declared in a macro of the same name. |
| * |
| * <p>Used by {@link #getId}. |
| */ |
| public int getSameNameDepth() { |
| return sameNameDepth; |
| } |
| |
| /** |
| * Returns the id of this macro instance. The id is the name, concatenated with {@code ":n"} where |
| * n is an integer distinguishing this from other macro instances of the same name in the package. |
| * |
| * <p>Within a package, two macro instances are not allowed to share the same name except when one |
| * of them is the main submacro of the other. More generally, there may be a contiguous chain of |
| * nested main submacros that all share the same name, but these may not share with any other |
| * macro outside the chain. We allow this exception so that the build does not break if the rule |
| * of a main target is refactored into a macro. The tradeoff of this design is that the name alone |
| * is not enough to disambiguate between macros in the chain. |
| * |
| * <p>The number n is simply the depth of the macro in the chain of same-named macros, starting at |
| * 1. For example, if we have a chain of macro expansions foo -> foo_bar -> foo_bar -> foo_bar -> |
| * foo_bar_baz, then the ids of these macros are respectively "foo:1", "foo_bar:1", "foo_bar:2", |
| * "foo_bar:3", "foo_bar_baz:1". |
| * |
| * <p>Note that ids only serve to canonically identify macro instances, and play no role in naming |
| * or name conflict detection. |
| */ |
| public String getId() { |
| return getName() + ":" + sameNameDepth; |
| } |
| |
| /** |
| * Returns the name of this instance, as given in the {@code name = ...} attribute in the calling |
| * BUILD file or macro. |
| */ |
| public String getName() { |
| // Type and existence enforced by RuleClass.NAME_ATTRIBUTE. |
| return (String) Preconditions.checkNotNull(attrValues.get("name")); |
| } |
| |
| /** |
| * Returns the visibility of this macro instance. |
| * |
| * <p>This value will be observed as the {@code visibility} parameter of the implementation |
| * function. It is not necessarily the same as the {@code visibility} value passed in when |
| * instantiating the macro, since the latter needs processing to add the call site's location and |
| * possibly apply the package's default visibility. |
| * |
| * <p>It can be assumed that the returned list satisfies {@link RuleVisibility#validate}. |
| */ |
| public ImmutableList<Label> getVisibility() { |
| @SuppressWarnings("unchecked") |
| List<Label> visibility = (List<Label>) Preconditions.checkNotNull(attrValues.get("visibility")); |
| return ImmutableList.copyOf(visibility); |
| } |
| |
| /** |
| * Dictionary of attributes for this instance. |
| * |
| * <p>Contains all attributes, as seen after processing by {@link |
| * MacroClass#instantiateAndAddMacro}. |
| */ |
| public ImmutableMap<String, Object> getAttrValues() { |
| return attrValues; |
| } |
| |
| /** |
| * Returns a {@code RuleVisibility} representing the result of concatenating this macro's {@link |
| * MacroClass}'s definition location to the given {@code visibility}. |
| * |
| * <p>The definition location of a macro class is the package containing the .bzl file from which |
| * the macro class was exported. |
| * |
| * <p>Logically, this represents the visibility that a target would have, if it were passed the |
| * given value for its {@code visibility} attribute, and if the target were declared directly in |
| * this macro (i.e. not in a submacro). |
| */ |
| // TODO: #19922 - Maybe refactor this to getDefinitionLocation and let the caller do the concat, |
| // so we don't have to basically repeat it in MacroClass#instantiateMacro. |
| public RuleVisibility concatDefinitionLocationToVisibility(RuleVisibility visibility) { |
| PackageIdentifier macroLocation = macroClass.getDefiningBzlLabel().getPackageIdentifier(); |
| return RuleVisibility.concatWithPackage(visibility, macroLocation); |
| } |
| |
| /** |
| * Visits all labels appearing in non-implicit attributes of {@link Type.LabelClass#DEPENDENCY} |
| * label type, i.e. ignoring nodep labels. |
| * |
| * <p>This is useful for checking whether a given label was passed as an input to this macro by |
| * the caller, which in turn is needed in order to decide whether the caller delegated a |
| * visibility privilege to us. |
| */ |
| public void visitExplicitAttributeLabels(Consumer<Label> consumer) { |
| for (Attribute attribute : macroClass.getAttributes().values()) { |
| String name = attribute.getName(); |
| Type<?> type = attribute.getType(); |
| if (name.startsWith("_")) { |
| continue; |
| } |
| if (type.getLabelClass() != Type.LabelClass.DEPENDENCY) { |
| continue; |
| } |
| Object value = attrValues.get(name); |
| if (value == Starlark.NONE) { |
| continue; |
| } |
| visitAttributeLabels(value, type, attribute, consumer); |
| } |
| } |
| |
| // Separate method needed to satisfy type system w.r.t. Type<T>. |
| // `value` is either a T or SelectorList<T>. |
| private static <T> void visitAttributeLabels( |
| Object value, Type<T> type, Attribute attribute, Consumer<Label> consumer) { |
| // The attribute value is stored as a Starlark value. Convert it to the internal type as would |
| // be used in rules, so we can apply visitLabels() machinery to it. selectableConvert() will |
| // yield either a T or a BuildType.SelectorList. |
| Object convertedValue; |
| try { |
| convertedValue = |
| BuildType.selectableConvert( |
| type, |
| value, |
| "macro attribute (internal)", |
| // No string -> Label conversion is being done here. |
| /* context= */ null, |
| // Macros always preserve selects as selects. |
| /* simplifyUnconditionalSelects= */ false); |
| } catch (ConversionException e) { |
| // TODO: #19922 - The fact that we have to do this seems like a signal that we should |
| // transition to storing macro attribute values as native-typed attributes in the future. |
| throw new IllegalStateException("Could not convert macro attribute value internally", e); |
| } |
| |
| // Unlike rules, null attribute values are disallowed here by construction (the attrValues |
| // map won't tolerate them). It's unclear if the visitor can be passed null values like it can |
| // for rules, so filter them out just in case. |
| Type.LabelVisitor visitor = |
| (label, unusedAttribute) -> { |
| if (label != null) { |
| consumer.accept(label); |
| } |
| }; |
| |
| if (convertedValue instanceof SelectorList) { |
| @SuppressWarnings("unchecked") // safe by precondition assumption |
| SelectorList<T> selectorList = (SelectorList<T>) convertedValue; |
| AggregatingAttributeMapper.visitLabelsInSelect( |
| selectorList, |
| attribute, |
| type, |
| visitor, |
| /* rule= */ null, // safe because late-bound defaults aren't a thing for macros |
| /* includeKeys= */ false, |
| /* includeValues= */ true); |
| } else { |
| T castValue = type.cast(convertedValue); |
| type.visitLabels(visitor, castValue, attribute); |
| } |
| } |
| |
| /** |
| * Logical tuple of the package and id within the package. Used to label the Starlark evaluation |
| * environment. |
| */ |
| @AutoValue |
| abstract static class UniqueId { |
| static UniqueId create(PackageIdentifier packageId, String id) { |
| return new AutoValue_MacroInstance_UniqueId(packageId, id); |
| } |
| |
| abstract PackageIdentifier packageId(); |
| |
| abstract String id(); |
| } |
| } |