// 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.base.Verify; 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.InvalidConfigurationException; import com.google.devtools.build.lib.analysis.config.PatchTransition; 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.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 com.google.devtools.build.lib.util.Preconditions; 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. * * <p>Includes logic to derive the right configurations depending on transition type. */ public abstract class DependencyResolver { protected 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. * * <p>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. * * <p>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}. * * <p>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 * @return a mapping of each attribute in this rule or aspects to its dependent nodes */ public final OrderedSetMultimap<Attribute, Dependency> dependentNodeMap( TargetAndConfiguration node, BuildConfiguration hostConfig, @Nullable Aspect aspect, ImmutableMap<Label, ConfigMatchingProvider> configConditions) throws EvalException, InvalidConfigurationException, InterruptedException, InconsistentAspectOrderException { NestedSetBuilder<Label> rootCauses = NestedSetBuilder.<Label>stableOrder(); OrderedSetMultimap<Attribute, Dependency> outgoingEdges = dependentNodeMap( node, hostConfig, aspect != null ? ImmutableList.of(aspect) : ImmutableList.<Aspect>of(), configConditions, rootCauses); if (!rootCauses.isEmpty()) { throw new IllegalStateException(rootCauses.build().iterator().next().toString()); } return outgoingEdges; } /** * 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. * * <p>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. * * <p>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}. * * <p>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 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<Attribute, Dependency> dependentNodeMap( TargetAndConfiguration node, BuildConfiguration hostConfig, Iterable<Aspect> aspects, ImmutableMap<Label, ConfigMatchingProvider> configConditions, NestedSetBuilder<Label> rootCauses) throws EvalException, InvalidConfigurationException, InterruptedException, InconsistentAspectOrderException { Target target = node.getTarget(); BuildConfiguration config = node.getConfiguration(); OrderedSetMultimap<Attribute, Dependency> outgoingEdges = OrderedSetMultimap.create(); if (target instanceof OutputFile) { Preconditions.checkNotNull(config); visitTargetVisibility(node, rootCauses, outgoingEdges.get(null)); Rule rule = ((OutputFile) target).getGeneratingRule(); outgoingEdges.put(null, Dependency.withConfiguration(rule.getLabel(), config)); } else if (target instanceof InputFile) { visitTargetVisibility(node, rootCauses, outgoingEdges.get(null)); } else if (target instanceof EnvironmentGroup) { visitTargetVisibility(node, rootCauses, outgoingEdges.get(null)); } else if (target instanceof Rule) { visitRule(node, hostConfig, aspects, configConditions, rootCauses, outgoingEdges); } else if (target instanceof PackageGroup) { visitPackageGroup(node, (PackageGroup) target, rootCauses, outgoingEdges.get(null)); } else { throw new IllegalStateException(target.getLabel().toString()); } return outgoingEdges; } private void visitRule( TargetAndConfiguration node, BuildConfiguration hostConfig, Iterable<Aspect> aspects, ImmutableMap<Label, ConfigMatchingProvider> configConditions, NestedSetBuilder<Label> rootCauses, OrderedSetMultimap<Attribute, Dependency> outgoingEdges) throws EvalException, InvalidConfigurationException, InconsistentAspectOrderException, InterruptedException{ Preconditions.checkArgument(node.getTarget() instanceof Rule); BuildConfiguration ruleConfig = Preconditions.checkNotNull(node.getConfiguration()); Rule rule = (Rule) node.getTarget(); ConfiguredAttributeMapper attributeMap = ConfiguredAttributeMapper.of(rule, configConditions); attributeMap.validateAttributes(); RuleResolver depResolver = new RuleResolver(rule, ruleConfig, aspects, attributeMap, rootCauses, outgoingEdges); visitTargetVisibility(node, rootCauses, outgoingEdges.get(null)); resolveEarlyBoundAttributes(depResolver); resolveLateBoundAttributes(depResolver, ruleConfig, hostConfig); } /** * Resolves the dependencies for all attributes in this rule except late-bound attributes * (which require special processing: see {@link #resolveLateBoundAttributes}). */ private void resolveEarlyBoundAttributes(RuleResolver depResolver) throws EvalException, InterruptedException, InconsistentAspectOrderException { Rule rule = depResolver.rule; resolveExplicitAttributes(depResolver); resolveImplicitAttributes(depResolver); // Add the rule's visibility labels (which may come from the rule or from package defaults). addExplicitDeps(depResolver, "visibility", rule.getVisibility().getDependencyLabels()); // Add package default constraints when the rule doesn't explicitly declare them. // // Note that this can have subtle implications for constraint semantics. For example: say that // package defaults declare compatibility with ':foo' and rule R declares compatibility with // ':bar'. Does that mean that R is compatible with [':foo', ':bar'] or just [':bar']? In other // words, did R's author intend to add additional compatibility to the package defaults or to // override them? More severely, what if package defaults "restrict" support to just [':baz']? // Should R's declaration signify [':baz'] + ['bar'], [ORIGINAL_DEFAULTS] + ['bar'], or // something else? // // Rather than try to answer these questions with possibly confusing logic, we take the // simple approach of assigning the rule's "restriction" attribute to the rule-declared value if // it exists, else the package defaults value (and likewise for "compatibility"). This may not // always provide what users want, but it makes it easy for them to understand how rule // declarations and package defaults intermix (and how to refactor them to get what they want). // // An alternative model would be to apply the "rule declaration" / "rule class defaults" // relationship, i.e. the rule class' "compatibility" and "restriction" declarations are merged // to generate a set of default environments, then the rule's declarations are independently // processed on top of that. This protects against obscure coupling behavior between // declarations from wildly different places (e.g. it offers clear answers to the examples posed // above). But within the scope of a single package it seems better to keep the model simple and // make the user responsible for resolving ambiguities. if (!rule.isAttributeValueExplicitlySpecified(RuleClass.COMPATIBLE_ENVIRONMENT_ATTR)) { addExplicitDeps(depResolver, RuleClass.COMPATIBLE_ENVIRONMENT_ATTR, rule.getPackage().getDefaultCompatibleWith()); } if (!rule.isAttributeValueExplicitlySpecified(RuleClass.RESTRICTED_ENVIRONMENT_ATTR)) { addExplicitDeps(depResolver, RuleClass.RESTRICTED_ENVIRONMENT_ATTR, rule.getPackage().getDefaultRestrictedTo()); } } private void resolveExplicitAttributes(final RuleResolver depResolver) throws InterruptedException, InconsistentAspectOrderException { // Record error that might happen during label visitation. final InconsistentAspectOrderException[] exception = new InconsistentAspectOrderException[1]; depResolver.attributeMap.visitLabels( new AttributeMap.AcceptsLabelAttribute() { @Override public void acceptLabelAttribute(Label label, Attribute attribute) throws InterruptedException { if (attribute.getType() == BuildType.NODEP_LABEL || attribute.isImplicit() || attribute.isLateBound()) { return; } try { depResolver.resolveDep(new AttributeAndOwner(attribute), label); } catch (InconsistentAspectOrderException e) { if (exception[0] == null) { exception[0] = e; } } } }); if (exception[0] != null) { throw exception[0]; } } /** Resolves the dependencies for all implicit attributes in this rule. */ private void resolveImplicitAttributes(RuleResolver depResolver) throws InterruptedException, InconsistentAspectOrderException { // Since the attributes that come from aspects do not appear in attributeMap, we have to get // their values from somewhere else. This incidentally means that aspects attributes are not // configurable. It would be nice if that wasn't the case, but we'd have to revamp how // attribute mapping works, which is a large chunk of work. Rule rule = depResolver.rule; Label ruleLabel = rule.getLabel(); ConfiguredAttributeMapper attributeMap = depResolver.attributeMap; ImmutableSet<String> mappedAttributes = ImmutableSet.copyOf(attributeMap.getAttributeNames()); for (AttributeAndOwner attributeAndOwner : depResolver.attributes) { Attribute attribute = attributeAndOwner.attribute; if (!attribute.isImplicit() || !attribute.getCondition().apply(attributeMap)) { continue; } if (attribute.getType() == BuildType.LABEL) { Label label = mappedAttributes.contains(attribute.getName()) ? attributeMap.get(attribute.getName(), BuildType.LABEL) : BuildType.LABEL.cast(attribute.getDefaultValue(rule)); if (label != null) { label = ruleLabel.resolveRepositoryRelative(label); depResolver.resolveDep(attributeAndOwner, label); } } else if (attribute.getType() == BuildType.LABEL_LIST) { List<Label> labelList; if (mappedAttributes.contains(attribute.getName())) { labelList = new ArrayList<>(); for (Label label : attributeMap.get(attribute.getName(), BuildType.LABEL_LIST)) { labelList.add(label); } } else { labelList = BuildType.LABEL_LIST.cast(attribute.getDefaultValue(rule)); } for (Label label : labelList) { depResolver.resolveDep(attributeAndOwner, ruleLabel.resolveRepositoryRelative(label)); } } } } /** * Resolves the dependencies for all late-bound attributes in this rule. * * <p>Late-bound attributes need special handling because they require configuration * transitions to determine their values. * * <p>In other words, the normal process of dependency resolution is: * <ol> * <li>Find every label value in the rule's attributes</li> * <li>Apply configuration transitions over each value to get its dep configuration * <li>Return each value with its dep configuration</li> * </ol> * * 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. * * <p>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; } @SuppressWarnings("unchecked") LateBoundDefault<BuildConfiguration> lateBoundDefault = (LateBoundDefault<BuildConfiguration>) attribute.getLateBoundDefault(); Collection<BuildOptions> splitOptions = getSplitOptions(depResolver.rule, attribute, ruleConfig); if (!splitOptions.isEmpty() && !ruleConfig.isHostConfiguration()) { // Late-bound attribute with a split transition: // Since we want to get the same results as BuildConfiguration.evaluateTransition (but // skip it since we've already applied the split), we want to make sure this logic // doesn't do anything differently. evaluateTransition has additional logic // for host configs and attributes with configurators. To keep the fork as simple as // possible, we don't support the configurator case. And when we're in the host // configuration, we fall back to the non-split branch, which calls // BuildConfiguration.evaluateTransition, which returns its "host mode" result without // ever looking at the split. Verify.verify(attribute.getConfigurator() == null); Iterable<BuildConfiguration> splitConfigs; if (!ruleConfig.useDynamicConfigurations()) { splitConfigs = ruleConfig .getSplitConfigurations(attribute.getSplitTransition(depResolver.rule)); } else { 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); } } } } /** * 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. * * <p>Even though the attribute may have a split, splits don't have to apply in every * configuration (see {@link Attribute.SplitTransition#split}). */ private static Collection<BuildOptions> getSplitOptions(Rule rule, Attribute attribute, BuildConfiguration ruleConfig) { if (!attribute.hasSplitConfigurationTransition()) { return ImmutableList.<BuildOptions>of(); } @SuppressWarnings("unchecked") // Attribute.java doesn't have the BuildOptions symbol. Attribute.SplitTransition<BuildOptions> transition = (Attribute.SplitTransition<BuildOptions>) attribute.getSplitTransition(rule); 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<Label> resolveLateBoundAttribute( Rule rule, Attribute attribute, BuildConfiguration config, AttributeMap attributeMap) throws EvalException, InterruptedException { Preconditions.checkArgument(attribute.isLateBound()); @SuppressWarnings("unchecked") LateBoundDefault<BuildConfiguration> lateBoundDefault = (LateBoundDefault<BuildConfiguration>) attribute.getLateBoundDefault(); // TODO(bazel-team): This might be too expensive - can we cache this somehow? if (!lateBoundDefault.getRequiredConfigurationFragments().isEmpty()) { if (!config.hasAllFragments(lateBoundDefault.getRequiredConfigurationFragments())) { return ImmutableList.<Label>of(); } } // TODO(bazel-team): We should check if the implementation tries to access an undeclared // fragment. Object actualValue = lateBoundDefault.resolve(rule, attributeMap, config); if (EvalUtils.isNullOrNone(actualValue)) { return ImmutableList.<Label>of(); } try { ImmutableList.Builder<Label> deps = ImmutableList.builder(); if (attribute.getType() == BuildType.LABEL) { deps.add(rule.getLabel().resolveRepositoryRelative(BuildType.LABEL.cast(actualValue))); } else if (attribute.getType() == BuildType.LABEL_LIST) { for (Label label : BuildType.LABEL_LIST.cast(actualValue)) { deps.add(rule.getLabel().resolveRepositoryRelative(label)); } } else { throw new IllegalStateException( String.format( "Late bound attribute '%s' is not a label or a label list", attribute.getName())); } return deps.build(); } catch (ClassCastException e) { // From either of the cast calls above. throw new EvalException( rule.getLocation(), String.format( "When computing the default value of %s, expected '%s', got '%s'", attribute.getName(), attribute.getType(), EvalUtils.getDataTypeName(actualValue, true))); } } /** * Adds new dependencies to the given rule under the given attribute name * * @param depResolver the resolver for this rule's deps * @param attrName the name of the attribute to add dependency labels to * @param labels the dependencies to add */ private void addExplicitDeps(RuleResolver depResolver, String attrName, Iterable<Label> labels) throws InterruptedException, InconsistentAspectOrderException { Rule rule = depResolver.rule; if (!rule.isAttrDefined(attrName, BuildType.LABEL_LIST) && !rule.isAttrDefined(attrName, BuildType.NODEP_LABEL_LIST)) { return; } Attribute attribute = rule.getRuleClassObject().getAttributeByName(attrName); for (Label label : labels) { depResolver.resolveDep(new AttributeAndOwner(attribute), label); } } /** * Converts the given multimap of attributes to labels into a multi map of attributes to {@link * Dependency} objects using the proper configuration transition for each attribute. * * @throws IllegalArgumentException if the {@code node} does not refer to a {@link Rule} instance */ public final Collection<Dependency> resolveRuleLabels( TargetAndConfiguration node, OrderedSetMultimap<Attribute, Label> depLabels, NestedSetBuilder<Label> rootCauses) throws InterruptedException, InconsistentAspectOrderException { Preconditions.checkArgument(node.getTarget() instanceof Rule); Rule rule = (Rule) node.getTarget(); OrderedSetMultimap<Attribute, Dependency> outgoingEdges = OrderedSetMultimap.create(); RuleResolver depResolver = new RuleResolver( rule, node.getConfiguration(), ImmutableList.<Aspect>of(), /*attributeMap=*/null, rootCauses, outgoingEdges); Map<Attribute, Collection<Label>> m = depLabels.asMap(); for (Map.Entry<Attribute, Collection<Label>> entry : depLabels.asMap().entrySet()) { for (Label depLabel : entry.getValue()) { depResolver.resolveDep(new AttributeAndOwner(entry.getKey()), depLabel); } } return outgoingEdges.values(); } private void visitPackageGroup( TargetAndConfiguration node, PackageGroup packageGroup, NestedSetBuilder<Label> rootCauses, Collection<Dependency> outgoingEdges) throws InterruptedException { for (Label label : packageGroup.getIncludes()) { Target target = getTarget(packageGroup, label, rootCauses); if (target == null) { continue; } if (!(target instanceof PackageGroup)) { // Note that this error could also be caught in PackageGroupConfiguredTarget, but since // these have the null configuration, visiting the corresponding target would trigger an // analysis of a rule with a null configuration, which doesn't work. invalidPackageGroupReferenceHook(node, label); continue; } outgoingEdges.add(Dependency.withNullConfiguration(label)); } } private AspectCollection requiredAspects( Iterable<Aspect> aspectPath, AttributeAndOwner attributeAndOwner, final Target target, Rule originalRule) throws InconsistentAspectOrderException { if (!(target instanceof Rule)) { return AspectCollection.EMPTY; } if (attributeAndOwner.ownerAspect != null) { // Do not propagate aspects along aspect attributes. return AspectCollection.EMPTY; } ImmutableList.Builder<Aspect> filteredAspectPath = ImmutableList.builder(); ImmutableSet.Builder<AspectDescriptor> visibleAspects = ImmutableSet.builder(); Attribute attribute = attributeAndOwner.attribute; collectOriginatingAspects(originalRule, attribute, (Rule) target, filteredAspectPath, visibleAspects); collectPropagatingAspects(aspectPath, attribute, (Rule) target, filteredAspectPath, visibleAspects); try { return AspectCollection.create(filteredAspectPath.build(), visibleAspects.build()); } catch (AspectCycleOnPathException e) { throw new InconsistentAspectOrderException(originalRule, attribute, target, e); } } /** * Collects into {@code filteredAspectPath} * aspects from {@code aspectPath} that propagate along {@code attribute} * and apply to a given {@code target}. * * The last aspect in {@code aspectPath} is (potentially) visible and recorded * in {@code visibleAspects}. */ private static void collectPropagatingAspects(Iterable<Aspect> aspectPath, Attribute attribute, Rule target, ImmutableList.Builder<Aspect> filteredAspectPath, ImmutableSet.Builder<AspectDescriptor> visibleAspects) { Aspect lastAspect = null; for (Aspect aspect : aspectPath) { lastAspect = aspect; if (aspect.getDefinition().propagateAlong(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<Aspect> filteredAspectPath, ImmutableSet.Builder<AspectDescriptor> visibleAspects) { ImmutableList<Aspect> 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). * * <p>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 <Attribute, Label> pairs for a rule into the * <Attribute, Dependency> pairs DependencyResolver ultimately returns. * * <p>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<Aspect> aspects; private final ConfiguredAttributeMapper attributeMap; private final NestedSetBuilder<Label> rootCauses; private final OrderedSetMultimap<Attribute, Dependency> outgoingEdges; private final List<AttributeAndOwner> 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<Aspect> aspects, ConfiguredAttributeMapper attributeMap, NestedSetBuilder<Label> rootCauses, OrderedSetMultimap<Attribute, Dependency> outgoingEdges) { this.rule = rule; this.ruleConfig = ruleConfig; this.aspects = aspects; this.attributeMap = attributeMap; this.rootCauses = rootCauses; this.outgoingEdges = outgoingEdges; 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. Iterables.getLast(aspects, null)); } /** Returns the attributes that should be visited for this rule/aspect combination. */ private List<AttributeAndOwner> getAttributes(Rule rule, @Nullable Aspect aspect) { ImmutableList.Builder<AttributeAndOwner> result = ImmutableList.builder(); List<Attribute> ruleDefs = rule.getRuleClassObject().getAttributes(); for (Attribute attribute : ruleDefs) { result.add(new AttributeAndOwner(attribute)); } if (aspect != null) { 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. } BuildConfiguration.TransitionApplier resolver = ruleConfig.getTransitionApplier(); ruleConfig.evaluateTransition(rule, attributeAndOwner.attribute, toTarget, resolver); // An <Attribute, Label> pair can resolve to multiple deps because of split transitions. for (Dependency dependency : resolver.getDependencies(depLabel, requiredAspects(aspects, attributeAndOwner, toTarget, rule))) { outgoingEdges.put(attributeAndOwner.attribute, dependency); } } /** * Resolves the given dep for the given attribute using a pre-prepared configuration. * * <p>Use this method with care: it skips Bazel's standard config transition semantics ({@link * BuildConfiguration#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. } BuildConfiguration.TransitionApplier transitionApplier = config.getTransitionApplier(); boolean applyNullTransition = false; if (BuildConfiguration.usesNullConfiguration(toTarget)) { transitionApplier.applyTransition(Attribute.ConfigurationTransition.NULL); applyNullTransition = true; } AspectCollection aspects = requiredAspects(this.aspects, attributeAndOwner, toTarget, rule); Dependency dep; if (config.useDynamicConfigurations() && !applyNullTransition) { // Pass a transition rather than directly feeding the configuration so deps get trimmed. dep = Dependency.withTransitionAndAspects( depLabel, new FixedTransition(config.getOptions()), aspects); } else { dep = Iterables.getOnlyElement(transitionApplier.getDependencies(depLabel, aspects)); } outgoingEdges.put(attributeAndOwner.attribute, dep); } } /** * 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; } @Override public boolean defaultsToSelf() { return false; } } private void visitTargetVisibility( TargetAndConfiguration node, NestedSetBuilder<Label> rootCauses, Collection<Dependency> 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. * * <p>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<Label> rootCauses) throws InterruptedException; /** * Returns the build configurations with the given options and fragments, in the same order as the * input options. * * <p>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<BuildConfiguration> getConfigurations( Set<Class<? extends BuildConfiguration.Fragment>> fragments, Iterable<BuildOptions> 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; } } }