| // 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.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. |
| * |
| * <p>For the work of turning these transitions into actual configurations, see {@link |
| * ConfigurationResolver}. |
| * |
| * <p>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 attributeTransition the configuration transition of the attribute of this dependency |
| * edge |
| * @param toTarget the child target (which may or may not be a 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(s), expressed as a diff from the parent's configuration. |
| */ |
| public static ConfigurationTransition evaluateTransition( |
| BuildConfiguration fromConfig, |
| ConfigurationTransition attributeTransition, |
| Target toTarget, |
| @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. |
| // III. Apply whatever transition the attribute requires. |
| ConfigurationTransition currentTransition = attributeTransition; |
| |
| // 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(); |
| |
| // 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. */ |
| public 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; |
| } |
| } |