// Copyright 2017 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.config; import com.google.common.base.Preconditions; import com.google.devtools.build.lib.analysis.TargetAndConfiguration; import com.google.devtools.build.lib.analysis.config.transitions.ComposingTransition; import com.google.devtools.build.lib.analysis.config.transitions.ConfigurationTransition; import com.google.devtools.build.lib.analysis.config.transitions.NoTransition; 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.packages.Attribute; import com.google.devtools.build.lib.packages.ConfiguredAttributeMapper; import com.google.devtools.build.lib.packages.InputFile; import com.google.devtools.build.lib.packages.PackageGroup; import com.google.devtools.build.lib.packages.Rule; import com.google.devtools.build.lib.packages.RuleTransitionFactory; import com.google.devtools.build.lib.packages.Target; import javax.annotation.Nullable; /** * Tool for evaluating which {@link ConfigurationTransition}(s) should be applied to given targets. * *

For the work of turning these transitions into actual configurations, see {@link * ConfigurationResolver}. * *

This is the "generic engine" for configuration selection. It doesn't know anything about * specific rules or their requirements. Rule writers decide those with appropriately placed {@link * PatchTransition} declarations. This class then processes those declarations to determine final * transitions. */ public final class TransitionResolver { /** * Given a parent rule and configuration depending on a child through an attribute, determines * the configuration the child should take. * * @param fromConfig the parent rule's configuration * @param fromRule the parent rule * @param attribute the attribute creating the dependency (e.g. "srcs") * @param toTarget the child target (which may or may not be a rule) * @param attributeMap the attributes of the rule * @param trimmingTransitionFactory the transition factory used to trim rules (note: this is a * temporary feature; see the corresponding methods in ConfiguredRuleClassProvider) * * @return the child's configuration, expressed as a diff from the parent's configuration. This * is either a {@link PatchTransition} or {@link SplitTransition}. */ public static ConfigurationTransition evaluateTransition( BuildConfiguration fromConfig, final Rule fromRule, final Attribute attribute, final Target toTarget, ConfiguredAttributeMapper attributeMap, @Nullable RuleTransitionFactory trimmingTransitionFactory) { // I. Input files and package groups have no configurations. We don't want to duplicate them. if (usesNullConfiguration(toTarget)) { return NullTransition.INSTANCE; } // II. Host configurations never switch to another. All prerequisites of host targets have the // same host configuration. if (fromConfig.isHostConfiguration()) { return NoTransition.INSTANCE; } // Make sure config_setting dependencies are resolved in the referencing rule's configuration, // unconditionally. For example, given: // // genrule( // name = 'myrule', // tools = select({ '//a:condition': [':sometool'] }) // // all labels in "tools" get resolved in the host configuration (since the "tools" attribute // declares a host configuration transition). We want to explicitly exclude configuration labels // from these transitions, since their *purpose* is to do computation on the owning // rule's configuration. // TODO(bazel-team): don't require special casing here. This is far too hackish. if (toTarget instanceof Rule && ((Rule) toTarget).getRuleClassObject().isConfigMatcher()) { // TODO(gregce): see if this actually gets called return NoTransition.INSTANCE; } // The current transition to apply. When multiple transitions are requested, this is a // ComposingTransition, which encapsulates them into a single object so calling code // doesn't need special logic for combinations. ConfigurationTransition currentTransition = NoTransition.INSTANCE; // TODO(gregce): make the below transitions composable (i.e. take away the "else" clauses). // The "else" is a legacy restriction from static configurations. if (attribute.hasSplitConfigurationTransition()) { currentTransition = split(currentTransition, attribute.getSplitTransition(attributeMap)); } else { // III. Attributes determine configurations. The configuration of a prerequisite is determined // by the attribute. currentTransition = composeTransitions(currentTransition, attribute.getConfigurationTransition()); } // IV. Applies any rule transitions associated with the dep target and composes their // transitions with a passed-in existing transition. currentTransition = applyRuleTransition(currentTransition, toTarget); // V. Applies a transition to trim the result and returns it. (note: this is a temporary // feature; see the corresponding methods in ConfiguredRuleClassProvider) return applyTransitionFromFactory(currentTransition, toTarget, trimmingTransitionFactory); } /** * Same as evaluateTransition except does not check for transitions coming from parents and * enables support for rule-triggered top-level configuration hooks. */ public static ConfigurationTransition evaluateTopLevelTransition( TargetAndConfiguration targetAndConfig, @Nullable RuleTransitionFactory trimmingTransitionFactory) { Target target = targetAndConfig.getTarget(); BuildConfiguration fromConfig = targetAndConfig.getConfiguration(); // Rule class transitions (chosen by rule class definitions): if (target.getAssociatedRule() == null) { return NoTransition.INSTANCE; } ConfigurationTransition ruleTransition = applyRuleTransition(NoTransition.INSTANCE, target); ConfigurationTransition trimmingTransition = applyTransitionFromFactory(ruleTransition, target, trimmingTransitionFactory); return trimmingTransition; } /** * Returns true if the given target should have a null configuration. This method is the * "source of truth" for this determination. */ public static boolean usesNullConfiguration(Target target) { return target instanceof InputFile || target instanceof PackageGroup; } /** * Composes two transitions together efficiently. */ public static ConfigurationTransition composeTransitions(ConfigurationTransition transition1, ConfigurationTransition transition2) { Preconditions.checkNotNull(transition1); Preconditions.checkNotNull(transition2); if (isFinal(transition1) || transition2 == NoTransition.INSTANCE) { return transition1; } else if (isFinal(transition2) || transition1 == NoTransition.INSTANCE) { // When the second transition is a HOST transition, there's no need to compose. But this also // improves performance: host transitions are common, and ConfiguredTargetFunction has special // optimized logic to handle them. If they were buried in the last segment of a // ComposingTransition, those optimizations wouldn't trigger. return transition2; } else { return new ComposingTransition(transition1, transition2); } } /** * Returns true if once the given transition is applied to a dep no followup transitions should * be composed after it. */ private static boolean isFinal(ConfigurationTransition transition) { return (transition == NullTransition.INSTANCE || transition == HostTransition.INSTANCE); } /** * Applies the given split and composes it after an existing transition. */ private static ConfigurationTransition split(ConfigurationTransition currentTransition, SplitTransition split) { Preconditions.checkState(currentTransition != NullTransition.INSTANCE, "cannot apply splits after null transitions (null transitions are expected to be final)"); Preconditions.checkState(currentTransition != HostTransition.INSTANCE, "cannot apply splits after host transitions (host transitions are expected to be final)"); return composeTransitions(currentTransition, split); } /** * @param currentTransition a pre-existing transition to be composed with * @param toTarget target whose associated rule's incoming transition should be applied */ private static ConfigurationTransition applyRuleTransition( ConfigurationTransition currentTransition, Target toTarget) { Rule associatedRule = toTarget.getAssociatedRule(); RuleTransitionFactory transitionFactory = associatedRule.getRuleClassObject().getTransitionFactory(); return applyTransitionFromFactory(currentTransition, toTarget, transitionFactory); } /** * @param currentTransition a pre-existing transition to be composed with * @param toTarget target whose associated rule's incoming transition should be applied * @param transitionFactory a rule transition factory to apply, or null to do nothing */ private static ConfigurationTransition applyTransitionFromFactory( ConfigurationTransition currentTransition, Target toTarget, @Nullable RuleTransitionFactory transitionFactory) { if (isFinal(currentTransition)) { return currentTransition; } if (transitionFactory != null) { return composeTransitions( currentTransition, transitionFactory.buildTransitionFor(toTarget.getAssociatedRule())); } return currentTransition; } }