Repository: bazelbuild/bazel
Issue: [bzlmod] bazel mod graph fails with tuples in MODULE.bazel
Labels: type: bug
team-ExternalDeps
untriaged
area-Bzlmod

### Description of the bug:


I run `bazel mod graph` and I get this failure:

```
 checking cached actions
FATAL: bazel crashed due to an internal error. Printing stack trace:
java.lang.IllegalArgumentException: Unsupported type: class net.starlark.java.eval.Tuple
        at com.google.devtools.build.lib.bazel.bzlmod.AttributeValuesAdapter.serializeObject(AttributeValuesAdapter.java:98)
        at com.google.devtools.build.lib.bazel.bzlmod.AttributeValuesAdapter.serializeObject(AttributeValuesAdapter.java:88)
        at com.google.devtools.build.lib.bazel.bzlmod.AttributeValuesAdapter.write(AttributeValuesAdapter.java:53)
        at com.google.devtools.build.lib.bazel.bzlmod.AttributeValuesAdapter.write(AttributeValuesAdapter.java:44)
        at com.google.devtools.build.lib.bazel.bzlmod.Tag_GsonTypeAdapter.write(Tag_GsonTypeAdapter.java:57)
        at com.google.devtools.build.lib.bazel.bzlmod.Tag_GsonTypeAdapter.write(Tag_GsonTypeAdapter.java:12)
        at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:69)
        at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:97)
        at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:61)
        at com.google.devtools.build.lib.bazel.bzlmod.DelegateTypeAdapterFactory$1.write(DelegateTypeAdapterFactory.java:128)
        at com.google.devtools.build.lib.bazel.bzlmod.ModuleExtensionUsage_GsonTypeAdapter.write(ModuleExtensionUsage_GsonTypeAdapter.java:130)
        at com.google.devtools.build.lib.bazel.bzlmod.ModuleExtensionUsage_GsonTypeAdapter.write(ModuleExtensionUsage_GsonTypeAdapter.java:17)
        at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:69)
        at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:97)
        at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:61)
        at com.google.devtools.build.lib.bazel.bzlmod.DelegateTypeAdapterFactory$1.write(DelegateTypeAdapterFactory.java:128)
        at com.google.devtools.build.lib.bazel.bzlmod.Module_GsonTypeAdapter.write(Module_GsonTypeAdapter.java:115)
        at com.google.devtools.build.lib.bazel.bzlmod.Module_GsonTypeAdapter.write(Module_GsonTypeAdapter.java:14)
        at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:69)
        at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.write(MapTypeAdapterFactory.java:239)
        at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.write(MapTypeAdapterFactory.java:145)
        at com.google.devtools.build.lib.bazel.bzlmod.DelegateTypeAdapterFactory$1.write(DelegateTypeAdapterFactory.java:128)
        at com.google.devtools.build.lib.bazel.bzlmod.BazelLockFileValue_GsonTypeAdapter.write(BazelLockFileValue_GsonTypeAdapter.java:90)
        at com.google.devtools.build.lib.bazel.bzlmod.BazelLockFileValue_GsonTypeAdapter.write(BazelLockFileValue_GsonTypeAdapter.java:13)
        at com.google.gson.Gson.toJson(Gson.java:704)
        at com.google.gson.Gson.toJson(Gson.java:683)
        at com.google.gson.Gson.toJson(Gson.java:638)
        at com.google.gson.Gson.toJson(Gson.java:618)
        at com.google.devtools.build.lib.bazel.bzlmod.BazelLockFileModule.updateLockfile(BazelLockFileModule.java:234)
        at com.google.devtools.build.lib.bazel.bzlmod.BazelLockFileModule.afterCommand(BazelLockFileModule.java:114)
        at com.google.devtools.build.lib.runtime.BlazeRuntime.afterCommand(BlazeRuntime.java:625)
        at com.google.devtools.build.lib.runtime.BlazeCommandDispatcher.execExclusively(BlazeCommandDispatcher.java:678)
        at com.google.devtools.build.lib.runtime.BlazeCommandDispatcher.exec(BlazeCommandDispatcher.java:246)
        at com.google.devtools.build.lib.server.GrpcServerImpl.executeCommand(GrpcServerImpl.java:550)
        at com.google.devtools.build.lib.server.GrpcServerImpl.lambda$run$1(GrpcServerImpl.java:614)
        at io.grpc.Context$1.run(Context.java:566)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
        at java.base/java.lang.Thread.run(Unknown Source)

```

### Which category does this issue belong to?


CLI

### What's the simplest, easiest way to reproduce this bug? Please provide a minimal example if possible.


Create a MODULE.bazel with a go_sdk that looks like this:
```
go_sdk = use_extension("@io_bazel_rules_go//go:extensions.bzl", "go_sdk")
go_sdk.download(
    name = "go_sdk",
    patches = [
        "//...patches_to_stdlib",...
    ],
    sdks = {
        "linux_amd64": (
            "go{}.linux-amd64".format(go_version),
            "3f5c50e592d8845d30eebd08ddb9c670fd2d909e032b6c2b83ecf97afb34660c",
        ),
        "linux_arm64": (
            "go{}.linux-arm64".format(go_version),
            "30ce0b015c9c238e0c73014a6f92a22b8fe31ffded5b62a23840e3f13a90c842",
        ),
        "darwin_amd64": (
            "go{}.darwin-amd64".format(go_version),
            "3b941e669e22b517db8ff65924666abfe306847a0d918967e202b468d3733d2e",
        ),
        "darwin_arm64": (
            "go{}.darwin-arm64".format(go_version),
            "fbe78beee7861da09de9319498a1521bc41a8b79d3ead2218ce307b9fa5503e5",
        ),
    },
    urls = [
        "some urls"
    ],
    version = go_version,
)
use_repo(go_sdk, "go_sdk")
```

Then run `bazel mod tidy`

### Which operating system are you running Bazel on?


Linux AMD 64

### What is the output of `bazel info release`?


6.4.0

### If `bazel info release` returns `development version` or `(@non-git)`, tell us how you built Bazel.


_No response_

### What's the output of `git remote get-url origin; git rev-parse master; git rev-parse HEAD` ?


_No response_

### Is this a regression? If yes, please try to identify the Bazel commit where the bug was introduced.


_No response_

### Have you found anything relevant by searching the web?

_No response_

### Any other information, logs, or outputs that you want to share?

_No response_

Issue comments:
This reproduces with bazel version set to `7.0.0rc2`
cc @fmeum 
You are passing a dict from strings to tuples to `sdk`, but that's not a supported attribute type. Instead, you should pass a dict from strings to lists. But yes, Bazel shouldn't crash in this case.
I see, so we should also probably adjust the `sdks` [README example](https://github.com/bazelbuild/rules_go/blob/master/go/toolchains.rst#go_download_sdk), which uses tuples
Actually, I do see the semantic appeal of this now and if BUILD files support this and it's reasonably simple to add support for MODULE.bazel files, then why not. We need to handle the crash in some way anyway. I submitted https://github.com/bazelbuild/bazel/pull/20129.
A fix for this issue has been included in [Bazel 7.0.0 RC5](https://releases.bazel.build/7.0.0/rc5/index.html). Please test out the release candidate and report any issues as soon as possible. Thanks!

Pull request review comments:

Patch that resolved the issue:
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/AttributeValuesAdapter.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/AttributeValuesAdapter.java
index 248d1060975d91..85fdd2a767eb2c 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/AttributeValuesAdapter.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/AttributeValuesAdapter.java
@@ -88,9 +88,11 @@ private JsonElement serializeObject(Object obj) {
         jsonObject.add(serializeObjToString(entry.getKey()), serializeObject(entry.getValue()));
       }
       return jsonObject;
-    } else if (obj instanceof StarlarkList) {
+    } else if (obj instanceof Iterable) {
+      // ListType supports any kind of Iterable, including Tuples and StarlarkLists. All of them
+      // are converted to an equivalent StarlarkList during deserialization.
       JsonArray jsonArray = new JsonArray();
-      for (Object item : (StarlarkList<?>) obj) {
+      for (Object item : (Iterable<?>) obj) {
         jsonArray.add(serializeObject(item));
       }
       return jsonArray;


Test patch:
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/AttributeValuesAdapterTest.java b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/AttributeValuesAdapterTest.java
index 45fcbd7d322ea6..b5c0cda5ae66bb 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/AttributeValuesAdapterTest.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/AttributeValuesAdapterTest.java
@@ -30,6 +30,7 @@
 import net.starlark.java.eval.Mutability;
 import net.starlark.java.eval.StarlarkInt;
 import net.starlark.java.eval.StarlarkList;
+import net.starlark.java.eval.Tuple;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -89,4 +90,24 @@ public void testAttributeValuesAdapter() throws IOException {
     assertThat(jsonString).contains(":\"Hello String\"");
     assertThat((Map<?, ?>) attributeValues.attributes()).containsExactlyEntriesIn(builtDict);
   }
+
+  @Test
+  public void testTuple() throws IOException {
+    Dict.Builder<String, Object> dict = new Dict.Builder<>();
+    dict.put("Tuple", Tuple.of("bzl", "mod"));
+
+    Dict<String, Object> builtDict = dict.buildImmutable();
+    AttributeValuesAdapter attrAdapter = new AttributeValuesAdapter();
+    String jsonString;
+    try (StringWriter stringWriter = new StringWriter()) {
+      attrAdapter.write(new JsonWriter(stringWriter), AttributeValues.create(builtDict));
+      jsonString = stringWriter.toString();
+    }
+    AttributeValues attributeValues;
+    try (StringReader stringReader = new StringReader(jsonString)) {
+      attributeValues = attrAdapter.read(new JsonReader(stringReader));
+    }
+    assertThat((Map<?, ?>) attributeValues.attributes())
+        .containsExactly("Tuple", StarlarkList.of(Mutability.IMMUTABLE, "bzl", "mod"));
+  }
 }