| // 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 com.google.auto.value.AutoValue; |
| import com.google.common.collect.ImmutableCollection; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.devtools.build.lib.analysis.DependencyKey; |
| 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.DependencyResolver; |
| import com.google.devtools.build.lib.analysis.InconsistentAspectOrderException; |
| import com.google.devtools.build.lib.analysis.TargetAndConfiguration; |
| import com.google.devtools.build.lib.analysis.ToolchainCollection; |
| import com.google.devtools.build.lib.analysis.ToolchainContext; |
| 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.ConfigMatchingProvider; |
| 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.TransitionFactory; |
| import com.google.devtools.build.lib.analysis.config.transitions.TransitionUtil; |
| import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget; |
| 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.RuleTransitionData; |
| import com.google.devtools.build.lib.packages.Target; |
| import com.google.devtools.build.lib.util.OrderedSetMultimap; |
| import java.util.Map; |
| 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 DependencyResolver dependencyResolver; |
| private final ConfiguredTargetAccessor accessor; |
| private final CqueryThreadsafeCallback cqueryThreadsafeCallback; |
| @Nullable private final TransitionFactory<RuleTransitionData> trimmingTransitionFactory; |
| |
| public CqueryTransitionResolver( |
| ExtendedEventHandler eventHandler, |
| DependencyResolver dependencyResolver, |
| ConfiguredTargetAccessor accessor, |
| CqueryThreadsafeCallback cqueryThreadsafeCallback, |
| @Nullable TransitionFactory<RuleTransitionData> trimmingTransitionFactory) { |
| this.eventHandler = eventHandler; |
| this.dependencyResolver = dependencyResolver; |
| this.accessor = accessor; |
| this.cqueryThreadsafeCallback = cqueryThreadsafeCallback; |
| this.trimmingTransitionFactory = trimmingTransitionFactory; |
| } |
| |
| /** |
| * Return the set of dependencies of a KeyedConfiguredTarget, including information about the |
| * configuration transitions applied to the dependencies. |
| * |
| * @see ResolvedTransition for more details. |
| * @param keyedConfiguredTarget the configured target whose dependencies are being looked up. |
| */ |
| public ImmutableSet<ResolvedTransition> dependencies(KeyedConfiguredTarget keyedConfiguredTarget) |
| throws DependencyResolver.Failure, InconsistentAspectOrderException, InterruptedException { |
| ImmutableSet.Builder<ResolvedTransition> resolved = new ImmutableSet.Builder<>(); |
| |
| if (!(keyedConfiguredTarget.getConfiguredTarget() instanceof RuleConfiguredTarget)) { |
| return resolved.build(); |
| } |
| |
| Target target = accessor.getTarget(keyedConfiguredTarget); |
| BuildConfigurationValue config = |
| cqueryThreadsafeCallback.getConfiguration(keyedConfiguredTarget.getConfigurationKey()); |
| |
| ImmutableMap<Label, ConfigMatchingProvider> configConditions = |
| keyedConfiguredTarget.getConfigConditions(); |
| |
| // Get a ToolchainContext to use for dependency resolution. |
| ToolchainCollection<ToolchainContext> toolchainContexts = |
| accessor.getToolchainContexts(target, config); |
| // We don't actually use fromOptions in our implementation of |
| // DependencyResolver but passing to avoid passing a null and since we have the information |
| // anyway. |
| OrderedSetMultimap<DependencyKind, DependencyKey> deps = |
| dependencyResolver.dependentNodeMap( |
| new TargetAndConfiguration(target, config), |
| /*aspect=*/ null, |
| configConditions, |
| toolchainContexts, |
| trimmingTransitionFactory); |
| for (Map.Entry<DependencyKind, DependencyKey> attributeAndDep : deps.entries()) { |
| DependencyKey dep = attributeAndDep.getValue(); |
| |
| if (attributeAndDep.getKey() instanceof NonAttributeDependencyKind) { |
| // No attribute edge to report. |
| continue; |
| } |
| |
| String dependencyName; |
| if (DependencyKind.isToolchain(attributeAndDep.getKey())) { |
| ToolchainDependencyKind tdk = (ToolchainDependencyKind) attributeAndDep.getKey(); |
| if (tdk.isDefaultExecGroup()) { |
| dependencyName = "[toolchain dependency]"; |
| } else { |
| dependencyName = String.format("[toolchain dependency: %s]", tdk.getExecGroupName()); |
| } |
| } else { |
| dependencyName = attributeAndDep.getKey().getAttribute().getName(); |
| } |
| |
| if (attributeAndDep.getValue().getTransition() == NoTransition.INSTANCE |
| || attributeAndDep.getValue().getTransition() == NullTransition.INSTANCE) { |
| resolved.add( |
| ResolvedTransition.create( |
| dep.getLabel(), |
| ImmutableList.of(), |
| dependencyName, |
| attributeAndDep.getValue().getTransition().getName())); |
| continue; |
| } |
| BuildOptions fromOptions = config.getOptions(); |
| // TODO(bazel-team): support transitions on Starlark-defined build flags. These require |
| // Skyframe loading to get flag default values. See ConfigurationResolver.applyTransition |
| // for an example of the required logic. |
| ImmutableSet<BuildOptions> toOptions = |
| ImmutableSet.copyOf( |
| dep.getTransition() |
| .apply(TransitionUtil.restrict(dep.getTransition(), fromOptions), eventHandler) |
| .values()); |
| resolved.add( |
| ResolvedTransition.create( |
| dep.getLabel(), toOptions, dependencyName, dep.getTransition().getName())); |
| } |
| return resolved.build(); |
| } |
| } |