|  | // 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.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.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. | 
|  | * | 
|  | * <p>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. | 
|  | * | 
|  | * <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 | 
|  | * @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<Attribute, Dependency> dependentNodeMap( | 
|  | TargetAndConfiguration node, | 
|  | BuildConfiguration hostConfig, | 
|  | @Nullable Aspect aspect, | 
|  | ImmutableMap<Label, ConfigMatchingProvider> configConditions, | 
|  | ToolchainContext toolchainContext) | 
|  | 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, | 
|  | toolchainContext, | 
|  | 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. | 
|  | * | 
|  | * <p>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 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<Attribute, Dependency> dependentNodeMap( | 
|  | TargetAndConfiguration node, | 
|  | BuildConfiguration hostConfig, | 
|  | Iterable<Aspect> aspects, | 
|  | ImmutableMap<Label, ConfigMatchingProvider> configConditions, | 
|  | @Nullable ToolchainContext toolchainContext, | 
|  | 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, toolchainContext, 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, | 
|  | @Nullable ToolchainContext toolchainContext, | 
|  | 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); | 
|  |  | 
|  | if (toolchainContext != null) { | 
|  | Attribute toolchainsAttribute = | 
|  | attributeMap.getAttributeDefinition(PlatformSemantics.TOOLCHAINS_ATTR); | 
|  | resolveToolchainDependencies(outgoingEdges.get(toolchainsAttribute), toolchainContext); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * 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; | 
|  | } | 
|  |  | 
|  | LateBoundDefault<?, ?> lateBoundDefault = attribute.getLateBoundDefault(); | 
|  |  | 
|  | Collection<BuildOptions> 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<BuildConfiguration> 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<Dependency> 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. | 
|  | * | 
|  | * <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(ConfiguredAttributeMapper attributeMap, | 
|  | 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(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<Label> resolveLateBoundAttribute( | 
|  | Rule rule, | 
|  | Attribute attribute, | 
|  | BuildConfiguration config, | 
|  | AttributeMap attributeMap) | 
|  | throws EvalException, InterruptedException { | 
|  | Preconditions.checkArgument(attribute.isLateBound()); | 
|  |  | 
|  | Object actualValue = | 
|  | resolveLateBoundDefault(attribute.getLateBoundDefault(), 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))); | 
|  | } | 
|  | } | 
|  |  | 
|  | @VisibleForTesting(/* used to test LateBoundDefaults' default values */ ) | 
|  | public static <FragmentT, ValueT> ValueT resolveLateBoundDefault( | 
|  | LateBoundDefault<FragmentT, ValueT> lateBoundDefault, | 
|  | Rule rule, | 
|  | AttributeMap attributeMap, | 
|  | BuildConfiguration config) throws EvalException { | 
|  | Class<FragmentT> fragmentClass = lateBoundDefault.getFragmentClass(); | 
|  | // TODO(b/65746853): remove this when nothing uses it anymore | 
|  | if (BuildConfiguration.class.equals(fragmentClass)) { | 
|  | return lateBoundDefault.resolve(rule, attributeMap, fragmentClass.cast(config)); | 
|  | } | 
|  | if (Void.class.equals(fragmentClass)) { | 
|  | return lateBoundDefault.resolve(rule, attributeMap, null); | 
|  | } | 
|  | FragmentT fragment = | 
|  | fragmentClass.cast( | 
|  | config.getFragment((Class<? extends BuildConfiguration.Fragment>) fragmentClass)); | 
|  | if (fragment == null) { | 
|  | return null; | 
|  | } | 
|  | return lateBoundDefault.resolve(rule, attributeMap, fragment); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * 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)); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * 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. | 
|  | } | 
|  | Attribute.Transition transition = transitionResolver.evaluateTransition( | 
|  | ruleConfig, rule, attributeAndOwner.attribute, toTarget, attributeMap); | 
|  | outgoingEdges.put( | 
|  | attributeAndOwner.attribute, | 
|  | transition == Attribute.ConfigurationTransition.NULL | 
|  | ? Dependency.withNullConfiguration(depLabel) | 
|  | : Dependency.withTransitionAndAspects(depLabel, transition, | 
|  | requiredAspects(attributeAndOwner, toTarget))); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * 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 | 
|  | * 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; | 
|  | } | 
|  |  | 
|  | 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(rule, attribute, (Rule) target, | 
|  | filteredAspectPath, visibleAspects); | 
|  |  | 
|  | collectPropagatingAspects(aspects, | 
|  | attribute, | 
|  | (Rule) target, filteredAspectPath, visibleAspects); | 
|  | try { | 
|  | return AspectCollection.create(filteredAspectPath.build(), visibleAspects.build()); | 
|  | } catch (AspectCycleOnPathException e) { | 
|  | throw new InconsistentAspectOrderException(rule, 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<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; | 
|  | } | 
|  | } | 
|  | } |