[7.0.2] Disregard WORKSPACE while verifying lockfile repo mapping entries in extension eval (#21003)

See code comment and linked issue for more information.

Fixes #20942.

Closes #20982.

Commit
https://github.com/bazelbuild/bazel/commit/21508b1446b776635b64f53f0ed4770153741e4b

PiperOrigin-RevId: 600856392
Change-Id: I5b8a0ed3a38e37ab51ffb49b19a59f2e161b9a33

---------

Co-authored-by: Xdng Yng <wyverald@gmail.com>
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/SingleExtensionEvalFunction.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/SingleExtensionEvalFunction.java
index 753a8cc..fcdbcab 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/SingleExtensionEvalFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/SingleExtensionEvalFunction.java
@@ -342,10 +342,19 @@
   private static boolean didRepoMappingsChange(
       Environment env, ImmutableTable<RepositoryName, String, RepositoryName> recordedRepoMappings)
       throws InterruptedException, NeedsSkyframeRestartException {
+    // Request repo mappings for any 'source repos' in the recorded mapping entries.
+    // Note specially that the main repo needs to be treated differently: if any .bzl file from the
+    // main repo was used for module extension eval, it _has_ to be before WORKSPACE is evaluated
+    // (see relevant code in BzlLoadFunction#getRepositoryMapping), so we only request the main repo
+    // mapping _without_ WORKSPACE repos. See #20942 for more information.
     SkyframeLookupResult result =
         env.getValuesAndExceptions(
             recordedRepoMappings.rowKeySet().stream()
-                .map(RepositoryMappingValue::key)
+                .map(
+                    repoName ->
+                        repoName.isMain()
+                            ? RepositoryMappingValue.KEY_FOR_ROOT_MODULE_WITHOUT_WORKSPACE_REPOS
+                            : RepositoryMappingValue.key(repoName))
                 .collect(toImmutableSet()));
     if (env.valuesMissing()) {
       // This likely means that one of the 'source repos' in the recorded mapping entries is no
@@ -354,7 +363,11 @@
     }
     for (Table.Cell<RepositoryName, String, RepositoryName> cell : recordedRepoMappings.cellSet()) {
       RepositoryMappingValue repoMappingValue =
-          (RepositoryMappingValue) result.get(RepositoryMappingValue.key(cell.getRowKey()));
+          (RepositoryMappingValue)
+              result.get(
+                  cell.getRowKey().isMain()
+                      ? RepositoryMappingValue.KEY_FOR_ROOT_MODULE_WITHOUT_WORKSPACE_REPOS
+                      : RepositoryMappingValue.key(cell.getRowKey()));
       if (repoMappingValue == null) {
         throw new NeedsSkyframeRestartException();
       }
diff --git a/src/test/py/bazel/bzlmod/bazel_lockfile_test.py b/src/test/py/bazel/bzlmod/bazel_lockfile_test.py
index f09ca3b..9bff260 100644
--- a/src/test/py/bazel/bzlmod/bazel_lockfile_test.py
+++ b/src/test/py/bazel/bzlmod/bazel_lockfile_test.py
@@ -1863,6 +1863,50 @@
     self.assertIn('ran the extension!', '\n'.join(stderr))
     self.assertIn('STR=@@quux~1.0//:quux.h', '\n'.join(stderr))
 
+  def testExtensionRepoMappingChange_mainRepoEvalCycleWithWorkspace(self):
+    # Regression test for #20942
+    self.main_registry.createCcModule('foo', '1.0')
+    self.ScratchFile(
+        'MODULE.bazel',
+        [
+            'bazel_dep(name="foo",version="1.0")',
+            'ext = use_extension(":ext.bzl", "ext")',
+            'use_repo(ext, "repo")',
+        ],
+    )
+    self.ScratchFile(
+        'BUILD.bazel',
+        [
+            'load("@repo//:defs.bzl", "STR")',
+            'print("STR="+STR)',
+            'filegroup(name="lol")',
+        ],
+    )
+    self.ScratchFile(
+        'ext.bzl',
+        [
+            'def _repo_impl(rctx):',
+            '  rctx.file("BUILD")',
+            '  rctx.file("defs.bzl", "STR = " + repr(str(rctx.attr.value)))',
+            'repo = repository_rule(_repo_impl,attrs={"value":attr.label()})',
+            'def _ext_impl(mctx):',
+            '  print("ran the extension!")',
+            '  repo(name = "repo", value = Label("@foo//:lib_foo"))',
+            'ext = module_extension(_ext_impl)',
+        ],
+    )
+    # any `load` in WORKSPACE should trigger the bug
+    self.ScratchFile('WORKSPACE.bzlmod', ['load("@repo//:defs.bzl","STR")'])
+
+    _, _, stderr = self.RunBazel(['build', ':lol'])
+    self.assertIn('STR=@@foo~1.0//:lib_foo', '\n'.join(stderr))
+
+    # Shutdown bazel to make sure we rely on the lockfile and not skyframe
+    self.RunBazel(['shutdown'])
+    # Build again. This should _NOT_ trigger a failure!
+    _, _, stderr = self.RunBazel(['build', ':lol'])
+    self.assertNotIn('ran the extension!', '\n'.join(stderr))
+
 
 if __name__ == '__main__':
   absltest.main()