Add BzlLoadValue.KeyForBuiltins

Work toward #11437.

RELNOTES:
PiperOrigin-RevId: 313399299
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/BzlLoadValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/BzlLoadValue.java
index 8d6c1f2..5d520c1 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/BzlLoadValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/BzlLoadValue.java
@@ -71,7 +71,13 @@
   /** SkyKey for a Starlark load. */
   abstract static class Key implements SkyKey {
 
-    /** Returns the label of the .bzl file to be loaded. */
+    /**
+     * Returns the label of the .bzl file to be loaded.
+     *
+     * <p>For {@link KeyForBuiltins}, it usually begins with {@code @builtins//:}. Other values are
+     * possible but indicate a bug in the {@code @builtins} .bzl files and will fail during
+     * evaluation.
+     */
     abstract Label getLabel();
 
     /**
@@ -202,6 +208,57 @@
     }
   }
 
+  /**
+   * A key for loading a .bzl during {@code @builtins} evaluation.
+   *
+   * <p>This kind of key is only requested by {@link StarlarkBuiltinsFunction} and its transitively
+   * loaded {@link BzlLoadFunction} calls.
+   *
+   * <p>The label begins with {@code @builtins//:}, but it is distinct from any .bzl in an external
+   * repository named {@code "@builtins"}.
+   */
+  @Immutable
+  @AutoCodec.VisibleForSerialization
+  static final class KeyForBuiltins extends Key {
+
+    private final Label label;
+
+    private KeyForBuiltins(Label label) {
+      this.label = Preconditions.checkNotNull(label);
+    }
+
+    @Override
+    Label getLabel() {
+      return label;
+    }
+
+    @Override
+    Key getKeyForLoad(Label label) {
+      return keyForBuiltins(label);
+    }
+
+    @Override
+    public String toString() {
+      return label + " (in builtins)";
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (this == obj) {
+        return true;
+      }
+      if (!(obj instanceof KeyForBuiltins)) {
+        return false;
+      }
+      return this.label.equals(((KeyForBuiltins) obj).label);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(KeyForBuiltins.class, label);
+    }
+  }
+
   /** Constructs a key for loading a regular (non-workspace) .bzl file, from the .bzl's label. */
   static Key keyForBuild(Label label) {
     return keyInterner.intern(new KeyForBuild(label));
@@ -218,4 +275,9 @@
   static Key keyForWorkspace(Label label, int workspaceChunk, RootedPath workspacePath) {
     return keyInterner.intern(new KeyForWorkspace(label, workspaceChunk, workspacePath));
   }
+
+  /** Constructs a key for loading a .bzl file within the {@code @builtins} pseudo-repository. */
+  static Key keyForBuiltins(Label label) {
+    return keyInterner.intern(new KeyForBuiltins(label));
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/StarlarkBuiltinsFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/StarlarkBuiltinsFunction.java
index 10fa795..97f9ba3 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/StarlarkBuiltinsFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/StarlarkBuiltinsFunction.java
@@ -26,10 +26,10 @@
 import com.google.devtools.build.skyframe.SkyValue;
 import javax.annotation.Nullable;
 
-// TODO(brandjon): Determine places where we need to teach Skyframe about this Skyfunction. Look for
+// TODO(#11437): Determine places where we need to teach Skyframe about this Skyfunction. Look for
 // special treatment of BzlLoadFunction or ASTFileLookupFunction in existing code.
 
-// TODO(brandjon): Add support to StarlarkModuleCycleReporter to pretty-print cycles involving
+// TODO(#11437): Add support to StarlarkModuleCycleReporter to pretty-print cycles involving
 // @builtins. Blocked on us actually loading files from @builtins.
 
 /**
@@ -56,6 +56,17 @@
   private static final Location EXPORTS_ENTRYPOINT_LOC =
       new Location(EXPORTS_ENTRYPOINT.getCanonicalForm(), /*line=*/ 0, /*column=*/ 0);
 
+  /**
+   * Key for loading exports.bzl. {@code keyForBuiltins} (as opposed to {@code keyForBuild} ensures
+   * that 1) we can resolve the {@code @builtins} name appropriately, and 2) loading it does not
+   * trigger a cyclic call back into {@code StarlarkBuiltinsFunction}.
+   */
+  private static final SkyKey EXPORTS_ENTRYPOINT_KEY =
+      BzlLoadValue.keyForBuiltins(
+          // TODO(#11437): Replace by EXPORTS_ENTRYPOINT once BzlLoadFunction can resolve the
+          // @builtins namespace.
+          Label.parseAbsoluteUnchecked("//tools/builtins_staging:exports.bzl"));
+
   StarlarkBuiltinsFunction() {}
 
   @Override
@@ -63,12 +74,7 @@
       throws StarlarkBuiltinsFunctionException, InterruptedException {
     // skyKey is a singleton, unused.
 
-    // TODO(brandjon): Replace by @builtins//:exports once BzlLoadFunction can resolve the @builtins
-    // namespace.
-    SkyKey exportsKey =
-        BzlLoadValue.keyForBuild(
-            Label.parseAbsoluteUnchecked("//tools/builtins_staging:exports.bzl"));
-    BzlLoadValue exportsValue = (BzlLoadValue) env.getValue(exportsKey);
+    BzlLoadValue exportsValue = (BzlLoadValue) env.getValue(EXPORTS_ENTRYPOINT_KEY);
     if (exportsValue == null) {
       return null;
     }
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/BzlLoadKeyCodecTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/BzlLoadKeyCodecTest.java
index 4b9ae72..0ecf54e 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/BzlLoadKeyCodecTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/BzlLoadKeyCodecTest.java
@@ -13,7 +13,6 @@
 // limitations under the License.
 package com.google.devtools.build.lib.skyframe;
 
-import com.google.common.collect.ImmutableMap;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.skyframe.serialization.testutils.FsUtils;
 import com.google.devtools.build.lib.skyframe.serialization.testutils.SerializationTester;
@@ -29,11 +28,12 @@
   public void testCodec() throws Exception {
     SerializationTester serializationTester =
         new SerializationTester(
-            BzlLoadValue.keyForBuild(Label.parseAbsolute("//foo/bar:baz", ImmutableMap.of())),
+            BzlLoadValue.keyForBuild(Label.parseAbsoluteUnchecked("//foo/bar:baz")),
             BzlLoadValue.keyForWorkspace(
-                Label.parseAbsolute("//foo/bar:baz", ImmutableMap.of()),
+                Label.parseAbsoluteUnchecked("//foo/bar:baz"),
                 /*workspaceChunk=*/ 4,
-                /*workspacePath=*/ FsUtils.TEST_ROOTED_PATH));
+                /*workspacePath=*/ FsUtils.TEST_ROOTED_PATH),
+            BzlLoadValue.keyForBuiltins(Label.parseAbsoluteUnchecked("//foo/bar:baz")));
     FsUtils.addDependencies(serializationTester);
     serializationTester.runTests();
   }