// 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.common.collect.Iterables; 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.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Stream; 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; Set

Returns null if Skyframe dependencies are missing. * * @throws IllegalArgumentException if the {@code node} does not refer to a {@link Rule} instance */ @Nullable public final Collection resolveRuleLabels( TargetAndConfiguration node, OrderedSetMultimap depLabels, NestedSetBuilder rootCauses, @Nullable RuleTransitionFactory trimmingTransitionFactory) throws InterruptedException, InconsistentAspectOrderException { Preconditions.checkArgument(node.getTarget() instanceof Rule); Rule rule = (Rule) node.getTarget(); OrderedSetMultimap outgoingEdges = OrderedSetMultimap.create(); RuleResolver depResolver = new RuleResolver( rule, node.getConfiguration(), ImmutableList.of(), /*attributeMap=*/ null, rootCauses, outgoingEdges, trimmingTransitionFactory); Map result = getTargets(depLabels.values(), rule, rootCauses, depLabels.size()); if (result == null) { return null; } for (Map.Entry> entry : depLabels.asMap().entrySet()) { AttributeAndOwner attributeAndOwner = new AttributeAndOwner(entry.getKey()); for (Label depLabel : entry.getValue()) { Target target = result.get(depLabel); if (target != null) { depResolver.registerEdge(attributeAndOwner, target); } } } return outgoingEdges.values(); } private void visitPackageGroup( TargetAndConfiguration node, PackageGroup packageGroup, NestedSetBuilder rootCauses, Collection outgoingEdges) throws InterruptedException { List

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, determining which configurations to apply to * it. */ void registerEdge(AttributeAndOwner attributeAndOwner, Target toTarget) throws InconsistentAspectOrderException { ConfigurationTransition transition = TransitionResolver.evaluateTransition( ruleConfig, rule, attributeAndOwner.attribute, toTarget, attributeMap, trimmingTransitionFactory); outgoingEdges.put( attributeAndOwner.attribute, transition == NullTransition.INSTANCE ? Dependency.withNullConfiguration(toTarget.getLabel()) : Dependency.withTransitionAndAspects( toTarget.getLabel(), 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 * #registerEdge(AttributeAndOwner, Target)}. */ void registerEdge( AttributeAndOwner attributeAndOwner, Target toTarget, BuildConfiguration config) throws InconsistentAspectOrderException { outgoingEdges.put( attributeAndOwner.attribute, TransitionResolver.usesNullConfiguration(toTarget) ? Dependency.withNullConfiguration(toTarget.getLabel()) : Dependency.withTransitionAndAspects( toTarget.getLabel(), 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(); List

Returns null if any targets are not ready to be returned at this moment because of missing * Skyframe dependencies. If getTargets returns null once or more during a {@link * #dependentNodeMap} call, the results of that call will be incomplete. As is usual in these * situation, the caller must return control to Skyframe and wait for the SkyFunction to be * restarted, at which point the requested dependencies will be available. */ protected abstract Map getTargets( Iterable

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; } } }