blob: 9b977bde31425a277270e700f6cf2889dd3409d5 [file] [log] [blame]
// 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 java.util.Collections.reverse;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;
import com.google.devtools.build.lib.analysis.AspectCollection.AspectCycleOnPathException;
import com.google.devtools.build.lib.analysis.starlark.StarlarkAspectPropagationContext;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.events.ExtendedEventHandler;
import com.google.devtools.build.lib.packages.AdvertisedProviderSet;
import com.google.devtools.build.lib.packages.Aspect;
import com.google.devtools.build.lib.packages.AspectClass;
import com.google.devtools.build.lib.packages.AspectDefinition;
import com.google.devtools.build.lib.packages.AspectPropagationEdgesSupplier.FixedListSupplier;
import com.google.devtools.build.lib.packages.AspectPropagationEdgesSupplier.FunctionSupplier;
import com.google.devtools.build.lib.packages.Attribute;
import com.google.devtools.build.lib.packages.ConfiguredAttributeMapper;
import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.packages.RuleClass;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.skyframe.toolchains.UnloadedToolchainContext;
import com.google.devtools.build.lib.util.OrderedSetMultimap;
import java.util.ArrayList;
import javax.annotation.Nullable;
import net.starlark.java.eval.EvalException;
import net.starlark.java.syntax.Location;
/** 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,
ImmutableMultimap<Aspect, String> computedAttributeAspects,
ImmutableMultimap<Aspect, Label> computedToolchainsAspects,
Rule rule,
@Nullable ToolchainCollection<UnloadedToolchainContext> baseTargetToolchainContext) {
if (DependencyKind.isBaseTargetToolchain(kind)) {
return computePropagatingAspectsToToolchainDep(
(DependencyKind.BaseTargetToolchainDependencyKind) kind,
aspectsPath,
computedToolchainsAspects,
baseTargetToolchainContext);
}
Attribute attribute = kind.getAttribute();
if (attribute == null) {
return ImmutableList.of();
}
var aspectsBuilder = new ImmutableList.Builder<Aspect>().addAll(attribute.getAspects(rule));
collectPropagatingAspects(
aspectsPath,
computedAttributeAspects,
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,
ImmutableMultimap<Aspect, Label> computedToolchainsAspects,
@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(t -> propagatesTo(t, aspect, computedToolchainsAspects))
|| 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);
}
private static <T> boolean propagatesTo(
T edge, Aspect aspect, ImmutableMultimap<Aspect, T> computedEdges) {
return computedEdges.containsEntry(aspect, edge) || computedEdges.containsEntry(aspect, "*");
}
/**
* 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(
ImmutableList<Aspect> aspects,
AdvertisedProviderSet advertisedProviders,
Label targetLabel,
RuleClass ruleClass,
ImmutableList<String> tags,
Location targetLocation,
ExtendedEventHandler eventHandler)
throws InconsistentAspectOrderException, InterruptedException, EvalException {
var filteredAspectPath = new ArrayList<Aspect>();
int aspectsCount = aspects.size();
for (int i = aspectsCount - 1; i >= 0; i--) {
Aspect aspect = aspects.get(i);
if (AspectDefinition.satisfies(aspect, advertisedProviders)
|| isAspectRequired(aspect, filteredAspectPath)) {
// Considers the aspect if the target satisfies its required providers or it is
// required by an aspect already in the {@code filteredAspectPath}.
if (evaluatePropagationPredicate(aspect, targetLabel, ruleClass, tags, eventHandler)) {
// Only add the aspect if its propagation predicate is satisfied by the target.
filteredAspectPath.add(aspect);
}
}
}
reverse(filteredAspectPath);
return computeAspectCollectionNoAspectsFiltering(
ImmutableList.copyOf(filteredAspectPath), targetLabel, targetLocation);
}
public static AspectCollection computeAspectCollectionNoAspectsFiltering(
ImmutableList<Aspect> aspects, Label targetLabel, Location targetLocation)
throws InconsistentAspectOrderException {
try {
return AspectCollection.create(aspects);
} catch (AspectCycleOnPathException e) {
throw new InconsistentAspectOrderException(targetLabel, targetLocation, e);
}
}
private static boolean evaluatePropagationPredicate(
Aspect aspect,
Label label,
RuleClass ruleClass,
ImmutableList<String> tags,
ExtendedEventHandler eventHandler)
throws InterruptedException, EvalException {
if (aspect.getDefinition().getPropagationPredicate() == null) {
return true;
}
return aspect
.getDefinition()
.getPropagationPredicate()
.evaluate(
StarlarkAspectPropagationContext.createForPropagationPredicate(
aspect, label, ruleClass, tags),
eventHandler);
}
/**
* 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,
ImmutableMultimap<Aspect, String> computedAttributeAspects,
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 (propagatesTo(attributeName, aspect, computedAttributeAspects)
|| 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;
}
public static ImmutableMultimap<Aspect, String> computeAttributeAspects(
ImmutableList<Aspect> aspects,
Target target,
ConfiguredAttributeMapper attributeMap,
OrderedSetMultimap<DependencyKind, Label> dependencyLabels,
ExtendedEventHandler eventHandler)
throws InterruptedException, EvalException {
var result = ImmutableMultimap.<Aspect, String>builder();
for (Aspect aspect : aspects) {
var attributeAspects = aspect.getDefinition().getAttributeAspects();
switch (attributeAspects) {
case FixedListSupplier<String> s -> result.putAll(aspect, s.getList());
case FunctionSupplier<String> s when target.isRule() ->
result.putAll(
aspect,
s.computeList(
StarlarkAspectPropagationContext.createForPropagationEdges(
aspect, (Rule) target, attributeMap, dependencyLabels),
eventHandler));
default -> {}
}
}
return result.build();
}
public static ImmutableMultimap<Aspect, Label> computeToolchainsAspects(
ImmutableList<Aspect> aspects,
Target target,
ConfiguredAttributeMapper attributeMap,
OrderedSetMultimap<DependencyKind, Label> dependencyLabels,
ExtendedEventHandler eventHandler)
throws InterruptedException, EvalException {
var result = ImmutableMultimap.<Aspect, Label>builder();
for (Aspect aspect : aspects) {
var toolchainsAspects = aspect.getDefinition().getToolchainsAspects();
switch (toolchainsAspects) {
case FixedListSupplier<Label> s -> result.putAll(aspect, s.getList());
case FunctionSupplier<Label> s when target.isRule() ->
result.putAll(
aspect,
s.computeList(
StarlarkAspectPropagationContext.createForPropagationEdges(
aspect, (Rule) target, attributeMap, dependencyLabels),
eventHandler));
default -> {}
}
}
return result.build();
}
}