Make it possible for aspects to provide FileProvider.

This will then be merged with the FileProvider of the associated configured target.

RELNOTES[NEW]: aspects can now return DefaultInfo, which will then be merged with that of the configured target they are applied to. Currently, only the files= field is supported.

PiperOrigin-RevId: 661221846
Change-Id: I15a95220b9ea263e3e2125f80c8acadd80078565
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/AspectBaseTargetResolvedToolchainContext.java b/src/main/java/com/google/devtools/build/lib/analysis/AspectBaseTargetResolvedToolchainContext.java
index e27fd48..3fb9985 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/AspectBaseTargetResolvedToolchainContext.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/AspectBaseTargetResolvedToolchainContext.java
@@ -20,6 +20,7 @@
 import com.google.common.collect.ImmutableMultimap;
 import com.google.common.collect.Iterables;
 import com.google.devtools.build.lib.analysis.configuredtargets.MergedConfiguredTarget;
+import com.google.devtools.build.lib.analysis.configuredtargets.MergedConfiguredTarget.MergingException;
 import com.google.devtools.build.lib.analysis.platform.ToolchainTypeInfo;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.packages.Provider;
@@ -47,7 +48,7 @@
       UnloadedToolchainContext unloadedToolchainContext,
       String targetDescription,
       ImmutableMultimap<ToolchainTypeInfo, ConfiguredTargetAndData> toolchainTargets)
-      throws DuplicateException {
+      throws MergingException {
 
     ImmutableMap.Builder<ToolchainTypeInfo, ToolchainAspectsProviders> toolchainsBuilder =
         new ImmutableMap.Builder<>();
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BUILD b/src/main/java/com/google/devtools/build/lib/analysis/BUILD
index 59a75aa..177b241 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/analysis/BUILD
@@ -89,7 +89,6 @@
         ":constraints/supported_environments_provider",
         ":constraints/top_level_constraint_semantics",
         ":dependency_kind",
-        ":duplicate_exception",
         ":extra/extra_action_info_file_write_action",
         ":extra_action_artifacts_provider",
         ":file_provider",
@@ -305,7 +304,6 @@
         ":constraints/supported_environments",
         ":constraints/supported_environments_provider",
         ":dependency_kind",
-        ":duplicate_exception",
         ":exec_group_collection",
         ":extra/extra_action_info_file_write_action",
         ":extra_action_artifacts_provider",
@@ -751,11 +749,6 @@
 )
 
 java_library(
-    name = "duplicate_exception",
-    srcs = ["DuplicateException.java"],
-)
-
-java_library(
     name = "exec_group_collection",
     srcs = ["ExecGroupCollection.java"],
     deps = [
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/DuplicateException.java b/src/main/java/com/google/devtools/build/lib/analysis/DuplicateException.java
deleted file mode 100644
index 3ad5cc1..0000000
--- a/src/main/java/com/google/devtools/build/lib/analysis/DuplicateException.java
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2020 The Bazel Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//    http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-package com.google.devtools.build.lib.analysis;
-
-/**
- * This exception is thrown when configured targets and aspects being merged provide duplicate
- * things that they shouldn't (output groups or providers).
- */
-public final class DuplicateException extends Exception {
-  public DuplicateException(String message) {
-    super(message);
-  }
-}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/OutputGroupInfo.java b/src/main/java/com/google/devtools/build/lib/analysis/OutputGroupInfo.java
index e38e157..85de020 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/OutputGroupInfo.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/OutputGroupInfo.java
@@ -26,6 +26,7 @@
 import com.google.common.collect.Iterators;
 import com.google.common.collect.Sets;
 import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.configuredtargets.MergedConfiguredTarget.MergingException;
 import com.google.devtools.build.lib.analysis.starlark.StarlarkRuleConfiguredTargetUtil;
 import com.google.devtools.build.lib.collect.ImmutableSharedKeyMap;
 import com.google.devtools.build.lib.collect.nestedset.Depset;
@@ -178,7 +179,7 @@
    * disjoint, except for the special validation output group, which is always merged.
    */
   @Nullable
-  public static OutputGroupInfo merge(List<OutputGroupInfo> providers) throws DuplicateException {
+  public static OutputGroupInfo merge(List<OutputGroupInfo> providers) throws MergingException {
     if (providers.isEmpty()) {
       return null;
     }
@@ -193,7 +194,7 @@
           continue;
         }
         if (outputGroups.put(group, provider.getOutputGroup(group)) != null) {
-          throw new DuplicateException("Output group " + group + " provided twice");
+          throw new MergingException("Output group " + group + " provided twice");
         }
       }
     }
@@ -204,7 +205,7 @@
         validationOutputs.addTransitive(provider.getOutputGroup(VALIDATION));
       } catch (IllegalArgumentException e) {
         // Thrown if nested set orders aren't compatible.
-        throw new DuplicateException(
+        throw new MergingException(
             "Output group " + VALIDATION + " provided twice with incompatible depset orders");
       }
     }
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/configuredtargets/MergedConfiguredTarget.java b/src/main/java/com/google/devtools/build/lib/analysis/configuredtargets/MergedConfiguredTarget.java
index 4f64db4..801e48b 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/configuredtargets/MergedConfiguredTarget.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/configuredtargets/MergedConfiguredTarget.java
@@ -16,11 +16,13 @@
 import com.google.common.collect.ImmutableList;
 import com.google.devtools.build.lib.actions.ActionAnalysisMetadata;
 import com.google.devtools.build.lib.actions.ActionLookupKey;
+import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.analysis.AnalysisUtils;
 import com.google.devtools.build.lib.analysis.ConfiguredAspect;
 import com.google.devtools.build.lib.analysis.ConfiguredTarget;
-import com.google.devtools.build.lib.analysis.DuplicateException;
+import com.google.devtools.build.lib.analysis.DefaultInfo;
 import com.google.devtools.build.lib.analysis.ExtraActionArtifactsProvider;
+import com.google.devtools.build.lib.analysis.FileProvider;
 import com.google.devtools.build.lib.analysis.OutputGroupInfo;
 import com.google.devtools.build.lib.analysis.RequiredConfigFragmentsProvider;
 import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
@@ -30,7 +32,9 @@
 import com.google.devtools.build.lib.analysis.test.AnalysisFailureInfo;
 import com.google.devtools.build.lib.analysis.test.InstrumentedFilesInfo;
 import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.collect.nestedset.Depset.TypeException;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
 import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
 import com.google.devtools.build.lib.packages.Info;
 import com.google.devtools.build.lib.packages.Provider;
@@ -52,8 +56,23 @@
  */
 @Immutable
 public final class MergedConfiguredTarget extends AbstractConfiguredTarget {
+  /**
+   * This exception is thrown when the providers of a configured target and the aspects applied to
+   * it cannot be merged.
+   */
+  public static final class MergingException extends Exception {
+    public MergingException(String message) {
+      super(message);
+    }
+
+    public MergingException(String message, Throwable cause) {
+      super(message, cause);
+    }
+  }
+
   private final ConfiguredTarget base;
   private final ImmutableList<ConfiguredAspect> aspects;
+
   /**
    * Providers that come from any source that isn't a pure pointer to the base rule's providers.
    *
@@ -160,13 +179,50 @@
 
   /** Creates an instance based on a configured target and a set of aspects. */
   public static ConfiguredTarget of(ConfiguredTarget base, Collection<ConfiguredAspect> aspects)
-      throws DuplicateException {
+      throws MergingException {
     if (aspects.isEmpty()) {
       return base; // If there are no aspects, don't bother with creating a proxy object.
     }
 
     TransitiveInfoProviderMapBuilder nonBaseProviders = new TransitiveInfoProviderMapBuilder();
 
+    // filesToBuild is special: for native aspects, it is returned in a FileProvider, for Starlark
+    // ones, in a DefaultInfo. Furthermore, DefaultInfo is not a "normal" provider on configured
+    // targets but is created on demand from FileProvider, etc. So we need to jump through some
+    // hoops here.
+    List<NestedSet<Artifact>> filesToBuild = new ArrayList<>();
+    filesToBuild.add(base.getProvider(FileProvider.class).getFilesToBuild());
+    for (ConfiguredAspect aspect : aspects) {
+      if (aspect.getProvider(FileProvider.class) != null) {
+        filesToBuild.add(aspect.getProvider(FileProvider.class).getFilesToBuild());
+      } else if (aspect.get(DefaultInfo.PROVIDER.getKey()) != null) {
+        DefaultInfo defaultInfo = (DefaultInfo) aspect.get(DefaultInfo.PROVIDER.getKey());
+        if (defaultInfo.getDataRunfiles() != null
+            || defaultInfo.getDefaultRunfiles() != null
+            || defaultInfo.getExecutable() != null
+            || defaultInfo.getFilesToRun() != null) {
+          throw new MergingException(
+              "Provider 'DefaultInfo' returned by an aspect not at top level must only have the "
+                  + "'files' field set");
+        }
+
+        if (defaultInfo.getFiles() != null) {
+          try {
+            filesToBuild.add(defaultInfo.getFiles().getSet(Artifact.class));
+          } catch (TypeException e) {
+            throw new MergingException(
+                "'files' field of 'DefaultInfo' should contain a depset of files", e);
+          }
+        }
+      }
+    }
+
+    if (filesToBuild.size() > 1) {
+      nonBaseProviders.put(
+          FileProvider.class,
+          FileProvider.of(NestedSetBuilder.fromNestedSets(filesToBuild).build()));
+    }
+
     // Merge output group providers.
     OutputGroupInfo mergedOutputGroupInfo = mergeOutputGroupProviders(base, aspects);
     if (mergedOutputGroupInfo != null) {
@@ -199,23 +255,27 @@
         Object providerKey = providers.getProviderKeyAt(i);
         if (OutputGroupInfo.STARLARK_CONSTRUCTOR.getKey().equals(providerKey)
             || AnalysisFailureInfo.STARLARK_CONSTRUCTOR.getKey().equals(providerKey)
+            || FileProvider.class.equals(providerKey)
             || ExtraActionArtifactsProvider.class.equals(providerKey)
             || RequiredConfigFragmentsProvider.class.equals(providerKey)) {
           continue;
         }
 
-        if (providerKey instanceof Class<?>) {
+        if (providerKey.equals(DefaultInfo.PROVIDER.getKey())) {
+          // This was handled when creating FileProvider above.
+          continue;
+        } else if (providerKey instanceof Class<?>) {
           @SuppressWarnings("unchecked")
           Class<? extends TransitiveInfoProvider> providerClass =
               (Class<? extends TransitiveInfoProvider>) providerKey;
           if (base.getProvider(providerClass) != null || nonBaseProviders.contains(providerClass)) {
-            throw new DuplicateException("Provider " + providerKey + " provided twice");
+            throw new MergingException("Provider " + providerKey + " provided twice");
           }
           nonBaseProviders.put(
               providerClass, (TransitiveInfoProvider) providers.getProviderInstanceAt(i));
         } else if (providerKey instanceof String legacyId) {
           if (base.get(legacyId) != null || nonBaseProviders.contains(legacyId)) {
-            throw new DuplicateException("Provider " + legacyId + " provided twice");
+            throw new MergingException("Provider " + legacyId + " provided twice");
           }
           nonBaseProviders.put(legacyId, providers.getProviderInstanceAt(i));
         } else if (providerKey instanceof Provider.Key key) {
@@ -227,7 +287,7 @@
           if ((!InstrumentedFilesInfo.STARLARK_CONSTRUCTOR.getKey().equals(key)
                   && base.get(key) != null)
               || nonBaseProviders.contains(key)) {
-            throw new DuplicateException("Provider " + key + " provided twice");
+            throw new MergingException("Provider " + key + " provided twice");
           }
           nonBaseProviders.put((Info) providers.getProviderInstanceAt(i));
         }
@@ -237,8 +297,7 @@
   }
 
   private static OutputGroupInfo mergeOutputGroupProviders(
-      @Nullable ConfiguredTarget base, Iterable<ConfiguredAspect> aspects)
-      throws DuplicateException {
+      @Nullable ConfiguredTarget base, Iterable<ConfiguredAspect> aspects) throws MergingException {
     ImmutableList.Builder<OutputGroupInfo> providers = ImmutableList.builder();
 
     if (base != null) {
@@ -321,7 +380,7 @@
   }
 
   /** Returns only the providers from the aspects. */
-  public TransitiveInfoProviderMap getAspectsProviders() throws DuplicateException {
+  public TransitiveInfoProviderMap getAspectsProviders() throws MergingException {
     TransitiveInfoProviderMapBuilder aspectsProviders = new TransitiveInfoProviderMapBuilder();
 
     // Merge output group providers of aspects only. Filtering the base target output
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/producers/BUILD b/src/main/java/com/google/devtools/build/lib/analysis/producers/BUILD
index 2849660..a7bc9ed 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/producers/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/analysis/producers/BUILD
@@ -46,7 +46,6 @@
         "//src/main/java/com/google/devtools/build/lib/analysis:configured_target_value",
         "//src/main/java/com/google/devtools/build/lib/analysis:constraints/incompatible_target_checker",
         "//src/main/java/com/google/devtools/build/lib/analysis:dependency_kind",
-        "//src/main/java/com/google/devtools/build/lib/analysis:duplicate_exception",
         "//src/main/java/com/google/devtools/build/lib/analysis:exec_group_collection",
         "//src/main/java/com/google/devtools/build/lib/analysis:inconsistent_aspect_order_exception",
         "//src/main/java/com/google/devtools/build/lib/analysis:inconsistent_null_config_exception",
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/producers/ConfiguredAspectProducer.java b/src/main/java/com/google/devtools/build/lib/analysis/producers/ConfiguredAspectProducer.java
index e34533c..14d8935 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/producers/ConfiguredAspectProducer.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/producers/ConfiguredAspectProducer.java
@@ -20,9 +20,9 @@
 import com.google.devtools.build.lib.analysis.AspectCollection.AspectDeps;
 import com.google.devtools.build.lib.analysis.AspectValue;
 import com.google.devtools.build.lib.analysis.ConfiguredAspect;
-import com.google.devtools.build.lib.analysis.DuplicateException;
 import com.google.devtools.build.lib.analysis.TransitiveDependencyState;
 import com.google.devtools.build.lib.analysis.configuredtargets.MergedConfiguredTarget;
+import com.google.devtools.build.lib.analysis.configuredtargets.MergedConfiguredTarget.MergingException;
 import com.google.devtools.build.lib.packages.AspectDescriptor;
 import com.google.devtools.build.lib.skyframe.AspectCreationException;
 import com.google.devtools.build.lib.skyframe.AspectKeyCreator.AspectKey;
@@ -42,7 +42,7 @@
 
     void acceptConfiguredAspectError(AspectCreationException error);
 
-    void acceptConfiguredAspectError(DuplicateException error);
+    void acceptConfiguredAspectError(MergingException error);
   }
 
   // -------------------- Input --------------------
@@ -117,7 +117,7 @@
           outputIndex,
           prerequisite.fromConfiguredTarget(
               MergedConfiguredTarget.of(prerequisite.getConfiguredTarget(), configuredAspects)));
-    } catch (DuplicateException e) {
+    } catch (MergingException e) {
       sink.acceptConfiguredAspectError(e);
     }
     return DONE;
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/producers/PrerequisitesProducer.java b/src/main/java/com/google/devtools/build/lib/analysis/producers/PrerequisitesProducer.java
index bc2c047..ac198ed 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/producers/PrerequisitesProducer.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/producers/PrerequisitesProducer.java
@@ -22,11 +22,11 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.devtools.build.lib.analysis.AspectCollection;
-import com.google.devtools.build.lib.analysis.DuplicateException;
 import com.google.devtools.build.lib.analysis.InconsistentAspectOrderException;
 import com.google.devtools.build.lib.analysis.InconsistentNullConfigException;
 import com.google.devtools.build.lib.analysis.InvalidVisibilityDependencyException;
 import com.google.devtools.build.lib.analysis.config.DependencyEvaluationException;
+import com.google.devtools.build.lib.analysis.configuredtargets.MergedConfiguredTarget.MergingException;
 import com.google.devtools.build.lib.analysis.configuredtargets.PackageGroupConfiguredTarget;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.packages.Aspect;
@@ -243,7 +243,7 @@
   }
 
   @Override
-  public void acceptConfiguredAspectError(DuplicateException error) {
+  public void acceptConfiguredAspectError(MergingException error) {
     hasError = true;
     sink.acceptPrerequisitesAspectError(
         new DependencyEvaluationException(
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java
index 18095b3..05a5cdc 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java
@@ -35,7 +35,6 @@
 import com.google.devtools.build.lib.analysis.ConfiguredTarget;
 import com.google.devtools.build.lib.analysis.ConfiguredTargetValue;
 import com.google.devtools.build.lib.analysis.DependencyKind;
-import com.google.devtools.build.lib.analysis.DuplicateException;
 import com.google.devtools.build.lib.analysis.ExecGroupCollection;
 import com.google.devtools.build.lib.analysis.ExecGroupCollection.InvalidExecGroupException;
 import com.google.devtools.build.lib.analysis.IncompatiblePlatformProvider;
@@ -50,6 +49,7 @@
 import com.google.devtools.build.lib.analysis.config.StarlarkExecTransitionLoader;
 import com.google.devtools.build.lib.analysis.config.StarlarkExecTransitionLoader.StarlarkExecTransitionLoadingException;
 import com.google.devtools.build.lib.analysis.configuredtargets.MergedConfiguredTarget;
+import com.google.devtools.build.lib.analysis.configuredtargets.MergedConfiguredTarget.MergingException;
 import com.google.devtools.build.lib.analysis.platform.ToolchainTypeInfo;
 import com.google.devtools.build.lib.analysis.producers.DependencyContext;
 import com.google.devtools.build.lib.analysis.producers.DependencyContextProducer;
@@ -338,7 +338,7 @@
           Lists.transform(key.getBaseKeys(), k -> ((AspectValue) aspectValues.get(k)));
       try {
         associatedTarget = MergedConfiguredTarget.of(associatedTarget, directlyRequiredAspects);
-      } catch (DuplicateException e) {
+      } catch (MergingException e) {
         env.getListener().handle(Event.error(target.getLocation(), e.getMessage()));
         throw new AspectFunctionException(
             new AspectCreationException(e.getMessage(), target.getLabel(), configuration));
@@ -426,7 +426,7 @@
         baseTargetToolchainContexts =
             getBaseTargetToolchainContexts(
                 baseTargetUnloadedToolchainContexts, aspect, target, depValueMap);
-      } catch (DuplicateException e) {
+      } catch (MergingException e) {
         env.getListener().handle(Event.error(target.getLocation(), e.getMessage()));
         throw new AspectFunctionException(
             new AspectCreationException(e.getMessage(), target.getLabel(), configuration));
@@ -487,7 +487,7 @@
           Aspect aspect,
           Target target,
           OrderedSetMultimap<DependencyKind, ConfiguredTargetAndData> depValueMap)
-          throws DuplicateException {
+          throws MergingException {
     if (baseTargetUnloadedToolchainContexts == null) {
       return null;
     }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/BUILD b/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
index 7bf8943..f8da79a 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
@@ -264,7 +264,6 @@
         "//src/main/java/com/google/devtools/build/lib/analysis:configured_target_value",
         "//src/main/java/com/google/devtools/build/lib/analysis:constraints/incompatible_target_checker",
         "//src/main/java/com/google/devtools/build/lib/analysis:dependency_kind",
-        "//src/main/java/com/google/devtools/build/lib/analysis:duplicate_exception",
         "//src/main/java/com/google/devtools/build/lib/analysis:exec_group_collection",
         "//src/main/java/com/google/devtools/build/lib/analysis:incompatible_platform_provider",
         "//src/main/java/com/google/devtools/build/lib/analysis:inconsistent_aspect_order_exception",
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/AspectTest.java b/src/test/java/com/google/devtools/build/lib/analysis/AspectTest.java
index 7486f01..e28262a 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/AspectTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/AspectTest.java
@@ -129,6 +129,18 @@
   }
 
   @Test
+  public void fileProviderMerged() throws Exception {
+    setRulesAvailableInTests(
+        TestAspects.BASE_RULE, TestAspects.FILE_PROVIDER_ASPECT_REQUIRING_RULE);
+    pkg("a", "file_provider_aspect(name='a', dep=':b')", "filegroup(name='b', srcs=['source'])");
+
+    ConfiguredTarget a = getConfiguredTarget("//a:a");
+    NestedSet<Artifact> filesToBuild = a.getProvider(FileProvider.class).getFilesToBuild();
+    assertThat(ActionsTestUtil.baseArtifactNames(filesToBuild))
+        .containsExactly("file_provider_aspect_file", "source");
+  }
+
+  @Test
   public void providersOfAspectAreMergedIntoDependency() throws Exception {
     setRulesAvailableInTests(TestAspects.BASE_RULE, TestAspects.ASPECT_REQUIRING_RULE);
     pkg("a",
@@ -172,10 +184,6 @@
         .containsExactly("rule //a:a", "aspect //a:b", "aspect //a:c");
   }
 
-
-
-
-
   @Test
   public void aspectCreationWorksThroughBind() throws Exception {
     if (getInternalTestExecutionMode() != TestConstants.InternalTestExecutionMode.NORMAL) {
@@ -1322,36 +1330,42 @@
     scratch.file(
         "aspect/build_defs.bzl",
         """
+        DuplicateInfo = provider(fields=[])
         def _aspect_impl(target, ctx):
-            return [DefaultInfo()]
+            return [DuplicateInfo()]
 
-        returns_default_info_aspect = aspect(implementation = _aspect_impl)
+        returns_duplicate_aspect = aspect(implementation = _aspect_impl)
+
+        def _duplicate_rule_impl(ctx):
+          return [DefaultInfo(), DuplicateInfo()]
+
+        duplicate_rule = rule(implementation = _duplicate_rule_impl, attrs = {})
 
         def _rule_impl(ctx):
             pass
 
-        duplicate_provider_aspect_applying_rule = rule(
+        duplicate_aspect_applying_rule = rule(
             implementation = _rule_impl,
-            attrs = {"to": attr.label(aspects = [returns_default_info_aspect])},
+            attrs = {"to": attr.label(aspects = [returns_duplicate_aspect])},
         )
         """);
     scratch.file(
         "aspect/BUILD",
         """
-        load("build_defs.bzl", "duplicate_provider_aspect_applying_rule")
+        load("build_defs.bzl", "duplicate_aspect_applying_rule", "duplicate_rule")
 
-        cc_library(name = "rule_target")
+        duplicate_rule(name = "duplicate")
 
-        duplicate_provider_aspect_applying_rule(
+        duplicate_aspect_applying_rule(
             name = "applies_aspect",
-            to = ":rule_target",
+            to = ":duplicate",
         )
         """);
     assertThat(
             assertThrows(
                 AssertionError.class, () -> getConfiguredTarget("//aspect:applies_aspect")))
         .hasMessageThat()
-        .contains("Provider DefaultInfo provided twice");
+        .contains("Provider DuplicateInfo provided twice");
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestCase.java b/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestCase.java
index d6707e0..f64710c 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestCase.java
@@ -710,6 +710,7 @@
             TestAspects.EXTRA_ATTRIBUTE_ASPECT,
             TestAspects.PACKAGE_GROUP_ATTRIBUTE_ASPECT,
             TestAspects.COMPUTED_ATTRIBUTE_ASPECT,
+            TestAspects.FILE_PROVIDER_ASPECT,
             TestAspects.FOO_PROVIDER_ASPECT,
             TestAspects.ASPECT_REQUIRING_PROVIDER_SETS,
             TestAspects.WARNING_ASPECT,
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 7e1dd40..50c8d83 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
@@ -31,6 +31,7 @@
 import com.google.devtools.build.lib.analysis.ConfiguredAspect;
 import com.google.devtools.build.lib.analysis.ConfiguredAspectFactory;
 import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.FileProvider;
 import com.google.devtools.build.lib.analysis.PackageSpecificationProvider;
 import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
 import com.google.devtools.build.lib.analysis.RuleConfiguredTargetFactory;
@@ -39,6 +40,7 @@
 import com.google.devtools.build.lib.analysis.RunfilesProvider;
 import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
 import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
+import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
 import com.google.devtools.build.lib.cmdline.RepositoryName;
@@ -170,6 +172,18 @@
     return result.build();
   }
 
+  public static class FileProviderForwardingRuleFactory implements RuleConfiguredTargetFactory {
+    @Override
+    public ConfiguredTarget create(RuleContext ruleContext)
+        throws InterruptedException, RuleErrorException, ActionConflictException {
+      return new RuleConfiguredTargetBuilder(ruleContext)
+          .setFilesToBuild(ruleContext.getPrerequisite("dep", FileProvider.class).getFilesToBuild())
+          .setRunfilesSupport(null, null)
+          .addProvider(RunfilesProvider.class, RunfilesProvider.simple(Runfiles.EMPTY))
+          .build();
+    }
+  }
+
   /**
    * A simple rule configured target factory that is used in all the mock rules in this class.
    */
@@ -273,7 +287,30 @@
     }
   }
 
+  public static class FileProviderAspect extends BaseAspect {
+    @Override
+    public ConfiguredAspect create(
+        Label targetLabel,
+        ConfiguredTarget ct,
+        RuleContext ruleContext,
+        AspectParameters parameters,
+        RepositoryName toolsRepository)
+        throws ActionConflictException, InterruptedException {
+      Artifact artifact = ruleContext.getBinArtifact("file_provider_aspect_file");
+      ruleContext.registerAction(FileWriteAction.create(ruleContext, artifact, "empty", false));
+      return new ConfiguredAspect.Builder(ruleContext)
+          .addProvider(FileProvider.of(NestedSetBuilder.create(Order.STABLE_ORDER, artifact)))
+          .build();
+    }
+
+    @Override
+    public AspectDefinition getDefinition(AspectParameters aspectParameters) {
+      return SIMPLE_ASPECT_DEFINITION;
+    }
+  }
+
   public static final SimpleAspect SIMPLE_ASPECT = new SimpleAspect();
+  public static final FileProviderAspect FILE_PROVIDER_ASPECT = new FileProviderAspect();
   public static final FooProviderAspect FOO_PROVIDER_ASPECT = new FooProviderAspect();
   public static final BarProviderAspect BAR_PROVIDER_ASPECT = new BarProviderAspect();
   public static final SimpleStarlarkNativeAspect SIMPLE_STARLARK_NATIVE_ASPECT =
@@ -803,6 +840,16 @@
   public static final MockRule BASE_RULE = () ->
       MockRule.factory(DummyRuleFactory.class).define("base");
 
+  public static final MockRule FILE_PROVIDER_ASPECT_REQUIRING_RULE =
+      () ->
+          MockRule.ancestor(BASE_RULE.getClass())
+              .factory(FileProviderForwardingRuleFactory.class)
+              .define(
+                  "file_provider_aspect",
+                  attr("dep", LABEL)
+                      .allowedFileTypes(FileTypeSet.ANY_FILE)
+                      .aspect(FILE_PROVIDER_ASPECT));
+
   /**
    * A rule that defines an aspect on one of its attributes.
    */
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 8569258..4006079 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,11 +29,13 @@
 import com.google.devtools.build.lib.actions.Action;
 import com.google.devtools.build.lib.actions.ActionOwner;
 import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
 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.FileProvider;
 import com.google.devtools.build.lib.analysis.OutputGroupInfo;
 import com.google.devtools.build.lib.analysis.ViewCreationFailedException;
 import com.google.devtools.build.lib.analysis.util.AnalysisTestCase;
@@ -1091,6 +1093,127 @@
   }
 
   @Test
+  public void mergesFileProvider() throws Exception {
+    scratch.file(
+        "test/aspect.bzl",
+        """
+        def _asp_impl(target, ctx):
+          f = ctx.actions.declare_file('aspectfile')
+          ctx.actions.write(f, 'f')
+          return [DefaultInfo(files=depset([f]))]
+
+        asp = aspect(implementation=_asp_impl)
+        def _rule_impl(ctx):
+          return [DefaultInfo(files=depset(ctx.files.dep))]
+        my_rule = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [asp]) })
+        """);
+    scratch.file(
+        "test/BUILD",
+        """
+        load(':aspect.bzl', 'my_rule')
+        my_rule(name='a', dep=':b')
+        filegroup(name='b', srcs=['sourcefile'])
+        """);
+
+    AnalysisResult analysisResult = update("//test:a");
+    NestedSet<Artifact> filesToBuild =
+        Iterables.getOnlyElement(analysisResult.getTargetsToBuild())
+            .getProvider(FileProvider.class)
+            .getFilesToBuild();
+    assertThat(ActionsTestUtil.baseArtifactNames(filesToBuild))
+        .containsExactly("sourcefile", "aspectfile");
+  }
+
+  @Test
+  public void aspectDefaultInfoWithNonArtifacts() throws Exception {
+    scratch.file(
+        "test/aspect.bzl",
+        """
+        def _asp_impl(target, ctx):
+          return [DefaultInfo(files=depset([1]))]
+
+        asp = aspect(implementation=_asp_impl)
+        def _rule_impl(ctx):
+          return [DefaultInfo()]
+        my_rule = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [asp]) })
+        """);
+    scratch.file(
+        "test/BUILD",
+        """
+        load(':aspect.bzl', 'my_rule')
+        my_rule(name='a', dep=':b')
+        filegroup(name='b', srcs=['sourcefile'])
+        """);
+
+    reporter.removeHandler(failFastHandler);
+    try {
+      update("//test:a");
+    } catch (ViewCreationFailedException e) {
+      // expected.
+    }
+    assertContainsEvent("should contain a depset of files");
+  }
+
+  @Test
+  public void aspectDefaultInfoSomethingElseThanFiles() throws Exception {
+    scratch.file(
+        "test/aspect.bzl",
+        """
+        def _asp_impl(target, ctx):
+          return [DefaultInfo(runfiles=ctx.runfiles([]))]
+
+        asp = aspect(implementation=_asp_impl)
+        def _rule_impl(ctx):
+          return [DefaultInfo()]
+        my_rule = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [asp]) })
+        """);
+    scratch.file(
+        "test/BUILD",
+        """
+        load(':aspect.bzl', 'my_rule')
+        my_rule(name='a', dep=':b')
+        filegroup(name='b', srcs=['sourcefile'])
+        """);
+
+    reporter.removeHandler(failFastHandler);
+    try {
+      update("//test:a");
+    } catch (ViewCreationFailedException e) {
+      // expected.
+    }
+    assertContainsEvent("must only have the 'files' field set");
+  }
+
+  @Test
+  public void aspectEmptyDefaultInfo() throws Exception {
+    scratch.file(
+        "test/aspect.bzl",
+        """
+        def _asp_impl(target, ctx):
+          return [DefaultInfo()]
+
+        asp = aspect(implementation=_asp_impl)
+        def _rule_impl(ctx):
+          return [DefaultInfo(files=depset(ctx.files.dep))]
+        my_rule = rule(_rule_impl, attrs = { 'dep' : attr.label(aspects = [asp]) })
+        """);
+    scratch.file(
+        "test/BUILD",
+        """
+        load(':aspect.bzl', 'my_rule')
+        my_rule(name='a', dep=':b')
+        filegroup(name='b', srcs=['sourcefile'])
+        """);
+
+    AnalysisResult analysisResult = update("//test:a");
+    NestedSet<Artifact> filesToBuild =
+        Iterables.getOnlyElement(analysisResult.getTargetsToBuild())
+            .getProvider(FileProvider.class)
+            .getFilesToBuild();
+    assertThat(ActionsTestUtil.baseArtifactNames(filesToBuild)).containsExactly("sourcefile");
+  }
+
+  @Test
   public void outputGroupsFromOneAspect() throws Exception {
     scratch.file(
         "test/aspect.bzl",