// 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.ArrayListMultimap; 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.common.collect.ListMultimap; import com.google.common.collect.Sets; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; import com.google.devtools.build.lib.analysis.config.BuildOptions; import com.google.devtools.build.lib.analysis.config.ConfigMatchingProvider; import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException; import com.google.devtools.build.lib.analysis.config.PatchTransition; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; import com.google.devtools.build.lib.packages.Aspect; import com.google.devtools.build.lib.packages.AspectClass; 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.NativeAspectClass; 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.Preconditions; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; /** * Resolver for dependencies between configured targets. * *

Includes logic to derive the right configurations depending on transition type. */ public abstract class DependencyResolver { 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. Visibility attributes are only visited if {@code visitVisibility} is * {@code true}. * *

If {@code aspect} is null, returns the dependent nodes of the configured * target node representing the given target and configuration, otherwise that of the aspect * node accompanying the aforementioned configured target node for the specified aspect. * *

The values are not simply labels because this also implements the first step of applying * configuration transitions, namely, split transitions. This needs to be done before the labels * are resolved because late bound attributes depend on the configuration. A good example for this * is @{code :cc_toolchain}. * *

The long-term goal is that most configuration transitions be applied here. However, in order * to do that, we first have to eliminate transitions that depend on the rule class of the * dependency. * * @param node the target/configuration being evaluated * @param hostConfig the configuration this target would use if it was evaluated as a host * tool. This is needed to support {@link LateBoundDefault#useHostConfiguration()}. * @param aspect the aspect applied to this target (if any) * @param configConditions resolver for config_setting labels * * @return a mapping of each attribute in this rule or aspect to its dependent nodes */ public final ListMultimap dependentNodeMap( TargetAndConfiguration node, BuildConfiguration hostConfig, @Nullable Aspect aspect, ImmutableMap configConditions) throws EvalException, InvalidConfigurationException, InterruptedException { NestedSetBuilder

If {@code aspect} is null, returns the dependent nodes of the configured * target node representing the given target and configuration, otherwise that of the aspect * node accompanying the aforementioned configured target node for the specified aspect. * *

The values are not simply labels because this also implements the first step of applying * configuration transitions, namely, split transitions. This needs to be done before the labels * are resolved because late bound attributes depend on the configuration. A good example for this * is @{code :cc_toolchain}. * *

The long-term goal is that most configuration transitions be applied here. However, in order * to do that, we first have to eliminate transitions that depend on the rule class of the * dependency. * * @param node the target/configuration being evaluated * @param hostConfig the configuration this target would use if it was evaluated as a host * tool. This is needed to support {@link LateBoundDefault#useHostConfiguration()}. * @param aspect the aspect applied to this target (if any) * @param configConditions resolver for config_setting labels * @param rootCauses collector for dep labels that can't be (loading phase) loaded * * @return a mapping of each attribute in this rule or aspect to its dependent nodes */ public final ListMultimap dependentNodeMap( TargetAndConfiguration node, BuildConfiguration hostConfig, @Nullable Aspect aspect, ImmutableMap configConditions, NestedSetBuilder

Late-bound attributes need special handling because they require configuration * transitions to determine their values. * *

In other words, the normal process of dependency resolution is: *

    *
  1. Find every label value in the rule's attributes
  2. *
  3. Apply configuration transitions over each value to get its dep configuration *
  4. Return each value with its dep configuration
  5. *
* * This doesn't work for late-bound attributes because you can't get their values without * knowing the configuration first. And that configuration may not be the owning rule's * configuration. Specifically, {@link LateBoundDefault#useHostConfiguration()} switches to the * host config and late-bound split attributes branch into multiple split configs. * *

This method implements that logic and makes sure the normal configuration * transition logic mixes with it cleanly. * * @param depResolver the resolver for this rule's deps * @param ruleConfig the rule's configuration * @param hostConfig the equivalent host configuration */ private void resolveLateBoundAttributes( RuleResolver depResolver, BuildConfiguration ruleConfig, BuildConfiguration hostConfig) throws EvalException, InvalidConfigurationException, InterruptedException { ConfiguredAttributeMapper attributeMap = depResolver.attributeMap; for (Attribute attribute : depResolver.attributes) { if (!attribute.isLateBound() || !attribute.getCondition().apply(attributeMap)) { continue; } @SuppressWarnings("unchecked") LateBoundDefault lateBoundDefault = (LateBoundDefault) attribute.getLateBoundDefault(); Collection splitOptions = getSplitOptions(depResolver.rule, attribute, ruleConfig); if (!splitOptions.isEmpty()) { // 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. So we check here that neither of // of those apply, in the name of keeping the fork as simple as possible. Verify.verify(attribute.getConfigurator() == null); Verify.verify(!lateBoundDefault.useHostConfiguration()); Iterable 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, 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(attribute, 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(attribute, dep); } } } } /** * Returns true if the rule's attribute triggers a split in this configuration. * *

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 getSplitOptions(Rule rule, Attribute attribute, BuildConfiguration ruleConfig) { if (!attribute.hasSplitConfigurationTransition()) { return ImmutableList.of(); } @SuppressWarnings("unchecked") // Attribute.java doesn't have the BuildOptions symbol. Attribute.SplitTransition transition = (Attribute.SplitTransition) 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

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 Aspect aspect; private final ConfiguredAttributeMapper attributeMap; private final NestedSetBuilder

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(Attribute, Label)}. */ void resolveDep(Attribute attribute, Label depLabel, BuildConfiguration config) { 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; } ImmutableSet aspects = requiredAspects(aspect, attribute, 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(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

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

Returns null if any configurations aren't ready to be returned at this moment. If * getConfigurations returns null once or more during a {@link #dependentNodeMap} call, the * results of that call will be incomplete. For use within Skyframe, where several iterations may * be needed to discover all dependencies. */ @Nullable protected abstract List getConfigurations( Set> fragments, Iterable buildOptions) throws InvalidConfigurationException; }