Repository: pallets/click

Issue:
Don't fail with error when bash is not found or too old
<!--
This issue tracker is a tool to address bugs in Click itself. Please use
Pallets Discord or Stack Overflow for questions about your own code.

Replace this comment with a clear outline of what the bug is.
-->

<!--
Describe how to replicate the bug.

Include a minimal reproducible example that demonstrates the bug.
Include the full traceback if there was an exception.
-->

<!--
Describe the expected behavior that should have happened but didn't.
-->

As discussed in #2152 and #2200, `click` does not support bash shell autocompletion for bash versions older than version 4.4. I understand this and the rational behind this decision, and I am **not** requesting that this should be changed. However, the way that `click` makes developers aware of this version limitation has caused an issue with the ability for autocompletion generation to be scripted.

I have developed [a CLI tool](https://github.com/superatomic/tldr-man-client) using `click` and I have tried to package it for the [Homebrew](https://brew.sh) package manager. As a part of the installation script, autocompletion scripts for `bash`, `zsh`, and `fish` must be generated at installation time. [Homebrew isolates builds by removing `/usr/local/bin` and all user `PATH`s that are not essential for the build](https://docs.brew.sh/Formula-Cookbook#superenv-notes). This means that a newer version of `bash` (>4.4) will **not** be present at build time, **even if it is present on the system**, which causes `click` to fail to generate bash autocompletion, even if a user has a newer version of bash installed on their system:

```
Traceback (most recent call last):
  File "/Users/Olivia/Code/tldr-man/.venv/bin/tldr", line 6, in <module>
    sys.exit(cli())
  File "/Users/Olivia/Code/tldr-man/.venv/lib/python3.10/site-packages/click/core.py", line 1130, in __call__
    return self.main(*args, **kwargs)
  File "/Users/Olivia/Code/tldr-man/.venv/lib/python3.10/site-packages/click/core.py", line 1050, in main
    self._main_shell_completion(extra, prog_name, complete_var)
  File "/Users/Olivia/Code/tldr-man/.venv/lib/python3.10/site-packages/click/core.py", line 1125, in _main_shell_completion
    rv = shell_complete(self, ctx_args, prog_name, complete_var, instruction)
  File "/Users/Olivia/Code/tldr-man/.venv/lib/python3.10/site-packages/click/shell_completion.py", line 45, in shell_complete
    echo(comp.source())
  File "/Users/Olivia/Code/tldr-man/.venv/lib/python3.10/site-packages/click/shell_completion.py", line 326, in source
    self._check_version()
  File "/Users/Olivia/Code/tldr-man/.venv/lib/python3.10/site-packages/click/shell_completion.py", line 314, in _check_version
    raise RuntimeError(
RuntimeError: Shell completion is not supported for Bash versions older than 4.4.
```

This error is caused by the following function, which will  make bash autocompletion simply fail if it detects that an incompatible version is being used: https://github.com/pallets/click/blob/f45198a3bcaf41d72d31273f9e6a2e3dfeccd19b/src/click/shell_completion.py#L304-L325

It makes sense for bash autocompletion *generation* to always succeed, like it does for both `zsh` and `fish` (zsh and fish autocompletion generation will succeed no matter what, even if neither shell is present on the system). While it is important to notify the user if they are using a version of bash that is too old to be supported, it is a bug for this to cause autocompletion generation to fail entirely. I propose for these error messages to be changed into warning messages that are still displayed, but do not prevent the generation from succeeding.

I am willing to submit a pull request myself with the proposed changes if these changes are wanted.

Environment:

- Python version: 3.10.12
- Click version: 8.1.3



Discussion:
> autocompletion scripts for bash, zsh, and fish must be generated at installation time.

No they don't, they can be generated at build time and installed to the correct locations. That's actually the intended workflow when distributing an application. 
> > autocompletion scripts for bash, zsh, and fish must be generated at installation time.
> 
> No they don't, they can be generated at build time and installed to the correct locations. That's actually the intended workflow when distributing an application.

Thank you for your response!

With Homebrew, packages are built and installed in the same place. Unless I misunderstand you?

Regardless, it seems unintended for bash to behave differently than zsh and fish.
I didn't realize it was built on the user's machine. I'm fine with modifying the check to be a warning at generation and an error at runtime. Happy to review aPR. 
Thank you! I'll develop and submit a PR for review tomorrow.
A quick clarification: by "error at runtime", do you mean checking the bash version at the start of the bash script itself, and displaying the error message then?
The completion script calls click to produce the results, there's a method in the bash completer class that can be modified. 
I moved the version check from the `source(...)` method into the `get_completion_args(...)` method, and it works. However, there is a problem with this implementation: Since bash completion does not work with versions `<4.4`, the completion script isn't able to call click to produce the results, since the script fails to call `complete` with the error `bash: complete: nosort: invalid option name`. This means that the check will not run in common usage. Notably, this change does resolve the issue (Homebrew will now generate Bash autocompletion), since the call to `_check_version()` no longer occurs at build time.

I see two solutions to this problem:
- Write the `BASH_VERSION` check inside the Bash script itself by changing `_SOURCE_BASH`.
- Have `_check_version()` print a warning to **stderr** instead of raising an error, and don't raise any error at runtime, allowing for the autocompletion to fail with the error `bash: complete: nosort: invalid option name`.

Option 2 is what I currently have implemented, and it works pretty well. It prints warnings when generating Bash autocompletion with an insufficient version at build time, and resolves this issue.

It's also worth mentioning that that [the click shell completion documentation](https://click.palletsprojects.com/en/8.1.x/shell-completion/) details the Bash version requirement in the first paragraph.

Resolving patch:
diff --git a/src/click/shell_completion.py b/src/click/shell_completion.py
--- a/src/click/shell_completion.py
+++ b/src/click/shell_completion.py
@@ -301,7 +301,8 @@ class BashComplete(ShellComplete):
     name = "bash"
     source_template = _SOURCE_BASH
 
-    def _check_version(self) -> None:
+    @staticmethod
+    def _check_version() -> None:
         import subprocess
 
         output = subprocess.run(
@@ -313,15 +314,17 @@ def _check_version(self) -> None:
             major, minor = match.groups()
 
             if major < "4" or major == "4" and minor < "4":
-                raise RuntimeError(
+                echo(
                     _(
                         "Shell completion is not supported for Bash"
                         " versions older than 4.4."
-                    )
+                    ),
+                    err=True,
                 )
         else:
-            raise RuntimeError(
-                _("Couldn't detect Bash version, shell completion is not supported.")
+            echo(
+                _("Couldn't detect Bash version, shell completion is not supported."),
+                err=True,
             )
 
     def source(self) -> str:


Test patch: