Implement OutputGroupInfo provider.

Work towards #2894.

RELNOTES: None.
PiperOrigin-RevId: 154829065
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/AnalysisUtils.java b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisUtils.java
index 01004d1..7159395 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/AnalysisUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisUtils.java
@@ -82,14 +82,15 @@
    * Returns the list of declared providers (native and Skylark) of the specified Skylark key from a
    * set of transitive info collections.
    */
-  public static Iterable<SkylarkClassObject> getProviders(
+  public static <T extends SkylarkClassObject>  Iterable<T> getProviders(
       Iterable<? extends TransitiveInfoCollection> prerequisites,
-      final ClassObjectConstructor.Key skylarkKey) {
-    ImmutableList.Builder<SkylarkClassObject> result = ImmutableList.builder();
+      final ClassObjectConstructor.Key skylarkKey,
+      Class<T> resultClass) {
+    ImmutableList.Builder<T> result = ImmutableList.builder();
     for (TransitiveInfoCollection prerequisite : prerequisites) {
       SkylarkClassObject prerequisiteProvider = prerequisite.get(skylarkKey);
       if (prerequisiteProvider != null) {
-        result.add(prerequisiteProvider);
+        result.add(resultClass.cast(prerequisiteProvider));
       }
     }
     return result.build();
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 6b7c031..18c00f9 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
@@ -30,11 +30,13 @@
 import com.google.devtools.build.lib.packages.AspectDescriptor;
 import com.google.devtools.build.lib.packages.AspectParameters;
 import com.google.devtools.build.lib.packages.ClassObjectConstructor;
+import com.google.devtools.build.lib.packages.ClassObjectConstructor.Key;
 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;
+import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.TreeMap;
 import javax.annotation.Nullable;
@@ -116,8 +118,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 LinkedHashMap<Key, SkylarkClassObject>
+        skylarkDeclaredProvidersBuilder = new LinkedHashMap<>();
     private final RuleContext ruleContext;
     private final AspectDescriptor descriptor;
 
@@ -212,6 +214,14 @@
       return this;
     }
 
+    public Builder addNativeDeclaredProvider(SkylarkClassObject declaredProvider) {
+      ClassObjectConstructor constructor = declaredProvider.getConstructor();
+      Preconditions.checkState(constructor.isExported());
+      skylarkDeclaredProvidersBuilder.put(constructor.getKey(), declaredProvider);
+      return this;
+    }
+
+
     public ConfiguredAspect build() {
       if (!outputGroupBuilders.isEmpty()) {
         ImmutableMap.Builder<String, NestedSet<Artifact>> outputGroups = ImmutableMap.builder();
@@ -219,16 +229,19 @@
           outputGroups.put(entry.getKey(), entry.getValue().build());
         }
 
-        if (providers.contains(OutputGroupProvider.class)) {
+        if (skylarkDeclaredProvidersBuilder.containsKey(
+            OutputGroupProvider.SKYLARK_CONSTRUCTOR.getKey())) {
           throw new IllegalStateException(
               "OutputGroupProvider was provided explicitly; do not use addOutputGroup");
         }
-        addProvider(new OutputGroupProvider(outputGroups.build()));
+        skylarkDeclaredProvidersBuilder.put(
+            OutputGroupProvider.SKYLARK_CONSTRUCTOR.getKey(),
+            new OutputGroupProvider(outputGroups.build()));
       }
 
       ImmutableMap<String, Object> skylarkProvidersMap = skylarkProviderBuilder.build();
       ImmutableMap<SkylarkClassObjectConstructor.Key, SkylarkClassObject>
-          skylarkDeclaredProvidersMap = skylarkDeclaredProvidersBuilder.build();
+          skylarkDeclaredProvidersMap = ImmutableMap.copyOf(skylarkDeclaredProvidersBuilder);
       if (!skylarkProvidersMap.isEmpty() || !skylarkDeclaredProvidersMap.isEmpty()) {
         providers.add(new SkylarkProviders(skylarkProvidersMap, skylarkDeclaredProvidersMap));
       }
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/MergedConfiguredTarget.java b/src/main/java/com/google/devtools/build/lib/analysis/MergedConfiguredTarget.java
index fcd4e91..2c0fcfb 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/MergedConfiguredTarget.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/MergedConfiguredTarget.java
@@ -17,6 +17,8 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.packages.SkylarkClassObject;
+import com.google.devtools.build.lib.packages.SkylarkClassObjectConstructor;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -85,16 +87,23 @@
 
     // Merge output group providers.
     OutputGroupProvider mergedOutputGroupProvider =
-        OutputGroupProvider.merge(getAllProviders(base, aspects, OutputGroupProvider.class));
+        OutputGroupProvider.merge(getAllOutputGroupProviders(base, aspects));
 
     // Merge Skylark providers.
-    ImmutableMap<String, Object> premergedProviders =
+    ImmutableMap<String, Object> premergedLegacyProviders =
         mergedOutputGroupProvider == null
-        ? ImmutableMap.<String, Object>of()
-        : ImmutableMap.<String, Object>of(
-            OutputGroupProvider.SKYLARK_NAME, mergedOutputGroupProvider);
+            ? ImmutableMap.<String, Object>of()
+            : ImmutableMap.<String, Object>of(
+                OutputGroupProvider.SKYLARK_NAME, mergedOutputGroupProvider);
+
+    ImmutableMap<SkylarkClassObjectConstructor.Key, SkylarkClassObject> premergedProviders =
+        mergedOutputGroupProvider == null
+        ? ImmutableMap.<SkylarkClassObjectConstructor.Key, SkylarkClassObject>of()
+        : ImmutableMap.<SkylarkClassObjectConstructor.Key, SkylarkClassObject>of(
+            OutputGroupProvider.SKYLARK_CONSTRUCTOR.getKey(), mergedOutputGroupProvider);
     SkylarkProviders mergedSkylarkProviders =
         SkylarkProviders.merge(
+            premergedLegacyProviders,
             premergedProviders,
             getAllProviders(base, aspects, SkylarkProviders.class));
 
@@ -103,9 +112,6 @@
         getAllProviders(base, aspects, ExtraActionArtifactsProvider.class));
 
     TransitiveInfoProviderMap.Builder aspectProviders = TransitiveInfoProviderMap.builder();
-    if (mergedOutputGroupProvider != null) {
-      aspectProviders.add(mergedOutputGroupProvider);
-    }
     if (mergedSkylarkProviders != null) {
       aspectProviders.add(mergedSkylarkProviders);
     }
@@ -117,8 +123,7 @@
       for (Map.Entry<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> entry :
           aspect.getProviders().entrySet()) {
         Class<? extends TransitiveInfoProvider> providerClass = entry.getKey();
-        if (OutputGroupProvider.class.equals(providerClass)
-            || SkylarkProviders.class.equals(providerClass)
+        if (SkylarkProviders.class.equals(providerClass)
             || ExtraActionArtifactsProvider.class.equals(providerClass)) {
           continue;
         }
@@ -133,6 +138,24 @@
     return new MergedConfiguredTarget(base, aspectProviders.build());
   }
 
+  private static ImmutableList<OutputGroupProvider> getAllOutputGroupProviders(
+      ConfiguredTarget base, Iterable<ConfiguredAspect> aspects) {
+    OutputGroupProvider baseProvider = OutputGroupProvider.get(base);
+    ImmutableList.Builder<OutputGroupProvider> providers = ImmutableList.builder();
+    if (baseProvider != null) {
+      providers.add(baseProvider);
+    }
+
+    for (ConfiguredAspect configuredAspect : aspects) {
+      OutputGroupProvider aspectProvider = OutputGroupProvider.get(configuredAspect);;
+      if (aspectProvider == null) {
+        continue;
+      }
+      providers.add(aspectProvider);
+    }
+    return providers.build();
+  }
+
   private static <T extends TransitiveInfoProvider> List<T> getAllProviders(
       ConfiguredTarget base, Iterable<ConfiguredAspect> aspects, Class<T> providerClass) {
     T baseProvider = base.getProvider(providerClass);
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/OutputGroupProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/OutputGroupProvider.java
index 7ba2eff..397d7fd 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/OutputGroupProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/OutputGroupProvider.java
@@ -16,6 +16,7 @@
 
 import static com.google.devtools.build.lib.syntax.EvalUtils.SKYLARK_COMPARATOR;
 
+import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSortedSet;
@@ -27,6 +28,9 @@
 import com.google.devtools.build.lib.collect.nestedset.Order;
 import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
 import com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.packages.NativeClassObjectConstructor;
+import com.google.devtools.build.lib.packages.SkylarkClassObject;
+import com.google.devtools.build.lib.rules.SkylarkRuleConfiguredTargetBuilder;
 import com.google.devtools.build.lib.syntax.EvalException;
 import com.google.devtools.build.lib.syntax.EvalUtils;
 import com.google.devtools.build.lib.syntax.SkylarkIndexable;
@@ -34,6 +38,8 @@
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 import javax.annotation.Nullable;
 
@@ -51,10 +57,12 @@
  * not mentioned on the output.
  */
 @Immutable
-public final class OutputGroupProvider implements
-    TransitiveInfoProvider, SkylarkIndexable, Iterable<String> {
+public final class OutputGroupProvider extends SkylarkClassObject
+    implements SkylarkIndexable, Iterable<String> {
   public static final String SKYLARK_NAME = "output_groups";
 
+  public static NativeClassObjectConstructor SKYLARK_CONSTRUCTOR = new Constructor();
+
   /**
    * Prefix for output groups that are not reported to the user on the terminal output of Blaze when
    * they are built.
@@ -113,9 +121,26 @@
   private final ImmutableMap<String, NestedSet<Artifact>> outputGroups;
 
   public OutputGroupProvider(ImmutableMap<String, NestedSet<Artifact>> outputGroups) {
+    super(SKYLARK_CONSTRUCTOR, ImmutableMap.<String, Object>of());
     this.outputGroups = outputGroups;
   }
 
+  @Nullable
+  public static OutputGroupProvider get(TransitiveInfoCollection collection) {
+    return (OutputGroupProvider) collection.get(SKYLARK_CONSTRUCTOR.getKey());
+  }
+
+  @Nullable
+  public static OutputGroupProvider get(ConfiguredAspect aspect) {
+    SkylarkProviders skylarkProviders = aspect.getProvider(SkylarkProviders.class);
+
+
+    return skylarkProviders != null
+        ? (OutputGroupProvider) skylarkProviders.getDeclaredProvider(SKYLARK_CONSTRUCTOR.getKey())
+        : null;
+  }
+
+
   /** Return the artifacts in a particular output group.
    *
    * @return the artifacts in the output group with the given name. The return value is never null.
@@ -210,7 +235,6 @@
           "Output group %s not present", key
       ));
     }
-
   }
 
   @Override
@@ -222,4 +246,51 @@
   public Iterator<String> iterator() {
     return SKYLARK_COMPARATOR.sortedCopy(outputGroups.keySet()).iterator();
   }
+
+  @Override
+  public Object getValue(String name) {
+    NestedSet<Artifact> result = outputGroups.get(name);
+    if (result == null) {
+      return null;
+    }
+    return SkylarkNestedSet.of(Artifact.class, result);
+  }
+
+  @Override
+  public ImmutableCollection<String> getKeys() {
+    return outputGroups.keySet();
+  }
+
+  /**
+   * A constructor callable from Skylark for OutputGroupProvider.
+   */
+  private static class Constructor extends NativeClassObjectConstructor {
+
+    private Constructor() {
+      super("OutputGroupInfo");
+    }
+
+    @Override
+    protected SkylarkClassObject createInstanceFromSkylark(Object[] args, Location loc)
+        throws EvalException {
+
+      @SuppressWarnings("unchecked")
+      Map<String, Object> kwargs = (Map<String, Object>) args[0];
+
+      ImmutableMap.Builder<String, NestedSet<Artifact>> builder = ImmutableMap.builder();
+      for (Entry<String, Object> entry : kwargs.entrySet()) {
+        builder.put(entry.getKey(),
+            SkylarkRuleConfiguredTargetBuilder.convertToOutputGroupValue(
+                loc, entry.getKey(), entry.getValue()));
+
+
+      }
+      return new OutputGroupProvider(builder.build());
+    }
+
+    @Override
+    public String getErrorMessageFormatForInstances() {
+      return "Output group %s not present";
+    }
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java
index 6a9c6d6..bdf8e10 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java
@@ -131,7 +131,7 @@
       }
 
       OutputGroupProvider outputGroupProvider = new OutputGroupProvider(outputGroups.build());
-      addProvider(OutputGroupProvider.class, outputGroupProvider);
+      addNativeDeclaredProvider(outputGroupProvider);
       addSkylarkTransitiveInfo(OutputGroupProvider.SKYLARK_NAME, outputGroupProvider);
     }
 
@@ -294,6 +294,9 @@
    * Adds a "declared provider" defined in Skylark to the rule.
    * Use this method for declared providers defined in Skyark.
    *
+   * Has special handling for {@link OutputGroupProvider}: that provider is not added
+   * from Skylark directly, instead its outpuyt groups are added.
+   *
    * Use {@link #addNativeDeclaredProvider(SkylarkClassObject)} in definitions of
    * native rules.
    */
@@ -304,7 +307,14 @@
       throw new EvalException(constructor.getLocation(),
           "All providers must be top level values");
     }
-    skylarkDeclaredProviders.put(constructor.getKey(), provider);
+    if (OutputGroupProvider.SKYLARK_CONSTRUCTOR.getKey().equals(constructor.getKey())) {
+      OutputGroupProvider outputGroupProvider = (OutputGroupProvider) provider;
+      for (String outputGroup : outputGroupProvider) {
+        addOutputGroup(outputGroup, outputGroupProvider.getOutputGroup(outputGroup));
+      }
+    } else {
+      skylarkDeclaredProviders.put(constructor.getKey(), provider);
+    }
     return this;
   }
 
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 f47b38d..30007e6 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
@@ -857,9 +857,11 @@
    * Returns all the declared providers (native and Skylark) for the specified constructor under the
    * specified attribute of this target in the BUILD file.
    */
-  public Iterable<SkylarkClassObject> getPrerequisites(
-      String attributeName, Mode mode, final ClassObjectConstructor.Key skylarkKey) {
-    return AnalysisUtils.getProviders(getPrerequisites(attributeName, mode), skylarkKey);
+  public <T extends SkylarkClassObject> Iterable<T> getPrerequisites(
+      String attributeName, Mode mode,
+      final ClassObjectConstructor.Key skylarkKey,
+      Class<T> result) {
+    return AnalysisUtils.getProviders(getPrerequisites(attributeName, mode), skylarkKey, result);
   }
 
   /**
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 05c9c34..0778ade 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
@@ -20,6 +20,7 @@
 import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
 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.packages.SkylarkProviderIdentifier;
 import com.google.devtools.build.lib.rules.SkylarkApiProvider;
 import com.google.devtools.build.lib.syntax.EvalException;
@@ -124,7 +125,8 @@
    * @param providers providers to merge {@code this} with.
    */
   public static SkylarkProviders merge(
-      Map<String, Object> premergedProviders,
+      ImmutableMap<String, Object> premergedLegacyProviders,
+      ImmutableMap<SkylarkClassObjectConstructor.Key, SkylarkClassObject> premergedProviders,
       List<SkylarkProviders> providers)
       throws DuplicateException {
     if (premergedProviders.size() == 0 && providers.size() == 0) {
@@ -136,11 +138,11 @@
 
     ImmutableMap<String, Object> skylarkProviders = mergeMaps(providers,
         SKYLARK_PROVIDERS_MAP_FUNCTION,
-        premergedProviders);
+        premergedLegacyProviders);
 
     ImmutableMap<ClassObjectConstructor.Key, SkylarkClassObject> declaredProviders =
         mergeMaps(providers, DECLARED_PROVIDERS_MAP_FUNCTION,
-            ImmutableMap.<ClassObjectConstructor.Key, SkylarkClassObject>of());
+            premergedProviders);
 
     return new SkylarkProviders(skylarkProviders, declaredProviders);
   }
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactHelper.java b/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactHelper.java
index 6665918..a9b77b4 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactHelper.java
@@ -169,7 +169,7 @@
   public static ArtifactsToBuild getAllArtifactsToBuild(TransitiveInfoCollection target,
       TopLevelArtifactContext context) {
     return getAllArtifactsToBuild(
-        target.getProvider(OutputGroupProvider.class),
+        OutputGroupProvider.get(target),
         target.getProvider(FileProvider.class),
         context
     );
@@ -179,7 +179,7 @@
       AspectValue aspectValue, TopLevelArtifactContext context) {
     ConfiguredAspect configuredAspect = aspectValue.getConfiguredAspect();
     return getAllArtifactsToBuild(
-        configuredAspect.getProvider(OutputGroupProvider.class),
+        OutputGroupProvider.get(configuredAspect),
         configuredAspect.getProvider(FileProvider.class),
         context);
   }