We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
coursier post processing script choking on null dependency file in report json
**Describe the bug**
One of our 3rdparty jars (`org.geotools:gt-main:9.2` from the `https://repo.osgeo.org/repository/release` repositiory) transitively depends on a `javax.media:jai_core:1.1.3` jar. For whatever reason, Coursier's resolve report json lists `"file": null` for that jar, which the post processing script doesn't currently handle.

**Pants version**
2.9.0

**OS**
Encountered on MacOS, though expect we would see this on Linux as well

**Additional info**
Coursier config:
```
[coursier]
repos = [
  "https://maven-central.storage-download.googleapis.com/maven2",
  "https://repo1.maven.org/maven2",
  "https://repo.osgeo.org/repository/release",
]
```

affected jvm_artifact:
```
jvm_artifact(
  group="org.geotools",
  artifact="gt-main",
  version="9.2",
)
```

[slack discussion](https://pantsbuild.slack.com/archives/C046T6T9U/p1645598534013879) with links to full resolve output and json


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/python/pants/jvm/resolve/coursier_setup.py`
Content:
```
1 # Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).
2 # Licensed under the Apache License, Version 2.0 (see LICENSE).
3 
4 from __future__ import annotations
5 
6 import os
7 import shlex
8 import textwrap
9 from dataclasses import dataclass
10 from typing import ClassVar, Iterable, Tuple
11 
12 from pants.core.util_rules import external_tool
13 from pants.core.util_rules.external_tool import (
14     DownloadedExternalTool,
15     ExternalToolRequest,
16     TemplatedExternalTool,
17 )
18 from pants.engine.fs import CreateDigest, Digest, FileContent, MergeDigests
19 from pants.engine.platform import Platform
20 from pants.engine.process import BashBinary, Process
21 from pants.engine.rules import Get, MultiGet, collect_rules, rule
22 from pants.python.binaries import PythonBinary
23 from pants.util.logging import LogLevel
24 
25 COURSIER_POST_PROCESSING_SCRIPT = textwrap.dedent(
26     """\
27     import json
28     import sys
29     import os
30     from pathlib import PurePath
31     from shutil import copyfile
32 
33     report = json.load(open(sys.argv[1]))
34 
35     # Mapping from dest path to source path. It is ok to capture the same output filename multiple
36     # times if the source is the same as well.
37     classpath = dict()
38     for dep in report['dependencies']:
39         source = PurePath(dep['file'])
40         dest_name = dep['coord'].replace(":", "_")
41         _, ext = os.path.splitext(source)
42         classpath_dest = f"classpath/{dest_name}{ext}"
43 
44         existing_source = classpath.get(classpath_dest)
45         if existing_source:
46             if existing_source == source:
47                 # We've already captured this file.
48                 continue
49             raise Exception(
50                 f"Duplicate jar name {classpath_dest} with incompatible source:\\n"
51                 f"  {source}\\n"
52                 f"  {existing_source}\\n"
53             )
54         classpath[classpath_dest] = source
55         copyfile(source, classpath_dest)
56     """
57 )
58 
59 COURSIER_FETCH_WRAPPER_SCRIPT = textwrap.dedent(
60     """\
61     set -eux
62 
63     coursier_exe="$1"
64     shift
65     json_output_file="$1"
66     shift
67 
68     working_dir="$(pwd)"
69     "$coursier_exe" fetch {repos_args} \
70         --json-output-file="$json_output_file" \
71         "${{@//{coursier_working_directory}/$working_dir}}"
72     /bin/mkdir -p classpath
73     {python_path} {coursier_bin_dir}/coursier_post_processing_script.py "$json_output_file"
74     """
75 )
76 
77 
78 # TODO: Coursier renders setrlimit error line on macOS.
79 #   see https://github.com/pantsbuild/pants/issues/13942.
80 POST_PROCESS_COURSIER_STDERR_SCRIPT = textwrap.dedent(
81     """\
82     #!{python_path}
83     import sys
84     from subprocess import run, PIPE
85 
86     proc = run(sys.argv[1:], stdout=PIPE, stderr=PIPE)
87 
88     sys.stdout.buffer.write(proc.stdout)
89     sys.stderr.buffer.write(proc.stderr.replace(b"setrlimit to increase file descriptor limit failed, errno 22\\n", b""))
90     sys.exit(proc.returncode)
91     """
92 )
93 
94 
95 class CoursierSubsystem(TemplatedExternalTool):
96     options_scope = "coursier"
97     name = "coursier"
98     help = "A dependency resolver for the Maven ecosystem."
99 
100     default_version = "v2.0.16-169-g194ebc55c"
101     default_known_versions = [
102         "v2.0.16-169-g194ebc55c|linux_arm64 |da38c97d55967505b8454c20a90370c518044829398b9bce8b637d194d79abb3|18114472",
103         "v2.0.16-169-g194ebc55c|linux_x86_64|4c61a634c4bd2773b4543fe0fc32210afd343692891121cddb447204b48672e8|18486946",
104         "v2.0.16-169-g194ebc55c|macos_arm64 |15bce235d223ef1d022da30b67b4c64e9228d236b876c834b64e029bbe824c6f|17957182",
105         "v2.0.16-169-g194ebc55c|macos_x86_64|15bce235d223ef1d022da30b67b4c64e9228d236b876c834b64e029bbe824c6f|17957182",
106     ]
107     default_url_template = (
108         "https://github.com/coursier/coursier/releases/download/{version}/cs-{platform}.gz"
109     )
110     default_url_platform_mapping = {
111         "macos_arm64": "x86_64-apple-darwin",
112         "macos_x86_64": "x86_64-apple-darwin",
113         "linux_arm64": "aarch64-pc-linux",
114         "linux_x86_64": "x86_64-pc-linux",
115     }
116 
117     @classmethod
118     def register_options(cls, register) -> None:
119         super().register_options(register)
120         register(
121             "--repos",
122             type=list,
123             member_type=str,
124             default=[
125                 "https://maven-central.storage-download.googleapis.com/maven2",
126                 "https://repo1.maven.org/maven2",
127             ],
128             help=("Maven style repositories to resolve artifacts from."),
129         )
130 
131     def generate_exe(self, plat: Platform) -> str:
132         archive_filename = os.path.basename(self.generate_url(plat))
133         filename = os.path.splitext(archive_filename)[0]
134         return f"./{filename}"
135 
136 
137 @dataclass(frozen=True)
138 class Coursier:
139     """The Coursier tool and various utilities, prepared for use via `immutable_input_digests`."""
140 
141     coursier: DownloadedExternalTool
142     _digest: Digest
143 
144     bin_dir: ClassVar[str] = "__coursier"
145     fetch_wrapper_script: ClassVar[str] = f"{bin_dir}/coursier_fetch_wrapper_script.sh"
146     post_processing_script: ClassVar[str] = f"{bin_dir}/coursier_post_processing_script.py"
147     post_process_stderr: ClassVar[str] = f"{bin_dir}/coursier_post_process_stderr.py"
148     cache_name: ClassVar[str] = "coursier"
149     cache_dir: ClassVar[str] = ".cache"
150     working_directory_placeholder: ClassVar[str] = "___COURSIER_WORKING_DIRECTORY___"
151 
152     def args(self, args: Iterable[str], *, wrapper: Iterable[str] = ()) -> tuple[str, ...]:
153         return (
154             self.post_process_stderr,
155             *wrapper,
156             os.path.join(self.bin_dir, self.coursier.exe),
157             *args,
158         )
159 
160     @property
161     def env(self) -> dict[str, str]:
162         # NB: These variables have changed a few times, and they change again on `main`. But as of
163         # `v2.0.16+73-gddc6d9cc9` they are accurate. See:
164         #  https://github.com/coursier/coursier/blob/v2.0.16+73-gddc6d9cc9/modules/paths/src/main/java/coursier/paths/CoursierPaths.java#L38-L48
165         return {
166             "COURSIER_CACHE": f"{self.cache_dir}/jdk",
167             "COURSIER_ARCHIVE_CACHE": f"{self.cache_dir}/arc",
168             "COURSIER_JVM_CACHE": f"{self.cache_dir}/v1",
169         }
170 
171     @property
172     def append_only_caches(self) -> dict[str, str]:
173         return {self.cache_name: self.cache_dir}
174 
175     @property
176     def immutable_input_digests(self) -> dict[str, Digest]:
177         return {self.bin_dir: self._digest}
178 
179 
180 @dataclass(frozen=True)
181 class CoursierWrapperProcess:
182 
183     args: Tuple[str, ...]
184     input_digest: Digest
185     output_directories: Tuple[str, ...]
186     output_files: Tuple[str, ...]
187     description: str
188 
189 
190 @rule
191 async def invoke_coursier_wrapper(
192     bash: BashBinary,
193     coursier: Coursier,
194     request: CoursierWrapperProcess,
195 ) -> Process:
196 
197     return Process(
198         argv=coursier.args(
199             request.args,
200             wrapper=[bash.path, coursier.fetch_wrapper_script],
201         ),
202         input_digest=request.input_digest,
203         immutable_input_digests=coursier.immutable_input_digests,
204         output_directories=request.output_directories,
205         output_files=request.output_files,
206         append_only_caches=coursier.append_only_caches,
207         env=coursier.env,
208         description=request.description,
209         level=LogLevel.DEBUG,
210     )
211 
212 
213 @rule
214 async def setup_coursier(
215     coursier_subsystem: CoursierSubsystem,
216     python: PythonBinary,
217 ) -> Coursier:
218     repos_args = " ".join(f"-r={shlex.quote(repo)}" for repo in coursier_subsystem.options.repos)
219     coursier_wrapper_script = COURSIER_FETCH_WRAPPER_SCRIPT.format(
220         repos_args=repos_args,
221         coursier_working_directory=Coursier.working_directory_placeholder,
222         python_path=python.path,
223         coursier_bin_dir=Coursier.bin_dir,
224     )
225 
226     post_process_stderr = POST_PROCESS_COURSIER_STDERR_SCRIPT.format(python_path=python.path)
227 
228     downloaded_coursier_get = Get(
229         DownloadedExternalTool,
230         ExternalToolRequest,
231         coursier_subsystem.get_request(Platform.current),
232     )
233     wrapper_scripts_digest_get = Get(
234         Digest,
235         CreateDigest(
236             [
237                 FileContent(
238                     os.path.basename(Coursier.fetch_wrapper_script),
239                     coursier_wrapper_script.encode("utf-8"),
240                     is_executable=True,
241                 ),
242                 FileContent(
243                     os.path.basename(Coursier.post_processing_script),
244                     COURSIER_POST_PROCESSING_SCRIPT.encode("utf-8"),
245                     is_executable=True,
246                 ),
247                 FileContent(
248                     os.path.basename(Coursier.post_process_stderr),
249                     post_process_stderr.encode("utf-8"),
250                     is_executable=True,
251                 ),
252             ]
253         ),
254     )
255 
256     downloaded_coursier, wrapper_scripts_digest = await MultiGet(
257         downloaded_coursier_get, wrapper_scripts_digest_get
258     )
259 
260     return Coursier(
261         coursier=downloaded_coursier,
262         _digest=await Get(
263             Digest,
264             MergeDigests(
265                 [
266                     downloaded_coursier.digest,
267                     wrapper_scripts_digest,
268                 ]
269             ),
270         ),
271     )
272 
273 
274 def rules():
275     return [
276         *collect_rules(),
277         *external_tool.rules(),
278     ]
279
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/python/pants/jvm/resolve/coursier_setup.py b/src/python/pants/jvm/resolve/coursier_setup.py
--- a/src/python/pants/jvm/resolve/coursier_setup.py
+++ b/src/python/pants/jvm/resolve/coursier_setup.py
@@ -215,7 +215,10 @@
     coursier_subsystem: CoursierSubsystem,
     python: PythonBinary,
 ) -> Coursier:
-    repos_args = " ".join(f"-r={shlex.quote(repo)}" for repo in coursier_subsystem.options.repos)
+    repos_args = (
+        " ".join(f"-r={shlex.quote(repo)}" for repo in coursier_subsystem.options.repos)
+        + " --no-default"
+    )
     coursier_wrapper_script = COURSIER_FETCH_WRAPPER_SCRIPT.format(
         repos_args=repos_args,
         coursier_working_directory=Coursier.working_directory_placeholder,

{"golden_diff": "diff --git a/src/python/pants/jvm/resolve/coursier_setup.py b/src/python/pants/jvm/resolve/coursier_setup.py\n--- a/src/python/pants/jvm/resolve/coursier_setup.py\n+++ b/src/python/pants/jvm/resolve/coursier_setup.py\n@@ -215,7 +215,10 @@\n     coursier_subsystem: CoursierSubsystem,\n     python: PythonBinary,\n ) -> Coursier:\n-    repos_args = \" \".join(f\"-r={shlex.quote(repo)}\" for repo in coursier_subsystem.options.repos)\n+    repos_args = (\n+        \" \".join(f\"-r={shlex.quote(repo)}\" for repo in coursier_subsystem.options.repos)\n+        + \" --no-default\"\n+    )\n     coursier_wrapper_script = COURSIER_FETCH_WRAPPER_SCRIPT.format(\n         repos_args=repos_args,\n         coursier_working_directory=Coursier.working_directory_placeholder,\n", "issue": "coursier post processing script choking on null dependency file in report json\n**Describe the bug**\r\nOne of our 3rdparty jars (`org.geotools:gt-main:9.2` from the `https://repo.osgeo.org/repository/release` repositiory) transitively depends on a `javax.media:jai_core:1.1.3` jar. For whatever reason, Coursier's resolve report json lists `\"file\": null` for that jar, which the post processing script doesn't currently handle.\r\n\r\n**Pants version**\r\n2.9.0\r\n\r\n**OS**\r\nEncountered on MacOS, though expect we would see this on Linux as well\r\n\r\n**Additional info**\r\nCoursier config:\r\n```\r\n[coursier]\r\nrepos = [\r\n  \"https://maven-central.storage-download.googleapis.com/maven2\",\r\n  \"https://repo1.maven.org/maven2\",\r\n  \"https://repo.osgeo.org/repository/release\",\r\n]\r\n```\r\n\r\naffected jvm_artifact:\r\n```\r\njvm_artifact(\r\n  group=\"org.geotools\",\r\n  artifact=\"gt-main\",\r\n  version=\"9.2\",\r\n)\r\n```\r\n\r\n[slack discussion](https://pantsbuild.slack.com/archives/C046T6T9U/p1645598534013879) with links to full resolve output and json\r\n\n", "before_files": [{"content": "# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).\n# Licensed under the Apache License, Version 2.0 (see LICENSE).\n\nfrom __future__ import annotations\n\nimport os\nimport shlex\nimport textwrap\nfrom dataclasses import dataclass\nfrom typing import ClassVar, Iterable, Tuple\n\nfrom pants.core.util_rules import external_tool\nfrom pants.core.util_rules.external_tool import (\n    DownloadedExternalTool,\n    ExternalToolRequest,\n    TemplatedExternalTool,\n)\nfrom pants.engine.fs import CreateDigest, Digest, FileContent, MergeDigests\nfrom pants.engine.platform import Platform\nfrom pants.engine.process import BashBinary, Process\nfrom pants.engine.rules import Get, MultiGet, collect_rules, rule\nfrom pants.python.binaries import PythonBinary\nfrom pants.util.logging import LogLevel\n\nCOURSIER_POST_PROCESSING_SCRIPT = textwrap.dedent(\n    \"\"\"\\\n    import json\n    import sys\n    import os\n    from pathlib import PurePath\n    from shutil import copyfile\n\n    report = json.load(open(sys.argv[1]))\n\n    # Mapping from dest path to source path. It is ok to capture the same output filename multiple\n    # times if the source is the same as well.\n    classpath = dict()\n    for dep in report['dependencies']:\n        source = PurePath(dep['file'])\n        dest_name = dep['coord'].replace(\":\", \"_\")\n        _, ext = os.path.splitext(source)\n        classpath_dest = f\"classpath/{dest_name}{ext}\"\n\n        existing_source = classpath.get(classpath_dest)\n        if existing_source:\n            if existing_source == source:\n                # We've already captured this file.\n                continue\n            raise Exception(\n                f\"Duplicate jar name {classpath_dest} with incompatible source:\\\\n\"\n                f\"  {source}\\\\n\"\n                f\"  {existing_source}\\\\n\"\n            )\n        classpath[classpath_dest] = source\n        copyfile(source, classpath_dest)\n    \"\"\"\n)\n\nCOURSIER_FETCH_WRAPPER_SCRIPT = textwrap.dedent(\n    \"\"\"\\\n    set -eux\n\n    coursier_exe=\"$1\"\n    shift\n    json_output_file=\"$1\"\n    shift\n\n    working_dir=\"$(pwd)\"\n    \"$coursier_exe\" fetch {repos_args} \\\n        --json-output-file=\"$json_output_file\" \\\n        \"${{@//{coursier_working_directory}/$working_dir}}\"\n    /bin/mkdir -p classpath\n    {python_path} {coursier_bin_dir}/coursier_post_processing_script.py \"$json_output_file\"\n    \"\"\"\n)\n\n\n# TODO: Coursier renders setrlimit error line on macOS.\n#   see https://github.com/pantsbuild/pants/issues/13942.\nPOST_PROCESS_COURSIER_STDERR_SCRIPT = textwrap.dedent(\n    \"\"\"\\\n    #!{python_path}\n    import sys\n    from subprocess import run, PIPE\n\n    proc = run(sys.argv[1:], stdout=PIPE, stderr=PIPE)\n\n    sys.stdout.buffer.write(proc.stdout)\n    sys.stderr.buffer.write(proc.stderr.replace(b\"setrlimit to increase file descriptor limit failed, errno 22\\\\n\", b\"\"))\n    sys.exit(proc.returncode)\n    \"\"\"\n)\n\n\nclass CoursierSubsystem(TemplatedExternalTool):\n    options_scope = \"coursier\"\n    name = \"coursier\"\n    help = \"A dependency resolver for the Maven ecosystem.\"\n\n    default_version = \"v2.0.16-169-g194ebc55c\"\n    default_known_versions = [\n        \"v2.0.16-169-g194ebc55c|linux_arm64 |da38c97d55967505b8454c20a90370c518044829398b9bce8b637d194d79abb3|18114472\",\n        \"v2.0.16-169-g194ebc55c|linux_x86_64|4c61a634c4bd2773b4543fe0fc32210afd343692891121cddb447204b48672e8|18486946\",\n        \"v2.0.16-169-g194ebc55c|macos_arm64 |15bce235d223ef1d022da30b67b4c64e9228d236b876c834b64e029bbe824c6f|17957182\",\n        \"v2.0.16-169-g194ebc55c|macos_x86_64|15bce235d223ef1d022da30b67b4c64e9228d236b876c834b64e029bbe824c6f|17957182\",\n    ]\n    default_url_template = (\n        \"https://github.com/coursier/coursier/releases/download/{version}/cs-{platform}.gz\"\n    )\n    default_url_platform_mapping = {\n        \"macos_arm64\": \"x86_64-apple-darwin\",\n        \"macos_x86_64\": \"x86_64-apple-darwin\",\n        \"linux_arm64\": \"aarch64-pc-linux\",\n        \"linux_x86_64\": \"x86_64-pc-linux\",\n    }\n\n    @classmethod\n    def register_options(cls, register) -> None:\n        super().register_options(register)\n        register(\n            \"--repos\",\n            type=list,\n            member_type=str,\n            default=[\n                \"https://maven-central.storage-download.googleapis.com/maven2\",\n                \"https://repo1.maven.org/maven2\",\n            ],\n            help=(\"Maven style repositories to resolve artifacts from.\"),\n        )\n\n    def generate_exe(self, plat: Platform) -> str:\n        archive_filename = os.path.basename(self.generate_url(plat))\n        filename = os.path.splitext(archive_filename)[0]\n        return f\"./{filename}\"\n\n\n@dataclass(frozen=True)\nclass Coursier:\n    \"\"\"The Coursier tool and various utilities, prepared for use via `immutable_input_digests`.\"\"\"\n\n    coursier: DownloadedExternalTool\n    _digest: Digest\n\n    bin_dir: ClassVar[str] = \"__coursier\"\n    fetch_wrapper_script: ClassVar[str] = f\"{bin_dir}/coursier_fetch_wrapper_script.sh\"\n    post_processing_script: ClassVar[str] = f\"{bin_dir}/coursier_post_processing_script.py\"\n    post_process_stderr: ClassVar[str] = f\"{bin_dir}/coursier_post_process_stderr.py\"\n    cache_name: ClassVar[str] = \"coursier\"\n    cache_dir: ClassVar[str] = \".cache\"\n    working_directory_placeholder: ClassVar[str] = \"___COURSIER_WORKING_DIRECTORY___\"\n\n    def args(self, args: Iterable[str], *, wrapper: Iterable[str] = ()) -> tuple[str, ...]:\n        return (\n            self.post_process_stderr,\n            *wrapper,\n            os.path.join(self.bin_dir, self.coursier.exe),\n            *args,\n        )\n\n    @property\n    def env(self) -> dict[str, str]:\n        # NB: These variables have changed a few times, and they change again on `main`. But as of\n        # `v2.0.16+73-gddc6d9cc9` they are accurate. See:\n        #  https://github.com/coursier/coursier/blob/v2.0.16+73-gddc6d9cc9/modules/paths/src/main/java/coursier/paths/CoursierPaths.java#L38-L48\n        return {\n            \"COURSIER_CACHE\": f\"{self.cache_dir}/jdk\",\n            \"COURSIER_ARCHIVE_CACHE\": f\"{self.cache_dir}/arc\",\n            \"COURSIER_JVM_CACHE\": f\"{self.cache_dir}/v1\",\n        }\n\n    @property\n    def append_only_caches(self) -> dict[str, str]:\n        return {self.cache_name: self.cache_dir}\n\n    @property\n    def immutable_input_digests(self) -> dict[str, Digest]:\n        return {self.bin_dir: self._digest}\n\n\n@dataclass(frozen=True)\nclass CoursierWrapperProcess:\n\n    args: Tuple[str, ...]\n    input_digest: Digest\n    output_directories: Tuple[str, ...]\n    output_files: Tuple[str, ...]\n    description: str\n\n\n@rule\nasync def invoke_coursier_wrapper(\n    bash: BashBinary,\n    coursier: Coursier,\n    request: CoursierWrapperProcess,\n) -> Process:\n\n    return Process(\n        argv=coursier.args(\n            request.args,\n            wrapper=[bash.path, coursier.fetch_wrapper_script],\n        ),\n        input_digest=request.input_digest,\n        immutable_input_digests=coursier.immutable_input_digests,\n        output_directories=request.output_directories,\n        output_files=request.output_files,\n        append_only_caches=coursier.append_only_caches,\n        env=coursier.env,\n        description=request.description,\n        level=LogLevel.DEBUG,\n    )\n\n\n@rule\nasync def setup_coursier(\n    coursier_subsystem: CoursierSubsystem,\n    python: PythonBinary,\n) -> Coursier:\n    repos_args = \" \".join(f\"-r={shlex.quote(repo)}\" for repo in coursier_subsystem.options.repos)\n    coursier_wrapper_script = COURSIER_FETCH_WRAPPER_SCRIPT.format(\n        repos_args=repos_args,\n        coursier_working_directory=Coursier.working_directory_placeholder,\n        python_path=python.path,\n        coursier_bin_dir=Coursier.bin_dir,\n    )\n\n    post_process_stderr = POST_PROCESS_COURSIER_STDERR_SCRIPT.format(python_path=python.path)\n\n    downloaded_coursier_get = Get(\n        DownloadedExternalTool,\n        ExternalToolRequest,\n        coursier_subsystem.get_request(Platform.current),\n    )\n    wrapper_scripts_digest_get = Get(\n        Digest,\n        CreateDigest(\n            [\n                FileContent(\n                    os.path.basename(Coursier.fetch_wrapper_script),\n                    coursier_wrapper_script.encode(\"utf-8\"),\n                    is_executable=True,\n                ),\n                FileContent(\n                    os.path.basename(Coursier.post_processing_script),\n                    COURSIER_POST_PROCESSING_SCRIPT.encode(\"utf-8\"),\n                    is_executable=True,\n                ),\n                FileContent(\n                    os.path.basename(Coursier.post_process_stderr),\n                    post_process_stderr.encode(\"utf-8\"),\n                    is_executable=True,\n                ),\n            ]\n        ),\n    )\n\n    downloaded_coursier, wrapper_scripts_digest = await MultiGet(\n        downloaded_coursier_get, wrapper_scripts_digest_get\n    )\n\n    return Coursier(\n        coursier=downloaded_coursier,\n        _digest=await Get(\n            Digest,\n            MergeDigests(\n                [\n                    downloaded_coursier.digest,\n                    wrapper_scripts_digest,\n                ]\n            ),\n        ),\n    )\n\n\ndef rules():\n    return [\n        *collect_rules(),\n        *external_tool.rules(),\n    ]\n", "path": "src/python/pants/jvm/resolve/coursier_setup.py"}], "after_files": [{"content": "# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).\n# Licensed under the Apache License, Version 2.0 (see LICENSE).\n\nfrom __future__ import annotations\n\nimport os\nimport shlex\nimport textwrap\nfrom dataclasses import dataclass\nfrom typing import ClassVar, Iterable, Tuple\n\nfrom pants.core.util_rules import external_tool\nfrom pants.core.util_rules.external_tool import (\n    DownloadedExternalTool,\n    ExternalToolRequest,\n    TemplatedExternalTool,\n)\nfrom pants.engine.fs import CreateDigest, Digest, FileContent, MergeDigests\nfrom pants.engine.platform import Platform\nfrom pants.engine.process import BashBinary, Process\nfrom pants.engine.rules import Get, MultiGet, collect_rules, rule\nfrom pants.python.binaries import PythonBinary\nfrom pants.util.logging import LogLevel\n\nCOURSIER_POST_PROCESSING_SCRIPT = textwrap.dedent(\n    \"\"\"\\\n    import json\n    import sys\n    import os\n    from pathlib import PurePath\n    from shutil import copyfile\n\n    report = json.load(open(sys.argv[1]))\n\n    # Mapping from dest path to source path. It is ok to capture the same output filename multiple\n    # times if the source is the same as well.\n    classpath = dict()\n    for dep in report['dependencies']:\n        source = PurePath(dep['file'])\n        dest_name = dep['coord'].replace(\":\", \"_\")\n        _, ext = os.path.splitext(source)\n        classpath_dest = f\"classpath/{dest_name}{ext}\"\n\n        existing_source = classpath.get(classpath_dest)\n        if existing_source:\n            if existing_source == source:\n                # We've already captured this file.\n                continue\n            raise Exception(\n                f\"Duplicate jar name {classpath_dest} with incompatible source:\\\\n\"\n                f\"  {source}\\\\n\"\n                f\"  {existing_source}\\\\n\"\n            )\n        classpath[classpath_dest] = source\n        copyfile(source, classpath_dest)\n    \"\"\"\n)\n\nCOURSIER_FETCH_WRAPPER_SCRIPT = textwrap.dedent(\n    \"\"\"\\\n    set -eux\n\n    coursier_exe=\"$1\"\n    shift\n    json_output_file=\"$1\"\n    shift\n\n    working_dir=\"$(pwd)\"\n    \"$coursier_exe\" fetch {repos_args} \\\n        --json-output-file=\"$json_output_file\" \\\n        \"${{@//{coursier_working_directory}/$working_dir}}\"\n    /bin/mkdir -p classpath\n    {python_path} {coursier_bin_dir}/coursier_post_processing_script.py \"$json_output_file\"\n    \"\"\"\n)\n\n\n# TODO: Coursier renders setrlimit error line on macOS.\n#   see https://github.com/pantsbuild/pants/issues/13942.\nPOST_PROCESS_COURSIER_STDERR_SCRIPT = textwrap.dedent(\n    \"\"\"\\\n    #!{python_path}\n    import sys\n    from subprocess import run, PIPE\n\n    proc = run(sys.argv[1:], stdout=PIPE, stderr=PIPE)\n\n    sys.stdout.buffer.write(proc.stdout)\n    sys.stderr.buffer.write(proc.stderr.replace(b\"setrlimit to increase file descriptor limit failed, errno 22\\\\n\", b\"\"))\n    sys.exit(proc.returncode)\n    \"\"\"\n)\n\n\nclass CoursierSubsystem(TemplatedExternalTool):\n    options_scope = \"coursier\"\n    name = \"coursier\"\n    help = \"A dependency resolver for the Maven ecosystem.\"\n\n    default_version = \"v2.0.16-169-g194ebc55c\"\n    default_known_versions = [\n        \"v2.0.16-169-g194ebc55c|linux_arm64 |da38c97d55967505b8454c20a90370c518044829398b9bce8b637d194d79abb3|18114472\",\n        \"v2.0.16-169-g194ebc55c|linux_x86_64|4c61a634c4bd2773b4543fe0fc32210afd343692891121cddb447204b48672e8|18486946\",\n        \"v2.0.16-169-g194ebc55c|macos_arm64 |15bce235d223ef1d022da30b67b4c64e9228d236b876c834b64e029bbe824c6f|17957182\",\n        \"v2.0.16-169-g194ebc55c|macos_x86_64|15bce235d223ef1d022da30b67b4c64e9228d236b876c834b64e029bbe824c6f|17957182\",\n    ]\n    default_url_template = (\n        \"https://github.com/coursier/coursier/releases/download/{version}/cs-{platform}.gz\"\n    )\n    default_url_platform_mapping = {\n        \"macos_arm64\": \"x86_64-apple-darwin\",\n        \"macos_x86_64\": \"x86_64-apple-darwin\",\n        \"linux_arm64\": \"aarch64-pc-linux\",\n        \"linux_x86_64\": \"x86_64-pc-linux\",\n    }\n\n    @classmethod\n    def register_options(cls, register) -> None:\n        super().register_options(register)\n        register(\n            \"--repos\",\n            type=list,\n            member_type=str,\n            default=[\n                \"https://maven-central.storage-download.googleapis.com/maven2\",\n                \"https://repo1.maven.org/maven2\",\n            ],\n            help=(\"Maven style repositories to resolve artifacts from.\"),\n        )\n\n    def generate_exe(self, plat: Platform) -> str:\n        archive_filename = os.path.basename(self.generate_url(plat))\n        filename = os.path.splitext(archive_filename)[0]\n        return f\"./{filename}\"\n\n\n@dataclass(frozen=True)\nclass Coursier:\n    \"\"\"The Coursier tool and various utilities, prepared for use via `immutable_input_digests`.\"\"\"\n\n    coursier: DownloadedExternalTool\n    _digest: Digest\n\n    bin_dir: ClassVar[str] = \"__coursier\"\n    fetch_wrapper_script: ClassVar[str] = f\"{bin_dir}/coursier_fetch_wrapper_script.sh\"\n    post_processing_script: ClassVar[str] = f\"{bin_dir}/coursier_post_processing_script.py\"\n    post_process_stderr: ClassVar[str] = f\"{bin_dir}/coursier_post_process_stderr.py\"\n    cache_name: ClassVar[str] = \"coursier\"\n    cache_dir: ClassVar[str] = \".cache\"\n    working_directory_placeholder: ClassVar[str] = \"___COURSIER_WORKING_DIRECTORY___\"\n\n    def args(self, args: Iterable[str], *, wrapper: Iterable[str] = ()) -> tuple[str, ...]:\n        return (\n            self.post_process_stderr,\n            *wrapper,\n            os.path.join(self.bin_dir, self.coursier.exe),\n            *args,\n        )\n\n    @property\n    def env(self) -> dict[str, str]:\n        # NB: These variables have changed a few times, and they change again on `main`. But as of\n        # `v2.0.16+73-gddc6d9cc9` they are accurate. See:\n        #  https://github.com/coursier/coursier/blob/v2.0.16+73-gddc6d9cc9/modules/paths/src/main/java/coursier/paths/CoursierPaths.java#L38-L48\n        return {\n            \"COURSIER_CACHE\": f\"{self.cache_dir}/jdk\",\n            \"COURSIER_ARCHIVE_CACHE\": f\"{self.cache_dir}/arc\",\n            \"COURSIER_JVM_CACHE\": f\"{self.cache_dir}/v1\",\n        }\n\n    @property\n    def append_only_caches(self) -> dict[str, str]:\n        return {self.cache_name: self.cache_dir}\n\n    @property\n    def immutable_input_digests(self) -> dict[str, Digest]:\n        return {self.bin_dir: self._digest}\n\n\n@dataclass(frozen=True)\nclass CoursierWrapperProcess:\n\n    args: Tuple[str, ...]\n    input_digest: Digest\n    output_directories: Tuple[str, ...]\n    output_files: Tuple[str, ...]\n    description: str\n\n\n@rule\nasync def invoke_coursier_wrapper(\n    bash: BashBinary,\n    coursier: Coursier,\n    request: CoursierWrapperProcess,\n) -> Process:\n\n    return Process(\n        argv=coursier.args(\n            request.args,\n            wrapper=[bash.path, coursier.fetch_wrapper_script],\n        ),\n        input_digest=request.input_digest,\n        immutable_input_digests=coursier.immutable_input_digests,\n        output_directories=request.output_directories,\n        output_files=request.output_files,\n        append_only_caches=coursier.append_only_caches,\n        env=coursier.env,\n        description=request.description,\n        level=LogLevel.DEBUG,\n    )\n\n\n@rule\nasync def setup_coursier(\n    coursier_subsystem: CoursierSubsystem,\n    python: PythonBinary,\n) -> Coursier:\n    repos_args = (\n        \" \".join(f\"-r={shlex.quote(repo)}\" for repo in coursier_subsystem.options.repos)\n        + \" --no-default\"\n    )\n    coursier_wrapper_script = COURSIER_FETCH_WRAPPER_SCRIPT.format(\n        repos_args=repos_args,\n        coursier_working_directory=Coursier.working_directory_placeholder,\n        python_path=python.path,\n        coursier_bin_dir=Coursier.bin_dir,\n    )\n\n    post_process_stderr = POST_PROCESS_COURSIER_STDERR_SCRIPT.format(python_path=python.path)\n\n    downloaded_coursier_get = Get(\n        DownloadedExternalTool,\n        ExternalToolRequest,\n        coursier_subsystem.get_request(Platform.current),\n    )\n    wrapper_scripts_digest_get = Get(\n        Digest,\n        CreateDigest(\n            [\n                FileContent(\n                    os.path.basename(Coursier.fetch_wrapper_script),\n                    coursier_wrapper_script.encode(\"utf-8\"),\n                    is_executable=True,\n                ),\n                FileContent(\n                    os.path.basename(Coursier.post_processing_script),\n                    COURSIER_POST_PROCESSING_SCRIPT.encode(\"utf-8\"),\n                    is_executable=True,\n                ),\n                FileContent(\n                    os.path.basename(Coursier.post_process_stderr),\n                    post_process_stderr.encode(\"utf-8\"),\n                    is_executable=True,\n                ),\n            ]\n        ),\n    )\n\n    downloaded_coursier, wrapper_scripts_digest = await MultiGet(\n        downloaded_coursier_get, wrapper_scripts_digest_get\n    )\n\n    return Coursier(\n        coursier=downloaded_coursier,\n        _digest=await Get(\n            Digest,\n            MergeDigests(\n                [\n                    downloaded_coursier.digest,\n                    wrapper_scripts_digest,\n                ]\n            ),\n        ),\n    )\n\n\ndef rules():\n    return [\n        *collect_rules(),\n        *external_tool.rules(),\n    ]\n", "path": "src/python/pants/jvm/resolve/coursier_setup.py"}]}