Add Starlark API to access result of propagating aspects to targets toolchains

for an aspect applied to a target, it can see its providers on the target's toolchains through `ctx.rule.toolchains[TOOLCHAIN_TYPE]` and `ctx.rule.exec_groups[GP_NAME][TOOLCHAIN_TYPE]`. The returned value is a list of providers that the aspect (and its base aspects if any) returned.

PiperOrigin-RevId: 654128385
Change-Id: I4f60fbf0b7322259474206b94cf168ccdb1be03d
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/AspectBaseTargetResolvedToolchainContext.java b/src/main/java/com/google/devtools/build/lib/analysis/AspectBaseTargetResolvedToolchainContext.java
new file mode 100644
index 0000000..e27fd48
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/AspectBaseTargetResolvedToolchainContext.java
@@ -0,0 +1,158 @@
+// Copyright 2024 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;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.analysis.configuredtargets.MergedConfiguredTarget;
+import com.google.devtools.build.lib.analysis.platform.ToolchainTypeInfo;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.packages.Provider;
+import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData;
+import com.google.devtools.build.lib.skyframe.toolchains.UnloadedToolchainContext;
+import javax.annotation.Nullable;
+import net.starlark.java.eval.EvalException;
+import net.starlark.java.eval.Printer;
+import net.starlark.java.eval.Starlark;
+import net.starlark.java.eval.StarlarkIndexable;
+import net.starlark.java.eval.StarlarkSemantics;
+
+/**
+ * A toolchain context for the aspect's base target toolchains. It is used to represent the result
+ * of applying the aspects propagation to the base target toolchains.
+ */
+@AutoValue
+public abstract class AspectBaseTargetResolvedToolchainContext
+    implements ResolvedToolchainsDataInterface<
+        AspectBaseTargetResolvedToolchainContext.ToolchainAspectsProviders> {
+
+  public abstract ImmutableMap<ToolchainTypeInfo, ToolchainAspectsProviders> getToolchains();
+
+  public static AspectBaseTargetResolvedToolchainContext load(
+      UnloadedToolchainContext unloadedToolchainContext,
+      String targetDescription,
+      ImmutableMultimap<ToolchainTypeInfo, ConfiguredTargetAndData> toolchainTargets)
+      throws DuplicateException {
+
+    ImmutableMap.Builder<ToolchainTypeInfo, ToolchainAspectsProviders> toolchainsBuilder =
+        new ImmutableMap.Builder<>();
+
+    for (var toolchainType : unloadedToolchainContext.toolchainTypeToResolved().keySet()) {
+      Preconditions.checkArgument(toolchainTargets.get(toolchainType).size() == 1);
+
+      var toolchainTarget =
+          Iterables.getOnlyElement(toolchainTargets.get(toolchainType)).getConfiguredTarget();
+
+      if (toolchainTarget instanceof MergedConfiguredTarget mergedConfiguredTarget) {
+        // Only add the aspects providers from the toolchains that the aspects applied to.
+        toolchainsBuilder.put(
+            toolchainType,
+            new ToolchainAspectsProviders(
+                mergedConfiguredTarget.getAspectsProviders(), mergedConfiguredTarget.getLabel()));
+      } else {
+        // Add empty providers for the toolchains that the aspects did not apply to.
+        toolchainsBuilder.put(
+            toolchainType,
+            new ToolchainAspectsProviders(
+                new TransitiveInfoProviderMapBuilder().build(), toolchainTarget.getLabel()));
+      }
+    }
+    ImmutableMap<ToolchainTypeInfo, ToolchainAspectsProviders> toolchains =
+        toolchainsBuilder.buildOrThrow();
+
+    return new AutoValue_AspectBaseTargetResolvedToolchainContext(
+        // ToolchainContext:
+        unloadedToolchainContext.key(),
+        unloadedToolchainContext.executionPlatform(),
+        unloadedToolchainContext.targetPlatform(),
+        unloadedToolchainContext.toolchainTypes(),
+        unloadedToolchainContext.resolvedToolchainLabels(),
+        // ResolvedToolchainsDataInterface:
+        targetDescription,
+        unloadedToolchainContext.requestedLabelToToolchainType(),
+        // this:
+        toolchains);
+  }
+
+  @Override
+  @Nullable
+  public ToolchainAspectsProviders forToolchainType(Label toolchainTypeLabel) {
+    if (requestedToolchainTypeLabels().containsKey(toolchainTypeLabel)) {
+      return getToolchains().get(requestedToolchainTypeLabels().get(toolchainTypeLabel));
+    }
+
+    return null;
+  }
+
+  /**
+   * A Starlark-indexable wrapper used to represent the providers of the aspects applied on the base
+   * target toolchains.
+   */
+  public static class ToolchainAspectsProviders
+      implements StarlarkIndexable, ResolvedToolchainData {
+
+    private final TransitiveInfoProviderMap aspectsProviders;
+    private final Label label;
+
+    private ToolchainAspectsProviders(TransitiveInfoProviderMap aspectsProviders, Label label) {
+      this.aspectsProviders = aspectsProviders;
+      this.label = label;
+    }
+
+    @Override
+    public final Object getIndex(StarlarkSemantics semantics, Object key) throws EvalException {
+      Provider constructor = selectExportedProvider(key, "index");
+      Object declaredProvider = aspectsProviders.get(constructor.getKey());
+      if (declaredProvider != null) {
+        return declaredProvider;
+      }
+      throw Starlark.errorf(
+          "%s doesn't contain declared provider '%s'",
+          Starlark.repr(this), constructor.getPrintableName());
+    }
+
+    @Override
+    public boolean containsKey(StarlarkSemantics semantics, Object key) throws EvalException {
+      return aspectsProviders.get(selectExportedProvider(key, "query").getKey()) != null;
+    }
+
+    /**
+     * Selects the provider identified by {@code key}, throwing a Starlark error if the key is not a
+     * provider or not exported.
+     */
+    private Provider selectExportedProvider(Object key, String operation) throws EvalException {
+      if (!(key instanceof Provider constructor)) {
+        throw Starlark.errorf(
+            "This type only supports %sing by object constructors, got %s instead",
+            operation, Starlark.type(key));
+      }
+      if (!constructor.isExported()) {
+        throw Starlark.errorf(
+            "%s only supports %sing by exported providers. Assign the provider a name "
+                + "in a top-level assignment statement.",
+            Starlark.repr(this), operation);
+      }
+      return constructor;
+    }
+
+    @Override
+    public void repr(Printer printer) {
+      printer.append("<ToolchainAspectsProviders for toolchain target: " + label + ">");
+    }
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/AspectContext.java b/src/main/java/com/google/devtools/build/lib/analysis/AspectContext.java
index e7ae9f8..08a69c9 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/AspectContext.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/AspectContext.java
@@ -15,12 +15,15 @@
 package com.google.devtools.build.lib.analysis;
 
 import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableListMultimap;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Streams;
+import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.collect.ImmutableSortedKeyListMultimap;
 import com.google.devtools.build.lib.packages.Aspect;
 import com.google.devtools.build.lib.packages.AspectDescriptor;
@@ -30,6 +33,7 @@
 import java.util.Collection;
 import java.util.LinkedHashMap;
 import java.util.Map;
+import javax.annotation.Nullable;
 
 /** Extends {@link RuleContext} to provide all data available during the analysis of an aspect. */
 public final class AspectContext extends RuleContext {
@@ -45,18 +49,87 @@
 
   private final PrerequisitesCollection mainAspectPrerequisites;
 
+  /**
+   * The toolchain contexts for the base target.
+   *
+   * <p>It only contains the providers created by the aspects that propagate to the toolchains.
+   */
+  @Nullable
+  private final ToolchainCollection<AspectBaseTargetResolvedToolchainContext>
+      baseTargetToolchainContexts;
+
+  /** Whether the target uses auto exec groups. */
+  private final boolean targetUsesAutoExecGroups;
+
   AspectContext(
       RuleContext.Builder builder,
       AspectAwareAttributeMapper aspectAwareAttributeMapper,
       PrerequisitesCollection ruleAndBaseAspectsPrerequisites,
       PrerequisitesCollection mainAspectPrerequisites,
-      ExecGroupCollection execGroupCollection) {
+      ExecGroupCollection execGroupCollection,
+      @Nullable
+          ToolchainCollection<AspectBaseTargetResolvedToolchainContext> baseTargetToolchainContexts,
+      boolean targetUsesAutoExecGroups) {
     super(
         builder, aspectAwareAttributeMapper, ruleAndBaseAspectsPrerequisites, execGroupCollection);
 
     this.aspects = builder.getAspects();
     this.aspectDescriptors = aspects.stream().map(Aspect::getDescriptor).collect(toImmutableList());
     this.mainAspectPrerequisites = mainAspectPrerequisites;
+    this.baseTargetToolchainContexts = baseTargetToolchainContexts;
+    this.targetUsesAutoExecGroups = targetUsesAutoExecGroups;
+  }
+
+  /**
+   * Returns the toolchain contexts for the base target. Can be null if no aspect in the {@code
+   * aspects} path propagate to the toolchains.
+   */
+  @Nullable
+  public ToolchainCollection<AspectBaseTargetResolvedToolchainContext>
+      getBaseTargetToolchainContexts() {
+    return baseTargetToolchainContexts;
+  }
+
+  /** Returns the labels of default the toolchain types that aspects have propagated. */
+  public ImmutableSet<Label> getRequestedToolchainTypesLabels() {
+    if (targetUsesAutoExecGroups) {
+      return baseTargetToolchainContexts.getContextMap().entrySet().stream()
+          .filter(e -> isAutomaticExecGroup(e.getKey()))
+          .flatMap(e -> e.getValue().requestedToolchainTypeLabels().keySet().stream())
+          .collect(toImmutableSet());
+    } else {
+      return baseTargetToolchainContexts
+          .getDefaultToolchainContext()
+          .requestedToolchainTypeLabels()
+          .keySet();
+    }
+  }
+
+  /**
+   * Returns the toolchain data for the given type, or {@code null} if the toolchain type was not
+   * required in this context.
+   */
+  @Nullable
+  public AspectBaseTargetResolvedToolchainContext.ToolchainAspectsProviders getToolchainTarget(
+      Label toolchainType) {
+    var execGroupContext = baseTargetToolchainContexts.getDefaultToolchainContext();
+    if (targetUsesAutoExecGroups) {
+      execGroupContext =
+          baseTargetToolchainContexts.getContextMap().entrySet().stream()
+              .filter(
+                  e ->
+                      isAutomaticExecGroup(e.getKey())
+                          && e.getValue().requestedToolchainTypeLabels().containsKey(toolchainType))
+              .findFirst()
+              .map(e -> e.getValue())
+              .orElse(null);
+      if (execGroupContext == null) {
+        return null;
+      }
+    }
+    return execGroupContext
+        .getToolchains()
+        .get(execGroupContext.requestedToolchainTypeLabels().get(toolchainType));
   }
 
   /**
@@ -86,9 +159,10 @@
       Builder builder,
       AttributeMap ruleAttributes,
       ImmutableListMultimap<DependencyKind, ConfiguredTargetAndData> targetsMap,
-      ExecGroupCollection execGroupCollection) {
+      ExecGroupCollection execGroupCollection,
+      ToolchainCollection<AspectBaseTargetResolvedToolchainContext> baseTargetToolchainContexts) {
     return createAspectContextWithSeparatedPrerequisites(
-        builder, ruleAttributes, targetsMap, execGroupCollection);
+        builder, ruleAttributes, targetsMap, execGroupCollection, baseTargetToolchainContexts);
   }
 
   /**
@@ -99,7 +173,8 @@
       RuleContext.Builder builder,
       AttributeMap ruleAttributes,
       ImmutableListMultimap<DependencyKind, ConfiguredTargetAndData> prerequisitesMap,
-      ExecGroupCollection execGroupCollection) {
+      ExecGroupCollection execGroupCollection,
+      ToolchainCollection<AspectBaseTargetResolvedToolchainContext> baseTargetToolchainContexts) {
     ImmutableSortedKeyListMultimap.Builder<String, ConfiguredTargetAndData>
         mainAspectPrerequisites = ImmutableSortedKeyListMultimap.builder();
     ImmutableSortedKeyListMultimap.Builder<String, ConfiguredTargetAndData>
@@ -118,6 +193,9 @@
       }
     }
 
+    boolean targetUsesAutoExecGroups =
+        RuleContext.usesAutoExecGroups(ruleAttributes, builder.getConfiguration());
+
     return new AspectContext(
         builder,
         new AspectAwareAttributeMapper(
@@ -134,7 +212,9 @@
             builder.getErrorConsumer(),
             builder.getRule(),
             builder.getRuleClassNameForLogging()),
-        execGroupCollection);
+        execGroupCollection,
+        baseTargetToolchainContexts,
+        targetUsesAutoExecGroups);
   }
 
   private static AspectAwareAttributeMapper mergeRuleAndBaseAspectsAttributes(
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BUILD b/src/main/java/com/google/devtools/build/lib/analysis/BUILD
index e0c100c..e8b84ff 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/analysis/BUILD
@@ -159,6 +159,7 @@
         "AnalysisResult.java",
         "AnalysisRootCauseEvent.java",
         "AnalysisUtils.java",
+        "AspectBaseTargetResolvedToolchainContext.java",
         "AspectCompleteEvent.java",
         "AspectContext.java",
         "AspectResolutionHelpers.java",
@@ -322,6 +323,8 @@
         ":repo_mapping_manifest_action",
         ":required_config_fragments_provider",
         ":resolved_toolchain_context",
+        ":resolved_toolchain_data",
+        ":resolved_toolchain_data_interface",
         ":rule_configured_object_value",
         ":rule_definition_environment",
         ":rule_error_consumer",
@@ -420,6 +423,7 @@
         "//src/main/java/com/google/devtools/build/lib/skyframe/serialization:visible-for-serialization",
         "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec",
         "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec:serialization-constant",
+        "//src/main/java/com/google/devtools/build/lib/skyframe/toolchains:toolchain_context_key",
         "//src/main/java/com/google/devtools/build/lib/skyframe/toolchains:unloaded_toolchain_context",
         "//src/main/java/com/google/devtools/build/lib/starlarkbuildapi",
         "//src/main/java/com/google/devtools/build/lib/starlarkbuildapi/config:configuration_transition_api",
@@ -1038,8 +1042,8 @@
     srcs = ["ResolvedToolchainContext.java"],
     deps = [
         ":config/toolchain_type_requirement",
+        ":resolved_toolchain_data_interface",
         ":template_variable_info",
-        ":toolchain_context",
         "//src/main/java/com/google/devtools/build/lib/analysis/platform",
         "//src/main/java/com/google/devtools/build/lib/analysis/platform:utils",
         "//src/main/java/com/google/devtools/build/lib/cmdline",
@@ -1056,6 +1060,25 @@
 )
 
 java_library(
+    name = "resolved_toolchain_data_interface",
+    srcs = ["ResolvedToolchainsDataInterface.java"],
+    deps = [
+        ":resolved_toolchain_data",
+        ":toolchain_context",
+        "//src/main/java/com/google/devtools/build/lib/analysis/platform",
+        "//src/main/java/com/google/devtools/build/lib/cmdline",
+        "//third_party:guava",
+        "//third_party:jsr305",
+    ],
+)
+
+java_library(
+    name = "resolved_toolchain_data",
+    srcs = ["ResolvedToolchainData.java"],
+    deps = ["//src/main/java/net/starlark/java/eval"],
+)
+
+java_library(
     name = "run_environment_info",
     srcs = ["RunEnvironmentInfo.java"],
     deps = [
@@ -2531,6 +2554,7 @@
     srcs = ["starlark/StarlarkExecGroupCollection.java"],
     deps = [
         ":resolved_toolchain_context",
+        ":resolved_toolchain_data_interface",
         ":starlark/starlark_toolchain_context",
         ":toolchain_collection",
         "//src/main/java/com/google/devtools/build/lib/packages:exec_group",
@@ -2585,6 +2609,7 @@
     name = "starlark/starlark_toolchain_context",
     srcs = ["starlark/StarlarkToolchainContext.java"],
     deps = [
+        ":resolved_toolchain_data",
         "//src/main/java/com/google/devtools/build/lib/analysis/platform",
         "//src/main/java/com/google/devtools/build/lib/cmdline",
         "//src/main/java/com/google/devtools/build/lib/packages",
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredTargetFactory.java b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredTargetFactory.java
index 9339b1c..90389a5 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredTargetFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredTargetFactory.java
@@ -602,7 +602,8 @@
       OrderedSetMultimap<DependencyKind, ConfiguredTargetAndData> prerequisiteMap,
       ConfigConditions configConditions,
       @Nullable ToolchainCollection<ResolvedToolchainContext> toolchainContexts,
-      @Nullable ToolchainCollection<ResolvedToolchainContext> baseTargetToolchainContexts,
+      @Nullable
+          ToolchainCollection<AspectBaseTargetResolvedToolchainContext> baseTargetToolchainContexts,
       @Nullable ExecGroupCollection.Builder execGroupCollectionBuilder,
       BuildConfigurationValue aspectConfiguration,
       @Nullable NestedSet<Package> transitivePackages,
@@ -623,6 +624,7 @@
             .setPrerequisites(removeToolchainDeps(prerequisiteMap))
             .setConfigConditions(configConditions)
             .setToolchainContexts(toolchainContexts)
+            .setBaseTargetToolchainContexts(baseTargetToolchainContexts)
             .setExecGroupCollectionBuilder(execGroupCollectionBuilder)
             .setExecProperties(ImmutableMap.of())
             .setRequiredConfigFragments(
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ResolvedToolchainContext.java b/src/main/java/com/google/devtools/build/lib/analysis/ResolvedToolchainContext.java
index 45593a9..6b6a9450 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/ResolvedToolchainContext.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ResolvedToolchainContext.java
@@ -38,7 +38,8 @@
 @AutoValue
 @Immutable
 @ThreadSafe
-public abstract class ResolvedToolchainContext implements ToolchainContext {
+public abstract class ResolvedToolchainContext
+    implements ResolvedToolchainsDataInterface<ToolchainInfo> {
 
   /**
    * Finishes preparing the {@link ResolvedToolchainContext} by finding the specific toolchain
@@ -114,12 +115,6 @@
         ImmutableSet.copyOf(toolchainTargets));
   }
 
-  /** Returns a description of the target being used, for error messaging. */
-  public abstract String targetDescription();
-
-  /** Sets the map from requested {@link Label} to toolchain type provider. */
-  public abstract ImmutableMap<Label, ToolchainTypeInfo> requestedToolchainTypeLabels();
-
   public abstract ImmutableMap<ToolchainTypeInfo, ToolchainInfo> toolchains();
 
   /** Returns the template variables that these toolchains provide. */
@@ -135,6 +130,7 @@
    * toolchains after Automatic Exec Groups are enabled. In that case please use {@code
    * RuleContext.getToolchainInfo(toolchainTypeLabel)}.
    */
+  @Override
   @Nullable
   public ToolchainInfo forToolchainType(Label toolchainTypeLabel) {
     ToolchainTypeInfo toolchainTypeInfo = requestedToolchainTypeLabels().get(toolchainTypeLabel);
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ResolvedToolchainData.java b/src/main/java/com/google/devtools/build/lib/analysis/ResolvedToolchainData.java
new file mode 100644
index 0000000..33bcbea
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ResolvedToolchainData.java
@@ -0,0 +1,24 @@
+// Copyright 2024 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;
+
+import net.starlark.java.eval.StarlarkValue;
+
+/**
+ * Common interface for {@link ToolchainInfo} and {@link
+ * AspectBaseTargetResolvedToolchainContext.ToolchainAspectsProviders} used to provide the resolved
+ * toolchain data in starlark through `ctx.toolchains` and `ctx.rule.toolchains` respectively.
+ */
+public interface ResolvedToolchainData extends StarlarkValue {}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ResolvedToolchainsDataInterface.java b/src/main/java/com/google/devtools/build/lib/analysis/ResolvedToolchainsDataInterface.java
new file mode 100644
index 0000000..4919356
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ResolvedToolchainsDataInterface.java
@@ -0,0 +1,44 @@
+// Copyright 2024 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;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.analysis.platform.ToolchainTypeInfo;
+import com.google.devtools.build.lib.cmdline.Label;
+import javax.annotation.Nullable;
+
+/**
+ * Interface for resolved toolchains data.
+ *
+ * <p>This interface is used to provide toolchain data to Starlark. This data can be the {@link
+ * ToolchainInfo} provider as in {@link ResolvedToolchainContext} for the aspect/rule own
+ * toolchains, or it can be collection of aspects providers evaluated on the aspect's base target's
+ * toolchains as in {@link AspectBaseTargetResolvedToolchainContext}.
+ */
+public interface ResolvedToolchainsDataInterface<T extends ResolvedToolchainData>
+    extends ToolchainContext {
+  /** Returns a description of the target being used, for error messaging. */
+  public String targetDescription();
+
+  /** Returns the map from requested {@link Label} to toolchain type provider. */
+  public ImmutableMap<Label, ToolchainTypeInfo> requestedToolchainTypeLabels();
+
+  /**
+   * Returns the toolchain data for the given type, or {@code null} if the toolchain type was not
+   * required in this context.
+   */
+  @Nullable
+  public T forToolchainType(Label toolchainTypeLabel);
+}
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 33616df..9bb260a 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
@@ -1070,10 +1070,15 @@
   }
 
   public boolean useAutoExecGroups() {
-    if (attributes().has("$use_auto_exec_groups")) {
-      return (boolean) attributes().get("$use_auto_exec_groups", Type.BOOLEAN);
+    return usesAutoExecGroups(attributes(), getConfiguration());
+  }
+
+  protected static boolean usesAutoExecGroups(
+      AttributeMap attributes, BuildConfigurationValue configuration) {
+    if (attributes.has("$use_auto_exec_groups")) {
+      return attributes.get("$use_auto_exec_groups", Type.BOOLEAN);
     } else {
-      return getConfiguration().useAutoExecGroups();
+      return configuration.useAutoExecGroups();
     }
   }
 
@@ -1459,6 +1464,8 @@
     private Mutability mutability;
     private NestedSet<PackageGroupContents> visibility;
     private ToolchainCollection<ResolvedToolchainContext> toolchainContexts;
+    private ToolchainCollection<AspectBaseTargetResolvedToolchainContext>
+        baseTargetToolchainContexts;
     private ExecGroupCollection.Builder execGroupCollectionBuilder;
     private ImmutableMap<String, String> rawExecProperties;
 
@@ -1521,7 +1528,8 @@
       if (aspects.isEmpty()) {
         return RuleContext.create(this, ruleAttributes, targetMap, execGroupCollection);
       } else {
-        return AspectContext.create(this, ruleAttributes, targetMap, execGroupCollection);
+        return AspectContext.create(
+            this, ruleAttributes, targetMap, execGroupCollection, baseTargetToolchainContexts);
       }
     }
 
@@ -1639,6 +1647,20 @@
       return this;
     }
 
+    /**
+     * Sets the collection of {@link AspectBaseTargetResolvedToolchainContext}s available to this
+     * aspect from its base target.
+     */
+    @CanIgnoreReturnValue
+    public Builder setBaseTargetToolchainContexts(
+        ToolchainCollection<AspectBaseTargetResolvedToolchainContext> baseTargetToolchainContexts) {
+      Preconditions.checkState(
+          this.baseTargetToolchainContexts == null,
+          "baseTargetToolchainContexts has already been set for this Builder");
+      this.baseTargetToolchainContexts = baseTargetToolchainContexts;
+      return this;
+    }
+
     @CanIgnoreReturnValue
     public Builder setExecGroupCollectionBuilder(
         ExecGroupCollection.Builder execGroupCollectionBuilder) {
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/configuredtargets/MergedConfiguredTarget.java b/src/main/java/com/google/devtools/build/lib/analysis/configuredtargets/MergedConfiguredTarget.java
index 76b5430..4f64db4 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/configuredtargets/MergedConfiguredTarget.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/configuredtargets/MergedConfiguredTarget.java
@@ -168,8 +168,7 @@
     TransitiveInfoProviderMapBuilder nonBaseProviders = new TransitiveInfoProviderMapBuilder();
 
     // Merge output group providers.
-    OutputGroupInfo mergedOutputGroupInfo =
-        OutputGroupInfo.merge(getAllOutputGroupProviders(base, aspects));
+    OutputGroupInfo mergedOutputGroupInfo = mergeOutputGroupProviders(base, aspects);
     if (mergedOutputGroupInfo != null) {
       nonBaseProviders.put(mergedOutputGroupInfo);
     }
@@ -237,12 +236,16 @@
     return new MergedConfiguredTarget(base, aspects, nonBaseProviders.build());
   }
 
-  private static ImmutableList<OutputGroupInfo> getAllOutputGroupProviders(
-      ConfiguredTarget base, Iterable<ConfiguredAspect> aspects) {
-    OutputGroupInfo baseProvider = OutputGroupInfo.get(base);
+  private static OutputGroupInfo mergeOutputGroupProviders(
+      @Nullable ConfiguredTarget base, Iterable<ConfiguredAspect> aspects)
+      throws DuplicateException {
     ImmutableList.Builder<OutputGroupInfo> providers = ImmutableList.builder();
-    if (baseProvider != null) {
-      providers.add(baseProvider);
+
+    if (base != null) {
+      OutputGroupInfo baseProvider = OutputGroupInfo.get(base);
+      if (baseProvider != null) {
+        providers.add(baseProvider);
+      }
     }
 
     for (ConfiguredAspect configuredAspect : aspects) {
@@ -252,7 +255,7 @@
       }
       providers.add(aspectProvider);
     }
-    return providers.build();
+    return OutputGroupInfo.merge(providers.build());
   }
 
   private static ImmutableList<NestedSet<AnalysisFailure>> getAnalysisFailures(
@@ -316,4 +319,43 @@
   public ConfiguredTarget unwrapIfMerged() {
     return base.unwrapIfMerged();
   }
+
+  /** Returns only the providers from the aspects. */
+  public TransitiveInfoProviderMap getAspectsProviders() throws DuplicateException {
+    TransitiveInfoProviderMapBuilder aspectsProviders = new TransitiveInfoProviderMapBuilder();
+
+    // Merge output group providers of aspects only. Filtering the base target output
+    // groups from `nonBaseProviders` does not work because some groups like
+    // `OutputGroupInfo#Validation` contains artifacts from both base
+    // target and aspects.
+    var outputGroups = mergeOutputGroupProviders(/* base= */ null, aspects);
+    if (outputGroups != null) {
+      aspectsProviders.put(outputGroups);
+    }
+
+    // Merge other aspects providers.
+    for (int i = 0; i < nonBaseProviders.getProviderCount(); ++i) {
+      Object providerKey = nonBaseProviders.getProviderKeyAt(i);
+      if (OutputGroupInfo.STARLARK_CONSTRUCTOR.getKey().equals(providerKey)
+          || AnalysisFailureInfo.STARLARK_CONSTRUCTOR.getKey().equals(providerKey)
+          || ExtraActionArtifactsProvider.class.equals(providerKey)
+          || RequiredConfigFragmentsProvider.class.equals(providerKey)) {
+        continue;
+      }
+
+      if (providerKey instanceof Class<?>) {
+        @SuppressWarnings("unchecked")
+        Class<? extends TransitiveInfoProvider> providerClass =
+            (Class<? extends TransitiveInfoProvider>) providerKey;
+        aspectsProviders.put(
+            providerClass, (TransitiveInfoProvider) nonBaseProviders.getProviderInstanceAt(i));
+      } else if (providerKey instanceof String legacyId) {
+        aspectsProviders.put(legacyId, nonBaseProviders.getProviderInstanceAt(i));
+      } else if (providerKey instanceof Provider.Key key) {
+        aspectsProviders.put((Info) nonBaseProviders.getProviderInstanceAt(i));
+      }
+    }
+
+    return aspectsProviders.build();
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/platform/BUILD b/src/main/java/com/google/devtools/build/lib/analysis/platform/BUILD
index ca9668e..6df3cdb 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/platform/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/analysis/platform/BUILD
@@ -25,6 +25,7 @@
     ),
     deps = [
         "//src/main/java/com/google/devtools/build/lib/analysis:config/config_matching_provider",
+        "//src/main/java/com/google/devtools/build/lib/analysis:resolved_toolchain_data",
         "//src/main/java/com/google/devtools/build/lib/analysis:transitive_info_provider",
         "//src/main/java/com/google/devtools/build/lib/cmdline",
         "//src/main/java/com/google/devtools/build/lib/concurrent",
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/platform/ToolchainInfo.java b/src/main/java/com/google/devtools/build/lib/analysis/platform/ToolchainInfo.java
index 9f88d0e..7993b3a 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/platform/ToolchainInfo.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/platform/ToolchainInfo.java
@@ -17,6 +17,7 @@
 import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSortedMap;
+import com.google.devtools.build.lib.analysis.ResolvedToolchainData;
 import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
 import com.google.devtools.build.lib.packages.Attribute;
 import com.google.devtools.build.lib.packages.BuiltinProvider;
@@ -38,7 +39,8 @@
  * additional fields to Starlark code. Also, these are not disjoint.
  */
 @Immutable
-public final class ToolchainInfo extends NativeInfo implements ToolchainInfoApi {
+public final class ToolchainInfo extends NativeInfo
+    implements ToolchainInfoApi, ResolvedToolchainData {
 
   /** Name used in Starlark for accessing this provider. */
   public static final String STARLARK_NAME = "ToolchainInfo";
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkAttributesCollection.java b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkAttributesCollection.java
index 8166715..609fccb 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkAttributesCollection.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkAttributesCollection.java
@@ -16,6 +16,7 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.analysis.AliasProvider;
+import com.google.devtools.build.lib.analysis.AspectContext;
 import com.google.devtools.build.lib.analysis.FilesToRunProvider;
 import com.google.devtools.build.lib.analysis.PrerequisiteArtifacts;
 import com.google.devtools.build.lib.analysis.PrerequisitesCollection;
@@ -29,6 +30,8 @@
 import com.google.devtools.build.lib.packages.Type;
 import com.google.devtools.build.lib.packages.Type.LabelClass;
 import com.google.devtools.build.lib.starlarkbuildapi.StarlarkAttributesCollectionApi;
+import com.google.devtools.build.lib.starlarkbuildapi.platform.ExecGroupCollectionApi;
+import com.google.devtools.build.lib.starlarkbuildapi.platform.ToolchainContextApi;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -116,6 +119,36 @@
     return ruleClassName;
   }
 
+  @Override
+  public ToolchainContextApi toolchains() throws EvalException {
+    checkMutable("toolchains");
+    if (((AspectContext) starlarkRuleContext.getRuleContext()).getBaseTargetToolchainContexts()
+        == null) {
+      return StarlarkToolchainContext.TOOLCHAINS_NOT_VALID;
+    }
+    var aspectContext = ((AspectContext) starlarkRuleContext.getRuleContext());
+
+    return StarlarkToolchainContext.create(
+        aspectContext
+            .getBaseTargetToolchainContexts()
+            .getDefaultToolchainContext()
+            .targetDescription(),
+        /* resolveToolchainDataFunc= */ aspectContext::getToolchainTarget,
+        /* resolvedToolchainTypeLabels= */ aspectContext.getRequestedToolchainTypesLabels());
+  }
+
+  @Override
+  public ExecGroupCollectionApi execGroups() throws EvalException {
+    checkMutable("exec_groups");
+    if (((AspectContext) starlarkRuleContext.getRuleContext()).getBaseTargetToolchainContexts()
+        == null) {
+      return StarlarkExecGroupCollection.EXEC_GRPOUP_COLLECTION_NOT_VALID;
+    }
+    // Create a thin wrapper around the toolchain collection, to expose the Starlark API.
+    return StarlarkExecGroupCollection.create(
+        ((AspectContext) starlarkRuleContext.getRuleContext()).getBaseTargetToolchainContexts());
+  }
+
   public ImmutableMap<Artifact, FilesToRunProvider> getExecutableRunfilesMap() {
     return executableRunfilesMap;
   }
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkExecGroupCollection.java b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkExecGroupCollection.java
index f75eb9e..8c63d98 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkExecGroupCollection.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkExecGroupCollection.java
@@ -19,6 +19,7 @@
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableMap;
 import com.google.devtools.build.lib.analysis.ResolvedToolchainContext;
+import com.google.devtools.build.lib.analysis.ResolvedToolchainsDataInterface;
 import com.google.devtools.build.lib.analysis.ToolchainCollection;
 import com.google.devtools.build.lib.starlarkbuildapi.platform.ExecGroupCollectionApi;
 import com.google.devtools.build.lib.starlarkbuildapi.platform.ToolchainContextApi;
@@ -39,18 +40,37 @@
 public abstract class StarlarkExecGroupCollection implements ExecGroupCollectionApi {
 
   /**
+   * Empty collection of exec groups to be used when exec groups are not valid in the current
+   * context.
+   */
+  public static final ExecGroupCollectionApi EXEC_GRPOUP_COLLECTION_NOT_VALID =
+      new ExecGroupCollectionApi() {
+        @Override
+        public boolean containsKey(StarlarkSemantics semantics, Object key) {
+          return false;
+        }
+
+        @Override
+        public Object getIndex(StarlarkSemantics semantics, Object key) throws EvalException {
+          throw Starlark.errorf("exec_groups are not valid in this context");
+        }
+      };
+
+  /**
    * Returns a new {@link StarlarkExecGroupCollection} backed by the given {@code
    * toolchainCollection}.
    */
   public static StarlarkExecGroupCollection create(
-      ToolchainCollection<ResolvedToolchainContext> toolchainCollection) {
+      ToolchainCollection<? extends ResolvedToolchainsDataInterface<?>> toolchainCollection) {
     return new AutoValue_StarlarkExecGroupCollection(toolchainCollection);
   }
 
-  protected abstract ToolchainCollection<ResolvedToolchainContext> toolchainCollection();
+  protected abstract ToolchainCollection<? extends ResolvedToolchainsDataInterface<?>>
+      toolchainCollection();
 
   @VisibleForTesting
-  public ImmutableMap<String, ResolvedToolchainContext> getToolchainCollectionForTesting() {
+  public ImmutableMap<String, ? extends ResolvedToolchainsDataInterface<?>>
+      getToolchainCollectionForTesting() {
     return toolchainCollection().getContextMap();
   }
 
@@ -82,8 +102,7 @@
           String.join(", ", getScrubbedExecGroups()));
     }
 
-    ResolvedToolchainContext toolchainContext =
-        toolchainCollection().getToolchainContext(execGroup);
+    var toolchainContext = toolchainCollection().getToolchainContext(execGroup);
     if (toolchainContext == null) {
       return new AutoValue_StarlarkExecGroupCollection_StarlarkExecGroupContext(
           StarlarkToolchainContext.TOOLCHAINS_NOT_VALID);
@@ -92,7 +111,7 @@
     ToolchainContextApi starlarkToolchainContext =
         StarlarkToolchainContext.create(
             /* targetDescription= */ toolchainContext.targetDescription(),
-            /* resolveToolchainInfoFunc= */ toolchainContext::forToolchainType,
+            /* resolveToolchainDataFunc= */ toolchainContext::forToolchainType,
             /* resolvedToolchainTypeLabels= */ toolchainContext
                 .requestedToolchainTypeLabels()
                 .keySet());
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkToolchainContext.java b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkToolchainContext.java
index 6463a73..065075c 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkToolchainContext.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkToolchainContext.java
@@ -18,7 +18,7 @@
 import com.google.auto.value.AutoValue;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableSet;
-import com.google.devtools.build.lib.analysis.platform.ToolchainInfo;
+import com.google.devtools.build.lib.analysis.ResolvedToolchainData;
 import com.google.devtools.build.lib.analysis.platform.ToolchainTypeInfo;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
@@ -56,19 +56,19 @@
 
   public static ToolchainContextApi create(
       String targetDescription,
-      Function<Label, ToolchainInfo> resolveToolchainInfoFunc,
+      Function<Label, ResolvedToolchainData> resolveToolchainDataFunc,
       ImmutableSet<Label> resolvedToolchainTypeLabels) {
     Preconditions.checkNotNull(targetDescription);
-    Preconditions.checkNotNull(resolveToolchainInfoFunc);
+    Preconditions.checkNotNull(resolveToolchainDataFunc);
     Preconditions.checkNotNull(resolvedToolchainTypeLabels);
 
     return new AutoValue_StarlarkToolchainContext(
-        targetDescription, resolveToolchainInfoFunc, resolvedToolchainTypeLabels);
+        targetDescription, resolveToolchainDataFunc, resolvedToolchainTypeLabels);
   }
 
   protected abstract String targetDescription();
 
-  protected abstract Function<Label, ToolchainInfo> resolveToolchainInfoFunc();
+  protected abstract Function<Label, ResolvedToolchainData> resolveToolchainFunc();
 
   protected abstract ImmutableSet<Label> resolvedToolchainTypeLabels();
 
@@ -120,7 +120,7 @@
           toolchainTypeLabel,
           resolvedToolchainTypeLabels().stream().map(Label::toString).collect(joining(", ")));
     }
-    ToolchainInfo toolchainInfo = resolveToolchainInfoFunc().apply(toolchainTypeLabel);
+    var toolchainInfo = resolveToolchainFunc().apply(toolchainTypeLabel);
     if (toolchainInfo == null) {
       return Starlark.NONE;
     }