Repository: pantsbuild/pants

Issue:
Wrong FileDigest when using multiple jvm_artifact with jar field in the same BUILD file
**Describe the bug**
We have one BUILD file under 3rdparty/jvm with all dependencies. In that BUILD file, we have multiple `jvm_artifact` with the `jar` field referencing a jar file. When generating the lockfile pants pick the wrong file digest for each of those `jvm_artifact`s.

To find the problem we ran pants from source and logged the `coursier_report.json` file, we noticed that the artifact is not matching the jar, here is the snippet from the file:

```
 {
         "coord":"com.lihaoyi:upickle-core_2.13:1.5.0-1-fc17a6",
         "file":"/private/var/folders/0_/9381kp7j0852xw9kj3twl59r0000gn/T/process-execution2ZZ8Vv/3rdparty/jvm/../../lib/upickle-implicits_2.13.jar",
         "directDependencies":[
            
         ],
         "dependencies":[
            
         ]
      },
      {
         "coord":"com.lihaoyi:upickle-implicits_2.13:1.5.0-1-fc17a6",
         "file":"/private/var/folders/0_/9381kp7j0852xw9kj3twl59r0000gn/T/process-execution2ZZ8Vv/3rdparty/jvm/../../lib/upickle_2.13.jar",
         "directDependencies":[
            
         ],
         "dependencies":[
            
         ]
      },
      {
         "coord":"com.lihaoyi:upickle_2.13:1.5.0-1-fc17a6",
         "file":"/private/var/folders/0_/9381kp7j0852xw9kj3twl59r0000gn/T/process-execution2ZZ8Vv/3rdparty/jvm/../../lib/upickle-core_2.13.jar",
         "directDependencies":[
            
         ],
         "dependencies":[
            
         ]
      }
```

It seems like a sorting problem and we pinpoint the issue to the zip function here:
https://github.com/pantsbuild/pants/blob/main/src/python/pants/jvm/resolve/coursier_fetch.py#L351

It seems the two collections are not sorted in the same way.
However, lacking enough knowledge of pants, we cannot think about how to sort both collections in a deterministic way.

**Pants version**
2.13.0.dev1


**OS**
MacOS

**Additional info**
We can workaround by either sorting the `jvm_artifact`s in the BUILD file or splitting them into one `jvm_artifact` per BUILD file.


Discussion:
Thanks for the report! I'll look at this one tomorrow.
Posted #15571 for this. I'm fairly confident in the fix, but didn't have time for a test today. If you have time to try it out and confirm, that would be appreciated. We'll cherry-pick back to `2.11.x`.
@stuhood tested this today, and so far looks good.
Thanks!

Resolving patch:
diff --git a/src/python/pants/jvm/resolve/coursier_fetch.py b/src/python/pants/jvm/resolve/coursier_fetch.py
--- a/src/python/pants/jvm/resolve/coursier_fetch.py
+++ b/src/python/pants/jvm/resolve/coursier_fetch.py
@@ -341,19 +341,29 @@ async def prepare_coursier_resolve_info(
         excludes_digest = await Get(Digest, CreateDigest([excludes_file_content]))
         extra_args += ["--local-exclude-file", LOCAL_EXCLUDE_FILE]
 
-    jar_files = await Get(SourceFiles, SourceFilesRequest(i[1] for i in jars))
-    jar_file_paths = jar_files.snapshot.files
+    jar_file_sources = await MultiGet(
+        Get(SourceFiles, SourceFilesRequest([jar_source_field])) for _, jar_source_field in jars
+    )
+    jar_file_paths = [jar_file_source.snapshot.files[0] for jar_file_source in jar_file_sources]
 
     resolvable_jar_requirements = [
         dataclasses.replace(
             req, jar=None, url=f"file:{Coursier.working_directory_placeholder}/{path}"
         )
-        for req, path in zip((i[0] for i in jars), jar_file_paths)
+        for (req, _), path in zip(jars, jar_file_paths)
     ]
 
     to_resolve = chain(no_jars, resolvable_jar_requirements)
 
-    digest = await Get(Digest, MergeDigests([jar_files.snapshot.digest, excludes_digest]))
+    digest = await Get(
+        Digest,
+        MergeDigests(
+            [
+                *(jar_file_source.snapshot.digest for jar_file_source in jar_file_sources),
+                excludes_digest,
+            ]
+        ),
+    )
 
     return CoursierResolveInfo(
         coord_arg_strings=frozenset(req.to_coord_arg_str() for req in to_resolve),


Test patch: