| // Copyright 2023 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.analysis; | |
| import static com.google.common.collect.ImmutableList.toImmutableList; | |
| import static java.util.Collections.reverse; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.devtools.build.lib.analysis.AspectCollection.AspectCycleOnPathException; | |
| import com.google.devtools.build.lib.packages.Aspect; | |
| import com.google.devtools.build.lib.packages.AspectClass; | |
| import com.google.devtools.build.lib.packages.Attribute; | |
| import com.google.devtools.build.lib.packages.Rule; | |
| import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData; | |
| import com.google.devtools.build.lib.skyframe.toolchains.UnloadedToolchainContext; | |
| import java.util.ArrayList; | |
| import javax.annotation.Nullable; | |
| /** Helpers for aspect resolution. */ | |
| public final class AspectResolutionHelpers { | |
| private AspectResolutionHelpers() {} | |
| /** | |
| * Computes the set of aspects that could be applied to a dependency. | |
| * | |
| * <p>This is composed of two parts: | |
| * | |
| * <ol> | |
| * <li>The aspects that are visible to this aspect being evaluated, if any. If another aspect is | |
| * visible on the configured target, it should also be visible on the dependencies for | |
| * consistency. This is the argument {@code aspectsPath}. | |
| * <li>The aspects propagated by the attributes of this configured target / aspect. | |
| * </ol> | |
| * | |
| * <p>The presence of an aspect here does not necessarily mean that it will be available on a | |
| * dependency: it can still be filtered out because it requires a provider that the configured | |
| * target it should be attached to it doesn't advertise. This is taken into account in {@link | |
| * #computeAspectCollection}. | |
| */ | |
| public static ImmutableList<Aspect> computePropagatingAspects( | |
| DependencyKind kind, | |
| ImmutableList<Aspect> aspectsPath, | |
| Rule rule, | |
| @Nullable ToolchainCollection<UnloadedToolchainContext> baseTargetToolchainContext) { | |
| if (DependencyKind.isBaseTargetToolchain(kind)) { | |
| return computePropagatingAspectsToToolchainDep( | |
| (DependencyKind.BaseTargetToolchainDependencyKind) kind, | |
| aspectsPath, | |
| baseTargetToolchainContext); | |
| } | |
| Attribute attribute = kind.getAttribute(); | |
| if (attribute == null) { | |
| return ImmutableList.of(); | |
| } | |
| var aspectsBuilder = new ImmutableList.Builder<Aspect>().addAll(attribute.getAspects(rule)); | |
| collectPropagatingAspects( | |
| aspectsPath, attribute.getName(), kind.getOwningAspect(), aspectsBuilder); | |
| return aspectsBuilder.build(); | |
| } | |
| /** | |
| * Compute the set of aspects propagating to the given {@link BaseTargetToolchainDependencyKind} | |
| * based on the {@code toolchains_aspects} of each aspect in the {@code aspectsPath}. | |
| */ | |
| private static ImmutableList<Aspect> computePropagatingAspectsToToolchainDep( | |
| DependencyKind.BaseTargetToolchainDependencyKind kind, | |
| ImmutableList<Aspect> aspectsPath, | |
| @Nullable ToolchainCollection<UnloadedToolchainContext> baseTargetToolchainContext) { | |
| var toolchainContext = baseTargetToolchainContext.getToolchainContext(kind.getExecGroupName()); | |
| var toolchainType = | |
| toolchainContext.requestedLabelToToolchainType().get(kind.getToolchainType()); | |
| // Since the label of the toolchain type can be an alias, we need to get all the labels that | |
| // point to the same toolchain type to compare them against the toolchain types that the aspects | |
| // can propagate. | |
| var allToolchainTypelabels = | |
| toolchainContext.requestedLabelToToolchainType().asMultimap().inverse().get(toolchainType); | |
| var filteredAspectPath = new ArrayList<Aspect>(); | |
| int aspectsCount = aspectsPath.size(); | |
| for (int i = aspectsCount - 1; i >= 0; i--) { | |
| Aspect aspect = aspectsPath.get(i); | |
| if (allToolchainTypelabels.stream() | |
| .anyMatch(label -> aspect.getDefinition().canPropagateToToolchainType(label)) | |
| || isAspectRequired(aspect, filteredAspectPath)) { | |
| // Adds the aspect if it propagates to the toolchain type or it is | |
| // required by an aspect already in the {@code filteredAspectPath}. | |
| filteredAspectPath.add(aspect); | |
| } | |
| } | |
| reverse(filteredAspectPath); | |
| return ImmutableList.copyOf(filteredAspectPath); | |
| } | |
| /** | |
| * Computes the way aspects should be computed for the direct dependencies. | |
| * | |
| * <p>This is done by filtering the aspects that can be propagated on any attribute according to | |
| * the providers advertised by direct dependencies and by creating the {@link AspectCollection} | |
| * that tells how to compute the final set of providers based on the interdependencies between the | |
| * propagating aspects. | |
| */ | |
| public static AspectCollection computeAspectCollection( | |
| ConfiguredTargetAndData prerequisite, ImmutableList<Aspect> propagatingAspects) | |
| throws InconsistentAspectOrderException { | |
| var aspects = propagatingAspects; | |
| if (prerequisite.isTargetOutputFile()) { | |
| // If `applyToGeneratingRules` holds, the aspect has no required providers so some of the | |
| // filtering logic below may be skipped, but it doesn't seem to be worth added complexity. | |
| aspects = | |
| aspects.stream() | |
| .filter(aspect -> aspect.getDefinition().applyToGeneratingRules()) | |
| .collect(toImmutableList()); | |
| } | |
| if (aspects.isEmpty() || (!prerequisite.isTargetOutputFile() && !prerequisite.isTargetRule())) { | |
| return AspectCollection.EMPTY; | |
| } | |
| var filteredAspectPath = new ArrayList<Aspect>(); | |
| int aspectsCount = aspects.size(); | |
| for (int i = aspectsCount - 1; i >= 0; i--) { | |
| Aspect aspect = aspects.get(i); | |
| if (prerequisite.satisfies(aspect.getDefinition().getRequiredProviders()) | |
| || isAspectRequired(aspect, filteredAspectPath)) { | |
| // Adds the aspect if the configured target satisfies its required providers or it is | |
| // required by an aspect already in the {@code filteredAspectPath}. | |
| filteredAspectPath.add(aspect); | |
| } | |
| } | |
| reverse(filteredAspectPath); | |
| try { | |
| return AspectCollection.create(filteredAspectPath); | |
| } catch (AspectCycleOnPathException e) { | |
| throw new InconsistentAspectOrderException( | |
| prerequisite.getTargetLabel(), prerequisite.getLocation(), e); | |
| } | |
| } | |
| /** | |
| * Collects the aspects from {@code aspectsPath} that need to be propagated along the attribute | |
| * {@code attributeName}. | |
| * | |
| * <p>It can happen that some of the aspects cannot be propagated if the dependency doesn't have a | |
| * provider that's required by them. These will be filtered out after the rule class of the | |
| * dependency is known. | |
| */ | |
| private static void collectPropagatingAspects( | |
| ImmutableList<Aspect> aspectsPath, | |
| String attributeName, | |
| @Nullable AspectClass aspectOwningAttribute, | |
| ImmutableList.Builder<Aspect> allFilteredAspects) { | |
| int aspectsNum = aspectsPath.size(); | |
| var filteredAspectsPath = new ArrayList<Aspect>(); | |
| // `aspectsPath` is ordered bottom up. Iterating backwards traverses top-down so the following | |
| // loop captures aspects that propagate along the given attribute and all their transitive | |
| // requirements. | |
| for (int i = aspectsNum - 1; i >= 0; i--) { | |
| Aspect aspect = aspectsPath.get(i); | |
| if (aspect.getAspectClass().equals(aspectOwningAttribute)) { | |
| // Do not propagate over the aspect's own attributes. | |
| continue; | |
| } | |
| if (aspect.getDefinition().propagateAlong(attributeName) | |
| || isAspectRequired(aspect, filteredAspectsPath)) { | |
| // Add the aspect if it can propagate over this {@code attributeName} based on its | |
| // attr_aspects or it is required by an aspect already in the {@code filteredAspectsPath}. | |
| filteredAspectsPath.add(aspect); | |
| } | |
| } | |
| // Reverse filteredAspectsPath to return it to the same order as the input aspectsPath. | |
| reverse(filteredAspectsPath); | |
| allFilteredAspects.addAll(filteredAspectsPath); | |
| } | |
| /** Checks if {@code aspect} is required by any {@link Aspect} in {@code aspectsPath}. */ | |
| private static boolean isAspectRequired(Aspect aspect, Iterable<Aspect> aspectsPath) { | |
| for (Aspect existingAspect : aspectsPath) { | |
| if (existingAspect.getDefinition().requires(aspect)) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| } |