From c84e8d2c69daf57ef13b4474dbb4a6aaf8126e3c Mon Sep 17 00:00:00 2001 From: Etienne Dechamps Date: Sun, 24 Dec 2023 10:26:50 +0000 Subject: [PATCH 1/3] Run commands using their real paths Fixes #1164 --- CHANGELOG.md | 1 + src/pipx/util.py | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50037843e3..a45ec8c4c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## dev +- Fix "No pyvenv.cfg file" error when using Microsoft Store Python (#1164) - Add `--quiet` and `--verbose` options for the `pipx` subcommands - [docs] Add Scoop installation instructions - Add ability to install multiple packages at once diff --git a/src/pipx/util.py b/src/pipx/util.py index b2c340818f..2a9965ef4b 100644 --- a/src/pipx/util.py +++ b/src/pipx/util.py @@ -171,6 +171,14 @@ def run_subprocess( logger.info(f"running {log_cmd_str}") # windows cannot take Path objects, only strings cmd_str_list = [str(c) for c in cmd] + # Make sure to call the binary using its real path. This matters especially on Windows when using the packaged app + # (Microsoft Store) version of Python, which uses path redirection for sandboxing. If the path to the executable is + # redirected, the executable can get confused as to which directory it's being run from, leading to problems. + # 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]): + cmd_str_list[0] = os.path.realpath(cmd_str_list[0]) completed_process = subprocess.run( cmd_str_list, env=env, From 5a16aac6a883525a2ca6f0fb26d02f855a96ba4f Mon Sep 17 00:00:00 2001 From: Etienne Dechamps Date: Sun, 24 Dec 2023 13:07:23 +0000 Subject: [PATCH 2/3] Call rmdir with the real path This gets rid of the following warning when using the Microsoft Store version of Python: The system cannot find the path specified. Failed to delete C:\Users\etien\AppData\Local\pipx\pipx\trash. You may need to delete it manually. See also #1164 --- CHANGELOG.md | 1 + src/pipx/util.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a45ec8c4c4..7f5443637a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## dev +- Fix "Failed to delete" error when using Microsoft Store Python - Fix "No pyvenv.cfg file" error when using Microsoft Store Python (#1164) - Add `--quiet` and `--verbose` options for the `pipx` subcommands - [docs] Add Scoop installation instructions diff --git a/src/pipx/util.py b/src/pipx/util.py index 2a9965ef4b..4bba09d6f5 100644 --- a/src/pipx/util.py +++ b/src/pipx/util.py @@ -55,7 +55,9 @@ def rmdir(path: Path, safe_rm: bool = True) -> None: logger.info(f"removing directory {path}") try: if WINDOWS: - os.system(f'rmdir /S /Q "{str(path)}"') + # The packaged app (Microsoft Store) version of Python uses path redirections, but `rmdir` won't follow + # these, so use realpath() to manually apply the redirection first. + os.system(f'rmdir /S /Q "{os.path.realpath(path)}"') else: shutil.rmtree(path) except FileNotFoundError: From ac6027ac8beae9395f8f3fef0c31ca7f86c89c2b Mon Sep 17 00:00:00 2001 From: Etienne Dechamps Date: Thu, 28 Dec 2023 15:00:38 +0100 Subject: [PATCH 3/3] Use rmtree instead of spawning rmdir on Windows This code dates all the way back to 7cb6561c53ac338c0f9b134b042dcca3ccf49917. It's not clear why this code decides to spawn an rmdir process on Windows instead of simply calling `shutil.rmtree()` directly. --- CHANGELOG.md | 1 + src/pipx/util.py | 7 +------ 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f5443637a..9740f84cf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## dev +- Delete directories directly instead of spawning rmdir on Windows - Fix "Failed to delete" error when using Microsoft Store Python - Fix "No pyvenv.cfg file" error when using Microsoft Store Python (#1164) - Add `--quiet` and `--verbose` options for the `pipx` subcommands diff --git a/src/pipx/util.py b/src/pipx/util.py index 4bba09d6f5..3e899aa69e 100644 --- a/src/pipx/util.py +++ b/src/pipx/util.py @@ -54,12 +54,7 @@ def rmdir(path: Path, safe_rm: bool = True) -> None: logger.info(f"removing directory {path}") try: - if WINDOWS: - # The packaged app (Microsoft Store) version of Python uses path redirections, but `rmdir` won't follow - # these, so use realpath() to manually apply the redirection first. - os.system(f'rmdir /S /Q "{os.path.realpath(path)}"') - else: - shutil.rmtree(path) + shutil.rmtree(path) except FileNotFoundError: pass