Locate builtins using path from flag

This makes BzlLoadFunction resolve builtins bzls by reading the Root from --experimental_builtins_bzl_path, instead of doing package lookup on a hardcoded placeholder package path. Builtins bzls must now be loaded via the `@_builtins//:` syntax.

The special %install_base% and %workspace% path values are not yet supported.

Also:
- BzlLoadFunction now takes a BlazeDirectories
- @_builtins is a reserved RepositoryName
- Error messages in BzlLoadFunction now signal when the failure was in a builtin

Work toward #11437.

PiperOrigin-RevId: 339141292
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 1c139b5..b4d3065 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
@@ -24,6 +24,7 @@
 import com.google.common.flogger.GoogleLogger;
 import com.google.common.hash.HashFunction;
 import com.google.devtools.build.lib.actions.InconsistentFilesystemException;
+import com.google.devtools.build.lib.analysis.BlazeDirectories;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.cmdline.LabelConstants;
 import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
@@ -49,6 +50,7 @@
 import com.google.devtools.build.lib.util.Fingerprint;
 import com.google.devtools.build.lib.util.Pair;
 import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.lib.vfs.Root;
 import com.google.devtools.build.skyframe.RecordingSkyFunctionEnvironment;
 import com.google.devtools.build.skyframe.SkyFunction;
 import com.google.devtools.build.skyframe.SkyFunctionException;
@@ -98,6 +100,9 @@
   // computeInline() entry point.
   private final PackageFactory packageFactory;
 
+  // Used for determining paths to builtins bzls.
+  private final BlazeDirectories directories;
+
   // Handles retrieving BzlCompileValues, either by calling Skyframe or by inlining
   // BzlCompileFunction; the latter is not to be confused with inlining of BzlLoadFunction. See
   // comment in create() for rationale.
@@ -110,19 +115,23 @@
 
   private BzlLoadFunction(
       PackageFactory packageFactory,
+      BlazeDirectories directories,
       ValueGetter getter,
       @Nullable CachedBzlLoadDataManager cachedBzlLoadDataManager) {
     this.packageFactory = packageFactory;
+    this.directories = directories;
     this.getter = getter;
     this.cachedBzlLoadDataManager = cachedBzlLoadDataManager;
   }
 
   public static BzlLoadFunction create(
       PackageFactory packageFactory,
+      BlazeDirectories directories,
       HashFunction hashFunction,
       Cache<BzlCompileValue.Key, BzlCompileValue> bzlCompileCache) {
     return new BzlLoadFunction(
         packageFactory,
+        directories,
         // When we are not inlining BzlLoadValue nodes, there is no need to have separate
         // BzlCompileValue nodes for bzl files. Instead we inline BzlCompileFunction for a
         // strict memory win, at a small code complexity cost.
@@ -152,9 +161,10 @@
   }
 
   public static BzlLoadFunction createForInlining(
-      PackageFactory packageFactory, int bzlLoadValueCacheSize) {
+      PackageFactory packageFactory, BlazeDirectories directories, int bzlLoadValueCacheSize) {
     return new BzlLoadFunction(
         packageFactory,
+        directories,
         // When we are inlining BzlLoadValue nodes, then we want to have explicit BzlCompileValue
         // nodes, since now (1) in the comment above doesn't hold. This way we read and parse each
         // needed bzl file at most once total globally, rather than once per need (in the worst-case
@@ -465,7 +475,12 @@
     Label label = key.getLabel();
     PathFragment filePath = label.toPathFragment();
 
-    BzlCompileValue.Key compileKey = validatePackageAndGetCompileKey(key, env);
+    StarlarkSemantics starlarkSemantics = PrecomputedValue.STARLARK_SEMANTICS.get(env);
+    if (starlarkSemantics == null) {
+      return null;
+    }
+
+    BzlCompileValue.Key compileKey = validatePackageAndGetCompileKey(key, env, starlarkSemantics);
     if (compileKey == null) {
       return null;
     }
@@ -484,7 +499,8 @@
     // result).
     boolean completed = true;
     try {
-      result = computeInternalWithCompiledBzl(key, compileValue, env, inliningState);
+      result =
+          computeInternalWithCompiledBzl(key, compileValue, starlarkSemantics, env, inliningState);
       completed = result != null;
     } finally {
       if (completed) { // only false on unexceptional null result
@@ -495,17 +511,33 @@
   }
 
   /**
-   * Returns the compile key for a bzl, or null for a missing Skyframe dep or unspecified exception.
+   * Given a bzl key, validates that the corresponding package exists (if required) and returns the
+   * associated compile key based on the package's root. Returns null for a missing Skyframe dep or
+   * unspecified exception.
    *
-   * <p>Except for builtins bzls, a bzl is not considered loadable unless its load label matches its
-   * file target label.
+   * <p>.bzl files are not necessarily targets, because they can be loaded by BUILD and other .bzl
+   * files without ever being declared in a BUILD file. However, .bzl files are still identified by
+   * a label in the same way that file targets are. In particular, it is illegal to refer to a .bzl
+   * file using a label whose package part is not the .bzl file's innermost containing package. For
+   * example, if pkg and pkg/subpkg have BUILD files but not pkg/subdir, then {@code
+   * pkg/subdir:foo.bzl} and {@code pkg:subpkg/foo.bzl} are disallowed.
+   *
+   * <p>In the case of builtins .bzl files, all labels are written as if the pseudo-repo constitutes
+   * one big package, e.g {@code @builtins//:some/path/foo.bzl}, but no BUILD file need exist. The
+   * compile key's root is determined by {@code --experimental_builtins_bzl_path} instead of by
+   * package lookup.
    */
   @Nullable
-  private static BzlCompileValue.Key validatePackageAndGetCompileKey(
-      BzlLoadValue.Key key, Environment env) throws BzlLoadFailedException, InterruptedException {
-    Label label = key.getLabel();
+  private BzlCompileValue.Key validatePackageAndGetCompileKey(
+      BzlLoadValue.Key key, Environment env, StarlarkSemantics starlarkSemantics)
+      throws BzlLoadFailedException, InterruptedException {
+    // Bypass package lookup entirely if builtins.
+    if (key.isBuiltins()) {
+      return key.getCompileKey(getBuiltinsRoot(starlarkSemantics));
+    }
 
     // Do package lookup.
+    Label label = key.getLabel();
     PathFragment dir = Label.getContainingDirectory(label);
     PackageIdentifier dirId = PackageIdentifier.create(label.getRepository(), dir);
     ContainingPackageLookupValue packageLookup;
@@ -546,6 +578,31 @@
     return compileKey;
   }
 
+  private Root getBuiltinsRoot(StarlarkSemantics starlarkSemantics) throws BzlLoadFailedException {
+    String path = starlarkSemantics.get(BuildLanguageOptions.EXPERIMENTAL_BUILTINS_BZL_PATH);
+    if (path.isEmpty()) {
+      throw new IllegalStateException("Requested builtins root, but injection is disabled");
+    } else if (path.equals("%install_base%")) {
+      // TODO(#11437): Support reading builtins from the install base (the expected case in
+      // production)
+      // return Root.fromPath(directories.getInstallBase().getRelative("bzl_builtins"));
+      //
+      // This feature is experimental so the exact exception doesn't matter much, but let's not
+      // crash Bazel.
+      throw new BzlLoadFailedException(
+          "Loading builtins from install base is not implemented", Code.BUILTINS_ERROR);
+    } else if (path.equals("%workspace%")) {
+      // TODO(#11437): Support reading builtins from a well-known path in the workspace, as
+      // determined by the RuleClassProvider.
+      // return Root.fromPath(directories.getWorkspace());
+      throw new BzlLoadFailedException(
+          "Loading builtins from workspace is not implemented", Code.BUILTINS_ERROR);
+    } else {
+      // TODO(#11437): Should we consider interning these roots?
+      return Root.fromPath(directories.getWorkspace().getRelative(path));
+    }
+  }
+
   /**
    * Compute logic for once the compiled .bzl has been fetched and confirmed to exist (though it may
    * have Starlark errors).
@@ -554,11 +611,11 @@
   private BzlLoadValue computeInternalWithCompiledBzl(
       BzlLoadValue.Key key,
       BzlCompileValue compileValue,
+      StarlarkSemantics starlarkSemantics,
       Environment env,
       @Nullable InliningState inliningState)
       throws BzlLoadFailedException, InterruptedException {
     Label label = key.getLabel();
-    PathFragment filePath = label.toPathFragment();
 
     // Ensure the .bzl exists and passes static checks (parsing, resolving).
     // (A missing prelude file still returns a valid but empty BzlCompileValue.)
@@ -567,7 +624,7 @@
     }
     StarlarkFile file = compileValue.getAST();
     if (!file.ok()) {
-      throw BzlLoadFailedException.starlarkErrors(filePath);
+      throw BzlLoadFailedException.starlarkErrors(label);
     }
 
     // Determine dependency BzlLoadValue keys for the load statements in this bzl. Labels are
@@ -580,7 +637,7 @@
         getLoadLabels(env.getListener(), file, label.getPackageIdentifier(), repoMapping);
     if (loadLabels == null) {
       // malformed load statements
-      throw BzlLoadFailedException.starlarkErrors(filePath);
+      throw BzlLoadFailedException.starlarkErrors(label);
     }
     List<BzlLoadValue.Key> loadKeys = Lists.newArrayListWithExpectedSize(loadLabels.size());
     for (Pair<String, Label> entry : loadLabels) {
@@ -614,11 +671,7 @@
       fp.addBytes(v.getTransitiveDigest());
     }
 
-    // Retrieve semantics and predeclared symbols, and complete the digest computation.
-    StarlarkSemantics starlarkSemantics = PrecomputedValue.STARLARK_SEMANTICS.get(env);
-    if (starlarkSemantics == null) {
-      return null;
-    }
+    // Retrieve predeclared symbols and complete the digest computation.
     ImmutableMap<String, Object> predeclared =
         getAndDigestPredeclaredEnvironment(key, env, starlarkSemantics, fp, inliningState);
     if (predeclared == null) {
@@ -912,7 +965,7 @@
         skyframeEventHandler.post(post);
       }
       if (starlarkEventHandler.hasErrors()) {
-        throw BzlLoadFailedException.errors(label.toPathFragment());
+        throw BzlLoadFailedException.errors(label);
       }
     }
   }
@@ -1111,13 +1164,21 @@
     static BzlLoadFailedException whileLoadingDep(
         String requestingFile, BzlLoadFailedException cause) {
       // Don't chain exception cause, just incorporate the message with a prefix.
+      // TODO(brandjon): Normalize the requestingFile part to use the same convention for printing
+      // the bzl file as in errors(), starlarkErrors().
       return new BzlLoadFailedException(
           "in " + requestingFile + ": " + cause.getMessage(), cause.getDetailedExitCode());
     }
 
-    static BzlLoadFailedException errors(PathFragment file) {
+    static BzlLoadFailedException errors(Label label) {
+      // TODO(brandjon): Merge with starlarkErrors(). Drop "Extension" terminology, but still
+      // distinguish between static and dynamic (eval) errors in the message.
       return new BzlLoadFailedException(
-          String.format("Extension file '%s' has errors", file), Code.EVAL_ERROR);
+          String.format(
+              "Extension file '%s'%s has errors",
+              label.toPathFragment(),
+              StarlarkBuiltinsValue.isBuiltinsRepo(label.getRepository()) ? " (internal)" : ""),
+          Code.EVAL_ERROR);
     }
 
     static BzlLoadFailedException errorFindingContainingPackage(
@@ -1170,9 +1231,13 @@
           Code.LABEL_CROSSES_PACKAGE_BOUNDARY);
     }
 
-    static BzlLoadFailedException starlarkErrors(PathFragment file) {
+    static BzlLoadFailedException starlarkErrors(Label label) {
       return new BzlLoadFailedException(
-          String.format("Extension '%s' has errors", file), Code.PARSE_ERROR);
+          String.format(
+              "Extension '%s'%s has errors",
+              label.toPathFragment(),
+              StarlarkBuiltinsValue.isBuiltinsRepo(label.getRepository()) ? " (internal)" : ""),
+          Code.PARSE_ERROR);
     }
 
     static BzlLoadFailedException builtinsFailed(Label file, BuiltinsFailedException cause) {