| // Copyright 2022 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.query2.cquery; |
| |
| import static com.google.common.collect.ImmutableList.toImmutableList; |
| import static com.google.devtools.build.lib.analysis.producers.TargetAndConfigurationProducer.computeTransition; |
| |
| import com.google.auto.value.AutoValue; |
| import com.google.common.collect.HashBasedTable; |
| import com.google.common.collect.ImmutableCollection; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableListMultimap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Multimaps; |
| import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider; |
| import com.google.devtools.build.lib.analysis.ConfiguredTarget; |
| import com.google.devtools.build.lib.analysis.DependencyKind; |
| import com.google.devtools.build.lib.analysis.DependencyKind.NonAttributeDependencyKind; |
| import com.google.devtools.build.lib.analysis.DependencyKind.ToolchainDependencyKind; |
| import com.google.devtools.build.lib.analysis.TargetAndConfiguration; |
| import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue; |
| import com.google.devtools.build.lib.analysis.config.BuildOptions; |
| import com.google.devtools.build.lib.analysis.config.StarlarkTransitionCache; |
| 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.configuredtargets.RuleConfiguredTarget; |
| import com.google.devtools.build.lib.analysis.constraints.IncompatibleTargetChecker.IncompatibleTargetException; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; |
| import com.google.devtools.build.lib.events.ExtendedEventHandler; |
| import com.google.devtools.build.lib.packages.RuleClassProvider; |
| import com.google.devtools.build.lib.packages.Target; |
| import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData; |
| import com.google.devtools.build.lib.skyframe.ConfiguredTargetEvaluationExceptions.ReportedException; |
| import com.google.devtools.build.lib.skyframe.ConfiguredTargetEvaluationExceptions.UnreportedException; |
| import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey; |
| import com.google.devtools.build.lib.skyframe.PrerequisiteProducer; |
| import com.google.devtools.build.lib.util.OrderedSetMultimap; |
| import java.util.Collection; |
| import java.util.Map; |
| import java.util.Objects; |
| import javax.annotation.Nullable; |
| |
| /** |
| * TransitionResolver resolves the dependencies of a ConfiguredTarget, reporting which |
| * configurations its dependencies are actually needed in according to the transitions applied to |
| * them. This class has been extracted from TransitionsOutputFormatterCallback.java so that it can |
| * be used in both ProtoOutputFormatterCallback and TransitionsOutputFormatterCallback |
| */ |
| public class CqueryTransitionResolver { |
| |
| /** |
| * ResolvedTransition represents a single edge in the dependency graph, between some target and a |
| * target it depends on, reachable via a single attribute. |
| */ |
| @AutoValue |
| @Immutable |
| public abstract static class ResolvedTransition { |
| |
| static ResolvedTransition create( |
| Label label, |
| ImmutableCollection<BuildOptions> buildOptions, |
| String attributeName, |
| String transitionName) { |
| return new AutoValue_CqueryTransitionResolver_ResolvedTransition( |
| label, buildOptions, attributeName, transitionName); |
| } |
| |
| /** The label of the target being depended on. */ |
| abstract Label label(); |
| |
| /** |
| * The configuration(s) this edge results in. This is a collection because a split transition |
| * may lead to a single attribute requesting a dependency in multiple configurations. |
| * |
| * <p>If a target is depended on via two attributes, separate ResolvedTransitions should be |
| * used, rather than combining the two into a single ResolvedTransition with multiple options. |
| * |
| * <p>If no transition was applied to an attribute, this collection will be empty. |
| */ |
| abstract ImmutableCollection<BuildOptions> options(); |
| |
| /** The name of the attribute via which the dependency was requested. */ |
| abstract String attributeName(); |
| |
| /** The name of the transition applied to the attribute. */ |
| abstract String transitionName(); |
| } |
| |
| private final ExtendedEventHandler eventHandler; |
| private final ConfiguredTargetAccessor accessor; |
| private final CqueryThreadsafeCallback cqueryThreadsafeCallback; |
| private final RuleClassProvider ruleClassProvider; |
| private final StarlarkTransitionCache transitionCache; |
| |
| public CqueryTransitionResolver( |
| ExtendedEventHandler eventHandler, |
| ConfiguredTargetAccessor accessor, |
| CqueryThreadsafeCallback cqueryThreadsafeCallback, |
| RuleClassProvider ruleClassProvider, |
| StarlarkTransitionCache transitionCache) { |
| this.eventHandler = eventHandler; |
| this.accessor = accessor; |
| this.cqueryThreadsafeCallback = cqueryThreadsafeCallback; |
| this.ruleClassProvider = ruleClassProvider; |
| this.transitionCache = transitionCache; |
| } |
| |
| /** |
| * Return the set of dependencies of a ConfiguredTarget, including information about the |
| * configuration transitions applied to the dependencies. |
| * |
| * @see ResolvedTransition for more details. |
| * @param configuredTarget the configured target whose dependencies are being looked up. |
| */ |
| public ImmutableSet<ResolvedTransition> dependencies(ConfiguredTarget configuredTarget) |
| throws EvaluateException, InterruptedException { |
| if (!(configuredTarget instanceof RuleConfiguredTarget)) { |
| return ImmutableSet.of(); |
| } |
| |
| Target target = accessor.getTarget(configuredTarget); |
| BuildConfigurationValue configuration = |
| cqueryThreadsafeCallback.getConfiguration(configuredTarget.getConfigurationKey()); |
| |
| var state = new PrerequisiteProducer.State(/* storeTransitivePackages= */ false); |
| state.targetAndConfiguration = new TargetAndConfiguration(target, configuration); |
| var attributeTransitionCollector = |
| HashBasedTable.<DependencyKind, Label, ConfigurationTransition>create(); |
| |
| var producer = new PrerequisiteProducer(state.targetAndConfiguration); |
| try { |
| if (!producer.evaluate( |
| state, |
| ConfiguredTargetKey.fromConfiguredTarget(configuredTarget), |
| ruleClassProvider, |
| transitionCache, |
| /* semaphoreLocker= */ () -> {}, |
| attributeTransitionCollector::put, |
| accessor.getLookupEnvironment(), |
| eventHandler)) { |
| throw new EvaluateException("PrerequisiteProducer.evaluate did not complete"); |
| } |
| } catch (ReportedException | UnreportedException | IncompatibleTargetException e) { |
| throw new EvaluateException(e.getMessage()); |
| } |
| |
| if (!state.transitiveRootCauses().isEmpty()) { |
| throw new EvaluateException( |
| "expected empty: " + state.transitiveRootCauses().build().toList()); |
| } |
| |
| OrderedSetMultimap<DependencyKind, ConfiguredTargetAndData> deps = producer.getDepValueMap(); |
| |
| var resolved = ImmutableSet.<ResolvedTransition>builder(); |
| for (Map.Entry<DependencyKind, Collection<ConfiguredTargetAndData>> entry : |
| deps.asMap().entrySet()) { |
| DependencyKind kind = entry.getKey(); |
| if (kind instanceof NonAttributeDependencyKind) { |
| continue; // No attribute edge to report. |
| } |
| |
| // There can be multiple labels under a given kind. Groups the targets by label. |
| ImmutableListMultimap<Label, ConfiguredTargetAndData> targetsByLabel = |
| Multimaps.index( |
| entry.getValue(), |
| prerequisite -> prerequisite.getConfiguredTarget().getOriginalLabel()); |
| String dependencyName = getDependencyName(kind); |
| Map<Label, ConfigurationTransition> attributeTransitions = |
| attributeTransitionCollector.row(kind); |
| |
| for (Map.Entry<Label, Collection<ConfiguredTargetAndData>> labelEntry : |
| targetsByLabel.asMap().entrySet()) { |
| Label label = labelEntry.getKey(); |
| Collection<ConfiguredTargetAndData> targets = labelEntry.getValue(); |
| |
| ConfigurationTransition noOrNullTransition = |
| getTransitionIfNoOrNull(configuration, targets); |
| if (noOrNullTransition != null) { |
| resolved.add( |
| ResolvedTransition.create( |
| label, |
| /* buildOptions= */ ImmutableList.of(), |
| dependencyName, |
| noOrNullTransition.getName())); |
| continue; |
| } |
| |
| // The rule transition does not vary across a split so using the first target is sufficient. |
| ConfigurationTransition ruleTransition = |
| getRuleTransition(targets.iterator().next().getConfiguredTarget()); |
| |
| var toOptions = |
| targets.stream().map(t -> t.getConfiguration().getOptions()).collect(toImmutableList()); |
| |
| resolved.add( |
| ResolvedTransition.create( |
| label, |
| toOptions, |
| dependencyName, |
| getTransitionName(attributeTransitions.get(label), ruleTransition))); |
| } |
| } |
| return resolved.build(); |
| } |
| |
| static class EvaluateException extends Exception { |
| private EvaluateException(String message) { |
| super(message); |
| } |
| } |
| |
| @Nullable |
| private static ConfigurationTransition getTransitionIfNoOrNull( |
| BuildConfigurationValue fromConfiguration, Collection<ConfiguredTargetAndData> targets) { |
| ConfiguredTargetAndData first = targets.iterator().next(); |
| if (targets.size() == 1 && Objects.equals(fromConfiguration, first.getConfiguration())) { |
| return NoTransition.INSTANCE; |
| } |
| // If any target has a null configuration, they all do, so it's sufficient to check the first. |
| if (first.getConfiguration() == null) { |
| return NullTransition.INSTANCE; |
| } |
| return null; |
| } |
| |
| private static String getDependencyName(DependencyKind kind) { |
| if (DependencyKind.isToolchain(kind)) { |
| ToolchainDependencyKind tdk = (ToolchainDependencyKind) kind; |
| if (tdk.isDefaultExecGroup()) { |
| return "[toolchain dependency]"; |
| } |
| return String.format("[toolchain dependency: %s]", tdk.getExecGroupName()); |
| } |
| return kind.getAttribute().getName(); |
| } |
| |
| @Nullable |
| private ConfigurationTransition getRuleTransition(ConfiguredTarget configuredTarget) { |
| if (configuredTarget instanceof RuleConfiguredTarget) { |
| return computeTransition( |
| accessor.getTarget(configuredTarget).getAssociatedRule(), |
| ((ConfiguredRuleClassProvider) ruleClassProvider).getTrimmingTransitionFactory()); |
| } |
| return null; |
| } |
| |
| private static String getTransitionName( |
| @Nullable ConfigurationTransition attributeTransition, |
| @Nullable ConfigurationTransition ruleTransition) { |
| ConfigurationTransition transition = NoTransition.INSTANCE; |
| if (attributeTransition != null) { |
| transition = ComposingTransition.of(transition, attributeTransition); |
| } |
| if (ruleTransition != null) { |
| transition = ComposingTransition.of(transition, ruleTransition); |
| } |
| return transition.getName(); |
| } |
| } |