Repository: pypa/pipx (python)

Issue:
Failing to create venv when passing --python version on linux
<!--
Thank you for filing a bug! Please feel free to answer as much or as little of this template as you can.

Please check pipx's Troubleshooting page to see if any of those solutions help solve your issue:
https://pipx.pypa.io/stable/troubleshooting/
-->

**Describe the bug**

<!-- Please be as detailed as possible! -->
It looks like since https://github.com/pypa/pipx/pull/1168 pipx is no longer able to create a virtual environment when passing `--python` (at least on linux and with pyenv managed versions). It still works fine if a venv already exists.

From my tests I assume it's caused by how `os.path.realpath` creates the file path to the python executable provided. I've only tested this with python versions being managed by pyenv.

```
which python3.9
-> /home/robert/.pyenv/shims/python3.9 # This is fine

python3.9 -c "import sys; print(sys.executable)"
-> /home/robert/.pyenv/versions/3.9.18/bin/python3.9 # This is fine

python3.9 -c "import os;print(os.path.realpath('python3.9'));"
-> /home/robert/private/code/pipx/python3.9 # This is wrong!
```
**How to reproduce**
`pipx run --python=python3.11 pycowsay moo`

<!-- If possible, include output of `pipx --verbose ...` -->
```
(develop-3-12) ➜  pipx git:(main) ✗ pipx run --python=python3.9 --verbose pycowsay moo
pipx >(setup:853): pipx version is 0.1.dev661+g7d82745.d20240106
pipx >(setup:854): Default python interpreter is '/home/robert/private/code/pipx/.nox/develop-3-12/bin/python'
pipx >(run_package:156): venv location is /home/robert/.local/pipx/.cache/7c9b64e6b834c48
pipx >(package_name_from_spec:370): Determined package name: pycowsay
pipx >(package_name_from_spec:371): Package name determined in 0.0s
creating virtual environment...
pipx >(run_subprocess:168): running python3.9 -m venv --without-pip /home/robert/.local/pipx/.cache/7c9b64e6b834c48
Traceback (most recent call last):
  File "/home/robert/private/code/pipx/.nox/develop-3-12/bin/pipx", line 8, in <module>
    sys.exit(cli())
             ^^^^^
  File "/home/robert/private/code/pipx/src/pipx/main.py", line 914, in cli
    return run_pipx_command(parsed_pipx_args)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/robert/private/code/pipx/src/pipx/main.py", line 197, in run_pipx_command
    commands.run(
  File "/home/robert/private/code/pipx/src/pipx/commands/run.py", line 200, in run
    run_package(
  File "/home/robert/private/code/pipx/src/pipx/commands/run.py", line 157, in run_package
    _download_and_run(
  File "/home/robert/private/code/pipx/src/pipx/commands/run.py", line 234, in _download_and_run
    venv.create_venv(venv_args, pip_args, override_shared)
  File "/home/robert/private/code/pipx/src/pipx/venv.py", line 160, in create_venv
    venv_process = run_subprocess(cmd + venv_args + [str(self.root)])
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/robert/private/code/pipx/src/pipx/util.py", line 179, in run_subprocess
    completed_process = subprocess.run(
                        ^^^^^^^^^^^^^^^
  File "/home/robert/.pyenv/versions/3.12.1/lib/python3.12/subprocess.py", line 548, in run
    with Popen(*popenargs, **kwargs) as process:
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/robert/.pyenv/versions/3.12.1/lib/python3.12/subprocess.py", line 1026, in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
  File "/home/robert/.pyenv/versions/3.12.1/lib/python3.12/subprocess.py", line 1950, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: '/home/robert/private/code/pipx/python3.9'
```
**Expected behavior**

Creating a venv and then printing pycowsay.

<!-- What should have happened? -->


Pull request:
Fix python path resolution on linux
<!-- add an 'x' in the brackets below -->

- [X] I have added an entry to `docs/changelog.md`

## Summary of changes

Only use the logic introduced in https://github.com/pypa/pipx/pull/1168 to fix a path resolution issue on windows when pipx is running on a Windows machine. 
This fixes #1195 

## Test plan

<!-- provide evidence of testing, preferably with command(s) that can be copy+pasted by others -->
When no venv for python3.9 exists, but python is on the PATH (in my case via pyenv):
```
pipx run --python=python3.9 pycowsay moo
```
Before this change leads to the error described in #1195 , and works after this change.

Tested by running

```
pipx run --python=python3.9 pycowsay moo
```


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bed7454..756ee9d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@
 - Update `pipx run` on scripts using `/// script` and no `run` table following the updated version of PEP 723 (#1180)
 - Avoid repeated exception logging in a few rare cases (#1192)
 - Include `tomli` into `pipx.pyz` (zipapp) so that it can be executed with Python 3.10 or earlier (#1142)
+- Fix resolving the python executable path on linux
 - `pipx run`: Verify whether the script name provided is a file before running it
 
 ## 1.4.1
diff --git a/src/pipx/util.py b/src/pipx/util.py
index 3e899aa..fad47c8 100644
--- a/src/pipx/util.py
+++ b/src/pipx/util.py
@@ -174,7 +174,7 @@ def run_subprocess(
     # See https://github.com/pypa/pipx/issues/1164
     # Conversely, if the binary is a symlink, then we should NOT use the real path, as Python expects to receive the
     # symlink in argv[0] so that it can locate the venv.
-    if not os.path.islink(cmd_str_list[0]):
+    if not os.path.islink(cmd_str_list[0]) and WINDOWS:
         cmd_str_list[0] = os.path.realpath(cmd_str_list[0])
     completed_process = subprocess.run(
         cmd_str_list,


Test patch:
diff --git a/tests/test_util.py b/tests/test_util.py
new file mode 100644
index 0000000..f013782
--- /dev/null
+++ b/tests/test_util.py
@@ -0,0 +1,26 @@
+import sys
+
+import pytest  # type: ignore
+
+from pipx import util
+from pipx.util import run_subprocess
+
+
+@pytest.mark.skipif(sys.platform.startswith("win"), reason="Path resolution skip if not on windows")
+def test_executable_path_resolution_unix():
+    cmd = ["python99.99", "-c", "import sys;"]
+    try:
+        run_subprocess(cmd)
+    except FileNotFoundError as e:
+        assert "No such file or directory: 'python99.99'" in str(e)
+
+
+@pytest.mark.skipif(sys.platform.startswith("win"), reason="Path resolution skip if not on windows")
+def test_executable_path_resolution_fails_on_unix_if_not_skipped(monkeypatch: pytest.MonkeyPatch):
+    cmd = ["python99.99", "-c", "import sys;"]
+    monkeypatch.setattr(util, "WINDOWS", True)
+    monkeypatch.chdir("./tests")
+    try:
+        run_subprocess(cmd)
+    except FileNotFoundError as e:
+        assert "tests/python99.99'" in str(e)