// 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.FragmentClassSet; 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.TransitionResolver; import com.google.devtools.build.lib.analysis.config.transitions.ConfigurationTransition; import com.google.devtools.build.lib.analysis.config.transitions.NullTransition; import com.google.devtools.build.lib.analysis.config.transitions.PatchTransition; import com.google.devtools.build.lib.analysis.config.transitions.SplitTransition; import com.google.devtools.build.lib.causes.Cause; 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.RuleTransitionFactory; import com.google.devtools.build.lib.packages.Target; import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec; import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization; 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 { /** * 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 toolchainLabels required toolchain labels * @param defaultBuildOptions default build options provided to the server to use for creating * diffs during SkyKey construction * @param trimmingTransitionFactory the transition factory used to trim rules (note: this is a * temporary feature; see the corresponding methods in ConfiguredRuleClassProvider) * @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, ImmutableSet

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 toolchainLabels required toolchain labels * @param trimmingTransitionFactory the transition factory used to trim rules (note: this is a * temporary feature; see the corresponding methods in ConfiguredRuleClassProvider) * @param rootCauses collector for dep labels that can't be (loading phase) loaded * @param defaultBuildOptions default build options provided by the server to use for creating * diffs during SkyKey construction * @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, ImmutableSet

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. Apply configuration transitions over each value to get its dep configuration *
  3. Return each value with its dep configuration *
* * 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 * @param defaultBuildOptions default build options provided by the server to use for creating * diffs during SkyKey construction */ private void resolveLateBoundAttributes( RuleResolver depResolver, BuildConfiguration ruleConfig, BuildConfiguration hostConfig, BuildOptions defaultBuildOptions) 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(); boolean hasSplitTransition = false; List splitOptions = null; if (attribute.hasSplitConfigurationTransition()) { splitOptions = attribute.getSplitTransition(attributeMap).split(ruleConfig.getOptions()); hasSplitTransition = !SplitTransition.equals(ruleConfig.getOptions(), splitOptions); } if (hasSplitTransition && !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, defaultBuildOptions); 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, ImmutableSet

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 rootCauses; private final OrderedSetMultimap outgoingEdges; @Nullable private final RuleTransitionFactory trimmingTransitionFactory; private final List attributes; /** * Constructs a new dependency resolver for the specified rule context. * * @param rule the rule being evaluated * @param ruleConfig the rule's configuration * @param aspects the aspects applied to this rule (if any) * @param attributeMap mapper for the rule's attribute values * @param rootCauses output collector for dep labels that can't be (loading phase) loaded * @param outgoingEdges output collector for the resolved dependencies */ RuleResolver( Rule rule, BuildConfiguration ruleConfig, Iterable aspects, ConfiguredAttributeMapper attributeMap, NestedSetBuilder rootCauses, OrderedSetMultimap outgoingEdges, @Nullable RuleTransitionFactory trimmingTransitionFactory) { this.rule = rule; this.ruleConfig = ruleConfig; this.aspects = aspects; this.attributeMap = attributeMap; this.rootCauses = rootCauses; this.outgoingEdges = outgoingEdges; this.trimmingTransitionFactory = trimmingTransitionFactory; this.attributes = getAttributes( rule, // These are attributes that the application of `aspects` "path" // to the rule will see. Application of path is really the // application of the last aspect in the path, so we only let it see // it's own attributes. aspects); } /** Returns the attributes that should be visited for this rule/aspect combination. */ private List getAttributes(Rule rule, Iterable aspects) { ImmutableList.Builder result = ImmutableList.builder(); List ruleDefs = rule.getRuleClassObject().getAttributes(); for (Attribute attribute : ruleDefs) { result.add(new AttributeAndOwner(attribute)); } for (Aspect aspect : aspects) { for (Attribute attribute : aspect.getDefinition().getAttributes().values()) { result.add(new AttributeAndOwner(attribute, aspect.getAspectClass())); } } return result.build(); } /** * Resolves the given dep for the given attribute, including determining which configurations to * apply to it. */ void resolveDep(AttributeAndOwner attributeAndOwner, Label depLabel) throws InterruptedException, InconsistentAspectOrderException { Target toTarget = getTarget(rule, depLabel, rootCauses); if (toTarget == null) { return; // Skip this round: we still need to Skyframe-evaluate the dep's target. } ConfigurationTransition transition = TransitionResolver.evaluateTransition( ruleConfig, rule, attributeAndOwner.attribute, toTarget, attributeMap, trimmingTransitionFactory); outgoingEdges.put( attributeAndOwner.attribute, transition == NullTransition.INSTANCE ? Dependency.withNullConfiguration(depLabel) : Dependency.withTransitionAndAspects(depLabel, transition, requiredAspects(attributeAndOwner, toTarget))); } /** * Resolves the given dep for the given attribute using a pre-prepared configuration. * *

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. */ @AutoCodec @VisibleForSerialization static class FixedTransition implements PatchTransition { private final BuildOptions toOptions; FixedTransition(BuildOptions toOptions) { this.toOptions = toOptions; } @Override public BuildOptions patch(BuildOptions options) { return toOptions; } } private void visitTargetVisibility( TargetAndConfiguration node, NestedSetBuilder rootCauses, Collection outgoingEdges) throws InterruptedException { Target target = node.getTarget(); for (Label label : target.getVisibility().getDependencyLabels()) { Target visibilityTarget = getTarget(target, label, rootCauses); if (visibilityTarget == null) { continue; } if (!(visibilityTarget instanceof PackageGroup)) { // Note that this error could also be caught in // AbstractConfiguredTarget.convertVisibility(), but we have an // opportunity here to avoid dependency cycles that result from // the visibility attribute of a rule referring to a rule that // depends on it (instead of its package) invalidVisibilityReferenceHook(node, label); continue; } // Visibility always has null configuration outgoingEdges.add(Dependency.withNullConfiguration(label)); } } /** * Hook for the error case when an invalid visibility reference is found. * * @param node the node with the visibility attribute * @param label the invalid visibility reference */ protected abstract void invalidVisibilityReferenceHook(TargetAndConfiguration node, Label label); /** * Hook for the error case when an invalid package group reference is found. * * @param node the package group node with the includes attribute * @param label the invalid reference */ protected abstract void invalidPackageGroupReferenceHook(TargetAndConfiguration node, Label label); /** * Hook for the error case where a dependency is missing. * * @param from the target referencing the missing target * @param to the missing target * @param e the exception that was thrown, e.g., by {@link #getTarget} */ protected abstract void missingEdgeHook(Target from, Label to, NoSuchThingException e) throws InterruptedException; /** * Returns the target by the given label. * *

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 rootCauses) throws InterruptedException; /** * Returns the build configurations with the given fragments and {@link * BuildOptions.OptionsDiffForReconstruction} resulting from calling {@link * BuildOptions#diffForReconstruction} between the {@code defaultBuildOptions} and the provided * {@code buildOptions}. Results will be returned in the order the {@code buildOptions} are * provided. * *

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( FragmentClassSet fragments, Iterable buildOptions, BuildOptions defaultBuildOptions) 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; } } }