Add support for isolated extension usages to the lockfile (#19008)
Previously, Bazel would crash if a module declares an isolated extension usage and the lockfile is used.
Closes #18991.
PiperOrigin-RevId: 549631385
Change-Id: Id8e706991dc5053b2873847a62f5e0b777347c69
Co-authored-by: Fabian Meumertzheim <fabian@meumertzhe.im>
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/GsonTypeAdapterUtil.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/GsonTypeAdapterUtil.java
index 313471c8..f0190f4 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/GsonTypeAdapterUtil.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/GsonTypeAdapterUtil.java
@@ -41,7 +41,6 @@
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Base64;
-import java.util.List;
import java.util.Optional;
import javax.annotation.Nullable;
import net.starlark.java.syntax.Location;
@@ -82,52 +81,81 @@
@Override
public ModuleKey read(JsonReader jsonReader) throws IOException {
String jsonString = jsonReader.nextString();
- if (jsonString.equals("<root>")) {
- return ModuleKey.ROOT;
- }
- List<String> parts = Splitter.on('@').splitToList(jsonString);
- if (parts.get(1).equals("_")) {
- return ModuleKey.create(parts.get(0), Version.EMPTY);
- }
-
- Version version;
try {
- version = Version.parse(parts.get(1));
+ return ModuleKey.fromString(jsonString);
} catch (ParseException e) {
throw new JsonParseException(
String.format("Unable to parse ModuleKey %s version from the lockfile", jsonString),
e);
}
- return ModuleKey.create(parts.get(0), version);
}
};
- // TODO(salmasamy) need to handle "isolated" in module extensions when it is stable
public static final TypeAdapter<ModuleExtensionId> MODULE_EXTENSION_ID_TYPE_ADAPTER =
new TypeAdapter<>() {
@Override
public void write(JsonWriter jsonWriter, ModuleExtensionId moduleExtId) throws IOException {
- jsonWriter.value(moduleExtId.getBzlFileLabel() + "%" + moduleExtId.getExtensionName());
+ String isolationKeyPart = moduleExtId.getIsolationKey().map(key -> "%" + key).orElse("");
+ jsonWriter.value(
+ moduleExtId.getBzlFileLabel()
+ + "%"
+ + moduleExtId.getExtensionName()
+ + isolationKeyPart);
}
@Override
public ModuleExtensionId read(JsonReader jsonReader) throws IOException {
String jsonString = jsonReader.nextString();
- // [0] is labelString, [1] is extensionName
- List<String> extIdParts = Splitter.on("%").splitToList(jsonString);
+ var extIdParts = Splitter.on('%').splitToList(jsonString);
+ Optional<ModuleExtensionId.IsolationKey> isolationKey;
+ if (extIdParts.size() > 2) {
+ try {
+ isolationKey =
+ Optional.of(ModuleExtensionId.IsolationKey.fromString(extIdParts.get(2)));
+ } catch (ParseException e) {
+ throw new JsonParseException(
+ String.format(
+ "Unable to parse ModuleExtensionID isolation key: '%s' from the lockfile",
+ extIdParts.get(2)),
+ e);
+ }
+ } else {
+ isolationKey = Optional.empty();
+ }
try {
return ModuleExtensionId.create(
- Label.parseCanonical(extIdParts.get(0)), extIdParts.get(1), Optional.empty());
+ Label.parseCanonical(extIdParts.get(0)), extIdParts.get(1), isolationKey);
} catch (LabelSyntaxException e) {
throw new JsonParseException(
String.format(
- "Unable to parse ModuleExtensionID bzl file label: '%s' from the lockfile",
+ "Unable to parse ModuleExtensionID bzl file label: '%s' from the lockfile",
extIdParts.get(0)),
e);
}
}
};
+ public static final TypeAdapter<ModuleExtensionId.IsolationKey> ISOLATION_KEY_TYPE_ADAPTER =
+ new TypeAdapter<>() {
+ @Override
+ public void write(JsonWriter jsonWriter, ModuleExtensionId.IsolationKey isolationKey)
+ throws IOException {
+ jsonWriter.value(isolationKey.toString());
+ }
+
+ @Override
+ public ModuleExtensionId.IsolationKey read(JsonReader jsonReader) throws IOException {
+ String jsonString = jsonReader.nextString();
+ try {
+ return ModuleExtensionId.IsolationKey.fromString(jsonString);
+ } catch (ParseException e) {
+ throw new JsonParseException(
+ String.format("Unable to parse isolation key: '%s' from the lockfile", jsonString),
+ e);
+ }
+ }
+ };
+
public static final TypeAdapter<byte[]> BYTE_ARRAY_TYPE_ADAPTER =
new TypeAdapter<>() {
@Override
@@ -283,6 +311,7 @@
.registerTypeAdapter(Version.class, VERSION_TYPE_ADAPTER)
.registerTypeAdapter(ModuleKey.class, MODULE_KEY_TYPE_ADAPTER)
.registerTypeAdapter(ModuleExtensionId.class, MODULE_EXTENSION_ID_TYPE_ADAPTER)
+ .registerTypeAdapter(ModuleExtensionId.IsolationKey.class, ISOLATION_KEY_TYPE_ADAPTER)
.registerTypeAdapter(AttributeValues.class, new AttributeValuesAdapter())
.registerTypeAdapter(byte[].class, BYTE_ARRAY_TYPE_ADAPTER)
.create();
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
index 7690cf0..250f83a 100644
--- 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
@@ -19,8 +19,10 @@
import static java.util.Comparator.comparing;
import com.google.auto.value.AutoValue;
+import com.google.common.base.Splitter;
import com.google.devtools.build.lib.cmdline.Label;
import java.util.Comparator;
+import java.util.List;
import java.util.Optional;
/** A unique identifier for a {@link ModuleExtension}. */
@@ -49,6 +51,17 @@
public static IsolationKey create(ModuleKey module, String usageExportedName) {
return new AutoValue_ModuleExtensionId_IsolationKey(module, usageExportedName);
}
+
+ @Override
+ public final String toString() {
+ return getModule() + "~" + getUsageExportedName();
+ }
+
+ public static IsolationKey fromString(String s) throws Version.ParseException {
+ List<String> isolationKeyParts = Splitter.on("~").splitToList(s);
+ return ModuleExtensionId.IsolationKey.create(
+ ModuleKey.fromString(isolationKeyParts.get(0)), isolationKeyParts.get(1));
+ }
}
public abstract Label getBzlFileLabel();
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleKey.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleKey.java
index 5ae1c26..1aae4ed 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleKey.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleKey.java
@@ -16,9 +16,11 @@
package com.google.devtools.build.lib.bazel.bzlmod;
import com.google.auto.value.AutoValue;
+import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.cmdline.RepositoryName;
import java.util.Comparator;
+import java.util.List;
/** A module name, version pair that identifies a module in the external dependency graph. */
@AutoValue
@@ -82,4 +84,17 @@
return RepositoryName.createUnvalidated(
String.format("%s~%s", getName(), getVersion().isEmpty() ? "override" : getVersion()));
}
+
+ public static ModuleKey fromString(String s) throws Version.ParseException {
+ if (s.equals("<root>")) {
+ return ModuleKey.ROOT;
+ }
+ List<String> parts = Splitter.on('@').splitToList(s);
+ if (parts.get(1).equals("_")) {
+ return ModuleKey.create(parts.get(0), Version.EMPTY);
+ }
+
+ Version version = Version.parse(parts.get(1));
+ return ModuleKey.create(parts.get(0), version);
+ }
}
diff --git a/src/test/py/bazel/bzlmod/bazel_lockfile_test.py b/src/test/py/bazel/bzlmod/bazel_lockfile_test.py
index 1bbed92..76f603b 100644
--- a/src/test/py/bazel/bzlmod/bazel_lockfile_test.py
+++ b/src/test/py/bazel/bzlmod/bazel_lockfile_test.py
@@ -300,6 +300,52 @@
_, _, stderr = self.RunBazel(['build', '@hello//:all'])
self.assertNotIn('Hello from the other side!', ''.join(stderr))
+ def testIsolatedModuleExtension(self):
+ self.ScratchFile(
+ 'MODULE.bazel',
+ [
+ (
+ 'lockfile_ext = use_extension("extension.bzl", "lockfile_ext",'
+ ' isolate = True)'
+ ),
+ 'lockfile_ext.dep(name = "bmbm", versions = ["v1", "v2"])',
+ 'use_repo(lockfile_ext, "hello")',
+ ],
+ )
+ self.ScratchFile('BUILD.bazel')
+ self.ScratchFile(
+ 'extension.bzl',
+ [
+ 'def _repo_rule_impl(ctx):',
+ ' ctx.file("WORKSPACE")',
+ ' ctx.file("BUILD", "filegroup(name=\'lala\')")',
+ '',
+ 'repo_rule = repository_rule(implementation=_repo_rule_impl)',
+ '',
+ 'def _module_ext_impl(ctx):',
+ ' print("Hello from the other side!")',
+ ' repo_rule(name="hello")',
+ ' for mod in ctx.modules:',
+ ' for dep in mod.tags.dep:',
+ ' print("Name:", dep.name, ", Versions:", dep.versions)',
+ '',
+ '_dep = tag_class(attrs={"name": attr.string(), "versions":',
+ ' attr.string_list()})',
+ 'lockfile_ext = module_extension(',
+ ' implementation=_module_ext_impl,',
+ ' tag_classes={"dep": _dep},',
+ ')',
+ ],
+ )
+
+ _, _, stderr = self.RunBazel(['build', '@hello//:all'])
+ self.assertIn('Hello from the other side!', ''.join(stderr))
+ self.assertIn('Name: bmbm , Versions: ["v1", "v2"]', ''.join(stderr))
+
+ self.RunBazel(['shutdown'])
+ _, _, stderr = self.RunBazel(['build', '@hello//:all'])
+ self.assertNotIn('Hello from the other side!', ''.join(stderr))
+
def testModuleExtensionsInDifferentBuilds(self):
# Test that the module extension stays in the lockfile (as long as it's
# used in the module) even if it is not in the current build