Bzlmod: Proper repo mappings for @_builtins

@_builtins is not a real repo and does not currently have repo mappings. However, things in there still sometimes use things from other repos (eg. @platforms). This isn't a problem pre-Bzlmod since @platforms is just called @platforms, but in Bzlmod it's called @platforms.0.0.4, so things break.

This CL changes it so that when Bzlmod is enabled, @_builtins adopts the repo mapping of @bazel_tools, the built-in module. Note that the repo mapping of @bazel_tools does not have a dependency on the //external package, so there is still no circular SkyValue dependency.

Fixes https://github.com/bazelbuild/bazel/issues/15317.

PiperOrigin-RevId: 450665054
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileGlobals.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileGlobals.java
index 9447925..4b5b255 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileGlobals.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileGlobals.java
@@ -179,7 +179,8 @@
       throw Starlark.errorf("the module() directive can only be called once");
     }
     moduleCalled = true;
-    // TODO(wyv): add validation logic for name (alphanumerical) & others in the future
+    // TODO(wyv): add validation logic for name (alphanumerical, start with a letter) & others in
+    //   the future
     Version parsedVersion;
     try {
       parsedVersion = Version.parse(version);
@@ -250,7 +251,8 @@
     if (repoName.isEmpty()) {
       repoName = name;
     }
-    // TODO(wyv): add validation logic for name (alphanumerical) and repoName (RepositoryName?)
+    // TODO(wyv): add validation logic for name (alphanumerical, start with a letter) and repoName
+    //   (RepositoryName?, start with a letter)
     Version parsedVersion;
     try {
       parsedVersion = Version.parse(version);
@@ -341,6 +343,7 @@
 
     void addImport(String localRepoName, String exportedName, Location location)
         throws EvalException {
+      // TODO(wyv): validate both repo names (RepositoryName.validate; starts with a letter)
       addRepoNameUsage(localRepoName, "by a use_repo() call", location);
       if (imports.containsValue(exportedName)) {
         String collisionRepoName = imports.inverse().get(exportedName);
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 dbdb4b8..5d381a6 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
@@ -2129,6 +2129,7 @@
     deps = [
         ":package_value",
         ":repository_mapping_value",
+        ":starlark_builtins_value",
         "//src/main/java/com/google/devtools/build/lib/bazel/bzlmod:common",
         "//src/main/java/com/google/devtools/build/lib/bazel/bzlmod:module_extension",
         "//src/main/java/com/google/devtools/build/lib/bazel/bzlmod:resolution",
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/BzlLoadFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/BzlLoadFunction.java
index b7865e5..ebd03a6 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/BzlLoadFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/BzlLoadFunction.java
@@ -44,6 +44,7 @@
 import com.google.devtools.build.lib.packages.StarlarkExportable;
 import com.google.devtools.build.lib.packages.WorkspaceFileValue;
 import com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions;
+import com.google.devtools.build.lib.rules.repository.RepositoryDelegatorFunction;
 import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
 import com.google.devtools.build.lib.server.FailureDetails.StarlarkLoading;
 import com.google.devtools.build.lib.server.FailureDetails.StarlarkLoading.Code;
@@ -806,12 +807,14 @@
 
   private static RepositoryMapping getRepositoryMapping(BzlLoadValue.Key key, Environment env)
       throws InterruptedException {
-    if (key.isBuiltins()) {
-      // Builtins .bzls never have a repo mapping defined for them, so return without requesting a
-      // RepositoryMappingValue. (NB: In addition to being a slight optimization, this avoids
-      // adding a reverse dependency on the special //external package, which helps avoid tickling
-      // some peculiarities of the Google-internal Skyframe implementation; see b/182293526 for
-      // details.)
+    if (key.isBuiltins() && !RepositoryDelegatorFunction.ENABLE_BZLMOD.get(env)) {
+      // Without Bzlmod, builtins .bzls never have a repo mapping defined for them, so return
+      // without requesting a RepositoryMappingValue. (NB: In addition to being a slight
+      // optimization, this avoids adding a reverse dependency on the special //external package,
+      // which helps avoid tickling some peculiarities of the Google-internal Skyframe
+      // implementation; see b/182293526 for details.)
+      // Otherwise, builtins .bzls should use the repo mapping of @bazel_tools, and *do* request a
+      // normal RepositoryMappingValue (see logic in RepositoryMappingFunction).
       return RepositoryMapping.ALWAYS_FALLBACK;
     }
 
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/RepositoryMappingFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/RepositoryMappingFunction.java
index 4296154..58512bc 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/RepositoryMappingFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/RepositoryMappingFunction.java
@@ -50,6 +50,24 @@
 
     BazelModuleResolutionValue bazelModuleResolutionValue = null;
     if (Preconditions.checkNotNull(RepositoryDelegatorFunction.ENABLE_BZLMOD.get(env))) {
+      if (StarlarkBuiltinsValue.isBuiltinsRepo(repositoryName)) {
+        // Builtins .bzl files should use the repo mapping of @bazel_tools, to get access to repos
+        // such as @platforms.
+        RepositoryMappingValue bazelToolsMapping =
+            (RepositoryMappingValue)
+                env.getValue(RepositoryMappingValue.key(RepositoryName.BAZEL_TOOLS));
+        if (bazelToolsMapping == null) {
+          return null;
+        }
+        // We need to make sure that @_builtins maps to @_builtins too.
+        return RepositoryMappingValue.withMapping(
+            RepositoryMapping.create(
+                    ImmutableMap.of(
+                        StarlarkBuiltinsValue.BUILTINS_REPO, StarlarkBuiltinsValue.BUILTINS_REPO),
+                    StarlarkBuiltinsValue.BUILTINS_NAME)
+                .withAdditionalMappings(bazelToolsMapping.getRepositoryMapping()));
+      }
+
       bazelModuleResolutionValue =
           (BazelModuleResolutionValue) env.getValue(BazelModuleResolutionValue.KEY);
       if (env.valuesMissing()) {
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 9726844..0b7efe8 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
@@ -52,7 +52,8 @@
 
 /**
  * A Skyframe function that evaluates the {@code @_builtins} pseudo-repository and reports the
- * values exported by {@link #EXPORTS_ENTRYPOINT}.
+ * values exported by {@link #EXPORTS_ENTRYPOINT}. The {@code @_builtins} pseudo-repository shares a
+ * repo mapping with the {@code @bazel_tools} repository.
  *
  * <p>The process of "builtins injection" refers to evaluating this Skyfunction and applying its
  * result to {@link BzlLoadFunction}'s computation. See also the <a
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/RepositoryMappingFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/RepositoryMappingFunctionTest.java
index 2464de5..2ba7271 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/RepositoryMappingFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/RepositoryMappingFunctionTest.java
@@ -583,6 +583,39 @@
   }
 
   @Test
+  public void builtinsRepo() throws Exception {
+    scratch.overwriteFile(
+        "MODULE.bazel",
+        // Add an explicit dependency on @bazel_tools since we're not using built-in modules in this
+        // test
+        "bazel_dep(name='bazel_tools',version='1.0')");
+    registry
+        .addModule(
+            createModuleKey("bazel_tools", "1.0"),
+            "module(name='bazel_tools',version='1.0')",
+            "bazel_dep(name='foo', version='1.0')")
+        .addModule(createModuleKey("foo", "1.0"), "module(name='foo', version='1.0')");
+
+    RepositoryName name = RepositoryName.create("_builtins");
+    SkyKey skyKey = RepositoryMappingValue.key(name);
+    EvaluationResult<RepositoryMappingValue> result = eval(skyKey);
+
+    assertThat(result.hasError()).isFalse();
+    assertThatEvaluationResult(result)
+        .hasEntryThat(skyKey)
+        .isEqualTo(
+            withMapping(
+                ImmutableMap.of(
+                    RepositoryName.create("bazel_tools"),
+                    RepositoryName.create("bazel_tools"), // bazel_tools is a well-known module
+                    RepositoryName.create("foo"),
+                    RepositoryName.create("foo.1.0"),
+                    RepositoryName.create("_builtins"),
+                    RepositoryName.create("_builtins")),
+                name));
+  }
+
+  @Test
   public void testEqualsAndHashCode() throws Exception {
     new EqualsTester()
         .addEqualityGroup(