| // Copyright 2014 The Bazel Authors. All rights reserved. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| package com.google.devtools.build.lib.analysis; | |
| import com.google.common.annotations.VisibleForTesting; | |
| import com.google.common.base.Preconditions; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.collect.ImmutableMap; | |
| import com.google.common.collect.ImmutableSet; | |
| import com.google.common.collect.Iterables; | |
| import com.google.devtools.build.lib.analysis.AspectCollection.AspectCycleOnPathException; | |
| import com.google.devtools.build.lib.analysis.config.BuildConfiguration; | |
| import com.google.devtools.build.lib.analysis.config.BuildOptions; | |
| import com.google.devtools.build.lib.analysis.config.ConfigMatchingProvider; | |
| import com.google.devtools.build.lib.analysis.config.FragmentClassSet; | |
| import com.google.devtools.build.lib.analysis.config.HostTransition; | |
| import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException; | |
| import com.google.devtools.build.lib.analysis.config.TransitionResolver; | |
| import com.google.devtools.build.lib.analysis.config.transitions.ConfigurationTransition; | |
| import com.google.devtools.build.lib.analysis.config.transitions.NullTransition; | |
| import com.google.devtools.build.lib.analysis.config.transitions.PatchTransition; | |
| import com.google.devtools.build.lib.analysis.config.transitions.SplitTransition; | |
| import com.google.devtools.build.lib.causes.Cause; | |
| import com.google.devtools.build.lib.cmdline.Label; | |
| import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; | |
| import com.google.devtools.build.lib.events.Location; | |
| import com.google.devtools.build.lib.packages.Aspect; | |
| import com.google.devtools.build.lib.packages.AspectClass; | |
| import com.google.devtools.build.lib.packages.AspectDescriptor; | |
| import com.google.devtools.build.lib.packages.Attribute; | |
| import com.google.devtools.build.lib.packages.Attribute.LateBoundDefault; | |
| import com.google.devtools.build.lib.packages.AttributeMap; | |
| import com.google.devtools.build.lib.packages.BuildType; | |
| import com.google.devtools.build.lib.packages.ConfiguredAttributeMapper; | |
| import com.google.devtools.build.lib.packages.EnvironmentGroup; | |
| import com.google.devtools.build.lib.packages.InputFile; | |
| import com.google.devtools.build.lib.packages.NoSuchThingException; | |
| import com.google.devtools.build.lib.packages.OutputFile; | |
| import com.google.devtools.build.lib.packages.PackageGroup; | |
| import com.google.devtools.build.lib.packages.Rule; | |
| import com.google.devtools.build.lib.packages.RuleClass; | |
| import com.google.devtools.build.lib.packages.RuleTransitionFactory; | |
| import com.google.devtools.build.lib.packages.Target; | |
| import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec; | |
| import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization; | |
| import com.google.devtools.build.lib.syntax.EvalException; | |
| import com.google.devtools.build.lib.syntax.EvalUtils; | |
| import com.google.devtools.build.lib.util.OrderedSetMultimap; | |
| import java.util.Collection; | |
| import java.util.HashSet; | |
| import java.util.List; | |
| import java.util.Map; | |
| import java.util.Set; | |
| import java.util.stream.Stream; | |
| import javax.annotation.Nullable; | |
| /** | |
| * Resolver for dependencies between configured targets. | |
| * | |
| * <p>Includes logic to derive the right configurations depending on transition type. | |
| */ | |
| public abstract class DependencyResolver { | |
| /** | |
| * Returns ids for dependent nodes of a given node, sorted by attribute. Note that some | |
| * dependencies do not have a corresponding attribute here, and we use the null attribute to | |
| * represent those edges. | |
| * | |
| * <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 toolchainLabels required toolchain labels | |
| * @param defaultBuildOptions default build options provided to the server to use for creating | |
| * diffs during SkyKey construction | |
| * @param trimmingTransitionFactory the transition factory used to trim rules (note: this is a | |
| * temporary feature; see the corresponding methods in ConfiguredRuleClassProvider) | |
| * @return a mapping of each attribute in this rule or aspects to its dependent nodes | |
| */ | |
| public final OrderedSetMultimap<Attribute, Dependency> dependentNodeMap( | |
| TargetAndConfiguration node, | |
| BuildConfiguration hostConfig, | |
| @Nullable Aspect aspect, | |
| ImmutableMap<Label, ConfigMatchingProvider> configConditions, | |
| ImmutableSet<Label> toolchainLabels, | |
| BuildOptions defaultBuildOptions, | |
| @Nullable RuleTransitionFactory trimmingTransitionFactory) | |
| throws EvalException, InvalidConfigurationException, InterruptedException, | |
| InconsistentAspectOrderException { | |
| NestedSetBuilder<Cause> rootCauses = NestedSetBuilder.stableOrder(); | |
| OrderedSetMultimap<Attribute, Dependency> outgoingEdges = | |
| dependentNodeMap( | |
| node, | |
| hostConfig, | |
| aspect != null ? ImmutableList.of(aspect) : ImmutableList.<Aspect>of(), | |
| configConditions, | |
| toolchainLabels, | |
| rootCauses, | |
| defaultBuildOptions, | |
| trimmingTransitionFactory); | |
| 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 toolchainLabels required toolchain labels | |
| * @param trimmingTransitionFactory the transition factory used to trim rules (note: this is a | |
| * temporary feature; see the corresponding methods in ConfiguredRuleClassProvider) | |
| * @param rootCauses collector for dep labels that can't be (loading phase) loaded | |
| * @param defaultBuildOptions default build options provided by the server to use for creating | |
| * diffs during SkyKey construction | |
| * @return a mapping of each attribute in this rule or aspects to its dependent nodes | |
| */ | |
| public final OrderedSetMultimap<Attribute, Dependency> dependentNodeMap( | |
| TargetAndConfiguration node, | |
| BuildConfiguration hostConfig, | |
| Iterable<Aspect> aspects, | |
| ImmutableMap<Label, ConfigMatchingProvider> configConditions, | |
| ImmutableSet<Label> toolchainLabels, | |
| NestedSetBuilder<Cause> rootCauses, | |
| BuildOptions defaultBuildOptions, | |
| @Nullable RuleTransitionFactory trimmingTransitionFactory) | |
| 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, | |
| toolchainLabels, | |
| rootCauses, | |
| outgoingEdges, | |
| defaultBuildOptions, | |
| trimmingTransitionFactory); | |
| } 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, | |
| ImmutableSet<Label> toolchainLabels, | |
| NestedSetBuilder<Cause> rootCauses, | |
| OrderedSetMultimap<Attribute, Dependency> outgoingEdges, | |
| BuildOptions defaultBuildOptions, | |
| @Nullable RuleTransitionFactory trimmingTransitionFactory) | |
| throws EvalException, InvalidConfigurationException, InconsistentAspectOrderException, | |
| InterruptedException { | |
| Preconditions.checkArgument(node.getTarget() instanceof Rule, node); | |
| BuildConfiguration ruleConfig = Preconditions.checkNotNull(node.getConfiguration(), node); | |
| Rule rule = (Rule) node.getTarget(); | |
| ConfiguredAttributeMapper attributeMap = ConfiguredAttributeMapper.of(rule, configConditions); | |
| attributeMap.validateAttributes(); | |
| RuleResolver depResolver = | |
| new RuleResolver( | |
| rule, | |
| ruleConfig, | |
| aspects, | |
| attributeMap, | |
| rootCauses, | |
| outgoingEdges, | |
| trimmingTransitionFactory); | |
| visitTargetVisibility(node, rootCauses, outgoingEdges.get(null)); | |
| resolveEarlyBoundAttributes(depResolver); | |
| resolveLateBoundAttributes(depResolver, ruleConfig, hostConfig, defaultBuildOptions); | |
| Attribute toolchainsAttribute = | |
| attributeMap.getAttributeDefinition(PlatformSemantics.RESOLVED_TOOLCHAINS_ATTR); | |
| resolveToolchainDependencies(outgoingEdges.get(toolchainsAttribute), toolchainLabels); | |
| } | |
| /** | |
| * 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 { | |
| // Track size of filtered iterable by having an always-true clause that only gets checked after | |
| // all relevant clauses are checked. | |
| Collection<AttributeMap.DepEdge> depEdges = depResolver.attributeMap.visitLabels(); | |
| Iterable<AttributeMap.DepEdge> filteredEdges = | |
| Iterables.filter( | |
| depEdges, | |
| depEdge -> | |
| depEdge.getAttribute().getType() != BuildType.NODEP_LABEL | |
| && !depEdge.getAttribute().isImplicit() | |
| && !depEdge.getAttribute().isLateBound()); | |
| Map<Label, Target> result = | |
| getTargets( | |
| Iterables.transform(filteredEdges, AttributeMap.DepEdge::getLabel), | |
| depResolver.rule, | |
| depResolver.rootCauses, | |
| depEdges.size()); | |
| if (result == null) { | |
| return; | |
| } | |
| for (AttributeMap.DepEdge depEdge : filteredEdges) { | |
| Target target = result.get(depEdge.getLabel()); | |
| if (target != null) { | |
| depResolver.registerEdge(new AttributeAndOwner(depEdge.getAttribute()), target); | |
| } | |
| } | |
| } | |
| /** 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()); | |
| Set<Label> labelsToFetch = new HashSet<>(); | |
| Map<Label, Target> targetLookupResult = null; | |
| for (boolean collectingLabels : ImmutableList.of(Boolean.TRUE, Boolean.FALSE)) { | |
| 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); | |
| if (collectingLabels) { | |
| labelsToFetch.add(label); | |
| } else { | |
| Target target = targetLookupResult.get(label); | |
| if (target != null) { | |
| depResolver.registerEdge(attributeAndOwner, target); | |
| } | |
| } | |
| } | |
| } else if (attribute.getType() == BuildType.LABEL_LIST) { | |
| List<Label> labelList; | |
| if (mappedAttributes.contains(attribute.getName())) { | |
| labelList = attributeMap.get(attribute.getName(), BuildType.LABEL_LIST); | |
| } else { | |
| labelList = BuildType.LABEL_LIST.cast(attribute.getDefaultValue(rule)); | |
| } | |
| Stream<Label> labelStream = labelList.stream().map(ruleLabel::resolveRepositoryRelative); | |
| if (collectingLabels) { | |
| labelStream.forEach(labelsToFetch::add); | |
| } else { | |
| for (Label label : (Iterable<Label>) labelStream::iterator) { | |
| Target target = targetLookupResult.get(label); | |
| if (target != null) { | |
| depResolver.registerEdge(attributeAndOwner, target); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| if (collectingLabels) { | |
| targetLookupResult = | |
| getTargets(labelsToFetch, rule, depResolver.rootCauses, labelsToFetch.size()); | |
| if (targetLookupResult == null) { | |
| return; | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * 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>Apply configuration transitions over each value to get its dep configuration | |
| * <li>Return each value with its dep configuration | |
| * </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 | |
| * @param defaultBuildOptions default build options provided by the server to use for creating | |
| * diffs during SkyKey construction | |
| */ | |
| private void resolveLateBoundAttributes( | |
| RuleResolver depResolver, | |
| BuildConfiguration ruleConfig, | |
| BuildConfiguration hostConfig, | |
| BuildOptions defaultBuildOptions) | |
| throws EvalException, InvalidConfigurationException, InconsistentAspectOrderException, | |
| InterruptedException { | |
| ConfiguredAttributeMapper attributeMap = depResolver.attributeMap; | |
| Set<Label> labelsToFetch = new HashSet<>(); | |
| Map<Label, Target> targetLookupResult = null; | |
| for (boolean collectingLabels : ImmutableList.of(Boolean.TRUE, Boolean.FALSE)) { | |
| for (AttributeAndOwner attributeAndOwner : depResolver.attributes) { | |
| Attribute attribute = attributeAndOwner.attribute; | |
| if (!attribute.isLateBound() || !attribute.getCondition().apply(attributeMap)) { | |
| continue; | |
| } | |
| LateBoundDefault<?, ?> lateBoundDefault = attribute.getLateBoundDefault(); | |
| boolean hasSplitTransition = false; | |
| List<BuildOptions> splitOptions = null; | |
| if (attribute.hasSplitConfigurationTransition()) { | |
| splitOptions = attribute.getSplitTransition(attributeMap).split(ruleConfig.getOptions()); | |
| hasSplitTransition = !SplitTransition.equals(ruleConfig.getOptions(), splitOptions); | |
| } | |
| if (hasSplitTransition && !ruleConfig.isHostConfiguration()) { | |
| // Late-bound attribute with a split transition: | |
| // Since we want to get the same results as TransitionResolver.evaluateTransition (but | |
| // skip it since we've already applied the split), we want to make sure this logic | |
| // doesn't do anything differently. TransitionResolver.evaluateTransition has additional | |
| // logic for host configs. So when we're in the host configuration we fall back to the | |
| // non-split branch, which calls TransitionResolver.evaluateTransition, which returns its | |
| // "host mode" result without ever looking at the split. | |
| Iterable<BuildConfiguration> splitConfigs = | |
| getConfigurations(ruleConfig.fragmentClasses(), splitOptions, defaultBuildOptions); | |
| if (splitConfigs == null) { | |
| Preconditions.checkState(collectingLabels, attributeAndOwner); | |
| continue; // Need Skyframe deps. | |
| } | |
| for (BuildConfiguration splitConfig : splitConfigs) { | |
| for (Label dep : | |
| resolveLateBoundAttribute( | |
| depResolver.rule, | |
| attribute, | |
| lateBoundDefault.useHostConfiguration() ? hostConfig : splitConfig, | |
| attributeMap)) { | |
| if (collectingLabels) { | |
| labelsToFetch.add(dep); | |
| } else { | |
| // 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. | |
| Target target = targetLookupResult.get(dep); | |
| if (target != null) { | |
| depResolver.registerEdge(attributeAndOwner, target, splitConfig); | |
| } | |
| } | |
| } | |
| } | |
| } else { | |
| List<Label> deps = | |
| resolveLateBoundAttribute( | |
| depResolver.rule, | |
| attribute, | |
| lateBoundDefault.useHostConfiguration() ? hostConfig : ruleConfig, | |
| attributeMap); | |
| if (collectingLabels) { | |
| labelsToFetch.addAll(deps); | |
| } else { | |
| // Late-bound attribute without a split transition: | |
| for (Label dep : deps) { | |
| Target target = targetLookupResult.get(dep); | |
| if (target != null) { | |
| // Process this dep like a normal attribute. | |
| depResolver.registerEdge(attributeAndOwner, target); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| if (collectingLabels) { | |
| targetLookupResult = | |
| getTargets( | |
| labelsToFetch, depResolver.rule, depResolver.rootCauses, labelsToFetch.size()); | |
| if (targetLookupResult == null) { | |
| return; | |
| } | |
| } | |
| } | |
| } | |
| private void resolveToolchainDependencies( | |
| Set<Dependency> dependencies, ImmutableSet<Label> toolchainLabels) { | |
| for (Label label : toolchainLabels) { | |
| Dependency dependency = | |
| Dependency.withTransitionAndAspects( | |
| label, HostTransition.INSTANCE, AspectCollection.EMPTY); | |
| dependencies.add(dependency); | |
| } | |
| } | |
| /** | |
| * 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 List<Label> resolveLateBoundAttribute( | |
| Rule rule, Attribute attribute, BuildConfiguration config, AttributeMap attributeMap) | |
| throws EvalException { | |
| 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, Collection<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); | |
| Map<Label, Target> result = getTargets(labels, rule, depResolver.rootCauses, labels.size()); | |
| if (result == null) { | |
| return; | |
| } | |
| AttributeAndOwner attributeAndOwner = new AttributeAndOwner(attribute); | |
| for (Target target : result.values()) { | |
| depResolver.registerEdge(attributeAndOwner, target); | |
| } | |
| } | |
| /** | |
| * 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. | |
| * | |
| * <p>Returns null if Skyframe dependencies are missing. | |
| * | |
| * @throws IllegalArgumentException if the {@code node} does not refer to a {@link Rule} instance | |
| */ | |
| @Nullable | |
| public final Collection<Dependency> resolveRuleLabels( | |
| TargetAndConfiguration node, | |
| OrderedSetMultimap<Attribute, Label> depLabels, | |
| NestedSetBuilder<Cause> rootCauses, | |
| @Nullable RuleTransitionFactory trimmingTransitionFactory) | |
| 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, | |
| trimmingTransitionFactory); | |
| Map<Label, Target> result = getTargets(depLabels.values(), rule, rootCauses, depLabels.size()); | |
| if (result == null) { | |
| return null; | |
| } | |
| for (Map.Entry<Attribute, Collection<Label>> entry : depLabels.asMap().entrySet()) { | |
| AttributeAndOwner attributeAndOwner = new AttributeAndOwner(entry.getKey()); | |
| for (Label depLabel : entry.getValue()) { | |
| Target target = result.get(depLabel); | |
| if (target != null) { | |
| depResolver.registerEdge(attributeAndOwner, target); | |
| } | |
| } | |
| } | |
| return outgoingEdges.values(); | |
| } | |
| private void visitPackageGroup( | |
| TargetAndConfiguration node, | |
| PackageGroup packageGroup, | |
| NestedSetBuilder<Cause> rootCauses, | |
| Collection<Dependency> outgoingEdges) | |
| throws InterruptedException { | |
| List<Label> includes = packageGroup.getIncludes(); | |
| Map<Label, Target> targetMap = getTargets(includes, packageGroup, rootCauses, includes.size()); | |
| if (targetMap == null) { | |
| return; | |
| } | |
| Collection<Target> targets = targetMap.values(); | |
| for (Target target : targets) { | |
| 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, target.getLabel()); | |
| continue; | |
| } | |
| outgoingEdges.add(Dependency.withNullConfiguration(target.getLabel())); | |
| } | |
| } | |
| /** | |
| * Collects into {@code filteredAspectPath} aspects from {@code aspectPath} that propagate along | |
| * {@code attributeAndOwner} and apply to a given {@code target}. | |
| * | |
| * <p>The last aspect in {@code aspectPath} is (potentially) visible and recorded in {@code | |
| * visibleAspects}. | |
| */ | |
| private static void collectPropagatingAspects( | |
| Iterable<Aspect> aspectPath, | |
| AttributeAndOwner attributeAndOwner, | |
| Rule target, | |
| ImmutableList.Builder<Aspect> filteredAspectPath, | |
| ImmutableSet.Builder<AspectDescriptor> visibleAspects) { | |
| Aspect lastAspect = null; | |
| for (Aspect aspect : aspectPath) { | |
| if (aspect.getAspectClass().equals(attributeAndOwner.ownerAspect)) { | |
| // Do not propagate over the aspect's own attributes. | |
| continue; | |
| } | |
| lastAspect = aspect; | |
| if (aspect.getDefinition().propagateAlong(attributeAndOwner.attribute) | |
| && aspect | |
| .getDefinition() | |
| .getRequiredProviders() | |
| .isSatisfiedBy(target.getRuleClassObject().getAdvertisedProviders())) { | |
| filteredAspectPath.add(aspect); | |
| } else { | |
| lastAspect = null; | |
| } | |
| } | |
| if (lastAspect != null) { | |
| visibleAspects.add(lastAspect.getDescriptor()); | |
| } | |
| } | |
| /** | |
| * Collect all aspects that originate on {@code attribute} of {@code originalRule} | |
| * and are applicable to a {@code target} | |
| * | |
| * They are appended to {@code filteredAspectPath} and registered in {@code visibleAspects} set. | |
| */ | |
| private static void collectOriginatingAspects( | |
| Rule originalRule, Attribute attribute, Rule target, | |
| ImmutableList.Builder<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<Cause> rootCauses; | |
| private final OrderedSetMultimap<Attribute, Dependency> outgoingEdges; | |
| @Nullable private final RuleTransitionFactory trimmingTransitionFactory; | |
| 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<Cause> rootCauses, | |
| OrderedSetMultimap<Attribute, Dependency> outgoingEdges, | |
| @Nullable RuleTransitionFactory trimmingTransitionFactory) { | |
| this.rule = rule; | |
| this.ruleConfig = ruleConfig; | |
| this.aspects = aspects; | |
| this.attributeMap = attributeMap; | |
| this.rootCauses = rootCauses; | |
| this.outgoingEdges = outgoingEdges; | |
| this.trimmingTransitionFactory = trimmingTransitionFactory; | |
| this.attributes = | |
| getAttributes( | |
| rule, | |
| // These are attributes that the application of `aspects` "path" | |
| // to the rule will see. Application of path is really the | |
| // application of the last aspect in the path, so we only let it see | |
| // it's own attributes. | |
| aspects); | |
| } | |
| /** Returns the attributes that should be visited for this rule/aspect combination. */ | |
| private List<AttributeAndOwner> getAttributes(Rule rule, Iterable<Aspect> aspects) { | |
| ImmutableList.Builder<AttributeAndOwner> result = ImmutableList.builder(); | |
| List<Attribute> ruleDefs = rule.getRuleClassObject().getAttributes(); | |
| for (Attribute attribute : ruleDefs) { | |
| result.add(new AttributeAndOwner(attribute)); | |
| } | |
| for (Aspect aspect : aspects) { | |
| for (Attribute attribute : aspect.getDefinition().getAttributes().values()) { | |
| result.add(new AttributeAndOwner(attribute, aspect.getAspectClass())); | |
| } | |
| } | |
| return result.build(); | |
| } | |
| /** | |
| * Resolves the given dep for the given attribute, determining which configurations to apply to | |
| * it. | |
| */ | |
| void registerEdge(AttributeAndOwner attributeAndOwner, Target toTarget) | |
| throws InconsistentAspectOrderException { | |
| ConfigurationTransition transition = | |
| TransitionResolver.evaluateTransition( | |
| ruleConfig, | |
| rule, | |
| attributeAndOwner.attribute, | |
| toTarget, | |
| attributeMap, | |
| trimmingTransitionFactory); | |
| outgoingEdges.put( | |
| attributeAndOwner.attribute, | |
| transition == NullTransition.INSTANCE | |
| ? Dependency.withNullConfiguration(toTarget.getLabel()) | |
| : Dependency.withTransitionAndAspects( | |
| toTarget.getLabel(), transition, requiredAspects(attributeAndOwner, toTarget))); | |
| } | |
| /** | |
| * Resolves the given dep for the given attribute using a pre-prepared configuration. | |
| * | |
| * <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 | |
| * #registerEdge(AttributeAndOwner, Target)}. | |
| */ | |
| void registerEdge( | |
| AttributeAndOwner attributeAndOwner, Target toTarget, BuildConfiguration config) | |
| throws InconsistentAspectOrderException { | |
| outgoingEdges.put( | |
| attributeAndOwner.attribute, | |
| TransitionResolver.usesNullConfiguration(toTarget) | |
| ? Dependency.withNullConfiguration(toTarget.getLabel()) | |
| : Dependency.withTransitionAndAspects( | |
| toTarget.getLabel(), | |
| new FixedTransition(config.getOptions()), | |
| requiredAspects(attributeAndOwner, toTarget))); | |
| } | |
| private AspectCollection requiredAspects(AttributeAndOwner attributeAndOwner, | |
| final Target target) throws InconsistentAspectOrderException { | |
| if (!(target instanceof Rule)) { | |
| return AspectCollection.EMPTY; | |
| } | |
| ImmutableList.Builder<Aspect> filteredAspectPath = ImmutableList.builder(); | |
| ImmutableSet.Builder<AspectDescriptor> visibleAspects = ImmutableSet.builder(); | |
| if (attributeAndOwner.ownerAspect == null) { | |
| collectOriginatingAspects( | |
| rule, attributeAndOwner.attribute, (Rule) target, filteredAspectPath, visibleAspects); | |
| } | |
| collectPropagatingAspects( | |
| aspects, attributeAndOwner, (Rule) target, filteredAspectPath, visibleAspects); | |
| try { | |
| return AspectCollection.create(filteredAspectPath.build(), visibleAspects.build()); | |
| } catch (AspectCycleOnPathException e) { | |
| throw new InconsistentAspectOrderException(rule, attributeAndOwner.attribute, target, e); | |
| } | |
| } | |
| } | |
| /** A patch transition that returns a fixed set of options regardless of the input. */ | |
| @AutoCodec | |
| @VisibleForSerialization | |
| static class FixedTransition implements PatchTransition { | |
| private final BuildOptions toOptions; | |
| FixedTransition(BuildOptions toOptions) { | |
| this.toOptions = toOptions; | |
| } | |
| @Override | |
| public BuildOptions patch(BuildOptions options) { | |
| return toOptions; | |
| } | |
| } | |
| private void visitTargetVisibility( | |
| TargetAndConfiguration node, | |
| NestedSetBuilder<Cause> rootCauses, | |
| Collection<Dependency> outgoingEdges) | |
| throws InterruptedException { | |
| Target target = node.getTarget(); | |
| List<Label> dependencyLabels = target.getVisibility().getDependencyLabels(); | |
| Map<Label, Target> targetMap = | |
| getTargets(dependencyLabels, target, rootCauses, dependencyLabels.size()); | |
| if (targetMap == null) { | |
| return; | |
| } | |
| Collection<Target> targets = targetMap.values(); | |
| for (Target visibilityTarget : targets) { | |
| 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, visibilityTarget.getLabel()); | |
| continue; | |
| } | |
| // Visibility always has null configuration | |
| outgoingEdges.add(Dependency.withNullConfiguration(visibilityTarget.getLabel())); | |
| } | |
| } | |
| /** | |
| * 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 #getTargets} | |
| */ | |
| protected abstract void missingEdgeHook(Target from, Label to, NoSuchThingException e) | |
| throws InterruptedException; | |
| /** | |
| * Returns the targets for the given labels. | |
| * | |
| * <p>Returns null if any targets are not ready to be returned at this moment because of missing | |
| * Skyframe dependencies. If getTargets returns null once or more during a {@link | |
| * #dependentNodeMap} call, the results of that call will be incomplete. As is usual in these | |
| * situation, the caller must return control to Skyframe and wait for the SkyFunction to be | |
| * restarted, at which point the requested dependencies will be available. | |
| */ | |
| protected abstract Map<Label, Target> getTargets( | |
| Iterable<Label> labels, | |
| Target fromTarget, | |
| NestedSetBuilder<Cause> rootCauses, | |
| int labelsSizeHint) | |
| throws InterruptedException; | |
| /** | |
| * Returns the build configurations with the given fragments and {@link | |
| * BuildOptions.OptionsDiffForReconstruction} resulting from calling {@link | |
| * BuildOptions#diffForReconstruction} between the {@code defaultBuildOptions} and the provided | |
| * {@code buildOptions}. Results will be returned in the order the {@code buildOptions} are | |
| * provided. | |
| * | |
| * <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( | |
| FragmentClassSet fragments, | |
| Iterable<BuildOptions> buildOptions, | |
| BuildOptions defaultBuildOptions) | |
| throws InvalidConfigurationException, InterruptedException; | |
| /** | |
| * Signals an inconsistency on aspect path: an aspect occurs twice on the path and | |
| * the second occurrence sees a different set of aspects. | |
| * | |
| * {@see AspectCycleOnPathException} | |
| */ | |
| public class InconsistentAspectOrderException extends Exception { | |
| private final Location location; | |
| public InconsistentAspectOrderException(Rule originalRule, Attribute attribute, Target target, | |
| AspectCycleOnPathException e) { | |
| super(String.format("%s (when propagating from %s to %s via attribute %s)", | |
| e.getMessage(), | |
| originalRule.getLabel(), | |
| target.getLabel(), | |
| attribute.getName())); | |
| this.location = originalRule.getLocation(); | |
| } | |
| public Location getLocation() { | |
| return location; | |
| } | |
| } | |
| } |