Support declared providers for aspects

Fixes #2016

--
PiperOrigin-RevId: 149102037
MOS_MIGRATED_REVID=149102037
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredAspect.java b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredAspect.java
index 8578e76..48fdc0c 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredAspect.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredAspect.java
@@ -31,6 +31,7 @@
 import com.google.devtools.build.lib.packages.AspectParameters;
 import com.google.devtools.build.lib.packages.ClassObjectConstructor;
 import com.google.devtools.build.lib.packages.SkylarkClassObject;
+import com.google.devtools.build.lib.packages.SkylarkClassObjectConstructor;
 import com.google.devtools.build.lib.syntax.EvalException;
 import com.google.devtools.build.lib.util.Preconditions;
 import java.util.Arrays;
@@ -110,6 +111,8 @@
     private final Map<String, NestedSetBuilder<Artifact>> outputGroupBuilders = new TreeMap<>();
     private final ImmutableMap.Builder<String, Object> skylarkProviderBuilder =
         ImmutableMap.builder();
+    private final ImmutableMap.Builder<SkylarkClassObjectConstructor.Key, SkylarkClassObject>
+        skylarkDeclaredProvidersBuilder = ImmutableMap.builder();
     private final RuleContext ruleContext;
     private final AspectDescriptor descriptor;
 
@@ -195,6 +198,19 @@
       return this;
     }
 
+    public Builder addSkylarkDeclaredProvider(SkylarkClassObject declaredProvider, Location loc)
+        throws EvalException {
+      ClassObjectConstructor constructor = declaredProvider.getConstructor();
+      SkylarkProviderValidationUtil.validateAndThrowEvalException(
+          constructor.getPrintableName(), declaredProvider, loc);
+      if (!constructor.isExported()) {
+        throw new EvalException(
+            constructor.getLocation(), "All providers must be top level values");
+      }
+      skylarkDeclaredProvidersBuilder.put(constructor.getKey(), declaredProvider);
+      return this;
+    }
+
     public ConfiguredAspect build() {
       if (!outputGroupBuilders.isEmpty()) {
         ImmutableMap.Builder<String, NestedSet<Artifact>> outputGroups = ImmutableMap.builder();
@@ -210,10 +226,10 @@
       }
 
       ImmutableMap<String, Object> skylarkProvidersMap = skylarkProviderBuilder.build();
-      if (!skylarkProvidersMap.isEmpty()) {
-        providers.add(
-            new SkylarkProviders(skylarkProvidersMap,
-                ImmutableMap.<ClassObjectConstructor.Key, SkylarkClassObject>of()));
+      ImmutableMap<SkylarkClassObjectConstructor.Key, SkylarkClassObject>
+          skylarkDeclaredProvidersMap = skylarkDeclaredProvidersBuilder.build();
+      if (!skylarkProvidersMap.isEmpty() || !skylarkDeclaredProvidersMap.isEmpty()) {
+        providers.add(new SkylarkProviders(skylarkProvidersMap, skylarkDeclaredProvidersMap));
       }
 
       addProvider(
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/SkylarkProviders.java b/src/main/java/com/google/devtools/build/lib/analysis/SkylarkProviders.java
index be75e14..a568329 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/SkylarkProviders.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/SkylarkProviders.java
@@ -60,6 +60,11 @@
     return skylarkProviders.keySet();
   }
 
+  /** Returns the keys for the declared providers. */
+  public ImmutableCollection<ClassObjectConstructor.Key> getDeclaredProviderKeys() {
+    return declaredProviders.keySet();
+  }
+
   /**
    * Returns a Skylark provider; "key" must be one from {@link #getKeys()}.
    */
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java
index 3bf2cdc..809265e 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java
@@ -94,8 +94,7 @@
           && !(target instanceof Iterable)) {
         ruleContext.ruleError(
             String.format(
-                "Rule should return a return a struct or a list, but got %s",
-                SkylarkType.typeOf(target)));
+                "Rule should return a struct or a list, but got %s", SkylarkType.typeOf(target)));
         return null;
       } else if (!expectFailure.isEmpty()) {
         ruleContext.ruleError("Expected failure not found: " + expectFailure);
@@ -246,9 +245,13 @@
     } else if (target instanceof Iterable) {
       loc = ruleContext.getRule().getRuleClassObject().getConfiguredTargetFunction().getLocation();
       for (Object o : (Iterable) target) {
-        SkylarkClassObject declaredProvider = SkylarkType.cast(o, SkylarkClassObject.class, loc,
-            "A return value of rule implementation function should be "
-                + "a sequence of declared providers");
+        SkylarkClassObject declaredProvider =
+            SkylarkType.cast(
+                o,
+                SkylarkClassObject.class,
+                loc,
+                "A return value of a rule implementation function should be "
+                    + "a sequence of declared providers");
         if (declaredProvider.getConstructor().getKey().equals(
             SkylarkRuleContext.getDefaultProvider().getKey())) {
           parseProviderKeys(declaredProvider, true, ruleContext, loc, executable,
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkAspectFactory.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkAspectFactory.java
index e80a788..bd0900b 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkAspectFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkAspectFactory.java
@@ -31,6 +31,7 @@
 import com.google.devtools.build.lib.syntax.Environment;
 import com.google.devtools.build.lib.syntax.EvalException;
 import com.google.devtools.build.lib.syntax.EvalExceptionWithStackTrace;
+import com.google.devtools.build.lib.syntax.EvalUtils;
 import com.google.devtools.build.lib.syntax.Mutability;
 import com.google.devtools.build.lib.syntax.SkylarkType;
 import java.util.Map;
@@ -65,7 +66,7 @@
               .setGlobals(skylarkAspect.getFuncallEnv().getGlobals())
               .setEventHandler(ruleContext.getAnalysisEnvironment().getEventHandler())
               .build(); // NB: loading phase functions are not available: this is analysis already,
-                        // so we do *not* setLoadingPhase().
+      // so we do *not* setLoadingPhase().
       Object aspectSkylarkObject;
       try {
         aspectSkylarkObject =
@@ -79,26 +80,15 @@
 
         if (ruleContext.hasErrors()) {
           return null;
-        } else if (!(aspectSkylarkObject instanceof SkylarkClassObject)) {
-          ruleContext.ruleError("Aspect implementation doesn't return a struct");
+        } else if (!(aspectSkylarkObject instanceof SkylarkClassObject)
+            && !(aspectSkylarkObject instanceof Iterable)) {
+          ruleContext.ruleError(
+              String.format(
+                  "Aspect implementation should return a struct or a list, but got %s",
+                  SkylarkType.typeOf(aspectSkylarkObject)));
           return null;
         }
-
-        ConfiguredAspect.Builder builder = new ConfiguredAspect.Builder(
-            aspectDescriptor, ruleContext);
-
-        SkylarkClassObject struct = (SkylarkClassObject) aspectSkylarkObject;
-        Location loc = struct.getCreationLoc();
-        for (String key : struct.getKeys()) {
-          if (key.equals("output_groups")) {
-            addOutputGroups(struct.getValue(key), loc, builder);
-          } else {
-            builder.addSkylarkTransitiveInfo(key, struct.getValue(key), loc);
-          }
-        }
-        ConfiguredAspect configuredAspect = builder.build();
-        SkylarkProviderValidationUtil.checkOrphanArtifacts(ruleContext);
-        return configuredAspect;
+        return createAspect(aspectSkylarkObject, aspectDescriptor, ruleContext);
       } catch (EvalException e) {
         addAspectToStackTrace(base, e);
         ruleContext.ruleError("\n" + e.print());
@@ -107,6 +97,58 @@
     }
   }
 
+  private ConfiguredAspect createAspect(
+      Object aspectSkylarkObject, AspectDescriptor aspectDescriptor, RuleContext ruleContext)
+      throws EvalException {
+
+    ConfiguredAspect.Builder builder = new ConfiguredAspect.Builder(aspectDescriptor, ruleContext);
+
+    if (aspectSkylarkObject instanceof Iterable) {
+      addDeclaredProviders(builder, (Iterable) aspectSkylarkObject);
+    } else {
+      SkylarkClassObject struct = (SkylarkClassObject) aspectSkylarkObject;
+      Location loc = struct.getCreationLoc();
+      for (String key : struct.getKeys()) {
+        if (key.equals("output_groups")) {
+          addOutputGroups(struct.getValue(key), loc, builder);
+        } else if (key.equals("providers")) {
+          Object value = struct.getValue(key);
+          Iterable providers =
+              SkylarkType.cast(
+                  value,
+                  Iterable.class,
+                  loc,
+                  "The value for \"providers\" should be a list of declared providers, "
+                      + "got %s instead",
+                  EvalUtils.getDataTypeName(value, false));
+          addDeclaredProviders(builder, providers);
+        } else {
+          builder.addSkylarkTransitiveInfo(key, struct.getValue(key), loc);
+        }
+      }
+    }
+
+    ConfiguredAspect configuredAspect = builder.build();
+    SkylarkProviderValidationUtil.checkOrphanArtifacts(ruleContext);
+    return configuredAspect;
+  }
+
+  private void addDeclaredProviders(ConfiguredAspect.Builder builder, Iterable aspectSkylarkObject)
+      throws EvalException {
+    for (Object o : aspectSkylarkObject) {
+      Location loc = skylarkAspect.getImplementation().getLocation();
+      SkylarkClassObject declaredProvider =
+          SkylarkType.cast(
+              o,
+              SkylarkClassObject.class,
+              loc,
+              "A return value of an aspect implementation function should be "
+                  + "a sequence of declared providers");
+      Location creationLoc = declaredProvider.getCreationLocOrNull();
+      builder.addSkylarkDeclaredProvider(declaredProvider, creationLoc != null ? creationLoc : loc);
+    }
+  }
+
   private static void addOutputGroups(Object value, Location loc,
       ConfiguredAspect.Builder builder)
       throws EvalException {
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 9088a52..b5e951b 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
@@ -34,6 +34,8 @@
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.packages.AspectDefinition;
 import com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition;
+import com.google.devtools.build.lib.packages.ClassObjectConstructor.Key;
+import com.google.devtools.build.lib.packages.SkylarkClassObjectConstructor.SkylarkKey;
 import com.google.devtools.build.lib.rules.cpp.CppConfiguration;
 import com.google.devtools.build.lib.rules.java.Jvm;
 import com.google.devtools.build.lib.skyframe.AspectValue;
@@ -41,6 +43,7 @@
 import com.google.devtools.build.lib.syntax.SkylarkNestedSet;
 import com.google.devtools.build.lib.vfs.FileSystemUtils;
 import java.util.Arrays;
+import java.util.List;
 import javax.annotation.Nullable;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -74,6 +77,54 @@
         .containsExactly("//test:aspect.bzl%MyAspect(//test:xxx)");
   }
 
+  @Test
+  public void aspectWithDeclaredProviders() throws Exception {
+    scratch.file(
+        "test/aspect.bzl",
+        "foo = provider()",
+        "bar = provider()",
+        "def _impl(target, ctx):",
+        "   return [foo(), bar()]",
+        "MyAspect = aspect(implementation=_impl)");
+    scratch.file("test/BUILD", "java_library(name = 'xxx',)");
+
+    AnalysisResult analysisResult =
+        update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+    assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
+    assertThat(getAspectDescriptions(analysisResult))
+        .containsExactly("//test:aspect.bzl%MyAspect(//test:xxx)");
+
+    List<Key> providers = getDeclaredProviderKeys(analysisResult);
+    assertThat((providers.get(0)))
+        .isEqualTo(new SkylarkKey(Label.parseAbsolute("//test:aspect.bzl"), "foo"));
+    assertThat((providers.get(1)))
+        .isEqualTo(new SkylarkKey(Label.parseAbsolute("//test:aspect.bzl"), "bar"));
+  }
+
+  @Test
+  public void aspectWithDeclaredProvidersInAStruct() throws Exception {
+    scratch.file(
+        "test/aspect.bzl",
+        "foo = provider()",
+        "bar = provider()",
+        "def _impl(target, ctx):",
+        "   return struct(foobar='foobar', providers=[foo(), bar()])",
+        "MyAspect = aspect(implementation=_impl)");
+    scratch.file("test/BUILD", "java_library(name = 'xxx',)");
+
+    AnalysisResult analysisResult =
+        update(ImmutableList.of("test/aspect.bzl%MyAspect"), "//test:xxx");
+    assertThat(getLabelsToBuild(analysisResult)).containsExactly("//test:xxx");
+    assertThat(getAspectDescriptions(analysisResult))
+        .containsExactly("//test:aspect.bzl%MyAspect(//test:xxx)");
+
+    List<Key> providers = getDeclaredProviderKeys(analysisResult);
+    assertThat((providers.get(0)))
+        .isEqualTo(new SkylarkKey(Label.parseAbsolute("//test:aspect.bzl"), "foo"));
+    assertThat((providers.get(1)))
+        .isEqualTo(new SkylarkKey(Label.parseAbsolute("//test:aspect.bzl"), "bar"));
+  }
+
   private Iterable<String> getAspectDescriptions(AnalysisResult analysisResult) {
     return transform(
         analysisResult.getAspects(),
@@ -89,6 +140,25 @@
         });
   }
 
+  private List<Key> getDeclaredProviderKeys(AnalysisResult analysisResult) {
+    return transform(
+            analysisResult.getAspects(),
+            new Function<AspectValue, List<Key>>() {
+              @Nullable
+              @Override
+              public List<Key> apply(AspectValue aspectValue) {
+                return aspectValue
+                    .getConfiguredAspect()
+                    .getProviders()
+                    .getProvider(SkylarkProviders.class)
+                    .getDeclaredProviderKeys()
+                    .asList();
+              }
+            })
+        .iterator()
+        .next(); // Assume there's only one aspect
+  }
+
   @Test
   public void aspectCommandLineLabel() throws Exception {
     scratch.file(
@@ -585,7 +655,7 @@
     } catch (ViewCreationFailedException e) {
       // expect to fail.
     }
-    assertContainsEvent("Aspect implementation doesn't return a struct");
+    assertContainsEvent("Aspect implementation should return a struct or a list, but got int");
   }
 
   @Test