| // 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.annotations.VisibleForTesting; |
| import com.google.common.base.Preconditions; |
| import com.google.devtools.build.lib.analysis.TargetAndConfiguration; |
| import com.google.devtools.build.lib.packages.Attribute; |
| import com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition; |
| import com.google.devtools.build.lib.packages.Attribute.SplitTransition; |
| import com.google.devtools.build.lib.packages.Attribute.Transition; |
| 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; |
| |
| /** |
| * Tool for evaluating which {@link Attribute.Transition}(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 { |
| private final DynamicTransitionMapper transitionMapper; |
| |
| /** |
| * Instantiates this resolver with a helper class that maps non-{@link PatchTransition}s to |
| * {@link PatchTransition}s. |
| */ |
| public TransitionResolver(DynamicTransitionMapper transitionMapper) { |
| this.transitionMapper = transitionMapper; |
| } |
| |
| /** |
| * 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) |
| * |
| * @return the child's configuration, expressed as a diff from the parent's configuration. This |
| * is usually a {@PatchTransition} but exceptions apply (e.g. |
| * {@link Attribute.ConfigurationTransition}). |
| */ |
| public Transition evaluateTransition(BuildConfiguration fromConfig, final Rule fromRule, |
| final Attribute attribute, final Target toTarget, ConfiguredAttributeMapper attributeMap) { |
| |
| // I. Input files and package groups have no configurations. We don't want to duplicate them. |
| if (usesNullConfiguration(toTarget)) { |
| return Attribute.ConfigurationTransition.NULL; |
| } |
| |
| // II. Host configurations never switch to another. All prerequisites of host targets have the |
| // same host configuration. |
| if (fromConfig.isHostConfiguration()) { |
| return Attribute.ConfigurationTransition.NONE; |
| } |
| |
| // 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 Attribute.ConfigurationTransition.NONE; |
| } |
| |
| // The current transition to apply. When multiple transitions are requested, this is a |
| // ComposingSplitTransition, which encapsulates them into a single object so calling code |
| // doesn't need special logic for combinations. |
| Transition currentTransition = Attribute.ConfigurationTransition.NONE; |
| |
| // Apply the parent rule's outgoing transition if it has one. |
| RuleTransitionFactory transitionFactory = |
| fromRule.getRuleClassObject().getOutgoingTransitionFactory(); |
| if (transitionFactory != null) { |
| Transition transition = transitionFactory.buildTransitionFor(toTarget.getAssociatedRule()); |
| if (transition != null) { |
| currentTransition = composeTransitions(currentTransition, transition); |
| } |
| } |
| |
| // 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, |
| (SplitTransition<BuildOptions>) 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, composes their transitions |
| // with a passed-in existing transition, and returns the composed result. |
| return applyRuleTransition(currentTransition, toTarget, transitionMapper); |
| } |
| |
| /** |
| * Same as evaluateTransition except does not check for transitions coming from parents and |
| * enables support for rule-triggered top-level configuration hooks. |
| */ |
| public static Attribute.Transition evaluateTopLevelTransition( |
| TargetAndConfiguration targetAndConfig, DynamicTransitionMapper dynamicTransitionMapper) { |
| Target target = targetAndConfig.getTarget(); |
| BuildConfiguration fromConfig = targetAndConfig.getConfiguration(); |
| |
| // Top-level transitions (chosen by configuration fragments): |
| Transition topLevelTransition = fromConfig.topLevelConfigurationHook(target); |
| if (topLevelTransition == null) { |
| topLevelTransition = ConfigurationTransition.NONE; |
| } |
| |
| // Rule class transitions (chosen by rule class definitions): |
| if (target.getAssociatedRule() == null) { |
| return topLevelTransition; |
| } |
| return applyRuleTransition(topLevelTransition, target, dynamicTransitionMapper); |
| } |
| |
| /** |
| * 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. |
| */ |
| @VisibleForTesting |
| public Transition composeTransitions(Transition transition1, Transition transition2) { |
| if (isFinal(transition1)) { |
| return transition1; |
| } else if (transition2 == Attribute.ConfigurationTransition.NONE) { |
| return transition1; |
| } else if (transition2 == Attribute.ConfigurationTransition.NULL) { |
| // A NULL transition can just replace earlier transitions: no need to compose them. |
| return Attribute.ConfigurationTransition.NULL; |
| } else if (transition2 == Attribute.ConfigurationTransition.HOST) { |
| // A HOST transition can just replace earlier transitions: no need to compose them. |
| // But it 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 ComposingSplitTransition, those optimizations wouldn't trigger. |
| return HostTransition.INSTANCE; |
| } |
| |
| // TODO(gregce): remove the below conversion when all transitions are patch transitions. |
| Transition dynamicTransition = transitionMapper.map(transition2); |
| return transition1 == Attribute.ConfigurationTransition.NONE |
| ? dynamicTransition |
| : new ComposingSplitTransition(transition1, dynamicTransition); |
| } |
| |
| /** |
| * Returns true if once the given transition is applied to a dep no followup transitions should |
| * be composed after it. |
| */ |
| private static boolean isFinal(Transition transition) { |
| return (transition == Attribute.ConfigurationTransition.NULL |
| || transition == HostTransition.INSTANCE); |
| } |
| |
| /** |
| * Applies the given split and composes it after an existing transition. |
| */ |
| private static Transition split(Transition currentTransition, |
| SplitTransition<BuildOptions> split) { |
| Preconditions.checkState(currentTransition != Attribute.ConfigurationTransition.NULL, |
| "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 currentTransition == Attribute.ConfigurationTransition.NONE |
| ? split |
| : new ComposingSplitTransition(currentTransition, split); |
| } |
| |
| /** |
| * @param currentTransition a pre-existing transition to be composed with |
| * @param toTarget rule to examine for transitions |
| * @param transitionMapper only needed because of Attribute.ConfigurationTransition.DATA: this is |
| * C++-specific but non-C++ rules declare it. So they can't directly provide the C++-specific |
| * patch transition that implements it. |
| */ |
| private static Transition applyRuleTransition( |
| Transition currentTransition, Target toTarget, DynamicTransitionMapper transitionMapper) { |
| if (isFinal(currentTransition)) { |
| return currentTransition; |
| } |
| Rule associatedRule = toTarget.getAssociatedRule(); |
| RuleTransitionFactory transitionFactory = |
| associatedRule.getRuleClassObject().getTransitionFactory(); |
| if (transitionFactory != null) { |
| PatchTransition ruleClassTransition = (PatchTransition) |
| transitionMapper.map(transitionFactory.buildTransitionFor(associatedRule)); |
| if (ruleClassTransition != null) { |
| if (currentTransition == ConfigurationTransition.NONE) { |
| return ruleClassTransition; |
| } else { |
| return new ComposingSplitTransition(currentTransition, ruleClassTransition); |
| } |
| } |
| } |
| return currentTransition; |
| } |
| } |