| // 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.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.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, |
| /*toolchainContext=*/ null, |
| 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; |
| } |
| |
| @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); |
| } |
| } |
| } |
| } |
| |
| 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(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; |
| } |
| } |
| } |