Add support for required aspects in command line aspects

This CL enables the use of `requires` attribute for command line aspects. A top level aspect can now require a list of aspects in its definition to be executed before it and pass their produced providers to it on the targets that satisfy their required providers.

PiperOrigin-RevId: 387763617
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkAttrModule.java b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkAttrModule.java
index 03b5284..254bb74 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkAttrModule.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkAttrModule.java
@@ -268,7 +268,9 @@
             /** inheritedRequiredProviders= */
             ImmutableList.of(),
             /** inheritedAttributeAspects= */
-            ImmutableList.of());
+            ImmutableList.of(),
+            /** allowAspectsParameters= */
+            true);
       }
     }
 
diff --git a/src/main/java/com/google/devtools/build/lib/packages/StarlarkAspect.java b/src/main/java/com/google/devtools/build/lib/packages/StarlarkAspect.java
index b22f705..e866bc7 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/StarlarkAspect.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/StarlarkAspect.java
@@ -40,13 +40,17 @@
    *     parent aspects
    * @param inheritedAttributeAspects is the list of attribute aspects inherited from the aspect
    *     parent aspects
-   * @throws EvalException if this aspect cannot be successfully applied to the given attribute
+   * @param allowAspectsParameters if false an error will be reported if any aspect in the chain of
+   *     required aspects has parameters. This is needed for top-level aspects that do not allow
+   *     parameters at the moment.
+   * @throws EvalException if this aspect cannot be successfully added to the aspects list.
    */
   void attachToAspectsList(
       String baseAspectName,
       AspectsListBuilder aspectsListBuilder,
       ImmutableList<ImmutableSet<StarlarkProviderIdentifier>> inheritedRequiredProviders,
-      ImmutableList<String> inheritedAttributeAspects)
+      ImmutableList<String> inheritedAttributeAspects,
+      boolean allowAspectsParameters)
       throws EvalException;
 
   /** Returns the aspect class for this aspect. */
diff --git a/src/main/java/com/google/devtools/build/lib/packages/StarlarkDefinedAspect.java b/src/main/java/com/google/devtools/build/lib/packages/StarlarkDefinedAspect.java
index e006856..7a9a7d7 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/StarlarkDefinedAspect.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/StarlarkDefinedAspect.java
@@ -272,13 +272,19 @@
       String baseAspectName,
       AspectsListBuilder aspectsList,
       ImmutableList<ImmutableSet<StarlarkProviderIdentifier>> inheritedRequiredProviders,
-      ImmutableList<String> inheritedAttributeAspects)
+      ImmutableList<String> inheritedAttributeAspects,
+      boolean allowAspectsParameters)
       throws EvalException {
+
     if (!this.isExported()) {
       throw Starlark.errorf(
           "Aspects should be top-level values in extension files that define them.");
     }
 
+    if (!allowAspectsParameters && !this.paramAttributes.isEmpty()) {
+      throw Starlark.errorf("Cannot use parameterized aspect %s at the top level.", this.getName());
+    }
+
     if (!this.requiredAspects.isEmpty()) {
       ImmutableList.Builder<ImmutableSet<StarlarkProviderIdentifier>>
           requiredAspectInheritedRequiredProviders = ImmutableList.builder();
@@ -306,7 +312,8 @@
             this.getName(),
             aspectsList,
             requiredAspectInheritedRequiredProviders.build(),
-            requiredAspectInheritedAttributeAspects.build());
+            requiredAspectInheritedAttributeAspects.build(),
+            allowAspectsParameters);
       }
     }
 
diff --git a/src/main/java/com/google/devtools/build/lib/packages/StarlarkNativeAspect.java b/src/main/java/com/google/devtools/build/lib/packages/StarlarkNativeAspect.java
index 267cd2b..3a495e0 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/StarlarkNativeAspect.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/StarlarkNativeAspect.java
@@ -20,6 +20,7 @@
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
 import net.starlark.java.eval.EvalException;
 import net.starlark.java.eval.Printer;
+import net.starlark.java.eval.Starlark;
 
 /** A natively-defined aspect that is may be referenced by Starlark attribute definitions. */
 public abstract class StarlarkNativeAspect extends NativeAspectClass implements StarlarkAspect {
@@ -36,8 +37,14 @@
       String baseAspectName,
       AspectsListBuilder aspectsList,
       ImmutableList<ImmutableSet<StarlarkProviderIdentifier>> inheritedRequiredProviders,
-      ImmutableList<String> inheritedAttributeAspects)
+      ImmutableList<String> inheritedAttributeAspects,
+      boolean allowAspectsParameters)
       throws EvalException {
+
+    if (!allowAspectsParameters && !this.getParamAttributes().isEmpty()) {
+      throw Starlark.errorf("Cannot use parameterized aspect %s at the top level.", this.getName());
+    }
+
     aspectsList.addAspect(
         this, baseAspectName, inheritedRequiredProviders, inheritedAttributeAspects);
   }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/BuildTopLevelAspectsDetailsFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/BuildTopLevelAspectsDetailsFunction.java
index 3c2387e..17b9cbf 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/BuildTopLevelAspectsDetailsFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/BuildTopLevelAspectsDetailsFunction.java
@@ -136,7 +136,9 @@
               /** inheritedRequiredProviders= */
               ImmutableList.of(),
               /** inheritedAttributeAspects= */
-              ImmutableList.of());
+              ImmutableList.of(),
+              /** allowAspectsParameters= */
+              false);
         } catch (EvalException e) {
           env.getListener().handle(Event.error(e.getMessage()));
           throw new BuildTopLevelAspectsDetailsFunctionException(
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/TestAspects.java b/src/test/java/com/google/devtools/build/lib/analysis/util/TestAspects.java
index 56d26fc..bd01c82 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/util/TestAspects.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/TestAspects.java
@@ -260,6 +260,8 @@
       new SimpleStarlarkNativeAspect();
   public static final ParametrizedAspectWithProvider
       PARAMETRIZED_STARLARK_NATIVE_ASPECT_WITH_PROVIDER = new ParametrizedAspectWithProvider();
+  public static final StarlarkNativeAspectWithProvider STARLARK_NATIVE_ASPECT_WITH_PROVIDER =
+      new StarlarkNativeAspectWithProvider();
 
   private static final AspectDefinition SIMPLE_ASPECT_DEFINITION =
       new AspectDefinition.Builder(SIMPLE_ASPECT).build();
@@ -486,6 +488,28 @@
     }
   }
 
+  /** A native aspect exposed to Starlark and advertises a simple provider. */
+  public static class StarlarkNativeAspectWithProvider extends StarlarkNativeAspect
+      implements ConfiguredAspectFactory {
+
+    @Override
+    public AspectDefinition getDefinition(AspectParameters aspectParameters) {
+      AspectDefinition.Builder builder =
+          new AspectDefinition.Builder(STARLARK_NATIVE_ASPECT_WITH_PROVIDER);
+      return builder.build();
+    }
+
+    @Override
+    public ConfiguredAspect create(
+        ConfiguredTargetAndData ctadBase,
+        RuleContext ruleContext,
+        AspectParameters parameters,
+        String toolsRepository)
+        throws ActionConflictException, InterruptedException {
+      return new ConfiguredAspect.Builder(ruleContext).addProvider(new FooProvider()).build();
+    }
+  }
+
   /**
    * An aspect that has a definition depending on parameters provided by originating rule and
    * advertises a simple provider.
@@ -527,6 +551,11 @@
                 .build();
           };
     }
+
+    @Override
+    public ImmutableSet<String> getParamAttributes() {
+      return ImmutableSet.of("aspect_attr");
+    }
   }
 
   /**
diff --git a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkDefinedAspectsTest.java b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkDefinedAspectsTest.java
index 375e8a8..9ea2f73 100644
--- a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkDefinedAspectsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkDefinedAspectsTest.java
@@ -29,17 +29,20 @@
 import com.google.devtools.build.lib.analysis.AnalysisResult;
 import com.google.devtools.build.lib.analysis.AspectValue;
 import com.google.devtools.build.lib.analysis.ConfiguredAspect;
+import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
 import com.google.devtools.build.lib.analysis.ConfiguredTarget;
 import com.google.devtools.build.lib.analysis.OutputGroupInfo;
 import com.google.devtools.build.lib.analysis.ViewCreationFailedException;
 import com.google.devtools.build.lib.analysis.config.HostTransition;
 import com.google.devtools.build.lib.analysis.config.transitions.NoTransition;
 import com.google.devtools.build.lib.analysis.util.AnalysisTestCase;
+import com.google.devtools.build.lib.analysis.util.TestAspects;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.cmdline.TargetParsingException;
 import com.google.devtools.build.lib.collect.nestedset.Depset;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.packages.AspectDefinition;
+import com.google.devtools.build.lib.packages.RequiredProviders;
 import com.google.devtools.build.lib.packages.StarlarkAspectClass;
 import com.google.devtools.build.lib.packages.StarlarkProvider;
 import com.google.devtools.build.lib.packages.StructImpl;
@@ -49,6 +52,7 @@
 import com.google.devtools.build.lib.server.FailureDetails.Analysis.Code;
 import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
 import com.google.devtools.build.lib.skyframe.AspectValueKey.AspectKey;
+import com.google.devtools.build.lib.testutil.TestRuleClassProvider;
 import com.google.devtools.build.lib.vfs.FileSystemUtils;
 import com.google.devtools.build.lib.vfs.Path;
 import java.util.ArrayList;
@@ -3883,11 +3887,9 @@
     AnalysisResult analysisResult = update("//test:main");
 
     // aspect_a should only run on target_with_prov_a, aspect_b should only run on
-    // target_with_prov_b and aspect_c
-    // should only run on target_with_prov_c.
+    // target_with_prov_b and aspect_c should only run on target_with_prov_c.
     // aspect_c will reach target target_with_prov_c because it inherits the required_providers of
-    // aspect_c otherwise it
-    // would have stopped propagating after target_with_prov_b
+    // aspect_b otherwise it would have stopped propagating after target_with_prov_b.
     ConfiguredTarget configuredTarget =
         Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
     StarlarkProvider.Key collectorProv =
@@ -5453,6 +5455,1155 @@
             "aspect a2 on target //test:main sees a3p = a3p_val");
   }
 
+  @Test
+  public void testTopLevelAspectRequiresAspect_stackOfRequiredAspects() throws Exception {
+    useConfiguration("--experimental_required_aspects");
+    scratch.file(
+        "test/defs.bzl",
+        "def _impl(target, ctx):",
+        "   return []",
+        "aspect_c = aspect(implementation = _impl)",
+        "aspect_b = aspect(implementation = _impl, requires = [aspect_c])",
+        "aspect_a = aspect(implementation = _impl, requires = [aspect_b])");
+    scratch.file("test/BUILD", "cc_binary(name = 'main_target')");
+
+    AnalysisResult analysisResult =
+        update(ImmutableList.of("test/defs.bzl%aspect_a"), "//test:main_target");
+
+    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
+    assertThat(configuredAspects).hasSize(3);
+    assertThat(getConfiguredAspect(configuredAspects, "aspect_a")).isNotNull();
+    assertThat(getConfiguredAspect(configuredAspects, "aspect_b")).isNotNull();
+    assertThat(getConfiguredAspect(configuredAspects, "aspect_c")).isNotNull();
+  }
+
+  @Test
+  public void testTopLevelAspectRequiresAspect_aspectRequiredByMultipleAspects() throws Exception {
+    useConfiguration("--experimental_required_aspects");
+    scratch.file(
+        "test/defs.bzl",
+        "def _impl(target, ctx):",
+        "   return []",
+        "aspect_c = aspect(implementation = _impl)",
+        "aspect_b = aspect(implementation = _impl, requires = [aspect_c])",
+        "aspect_a = aspect(implementation = _impl, requires = [aspect_c])");
+    scratch.file("test/BUILD", "cc_binary(name = 'main_target')");
+
+    AnalysisResult analysisResult =
+        update(
+            ImmutableList.of("test/defs.bzl%aspect_a", "test/defs.bzl%aspect_b"),
+            "//test:main_target");
+
+    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
+    assertThat(configuredAspects).hasSize(3);
+    assertThat(getConfiguredAspect(configuredAspects, "aspect_a")).isNotNull();
+    assertThat(getConfiguredAspect(configuredAspects, "aspect_b")).isNotNull();
+    assertThat(getConfiguredAspect(configuredAspects, "aspect_c")).isNotNull();
+  }
+
+  @Test
+  public void testTopLevelAspectRequiresAspect_aspectRequiredByMultipleAspects2() throws Exception {
+    useConfiguration("--experimental_required_aspects");
+    scratch.file(
+        "test/defs.bzl",
+        "def _impl(target, ctx):",
+        "   return []",
+        "aspect_d = aspect(implementation = _impl)",
+        "aspect_c = aspect(implementation = _impl, requires = [aspect_d])",
+        "aspect_b = aspect(implementation = _impl, requires = [aspect_d])",
+        "aspect_a = aspect(implementation = _impl, requires = [aspect_b, aspect_c])");
+    scratch.file("test/BUILD", "cc_binary(name = 'main_target')");
+
+    AnalysisResult analysisResult =
+        update(ImmutableList.of("test/defs.bzl%aspect_a"), "//test:main_target");
+
+    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
+    assertThat(configuredAspects).hasSize(4);
+    assertThat(getConfiguredAspect(configuredAspects, "aspect_a")).isNotNull();
+    assertThat(getConfiguredAspect(configuredAspects, "aspect_b")).isNotNull();
+    assertThat(getConfiguredAspect(configuredAspects, "aspect_c")).isNotNull();
+    assertThat(getConfiguredAspect(configuredAspects, "aspect_d")).isNotNull();
+  }
+
+  @Test
+  public void testTopLevelAspectRequiresAspect_requireExistingAspect_passed() throws Exception {
+    useConfiguration("--experimental_required_aspects");
+    scratch.file(
+        "test/defs.bzl",
+        "def _impl(target, ctx):",
+        "   return []",
+        "aspect_b = aspect(implementation = _impl)",
+        "aspect_a = aspect(implementation = _impl, requires = [aspect_b])");
+    scratch.file("test/BUILD", "cc_binary(name = 'main_target')");
+
+    AnalysisResult analysisResult =
+        update(
+            ImmutableList.of("test/defs.bzl%aspect_b", "test/defs.bzl%aspect_a"),
+            "//test:main_target");
+
+    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
+    assertThat(configuredAspects).hasSize(2);
+    assertThat(getConfiguredAspect(configuredAspects, "aspect_a")).isNotNull();
+    assertThat(getConfiguredAspect(configuredAspects, "aspect_b")).isNotNull();
+  }
+
+  @Test
+  public void testTopLevelAspectRequiresAspect_requireExistingAspect_failed() throws Exception {
+    useConfiguration("--experimental_required_aspects");
+    scratch.file(
+        "test/defs.bzl",
+        "def _impl(target, ctx):",
+        "   return []",
+        "aspect_b = aspect(implementation = _impl)",
+        "aspect_a = aspect(implementation = _impl, requires = [aspect_b])");
+    scratch.file("test/BUILD", "cc_binary(name = 'main_target')");
+
+    AssertionError expected =
+        assertThrows(
+            AssertionError.class,
+            () ->
+                update(
+                    ImmutableList.of("test/defs.bzl%aspect_a", "test/defs.bzl%aspect_b"),
+                    "//test:main_target"));
+    assertThat(expected)
+        .hasMessageThat()
+        .contains(
+            "aspect //test:defs.bzl%aspect_b was added before as a required aspect of aspect"
+                + " //test:defs.bzl%aspect_a");
+  }
+
+  @Test
+  public void testTopLevelAspectRequiresAspect_inheritDefaultValues() throws Exception {
+    useConfiguration("--experimental_required_aspects");
+    scratch.file(
+        "test/defs.bzl",
+        "def _impl(target, ctx):",
+        "   return []",
+        "aspect_b = aspect(implementation = _impl)",
+        "aspect_a = aspect(implementation = _impl, requires = [aspect_b])");
+    scratch.file("test/BUILD", "cc_binary(name = 'main_target')");
+
+    AnalysisResult analysisResult =
+        update(ImmutableList.of("test/defs.bzl%aspect_a"), "//test:main_target");
+
+    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
+    assertThat(configuredAspects).hasSize(2);
+
+    // aspect_b inherits the required providers and propagation attributes from aspect_a
+    AspectKey aspectB = getAspectKey(configuredAspects, "aspect_b");
+    assertThat(aspectB).isNotNull();
+    assertThat(aspectB.getInheritedRequiredProviders())
+        .isEqualTo(RequiredProviders.acceptAnyBuilder().build());
+    assertThat(aspectB.getInheritedAttributeAspects()).isEmpty();
+
+    AspectKey aspectA = getAspectKey(configuredAspects, "aspect_a");
+    assertThat(aspectA).isNotNull();
+    assertThat(aspectA.getInheritedRequiredProviders()).isNull();
+    assertThat(aspectA.getInheritedAttributeAspects()).isEmpty();
+  }
+
+  @Test
+  public void testTopLevelAspectRequiresAspect_inheritAttrAspectsFromSingleAspect()
+      throws Exception {
+    useConfiguration("--experimental_required_aspects");
+    scratch.file(
+        "test/defs.bzl",
+        "def _impl(target, ctx):",
+        "   return []",
+        "aspect_b = aspect(implementation = _impl)",
+        "aspect_a = aspect(implementation = _impl,",
+        "                  attr_aspects = ['deps'],",
+        "                  requires = [aspect_b])");
+    scratch.file("test/BUILD", "cc_binary(name = 'main_target')");
+
+    AnalysisResult analysisResult =
+        update(ImmutableList.of("test/defs.bzl%aspect_a"), "//test:main_target");
+
+    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
+    AspectKey aspectB = getAspectKey(configuredAspects, "aspect_b");
+    assertThat(aspectB).isNotNull();
+    assertThat(aspectB.getInheritedAttributeAspects()).containsExactly("deps");
+  }
+
+  @Test
+  public void testTopLevelAspectRequiresAspect_inheritRequiredProvidersFromSingleAspect()
+      throws Exception {
+    useConfiguration("--experimental_required_aspects");
+    scratch.file(
+        "test/defs.bzl",
+        "cc = provider()",
+        "def _impl(target, ctx):",
+        "   return []",
+        "aspect_b = aspect(implementation = _impl)",
+        "aspect_a = aspect(implementation = _impl,",
+        "                  required_providers=[['java', cc], ['python']],",
+        "                  requires = [aspect_b])");
+    scratch.file("test/BUILD", "cc_binary(name = 'main_target')");
+
+    AnalysisResult analysisResult =
+        update(ImmutableList.of("test/defs.bzl%aspect_a"), "//test:main_target");
+
+    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
+    AspectKey aspectB = getAspectKey(configuredAspects, "aspect_b");
+    assertThat(aspectB).isNotNull();
+    assertThat(aspectB.getInheritedRequiredProviders().getDescription())
+        .isEqualTo("['java', 'cc'] or 'python'");
+  }
+
+  @Test
+  public void testTopLevelAspectRequiresExistingAspect_inheritAttrAspectsFromSingleAspect()
+      throws Exception {
+    useConfiguration("--experimental_required_aspects");
+    scratch.file(
+        "test/defs.bzl",
+        "def _impl(target, ctx):",
+        "   return []",
+        "aspect_b = aspect(implementation = _impl)",
+        "aspect_a = aspect(implementation = _impl,",
+        "                  attr_aspects = ['deps'],",
+        "                  requires = [aspect_b])");
+    scratch.file("test/BUILD", "cc_binary(name = 'main_target')");
+
+    AnalysisResult analysisResult =
+        update(
+            ImmutableList.of("test/defs.bzl%aspect_b", "test/defs.bzl%aspect_a"),
+            "//test:main_target");
+
+    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
+    AspectKey aspectB = getAspectKey(configuredAspects, "aspect_b");
+    assertThat(aspectB).isNotNull();
+    assertThat(aspectB.getInheritedAttributeAspects()).containsExactly("deps");
+  }
+
+  @Test
+  public void testTopLevelAspectRequiresExistingAspect_inheritRequiredProvidersFromSingleAspect()
+      throws Exception {
+    useConfiguration("--experimental_required_aspects");
+    scratch.file(
+        "test/defs.bzl",
+        "cc = provider()",
+        "def _impl(target, ctx):",
+        "   return []",
+        "aspect_b = aspect(implementation = _impl)",
+        "aspect_a = aspect(implementation = _impl,",
+        "                  required_providers=[['java', cc], ['python']],",
+        "                  requires = [aspect_b])");
+    scratch.file("test/BUILD", "cc_binary(name = 'main_target')");
+
+    AnalysisResult analysisResult =
+        update(
+            ImmutableList.of("test/defs.bzl%aspect_b", "test/defs.bzl%aspect_a"),
+            "//test:main_target");
+
+    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
+    AspectKey aspectB = getAspectKey(configuredAspects, "aspect_b");
+    assertThat(aspectB).isNotNull();
+    assertThat(aspectB.getInheritedRequiredProviders().getDescription())
+        .isEqualTo("['java', 'cc'] or 'python'");
+  }
+
+  @Test
+  public void testTopLevelAspectRequiresAspect_inheritAttrAspectsFromMultipleAspects()
+      throws Exception {
+    useConfiguration("--experimental_required_aspects");
+    scratch.file(
+        "test/defs.bzl",
+        "def _impl(target, ctx):",
+        "   return []",
+        "aspect_c = aspect(implementation = _impl)",
+        "aspect_b = aspect(implementation = _impl,",
+        "                  requires = [aspect_c],",
+        "                  attr_aspects = ['extra_deps'])",
+        "aspect_a = aspect(implementation = _impl,",
+        "                  requires = [aspect_c],",
+        "                  attr_aspects = ['deps'])");
+    scratch.file("test/BUILD", "cc_binary(name = 'main_target')");
+
+    AnalysisResult analysisResult =
+        update(
+            ImmutableList.of("test/defs.bzl%aspect_a", "test/defs.bzl%aspect_b"),
+            "//test:main_target");
+
+    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
+    AspectKey aspectC = getAspectKey(configuredAspects, "aspect_c");
+    assertThat(aspectC).isNotNull();
+    assertThat(aspectC.getInheritedAttributeAspects()).containsExactly("deps", "extra_deps");
+  }
+
+  @Test
+  public void testTopLevelAspectRequiresAspect_inheritRequiredProvidersFromMultipleAspects()
+      throws Exception {
+    useConfiguration("--experimental_required_aspects");
+    scratch.file(
+        "test/defs.bzl",
+        "cc = provider()",
+        "def _impl(target, ctx):",
+        "   return []",
+        "aspect_c = aspect(implementation = _impl)",
+        "aspect_b = aspect(implementation = _impl,",
+        "                  requires = [aspect_c],",
+        "                  required_providers=['go'])",
+        "aspect_a = aspect(implementation = _impl,",
+        "                  requires = [aspect_c],",
+        "                  required_providers=[['java', cc], ['python']])");
+    scratch.file("test/BUILD", "cc_binary(name = 'main_target')");
+
+    AnalysisResult analysisResult =
+        update(
+            ImmutableList.of("test/defs.bzl%aspect_a", "test/defs.bzl%aspect_b"),
+            "//test:main_target");
+
+    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
+    AspectKey aspectC = getAspectKey(configuredAspects, "aspect_c");
+    assertThat(aspectC).isNotNull();
+    assertThat(aspectC.getInheritedRequiredProviders().getDescription())
+        .isEqualTo("['java', 'cc'] or 'python' or 'go'");
+  }
+
+  @Test
+  public void testTopLevelAspectRequiresAspect_inheritAllAttrAspects() throws Exception {
+    useConfiguration("--experimental_required_aspects");
+    scratch.file(
+        "test/defs.bzl",
+        "cc = provider()",
+        "def _impl(target, ctx):",
+        "   return []",
+        "aspect_c = aspect(implementation = _impl)",
+        "aspect_b = aspect(implementation = _impl,",
+        "                  requires = [aspect_c],",
+        "                  attr_aspects = ['extra_deps'])",
+        "aspect_a = aspect(implementation = _impl,",
+        "                  requires = [aspect_c],",
+        "                  attr_aspects = ['*'])");
+    scratch.file("test/BUILD", "cc_binary(name = 'main_target')");
+
+    AnalysisResult analysisResult =
+        update(
+            ImmutableList.of("test/defs.bzl%aspect_a", "test/defs.bzl%aspect_b"),
+            "//test:main_target");
+
+    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
+    AspectKey aspectC = getAspectKey(configuredAspects, "aspect_c");
+    assertThat(aspectC).isNotNull();
+    // propagate along all attributes '*'
+    assertThat(aspectC.getInheritedAttributeAspects()).isNull();
+  }
+
+  @Test
+  public void testTopLevelAspectRequiresAspect_inheritAllRequiredProviders() throws Exception {
+    useConfiguration("--experimental_required_aspects");
+    scratch.file(
+        "test/defs.bzl",
+        "cc = provider()",
+        "def _impl(target, ctx):",
+        "   return []",
+        "aspect_c = aspect(implementation = _impl)",
+        "aspect_b = aspect(implementation = _impl,",
+        "                  requires = [aspect_c],",
+        "                  required_providers = [])",
+        "aspect_a = aspect(implementation = _impl,",
+        "                  requires = [aspect_c],",
+        "                  required_providers=[['java', cc], ['python']])");
+    scratch.file("test/BUILD", "cc_binary(name = 'main_target')");
+
+    AnalysisResult analysisResult =
+        update(
+            ImmutableList.of("test/defs.bzl%aspect_a", "test/defs.bzl%aspect_b"),
+            "//test:main_target");
+
+    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
+    AspectKey aspectC = getAspectKey(configuredAspects, "aspect_c");
+    assertThat(aspectC).isNotNull();
+    assertThat(aspectC.getInheritedRequiredProviders())
+        .isEqualTo(RequiredProviders.acceptAnyBuilder().build());
+  }
+
+  @Test
+  public void testTopLevelAspectRequiresAspect_inheritAttrAspectsFromAspectsStack()
+      throws Exception {
+    useConfiguration("--experimental_required_aspects");
+    scratch.file(
+        "test/defs.bzl",
+        "def _impl(target, ctx):",
+        "   return []",
+        "aspect_c = aspect(implementation = _impl)",
+        "aspect_b = aspect(implementation = _impl,",
+        "                  requires = [aspect_c],",
+        "                  attr_aspects = ['extra_deps'])",
+        "aspect_a = aspect(implementation = _impl,",
+        "                  requires = [aspect_b],",
+        "                  attr_aspects = ['deps'])");
+    scratch.file("test/BUILD", "cc_binary(name = 'main_target')");
+
+    AnalysisResult analysisResult =
+        update(ImmutableList.of("test/defs.bzl%aspect_a"), "//test:main_target");
+
+    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
+    AspectKey aspectC = getAspectKey(configuredAspects, "aspect_c");
+    assertThat(aspectC).isNotNull();
+    assertThat(aspectC.getInheritedAttributeAspects()).containsExactly("deps", "extra_deps");
+  }
+
+  @Test
+  public void testTopLevelAspectRequiresAspect_inheritRequiredProvidersFromAspectsStack()
+      throws Exception {
+    useConfiguration("--experimental_required_aspects");
+    scratch.file(
+        "test/defs.bzl",
+        "cc = provider()",
+        "def _impl(target, ctx):",
+        "   return []",
+        "aspect_c = aspect(implementation = _impl)",
+        "aspect_b = aspect(implementation = _impl,",
+        "                  requires = [aspect_c],",
+        "                  required_providers=['go'])",
+        "aspect_a = aspect(implementation = _impl,",
+        "                  requires = [aspect_b],",
+        "                  required_providers=[['java', cc], ['python']])");
+    scratch.file("test/BUILD", "cc_binary(name = 'main_target')");
+
+    AnalysisResult analysisResult =
+        update(ImmutableList.of("test/defs.bzl%aspect_a"), "//test:main_target");
+
+    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
+    AspectKey aspectC = getAspectKey(configuredAspects, "aspect_c");
+    assertThat(aspectC).isNotNull();
+    assertThat(aspectC.getInheritedRequiredProviders().getDescription())
+        .isEqualTo("['java', 'cc'] or 'python' or 'go'");
+  }
+
+  @Test
+  public void testTopLevelAspectRequiringAspect_inheritAllAttrAspectsFromAspectsStack()
+      throws Exception {
+    useConfiguration("--experimental_required_aspects");
+    scratch.file(
+        "test/defs.bzl",
+        "def _impl(target, ctx):",
+        "   return []",
+        "aspect_c = aspect(implementation = _impl)",
+        "aspect_b = aspect(implementation = _impl,",
+        "                  requires = [aspect_c],",
+        "                  attr_aspects = ['*'])",
+        "aspect_a = aspect(implementation = _impl,",
+        "                  requires = [aspect_b],",
+        "                  attr_aspects = ['deps'])");
+    scratch.file("test/BUILD", "cc_binary(name = 'main_target')");
+
+    AnalysisResult analysisResult =
+        update(ImmutableList.of("test/defs.bzl%aspect_a"), "//test:main_target");
+
+    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
+    AspectKey aspectC = getAspectKey(configuredAspects, "aspect_c");
+    assertThat(aspectC).isNotNull();
+    // propagate along all attributes '*'
+    assertThat(aspectC.getInheritedAttributeAspects()).isNull();
+  }
+
+  @Test
+  public void testTopLevelAspectRequiresAspect_inheritAllRequiredProvidersFromAspectsStack()
+      throws Exception {
+    useConfiguration("--experimental_required_aspects");
+    scratch.file(
+        "test/defs.bzl",
+        "cc = provider()",
+        "def _impl(target, ctx):",
+        "   return []",
+        "aspect_c = aspect(implementation = _impl)",
+        "aspect_b = aspect(implementation = _impl,",
+        "                  requires = [aspect_c],",
+        "                  required_providers=[cc])",
+        "aspect_a = aspect(implementation = _impl,",
+        "                  requires = [aspect_b],",
+        "                  required_providers=[])");
+    scratch.file("test/BUILD", "cc_binary(name = 'main_target')");
+
+    AnalysisResult analysisResult =
+        update(ImmutableList.of("test/defs.bzl%aspect_a"), "//test:main_target");
+
+    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
+    AspectKey aspectC = getAspectKey(configuredAspects, "aspect_c");
+    assertThat(aspectC).isNotNull();
+    assertThat(aspectC.getInheritedRequiredProviders())
+        .isEqualTo(RequiredProviders.acceptAnyBuilder().build());
+  }
+
+  @Test
+  public void testTopLevelAspectRequiresAspect_requiredNativeAspect_inheritsAttrAspects()
+      throws Exception {
+    exposeNativeAspectToStarlark();
+    useConfiguration("--experimental_required_aspects");
+    scratch.file(
+        "test/defs.bzl",
+        "def _impl(target, ctx):",
+        "   return []",
+        "aspect_a = aspect(implementation = _impl,",
+        "                  requires = [starlark_native_aspect],",
+        "                  attr_aspects = ['deps'])");
+    scratch.file("test/BUILD", "cc_binary(name = 'main_target')");
+
+    AnalysisResult analysisResult =
+        update(ImmutableList.of("test/defs.bzl%aspect_a"), "//test:main_target");
+
+    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
+    AspectKey nativeAspect = getAspectKey(configuredAspects, "StarlarkNativeAspectWithProvider");
+    assertThat(nativeAspect).isNotNull();
+    assertThat(nativeAspect.getInheritedAttributeAspects()).containsExactly("deps");
+  }
+
+  @Test
+  public void testTopLevelAspectRequiresAspect_requiredNativeAspect_inheritsRequiredProviders()
+      throws Exception {
+    exposeNativeAspectToStarlark();
+    useConfiguration("--experimental_required_aspects");
+    scratch.file(
+        "test/defs.bzl",
+        "rule_prov = provider()",
+        "def _impl(target, ctx):",
+        "   return []",
+        "aspect_a = aspect(implementation = _impl,",
+        "                  requires = [starlark_native_aspect],",
+        "                  required_providers = [['java', rule_prov], ['python']])");
+    scratch.file("test/BUILD", "cc_binary(name = 'main_target')");
+
+    AnalysisResult analysisResult =
+        update(ImmutableList.of("test/defs.bzl%aspect_a"), "//test:main_target");
+
+    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
+    AspectKey nativeAspect = getAspectKey(configuredAspects, "StarlarkNativeAspectWithProvider");
+    assertThat(nativeAspect).isNotNull();
+    assertThat(nativeAspect.getInheritedRequiredProviders().getDescription())
+        .isEqualTo("['java', 'rule_prov'] or 'python'");
+  }
+
+  @Test
+  public void testTopLevelAspectRequiresAspect_requiredNativeAspect_parametersNotAllowed()
+      throws Exception {
+    exposeNativeAspectToStarlark();
+    useConfiguration("--experimental_required_aspects");
+    scratch.file(
+        "test/defs.bzl",
+        "def _impl(target, ctx):",
+        "   return []",
+        "aspect_a = aspect(implementation = _impl,",
+        "                  requires = [parametrized_native_aspect])");
+    scratch.file("test/BUILD", "cc_binary(name = 'main_target')");
+
+    AssertionError expected =
+        assertThrows(
+            AssertionError.class,
+            () -> update(ImmutableList.of("test/defs.bzl%aspect_a"), "//test:main_target"));
+    assertThat(expected)
+        .hasMessageThat()
+        .contains(
+            "Cannot use parameterized aspect ParametrizedAspectWithProvider at the top level.");
+  }
+
+  @Test
+  public void testTopLevelAspectRequiresAspect_requiredStarlarkAspect_parametersNotAllowed()
+      throws Exception {
+    useConfiguration("--experimental_required_aspects");
+    scratch.file(
+        "test/defs.bzl",
+        "def _impl(target, ctx):",
+        "   return []",
+        "aspect_b = aspect(implementation = _impl,",
+        "                  attrs = {'attr': attr.string(values=['val'])})",
+        "aspect_a = aspect(implementation = _impl,",
+        "                  requires = [aspect_b])");
+    scratch.file("test/BUILD", "cc_binary(name = 'main_target')");
+
+    AssertionError expected =
+        assertThrows(
+            AssertionError.class,
+            () -> update(ImmutableList.of("test/defs.bzl%aspect_a"), "//test:main_target"));
+    assertThat(expected)
+        .hasMessageThat()
+        .contains("Cannot use parameterized aspect //test:defs.bzl%aspect_b at the top level.");
+  }
+
+  @Test
+  public void testTopLevelAspectRequiresAspect_ruleAttributes() throws Exception {
+    useConfiguration("--experimental_required_aspects");
+    scratch.file(
+        "test/defs.bzl",
+        "RequiredAspectProv = provider()",
+        "BaseAspectProv = provider()",
+        "",
+        "def _required_aspect_impl(target, ctx):",
+        "  p_val = ['In required_aspect, p = {} on target {}'",
+        "              .format(ctx.rule.attr.p, target.label)]",
+        "  if ctx.rule.attr.dep:",
+        "    p_val += ctx.rule.attr.dep[RequiredAspectProv].p_val",
+        "  return [RequiredAspectProv(p_val = p_val)]",
+        "required_aspect = aspect(",
+        "  implementation = _required_aspect_impl,",
+        "  attr_aspects = ['dep'],",
+        ")",
+        "",
+        "def _base_aspect_impl(target, ctx):",
+        "  p_val = ['In base_aspect, p = {} on target {}'.format(ctx.rule.attr.p, target.label)]",
+        "  if ctx.rule.attr.dep:",
+        "    p_val += ctx.rule.attr.dep[BaseAspectProv].p_val",
+        "  return [BaseAspectProv(p_val = p_val)]",
+        "base_aspect = aspect(",
+        "  implementation = _base_aspect_impl,",
+        "  attr_aspects = ['dep'],",
+        "  requires = [required_aspect],",
+        ")",
+        "",
+        "def _rule_impl(ctx):",
+        "  pass",
+        "",
+        "my_rule = rule(",
+        "  implementation = _rule_impl,",
+        "  attrs = {",
+        "    'dep': attr.label(),",
+        "    'p' : attr.string(values = ['main_val', 'dep_val']),",
+        "  },",
+        ")");
+    scratch.file(
+        "test/BUILD",
+        "load('//test:defs.bzl', 'my_rule')",
+        "my_rule(",
+        "  name = 'main_target',",
+        "  dep = ':dep_target',",
+        "  p = 'main_val',",
+        ")",
+        "my_rule(",
+        "  name = 'dep_target',",
+        "  p = 'dep_val',",
+        ")");
+
+    AnalysisResult analysisResult =
+        update(ImmutableList.of("test/defs.bzl%base_aspect"), "//test:main_target");
+
+    // Both base_aspect and required_aspect can see the attributes of the target they run on
+    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
+    ConfiguredAspect requiredAspect = getConfiguredAspect(configuredAspects, "required_aspect");
+    assertThat(requiredAspect).isNotNull();
+    StarlarkProvider.Key requiredAspectProv =
+        new StarlarkProvider.Key(
+            Label.parseAbsolute("//test:defs.bzl", ImmutableMap.of()), "RequiredAspectProv");
+    StructImpl requiredAspectProvider = (StructImpl) requiredAspect.get(requiredAspectProv);
+    assertThat((Sequence<?>) requiredAspectProvider.getValue("p_val"))
+        .containsExactly(
+            "In required_aspect, p = dep_val on target //test:dep_target",
+            "In required_aspect, p = main_val on target //test:main_target");
+
+    ConfiguredAspect baseAspect = getConfiguredAspect(configuredAspects, "base_aspect");
+    assertThat(baseAspect).isNotNull();
+    StarlarkProvider.Key baseAspectProv =
+        new StarlarkProvider.Key(
+            Label.parseAbsolute("//test:defs.bzl", ImmutableMap.of()), "BaseAspectProv");
+    StructImpl baseAspectProvider = (StructImpl) baseAspect.get(baseAspectProv);
+    assertThat((Sequence<?>) baseAspectProvider.getValue("p_val"))
+        .containsExactly(
+            "In base_aspect, p = dep_val on target //test:dep_target",
+            "In base_aspect, p = main_val on target //test:main_target");
+  }
+
+  @Test
+  public void testTopLevelAspectRequiresAspect_inheritPropagationAttributes() throws Exception {
+    // base_aspect propagates over base_dep attribute and requires first_required_aspect which
+    // propagates over first_dep attribute and requires second_required_aspect which propagates over
+    // second_dep attribute
+    useConfiguration("--experimental_required_aspects");
+    scratch.file(
+        "test/defs.bzl",
+        "BaseAspectProv = provider()",
+        "FirstRequiredAspectProv = provider()",
+        "SecondRequiredAspectProv = provider()",
+        "",
+        "def _second_required_aspect_impl(target, ctx):",
+        "  result = []",
+        "  if getattr(ctx.rule.attr, 'second_dep'):",
+        "    result += getattr(ctx.rule.attr, 'second_dep')[SecondRequiredAspectProv].result",
+        "  result += ['second_required_aspect run on target {}'.format(target.label)]",
+        "  return [SecondRequiredAspectProv(result = result)]",
+        "second_required_aspect = aspect(",
+        "  implementation = _second_required_aspect_impl,",
+        "  attr_aspects = ['second_dep'],",
+        ")",
+        "",
+        "def _first_required_aspect_impl(target, ctx):",
+        "  result = []",
+        "  result += target[SecondRequiredAspectProv].result",
+        "  if getattr(ctx.rule.attr, 'first_dep'):",
+        "    result += getattr(ctx.rule.attr, 'first_dep')[FirstRequiredAspectProv].result",
+        "  result += ['first_required_aspect run on target {}'.format(target.label)]",
+        "  return [FirstRequiredAspectProv(result = result)]",
+        "first_required_aspect = aspect(",
+        "  implementation = _first_required_aspect_impl,",
+        "  attr_aspects = ['first_dep'],",
+        "  requires = [second_required_aspect],",
+        ")",
+        "",
+        "def _base_aspect_impl(target, ctx):",
+        "  result = []",
+        "  result += target[FirstRequiredAspectProv].result",
+        "  if getattr(ctx.rule.attr, 'base_dep'):",
+        "    result += getattr(ctx.rule.attr, 'base_dep')[BaseAspectProv].result",
+        "  result += ['base_aspect run on target {}'.format(target.label)]",
+        "  return [BaseAspectProv(result = result)]",
+        "base_aspect = aspect(",
+        "  implementation = _base_aspect_impl,",
+        "  attr_aspects = ['base_dep'],",
+        "  requires = [first_required_aspect],",
+        ")",
+        "",
+        "def _my_rule_impl(ctx):",
+        "  pass",
+        "",
+        "my_rule = rule(",
+        "  implementation = _my_rule_impl,",
+        "  attrs = {",
+        "    'base_dep': attr.label(),",
+        "    'first_dep': attr.label(),",
+        "    'second_dep': attr.label()",
+        "  },",
+        ")");
+    scratch.file(
+        "test/BUILD",
+        "load('//test:defs.bzl', 'my_rule')",
+        "my_rule(",
+        "  name = 'main_target',",
+        "  base_dep = ':base_dep_target',",
+        "  first_dep = ':first_dep_target',",
+        "  second_dep = ':second_dep_target',",
+        ")",
+        "my_rule(",
+        "  name = 'base_dep_target',",
+        ")",
+        "my_rule(",
+        "  name = 'first_dep_target',",
+        ")",
+        "my_rule(",
+        "  name = 'second_dep_target',",
+        ")");
+
+    AnalysisResult analysisResult =
+        update(ImmutableList.of("test/defs.bzl%base_aspect"), "//test:main_target");
+
+    // base_aspect should propagate only along its attr_aspects: 'base_dep'
+    // first_required_aspect should propagate along 'base_dep' and 'first_dep'
+    // second_required_aspect should propagate along 'base_dep', 'first_dep' and 'second_dep'
+    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
+    ConfiguredAspect baseAspect = getConfiguredAspect(configuredAspects, "base_aspect");
+    assertThat(baseAspect).isNotNull();
+    StarlarkProvider.Key baseAspectProv =
+        new StarlarkProvider.Key(
+            Label.parseAbsolute("//test:defs.bzl", ImmutableMap.of()), "BaseAspectProv");
+    StructImpl baseAspectProvider = (StructImpl) baseAspect.get(baseAspectProv);
+    assertThat((Sequence<?>) baseAspectProvider.getValue("result"))
+        .containsExactly(
+            "second_required_aspect run on target //test:second_dep_target",
+            "second_required_aspect run on target //test:main_target",
+            "second_required_aspect run on target //test:first_dep_target",
+            "second_required_aspect run on target //test:base_dep_target",
+            "first_required_aspect run on target //test:first_dep_target",
+            "first_required_aspect run on target //test:main_target",
+            "first_required_aspect run on target //test:base_dep_target",
+            "base_aspect run on target //test:base_dep_target",
+            "base_aspect run on target //test:main_target");
+  }
+
+  @Test
+  public void testTopLevelAspectRequiresAspect_inheritRequiredProviders() throws Exception {
+    // aspect_a requires provider Prov_A and requires aspect_b which requires
+    // provider Prov_B and requires aspect_c which requires provider Prov_C
+    useConfiguration("--experimental_required_aspects");
+    scratch.file(
+        "test/defs.bzl",
+        "Prov_A = provider()",
+        "Prov_B = provider()",
+        "Prov_C = provider()",
+        "",
+        "CollectorProv = provider()",
+        "",
+        "def _aspect_c_impl(target, ctx):",
+        "  collector_result = ['aspect_c run on target {} and value of Prov_C = {}'",
+        "                                .format(target.label, target[Prov_C].val)]",
+        "  return [CollectorProv(result = collector_result)]",
+        "aspect_c = aspect(",
+        "  implementation = _aspect_c_impl,",
+        "  required_providers = [Prov_C],",
+        ")",
+        "",
+        "def _aspect_b_impl(target, ctx):",
+        "  collector_result = []",
+        "  collector_result += ctx.rule.attr.dep[CollectorProv].result",
+        "  collector_result += ['aspect_b run on target {} and value of Prov_B = {}'",
+        "                                 .format(target.label, target[Prov_B].val)]",
+        "  return [CollectorProv(result = collector_result)]",
+        "aspect_b = aspect(",
+        "  implementation = _aspect_b_impl,",
+        "  required_providers = [Prov_B],",
+        "  requires = [aspect_c],",
+        ")",
+        "",
+        "def _aspect_a_impl(target, ctx):",
+        "  collector_result = []",
+        "  collector_result += ctx.rule.attr.dep[CollectorProv].result",
+        "  collector_result += ['aspect_a run on target {} and value of Prov_A = {}'",
+        "                                 .format(target.label, target[Prov_A].val)]",
+        "  return [CollectorProv(result = collector_result)]",
+        "aspect_a = aspect(",
+        "  implementation = _aspect_a_impl,",
+        "  attr_aspects = ['dep'],",
+        "  required_providers = [Prov_A],",
+        "  requires = [aspect_b],",
+        ")",
+        "",
+        "def _my_rule_impl(ctx):",
+        "  return [Prov_A(val='main_val_a')]",
+        "my_rule = rule(",
+        "  implementation = _my_rule_impl,",
+        "  attrs = {",
+        "    'dep': attr.label(),",
+        "  },",
+        "  provides = [Prov_A]",
+        ")",
+        "",
+        "def _rule_with_prov_a_impl(ctx):",
+        "  return [Prov_A(val='val_a')]",
+        "rule_with_prov_a = rule(",
+        "  implementation = _rule_with_prov_a_impl,",
+        "  attrs = {",
+        "    'dep': attr.label(),",
+        "  },",
+        "  provides = [Prov_A]",
+        ")",
+        "",
+        "def _rule_with_prov_b_impl(ctx):",
+        "  return [Prov_B(val = 'val_b')]",
+        "rule_with_prov_b = rule(",
+        "  implementation = _rule_with_prov_b_impl,",
+        "  attrs = {",
+        "    'dep': attr.label(),",
+        "  },",
+        "  provides = [Prov_B]",
+        ")",
+        "",
+        "def _rule_with_prov_c_impl(ctx):",
+        "  return [Prov_C(val = 'val_c')]",
+        "rule_with_prov_c = rule(",
+        "  implementation = _rule_with_prov_c_impl,",
+        "  provides = [Prov_C]",
+        ")");
+    scratch.file(
+        "test/BUILD",
+        "load('//test:defs.bzl', 'my_rule', 'rule_with_prov_a', 'rule_with_prov_b',"
+            + " 'rule_with_prov_c')",
+        "my_rule(",
+        "  name = 'main_target',",
+        "  dep = ':target_with_prov_a',",
+        ")",
+        "rule_with_prov_a(",
+        "  name = 'target_with_prov_a',",
+        "  dep = ':target_with_prov_b'",
+        ")",
+        "rule_with_prov_b(",
+        "  name = 'target_with_prov_b',",
+        "  dep = ':target_with_prov_c'",
+        ")",
+        "rule_with_prov_c(",
+        "  name = 'target_with_prov_c'",
+        ")");
+
+    AnalysisResult analysisResult =
+        update(ImmutableList.of("test/defs.bzl%aspect_a"), "//test:main_target");
+
+    // aspect_a should run on main_target and target_with_prov_a
+    // aspect_b can reach target_with_prov_b because it inherits the required_providers of aspect_a
+    // aspect_c can reach target_with_prov_c because it inherits the required_providers of aspect_a
+    // and aspect_b
+    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
+    ConfiguredAspect aspectA = getConfiguredAspect(configuredAspects, "aspect_a");
+    assertThat(aspectA).isNotNull();
+    StarlarkProvider.Key collectorProv =
+        new StarlarkProvider.Key(
+            Label.parseAbsolute("//test:defs.bzl", ImmutableMap.of()), "CollectorProv");
+    StructImpl collectorProvider = (StructImpl) aspectA.get(collectorProv);
+    assertThat((Sequence<?>) collectorProvider.getValue("result"))
+        .containsExactly(
+            "aspect_c run on target //test:target_with_prov_c and value of Prov_C = val_c",
+            "aspect_b run on target //test:target_with_prov_b and value of Prov_B = val_b",
+            "aspect_a run on target //test:target_with_prov_a and value of Prov_A = val_a",
+            "aspect_a run on target //test:main_target and value of Prov_A = main_val_a")
+        .inOrder();
+  }
+
+  @Test
+  public void testTopLevelAspectRequiresAspect_inspectRequiredAspectActions() throws Exception {
+    useConfiguration("--experimental_required_aspects");
+    scratch.file(
+        "test/defs.bzl",
+        "BaseAspectProvider = provider()",
+        "def _required_aspect_impl(target, ctx):",
+        "  f = ctx.actions.declare_file('dummy.txt')",
+        "  ctx.actions.run_shell(outputs = [f], command='echo xxx > $(location f)',",
+        "                        mnemonic='RequiredAspectAction')",
+        "  return struct()",
+        "required_aspect = aspect(",
+        "  implementation = _required_aspect_impl,",
+        ")",
+        "",
+        "def _base_aspect_impl(target, ctx):",
+        "  required_aspect_action = None",
+        "  for action in target.actions:",
+        "    if action.mnemonic == 'RequiredAspectAction':",
+        "      required_aspect_action = action",
+        "  if required_aspect_action:",
+        "    return [BaseAspectProvider(result = 'base_aspect can see required_aspect action')]",
+        "  else:",
+        "    return [BaseAspectProvider(result = 'base_aspect cannot see required_aspect action')]",
+        "base_aspect = aspect(",
+        "  implementation = _base_aspect_impl,",
+        "  attr_aspects = ['dep'],",
+        "  requires = [required_aspect]",
+        ")",
+        "",
+        "def _my_rule_impl(ctx):",
+        "  pass",
+        "my_rule = rule(",
+        "  implementation = _my_rule_impl,",
+        ")");
+    scratch.file(
+        "test/BUILD",
+        "load('//test:defs.bzl', 'my_rule')",
+        "my_rule(",
+        "  name = 'main_target',",
+        ")");
+
+    AnalysisResult analysisResult =
+        update(ImmutableList.of("test/defs.bzl%base_aspect"), "//test:main_target");
+
+    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
+    ConfiguredAspect baseAspect = getConfiguredAspect(configuredAspects, "base_aspect");
+    assertThat(baseAspect).isNotNull();
+    StarlarkProvider.Key baseAspectProv =
+        new StarlarkProvider.Key(
+            Label.parseAbsolute("//test:defs.bzl", ImmutableMap.of()), "BaseAspectProvider");
+    StructImpl baseAspectProvider = (StructImpl) baseAspect.get(baseAspectProv);
+    assertThat(baseAspectProvider.getValue("result"))
+        .isEqualTo("base_aspect can see required_aspect action");
+  }
+
+  @Test
+  public void testTopLevelAspectRequiresAspect_inspectRequiredAspectGeneratedFiles()
+      throws Exception {
+    useConfiguration("--experimental_required_aspects");
+    scratch.file(
+        "test/defs.bzl",
+        "BaseAspectProvider = provider()",
+        "def _required_aspect_impl(target, ctx):",
+        "  file = ctx.actions.declare_file('required_aspect_file')",
+        "  ctx.actions.write(file, 'data')",
+        "  return [OutputGroupInfo(out = [file])]",
+        "required_aspect = aspect(",
+        "  implementation = _required_aspect_impl,",
+        ")",
+        "",
+        "def _base_aspect_impl(target, ctx):",
+        "  files = ['base_aspect can see file ' + f.path.split('/')[-1] ",
+        "               for f in target[OutputGroupInfo].out.to_list()]",
+        "  return [BaseAspectProvider(my_files = files)]",
+        "base_aspect = aspect(",
+        "  implementation = _base_aspect_impl,",
+        "  attr_aspects = ['dep'],",
+        "  requires = [required_aspect]",
+        ")",
+        "",
+        "def _my_rule_impl(ctx):",
+        "  pass",
+        "my_rule = rule(",
+        "  implementation = _my_rule_impl,",
+        ")");
+    scratch.file(
+        "test/BUILD",
+        "load('//test:defs.bzl', 'my_rule')",
+        "my_rule(",
+        "  name = 'main_target',",
+        ")");
+
+    AnalysisResult analysisResult =
+        update(ImmutableList.of("test/defs.bzl%base_aspect"), "//test:main_target");
+
+    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
+    ConfiguredAspect baseAspect = getConfiguredAspect(configuredAspects, "base_aspect");
+    assertThat(baseAspect).isNotNull();
+    StarlarkProvider.Key baseAspectProv =
+        new StarlarkProvider.Key(
+            Label.parseAbsolute("//test:defs.bzl", ImmutableMap.of()), "BaseAspectProvider");
+    StructImpl baseAspectProvider = (StructImpl) baseAspect.get(baseAspectProv);
+    assertThat((Sequence<?>) baseAspectProvider.getValue("my_files"))
+        .containsExactly("base_aspect can see file required_aspect_file");
+  }
+
+  @Test
+  public void testTopLevelAspectRequiresAspect_withRequiredAspectProvidersSatisfied()
+      throws Exception {
+    useConfiguration("--experimental_required_aspects");
+    scratch.file(
+        "test/defs.bzl",
+        "prov_a = provider()",
+        "prov_b = provider()",
+        "prov_b_forwarded = provider()",
+        "",
+        "def _aspect_b_impl(target, ctx):",
+        "  result = 'aspect_b on target {} '.format(target.label)",
+        "  if prov_b in target:",
+        "    result += 'found prov_b = {}'.format(target[prov_b].val)",
+        "    return struct(aspect_b_result = result,",
+        "                  providers = [prov_b_forwarded(val = target[prov_b].val)])",
+        "  else:",
+        "    result += 'cannot find prov_b'",
+        "    return struct(aspect_b_result = result)",
+        "aspect_b = aspect(",
+        "  implementation = _aspect_b_impl,",
+        "  required_aspect_providers = [prov_b]",
+        ")",
+        "",
+        "def _aspect_a_impl(target, ctx):",
+        "  result = 'aspect_a on target {} '.format(target.label)",
+        "  if prov_a in target:",
+        "    result += 'found prov_a = {}'.format(target[prov_a].val)",
+        "  else:",
+        "    result += 'cannot find prov_a'",
+        "  if prov_b_forwarded in target:",
+        "    result += ' and found prov_b = {}'.format(target[prov_b_forwarded].val)",
+        "  else:",
+        "    result += ' but cannot find prov_b'",
+        "  return struct(aspect_a_result = result)",
+        "",
+        "aspect_a = aspect(",
+        "  implementation = _aspect_a_impl,",
+        "  required_aspect_providers = [prov_a],",
+        "  attr_aspects = ['dep'],",
+        "  requires = [aspect_b]",
+        ")",
+        "",
+        "def _aspect_with_prov_a_impl(target, ctx):",
+        "  return [prov_a(val = 'a1')]",
+        "aspect_with_prov_a = aspect(",
+        "  implementation = _aspect_with_prov_a_impl,",
+        "  provides = [prov_a],",
+        "  attr_aspects = ['dep'],",
+        ")",
+        "",
+        "def _aspect_with_prov_b_impl(target, ctx):",
+        "  return [prov_b(val = 'b1')]",
+        "aspect_with_prov_b = aspect(",
+        "  implementation = _aspect_with_prov_b_impl,",
+        "  provides = [prov_b],",
+        "  attr_aspects = ['dep'],",
+        ")",
+        "",
+        "def _my_rule_impl(ctx):",
+        "  pass",
+        "my_rule = rule(",
+        "  implementation = _my_rule_impl,",
+        ")");
+    scratch.file(
+        "test/BUILD",
+        "load('//test:defs.bzl', 'my_rule')",
+        "my_rule(",
+        "  name = 'main_target',",
+        ")");
+
+    AnalysisResult analysisResult =
+        update(
+            ImmutableList.of(
+                "test/defs.bzl%aspect_with_prov_a",
+                "test/defs.bzl%aspect_with_prov_b", "test/defs.bzl%aspect_a"),
+            "//test:main_target");
+
+    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
+    ConfiguredAspect aspectA = getConfiguredAspect(configuredAspects, "aspect_a");
+    assertThat(aspectA).isNotNull();
+    String aspectAResult = (String) aspectA.get("aspect_a_result");
+    assertThat(aspectAResult)
+        .isEqualTo("aspect_a on target //test:main_target found prov_a = a1 and found prov_b = b1");
+
+    ConfiguredAspect aspectB = getConfiguredAspect(configuredAspects, "aspect_b");
+    assertThat(aspectB).isNotNull();
+    String aspectBResult = (String) aspectB.get("aspect_b_result");
+    assertThat(aspectBResult).isEqualTo("aspect_b on target //test:main_target found prov_b = b1");
+  }
+
+  @Test
+  public void testTopLevelAspectRequiresAspect_withRequiredAspectProvidersNotFound()
+      throws Exception {
+    useConfiguration("--experimental_required_aspects");
+    scratch.file(
+        "test/defs.bzl",
+        "prov_a = provider()",
+        "prov_b = provider()",
+        "",
+        "def _aspect_b_impl(target, ctx):",
+        "  result = 'aspect_b on target {} '.format(target.label)",
+        "  if prov_b in target:",
+        "    result += 'found prov_b = {}'.format(target[prov_b].val)",
+        "  else:",
+        "    result += 'cannot find prov_b'",
+        "  return struct(aspect_b_result = result)",
+        "aspect_b = aspect(",
+        "  implementation = _aspect_b_impl,",
+        "  required_aspect_providers = [prov_b]",
+        ")",
+        "",
+        "def _aspect_a_impl(target, ctx):",
+        "  result = 'aspect_a on target {} '.format(target.label)",
+        "  if prov_a in target:",
+        "    result += 'found prov_a = {}'.format(target[prov_a].val)",
+        "  else:",
+        "    result += 'cannot find prov_a'",
+        "  return struct(aspect_a_result = result)",
+        "",
+        "aspect_a = aspect(",
+        "  implementation = _aspect_a_impl,",
+        "  required_aspect_providers = [prov_a],",
+        "  attr_aspects = ['dep'],",
+        "  requires = [aspect_b]",
+        ")",
+        "",
+        "def _aspect_with_prov_a_impl(target, ctx):",
+        "  return [prov_a(val = 'a1')]",
+        "aspect_with_prov_a = aspect(",
+        "  implementation = _aspect_with_prov_a_impl,",
+        "  provides = [prov_a],",
+        "  attr_aspects = ['dep'],",
+        ")",
+        "",
+        "def _my_rule_impl(ctx):",
+        "  pass",
+        "my_rule = rule(",
+        "  implementation = _my_rule_impl,",
+        ")");
+    scratch.file(
+        "test/BUILD",
+        "load('//test:defs.bzl', 'my_rule')",
+        "my_rule(",
+        "  name = 'main_target',",
+        ")");
+
+    AnalysisResult analysisResult =
+        update(
+            ImmutableList.of("test/defs.bzl%aspect_with_prov_a", "test/defs.bzl%aspect_a"),
+            "//test:main_target");
+
+    Map<AspectKey, ConfiguredAspect> configuredAspects = analysisResult.getAspectsMap();
+    ConfiguredAspect aspectA = getConfiguredAspect(configuredAspects, "aspect_a");
+    assertThat(aspectA).isNotNull();
+    String aspectAResult = (String) aspectA.get("aspect_a_result");
+    assertThat(aspectAResult).isEqualTo("aspect_a on target //test:main_target found prov_a = a1");
+
+    ConfiguredAspect aspectB = getConfiguredAspect(configuredAspects, "aspect_b");
+    assertThat(aspectB).isNotNull();
+    String aspectBResult = (String) aspectB.get("aspect_b_result");
+    assertThat(aspectBResult).isEqualTo("aspect_b on target //test:main_target cannot find prov_b");
+  }
+
   private ConfiguredAspect getConfiguredAspect(
       Map<AspectKey, ConfiguredAspect> aspectsMap, String aspectName) {
     for (Map.Entry<AspectKey, ConfiguredAspect> entry : aspectsMap.entrySet()) {
@@ -5478,6 +6629,29 @@
     return null;
   }
 
+  private AspectKey getAspectKey(Map<AspectKey, ConfiguredAspect> aspectsMap, String aspectName) {
+    for (Map.Entry<AspectKey, ConfiguredAspect> entry : aspectsMap.entrySet()) {
+      String aspectExportedName = entry.getKey().getAspectClass().getName();
+      if (aspectExportedName.contains(aspectName)) {
+        return entry.getKey();
+      }
+    }
+    return null;
+  }
+
+  private void exposeNativeAspectToStarlark() throws Exception {
+    ConfiguredRuleClassProvider.Builder builder = new ConfiguredRuleClassProvider.Builder();
+    TestRuleClassProvider.addStandardRules(builder);
+    builder.addStarlarkAccessibleTopLevels(
+        "starlark_native_aspect", TestAspects.STARLARK_NATIVE_ASPECT_WITH_PROVIDER);
+    builder.addStarlarkAccessibleTopLevels(
+        "parametrized_native_aspect",
+        TestAspects.PARAMETRIZED_STARLARK_NATIVE_ASPECT_WITH_PROVIDER);
+    builder.addNativeAspectClass(TestAspects.STARLARK_NATIVE_ASPECT_WITH_PROVIDER);
+    builder.addNativeAspectClass(TestAspects.PARAMETRIZED_STARLARK_NATIVE_ASPECT_WITH_PROVIDER);
+    useRuleClassProvider(builder.build());
+  }
+
   /** StarlarkAspectTest with "keep going" flag */
   @RunWith(JUnit4.class)
   public static final class WithKeepGoing extends StarlarkDefinedAspectsTest {