prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  `shfmt` assumes downloaded executable will be named `shfmt_{version}_{platform}`, and breaks if it isn't
  **Describe the bug**
  
  To reduce network transfer & flakiness during CI, we've pre-cached all the "external" tools used by Pants in our executor container. As part of this I've overridden the `url_template` for each tool to use a `file://` URL. The URL-paths I ended up using in the image were "simplified" from the defaults - for example, I have:
  ```toml
  [shfmt]
  url_template = "file:///opt/pants-tools/shfmt/{version}/shfmt"
  ```
  
  When CI runs with this config, it fails with:
  ```
  Error launching process: Os { code: 2, kind: NotFound, message: "No such file or directory" }
  ```
  
  I `ssh`'d into one of the executors that hit this failure, and looked inside the failing sandbox. There I saw:
  1. The `shfmt` binary _was_ in the sandbox, and runnable
  2. According to `__run.sh`, Pants was trying to invoke `./shfmt_v3.2.4_linux_amd64` instead of plain `./shfmt`
  
  I believe this is happening because the `shfmt` subsystem defines `generate_exe` to hard-code the same naming pattern as is used in the default `url_pattern`: https://github.com/pantsbuild/pants/blob/ac9e27b142b14f079089522c1175a9e380291100/src/python/pants/backend/shell/lint/shfmt/subsystem.py#L56-L58
  
  I think things would operate as expected if we deleted that `generate_exe` override, since the `shfmt` download is the executable itself.
  
  **Pants version**
  
  2.15.0rc4
  
  **OS**
  
  Observed on Linux
  
  **Additional info**
  
  https://app.toolchain.com/organizations/color/repos/color/builds/pants_run_2023_02_15_12_48_26_897_660d20c55cc041fbb63374c79a4402b0/
  
  
  --- 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/backend/shell/lint/shfmt/subsystem.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.path
  7 from typing import Iterable
  8 
  9 from pants.core.util_rules.config_files import ConfigFilesRequest
  10 from pants.core.util_rules.external_tool import TemplatedExternalTool
  11 from pants.engine.platform import Platform
  12 from pants.option.option_types import ArgsListOption, BoolOption, SkipOption
  13 from pants.util.strutil import softwrap
  14 
  15 
  16 class Shfmt(TemplatedExternalTool):
  17     options_scope = "shfmt"
  18     name = "shfmt"
  19     help = "An autoformatter for shell scripts (https://github.com/mvdan/sh)."
  20 
  21     default_version = "v3.6.0"
  22     default_known_versions = [
  23         "v3.2.4|macos_arm64 |e70fc42e69debe3e400347d4f918630cdf4bf2537277d672bbc43490387508ec|2998546",
  24         "v3.2.4|macos_x86_64|43a0461a1b54070ddc04fbbf1b78f7861ee39a65a61f5466d15a39c4aba4f917|2980208",
  25         "v3.2.4|linux_arm64 |6474d9cc08a1c9fe2ef4be7a004951998e3067d46cf55a011ddd5ff7bfab3de6|2752512",
  26         "v3.2.4|linux_x86_64|3f5a47f8fec27fae3e06d611559a2063f5d27e4b9501171dde9959b8c60a3538|2797568",
  27         "v3.6.0|macos_arm64 |633f242246ee0a866c5f5df25cbf61b6af0d5e143555aca32950059cf13d91e0|3065202",
  28         "v3.6.0|macos_x86_64|b8c9c025b498e2816b62f0b717f6032e9ab49e725a45b8205f52f66318f17185|3047552",
  29         "v3.6.0|linux_arm64 |fb1cf0af3dbe9aac7d98e38e3c7426765208ecfe23cb2da51037bb234776fd70|2818048",
  30         "v3.6.0|linux_x86_64|5741a02a641de7e56b8da170e71a97e58050d66a3cf485fb268d6a5a8bb74afb|2850816",
  31     ]
  32 
  33     default_url_template = (
  34         "https://github.com/mvdan/sh/releases/download/{version}/shfmt_{version}_{platform}"
  35     )
  36     default_url_platform_mapping = {
  37         "macos_arm64": "darwin_arm64",
  38         "macos_x86_64": "darwin_amd64",
  39         "linux_arm64": "linux_arm64",
  40         "linux_x86_64": "linux_amd64",
  41     }
  42 
  43     skip = SkipOption("fmt", "lint")
  44     args = ArgsListOption(example="-i 2")
  45     config_discovery = BoolOption(
  46         default=True,
  47         advanced=True,
  48         help=softwrap(
  49             """
  50             If true, Pants will include all relevant `.editorconfig` files during runs.
  51             See https://editorconfig.org.
  52             """
  53         ),
  54     )
  55 
  56     def generate_exe(self, plat: Platform) -> str:
  57         plat_str = self.default_url_platform_mapping[plat.value]
  58         return f"./shfmt_{self.version}_{plat_str}"
  59 
  60     def config_request(self, dirs: Iterable[str]) -> ConfigFilesRequest:
  61         # Refer to https://editorconfig.org/#file-location for how config files are discovered.
  62         candidates = (os.path.join(d, ".editorconfig") for d in ("", *dirs))
  63         return ConfigFilesRequest(
  64             discovery=self.config_discovery,
  65             check_content={fp: b"[*.sh]" for fp in candidates},
  66         )
  67
  ```
  --- 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()
  
  ```
  
golden_diff: diff --git a/src/python/pants/backend/shell/lint/shfmt/subsystem.py b/src/python/pants/backend/shell/lint/shfmt/subsystem.py
  --- a/src/python/pants/backend/shell/lint/shfmt/subsystem.py
  +++ b/src/python/pants/backend/shell/lint/shfmt/subsystem.py
  @@ -8,7 +8,6 @@
   
   from pants.core.util_rules.config_files import ConfigFilesRequest
   from pants.core.util_rules.external_tool import TemplatedExternalTool
  -from pants.engine.platform import Platform
   from pants.option.option_types import ArgsListOption, BoolOption, SkipOption
   from pants.util.strutil import softwrap
   
  @@ -53,10 +52,6 @@
           ),
       )
   
  -    def generate_exe(self, plat: Platform) -> str:
  -        plat_str = self.default_url_platform_mapping[plat.value]
  -        return f"./shfmt_{self.version}_{plat_str}"
  -
       def config_request(self, dirs: Iterable[str]) -> ConfigFilesRequest:
           # Refer to https://editorconfig.org/#file-location for how config files are discovered.
           candidates = (os.path.join(d, ".editorconfig") for d in ("", *dirs))
  
verification_info: {"golden_diff": "diff --git a/src/python/pants/backend/shell/lint/shfmt/subsystem.py b/src/python/pants/backend/shell/lint/shfmt/subsystem.py\n--- a/src/python/pants/backend/shell/lint/shfmt/subsystem.py\n+++ b/src/python/pants/backend/shell/lint/shfmt/subsystem.py\n@@ -8,7 +8,6 @@\n \n from pants.core.util_rules.config_files import ConfigFilesRequest\n from pants.core.util_rules.external_tool import TemplatedExternalTool\n-from pants.engine.platform import Platform\n from pants.option.option_types import ArgsListOption, BoolOption, SkipOption\n from pants.util.strutil import softwrap\n \n@@ -53,10 +52,6 @@\n         ),\n     )\n \n-    def generate_exe(self, plat: Platform) -> str:\n-        plat_str = self.default_url_platform_mapping[plat.value]\n-        return f\"./shfmt_{self.version}_{plat_str}\"\n-\n     def config_request(self, dirs: Iterable[str]) -> ConfigFilesRequest:\n         # Refer to https://editorconfig.org/#file-location for how config files are discovered.\n         candidates = (os.path.join(d, \".editorconfig\") for d in (\"\", *dirs))\n", "issue": "`shfmt` assumes downloaded executable will be named `shfmt_{version}_{platform}`, and breaks if it isn't\n**Describe the bug**\r\n\r\nTo reduce network transfer & flakiness during CI, we've pre-cached all the \"external\" tools used by Pants in our executor container. As part of this I've overridden the `url_template` for each tool to use a `file://` URL. The URL-paths I ended up using in the image were \"simplified\" from the defaults - for example, I have:\r\n```toml\r\n[shfmt]\r\nurl_template = \"file:///opt/pants-tools/shfmt/{version}/shfmt\"\r\n```\r\n\r\nWhen CI runs with this config, it fails with:\r\n```\r\nError launching process: Os { code: 2, kind: NotFound, message: \"No such file or directory\" }\r\n```\r\n\r\nI `ssh`'d into one of the executors that hit this failure, and looked inside the failing sandbox. There I saw:\r\n1. The `shfmt` binary _was_ in the sandbox, and runnable\r\n2. According to `__run.sh`, Pants was trying to invoke `./shfmt_v3.2.4_linux_amd64` instead of plain `./shfmt`\r\n\r\nI believe this is happening because the `shfmt` subsystem defines `generate_exe` to hard-code the same naming pattern as is used in the default `url_pattern`: https://github.com/pantsbuild/pants/blob/ac9e27b142b14f079089522c1175a9e380291100/src/python/pants/backend/shell/lint/shfmt/subsystem.py#L56-L58\r\n\r\nI think things would operate as expected if we deleted that `generate_exe` override, since the `shfmt` download is the executable itself.\r\n\r\n**Pants version**\r\n\r\n2.15.0rc4\r\n\r\n**OS**\r\n\r\nObserved on Linux\r\n\r\n**Additional info**\r\n\r\nhttps://app.toolchain.com/organizations/color/repos/color/builds/pants_run_2023_02_15_12_48_26_897_660d20c55cc041fbb63374c79a4402b0/\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.path\nfrom typing import Iterable\n\nfrom pants.core.util_rules.config_files import ConfigFilesRequest\nfrom pants.core.util_rules.external_tool import TemplatedExternalTool\nfrom pants.engine.platform import Platform\nfrom pants.option.option_types import ArgsListOption, BoolOption, SkipOption\nfrom pants.util.strutil import softwrap\n\n\nclass Shfmt(TemplatedExternalTool):\n    options_scope = \"shfmt\"\n    name = \"shfmt\"\n    help = \"An autoformatter for shell scripts (https://github.com/mvdan/sh).\"\n\n    default_version = \"v3.6.0\"\n    default_known_versions = [\n        \"v3.2.4|macos_arm64 |e70fc42e69debe3e400347d4f918630cdf4bf2537277d672bbc43490387508ec|2998546\",\n        \"v3.2.4|macos_x86_64|43a0461a1b54070ddc04fbbf1b78f7861ee39a65a61f5466d15a39c4aba4f917|2980208\",\n        \"v3.2.4|linux_arm64 |6474d9cc08a1c9fe2ef4be7a004951998e3067d46cf55a011ddd5ff7bfab3de6|2752512\",\n        \"v3.2.4|linux_x86_64|3f5a47f8fec27fae3e06d611559a2063f5d27e4b9501171dde9959b8c60a3538|2797568\",\n        \"v3.6.0|macos_arm64 |633f242246ee0a866c5f5df25cbf61b6af0d5e143555aca32950059cf13d91e0|3065202\",\n        \"v3.6.0|macos_x86_64|b8c9c025b498e2816b62f0b717f6032e9ab49e725a45b8205f52f66318f17185|3047552\",\n        \"v3.6.0|linux_arm64 |fb1cf0af3dbe9aac7d98e38e3c7426765208ecfe23cb2da51037bb234776fd70|2818048\",\n        \"v3.6.0|linux_x86_64|5741a02a641de7e56b8da170e71a97e58050d66a3cf485fb268d6a5a8bb74afb|2850816\",\n    ]\n\n    default_url_template = (\n        \"https://github.com/mvdan/sh/releases/download/{version}/shfmt_{version}_{platform}\"\n    )\n    default_url_platform_mapping = {\n        \"macos_arm64\": \"darwin_arm64\",\n        \"macos_x86_64\": \"darwin_amd64\",\n        \"linux_arm64\": \"linux_arm64\",\n        \"linux_x86_64\": \"linux_amd64\",\n    }\n\n    skip = SkipOption(\"fmt\", \"lint\")\n    args = ArgsListOption(example=\"-i 2\")\n    config_discovery = BoolOption(\n        default=True,\n        advanced=True,\n        help=softwrap(\n            \"\"\"\n            If true, Pants will include all relevant `.editorconfig` files during runs.\n            See https://editorconfig.org.\n            \"\"\"\n        ),\n    )\n\n    def generate_exe(self, plat: Platform) -> str:\n        plat_str = self.default_url_platform_mapping[plat.value]\n        return f\"./shfmt_{self.version}_{plat_str}\"\n\n    def config_request(self, dirs: Iterable[str]) -> ConfigFilesRequest:\n        # Refer to https://editorconfig.org/#file-location for how config files are discovered.\n        candidates = (os.path.join(d, \".editorconfig\") for d in (\"\", *dirs))\n        return ConfigFilesRequest(\n            discovery=self.config_discovery,\n            check_content={fp: b\"[*.sh]\" for fp in candidates},\n        )\n", "path": "src/python/pants/backend/shell/lint/shfmt/subsystem.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.path\nfrom typing import Iterable\n\nfrom pants.core.util_rules.config_files import ConfigFilesRequest\nfrom pants.core.util_rules.external_tool import TemplatedExternalTool\nfrom pants.option.option_types import ArgsListOption, BoolOption, SkipOption\nfrom pants.util.strutil import softwrap\n\n\nclass Shfmt(TemplatedExternalTool):\n    options_scope = \"shfmt\"\n    name = \"shfmt\"\n    help = \"An autoformatter for shell scripts (https://github.com/mvdan/sh).\"\n\n    default_version = \"v3.6.0\"\n    default_known_versions = [\n        \"v3.2.4|macos_arm64 |e70fc42e69debe3e400347d4f918630cdf4bf2537277d672bbc43490387508ec|2998546\",\n        \"v3.2.4|macos_x86_64|43a0461a1b54070ddc04fbbf1b78f7861ee39a65a61f5466d15a39c4aba4f917|2980208\",\n        \"v3.2.4|linux_arm64 |6474d9cc08a1c9fe2ef4be7a004951998e3067d46cf55a011ddd5ff7bfab3de6|2752512\",\n        \"v3.2.4|linux_x86_64|3f5a47f8fec27fae3e06d611559a2063f5d27e4b9501171dde9959b8c60a3538|2797568\",\n        \"v3.6.0|macos_arm64 |633f242246ee0a866c5f5df25cbf61b6af0d5e143555aca32950059cf13d91e0|3065202\",\n        \"v3.6.0|macos_x86_64|b8c9c025b498e2816b62f0b717f6032e9ab49e725a45b8205f52f66318f17185|3047552\",\n        \"v3.6.0|linux_arm64 |fb1cf0af3dbe9aac7d98e38e3c7426765208ecfe23cb2da51037bb234776fd70|2818048\",\n        \"v3.6.0|linux_x86_64|5741a02a641de7e56b8da170e71a97e58050d66a3cf485fb268d6a5a8bb74afb|2850816\",\n    ]\n\n    default_url_template = (\n        \"https://github.com/mvdan/sh/releases/download/{version}/shfmt_{version}_{platform}\"\n    )\n    default_url_platform_mapping = {\n        \"macos_arm64\": \"darwin_arm64\",\n        \"macos_x86_64\": \"darwin_amd64\",\n        \"linux_arm64\": \"linux_arm64\",\n        \"linux_x86_64\": \"linux_amd64\",\n    }\n\n    skip = SkipOption(\"fmt\", \"lint\")\n    args = ArgsListOption(example=\"-i 2\")\n    config_discovery = BoolOption(\n        default=True,\n        advanced=True,\n        help=softwrap(\n            \"\"\"\n            If true, Pants will include all relevant `.editorconfig` files during runs.\n            See https://editorconfig.org.\n            \"\"\"\n        ),\n    )\n\n    def config_request(self, dirs: Iterable[str]) -> ConfigFilesRequest:\n        # Refer to https://editorconfig.org/#file-location for how config files are discovered.\n        candidates = (os.path.join(d, \".editorconfig\") for d in (\"\", *dirs))\n        return ConfigFilesRequest(\n            discovery=self.config_discovery,\n            check_content={fp: b\"[*.sh]\" for fp in candidates},\n        )\n", "path": "src/python/pants/backend/shell/lint/shfmt/subsystem.py"}]}