Add ctx.aspect_ids, deprecate ctx.aspect_id and Target.aspect_ids.

BUG=35456356

--
PiperOrigin-RevId: 148317662
MOS_MIGRATED_REVID=148317662
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 656ca25..76f6aa2 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
@@ -277,6 +277,13 @@
   }
 
   /**
+   * All aspects applied to the rule.
+   */
+  public ImmutableList<AspectDescriptor> getAspectDescriptors() {
+    return aspectDescriptors;
+  }
+
+  /**
    * Accessor for the attributes of the rule and its aspects.
    *
    * <p>The rule's native attributes can be queried both on their structure / existence and values
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java
index 8dfc157..2d6f5a2 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java
@@ -676,13 +676,30 @@
   }
 
   @SkylarkCallable(structField = true,
+      name = "aspect_ids",
+      doc = "Returns a list ids for all aspects applied to the target."
+      + " Only available in aspect implementation functions.")
+  public ImmutableList<String> aspectIds() throws EvalException {
+    if (ruleAttributesCollection == null) {
+      throw new EvalException(
+          Location.BUILTIN, "'aspect_ids' is only available in aspect implementations");
+    }
+
+    ImmutableList.Builder<String> result = ImmutableList.builder();
+    for (AspectDescriptor descriptor : ruleContext.getAspectDescriptors()) {
+      result.add(descriptor.getDescription());
+    }
+    return result.build();
+  }
+
+
+  @SkylarkCallable(structField = true,
       name = "aspect_id",
-      doc = "Returns a string uniquely identifying this aspect"
-          + " Only available in aspect implementation functions.")
+      doc = "Deprecated, use 'aspect_ids'.")
   public String aspectId() throws EvalException {
     if (ruleAttributesCollection == null) {
       throw new EvalException(
-          Location.BUILTIN, "'aspect' is only available in aspect implementations");
+          Location.BUILTIN, "'aspect_id' is only available in aspect implementations");
     }
     return aspectDescriptor.getDescription();
   }
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkAspectsTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkAspectsTest.java
index 4ba7fb8..66db9bb 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkAspectsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkAspectsTest.java
@@ -1666,7 +1666,7 @@
   }
 
   @Test
-  public void aspectDescriptions() throws Exception {
+  public void aspectDescriptionsDeprecated() throws Exception {
     scratch.file(
         "test/aspect.bzl",
         "def _a_impl(target,ctx):",
@@ -1703,6 +1703,45 @@
         "//test:r1[]@//test:aspect.bzl%a=//test:r0[\"//test:aspect.bzl%a\"],True");
   }
 
+  @Test
+  public void aspectDescriptions() throws Exception {
+    scratch.file(
+        "test/aspect.bzl",
+        "def _a_impl(target,ctx):",
+        "  s = str(target.label) + str(ctx.aspect_ids) + '='",
+        "  value = []",
+        "  if ctx.rule.attr.dep:",
+        "     d = ctx.rule.attr.dep",
+        "     this_id = ctx.aspect_ids[len(ctx.aspect_ids) - 1]",
+        "     s += str(d.label) + str(d.my_ids) + ',' + str(this_id in d.aspect_ids)",
+        "     value += ctx.rule.attr.dep.ap",
+        "  else:",
+        "     s += 'None'",
+        "  value.append(s)",
+        "  return struct(ap = value, my_ids = ctx.aspect_ids)",
+        "a = aspect(_a_impl, attr_aspects = ['dep'])",
+        "def _r_impl(ctx):",
+        "  if not ctx.attr.dep:",
+        "     return struct(result = [])",
+        "  return struct(result = ctx.attr.dep.ap)",
+        "r = rule(_r_impl, attrs = { 'dep' : attr.label(aspects = [a])})"
+    );
+    scratch.file(
+        "test/BUILD",
+        "load(':aspect.bzl', 'r')",
+        "r(name = 'r0')",
+        "r(name = 'r1', dep = ':r0')",
+        "r(name = 'r2', dep = ':r1')"
+    );
+    AnalysisResult analysisResult = update("//test:r2");
+    ConfiguredTarget target = Iterables.getOnlyElement(analysisResult.getTargetsToBuild());
+    SkylarkList<?> result = (SkylarkList<?>) target.get("result");
+
+    assertThat(result).containsExactly(
+        "//test:r0[\"//test:aspect.bzl%a\"]=None",
+        "//test:r1[\"//test:aspect.bzl%a\"]=//test:r0[\"//test:aspect.bzl%a\"],True");
+  }
+
 
   @Test
   public void attributesWithAspectsReused() throws Exception {