// Copyright 2014 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 com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.devtools.build.lib.analysis.AspectCollection.AspectCycleOnPathException; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; import com.google.devtools.build.lib.analysis.config.BuildOptions; import com.google.devtools.build.lib.analysis.config.ConfigMatchingProvider; import com.google.devtools.build.lib.analysis.config.DynamicTransitionMapper; import com.google.devtools.build.lib.analysis.config.HostTransition; import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException; import com.google.devtools.build.lib.analysis.config.PatchTransition; import com.google.devtools.build.lib.analysis.config.TransitionResolver; import com.google.devtools.build.lib.analysis.config.transitions.ConfigurationTransitionProxy; import com.google.devtools.build.lib.analysis.config.transitions.SplitTransition; import com.google.devtools.build.lib.analysis.config.transitions.Transition; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; import com.google.devtools.build.lib.events.Location; import com.google.devtools.build.lib.packages.Aspect; import com.google.devtools.build.lib.packages.AspectClass; import com.google.devtools.build.lib.packages.AspectDescriptor; import com.google.devtools.build.lib.packages.Attribute; import com.google.devtools.build.lib.packages.Attribute.LateBoundDefault; import com.google.devtools.build.lib.packages.AttributeMap; import com.google.devtools.build.lib.packages.BuildType; import com.google.devtools.build.lib.packages.ConfiguredAttributeMapper; import com.google.devtools.build.lib.packages.EnvironmentGroup; import com.google.devtools.build.lib.packages.InputFile; import com.google.devtools.build.lib.packages.NoSuchThingException; import com.google.devtools.build.lib.packages.OutputFile; import com.google.devtools.build.lib.packages.PackageGroup; 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.syntax.EvalException; import com.google.devtools.build.lib.syntax.EvalUtils; import com.google.devtools.build.lib.util.OrderedSetMultimap; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; /** * Resolver for dependencies between configured targets. * *

Includes logic to derive the right configurations depending on transition type. */ public abstract class DependencyResolver { private final TransitionResolver transitionResolver; protected DependencyResolver(DynamicTransitionMapper transitionMapper) { this.transitionResolver = new TransitionResolver(transitionMapper); } /** * Returns ids for dependent nodes of a given node, sorted by attribute. Note that some * dependencies do not have a corresponding attribute here, and we use the null attribute to * represent those edges. * *

If {@code aspect} is null, returns the dependent nodes of the configured target node * representing the given target and configuration, otherwise that of the aspect node accompanying * the aforementioned configured target node for the specified aspect. * *

The values are not simply labels because this also implements the first step of applying * configuration transitions, namely, split transitions. This needs to be done before the labels * are resolved because late bound attributes depend on the configuration. A good example for this * is @{code :cc_toolchain}. * *

The long-term goal is that most configuration transitions be applied here. However, in order * to do that, we first have to eliminate transitions that depend on the rule class of the * dependency. * * @param node the target/configuration being evaluated * @param hostConfig the configuration this target would use if it was evaluated as a host tool. * This is needed to support {@link LateBoundDefault#useHostConfiguration()}. * @param aspect the aspect applied to this target (if any) * @param configConditions resolver for config_setting labels * @param toolchainContext {@link ToolchainContext} for this target * @return a mapping of each attribute in this rule or aspects to its dependent nodes */ public final OrderedSetMultimap dependentNodeMap( TargetAndConfiguration node, BuildConfiguration hostConfig, @Nullable Aspect aspect, ImmutableMap configConditions, ToolchainContext toolchainContext) throws EvalException, InvalidConfigurationException, InterruptedException, InconsistentAspectOrderException { NestedSetBuilder

If {@code aspects} is empty, returns the dependent nodes of the configured target node * representing the given target and configuration. * *

Otherwise {@code aspects} represents an aspect path. The function returns dependent nodes of * the entire path applied to given target and configuration. These are the depenent nodes of the * last aspect in the path. * *

This also implements the first step of applying configuration transitions, namely, split * transitions. This needs to be done before the labels are resolved because late bound attributes * depend on the configuration. A good example for this is @{code :cc_toolchain}. * *

The long-term goal is that most configuration transitions be applied here. However, in order * to do that, we first have to eliminate transitions that depend on the rule class of the * dependency. * * @param node the target/configuration being evaluated * @param hostConfig the configuration this target would use if it was evaluated as a host tool. * This is needed to support {@link LateBoundDefault#useHostConfiguration()}. * @param aspects the aspects applied to this target (if any) * @param configConditions resolver for config_setting labels * @param toolchainContext context information for required toolchains * @param rootCauses collector for dep labels that can't be (loading phase) loaded @return a * mapping of each attribute in this rule or aspects to its dependent nodes */ public final OrderedSetMultimap dependentNodeMap( TargetAndConfiguration node, BuildConfiguration hostConfig, Iterable aspects, ImmutableMap configConditions, @Nullable ToolchainContext toolchainContext, NestedSetBuilder

Late-bound attributes need special handling because they require configuration * transitions to determine their values. * *

In other words, the normal process of dependency resolution is: *

    *
  1. Find every label value in the rule's attributes
  2. *
  3. Apply configuration transitions over each value to get its dep configuration *
  4. Return each value with its dep configuration
  5. *
* * This doesn't work for late-bound attributes because you can't get their values without * knowing the configuration first. And that configuration may not be the owning rule's * configuration. Specifically, {@link LateBoundDefault#useHostConfiguration()} switches to the * host config and late-bound split attributes branch into multiple split configs. * *

This method implements that logic and makes sure the normal configuration * transition logic mixes with it cleanly. * * @param depResolver the resolver for this rule's deps * @param ruleConfig the rule's configuration * @param hostConfig the equivalent host configuration */ private void resolveLateBoundAttributes( RuleResolver depResolver, BuildConfiguration ruleConfig, BuildConfiguration hostConfig) throws EvalException, InvalidConfigurationException, InconsistentAspectOrderException, InterruptedException{ ConfiguredAttributeMapper attributeMap = depResolver.attributeMap; for (AttributeAndOwner attributeAndOwner : depResolver.attributes) { Attribute attribute = attributeAndOwner.attribute; if (!attribute.isLateBound() || !attribute.getCondition().apply(attributeMap)) { continue; } LateBoundDefault lateBoundDefault = attribute.getLateBoundDefault(); Collection splitOptions = getSplitOptions(attributeMap, attribute, ruleConfig); if (!splitOptions.isEmpty() && !ruleConfig.isHostConfiguration()) { // Late-bound attribute with a split transition: // Since we want to get the same results as TransitionResolver.evaluateTransition (but // skip it since we've already applied the split), we want to make sure this logic // doesn't do anything differently. TransitionResolver.evaluateTransition has additional // logic for host configs. So when we're in the host configuration we fall back to the // non-split branch, which calls TransitionResolver.evaluateTransition, which returns its // "host mode" result without ever looking at the split. Iterable splitConfigs = getConfigurations(ruleConfig.fragmentClasses(), splitOptions); if (splitConfigs == null) { continue; // Need Skyframe deps. } for (BuildConfiguration splitConfig : splitConfigs) { for (Label dep : resolveLateBoundAttribute(depResolver.rule, attribute, lateBoundDefault.useHostConfiguration() ? hostConfig : splitConfig, attributeMap)) { // Skip the normal config transition pipeline and directly feed the split config. This // is because the split already had to be applied to determine the attribute's value. // This makes the split logic in the normal pipeline redundant and potentially // incorrect. depResolver.resolveDep(attributeAndOwner, dep, splitConfig); } } } else { // Late-bound attribute without a split transition: for (Label dep : resolveLateBoundAttribute(depResolver.rule, attribute, lateBoundDefault.useHostConfiguration() ? hostConfig : ruleConfig, attributeMap)) { // Process this dep like a normal attribute. depResolver.resolveDep(attributeAndOwner, dep); } } } } private void resolveToolchainDependencies( Set dependencies, ToolchainContext toolchainContext) { for (Label label : toolchainContext.getResolvedToolchainLabels()) { Dependency dependency = Dependency.withTransitionAndAspects( label, HostTransition.INSTANCE, AspectCollection.EMPTY); dependencies.add(dependency); } } /** * Returns the BuildOptions if the rule's attribute triggers a split in this configuration, or * the empty collection if the attribute does not trigger a split transition or if the split * transition does not apply. * *

Even though the attribute may have a split, splits don't have to apply in every * configuration (see {@link SplitTransition#split}). */ private static Collection getSplitOptions(ConfiguredAttributeMapper attributeMap, Attribute attribute, BuildConfiguration ruleConfig) { if (!attribute.hasSplitConfigurationTransition()) { return ImmutableList.of(); } SplitTransition transition = attribute.getSplitTransition(attributeMap); return transition.split(ruleConfig.getOptions()); } /** * Returns the label dependencies for the given late-bound attribute in this rule. * * @param rule the rule being evaluated * @param attribute the attribute to evaluate * @param config the configuration to evaluate the attribute in * @param attributeMap mapper to attribute values */ private Iterable

The last aspect in {@code aspectPath} is (potentially) visible and recorded in {@code * visibleAspects}. */ private static void collectPropagatingAspects( Iterable aspectPath, AttributeAndOwner attributeAndOwner, Rule target, ImmutableList.Builder filteredAspectPath, ImmutableSet.Builder visibleAspects) { Aspect lastAspect = null; for (Aspect aspect : aspectPath) { if (aspect.getAspectClass().equals(attributeAndOwner.ownerAspect)) { // Do not propagate over the aspect's own attributes. continue; } lastAspect = aspect; if (aspect.getDefinition().propagateAlong(attributeAndOwner.attribute) && aspect .getDefinition() .getRequiredProviders() .isSatisfiedBy(target.getRuleClassObject().getAdvertisedProviders())) { filteredAspectPath.add(aspect); } else { lastAspect = null; } } if (lastAspect != null) { visibleAspects.add(lastAspect.getDescriptor()); } } /** * Collect all aspects that originate on {@code attribute} of {@code originalRule} * and are applicable to a {@code target} * * They are appended to {@code filteredAspectPath} and registered in {@code visibleAspects} set. */ private static void collectOriginatingAspects( Rule originalRule, Attribute attribute, Rule target, ImmutableList.Builder filteredAspectPath, ImmutableSet.Builder visibleAspects) { ImmutableList baseAspects = attribute.getAspects(originalRule); RuleClass ruleClass = target.getRuleClassObject(); for (Aspect baseAspect : baseAspects) { if (baseAspect.getDefinition().getRequiredProviders() .isSatisfiedBy(ruleClass.getAdvertisedProviders())) { filteredAspectPath.add(baseAspect); visibleAspects.add(baseAspect.getDescriptor()); } } } /** * Pair of (attribute, owner aspect if attribute is from an aspect). * *

For "plain" rule attributes, this wrapper class will have value (attribute, null). */ final class AttributeAndOwner { final Attribute attribute; final @Nullable AspectClass ownerAspect; AttributeAndOwner(Attribute attribute) { this(attribute, null); } AttributeAndOwner(Attribute attribute, @Nullable AspectClass ownerAspect) { this.attribute = attribute; this.ownerAspect = ownerAspect; } } /** * Supplies the logic for translating pairs for a rule into the * pairs DependencyResolver ultimately returns. * *

The main difference between the two is that the latter applies configuration transitions, * i.e. it specifies not just which deps a rule has but also the configurations those deps * should take. */ private class RuleResolver { private final Rule rule; private final BuildConfiguration ruleConfig; private final Iterable aspects; private final ConfiguredAttributeMapper attributeMap; private final NestedSetBuilder

Use this method with care: it skips Bazel's standard config transition semantics ({@link * TransitionResolver#evaluateTransition}). That means attributes passed through here won't * obey standard rules on which configurations apply to their deps. This should only be done for * special circumstances that really justify the difference. When in doubt, use {@link * #resolveDep(AttributeAndOwner, Label)}. */ void resolveDep(AttributeAndOwner attributeAndOwner, Label depLabel, BuildConfiguration config) throws InterruptedException, InconsistentAspectOrderException { Target toTarget = getTarget(rule, depLabel, rootCauses); if (toTarget == null) { return; // Skip this round: this is either a loading error or unevaluated Skyframe dep. } outgoingEdges.put( attributeAndOwner.attribute, transitionResolver.usesNullConfiguration(toTarget) ? Dependency.withNullConfiguration(depLabel) : Dependency.withTransitionAndAspects(depLabel, new FixedTransition( config.getOptions()), requiredAspects(attributeAndOwner, toTarget))); } private AspectCollection requiredAspects(AttributeAndOwner attributeAndOwner, final Target target) throws InconsistentAspectOrderException { if (!(target instanceof Rule)) { return AspectCollection.EMPTY; } ImmutableList.Builder filteredAspectPath = ImmutableList.builder(); ImmutableSet.Builder visibleAspects = ImmutableSet.builder(); if (attributeAndOwner.ownerAspect == null) { collectOriginatingAspects( rule, attributeAndOwner.attribute, (Rule) target, filteredAspectPath, visibleAspects); } collectPropagatingAspects( aspects, attributeAndOwner, (Rule) target, filteredAspectPath, visibleAspects); try { return AspectCollection.create(filteredAspectPath.build(), visibleAspects.build()); } catch (AspectCycleOnPathException e) { throw new InconsistentAspectOrderException(rule, attributeAndOwner.attribute, target, e); } } } /** * A patch transition that returns a fixed set of options regardless of the input. */ private static class FixedTransition implements PatchTransition { private final BuildOptions toOptions; FixedTransition(BuildOptions toOptions) { this.toOptions = toOptions; } @Override public BuildOptions apply(BuildOptions options) { return toOptions; } } private void visitTargetVisibility( TargetAndConfiguration node, NestedSetBuilder

Returns null if the target is not ready to be returned at this moment. If getTarget returns * null once or more during a {@link #dependentNodeMap} call, the results of that call will be * incomplete. For use within Skyframe, where several iterations may be needed to discover all * dependencies. */ @Nullable protected abstract Target getTarget(Target from, Label label, NestedSetBuilder

Returns null if any configurations aren't ready to be returned at this moment. If * getConfigurations returns null once or more during a {@link #dependentNodeMap} call, the * results of that call will be incomplete. For use within Skyframe, where several iterations may * be needed to discover all dependencies. */ @Nullable protected abstract List getConfigurations( Set> fragments, Iterable buildOptions) throws InvalidConfigurationException, InterruptedException; /** * Signals an inconsistency on aspect path: an aspect occurs twice on the path and * the second occurrence sees a different set of aspects. * * {@see AspectCycleOnPathException} */ public class InconsistentAspectOrderException extends Exception { private final Location location; public InconsistentAspectOrderException(Rule originalRule, Attribute attribute, Target target, AspectCycleOnPathException e) { super(String.format("%s (when propagating from %s to %s via attribute %s)", e.getMessage(), originalRule.getLabel(), target.getLabel(), attribute.getName())); this.location = originalRule.getLocation(); } public Location getLocation() { return location; } } }