Use RequiredProviders to validate rule prerequisites in RuleContext.

We now use a unified way to check provider requirements everywhere.

Reland of https://github.com/bazelbuild/bazel/commit/c32e1b1efcd703b3780de47fba62974123593d71.

RELNOTES: None.
PiperOrigin-RevId: 164038621
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 771189a..de978e0 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
@@ -70,18 +70,17 @@
 import com.google.devtools.build.lib.packages.OutputFile;
 import com.google.devtools.build.lib.packages.PackageSpecification;
 import com.google.devtools.build.lib.packages.RawAttributeMapper;
+import com.google.devtools.build.lib.packages.RequiredProviders;
 import com.google.devtools.build.lib.packages.Rule;
 import com.google.devtools.build.lib.packages.RuleClass;
 import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException;
 import com.google.devtools.build.lib.packages.RuleErrorConsumer;
-import com.google.devtools.build.lib.packages.SkylarkProviderIdentifier;
 import com.google.devtools.build.lib.packages.Target;
 import com.google.devtools.build.lib.packages.TargetUtils;
 import com.google.devtools.build.lib.rules.AliasProvider;
 import com.google.devtools.build.lib.rules.MakeVariableProvider;
 import com.google.devtools.build.lib.rules.fileset.FilesetProvider;
 import com.google.devtools.build.lib.shell.ShellUtils;
-import com.google.devtools.build.lib.syntax.ClassObject;
 import com.google.devtools.build.lib.syntax.EvalException;
 import com.google.devtools.build.lib.syntax.Type;
 import com.google.devtools.build.lib.syntax.Type.LabelClass;
@@ -1975,154 +1974,101 @@
       }
     }
 
-    private String getMissingMandatoryProviders(ConfiguredTarget prerequisite, Attribute attribute){
-      ImmutableList<ImmutableSet<SkylarkProviderIdentifier>> mandatoryProvidersList =
-          attribute.getMandatoryProvidersList();
-      if (mandatoryProvidersList.isEmpty()) {
-        return null;
-      }
-      List<List<String>> missingProvidersList = new ArrayList<>();
-      for (ImmutableSet<SkylarkProviderIdentifier> providers : mandatoryProvidersList) {
-        List<String> missing = new ArrayList<>();
-        for (SkylarkProviderIdentifier provider : providers) {
-          // A rule may require a built-in provider that is always implicitly provided, e.g. "files"
-          ImmutableSet<String> availableKeys =
-              ImmutableSet.copyOf(((ClassObject) prerequisite).getKeys());
-          if ((prerequisite.get(provider) == null)
-              && !(provider.isLegacy() && availableKeys.contains(provider.getLegacyId()))) {
-            missing.add(provider.toString());
-          }
-        }
-        if (missing.isEmpty()) {
-          return null;
-        } else {
-          missingProvidersList.add(missing);
-        }
-      }
-      StringBuilder missingProviders = new StringBuilder();
-      Joiner joinProvider = Joiner.on("', '");
-      for (List<String> providers : missingProvidersList) {
-        if (missingProviders.length() > 0) {
-          missingProviders.append(" or ");
-        }
-        missingProviders.append((providers.size() > 1) ? "[" : "")
-                        .append("'");
-        joinProvider.appendTo(missingProviders, providers);
-        missingProviders.append("'")
-                        .append((providers.size() > 1) ? "]" : "");
-      }
-      return missingProviders.toString();
-    }
-
-    private String getMissingMandatoryNativeProviders(
-        ConfiguredTarget prerequisite, Attribute attribute) {
-      List<ImmutableList<Class<? extends TransitiveInfoProvider>>> mandatoryProvidersList =
-          attribute.getMandatoryNativeProvidersList();
-      if (mandatoryProvidersList.isEmpty()) {
-        return null;
-      }
-      List<List<String>> missingProvidersList = new ArrayList<>();
-      for (ImmutableList<Class<? extends TransitiveInfoProvider>> providers
-          : mandatoryProvidersList) {
-        List<String> missing = new ArrayList<>();
-        for (Class<? extends TransitiveInfoProvider> provider : providers) {
-          if (prerequisite.getProvider(provider) == null) {
-            missing.add(provider.getSimpleName());
-          }
-        }
-        if (missing.isEmpty()) {
-          return null;
-        } else {
-          missingProvidersList.add(missing);
-        }
-      }
-      StringBuilder missingProviders = new StringBuilder();
-      Joiner joinProvider = Joiner.on(", ");
-      for (List<String> providers : missingProvidersList) {
-        if (missingProviders.length() > 0) {
-          missingProviders.append(" or ");
-        }
-        missingProviders.append((providers.size() > 1) ? "[" : "");
-        joinProvider.appendTo(missingProviders, providers);
-        missingProviders.append((providers.size() > 1) ? "]" : "");
-      }
-      return missingProviders.toString();
-    }
-
     /**
-     * Because some rules still have to use allowedRuleClasses to do rule dependency validation.
-     * A dependency is valid if it is from a rule in allowedRuleClasses, OR if all of the providers
-     * in mandatoryNativeProviders AND mandatoryProvidersList are provided by the target.
+     * Because some rules still have to use allowedRuleClasses to do rule dependency validation. A
+     * dependency is valid if it is from a rule in allowedRuledClasses, OR if all of the providers
+     * in requiredProviders are provided by the target.
      */
     private void validateRuleDependency(ConfiguredTarget prerequisite, Attribute attribute) {
-      Target prerequisiteTarget = prerequisite.getTarget();
-      RuleClass ruleClass = ((Rule) prerequisiteTarget).getRuleClassObject();
-      String notAllowedRuleClassesMessage = null;
 
+      Set<String> unfulfilledRequirements = new LinkedHashSet<>();
+      if (checkRuleDependencyClass(prerequisite, attribute, unfulfilledRequirements)) {
+        return;
+      }
+
+      if (checkRuleDependencyClassWarnings(prerequisite, attribute)) {
+        return;
+      }
+
+      if (checkRuleDependencyMandatoryProviders(prerequisite, attribute, unfulfilledRequirements)) {
+        return;
+      }
+
+      // not allowed rule class and some mandatory providers missing => reject.
+      if (!unfulfilledRequirements.isEmpty()) {
+        attributeError(
+            attribute.getName(), StringUtil.joinEnglishList(unfulfilledRequirements, "and"));
+      }
+    }
+
+    /** Check if prerequisite should be allowed based on its rule class. */
+    private boolean checkRuleDependencyClass(
+        ConfiguredTarget prerequisite, Attribute attribute, Set<String> unfulfilledRequirements) {
       if (attribute.getAllowedRuleClassesPredicate() != Predicates.<RuleClass>alwaysTrue()) {
-        if (attribute.getAllowedRuleClassesPredicate().apply(ruleClass)) {
+        if (attribute
+            .getAllowedRuleClassesPredicate()
+            .apply(((Rule) prerequisite.getTarget()).getRuleClassObject())) {
           // prerequisite has an allowed rule class => accept.
-          return;
+          return true;
         }
         // remember that the rule class that was not allowed;
         // but maybe prerequisite provides required providers? do not reject yet.
-        notAllowedRuleClassesMessage =
+        unfulfilledRequirements.add(
             badPrerequisiteMessage(
-                prerequisiteTarget.getTargetKind(),
+                prerequisite.getTarget().getTargetKind(),
                 prerequisite,
                 "expected " + attribute.getAllowedRuleClassesPredicate(),
-                false);
+                false));
       }
+      return false;
+    }
 
-      if (attribute.getAllowedRuleClassesWarningPredicate().apply(ruleClass)) {
+    /**
+     * Check if prerequisite should be allowed with warning based on its rule class.
+     *
+     * <p>If yes, also issues said warning.
+     */
+    private boolean checkRuleDependencyClassWarnings(
+        ConfiguredTarget prerequisite, Attribute attribute) {
+      if (attribute
+          .getAllowedRuleClassesWarningPredicate()
+          .apply(((Rule) prerequisite.getTarget()).getRuleClassObject())) {
         Predicate<RuleClass> allowedRuleClasses = attribute.getAllowedRuleClassesPredicate();
-        reportBadPrerequisite(attribute, prerequisiteTarget.getTargetKind(), prerequisite,
+        reportBadPrerequisite(
+            attribute,
+            prerequisite.getTarget().getTargetKind(),
+            prerequisite,
             allowedRuleClasses == Predicates.<RuleClass>alwaysTrue()
                 ? null
                 : "expected " + allowedRuleClasses,
             true);
         // prerequisite has a rule class allowed with a warning => accept, emitting a warning.
-        return;
+        return true;
+      }
+      return false;
+    }
+
+    /** Check if prerequisite should be allowed based on required providers on the attribute. */
+    private boolean checkRuleDependencyMandatoryProviders(
+        ConfiguredTarget prerequisite, Attribute attribute, Set<String> unfulfilledRequirements) {
+      RequiredProviders requiredProviders = attribute.getRequiredProviders();
+
+      if (requiredProviders.acceptsAny()) {
+        // If no required providers specified, we do not know if we should accept.
+        return false;
       }
 
-      // If we get here, we have no allowed rule class.
-      // If mandatory providers are specified, try them.
-      Set<String> unfulfilledRequirements = new LinkedHashSet<>();
-      if (!attribute.getMandatoryNativeProvidersList().isEmpty()
-          || !attribute.getMandatoryProvidersList().isEmpty()) {
-        boolean hadAllMandatoryProviders = true;
-
-        String missingNativeProviders = getMissingMandatoryNativeProviders(prerequisite, attribute);
-        if (missingNativeProviders != null) {
-          unfulfilledRequirements.add(
-              "'" + prerequisite.getLabel() + "' does not have mandatory providers: "
-                  + missingNativeProviders);
-          hadAllMandatoryProviders = false;
-        }
-
-        String missingProviders = getMissingMandatoryProviders(prerequisite, attribute);
-        if (missingProviders != null) {
-          unfulfilledRequirements.add(
-              "'" + prerequisite.getLabel() + "' does not have mandatory providers: "
-                  + missingProviders);
-          hadAllMandatoryProviders = false;
-        }
-
-        if (hadAllMandatoryProviders) {
-          // all mandatory providers are present => accept.
-          return;
-        }
+      if (prerequisite.satisfies(requiredProviders)) {
+        return true;
       }
 
-      // not allowed rule class and some mandatory providers missing => reject.
-      if (notAllowedRuleClassesMessage != null) {
-        unfulfilledRequirements.add(notAllowedRuleClassesMessage);
-      }
+      unfulfilledRequirements.add(
+          String.format(
+              "'%s' does not have mandatory providers: %s",
+              prerequisite.getLabel(),
+              prerequisite.missingProviders(requiredProviders).getDescription()));
 
-      if (!unfulfilledRequirements.isEmpty()) {
-        attributeError(
-            attribute.getName(), StringUtil.joinEnglishList(unfulfilledRequirements, "and"));
-      }
+      return false;
     }
 
     private void validateDirectPrerequisite(Attribute attribute, ConfiguredTarget prerequisite) {