Respect Starlark builtins when dumping rules via `info build-language`

Note that we can still trigger the legacy behavior of dumping only old native
rules by calling `info build-language --experimental_builtins_bzl_path=`

PiperOrigin-RevId: 606332328
Change-Id: I3cca300574bac01775603dd908445e297bfa7948
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java b/src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java
index 04468fa..2143860 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java
@@ -735,6 +735,8 @@
 
   /**
    * Initializes and syncs the graph with the given options, readying it for the next evaluation.
+   *
+   * @throws IllegalStateException if the method has already been called in this environment.
    */
   public void syncPackageLoading(OptionsProvider options)
       throws InterruptedException, AbruptExitException {
@@ -760,6 +762,11 @@
                 options);
   }
 
+  /** Returns true if {@link #syncPackageLoading} has already been called. */
+  public boolean hasSyncedPackageLoading() {
+    return hasSyncedPackageLoading;
+  }
+
   public void recordLastExecutionTime() {
     workspace.recordLastExecutionTime(getCommandStartTime());
   }
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/InfoItem.java b/src/main/java/com/google/devtools/build/lib/runtime/InfoItem.java
index 1de9ea0..fff3c3e 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/InfoItem.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/InfoItem.java
@@ -49,6 +49,16 @@
   }
 
   /**
+   * Returns true if this info item requires CommandEnvironment.syncPackageLoading to be called,
+   * e.g. in order to initialize the skyframe executor.
+   *
+   * <p>Virtually all info items do not need it.
+   */
+  public boolean needsSyncPackageLoading() {
+    return false;
+  }
+
+  /**
    * Whether the key is printed when "blaze info" is invoked without arguments.
    *
    * <p>This is usually true for info keys that take multiple lines, thus, cannot really be included
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/InfoCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/InfoCommand.java
index 686d376..79cce76 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/InfoCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/InfoCommand.java
@@ -74,6 +74,7 @@
 import com.google.devtools.common.options.OptionEffectTag;
 import com.google.devtools.common.options.OptionsBase;
 import com.google.devtools.common.options.OptionsParsingResult;
+import com.google.devtools.common.options.OptionsProvider;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.util.List;
@@ -151,12 +152,12 @@
                 // In order to be able to answer configuration-specific queries, we need to set up
                 // the package path. Since info inherits all the build options, all the necessary
                 // information is available here.
-                env.syncPackageLoading(optionsParsingResult);
+                ensureSyncPackageLoading(env, optionsParsingResult);
                 // TODO(bazel-team): What if there are multiple configurations? [multi-config]
                 BuildOptions buildOptions = runtime.createBuildOptions(optionsParsingResult);
                 env.getSkyframeExecutor().setBaselineConfiguration(buildOptions);
                 return env.getSkyframeExecutor()
-                    .getConfiguration(env.getReporter(), buildOptions, /*keepGoing=*/ true);
+                    .getConfiguration(env.getReporter(), buildOptions, /* keepGoing= */ true);
               } catch (InvalidConfigurationException e) {
                 env.getReporter().handle(Event.error(e.getMessage()));
                 throw new AbruptExitRuntimeException(e.getDetailedExitCode());
@@ -189,7 +190,11 @@
           byte[] value;
           if (items.containsKey(key)) {
             try (SilentCloseable c = Profiler.instance().profile(key + ".infoItem")) {
-              value = items.get(key).get(configurationSupplier, env);
+              InfoItem infoItem = items.get(key);
+              if (infoItem.needsSyncPackageLoading()) {
+                ensureSyncPackageLoading(env, optionsParsingResult);
+              }
+              value = infoItem.get(configurationSupplier, env);
               if (residue.size() > 1) {
                 outErr.getOutputStream().write((key + ": ").getBytes(StandardCharsets.UTF_8));
               }
@@ -218,6 +223,9 @@
           if (infoItem.isHidden()) {
             continue;
           }
+          if (infoItem.needsSyncPackageLoading()) {
+            ensureSyncPackageLoading(env, optionsParsingResult);
+          }
           outErr.getOutputStream().write(
               (infoItem.getName() + ": ").getBytes(StandardCharsets.UTF_8));
           try (SilentCloseable c = Profiler.instance().profile(infoItem.getName() + ".infoItem")) {
@@ -242,6 +250,13 @@
     return BlazeCommandResult.success();
   }
 
+  private static void ensureSyncPackageLoading(CommandEnvironment env, OptionsProvider options)
+      throws InterruptedException, AbruptExitException {
+    if (!env.hasSyncedPackageLoading()) {
+      env.syncPackageLoading(options);
+    }
+  }
+
   private static BlazeCommandResult createFailureResult(
       String message, ExitCode exitCode, FailureDetails.InfoCommand.Code detailedCode) {
     return BlazeCommandResult.detailedExitCode(
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/info/BUILD b/src/main/java/com/google/devtools/build/lib/runtime/commands/info/BUILD
index 1c6b996..07948a1 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/info/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/info/BUILD
@@ -25,15 +25,20 @@
         "//src/main/java/com/google/devtools/build/lib/packages/semantics",
         "//src/main/java/com/google/devtools/build/lib/pkgcache",
         "//src/main/java/com/google/devtools/build/lib/skyframe:skyframe_cluster",
+        "//src/main/java/com/google/devtools/build/lib/skyframe:starlark_builtins_value",
         "//src/main/java/com/google/devtools/build/lib/util:abrupt_exit_exception",
         "//src/main/java/com/google/devtools/build/lib/util:debug-logger-configurator",
+        "//src/main/java/com/google/devtools/build/lib/util:detailed_exit_code",
         "//src/main/java/com/google/devtools/build/lib/util:heap_offset_helper",
         "//src/main/java/com/google/devtools/build/lib/util:string",
         "//src/main/java/com/google/devtools/build/lib/vfs",
         "//src/main/java/com/google/devtools/build/lib/worker:worker_process_metrics",
+        "//src/main/java/com/google/devtools/build/skyframe",
+        "//src/main/java/com/google/devtools/build/skyframe:skyframe-objects",
         "//src/main/java/com/google/devtools/common/options",
         "//src/main/java/net/starlark/java/eval",
         "//src/main/protobuf:build_java_proto",
+        "//src/main/protobuf:failure_details_java_proto",
         "//third_party:flogger",
         "//third_party:guava",
     ],
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/info/BuildLanguageInfoItem.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/info/BuildLanguageInfoItem.java
index b6d3fcd..c6cfd9c 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/info/BuildLanguageInfoItem.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/info/BuildLanguageInfoItem.java
@@ -20,6 +20,7 @@
 import com.google.common.base.Predicates;
 import com.google.common.base.Supplier;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue;
 import com.google.devtools.build.lib.packages.Attribute;
@@ -27,9 +28,10 @@
 import com.google.devtools.build.lib.packages.BuildType;
 import com.google.devtools.build.lib.packages.ProtoUtils;
 import com.google.devtools.build.lib.packages.RuleClass;
-import com.google.devtools.build.lib.packages.RuleClassProvider;
+import com.google.devtools.build.lib.packages.RuleFunction;
 import com.google.devtools.build.lib.packages.TriState;
 import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions;
 import com.google.devtools.build.lib.query2.proto.proto2api.Build.AllowedRuleClassInfo;
 import com.google.devtools.build.lib.query2.proto.proto2api.Build.AttributeDefinition;
 import com.google.devtools.build.lib.query2.proto.proto2api.Build.AttributeValue;
@@ -37,10 +39,17 @@
 import com.google.devtools.build.lib.query2.proto.proto2api.Build.RuleDefinition;
 import com.google.devtools.build.lib.runtime.CommandEnvironment;
 import com.google.devtools.build.lib.runtime.InfoItem;
+import com.google.devtools.build.lib.server.FailureDetails;
+import com.google.devtools.build.lib.skyframe.StarlarkBuiltinsValue;
+import com.google.devtools.build.lib.util.AbruptExitException;
+import com.google.devtools.build.lib.util.DetailedExitCode;
+import com.google.devtools.build.skyframe.EvaluationResult;
+import com.google.devtools.build.skyframe.SkyValue;
 import java.util.Collection;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
+import net.starlark.java.eval.StarlarkFunction;
 import net.starlark.java.eval.StarlarkInt;
 
 /**
@@ -50,24 +59,88 @@
  */
 @Deprecated
 public final class BuildLanguageInfoItem extends InfoItem {
+
   public BuildLanguageInfoItem() {
     super("build-language", "A protobuffer with the build language structure", true);
   }
 
   @Override
-  public byte[] get(
-      Supplier<BuildConfigurationValue> configurationSupplier, CommandEnvironment env) {
-    checkNotNull(env);
-    return print(getBuildLanguageDefinition(env.getRuntime().getRuleClassProvider()));
+  public boolean needsSyncPackageLoading() {
+    // Requires CommandEnvironment.syncPackageLoading to be called in order to initialize the
+    // skyframe executor.
+    return true;
   }
 
-  /** Returns a byte array containing a proto-buffer describing the build language. */
-  private static byte[] getBuildLanguageDefinition(RuleClassProvider provider) {
+  @Override
+  public byte[] get(Supplier<BuildConfigurationValue> configurationSupplier, CommandEnvironment env)
+      throws AbruptExitException {
+    checkNotNull(env);
+    StarlarkBuiltinsValue builtins = loadStarlarkBuiltins(env);
+    return print(getBuildLanguageDefinition(getRuleClasses(builtins, env)));
+  }
+
+  private StarlarkBuiltinsValue loadStarlarkBuiltins(CommandEnvironment env)
+      throws AbruptExitException {
+    EvaluationResult<SkyValue> result =
+        env.getSkyframeExecutor()
+            .evaluateSkyKeys(
+                env.getReporter(),
+                ImmutableList.of(StarlarkBuiltinsValue.key()),
+                /* keepGoing= */ false);
+    if (result.hasError()) {
+      throw new AbruptExitException(
+          DetailedExitCode.of(
+              FailureDetails.FailureDetail.newBuilder()
+                  .setMessage("Failed to load Starlark builtins")
+                  .setInfoCommand(FailureDetails.InfoCommand.getDefaultInstance())
+                  .build()));
+    }
+    return (StarlarkBuiltinsValue) result.get(StarlarkBuiltinsValue.key());
+  }
+
+  private ImmutableList<RuleClass> getRuleClasses(
+      StarlarkBuiltinsValue builtins, CommandEnvironment env) {
+    ImmutableMap<String, RuleClass> nativeRuleClasses =
+        env.getRuntime().getRuleClassProvider().getRuleClassMap();
+
+    // The conditional for selecting whether or not to load symbols from @_builtins is the same as
+    // in PackageFunction.compileBuildFile
+    if (builtins
+        .starlarkSemantics
+        .get(BuildLanguageOptions.EXPERIMENTAL_BUILTINS_BZL_PATH)
+        .isEmpty()) {
+      return ImmutableList.sortedCopyOf(
+          Comparator.comparing(RuleClass::getName), nativeRuleClasses.values());
+    } else {
+      ImmutableList.Builder<RuleClass> ruleClasses = ImmutableList.builder();
+      for (Map.Entry<String, Object> entry : builtins.predeclaredForBuild.entrySet()) {
+        if (entry.getValue() instanceof RuleFunction) {
+          ruleClasses.add(((RuleFunction) entry.getValue()).getRuleClass());
+        } else if (entry.getValue() instanceof StarlarkFunction) {
+          if (nativeRuleClasses.containsKey(entry.getKey())) {
+            // entry.getValue() is a Starlark macro in @_builtins overriding a native rule. We
+            // cannot extract the macro's metadata (other than by, perhaps, parsing its Starlark
+            // docstring via starlark_doc_extract, but that does not have sufficient fidelity to
+            // get rule attribute metadata), so we extract it from the legacy rule instead.
+            // Note that we *cannot* rely on StarlarkFunction.getName() because under which the
+            // macro is defined may not match the name under which @_builtins exports it.
+            ruleClasses.add(nativeRuleClasses.get(entry.getKey()));
+          }
+        }
+      }
+      return ImmutableList.sortedCopyOf(
+          Comparator.comparing(RuleClass::getName), ruleClasses.build());
+    }
+  }
+
+  /**
+   * Returns a byte array containing a proto-buffer describing the build language.
+   *
+   * @param ruleClasses a sorted list of rule classes
+   */
+  private static byte[] getBuildLanguageDefinition(ImmutableList<RuleClass> ruleClasses) {
     BuildLanguage.Builder resultPb = BuildLanguage.newBuilder();
-    ImmutableList<RuleClass> sortedRuleClasses =
-        ImmutableList.sortedCopyOf(
-            Comparator.comparing(RuleClass::getName), provider.getRuleClassMap().values());
-    for (RuleClass ruleClass : sortedRuleClasses) {
+    for (RuleClass ruleClass : ruleClasses) {
       if (isAbstractRule(ruleClass)) {
         continue;
       }
@@ -99,7 +172,7 @@
         }
         attrPb.setExecutable(attr.isExecutable());
         if (BuildType.isLabelType(t)) {
-          attrPb.setAllowedRuleClasses(getAllowedRuleClasses(sortedRuleClasses, attr));
+          attrPb.setAllowedRuleClasses(getAllowedRuleClasses(ruleClasses, attr));
           attrPb.setNodep(t.getLabelClass() == Type.LabelClass.NONDEP_REFERENCE);
         }
         rulePb.addAttribute(attrPb);
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
index 41bbc89..422709d 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
@@ -1928,30 +1928,11 @@
     return evaluateSkyKeys(eventHandler, skyKeys, false);
   }
 
-  /** Evaluates sky keys that require action execution and returns their evaluation results. */
-  public EvaluationResult<SkyValue> evaluateSkyKeysWithExecution(
-      final Reporter reporter,
-      final Executor executor,
-      final Iterable<? extends SkyKey> skyKeys,
-      final OptionsProvider options,
-      final ActionCacheChecker actionCacheChecker,
-      final ActionOutputDirectoryHelper outputDirectoryHelper) {
-
-    prepareSkyframeActionExecutorForExecution(
-        reporter, executor, options, actionCacheChecker, outputDirectoryHelper);
-    try {
-      return evaluateSkyKeys(
-          reporter, skyKeys, options.getOptions(KeepGoingOption.class).keepGoing);
-    } finally {
-      cleanUpAfterSingleEvaluationWithActionExecution(reporter);
-    }
-  }
-
   /**
    * Evaluates the given sky keys, blocks, and returns their evaluation results. Enables/disables
    * "keep going" on evaluation errors as specified.
    */
-  EvaluationResult<SkyValue> evaluateSkyKeys(
+  public EvaluationResult<SkyValue> evaluateSkyKeys(
       final ExtendedEventHandler eventHandler,
       final Iterable<? extends SkyKey> skyKeys,
       final boolean keepGoing) {
@@ -1973,6 +1954,25 @@
     return result;
   }
 
+  /** Evaluates sky keys that require action execution and returns their evaluation results. */
+  public EvaluationResult<SkyValue> evaluateSkyKeysWithExecution(
+      final Reporter reporter,
+      final Executor executor,
+      final Iterable<? extends SkyKey> skyKeys,
+      final OptionsProvider options,
+      final ActionCacheChecker actionCacheChecker,
+      final ActionOutputDirectoryHelper outputDirectoryHelper) {
+
+    prepareSkyframeActionExecutorForExecution(
+        reporter, executor, options, actionCacheChecker, outputDirectoryHelper);
+    try {
+      return evaluateSkyKeys(
+          reporter, skyKeys, options.getOptions(KeepGoingOption.class).keepGoing);
+    } finally {
+      cleanUpAfterSingleEvaluationWithActionExecution(reporter);
+    }
+  }
+
   private class EnableAnalysisScope implements AutoCloseable {
     private EnableAnalysisScope() {
       skyframeBuildView.enableAnalysis(true);