apply configuration transitions when following alias labels

PiperOrigin-RevId: 393033365
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java
index 81ae07d..6cd2826 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java
@@ -14,6 +14,8 @@
 
 package com.google.devtools.build.lib.skyframe;
 
+import static com.google.common.collect.ImmutableList.toImmutableList;
+
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -26,8 +28,11 @@
 import com.google.devtools.build.lib.analysis.CachingAnalysisEnvironment.MissingDepException;
 import com.google.devtools.build.lib.analysis.ConfiguredAspect;
 import com.google.devtools.build.lib.analysis.ConfiguredAspectFactory;
+import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
 import com.google.devtools.build.lib.analysis.ConfiguredTarget;
 import com.google.devtools.build.lib.analysis.ConfiguredTargetValue;
+import com.google.devtools.build.lib.analysis.Dependency;
+import com.google.devtools.build.lib.analysis.DependencyKey;
 import com.google.devtools.build.lib.analysis.DependencyKind;
 import com.google.devtools.build.lib.analysis.DuplicateException;
 import com.google.devtools.build.lib.analysis.ExecGroupCollection.InvalidExecGroupException;
@@ -38,8 +43,12 @@
 import com.google.devtools.build.lib.analysis.ToolchainCollection;
 import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
 import com.google.devtools.build.lib.analysis.config.ConfigConditions;
+import com.google.devtools.build.lib.analysis.config.ConfigurationResolver;
 import com.google.devtools.build.lib.analysis.config.DependencyEvaluationException;
 import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
+import com.google.devtools.build.lib.analysis.config.TransitionResolver;
+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.configuredtargets.MergedConfiguredTarget;
 import com.google.devtools.build.lib.analysis.starlark.StarlarkTransition.TransitionException;
 import com.google.devtools.build.lib.causes.Cause;
@@ -52,8 +61,10 @@
 import com.google.devtools.build.lib.packages.Aspect;
 import com.google.devtools.build.lib.packages.AspectDefinition;
 import com.google.devtools.build.lib.packages.AspectDescriptor;
+import com.google.devtools.build.lib.packages.Attribute;
 import com.google.devtools.build.lib.packages.BuildFileContainsErrorsException;
 import com.google.devtools.build.lib.packages.NativeAspectClass;
+import com.google.devtools.build.lib.packages.NoSuchPackageException;
 import com.google.devtools.build.lib.packages.NoSuchTargetException;
 import com.google.devtools.build.lib.packages.NoSuchThingException;
 import com.google.devtools.build.lib.packages.OutputFile;
@@ -281,11 +292,10 @@
 
     ConfiguredTarget associatedTarget = baseConfiguredTargetValue.getConfiguredTarget();
 
-    ConfiguredTargetAndData associatedConfiguredTargetAndData;
     Package targetPkg;
     BuildConfiguration configuration = null;
     PackageValue.Key packageKey =
-        PackageValue.key(associatedTarget.getLabel().getPackageIdentifier());
+        PackageValue.key(associatedTarget.getOriginalLabel().getPackageIdentifier());
     if (associatedTarget.getConfigurationKey() == null) {
       PackageValue val = ((PackageValue) env.getValue(packageKey));
       if (val == null) {
@@ -307,25 +317,34 @@
           ((BuildConfigurationValue) result.get(associatedTarget.getConfigurationKey()))
               .getConfiguration();
     }
+
+    Target target;
     try {
-      associatedConfiguredTargetAndData =
-          new ConfiguredTargetAndData(
-              associatedTarget,
-              targetPkg.getTarget(associatedTarget.getLabel().getName()),
-              configuration,
-              null);
+      target = targetPkg.getTarget(associatedTarget.getOriginalLabel().getName());
     } catch (NoSuchTargetException e) {
       throw new IllegalStateException("Name already verified", e);
     }
 
-    if (baseConfiguredTargetValue.getConfiguredTarget().getProvider(AliasProvider.class) != null) {
+    if (AliasProvider.isAlias(associatedTarget)) {
       return createAliasAspect(
           env,
-          associatedConfiguredTargetAndData.getTarget(),
+          view.getHostConfiguration(aspectConfiguration),
+          new TargetAndConfiguration(target, configuration),
           aspect,
           key,
-          baseConfiguredTargetValue.getConfiguredTarget());
+          aspectConfiguration,
+          associatedTarget);
     }
+    // If we get here, label should match original label, and therefore the target we looked up
+    // above indeed corresponds to associatedTarget.getLabel().
+    Preconditions.checkState(
+        associatedTarget.getOriginalLabel().equals(associatedTarget.getLabel()),
+        "Non-alias %s should have matching label but found %s",
+        associatedTarget.getOriginalLabel(),
+        associatedTarget.getLabel());
+
+    ConfiguredTargetAndData associatedConfiguredTargetAndData =
+        new ConfiguredTargetAndData(associatedTarget, target, configuration, null);
 
     // If the incompatible flag is set, the top-level aspect should not be applied on top-level
     // targets whose rules do not advertise the aspect's required providers. The aspect should not
@@ -338,7 +357,6 @@
         starlarkSemantics.getBool(
             BuildLanguageOptions.INCOMPATIBLE_TOP_LEVEL_ASPECTS_REQUIRE_PROVIDERS);
     if (checkRuleAdvertisedProviders) {
-      Target target = associatedConfiguredTargetAndData.getTarget();
       if (target instanceof Rule) {
         if (!aspect
             .getDefinition()
@@ -406,27 +424,8 @@
             associatedConfiguredTargetAndData.getTarget(), aspectConfiguration);
     ImmutableList<Aspect> aspectPath = aspectPathBuilder.build();
     try {
-      // Determine what toolchains are needed by this target.
-      UnloadedToolchainContext unloadedToolchainContext = null;
-      if (configuration != null) {
-        // Configuration can be null in the case of aspects applied to input files. In this case,
-        // there are no chances of toolchains being used, so skip it.
-        try {
-          ImmutableSet<Label> requiredToolchains = aspect.getDefinition().getRequiredToolchains();
-          unloadedToolchainContext =
-              (UnloadedToolchainContext)
-                  env.getValueOrThrow(
-                      ToolchainContextKey.key()
-                          .configurationKey(BuildConfigurationValue.key(configuration))
-                          .requiredToolchainTypeLabels(requiredToolchains)
-                          .build(),
-                      ToolchainException.class);
-        } catch (ToolchainException e) {
-          // TODO(katre): better error handling
-          throw new AspectCreationException(
-              e.getMessage(), new LabelCause(key.getLabel(), e.getDetailedExitCode()));
-        }
-      }
+      UnloadedToolchainContext unloadedToolchainContext =
+          getUnloadedToolchainContext(env, key, aspect, configuration);
       if (env.valuesMissing()) {
         return null;
       }
@@ -550,6 +549,37 @@
     }
   }
 
+  @Nullable
+  private static UnloadedToolchainContext getUnloadedToolchainContext(
+      Environment env, AspectKey key, Aspect aspect, @Nullable BuildConfiguration configuration)
+      throws InterruptedException, AspectCreationException {
+    // Determine what toolchains are needed by this target.
+    UnloadedToolchainContext unloadedToolchainContext = null;
+    if (configuration != null) {
+      // Configuration can be null in the case of aspects applied to input files. In this case,
+      // there are no chances of toolchains being used, so skip it.
+      try {
+        ImmutableSet<Label> requiredToolchains = aspect.getDefinition().getRequiredToolchains();
+        unloadedToolchainContext =
+            (UnloadedToolchainContext)
+                env.getValueOrThrow(
+                    ToolchainContextKey.key()
+                        .configurationKey(BuildConfigurationValue.key(configuration))
+                        .requiredToolchainTypeLabels(requiredToolchains)
+                        .build(),
+                    ToolchainException.class);
+      } catch (ToolchainException e) {
+        // TODO(katre): better error handling
+        throw new AspectCreationException(
+            e.getMessage(), new LabelCause(key.getLabel(), e.getDetailedExitCode()));
+      }
+    }
+    if (env.valuesMissing()) {
+      return null;
+    }
+    return unloadedToolchainContext;
+  }
+
   /**
    * Returns whether or not to use the new toolchain transition. Checks the global incompatible
    * change flag and the aspect's toolchain transition readiness attribute.
@@ -620,31 +650,125 @@
     aspectPathBuilder.add(key);
   }
 
-  private SkyValue createAliasAspect(
+  /**
+   * Computes the given aspectKey of an alias-like target, by depending on the corresponding key of
+   * the next target in the alias chain (if there are more), or the "real" configured target.
+   */
+  @Nullable
+  private AspectValue createAliasAspect(
       Environment env,
-      Target originalTarget,
+      BuildConfiguration hostConfiguration,
+      TargetAndConfiguration originalTarget,
       Aspect aspect,
       AspectKey originalKey,
+      BuildConfiguration aspectConfiguration,
       ConfiguredTarget configuredTarget)
-      throws InterruptedException {
-    ImmutableList<Label> aliasChain = configuredTarget.getProvider(AliasProvider.class)
-        .getAliasChain();
+      throws AspectFunctionException, InterruptedException {
+    ImmutableList<Label> aliasChain =
+        configuredTarget.getProvider(AliasProvider.class).getAliasChain();
     // Find the next alias in the chain: either the next alias (if there are two) or the name of
     // the real configured target.
-    Label aliasLabel = aliasChain.size() > 1 ? aliasChain.get(1) : configuredTarget.getLabel();
+    Label aliasedLabel = aliasChain.size() > 1 ? aliasChain.get(1) : configuredTarget.getLabel();
 
-    return createAliasAspect(env, originalTarget, aspect, originalKey, aliasLabel);
+    NestedSetBuilder<Package> transitivePackagesForPackageRootResolution =
+        storeTransitivePackagesForPackageRootResolution ? NestedSetBuilder.stableOrder() : null;
+    NestedSetBuilder<Cause> transitiveRootCauses = NestedSetBuilder.stableOrder();
+
+    // Compute the Dependency from originalTarget to aliasedLabel
+    Dependency dep;
+    try {
+      UnloadedToolchainContext unloadedToolchainContext =
+          getUnloadedToolchainContext(env, originalKey, aspect, originalTarget.getConfiguration());
+      if (env.valuesMissing()) {
+        return null;
+      }
+
+      // See comment in compute() above for why we pair target with aspectConfiguration here
+      TargetAndConfiguration originalTargetAndAspectConfiguration =
+          new TargetAndConfiguration(originalTarget.getTarget(), aspectConfiguration);
+
+      // Get the configuration targets that trigger this rule's configurable attributes.
+      ConfigConditions configConditions =
+          ConfiguredTargetFunction.getConfigConditions(
+              env,
+              originalTargetAndAspectConfiguration,
+              transitivePackagesForPackageRootResolution,
+              unloadedToolchainContext == null ? null : unloadedToolchainContext.targetPlatform(),
+              transitiveRootCauses);
+      if (configConditions == null) {
+        // Those targets haven't yet been resolved.
+        return null;
+      }
+
+      Target aliasedTarget = getTargetFromLabel(env, aliasedLabel);
+      if (aliasedTarget == null) {
+        return null;
+      }
+      ConfigurationTransition transition =
+          TransitionResolver.evaluateTransition(
+              aspectConfiguration,
+              NoTransition.INSTANCE,
+              aliasedTarget,
+              ((ConfiguredRuleClassProvider) ruleClassProvider).getTrimmingTransitionFactory());
+
+      // Use ConfigurationResolver to apply any configuration transitions on the alias edge.
+      // This is a shortened/simplified variant of ConfiguredTargetFunction.computeDependencies
+      // for just the one special attribute we care about here.
+      DependencyKey depKey =
+          DependencyKey.builder().setLabel(aliasedLabel).setTransition(transition).build();
+      DependencyKind depKind =
+          DependencyKind.AttributeDependencyKind.forRule(
+              getAttributeContainingAlias(originalTarget.getTarget()));
+      ConfigurationResolver resolver =
+          new ConfigurationResolver(
+              env,
+              originalTargetAndAspectConfiguration,
+              hostConfiguration,
+              configConditions.asProviders());
+      ImmutableList<Dependency> deps = resolver.resolveConfiguration(depKind, depKey);
+      if (deps == null) {
+        return null;
+      }
+      // Actual should resolve to exactly one dependency
+      Preconditions.checkState(
+          deps.size() == 1, "Unexpected split in alias %s: %s", originalTarget.getLabel(), deps);
+      dep = deps.get(0);
+    } catch (NoSuchPackageException | NoSuchTargetException e) {
+      throw new AspectFunctionException(e);
+    } catch (ConfiguredValueCreationException e) {
+      throw new AspectFunctionException(e);
+    } catch (AspectCreationException e) {
+      throw new AspectFunctionException(e);
+    }
+
+    if (!transitiveRootCauses.isEmpty()) {
+      NestedSet<Cause> causes = transitiveRootCauses.build();
+      throw new AspectFunctionException(
+          new AspectCreationException(
+              "Loading failed",
+              causes,
+              ConfiguredTargetFunction.getPrioritizedDetailedExitCode(causes)));
+    }
+
+    // Now that we have a Dependency, we can compute the aliased key and depend on it
+    AspectKey actualKey = buildAliasAspectKey(originalKey, aliasedLabel, dep);
+    return createAliasAspect(
+        env,
+        originalTarget.getTarget(),
+        originalKey,
+        aspect,
+        actualKey,
+        transitivePackagesForPackageRootResolution);
   }
 
   private AspectValue createAliasAspect(
       Environment env,
       Target originalTarget,
-      Aspect aspect,
       AspectKey originalKey,
-      Label aliasLabel)
+      Aspect aspect,
+      AspectKey depKey,
+      @Nullable NestedSetBuilder<Package> transitivePackagesForPackageRootResolution)
       throws InterruptedException {
-    SkyKey depKey = originalKey.withLabel(aliasLabel);
-
     // Compute the AspectValue of the target the alias refers to (which can itself be either an
     // alias or a real target)
     AspectValue real = (AspectValue) env.getValue(depKey);
@@ -652,20 +776,80 @@
       return null;
     }
 
-    NestedSet<Package> transitivePackagesForPackageRootResolution =
-        storeTransitivePackagesForPackageRootResolution
-            ? NestedSetBuilder.<Package>stableOrder()
-                .addTransitive(real.getTransitivePackagesForPackageRootResolution())
-                .add(originalTarget.getPackage())
-                .build()
-            : null;
-
+    NestedSet<Package> finalTransitivePackagesForPackageRootResolution = null;
+    if (transitivePackagesForPackageRootResolution != null) {
+      finalTransitivePackagesForPackageRootResolution =
+          transitivePackagesForPackageRootResolution
+              .addTransitive(real.getTransitivePackagesForPackageRootResolution())
+              .add(originalTarget.getPackage())
+              .build();
+    }
     return new AspectValue(
         originalKey,
         aspect,
         originalTarget.getLocation(),
         ConfiguredAspect.forAlias(real.getConfiguredAspect()),
-        transitivePackagesForPackageRootResolution);
+        finalTransitivePackagesForPackageRootResolution);
+  }
+
+  @Nullable
+  private static Target getTargetFromLabel(Environment env, Label aliasLabel)
+      throws InterruptedException, NoSuchPackageException, NoSuchTargetException {
+    SkyValue val =
+        env.getValueOrThrow(
+            PackageValue.key(aliasLabel.getPackageIdentifier()), NoSuchPackageException.class);
+    if (val == null) {
+      return null;
+    }
+
+    Package pkg = ((PackageValue) val).getPackage();
+    return pkg.getTarget(aliasLabel.getName());
+  }
+
+  private static AspectKey buildAliasAspectKey(
+      AspectKey originalKey, Label aliasLabel, Dependency dep) {
+    ImmutableList<AspectKey> aliasedBaseKeys =
+        originalKey.getBaseKeys().stream()
+            .map(baseKey -> buildAliasAspectKey(baseKey, aliasLabel, dep))
+            .collect(toImmutableList());
+    return AspectValueKey.createAspectKey(
+        aliasLabel,
+        dep.getConfiguration(),
+        aliasedBaseKeys,
+        originalKey.getAspectDescriptor(),
+        dep.getAspectConfiguration(originalKey.getAspectDescriptor()));
+  }
+
+  /**
+   * Given an alias-like target, returns the attribute containing the "actual", by looking for
+   * attribute names used in known alias rules (Alias, Bind, LateBoundAlias, XcodeConfigAlias).
+   *
+   * <p>Alias and Bind rules use "actual", which will be by far the most common match here. It'll
+   * likely be rare that aspects need to traverse across other alias-like rules.
+   */
+  // TODO(lberki,kmb): try to avoid this, maybe by recording the attribute name in AliasProvider
+  private static Attribute getAttributeContainingAlias(Target originalTarget) {
+    Attribute aliasAttr = null;
+    for (Attribute attr : originalTarget.getAssociatedRule().getAttributes()) {
+      switch (attr.getName()) {
+        case "actual": // alias and bind rules
+        case ":alias": // LateBoundAlias-derived rules
+        case ":xcode_config": // xcode_config_alias rule
+          Preconditions.checkState(
+              aliasAttr == null,
+              "Found multiple candidate attributes %s and %s in %s",
+              aliasAttr,
+              attr,
+              originalTarget);
+          aliasAttr = attr;
+          break;
+        default:
+          break;
+      }
+    }
+    Preconditions.checkState(
+        aliasAttr != null, "Attribute containing alias not found in %s", originalTarget);
+    return aliasAttr;
   }
 
   @Nullable
@@ -701,7 +885,13 @@
         && associatedTarget.getTarget() instanceof OutputFile) {
       OutputFile outputFile = (OutputFile) associatedTarget.getTarget();
       Label label = outputFile.getGeneratingRule().getLabel();
-      return createAliasAspect(env, associatedTarget.getTarget(), aspect, key, label);
+      return createAliasAspect(
+          env,
+          associatedTarget.getTarget(),
+          key,
+          aspect,
+          key.withLabel(label),
+          transitivePackagesForPackageRootResolution);
     } else if (AspectResolver.aspectMatchesConfiguredTarget(associatedTarget, aspect)) {
       try {
         CurrentRuleTracker.beginConfiguredAspect(aspect.getAspectClass());