| // 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 static java.util.Objects.requireNonNull; | |
| import com.google.common.annotations.VisibleForTesting; | |
| import com.google.common.base.Preconditions; | |
| import com.google.common.collect.ImmutableList; | |
| 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.Package.Declarations; | |
| import com.google.devtools.build.lib.server.FailureDetails.PackageLoading; | |
| import com.google.devtools.build.lib.util.HashCodes; | |
| import java.util.List; | |
| import java.util.Objects; | |
| import java.util.function.Consumer; | |
| import javax.annotation.Nullable; | |
| import net.starlark.java.eval.StarlarkThread; | |
| import net.starlark.java.syntax.Location; | |
| /** | |
| * 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 extends RuleOrMacroInstance { | |
| // 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.Metadata packageMetadata; | |
| // TODO(bazel-team): This is only needed for RuleOrMacroInstance#getPackageDeclarations(), which | |
| // is used by the attribute mapper logic. That might only be needed for rules rather than macros. | |
| // Consider removing it and pushing getPackageDeclarations() down to Rule. | |
| private final Package.Declarations packageDeclarations; | |
| // TODO(https://github.com/bazelbuild/bazel/issues/26128): replace with a parent identifier. The | |
| // existence of a parent pointer prevents change pruning on outer macro instances, forcing an | |
| // unconditional re-evaluation of all inner macros when an outer macro is invalidated. | |
| @Nullable private final MacroInstance parent; | |
| // Null if this symbolic macro was instantiated as a result of a legacy macro call without a | |
| // "name" parameter made at the top level of a BUILD file. | |
| @Nullable private final String generatorName; | |
| // TODO(https://github.com/bazelbuild/bazel/issues/26128): move location and Starlark stack to the | |
| // owning PackagePiece to make MacroInstance more change pruning friendly; we don't want the macro | |
| // to be invalidated if line numbers in a BUILD file or an ancestor macro's definition .bzl file | |
| // change. | |
| private final Location buildFileLocation; | |
| private final CallStack.Node parentCallStack; | |
| private final MacroClass macroClass; | |
| private final int sameNameDepth; | |
| /** | |
| * Instantiates the given macro class. | |
| * | |
| * <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. | |
| */ | |
| MacroInstance( | |
| Package.Metadata packageMetadata, | |
| Declarations packageDeclarations, | |
| @Nullable MacroInstance parent, | |
| @Nullable String generatorName, | |
| Location buildFileLocation, | |
| CallStack.Node parentCallStack, | |
| MacroClass macroClass, | |
| Label label, | |
| int sameNameDepth) { | |
| super(label, macroClass.getAttributeProvider().getAttributeCount()); | |
| this.packageMetadata = packageMetadata; | |
| this.packageDeclarations = packageDeclarations; | |
| this.parent = parent; | |
| this.generatorName = generatorName; | |
| this.buildFileLocation = buildFileLocation; | |
| this.parentCallStack = parentCallStack; | |
| this.macroClass = macroClass; | |
| Preconditions.checkArgument(sameNameDepth > 0); | |
| this.sameNameDepth = sameNameDepth; | |
| } | |
| @Override | |
| public boolean equals(Object obj) { | |
| if (this == obj) { | |
| return true; | |
| } | |
| // TODO(https://github.com/bazelbuild/bazel/issues/26128): consider comparing digests instead. | |
| return obj instanceof MacroInstance other | |
| && super.equalsHelper(other) | |
| && Objects.equals(packageMetadata, other.packageMetadata) | |
| && Objects.equals(packageDeclarations, other.packageDeclarations) | |
| && Objects.equals(parent, other.parent) | |
| && Objects.equals(generatorName, other.generatorName) | |
| && Objects.equals(buildFileLocation, other.buildFileLocation) | |
| && Objects.equals(parentCallStack, other.parentCallStack) | |
| && Objects.equals(macroClass, other.macroClass) | |
| && sameNameDepth == other.sameNameDepth; | |
| } | |
| @Override | |
| public int hashCode() { | |
| return super.hashCodeHelper() | |
| + HashCodes.MULTIPLIER | |
| * HashCodes.hashObjects( | |
| packageMetadata, | |
| packageDeclarations, | |
| parent, | |
| generatorName, | |
| buildFileLocation, | |
| parentCallStack, | |
| macroClass, | |
| sameNameDepth); | |
| } | |
| @Override | |
| public Package.Metadata getPackageMetadata() { | |
| return packageMetadata; | |
| } | |
| @Override | |
| Declarations getPackageDeclarations() { | |
| return packageDeclarations; | |
| } | |
| /** | |
| * Returns the macro instance that instantiated this one, or null if this was created directly | |
| * during BUILD evaluation. | |
| */ | |
| // TODO(bazel-team): Consider merging into getDeclaringMacro(). | |
| // TODO(https://github.com/bazelbuild/bazel/issues/26128): Avoid new uses of this method; it is | |
| // hostile to change pruning for lazy macro expansion. Replace with a method that either returns | |
| // the parent identifier, or takes a context argument that allows retrieving the parent by id. | |
| @Nullable | |
| public MacroInstance getParent() { | |
| return parent; | |
| } | |
| // TODO(https://github.com/bazelbuild/bazel/issues/26128): Avoid new uses of this method; it is | |
| // hostile to change pruning for lazy macro expansion. Replace with a method that either returns | |
| // the parent identifier, or takes a context argument that allows retrieving the parent by id. | |
| @Override | |
| @Nullable | |
| public MacroInstance getDeclaringMacro() { | |
| return parent; | |
| } | |
| /** | |
| * Returns the location in the BUILD file at which this macro was created or its outermost | |
| * enclosing symbolic or legacy macro was called. | |
| */ | |
| public Location getBuildFileLocation() { | |
| return buildFileLocation; | |
| } | |
| /** | |
| * Returns the value of the "name" parameter of the top-level call in a BUILD file which resulted | |
| * in this macro being instantiated. | |
| * | |
| * <p>This is either the "name" attribute of this macro's outermost symbolic macro ancestor, if it | |
| * was defined directly at the top level of a BUILD file; or the "name" parameter of the outermost | |
| * legacy macro wrapping it. | |
| * | |
| * <p>Null if this symbolic macro was instantiated as a result of a legacy macro call without a | |
| * "name" parameter made at the top level of a BUILD file. | |
| */ | |
| @Nullable | |
| public String getGeneratorName() { | |
| return generatorName; | |
| } | |
| /** | |
| * Returns the call stack of the Starlark thread that created this macro instance. | |
| * | |
| * <p>If this macro was instantiated in a BUILD file thread (as contrasted with a symbolic macro | |
| * thread), the call stack does not include the frame for the BUILD file top level, since it's | |
| * redundant with {@link #getBuildFileLocation}. | |
| */ | |
| CallStack.Node getParentCallStack() { | |
| return parentCallStack; | |
| } | |
| /** | |
| * Returns the call stack of the Starlark thread that created this macro instance. | |
| * | |
| * <p>Requires reconstructing the call stack from a compact representation, so should only be | |
| * called when the full call stack is needed. | |
| */ | |
| @VisibleForTesting | |
| public ImmutableList<StarlarkThread.CallStackEntry> reconstructParentCallStack() { | |
| ImmutableList.Builder<StarlarkThread.CallStackEntry> stack = ImmutableList.builder(); | |
| if (parent == null) { | |
| stack.add(StarlarkThread.callStackEntry(StarlarkThread.TOP_LEVEL, buildFileLocation)); | |
| } | |
| for (CallStack.Node node = parentCallStack; node != null; node = node.next()) { | |
| stack.add(node.toCallStackEntry()); | |
| } | |
| return stack.build(); | |
| } | |
| /** 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; | |
| } | |
| @Override | |
| public RuleVisibility getDefaultVisibility() { | |
| return RuleVisibility.parseUnchecked( | |
| ImmutableList.of( | |
| Label.createUnvalidated( | |
| macroClass.getDefiningBzlLabel().getPackageIdentifier(), "__pkg__"))); | |
| } | |
| /** | |
| * Returns the visibility of this macro instance, analogous to {@link Target#getActualVisibility}. | |
| * | |
| * <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> getActualVisibility() { | |
| @SuppressWarnings("unchecked") | |
| List<Label> visibility = (List<Label>) Preconditions.checkNotNull(getAttr("visibility")); | |
| return ImmutableList.copyOf(visibility); | |
| } | |
| /** | |
| * Returns the package containing the .bzl file from which this macro instance's macro class was | |
| * exported. | |
| * | |
| * <p>This is considered to be the place where the macro's code lives, and is used as the place | |
| * where a target is instantiated for the purposes of Macro-Aware Visibility. | |
| */ | |
| public PackageIdentifier getDefinitionPackage() { | |
| return macroClass.getDefiningBzlLabel().getPackageIdentifier(); | |
| } | |
| /** | |
| * 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.getAttributeProvider().getAttributes()) { | |
| String name = attribute.getName(); | |
| Type<?> type = attribute.getType(); | |
| if (name.startsWith("_")) { | |
| continue; | |
| } | |
| if (type.getLabelClass() != Type.LabelClass.DEPENDENCY) { | |
| continue; | |
| } | |
| Object value = getAttr(name, type); | |
| 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 <T> void visitAttributeLabels( | |
| Object value, Type<T> type, Attribute attribute, Consumer<Label> consumer) { | |
| if (value == null) { | |
| return; | |
| } | |
| Type.LabelVisitor visitor = | |
| (label, unusedAttribute) -> { | |
| if (label != null) { | |
| consumer.accept(label); | |
| } | |
| }; | |
| if (value instanceof SelectorList) { | |
| @SuppressWarnings("unchecked") // safe by precondition assumption | |
| SelectorList<T> selectorList = (SelectorList<T>) value; | |
| 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(value); | |
| type.visitLabels(visitor, castValue, attribute); | |
| } | |
| } | |
| @Override | |
| public AttributeProvider getAttributeProvider() { | |
| return macroClass.getAttributeProvider(); | |
| } | |
| // TODO(bazel-team): Merge with Rule#reportError after graveyarding | |
| // --incompatible_symbolic_macro_strict_attrs | |
| @Override | |
| void reportError(String message, TargetDefinitionContext targetDefinitionContext) { | |
| targetDefinitionContext | |
| .getLocalEventHandler() | |
| .handle(Package.error(buildFileLocation, message, PackageLoading.Code.STARLARK_EVAL_ERROR)); | |
| if (targetDefinitionContext.symbolicMacroStrictAttrs()) { | |
| targetDefinitionContext.setContainsErrors(); | |
| } | |
| } | |
| @Override | |
| public boolean isRuleInstance() { | |
| return false; | |
| } | |
| @Override | |
| public boolean isRuleCreatedInMacro() { | |
| return false; | |
| } | |
| /** Returns a human-readable description of the macro suitable for debugging output. */ | |
| public String getShortDescription() { | |
| return String.format( | |
| "%smacro %s:%s defined by %s%%%s", | |
| macroClass.isFinalizer() ? "finalizer " : "", | |
| packageMetadata.packageIdentifier().getCanonicalForm(), | |
| getName(), | |
| macroClass.getDefiningBzlLabel().getCanonicalForm(), | |
| macroClass.getName()); | |
| } | |
| /** | |
| * Logical tuple of the package and id within the package. Used to label the Starlark evaluation | |
| * environment. | |
| */ | |
| record UniqueId(PackageIdentifier packageId, String id) { | |
| UniqueId { | |
| requireNonNull(packageId, "packageId"); | |
| requireNonNull(id, "id"); | |
| } | |
| static UniqueId create(PackageIdentifier packageId, String id) { | |
| return new UniqueId(packageId, id); | |
| } | |
| } | |
| } |