Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: fix venv paths redirected by Python from MS Store #5931

Merged
merged 2 commits into from
Jul 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion src/poetry/utils/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,12 @@
from poetry.core.utils.helpers import temporary_directory
from virtualenv.seed.wheels.embed import get_embed_wheel

from poetry.utils._compat import WINDOWS
from poetry.utils._compat import decode
from poetry.utils._compat import encode
from poetry.utils._compat import list_to_shell_command
from poetry.utils._compat import metadata
from poetry.utils.helpers import get_real_windows_path
from poetry.utils.helpers import is_dir_writable
from poetry.utils.helpers import paths_csv
from poetry.utils.helpers import remove_directory
Expand Down Expand Up @@ -960,7 +962,10 @@ def create_venv(

return self.get_system_env()

io.write_line(f"Creating virtualenv <c1>{name}</> in {venv_path!s}")
io.write_line(
f"Creating virtualenv <c1>{name}</> in"
f" {venv_path if not WINDOWS else get_real_windows_path(venv_path)!s}"
)
else:
create_venv = False
if force:
Expand Down Expand Up @@ -1012,6 +1017,10 @@ def build_venv(
with_setuptools: bool | None = None,
prompt: str | None = None,
) -> virtualenv.run.session.Session:
if WINDOWS:
path = get_real_windows_path(path)
executable = get_real_windows_path(executable) if executable else None

flags = flags or {}

flags["no-pip"] = (
Expand Down Expand Up @@ -1153,6 +1162,10 @@ def __init__(self, path: Path, base: Path | None = None) -> None:
self._is_mingw = sysconfig.get_platform().startswith("mingw")
self._is_conda = bool(os.environ.get("CONDA_DEFAULT_ENV"))

if self._is_windows:
path = get_real_windows_path(path)
base = get_real_windows_path(base) if base else None

if not self._is_windows or self._is_mingw:
bin_dir = "bin"
else:
Expand Down
86 changes: 86 additions & 0 deletions src/poetry/utils/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import re
import shutil
import stat
import sys
import tempfile

from contextlib import contextmanager
Expand All @@ -12,6 +13,7 @@
from typing import Any
from typing import Iterator
from typing import Mapping
from typing import cast

from poetry.utils.constants import REQUESTS_TIMEOUT

Expand Down Expand Up @@ -171,3 +173,87 @@ def safe_extra(extra: str) -> str:
https://github.com/pypa/setuptools/blob/452e13c/pkg_resources/__init__.py#L1423-L1431.
"""
return re.sub("[^A-Za-z0-9.-]+", "_", extra).lower()


def _get_win_folder_from_registry(csidl_name: str) -> str:
if sys.platform != "win32":
raise RuntimeError("Method can only be called on Windows.")

import winreg as _winreg

shell_folder_name = {
"CSIDL_APPDATA": "AppData",
"CSIDL_COMMON_APPDATA": "Common AppData",
"CSIDL_LOCAL_APPDATA": "Local AppData",
"CSIDL_PROGRAM_FILES": "Program Files",
}[csidl_name]

key = _winreg.OpenKey(
_winreg.HKEY_CURRENT_USER,
r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders",
)
dir, type = _winreg.QueryValueEx(key, shell_folder_name)

return cast(str, dir)


def _get_win_folder_with_ctypes(csidl_name: str) -> str:
if sys.platform != "win32":
raise RuntimeError("Method can only be called on Windows.")

import ctypes

csidl_const = {
"CSIDL_APPDATA": 26,
"CSIDL_COMMON_APPDATA": 35,
"CSIDL_LOCAL_APPDATA": 28,
"CSIDL_PROGRAM_FILES": 38,
}[csidl_name]

buf = ctypes.create_unicode_buffer(1024)
ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf)

# Downgrade to short path name if have highbit chars. See
# <http://bugs.activestate.com/show_bug.cgi?id=85099>.
has_high_char = False
for c in buf:
if ord(c) > 255:
has_high_char = True
break
if has_high_char:
buf2 = ctypes.create_unicode_buffer(1024)
if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024):
buf = buf2

return buf.value


def get_win_folder(csidl_name: str) -> Path:
if sys.platform == "win32":
try:
from ctypes import windll # noqa: F401

_get_win_folder = _get_win_folder_with_ctypes
except ImportError:
_get_win_folder = _get_win_folder_from_registry

return Path(_get_win_folder(csidl_name))

raise RuntimeError("Method can only be called on Windows.")


def get_real_windows_path(path: str | Path) -> Path:
program_files = get_win_folder("CSIDL_PROGRAM_FILES")
local_appdata = get_win_folder("CSIDL_LOCAL_APPDATA")

path = Path(
str(path).replace(
str(program_files / "WindowsApps"),
str(local_appdata / "Microsoft/WindowsApps"),
)
)

if path.as_posix().startswith(local_appdata.as_posix()):
path = path.resolve()

return path