bzlmod: Precompute data structures needed for module extension resolution

(https://github.com/bazelbuild/bazel/issues/13316)

At the end of BazelModuleResolutionFunction, we do some precomputation such as grouping all ModuleExtensionUsages by their "extension ID", and calculating a unique name for each extension.

PiperOrigin-RevId: 395948289
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/AbridgedModule.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/AbridgedModule.java
new file mode 100644
index 0000000..aed3f74
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/AbridgedModule.java
@@ -0,0 +1,48 @@
+// Copyright 2021 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package com.google.devtools.build.lib.bazel.bzlmod;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.cmdline.RepositoryMapping;
+
+/**
+ * An abridged version of a {@link Module}, with a reduced set of information available, used for
+ * module extension resolution.
+ */
+@AutoValue
+public abstract class AbridgedModule {
+  public abstract String getName();
+
+  public abstract Version getVersion();
+
+  public abstract ModuleKey getKey();
+
+  public final String getCanonicalRepoName() {
+    return getKey().getCanonicalRepoName();
+  }
+
+  public abstract ImmutableMap<String, ModuleKey> getDeps();
+
+  public final RepositoryMapping getRepoMapping() {
+    return Module.getRepoMappingWithBazelDepsOnly(getKey(), getName(), getDeps());
+  }
+
+  public static AbridgedModule from(Module module) {
+    return new AutoValue_AbridgedModule(
+        module.getName(), module.getVersion(), module.getKey(), module.getDeps());
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BUILD b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BUILD
index fe9853f..749a238 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BUILD
@@ -44,6 +44,7 @@
     name = "module_extension",
     srcs = [
         "ModuleExtension.java",
+        "ModuleExtensionId.java",
         "ModuleExtensionUsage.java",
         "Tag.java",
         "TagClass.java",
@@ -81,6 +82,7 @@
 java_library(
     name = "resolution",
     srcs = [
+        "AbridgedModule.java",
         "ArchiveOverride.java",
         "BazelModuleResolutionValue.java",
         "GitOverride.java",
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleResolutionFunction.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleResolutionFunction.java
index 8edde3b..76265cb 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleResolutionFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleResolutionFunction.java
@@ -15,16 +15,24 @@
 
 package com.google.devtools.build.lib.bazel.bzlmod;
 
+import static com.google.common.collect.ImmutableList.toImmutableList;
 import static com.google.common.collect.ImmutableMap.toImmutableMap;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableTable;
 import com.google.devtools.build.lib.bazel.bzlmod.ModuleFileValue.RootModuleFileValue;
+import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
+import com.google.devtools.build.lib.packages.BuildType.LabelConversionContext;
+import com.google.devtools.build.lib.server.FailureDetails.ExternalDeps.Code;
 import com.google.devtools.build.skyframe.SkyFunction;
 import com.google.devtools.build.skyframe.SkyFunctionException;
 import com.google.devtools.build.skyframe.SkyFunctionException.Transience;
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.SkyValue;
+import java.util.HashMap;
 
 /**
  * Runs Bazel module resolution. This function produces the dependency graph containing all Bazel
@@ -57,7 +65,9 @@
 
   @VisibleForTesting
   static BazelModuleResolutionValue createValue(
-      ImmutableMap<ModuleKey, Module> depGraph, ImmutableMap<String, ModuleOverride> overrides) {
+      ImmutableMap<ModuleKey, Module> depGraph, ImmutableMap<String, ModuleOverride> overrides)
+      throws BazelModuleResolutionFunctionException {
+    // Build some reverse lookups for later use.
     ImmutableMap<String, ModuleKey> canonicalRepoNameLookup =
         depGraph.keySet().stream()
             .collect(toImmutableMap(ModuleKey::getCanonicalRepoName, key -> key));
@@ -69,7 +79,58 @@
             .filter(key -> !(overrides.get(key.getName()) instanceof MultipleVersionOverride))
             .collect(toImmutableMap(ModuleKey::getName, key -> key));
 
-    return BazelModuleResolutionValue.create(depGraph, canonicalRepoNameLookup, moduleNameLookup);
+    // For each extension usage, we resolve (i.e. canonicalize) its bzl file label. Then we can
+    // group all usages by the label + name (the ModuleExtensionId).
+    ImmutableTable.Builder<ModuleExtensionId, ModuleKey, ModuleExtensionUsage>
+        extensionUsagesTableBuilder = ImmutableTable.builder();
+    for (Module module : depGraph.values()) {
+      LabelConversionContext labelConversionContext =
+          new LabelConversionContext(
+              StarlarkBazelModule.createModuleRootLabel(module.getCanonicalRepoName()),
+              module.getRepoMappingWithBazelDepsOnly(),
+              new HashMap<>());
+      for (ModuleExtensionUsage usage : module.getExtensionUsages()) {
+        try {
+          ModuleExtensionId moduleExtensionId =
+              ModuleExtensionId.create(
+                  labelConversionContext.convert(usage.getExtensionBzlFile()),
+                  usage.getExtensionName());
+          extensionUsagesTableBuilder.put(moduleExtensionId, module.getKey(), usage);
+        } catch (LabelSyntaxException e) {
+          throw new BazelModuleResolutionFunctionException(
+              ExternalDepsException.withCauseAndMessage(
+                  Code.BAD_MODULE,
+                  e,
+                  "invalid label for module extension found at %s",
+                  usage.getLocation()),
+              Transience.PERSISTENT);
+        }
+      }
+    }
+    ImmutableTable<ModuleExtensionId, ModuleKey, ModuleExtensionUsage> extensionUsagesById =
+        extensionUsagesTableBuilder.build();
+
+    // Calculate a unique name for each used extension id.
+    BiMap<String, ModuleExtensionId> extensionUniqueNames = HashBiMap.create();
+    for (ModuleExtensionId id : extensionUsagesById.rowKeySet()) {
+      String bestName =
+          id.getBzlFileLabel().getRepository().strippedName() + "." + id.getExtensionName();
+      if (extensionUniqueNames.putIfAbsent(bestName, id) == null) {
+        continue;
+      }
+      int suffix = 2;
+      while (extensionUniqueNames.putIfAbsent(bestName + suffix, id) != null) {
+        suffix++;
+      }
+    }
+
+    return BazelModuleResolutionValue.create(
+        depGraph,
+        canonicalRepoNameLookup,
+        moduleNameLookup,
+        depGraph.values().stream().map(AbridgedModule::from).collect(toImmutableList()),
+        extensionUsagesById,
+        ImmutableMap.copyOf(extensionUniqueNames.inverse()));
   }
 
   @Override
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleResolutionValue.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleResolutionValue.java
index 0a5bc1d..e8f39fb 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleResolutionValue.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleResolutionValue.java
@@ -16,11 +16,16 @@
 package com.google.devtools.build.lib.bazel.bzlmod;
 
 import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableTable;
+import com.google.devtools.build.lib.cmdline.RepositoryMapping;
+import com.google.devtools.build.lib.cmdline.RepositoryName;
 import com.google.devtools.build.lib.skyframe.SkyFunctions;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.SkyValue;
+import java.util.Map;
 
 /**
  * The result of running Bazel module resolution, containing the Bazel module dependency graph
@@ -33,9 +38,17 @@
   public static BazelModuleResolutionValue create(
       ImmutableMap<ModuleKey, Module> depGraph,
       ImmutableMap<String, ModuleKey> canonicalRepoNameLookup,
-      ImmutableMap<String, ModuleKey> moduleNameLookup) {
+      ImmutableMap<String, ModuleKey> moduleNameLookup,
+      ImmutableList<AbridgedModule> abridgedModules,
+      ImmutableTable<ModuleExtensionId, ModuleKey, ModuleExtensionUsage> extensionUsagesTable,
+      ImmutableMap<ModuleExtensionId, String> extensionUniqueNames) {
     return new AutoValue_BazelModuleResolutionValue(
-        depGraph, canonicalRepoNameLookup, moduleNameLookup);
+        depGraph,
+        canonicalRepoNameLookup,
+        moduleNameLookup,
+        abridgedModules,
+        extensionUsagesTable,
+        extensionUniqueNames);
   }
 
   /**
@@ -52,4 +65,46 @@
    * or modules with multiple-version overrides.
    */
   public abstract ImmutableMap<String, ModuleKey> getModuleNameLookup();
+
+  /** All modules in the same order as {@link #getDepGraph}, but with limited information. */
+  public abstract ImmutableList<AbridgedModule> getAbridgedModules();
+
+  /**
+   * All module extension usages grouped by the extension's ID and the key of the module where this
+   * usage occurs. For each extension identifier ID, extensionUsagesTable[ID][moduleKey] is the
+   * ModuleExtensionUsage of ID in the module keyed by moduleKey.
+   */
+  public abstract ImmutableTable<ModuleExtensionId, ModuleKey, ModuleExtensionUsage>
+      getExtensionUsagesTable();
+
+  /**
+   * A mapping from the ID of a module extension to a unique string that serves as its "name". This
+   * is not the same as the extension's declared name, as the declared name is only unique within
+   * the .bzl file, whereas this unique name is guaranteed to be unique across the workspace.
+   */
+  public abstract ImmutableMap<ModuleExtensionId, String> getExtensionUniqueNames();
+
+  /**
+   * Returns the full {@link RepositoryMapping} for the given module, including repos from Bazel
+   * module deps and module extensions.
+   */
+  public final RepositoryMapping getFullRepoMapping(ModuleKey key) {
+    ImmutableMap.Builder<RepositoryName, RepositoryName> mapping = ImmutableMap.builder();
+    for (Map.Entry<ModuleExtensionId, ModuleExtensionUsage> e :
+        getExtensionUsagesTable().column(key).entrySet()) {
+      ModuleExtensionId extensionId = e.getKey();
+      ModuleExtensionUsage usage = e.getValue();
+      for (Map.Entry<String, String> entry : usage.getImports().entrySet()) {
+        String canonicalRepoName =
+            getExtensionUniqueNames().get(extensionId) + "." + entry.getValue();
+        mapping.put(
+            RepositoryName.createFromValidStrippedName(entry.getKey()),
+            RepositoryName.createFromValidStrippedName(canonicalRepoName));
+      }
+    }
+    return getDepGraph()
+        .get(key)
+        .getRepoMappingWithBazelDepsOnly()
+        .withAdditionalMappings(mapping.build());
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/Module.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/Module.java
index d05f348..b9dbf77 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/Module.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/Module.java
@@ -63,6 +63,10 @@
    */
   public abstract ModuleKey getKey();
 
+  public final String getCanonicalRepoName() {
+    return getKey().getCanonicalRepoName();
+  }
+
   /**
    * The compatibility level of the module, which essentially signifies the "major version" of the
    * module in terms of SemVer.
@@ -75,49 +79,36 @@
    */
   public abstract ImmutableMap<String, ModuleKey> getDeps();
 
-  /**
-   * Used in {@link #getRepoMapping} to denote whether only repos from {@code bazel_dep}s should be
-   * returned, or repos from module extensions should also be returned.
-   */
-  public enum WhichRepoMappings {
-    BAZEL_DEPS_ONLY,
-    WITH_MODULE_EXTENSIONS_TOO
-  }
-
-  /** Returns the {@link RepositoryMapping} for the repo corresponding to this module. */
-  public final RepositoryMapping getRepoMapping(WhichRepoMappings whichRepoMappings) {
+  static RepositoryMapping getRepoMappingWithBazelDepsOnly(
+      ModuleKey key, String name, ImmutableMap<String, ModuleKey> deps) {
     ImmutableMap.Builder<RepositoryName, RepositoryName> mapping = ImmutableMap.builder();
     // If this is the root module, then the main repository should be visible as `@`.
-    if (getKey().equals(ModuleKey.ROOT)) {
+    if (key.equals(ModuleKey.ROOT)) {
       mapping.put(RepositoryName.MAIN, RepositoryName.MAIN);
     }
     // Every module should be able to reference itself as @<module name>.
     // If this is the root module, this perfectly falls into @<module name> => @
-    if (!getName().isEmpty()) {
+    if (!name.isEmpty()) {
       mapping.put(
-          RepositoryName.createFromValidStrippedName(getName()),
-          RepositoryName.createFromValidStrippedName(getKey().getCanonicalRepoName()));
+          RepositoryName.createFromValidStrippedName(name),
+          RepositoryName.createFromValidStrippedName(key.getCanonicalRepoName()));
     }
-    for (Map.Entry<String, ModuleKey> dep : getDeps().entrySet()) {
+    for (Map.Entry<String, ModuleKey> dep : deps.entrySet()) {
       // Special note: if `dep` is actually the root module, its ModuleKey would be ROOT whose
       // canonicalRepoName is the empty string. This perfectly maps to the main repo ("@").
       mapping.put(
           RepositoryName.createFromValidStrippedName(dep.getKey()),
           RepositoryName.createFromValidStrippedName(dep.getValue().getCanonicalRepoName()));
     }
-    if (whichRepoMappings.equals(WhichRepoMappings.WITH_MODULE_EXTENSIONS_TOO)) {
-      for (ModuleExtensionUsage usage : getExtensionUsages()) {
-        for (Map.Entry<String, String> entry : usage.getImports().entrySet()) {
-          // TODO(wyv): work out a rigorous canonical repo name format (and potentially a shorter
-          //   version when ambiguities aren't present).
-          String canonicalRepoName = usage.getExtensionName() + "." + entry.getValue();
-          mapping.put(
-              RepositoryName.createFromValidStrippedName(entry.getKey()),
-              RepositoryName.createFromValidStrippedName(canonicalRepoName));
-        }
-      }
-    }
-    return RepositoryMapping.create(mapping.build(), getKey().getCanonicalRepoName());
+    return RepositoryMapping.create(mapping.build(), key.getCanonicalRepoName());
+  }
+
+  /**
+   * Returns a {@link RepositoryMapping} with only Bazel module repos and no repos from module
+   * extensions. For the full mapping, see {@link BazelModuleResolutionValue#getFullRepoMappings}.
+   */
+  public final RepositoryMapping getRepoMappingWithBazelDepsOnly() {
+    return getRepoMappingWithBazelDepsOnly(getKey(), getName(), getDeps());
   }
 
   /**
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtensionId.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtensionId.java
new file mode 100644
index 0000000..9fef1ef
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtensionId.java
@@ -0,0 +1,31 @@
+// Copyright 2021 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package com.google.devtools.build.lib.bazel.bzlmod;
+
+import com.google.auto.value.AutoValue;
+import com.google.devtools.build.lib.cmdline.Label;
+
+/** A unique identifier for a {@link ModuleExtension}. */
+@AutoValue
+public abstract class ModuleExtensionId {
+  public abstract Label getBzlFileLabel();
+
+  public abstract String getExtensionName();
+
+  public static ModuleExtensionId create(Label bzlFileLabel, String extensionName) {
+    return new AutoValue_ModuleExtensionId(bzlFileLabel, extensionName);
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/StarlarkBazelModule.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/StarlarkBazelModule.java
index 59237ece..dbbb032 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/StarlarkBazelModule.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/StarlarkBazelModule.java
@@ -15,8 +15,8 @@
 package com.google.devtools.build.lib.bazel.bzlmod;
 
 import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableListMultimap;
-import com.google.devtools.build.lib.bazel.bzlmod.Module.WhichRepoMappings;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.cmdline.PackageIdentifier;
 import com.google.devtools.build.lib.cmdline.RepositoryName;
@@ -84,27 +84,34 @@
   }
 
   /**
-   * Creates a new {@link StarlarkBazelModule} object representing the given {@link Module}, with
-   * its scope limited to the given {@link ModuleExtension}. It'll be populated with the tags
+   * Creates a label pointing to the root package of the repo with the given canonical repo name.
+   * This label can be used to anchor (relativize) labels with no "@foo" part.
+   */
+  static Label createModuleRootLabel(String canonicalRepoName) {
+    return Label.createUnvalidated(
+        PackageIdentifier.create(
+            RepositoryName.createFromValidStrippedName(canonicalRepoName),
+            PathFragment.EMPTY_FRAGMENT),
+        "unused_dummy_target_name");
+  }
+
+  /**
+   * Creates a new {@link StarlarkBazelModule} object representing the given {@link AbridgedModule},
+   * with its scope limited to the given {@link ModuleExtension}. It'll be populated with the tags
    * present in the given {@link ModuleExtensionUsage}.
    */
   public static StarlarkBazelModule create(
-      Module module, ModuleExtension extension, ModuleExtensionUsage usage)
+      AbridgedModule module, ModuleExtension extension, @Nullable ModuleExtensionUsage usage)
       throws ExternalDepsException {
-    Label moduleRootLabel =
-        Label.createUnvalidated(
-            PackageIdentifier.create(
-                RepositoryName.createFromValidStrippedName(module.getKey().getCanonicalRepoName()),
-                PathFragment.EMPTY_FRAGMENT),
-            "unused_dummy_target_name");
     LabelConversionContext labelConversionContext =
         new LabelConversionContext(
-            moduleRootLabel,
-            module.getRepoMapping(WhichRepoMappings.BAZEL_DEPS_ONLY),
+            createModuleRootLabel(module.getCanonicalRepoName()),
+            module.getRepoMapping(),
             /* convertedLabelsInPackage= */ new HashMap<>());
+    ImmutableList<Tag> tags = usage == null ? ImmutableList.of() : usage.getTags();
     ImmutableListMultimap.Builder<String, TypeCheckedTag> typeCheckedTags =
         ImmutableListMultimap.builder();
-    for (Tag tag : usage.getTags()) {
+    for (Tag tag : tags) {
       TagClass tagClass = extension.getTagClasses().get(tag.getTagName());
       if (tagClass == null) {
         throw ExternalDepsException.withMessage(
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 ed7cc7a..85604f3 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
@@ -25,7 +25,6 @@
 import com.google.common.hash.HashFunction;
 import com.google.devtools.build.lib.analysis.BlazeDirectories;
 import com.google.devtools.build.lib.bazel.bzlmod.BazelModuleResolutionValue;
-import com.google.devtools.build.lib.bazel.bzlmod.Module.WhichRepoMappings;
 import com.google.devtools.build.lib.bazel.bzlmod.ModuleKey;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.cmdline.LabelConstants;
@@ -866,7 +865,7 @@
       return bazelModuleResolutionValue
           .getDepGraph()
           .get(moduleKey)
-          .getRepoMapping(WhichRepoMappings.BAZEL_DEPS_ONLY);
+          .getRepoMappingWithBazelDepsOnly();
     }
 
     // We are fully done with workspace evaluation so we should get the mappings from the
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 387f614..c5b391e 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
@@ -18,8 +18,6 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
 import com.google.devtools.build.lib.bazel.bzlmod.BazelModuleResolutionValue;
-import com.google.devtools.build.lib.bazel.bzlmod.Module;
-import com.google.devtools.build.lib.bazel.bzlmod.Module.WhichRepoMappings;
 import com.google.devtools.build.lib.bazel.bzlmod.ModuleKey;
 import com.google.devtools.build.lib.cmdline.LabelConstants;
 import com.google.devtools.build.lib.cmdline.RepositoryMapping;
@@ -108,8 +106,7 @@
     if (moduleKey == null) {
       return Optional.empty();
     }
-    Module module = bazelModuleResolutionValue.getDepGraph().get(moduleKey);
-    return Optional.of(module.getRepoMapping(WhichRepoMappings.WITH_MODULE_EXTENSIONS_TOO));
+    return Optional.of(bazelModuleResolutionValue.getFullRepoMapping(moduleKey));
   }
 
   private SkyValue computeFromWorkspace(
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleResolutionFunctionTest.java b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleResolutionFunctionTest.java
index d610496..27ad8d7 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleResolutionFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleResolutionFunctionTest.java
@@ -15,11 +15,16 @@
 
 package com.google.devtools.build.lib.bazel.bzlmod;
 
+import static com.google.common.collect.ImmutableList.toImmutableList;
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.devtools.build.lib.bazel.bzlmod.BzlmodTestUtil.createModuleKey;
+import static com.google.devtools.build.lib.bazel.bzlmod.BzlmodTestUtil.createRepositoryMapping;
 
+import com.google.common.collect.ImmutableBiMap;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.cmdline.Label;
+import net.starlark.java.syntax.Location;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -94,5 +99,117 @@
         .containsExactly(
             "rules_cc", createModuleKey("rules_cc", "1.0"),
             "rules_java", createModuleKey("rules_java", ""));
+    assertThat(value.getAbridgedModules())
+        .containsExactlyElementsIn(
+            depGraph.values().stream().map(AbridgedModule::from).collect(toImmutableList()));
+  }
+
+  private static ModuleExtensionUsage createModuleExtensionUsage(
+      String bzlFile, String name, String... imports) {
+    ImmutableBiMap.Builder<String, String> importsBuilder = ImmutableBiMap.builder();
+    for (int i = 0; i < imports.length; i += 2) {
+      importsBuilder.put(imports[i], imports[i + 1]);
+    }
+    return ModuleExtensionUsage.builder()
+        .setExtensionBzlFile(bzlFile)
+        .setExtensionName(name)
+        .setImports(importsBuilder.build())
+        .setLocation(Location.BUILTIN)
+        .build();
+  }
+
+  @Test
+  public void createValue_moduleExtensions() throws Exception {
+    Module root =
+        Module.builder()
+            .setName("root")
+            .setVersion(Version.parse("1.0"))
+            .setKey(ModuleKey.ROOT)
+            .addDep("rje", createModuleKey("rules_jvm_external", "1.0"))
+            .addDep("rpy", createModuleKey("rules_python", "2.0"))
+            .addExtensionUsage(
+                createModuleExtensionUsage("@rje//:defs.bzl", "maven", "av", "autovalue"))
+            .addExtensionUsage(
+                createModuleExtensionUsage("@rpy//:defs.bzl", "pip", "numpy", "numpy"))
+            .build();
+    ModuleKey depKey = createModuleKey("dep", "2.0");
+    Module dep =
+        Module.builder()
+            .setName("dep")
+            .setVersion(Version.parse("2.0"))
+            .setKey(depKey)
+            .addDep("rules_python", createModuleKey("rules_python", "2.0"))
+            .addExtensionUsage(
+                createModuleExtensionUsage("@rules_python//:defs.bzl", "pip", "np", "numpy"))
+            .addExtensionUsage(
+                createModuleExtensionUsage("//:defs.bzl", "myext", "oneext", "myext"))
+            .addExtensionUsage(
+                createModuleExtensionUsage("//incredible:conflict.bzl", "myext", "twoext", "myext"))
+            .build();
+    ImmutableMap<ModuleKey, Module> depGraph = ImmutableMap.of(ModuleKey.ROOT, root, depKey, dep);
+
+    ModuleExtensionId maven =
+        ModuleExtensionId.create(
+            Label.parseAbsoluteUnchecked("@rules_jvm_external.1.0//:defs.bzl"), "maven");
+    ModuleExtensionId pip =
+        ModuleExtensionId.create(
+            Label.parseAbsoluteUnchecked("@rules_python.2.0//:defs.bzl"), "pip");
+    ModuleExtensionId myext =
+        ModuleExtensionId.create(Label.parseAbsoluteUnchecked("@dep.2.0//:defs.bzl"), "myext");
+    ModuleExtensionId myext2 =
+        ModuleExtensionId.create(
+            Label.parseAbsoluteUnchecked("@dep.2.0//incredible:conflict.bzl"), "myext");
+
+    BazelModuleResolutionValue value =
+        BazelModuleResolutionFunction.createValue(depGraph, ImmutableMap.of());
+    assertThat(value.getExtensionUsagesTable()).hasSize(5);
+    assertThat(value.getExtensionUsagesTable())
+        .containsCell(maven, ModuleKey.ROOT, root.getExtensionUsages().get(0));
+    assertThat(value.getExtensionUsagesTable())
+        .containsCell(pip, ModuleKey.ROOT, root.getExtensionUsages().get(1));
+    assertThat(value.getExtensionUsagesTable())
+        .containsCell(pip, depKey, dep.getExtensionUsages().get(0));
+    assertThat(value.getExtensionUsagesTable())
+        .containsCell(myext, depKey, dep.getExtensionUsages().get(1));
+    assertThat(value.getExtensionUsagesTable())
+        .containsCell(myext2, depKey, dep.getExtensionUsages().get(2));
+
+    assertThat(value.getExtensionUniqueNames())
+        .containsExactly(
+            maven, "rules_jvm_external.1.0.maven",
+            pip, "rules_python.2.0.pip",
+            myext, "dep.2.0.myext",
+            myext2, "dep.2.0.myext2");
+
+    assertThat(value.getFullRepoMapping(ModuleKey.ROOT))
+        .isEqualTo(
+            createRepositoryMapping(
+                ModuleKey.ROOT,
+                "",
+                "",
+                "root",
+                "",
+                "rje",
+                "rules_jvm_external.1.0",
+                "rpy",
+                "rules_python.2.0",
+                "av",
+                "rules_jvm_external.1.0.maven.autovalue",
+                "numpy",
+                "rules_python.2.0.pip.numpy"));
+    assertThat(value.getFullRepoMapping(depKey))
+        .isEqualTo(
+            createRepositoryMapping(
+                depKey,
+                "dep",
+                "dep.2.0",
+                "rules_python",
+                "rules_python.2.0",
+                "np",
+                "rules_python.2.0.pip.numpy",
+                "oneext",
+                "dep.2.0.myext.myext",
+                "twoext",
+                "dep.2.0.myext2.myext"));
   }
 }
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleTest.java b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleTest.java
index 3ef4e6b..e13c265 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleTest.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleTest.java
@@ -18,9 +18,6 @@
 import static com.google.devtools.build.lib.bazel.bzlmod.BzlmodTestUtil.createModuleKey;
 import static com.google.devtools.build.lib.bazel.bzlmod.BzlmodTestUtil.createRepositoryMapping;
 
-import com.google.common.collect.ImmutableBiMap;
-import com.google.devtools.build.lib.bazel.bzlmod.Module.WhichRepoMappings;
-import net.starlark.java.syntax.Location;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -58,15 +55,8 @@
             .addDep("my_foo", createModuleKey("foo", "1.0"))
             .addDep("my_bar", createModuleKey("bar", "2.0"))
             .addDep("my_root", ModuleKey.ROOT)
-            .addExtensionUsage(
-                ModuleExtensionUsage.builder()
-                    .setExtensionBzlFile("//:defs.bzl")
-                    .setExtensionName("maven")
-                    .setLocation(Location.BUILTIN)
-                    .setImports(ImmutableBiMap.of("my_guava", "guava"))
-                    .build())
             .build();
-    assertThat(module.getRepoMapping(WhichRepoMappings.BAZEL_DEPS_ONLY))
+    assertThat(module.getRepoMappingWithBazelDepsOnly())
         .isEqualTo(
             createRepositoryMapping(
                 key,
@@ -78,20 +68,6 @@
                 "bar.2.0",
                 "my_root",
                 ""));
-    assertThat(module.getRepoMapping(WhichRepoMappings.WITH_MODULE_EXTENSIONS_TOO))
-        .isEqualTo(
-            createRepositoryMapping(
-                key,
-                "test_module",
-                "test_module.1.0",
-                "my_foo",
-                "foo.1.0",
-                "my_bar",
-                "bar.2.0",
-                "my_root",
-                "",
-                "my_guava",
-                "maven.guava"));
   }
 
   @Test
@@ -104,7 +80,7 @@
             .addDep("my_foo", createModuleKey("foo", "1.0"))
             .addDep("my_bar", createModuleKey("bar", "2.0"))
             .build();
-    assertThat(module.getRepoMapping(WhichRepoMappings.BAZEL_DEPS_ONLY))
+    assertThat(module.getRepoMappingWithBazelDepsOnly())
         .isEqualTo(
             createRepositoryMapping(
                 ModuleKey.ROOT,
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/StarlarkBazelModuleTest.java b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/StarlarkBazelModuleTest.java
index 224726b..04c9958 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/StarlarkBazelModuleTest.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/StarlarkBazelModuleTest.java
@@ -81,14 +81,14 @@
                                 .allowedFileTypes(FileTypeSet.ANY_FILE)
                                 .build())))
             .build();
-    Module module =
-        Module.builder()
-            .setName("foo")
-            .setVersion(Version.parse("1.0"))
-            .setKey(createModuleKey("foo", ""))
-            .addDep("bar", createModuleKey("bar", "2.0"))
-            .addExtensionUsage(usage)
-            .build();
+    AbridgedModule module =
+        AbridgedModule.from(
+            Module.builder()
+                .setName("foo")
+                .setVersion(Version.parse("1.0"))
+                .setKey(createModuleKey("foo", ""))
+                .addDep("bar", createModuleKey("bar", "2.0"))
+                .build());
 
     StarlarkBazelModule moduleProxy = StarlarkBazelModule.create(module, extension, usage);
 
@@ -124,13 +124,13 @@
     ModuleExtensionUsage usage = getBaseUsageBuilder().addTag(buildTag("blep").build()).build();
     ModuleExtension extension =
         getBaseExtensionBuilder().setTagClasses(ImmutableMap.of("dep", createTagClass())).build();
-    Module module =
-        Module.builder()
-            .setName("foo")
-            .setVersion(Version.parse("1.0"))
-            .setKey(createModuleKey("foo", ""))
-            .addExtensionUsage(usage)
-            .build();
+    AbridgedModule module =
+        AbridgedModule.from(
+            Module.builder()
+                .setName("foo")
+                .setVersion(Version.parse("1.0"))
+                .setKey(createModuleKey("foo", ""))
+                .build());
 
     ExternalDepsException e =
         assertThrows(