Precomputes and stores split transition results for null configuration dependencies in ConfigurationResolver#resolveConfigurations so that RuleContext#getSplitPrerequisiteConfiguredTargetAndTargets does not need to execute split transitions in any cases.

PiperOrigin-RevId: 309053703
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/Dependency.java b/src/main/java/com/google/devtools/build/lib/analysis/Dependency.java
index 7dc403c..0b7e227 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/Dependency.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/Dependency.java
@@ -14,6 +14,7 @@
 package com.google.devtools.build.lib.analysis;
 
 import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
 import com.google.devtools.build.lib.analysis.config.transitions.ConfigurationTransition;
@@ -30,8 +31,8 @@
  *
  * <p>Explicit configurations: includes the target and the configuration of the dependency
  * configured target and any aspects that may be required, as well as the configurations for these
- * aspects and an optional transition key. {@link Dependency#getTransitionKey} provides some more
- * context on transition keys.
+ * aspects and transition keys. {@link Dependency#getTransitionKeys} provides some more context on
+ * transition keys.
  *
  * <p>Configuration transitions: includes the target and the desired configuration transitions that
  * should be applied to produce the dependency's configuration. It's the caller's responsibility to
@@ -51,11 +52,21 @@
    * configuration.
    */
   public static Dependency withNullConfiguration(Label label) {
-    return new NullConfigurationDependency(label);
+    return new NullConfigurationDependency(label, ImmutableList.of());
   }
 
   /**
-   * Creates a new {@link Dependency} with the given explicit configuration.
+   * Creates a new {@link Dependency} with a null configuration and transition keys, used when edges
+   * with a split transition were resolved to null configuration targets.
+   */
+  public static Dependency withNullConfigurationAndTransitionKeys(
+      Label label, ImmutableList<String> transitionKeys) {
+    return new NullConfigurationDependency(label, transitionKeys);
+  }
+
+  /**
+   * Creates a new {@link Dependency} with the given explicit configuration. Should only be used for
+   * dependency with no configuration changes.
    *
    * <p>The configuration must not be {@code null}.
    *
@@ -67,12 +78,12 @@
         configuration,
         AspectCollection.EMPTY,
         ImmutableMap.<AspectDescriptor, BuildConfiguration>of(),
-        null);
+        ImmutableList.of());
   }
 
   /**
-   * Creates a new {@link Dependency} with the given configuration and aspects. The configuration
-   * is also applied to all aspects.
+   * Creates a new {@link Dependency} with the given configuration and aspects. The configuration is
+   * also applied to all aspects. Should only be used for dependency with no configuration changes.
    *
    * <p>The configuration and aspects must not be {@code null}.
    */
@@ -84,7 +95,7 @@
       aspectBuilder.put(aspect, configuration);
     }
     return new ExplicitConfigurationDependency(
-        label, configuration, aspects, aspectBuilder.build(), null);
+        label, configuration, aspects, aspectBuilder.build(), ImmutableList.of());
   }
 
   /**
@@ -106,22 +117,31 @@
       aspectBuilder.put(aspect, configuration);
     }
     return new ExplicitConfigurationDependency(
-        label, configuration, aspects, aspectBuilder.build(), transitionKey);
+        label,
+        configuration,
+        aspects,
+        aspectBuilder.build(),
+        transitionKey == null ? ImmutableList.of() : ImmutableList.of(transitionKey));
   }
 
   /**
-   * Creates a new {@link Dependency} with the given configuration and aspects, suitable for
-   * storing the output of a configuration trimming step. The aspects each have their own
-   * configuration.
+   * Creates a new {@link Dependency} with the given configuration and aspects, suitable for storing
+   * the output of a configuration trimming step. The aspects each have their own configuration.
+   * Should only be used for dependency with no configuration changes.
    *
    * <p>The aspects and configurations must not be {@code null}.
    */
   public static Dependency withConfiguredAspects(
-      Label label, BuildConfiguration configuration,
+      Label label,
+      BuildConfiguration configuration,
       AspectCollection aspects,
       Map<AspectDescriptor, BuildConfiguration> aspectConfigurations) {
     return new ExplicitConfigurationDependency(
-        label, configuration, aspects, ImmutableMap.copyOf(aspectConfigurations), null);
+        label,
+        configuration,
+        aspects,
+        ImmutableMap.copyOf(aspectConfigurations),
+        ImmutableList.of());
   }
 
   /**
@@ -184,20 +204,28 @@
   public abstract BuildConfiguration getAspectConfiguration(AspectDescriptor aspect);
 
   /**
-   * Returns the key of a configuration transition, if exists, associated with this dependency. See
-   * {@link ConfigurationTransition#apply} for more details.
+   * Returns the keys of a configuration transition, if exist, associated with this dependency. See
+   * {@link ConfigurationTransition#apply} for more details. Normally, this returns an empty list,
+   * when there was no configuration transition in effect, or one with a single entry, when there
+   * was a specific configuration transition result that led to this. It may also return a list with
+   * multiple entries if the dependency has a null configuration, yet the outgoing edge has a split
+   * transition. In such cases all transition keys returned by the transition are tagged to the
+   * dependency.
    *
    * @throws IllegalStateException if {@link #hasExplicitConfiguration()} returns false.
    */
-  public abstract String getTransitionKey();
+  public abstract ImmutableList<String> getTransitionKeys();
 
   /**
    * Implementation of a dependency with no configuration, suitable for, e.g., source files or
    * visibility.
    */
   private static final class NullConfigurationDependency extends Dependency {
-    public NullConfigurationDependency(Label label) {
+    private final ImmutableList<String> transitionKeys;
+
+    public NullConfigurationDependency(Label label, ImmutableList<String> transitionKeys) {
       super(label);
+      this.transitionKeys = Preconditions.checkNotNull(transitionKeys);
     }
 
     @Override
@@ -227,10 +255,9 @@
       return null;
     }
 
-    @Nullable
     @Override
-    public String getTransitionKey() {
-      return null;
+    public ImmutableList<String> getTransitionKeys() {
+      return transitionKeys;
     }
 
     @Override
@@ -260,19 +287,19 @@
     private final BuildConfiguration configuration;
     private final AspectCollection aspects;
     private final ImmutableMap<AspectDescriptor, BuildConfiguration> aspectConfigurations;
-    @Nullable private final String transitionKey;
+    private final ImmutableList<String> transitionKeys;
 
     public ExplicitConfigurationDependency(
         Label label,
         BuildConfiguration configuration,
         AspectCollection aspects,
         ImmutableMap<AspectDescriptor, BuildConfiguration> aspectConfigurations,
-        @Nullable String transitionKey) {
+        ImmutableList<String> transitionKeys) {
       super(label);
       this.configuration = Preconditions.checkNotNull(configuration);
       this.aspects = Preconditions.checkNotNull(aspects);
       this.aspectConfigurations = Preconditions.checkNotNull(aspectConfigurations);
-      this.transitionKey = transitionKey;
+      this.transitionKeys = Preconditions.checkNotNull(transitionKeys);
     }
 
     @Override
@@ -301,15 +328,14 @@
       return aspectConfigurations.get(aspect);
     }
 
-    @Nullable
     @Override
-    public String getTransitionKey() {
-      return transitionKey;
+    public ImmutableList<String> getTransitionKeys() {
+      return transitionKeys;
     }
 
     @Override
     public int hashCode() {
-      return Objects.hash(label, configuration, aspectConfigurations, transitionKey);
+      return Objects.hash(label, configuration, aspectConfigurations, transitionKeys);
     }
 
     @Override
@@ -374,7 +400,7 @@
     }
 
     @Override
-    public String getTransitionKey() {
+    public ImmutableList<String> getTransitionKeys() {
       throw new IllegalStateException(
           "This dependency has a transition, not an explicit configuration.");
     }
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
index a68be1d..9e53631 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
@@ -44,13 +44,11 @@
 import com.google.devtools.build.lib.analysis.actions.ActionConstructionContext;
 import com.google.devtools.build.lib.analysis.buildinfo.BuildInfoKey;
 import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
-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.Fragment;
 import com.google.devtools.build.lib.analysis.config.FragmentCollection;
 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.SplitTransition;
 import com.google.devtools.build.lib.analysis.config.transitions.TransitionFactory;
 import com.google.devtools.build.lib.analysis.constraints.ConstraintSemantics;
 import com.google.devtools.build.lib.analysis.platform.ConstraintValueInfo;
@@ -905,40 +903,19 @@
   public Map<Optional<String>, List<ConfiguredTargetAndData>>
       getSplitPrerequisiteConfiguredTargetAndTargets(String attributeName) {
     checkAttribute(attributeName, TransitionMode.SPLIT);
-    Attribute attributeDefinition = attributes().getAttributeDefinition(attributeName);
-    Preconditions.checkState(attributeDefinition.getTransitionFactory().isSplit());
-    SplitTransition transition =
-        (SplitTransition)
-            attributeDefinition
-                .getTransitionFactory()
-                .create(
-                    AttributeTransitionData.builder()
-                        .attributes(ConfiguredAttributeMapper.of(rule, configConditions))
-                        .executionPlatform(getToolchainContext().executionPlatform().label())
-                        .build());
-    BuildOptions fromOptions = getConfiguration().getOptions();
-    Map<String, BuildOptions> splitOptions =
-        transition.split(fromOptions, getAnalysisEnvironment().getEventHandler());
-    List<ConfiguredTargetAndData> deps = getConfiguredTargetAndTargetDeps(attributeName);
-
-    if (SplitTransition.equals(fromOptions, splitOptions.values())) {
-      // The split transition is not active.
-      return ImmutableMap.of(Optional.<String>absent(), deps);
-    }
-
     // Use an ImmutableListMultimap.Builder here to preserve ordering.
     ImmutableListMultimap.Builder<Optional<String>, ConfiguredTargetAndData> result =
         ImmutableListMultimap.builder();
+    List<ConfiguredTargetAndData> deps = getConfiguredTargetAndTargetDeps(attributeName);
     for (ConfiguredTargetAndData t : deps) {
-      if (t.getTransitionKey() == null
-          || t.getTransitionKey().equals(ConfigurationTransition.PATCH_TRANSITION_KEY)) {
-        // The target doesn't have a specific transition key associated. This likely means it's a
-        // non-configurable target, e.g. files, package groups. Pan out to all available keys.
-        for (String key : splitOptions.keySet()) {
-          result.put(Optional.of(key), t);
-        }
-      } else {
-        result.put(Optional.of(t.getTransitionKey()), t);
+      ImmutableList<String> transitionKeys = t.getTransitionKeys();
+      if (transitionKeys.isEmpty()) {
+        // The split transition is not active, i.e. does not change build configurations.
+        // TODO(jungjw): Investigate if we need to do a sanity check here.
+        return ImmutableMap.of(Optional.absent(), deps);
+      }
+      for (String key : transitionKeys) {
+        result.put(Optional.of(key), t);
       }
     }
     return Multimaps.asMap(result.build());
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationResolver.java b/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationResolver.java
index 0696ef0..3dc86ce 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationResolver.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationResolver.java
@@ -19,6 +19,7 @@
 import com.google.common.base.Preconditions;
 import com.google.common.base.Verify;
 import com.google.common.base.VerifyException;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.LinkedHashMultimap;
@@ -34,6 +35,8 @@
 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.SplitTransition;
+import com.google.devtools.build.lib.analysis.config.transitions.TransitionFactory;
 import com.google.devtools.build.lib.analysis.skylark.StarlarkTransition;
 import com.google.devtools.build.lib.analysis.skylark.StarlarkTransition.TransitionException;
 import com.google.devtools.build.lib.cmdline.Label;
@@ -42,6 +45,8 @@
 import com.google.devtools.build.lib.events.ExtendedEventHandler;
 import com.google.devtools.build.lib.events.StoredEventHandler;
 import com.google.devtools.build.lib.packages.Attribute;
+import com.google.devtools.build.lib.packages.AttributeTransitionData;
+import com.google.devtools.build.lib.packages.ConfiguredAttributeMapper;
 import com.google.devtools.build.lib.packages.RuleClassProvider;
 import com.google.devtools.build.lib.packages.Target;
 import com.google.devtools.build.lib.packages.TargetUtils;
@@ -105,6 +110,8 @@
    * @param originalDeps the transition requests for each dep and each dependency kind
    * @param hostConfiguration the host configuration
    * @param ruleClassProvider provider for determining the right configuration fragments for deps
+   * @param defaultBuildOptions default build options to diff options against for optimization
+   * @param configConditions {@link ConfigMatchingProvider} map for the rule
    * @return a mapping from each dependency kind in the source target to the {@link
    *     BuildConfiguration}s and {@link Label}s for the deps under that dependency kind . Returns
    *     null if not all Skyframe dependencies are available.
@@ -116,7 +123,8 @@
       OrderedSetMultimap<DependencyKind, Dependency> originalDeps,
       BuildConfiguration hostConfiguration,
       RuleClassProvider ruleClassProvider,
-      BuildOptions defaultBuildOptions)
+      BuildOptions defaultBuildOptions,
+      ImmutableMap<Label, ConfigMatchingProvider> configConditions)
       throws DependencyEvaluationException, InterruptedException {
 
     // Maps each Skyframe-evaluated BuildConfiguration to the dependencies that need that
@@ -162,7 +170,8 @@
 
     for (Map.Entry<DependencyKind, Dependency> depsEntry : originalDeps.entries()) {
       Dependency dep = depsEntry.getValue();
-      DependencyEdge dependencyEdge = new DependencyEdge(depsEntry.getKey(), dep.getLabel());
+      DependencyKind depKind = depsEntry.getKey();
+      DependencyEdge dependencyEdge = new DependencyEdge(depKind, dep.getLabel());
       attributesAndLabels.add(dependencyEdge);
       // DependencyResolver should never emit a Dependency with an explicit configuration
       Preconditions.checkState(!dep.hasExplicitConfiguration());
@@ -173,8 +182,48 @@
       // total analysis phase time.
       ConfigurationTransition transition = dep.getTransition();
       if (transition == NullTransition.INSTANCE) {
-        putOnlyEntry(
-            resolvedDeps, dependencyEdge, Dependency.withNullConfiguration(dep.getLabel()));
+        Dependency finalDependency = Dependency.withNullConfiguration(dep.getLabel());
+        // If the base transition is a split transition, execute the transition and store returned
+        // transition keys along with the null configuration dependency, so that other code relying
+        // on stored transition keys doesn't have to implement special handling logic just for this
+        // kind of cases.
+        if (depKind.getAttribute() != null) {
+          TransitionFactory<AttributeTransitionData> transitionFactory =
+              depKind.getAttribute().getTransitionFactory();
+          if (transitionFactory.isSplit()) {
+            AttributeTransitionData transitionData =
+                AttributeTransitionData.builder()
+                    .attributes(
+                        ConfiguredAttributeMapper.of(
+                            ctgValue.getTarget().getAssociatedRule(), configConditions))
+                    .build();
+            ConfigurationTransition baseTransition = transitionFactory.create(transitionData);
+            Map<String, BuildOptions> toOptions;
+            try {
+              // TODO(jungjw): See if we can dedup getBuildSettingPackages implementations and put
+              //  this in applyTransition.
+              HashMap<PackageValue.Key, PackageValue> buildSettingPackages =
+                  StarlarkTransition.getBuildSettingPackages(env, baseTransition);
+              if (buildSettingPackages == null) {
+                return null;
+              }
+              toOptions =
+                  applyTransition(
+                      currentConfiguration.getOptions(),
+                      baseTransition,
+                      buildSettingPackages,
+                      env.getListener());
+            } catch (TransitionException e) {
+              throw new DependencyEvaluationException(e);
+            }
+            if (!SplitTransition.equals(currentConfiguration.getOptions(), toOptions.values())) {
+              finalDependency =
+                  Dependency.withNullConfigurationAndTransitionKeys(
+                      dep.getLabel(), ImmutableList.copyOf(toOptions.keySet()));
+            }
+          }
+        }
+        putOnlyEntry(resolvedDeps, dependencyEdge, finalDependency);
         continue;
       }
 
@@ -592,7 +641,7 @@
     }
     Set<String> depFragmentNames = new HashSet<>();
     for (Class<? extends Fragment> fragmentClass : expectedDepFragments) {
-     depFragmentNames.add(fragmentClass.getSimpleName());
+      depFragmentNames.add(fragmentClass.getSimpleName());
     }
     Set<String> missing = Sets.difference(depFragmentNames, ctgFragmentNames);
     if (!missing.isEmpty()) {
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetAndData.java b/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetAndData.java
index a553da1..1d06228 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetAndData.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetAndData.java
@@ -16,6 +16,7 @@
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.devtools.build.lib.analysis.ConfiguredTarget;
 import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
@@ -29,10 +30,9 @@
 
 /**
  * A container class for a {@link ConfiguredTarget} and associated data, {@link Target}, {@link
- * BuildConfiguration}, and an optional transition key. In the future, {@link ConfiguredTarget}
- * objects will no longer contain their associated {@link BuildConfiguration}. Consumers that need
- * the {@link Target} or {@link BuildConfiguration} must therefore have access to one of these
- * objects.
+ * BuildConfiguration}, and transition keys. In the future, {@link ConfiguredTarget} objects will no
+ * longer contain their associated {@link BuildConfiguration}. Consumers that need the {@link
+ * Target} or {@link BuildConfiguration} must therefore have access to one of these objects.
  *
  * <p>These objects are intended to be short-lived, never stored in Skyframe, since they pair three
  * heavyweight objects, a {@link ConfiguredTarget}, a {@link Target} (which holds a {@link
@@ -42,18 +42,18 @@
   private final ConfiguredTarget configuredTarget;
   private final Target target;
   private final BuildConfiguration configuration;
-  @Nullable private final String transitionKey;
+  private final ImmutableList<String> transitionKeys;
 
   @VisibleForTesting
   public ConfiguredTargetAndData(
       ConfiguredTarget configuredTarget,
       Target target,
       BuildConfiguration configuration,
-      String transitionKey) {
+      ImmutableList<String> transitionKeys) {
     this.configuredTarget = configuredTarget;
     this.target = target;
     this.configuration = configuration;
-    this.transitionKey = transitionKey;
+    this.transitionKeys = transitionKeys;
     Preconditions.checkState(
         configuredTarget.getLabel().equals(target.getLabel()),
         "Unable to construct ConfiguredTargetAndData:"
@@ -123,7 +123,7 @@
     if (configuredTarget.equals(maybeNew)) {
       return this;
     }
-    return new ConfiguredTargetAndData(maybeNew, target, configuration, transitionKey);
+    return new ConfiguredTargetAndData(maybeNew, target, configuration, transitionKeys);
   }
 
   public Target getTarget() {
@@ -138,8 +138,7 @@
     return configuredTarget;
   }
 
-  @Nullable
-  public String getTransitionKey() {
-    return transitionKey;
+  public ImmutableList<String> getTransitionKeys() {
+    return transitionKeys;
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java
index 79e3f49..0e5594e 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java
@@ -620,7 +620,8 @@
             depValueNames,
             hostConfiguration,
             ruleClassProvider,
-            defaultBuildOptions);
+            defaultBuildOptions,
+            configConditions);
 
     // Return early in case packages were not loaded yet. In theory, we could start configuring
     // dependent targets in loaded packages. However, that creates an artificial sync boundary
@@ -872,7 +873,7 @@
                       depValue.getConfiguredTarget(),
                       pkgValue.getPackage().getTarget(depLabel.getName()),
                       depConfiguration,
-                      dep.getTransitionKey()));
+                      dep.getTransitionKeys()));
             } catch (NoSuchTargetException e) {
               throw new IllegalStateException("Target already verified for " + dep, e);
             }