diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/__init__.py b/.venv/lib/python3.11/site-packages/virtualenv/create/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/__pycache__/__init__.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/create/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2fccd581d76f789541a19d8adc4e98240e4d4420 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/create/__pycache__/__init__.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/__pycache__/creator.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/create/__pycache__/creator.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6cf939c367df1ce98d4169280e4498cb4e4dea2a Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/create/__pycache__/creator.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/__pycache__/debug.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/create/__pycache__/debug.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..260fb004939c65bfd40b3dbc141cc5b197cb1b06 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/create/__pycache__/debug.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/__pycache__/describe.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/create/__pycache__/describe.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..388783b3bf3c7f8a9a2ba365ccd4c9d8077e8db1 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/create/__pycache__/describe.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/__pycache__/pyenv_cfg.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/create/__pycache__/pyenv_cfg.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aad88333e3a21bab6ddd48af09745bb2efe6e70d Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/create/__pycache__/pyenv_cfg.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/creator.py b/.venv/lib/python3.11/site-packages/virtualenv/create/creator.py new file mode 100644 index 0000000000000000000000000000000000000000..1e577ada5312237cd076e4d74f0bfe63090f47ce --- /dev/null +++ b/.venv/lib/python3.11/site-packages/virtualenv/create/creator.py @@ -0,0 +1,241 @@ +from __future__ import annotations + +import json +import logging +import os +import sys +import textwrap +from abc import ABC, abstractmethod +from argparse import ArgumentTypeError +from ast import literal_eval +from collections import OrderedDict +from pathlib import Path + +from virtualenv.discovery.cached_py_info import LogCmd +from virtualenv.util.path import safe_delete +from virtualenv.util.subprocess import run_cmd +from virtualenv.version import __version__ + +from .pyenv_cfg import PyEnvCfg + +HERE = Path(os.path.abspath(__file__)).parent +DEBUG_SCRIPT = HERE / "debug.py" +LOGGER = logging.getLogger(__name__) + + +class CreatorMeta: + def __init__(self) -> None: + self.error = None + + +class Creator(ABC): + """A class that given a python Interpreter creates a virtual environment.""" + + def __init__(self, options, interpreter) -> None: + """ + Construct a new virtual environment creator. + + :param options: the CLI option as parsed from :meth:`add_parser_arguments` + :param interpreter: the interpreter to create virtual environment from + """ + self.interpreter = interpreter + self._debug = None + self.dest = Path(options.dest) + self.clear = options.clear + self.no_vcs_ignore = options.no_vcs_ignore + self.pyenv_cfg = PyEnvCfg.from_folder(self.dest) + self.app_data = options.app_data + self.env = options.env + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({', '.join(f'{k}={v}' for k, v in self._args())})" + + def _args(self): + return [ + ("dest", str(self.dest)), + ("clear", self.clear), + ("no_vcs_ignore", self.no_vcs_ignore), + ] + + @classmethod + def can_create(cls, interpreter): # noqa: ARG003 + """ + Determine if we can create a virtual environment. + + :param interpreter: the interpreter in question + :return: ``None`` if we can't create, any other object otherwise that will be forwarded to \ + :meth:`add_parser_arguments` + """ + return True + + @classmethod + def add_parser_arguments(cls, parser, interpreter, meta, app_data): # noqa: ARG003 + """ + Add CLI arguments for the creator. + + :param parser: the CLI parser + :param app_data: the application data folder + :param interpreter: the interpreter we're asked to create virtual environment for + :param meta: value as returned by :meth:`can_create` + """ + parser.add_argument( + "dest", + help="directory to create virtualenv at", + type=cls.validate_dest, + ) + parser.add_argument( + "--clear", + dest="clear", + action="store_true", + help="remove the destination directory if exist before starting (will overwrite files otherwise)", + default=False, + ) + parser.add_argument( + "--no-vcs-ignore", + dest="no_vcs_ignore", + action="store_true", + help="don't create VCS ignore directive in the destination directory", + default=False, + ) + + @abstractmethod + def create(self): + """Perform the virtual environment creation.""" + raise NotImplementedError + + @classmethod + def validate_dest(cls, raw_value): # noqa: C901 + """No path separator in the path, valid chars and must be write-able.""" + + def non_write_able(dest, value): + common = Path(*os.path.commonprefix([value.parts, dest.parts])) + msg = f"the destination {dest.relative_to(common)} is not write-able at {common}" + raise ArgumentTypeError(msg) + + # the file system must be able to encode + # note in newer CPython this is always utf-8 https://www.python.org/dev/peps/pep-0529/ + encoding = sys.getfilesystemencoding() + refused = OrderedDict() + kwargs = {"errors": "ignore"} if encoding != "mbcs" else {} + for char in str(raw_value): + try: + trip = char.encode(encoding, **kwargs).decode(encoding) + if trip == char: + continue + raise ValueError(trip) # noqa: TRY301 + except ValueError: + refused[char] = None + if refused: + bad = "".join(refused.keys()) + msg = f"the file system codec ({encoding}) cannot handle characters {bad!r} within {raw_value!r}" + raise ArgumentTypeError(msg) + if os.pathsep in raw_value: + msg = ( + f"destination {raw_value!r} must not contain the path separator ({os.pathsep})" + f" as this would break the activation scripts" + ) + raise ArgumentTypeError(msg) + + value = Path(raw_value) + if value.exists() and value.is_file(): + msg = f"the destination {value} already exists and is a file" + raise ArgumentTypeError(msg) + dest = Path(os.path.abspath(str(value))).resolve() # on Windows absolute does not imply resolve so use both + value = dest + while dest: + if dest.exists(): + if os.access(str(dest), os.W_OK): + break + non_write_able(dest, value) + base, _ = dest.parent, dest.name + if base == dest: + non_write_able(dest, value) # pragma: no cover + dest = base + return str(value) + + def run(self): + if self.dest.exists() and self.clear: + LOGGER.debug("delete %s", self.dest) + safe_delete(self.dest) + self.create() + self.add_cachedir_tag() + self.set_pyenv_cfg() + if not self.no_vcs_ignore: + self.setup_ignore_vcs() + + def add_cachedir_tag(self): + """Generate a file indicating that this is not meant to be backed up.""" + cachedir_tag_file = self.dest / "CACHEDIR.TAG" + if not cachedir_tag_file.exists(): + cachedir_tag_text = textwrap.dedent(""" + Signature: 8a477f597d28d172789f06886806bc55 + # This file is a cache directory tag created by Python virtualenv. + # For information about cache directory tags, see: + # https://bford.info/cachedir/ + """).strip() + cachedir_tag_file.write_text(cachedir_tag_text, encoding="utf-8") + + def set_pyenv_cfg(self): + self.pyenv_cfg.content = OrderedDict() + self.pyenv_cfg["home"] = os.path.dirname(os.path.abspath(self.interpreter.system_executable)) + self.pyenv_cfg["implementation"] = self.interpreter.implementation + self.pyenv_cfg["version_info"] = ".".join(str(i) for i in self.interpreter.version_info) + self.pyenv_cfg["virtualenv"] = __version__ + + def setup_ignore_vcs(self): + """Generate ignore instructions for version control systems.""" + # mark this folder to be ignored by VCS, handle https://www.python.org/dev/peps/pep-0610/#registered-vcs + git_ignore = self.dest / ".gitignore" + if not git_ignore.exists(): + git_ignore.write_text("# created by virtualenv automatically\n*\n", encoding="utf-8") + # Mercurial - does not support the .hgignore file inside a subdirectory directly, but only if included via the + # subinclude directive from root, at which point on might as well ignore the directory itself, see + # https://www.selenic.com/mercurial/hgignore.5.html for more details + # Bazaar - does not support ignore files in sub-directories, only at root level via .bzrignore + # Subversion - does not support ignore files, requires direct manipulation with the svn tool + + @property + def debug(self): + """:return: debug information about the virtual environment (only valid after :meth:`create` has run)""" + if self._debug is None and self.exe is not None: + self._debug = get_env_debug_info(self.exe, self.debug_script(), self.app_data, self.env) + return self._debug + + @staticmethod + def debug_script(): + return DEBUG_SCRIPT + + +def get_env_debug_info(env_exe, debug_script, app_data, env): + env = env.copy() + env.pop("PYTHONPATH", None) + + with app_data.ensure_extracted(debug_script) as debug_script_extracted: + cmd = [str(env_exe), str(debug_script_extracted)] + LOGGER.debug("debug via %r", LogCmd(cmd)) + code, out, err = run_cmd(cmd) + + try: + if code != 0: + if out: + result = literal_eval(out) + else: + if code == 2 and "file" in err: # noqa: PLR2004 + # Re-raise FileNotFoundError from `run_cmd()` + raise OSError(err) # noqa: TRY301 + raise Exception(err) # noqa: TRY002, TRY301 + else: + result = json.loads(out) + if err: + result["err"] = err + except Exception as exception: # noqa: BLE001 + return {"out": out, "err": err, "returncode": code, "exception": repr(exception)} + if "sys" in result and "path" in result["sys"]: + del result["sys"]["path"][0] + return result + + +__all__ = [ + "Creator", + "CreatorMeta", +] diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/debug.py b/.venv/lib/python3.11/site-packages/virtualenv/create/debug.py new file mode 100644 index 0000000000000000000000000000000000000000..8a4845e986941824a39632ce97a18ce8721222cc --- /dev/null +++ b/.venv/lib/python3.11/site-packages/virtualenv/create/debug.py @@ -0,0 +1,102 @@ +"""Inspect a target Python interpreter virtual environment wise.""" + +from __future__ import annotations + +import sys # built-in + + +def encode_path(value): + if value is None: + return None + if not isinstance(value, (str, bytes)): + value = repr(value) if isinstance(value, type) else repr(type(value)) + if isinstance(value, bytes): + value = value.decode(sys.getfilesystemencoding()) + return value + + +def encode_list_path(value): + return [encode_path(i) for i in value] + + +def run(): + """Print debug data about the virtual environment.""" + try: + from collections import OrderedDict # noqa: PLC0415 + except ImportError: # pragma: no cover + # this is possible if the standard library cannot be accessed + + OrderedDict = dict # pragma: no cover # noqa: N806 + result = OrderedDict([("sys", OrderedDict())]) + path_keys = ( + "executable", + "_base_executable", + "prefix", + "base_prefix", + "real_prefix", + "exec_prefix", + "base_exec_prefix", + "path", + "meta_path", + ) + for key in path_keys: + value = getattr(sys, key, None) + value = encode_list_path(value) if isinstance(value, list) else encode_path(value) + result["sys"][key] = value + result["sys"]["fs_encoding"] = sys.getfilesystemencoding() + result["sys"]["io_encoding"] = getattr(sys.stdout, "encoding", None) + result["version"] = sys.version + + try: + import sysconfig # noqa: PLC0415 + + # https://bugs.python.org/issue22199 + makefile = getattr(sysconfig, "get_makefile_filename", getattr(sysconfig, "_get_makefile_filename", None)) + result["makefile_filename"] = encode_path(makefile()) + except ImportError: + pass + + import os # landmark # noqa: PLC0415 + + result["os"] = repr(os) + + try: + import site # site # noqa: PLC0415 + + result["site"] = repr(site) + except ImportError as exception: # pragma: no cover + result["site"] = repr(exception) # pragma: no cover + + try: + import datetime # site # noqa: PLC0415 + + result["datetime"] = repr(datetime) + except ImportError as exception: # pragma: no cover + result["datetime"] = repr(exception) # pragma: no cover + + try: + import math # site # noqa: PLC0415 + + result["math"] = repr(math) + except ImportError as exception: # pragma: no cover + result["math"] = repr(exception) # pragma: no cover + + # try to print out, this will validate if other core modules are available (json in this case) + try: + import json # noqa: PLC0415 + + result["json"] = repr(json) + except ImportError as exception: + result["json"] = repr(exception) + else: + try: + content = json.dumps(result, indent=2) + sys.stdout.write(content) + except (ValueError, TypeError) as exception: # pragma: no cover + sys.stderr.write(repr(exception)) + sys.stdout.write(repr(result)) # pragma: no cover + raise SystemExit(1) # noqa: B904 # pragma: no cover + + +if __name__ == "__main__": + run() diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/describe.py b/.venv/lib/python3.11/site-packages/virtualenv/create/describe.py new file mode 100644 index 0000000000000000000000000000000000000000..1ee250cbc9327a126b431b8869e6637da8ca4b01 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/virtualenv/create/describe.py @@ -0,0 +1,110 @@ +from __future__ import annotations + +from abc import ABC +from collections import OrderedDict +from pathlib import Path + +from virtualenv.info import IS_WIN + + +class Describe: + """Given a host interpreter tell us information about what the created interpreter might look like.""" + + suffix = ".exe" if IS_WIN else "" + + def __init__(self, dest, interpreter) -> None: + self.interpreter = interpreter + self.dest = dest + self._stdlib = None + self._stdlib_platform = None + self._system_stdlib = None + self._conf_vars = None + + @property + def bin_dir(self): + return self.script_dir + + @property + def script_dir(self): + return self.dest / self.interpreter.install_path("scripts") + + @property + def purelib(self): + return self.dest / self.interpreter.install_path("purelib") + + @property + def platlib(self): + return self.dest / self.interpreter.install_path("platlib") + + @property + def libs(self): + return list(OrderedDict(((self.platlib, None), (self.purelib, None))).keys()) + + @property + def stdlib(self): + if self._stdlib is None: + self._stdlib = Path(self.interpreter.sysconfig_path("stdlib", config_var=self._config_vars)) + return self._stdlib + + @property + def stdlib_platform(self): + if self._stdlib_platform is None: + self._stdlib_platform = Path(self.interpreter.sysconfig_path("platstdlib", config_var=self._config_vars)) + return self._stdlib_platform + + @property + def _config_vars(self): + if self._conf_vars is None: + self._conf_vars = self._calc_config_vars(self.dest) + return self._conf_vars + + def _calc_config_vars(self, to): + sys_vars = self.interpreter.sysconfig_vars + return {k: (to if v is not None and v.startswith(self.interpreter.prefix) else v) for k, v in sys_vars.items()} + + @classmethod + def can_describe(cls, interpreter): # noqa: ARG003 + """Knows means it knows how the output will look.""" + return True + + @property + def env_name(self): + return self.dest.parts[-1] + + @property + def exe(self): + return self.bin_dir / f"{self.exe_stem()}{self.suffix}" + + @classmethod + def exe_stem(cls): + """Executable name without suffix - there seems to be no standard way to get this without creating it.""" + raise NotImplementedError + + def script(self, name): + return self.script_dir / f"{name}{self.suffix}" + + +class Python3Supports(Describe, ABC): + @classmethod + def can_describe(cls, interpreter): + return interpreter.version_info.major == 3 and super().can_describe(interpreter) # noqa: PLR2004 + + +class PosixSupports(Describe, ABC): + @classmethod + def can_describe(cls, interpreter): + return interpreter.os == "posix" and super().can_describe(interpreter) + + +class WindowsSupports(Describe, ABC): + @classmethod + def can_describe(cls, interpreter): + return interpreter.os == "nt" and super().can_describe(interpreter) + + +__all__ = [ + "Describe", + "PosixSupports", + "Python3Supports", + "WindowsSupports", +] diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/pyenv_cfg.py b/.venv/lib/python3.11/site-packages/virtualenv/create/pyenv_cfg.py new file mode 100644 index 0000000000000000000000000000000000000000..1d1beacff3a221d25b6226395a062bd3a8d16607 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/virtualenv/create/pyenv_cfg.py @@ -0,0 +1,68 @@ +from __future__ import annotations + +import logging +import os +from collections import OrderedDict + +LOGGER = logging.getLogger(__name__) + + +class PyEnvCfg: + def __init__(self, content, path) -> None: + self.content = content + self.path = path + + @classmethod + def from_folder(cls, folder): + return cls.from_file(folder / "pyvenv.cfg") + + @classmethod + def from_file(cls, path): + content = cls._read_values(path) if path.exists() else OrderedDict() + return PyEnvCfg(content, path) + + @staticmethod + def _read_values(path): + content = OrderedDict() + for line in path.read_text(encoding="utf-8").splitlines(): + equals_at = line.index("=") + key = line[:equals_at].strip() + value = line[equals_at + 1 :].strip() + content[key] = value + return content + + def write(self): + LOGGER.debug("write %s", self.path) + text = "" + for key, value in self.content.items(): + normalized_value = os.path.realpath(value) if value and os.path.exists(value) else value + line = f"{key} = {normalized_value}" + LOGGER.debug("\t%s", line) + text += line + text += "\n" + self.path.write_text(text, encoding="utf-8") + + def refresh(self): + self.content = self._read_values(self.path) + return self.content + + def __setitem__(self, key, value) -> None: + self.content[key] = value + + def __getitem__(self, key): + return self.content[key] + + def __contains__(self, item) -> bool: + return item in self.content + + def update(self, other): + self.content.update(other) + return self + + def __repr__(self) -> str: + return f"{self.__class__.__name__}(path={self.path})" + + +__all__ = [ + "PyEnvCfg", +] diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/__init__.py b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/__pycache__/__init__.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..12b59f9aeca606f05889e39f9122f8e0ed7e9610 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/__pycache__/__init__.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/__pycache__/_virtualenv.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/__pycache__/_virtualenv.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9ba1163d3da3c341afa9e5490cac1f6fe6687e94 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/__pycache__/_virtualenv.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/__pycache__/api.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/__pycache__/api.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..970545089df92875d49f1cf21a141949d73ceb8e Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/__pycache__/api.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/__pycache__/store.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/__pycache__/store.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f2901a995f6bbf602ef7952e85c42886929c479a Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/__pycache__/store.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/__pycache__/venv.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/__pycache__/venv.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ab61c9ce30af720cfc84452f0c38a8f49da384f9 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/__pycache__/venv.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/_virtualenv.py b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/_virtualenv.py new file mode 100644 index 0000000000000000000000000000000000000000..b61db3079bbd8874cfd7cab495c12580a4ba3615 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/_virtualenv.py @@ -0,0 +1,103 @@ +"""Patches that are applied at runtime to the virtual environment.""" + +from __future__ import annotations + +import os +import sys + +VIRTUALENV_PATCH_FILE = os.path.join(__file__) + + +def patch_dist(dist): + """ + Distutils allows user to configure some arguments via a configuration file: + https://docs.python.org/3/install/index.html#distutils-configuration-files. + + Some of this arguments though don't make sense in context of the virtual environment files, let's fix them up. + """ # noqa: D205 + # we cannot allow some install config as that would get packages installed outside of the virtual environment + old_parse_config_files = dist.Distribution.parse_config_files + + def parse_config_files(self, *args, **kwargs): + result = old_parse_config_files(self, *args, **kwargs) + install = self.get_option_dict("install") + + if "prefix" in install: # the prefix governs where to install the libraries + install["prefix"] = VIRTUALENV_PATCH_FILE, os.path.abspath(sys.prefix) + for base in ("purelib", "platlib", "headers", "scripts", "data"): + key = f"install_{base}" + if key in install: # do not allow global configs to hijack venv paths + install.pop(key, None) + return result + + dist.Distribution.parse_config_files = parse_config_files + + +# Import hook that patches some modules to ignore configuration values that break package installation in case +# of virtual environments. +_DISTUTILS_PATCH = "distutils.dist", "setuptools.dist" +# https://docs.python.org/3/library/importlib.html#setting-up-an-importer + + +class _Finder: + """A meta path finder that allows patching the imported distutils modules.""" + + fullname = None + + # lock[0] is threading.Lock(), but initialized lazily to avoid importing threading very early at startup, + # because there are gevent-based applications that need to be first to import threading by themselves. + # See https://github.com/pypa/virtualenv/issues/1895 for details. + lock = [] # noqa: RUF012 + + def find_spec(self, fullname, path, target=None): # noqa: ARG002 + if fullname in _DISTUTILS_PATCH and self.fullname is None: # noqa: PLR1702 + # initialize lock[0] lazily + if len(self.lock) == 0: + import threading # noqa: PLC0415 + + lock = threading.Lock() + # there is possibility that two threads T1 and T2 are simultaneously running into find_spec, + # observing .lock as empty, and further going into hereby initialization. However due to the GIL, + # list.append() operation is atomic and this way only one of the threads will "win" to put the lock + # - that every thread will use - into .lock[0]. + # https://docs.python.org/3/faq/library.html#what-kinds-of-global-value-mutation-are-thread-safe + self.lock.append(lock) + + from functools import partial # noqa: PLC0415 + from importlib.util import find_spec # noqa: PLC0415 + + with self.lock[0]: + self.fullname = fullname + try: + spec = find_spec(fullname, path) + if spec is not None: + # https://www.python.org/dev/peps/pep-0451/#how-loading-will-work + is_new_api = hasattr(spec.loader, "exec_module") + func_name = "exec_module" if is_new_api else "load_module" + old = getattr(spec.loader, func_name) + func = self.exec_module if is_new_api else self.load_module + if old is not func: + try: # noqa: SIM105 + setattr(spec.loader, func_name, partial(func, old)) + except AttributeError: + pass # C-Extension loaders are r/o such as zipimporter with <3.7 + return spec + finally: + self.fullname = None + return None + + @staticmethod + def exec_module(old, module): + old(module) + if module.__name__ in _DISTUTILS_PATCH: + patch_dist(module) + + @staticmethod + def load_module(old, name): + module = old(name) + if module.__name__ in _DISTUTILS_PATCH: + patch_dist(module) + return module + + +sys.meta_path.insert(0, _Finder()) diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/api.py b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/api.py new file mode 100644 index 0000000000000000000000000000000000000000..2bf43fa7450dfd3b29510c76afaf11c24d4b64e5 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/api.py @@ -0,0 +1,116 @@ +from __future__ import annotations + +import logging +import os +from abc import ABC +from pathlib import Path + +from virtualenv.create.creator import Creator, CreatorMeta +from virtualenv.info import fs_supports_symlink + +LOGGER = logging.getLogger(__name__) + + +class ViaGlobalRefMeta(CreatorMeta): + def __init__(self) -> None: + super().__init__() + self.copy_error = None + self.symlink_error = None + if not fs_supports_symlink(): + self.symlink_error = "the filesystem does not supports symlink" + + @property + def can_copy(self): + return not self.copy_error + + @property + def can_symlink(self): + return not self.symlink_error + + +class ViaGlobalRefApi(Creator, ABC): + def __init__(self, options, interpreter) -> None: + super().__init__(options, interpreter) + self.symlinks = self._should_symlink(options) + self.enable_system_site_package = options.system_site + + @staticmethod + def _should_symlink(options): + # Priority of where the option is set to follow the order: CLI, env var, file, hardcoded. + # If both set at same level prefers copy over symlink. + copies, symlinks = getattr(options, "copies", False), getattr(options, "symlinks", False) + copy_src, sym_src = options.get_source("copies"), options.get_source("symlinks") + for level in ["cli", "env var", "file", "default"]: + s_opt = symlinks if sym_src == level else None + c_opt = copies if copy_src == level else None + if s_opt is True and c_opt is True: + return False + if s_opt is True: + return True + if c_opt is True: + return False + return False # fallback to copy + + @classmethod + def add_parser_arguments(cls, parser, interpreter, meta, app_data): + super().add_parser_arguments(parser, interpreter, meta, app_data) + parser.add_argument( + "--system-site-packages", + default=False, + action="store_true", + dest="system_site", + help="give the virtual environment access to the system site-packages dir", + ) + if not meta.can_symlink and not meta.can_copy: + msg = "neither symlink or copy method supported" + raise RuntimeError(msg) + group = parser.add_mutually_exclusive_group() + if meta.can_symlink: + group.add_argument( + "--symlinks", + default=True, + action="store_true", + dest="symlinks", + help="try to use symlinks rather than copies, when symlinks are not the default for the platform", + ) + if meta.can_copy: + group.add_argument( + "--copies", + "--always-copy", + default=not meta.can_symlink, + action="store_true", + dest="copies", + help="try to use copies rather than symlinks, even when symlinks are the default for the platform", + ) + + def create(self): + self.install_patch() + + def install_patch(self): + text = self.env_patch_text() + if text: + pth = self.purelib / "_virtualenv.pth" + LOGGER.debug("create virtualenv import hook file %s", pth) + pth.write_text("import _virtualenv", encoding="utf-8") + dest_path = self.purelib / "_virtualenv.py" + LOGGER.debug("create %s", dest_path) + dest_path.write_text(text, encoding="utf-8") + + def env_patch_text(self): + """Patch the distutils package to not be derailed by its configuration files.""" + with self.app_data.ensure_extracted(Path(__file__).parent / "_virtualenv.py") as resolved_path: + text = resolved_path.read_text(encoding="utf-8") + return text.replace('"__SCRIPT_DIR__"', repr(os.path.relpath(str(self.script_dir), str(self.purelib)))) + + def _args(self): + return [*super()._args(), ("global", self.enable_system_site_package)] + + def set_pyenv_cfg(self): + super().set_pyenv_cfg() + self.pyenv_cfg["include-system-site-packages"] = "true" if self.enable_system_site_package else "false" + + +__all__ = [ + "ViaGlobalRefApi", + "ViaGlobalRefMeta", +] diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/__init__.py b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/__pycache__/__init__.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1ddaa553765b01b05f4c48f7168f1995230a9284 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/__pycache__/__init__.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/__pycache__/builtin_way.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/__pycache__/builtin_way.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6037a787a5bd5825cc1172c7e67c1103c4505bb0 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/__pycache__/builtin_way.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/__pycache__/ref.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/__pycache__/ref.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..04454e945be975654e5e2561a65538af57458ca9 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/__pycache__/ref.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/__pycache__/via_global_self_do.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/__pycache__/via_global_self_do.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fa8571ee73db5955da5db8058e18d9148118e88b Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/__pycache__/via_global_self_do.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/builtin_way.py b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/builtin_way.py new file mode 100644 index 0000000000000000000000000000000000000000..791b1d93d1e836abd63dac8d5f9e6d7d241f0974 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/builtin_way.py @@ -0,0 +1,19 @@ +from __future__ import annotations + +from abc import ABC + +from virtualenv.create.creator import Creator +from virtualenv.create.describe import Describe + + +class VirtualenvBuiltin(Creator, Describe, ABC): + """A creator that does operations itself without delegation, if we can create it we can also describe it.""" + + def __init__(self, options, interpreter) -> None: + Creator.__init__(self, options, interpreter) + Describe.__init__(self, self.dest, interpreter) + + +__all__ = [ + "VirtualenvBuiltin", +] diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/cpython/__init__.py b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/cpython/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/cpython/__pycache__/__init__.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/cpython/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0e5c5bb98f31c76d43d9d8694b4344bd8545eaa0 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/cpython/__pycache__/__init__.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/cpython/__pycache__/common.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/cpython/__pycache__/common.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..06e2102b269b6f8221c95de383fd09d5977a4f3a Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/cpython/__pycache__/common.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/cpython/__pycache__/cpython3.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/cpython/__pycache__/cpython3.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9be5b41fed686d6eca6a51b9d13364b5dfe53c29 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/cpython/__pycache__/cpython3.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/cpython/__pycache__/mac_os.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/cpython/__pycache__/mac_os.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..90d6ea1228a9c3b05b59e1346e3f803e1d367211 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/cpython/__pycache__/mac_os.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/cpython/common.py b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/cpython/common.py new file mode 100644 index 0000000000000000000000000000000000000000..7c2a04a32ef72d1bb22b18026b18e668c32610a3 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/cpython/common.py @@ -0,0 +1,73 @@ +from __future__ import annotations + +import re +from abc import ABC +from collections import OrderedDict +from pathlib import Path + +from virtualenv.create.describe import PosixSupports, WindowsSupports +from virtualenv.create.via_global_ref.builtin.ref import RefMust, RefWhen +from virtualenv.create.via_global_ref.builtin.via_global_self_do import ViaGlobalRefVirtualenvBuiltin + + +class CPython(ViaGlobalRefVirtualenvBuiltin, ABC): + @classmethod + def can_describe(cls, interpreter): + return interpreter.implementation == "CPython" and super().can_describe(interpreter) + + @classmethod + def exe_stem(cls): + return "python" + + +class CPythonPosix(CPython, PosixSupports, ABC): + """Create a CPython virtual environment on POSIX platforms.""" + + @classmethod + def _executables(cls, interpreter): + host_exe = Path(interpreter.system_executable) + major, minor = interpreter.version_info.major, interpreter.version_info.minor + targets = OrderedDict((i, None) for i in ["python", f"python{major}", f"python{major}.{minor}", host_exe.name]) + yield host_exe, list(targets.keys()), RefMust.NA, RefWhen.ANY + + +class CPythonWindows(CPython, WindowsSupports, ABC): + @classmethod + def _executables(cls, interpreter): + # symlink of the python executables does not work reliably, copy always instead + # - https://bugs.python.org/issue42013 + # - venv + host = cls.host_python(interpreter) + for path in (host.parent / n for n in {"python.exe", host.name}): + yield host, [path.name], RefMust.COPY, RefWhen.ANY + # for more info on pythonw.exe see https://stackoverflow.com/a/30313091 + python_w = host.parent / "pythonw.exe" + yield python_w, [python_w.name], RefMust.COPY, RefWhen.ANY + + @classmethod + def host_python(cls, interpreter): + return Path(interpreter.system_executable) + + +def is_mac_os_framework(interpreter): + if interpreter.platform == "darwin": + return interpreter.sysconfig_vars.get("PYTHONFRAMEWORK") == "Python3" + return False + + +def is_macos_brew(interpreter): + return interpreter.platform == "darwin" and _BREW.fullmatch(interpreter.system_prefix) is not None + + +_BREW = re.compile( + r"/(usr/local|opt/homebrew)/(opt/python@3\.\d{1,2}|Cellar/python@3\.\d{1,2}/3\.\d{1,2}\.\d{1,2})/Frameworks/" + r"Python\.framework/Versions/3\.\d{1,2}", +) + +__all__ = [ + "CPython", + "CPythonPosix", + "CPythonWindows", + "is_mac_os_framework", + "is_macos_brew", +] diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/cpython/cpython3.py b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/cpython/cpython3.py new file mode 100644 index 0000000000000000000000000000000000000000..daa474103ec98da8cf61678573e1e37390b74f75 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/cpython/cpython3.py @@ -0,0 +1,135 @@ +from __future__ import annotations + +import abc +import fnmatch +from itertools import chain +from operator import methodcaller as method +from pathlib import Path +from textwrap import dedent + +from virtualenv.create.describe import Python3Supports +from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest +from virtualenv.create.via_global_ref.store import is_store_python + +from .common import CPython, CPythonPosix, CPythonWindows, is_mac_os_framework, is_macos_brew + + +class CPython3(CPython, Python3Supports, abc.ABC): + """CPython 3 or later.""" + + +class CPython3Posix(CPythonPosix, CPython3): + @classmethod + def can_describe(cls, interpreter): + return ( + is_mac_os_framework(interpreter) is False + and is_macos_brew(interpreter) is False + and super().can_describe(interpreter) + ) + + def env_patch_text(self): + text = super().env_patch_text() + if self.pyvenv_launch_patch_active(self.interpreter): + text += dedent( + """ + # for https://github.com/python/cpython/pull/9516, see https://github.com/pypa/virtualenv/issues/1704 + import os + if "__PYVENV_LAUNCHER__" in os.environ: + del os.environ["__PYVENV_LAUNCHER__"] + """, + ) + return text + + @classmethod + def pyvenv_launch_patch_active(cls, interpreter): + ver = interpreter.version_info + return interpreter.platform == "darwin" and ((3, 7, 8) > ver >= (3, 7) or (3, 8, 3) > ver >= (3, 8)) + + +class CPython3Windows(CPythonWindows, CPython3): + """CPython 3 on Windows.""" + + @classmethod + def setup_meta(cls, interpreter): + if is_store_python(interpreter): # store python is not supported here + return None + return super().setup_meta(interpreter) + + @classmethod + def sources(cls, interpreter): + if cls.has_shim(interpreter): + refs = cls.executables(interpreter) + else: + refs = chain( + cls.executables(interpreter), + cls.dll_and_pyd(interpreter), + cls.python_zip(interpreter), + ) + yield from refs + + @classmethod + def executables(cls, interpreter): + return super().sources(interpreter) + + @classmethod + def has_shim(cls, interpreter): + return interpreter.version_info.minor >= 7 and cls.shim(interpreter) is not None # noqa: PLR2004 + + @classmethod + def shim(cls, interpreter): + shim = Path(interpreter.system_stdlib) / "venv" / "scripts" / "nt" / "python.exe" + if shim.exists(): + return shim + return None + + @classmethod + def host_python(cls, interpreter): + if cls.has_shim(interpreter): + # starting with CPython 3.7 Windows ships with a venvlauncher.exe that avoids the need for dll/pyd copies + # it also means the wrapper must be copied to avoid bugs such as https://bugs.python.org/issue42013 + return cls.shim(interpreter) + return super().host_python(interpreter) + + @classmethod + def dll_and_pyd(cls, interpreter): + folders = [Path(interpreter.system_executable).parent] + + # May be missing on some Python hosts. + # See https://github.com/pypa/virtualenv/issues/2368 + dll_folder = Path(interpreter.system_prefix) / "DLLs" + if dll_folder.is_dir(): + folders.append(dll_folder) + + for folder in folders: + for file in folder.iterdir(): + if file.suffix in {".pyd", ".dll"}: + yield PathRefToDest(file, cls.to_bin) + + @classmethod + def python_zip(cls, interpreter): + """ + "python{VERSION}.zip" contains compiled *.pyc std lib packages, where + "VERSION" is `py_version_nodot` var from the `sysconfig` module. + :see: https://docs.python.org/3/using/windows.html#the-embeddable-package + :see: `discovery.py_info.PythonInfo` class (interpreter). + :see: `python -m sysconfig` output. + + :note: The embeddable Python distribution for Windows includes + "python{VERSION}.zip" and "python{VERSION}._pth" files. User can + move/rename *zip* file and edit `sys.path` by editing *_pth* file. + Here the `pattern` is used only for the default *zip* file name! + """ # noqa: D205 + pattern = f"*python{interpreter.version_nodot}.zip" + matches = fnmatch.filter(interpreter.path, pattern) + matched_paths = map(Path, matches) + existing_paths = filter(method("exists"), matched_paths) + path = next(existing_paths, None) + if path is not None: + yield PathRefToDest(path, cls.to_bin) + + +__all__ = [ + "CPython3", + "CPython3Posix", + "CPython3Windows", +] diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/cpython/mac_os.py b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/cpython/mac_os.py new file mode 100644 index 0000000000000000000000000000000000000000..0ddbf9a33f2998652a67f91eead89a8267dbda66 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/cpython/mac_os.py @@ -0,0 +1,281 @@ +"""The Apple Framework builds require their own customization.""" + +from __future__ import annotations + +import logging +import os +import struct +import subprocess +from abc import ABC, abstractmethod +from pathlib import Path +from textwrap import dedent + +from virtualenv.create.via_global_ref.builtin.ref import ( + ExePathRefToDest, + PathRefToDest, + RefMust, +) +from virtualenv.create.via_global_ref.builtin.via_global_self_do import BuiltinViaGlobalRefMeta + +from .common import CPython, CPythonPosix, is_mac_os_framework, is_macos_brew +from .cpython3 import CPython3 + +LOGGER = logging.getLogger(__name__) + + +class CPythonmacOsFramework(CPython, ABC): + @classmethod + def can_describe(cls, interpreter): + return is_mac_os_framework(interpreter) and super().can_describe(interpreter) + + def create(self): + super().create() + + # change the install_name of the copied python executables + target = self.desired_mach_o_image_path() + current = self.current_mach_o_image_path() + for src in self._sources: + if isinstance(src, ExePathRefToDest) and (src.must == RefMust.COPY or not self.symlinks): + exes = [self.bin_dir / src.base] + if not self.symlinks: + exes.extend(self.bin_dir / a for a in src.aliases) + for exe in exes: + fix_mach_o(str(exe), current, target, self.interpreter.max_size) + + @classmethod + def _executables(cls, interpreter): + for _, targets, must, when in super()._executables(interpreter): + # Make sure we use the embedded interpreter inside the framework, even if sys.executable points to the + # stub executable in ${sys.prefix}/bin. + # See http://groups.google.com/group/python-virtualenv/browse_thread/thread/17cab2f85da75951 + fixed_host_exe = Path(interpreter.prefix) / "Resources" / "Python.app" / "Contents" / "MacOS" / "Python" + yield fixed_host_exe, targets, must, when + + @abstractmethod + def current_mach_o_image_path(self): + raise NotImplementedError + + @abstractmethod + def desired_mach_o_image_path(self): + raise NotImplementedError + + +class CPython3macOsFramework(CPythonmacOsFramework, CPython3, CPythonPosix): + def current_mach_o_image_path(self): + return "@executable_path/../../../../Python3" + + def desired_mach_o_image_path(self): + return "@executable_path/../.Python" + + @classmethod + def sources(cls, interpreter): + yield from super().sources(interpreter) + + # add a symlink to the host python image + exe = Path(interpreter.prefix) / "Python3" + yield PathRefToDest(exe, dest=lambda self, _: self.dest / ".Python", must=RefMust.SYMLINK) + + @property + def reload_code(self): + result = super().reload_code + return dedent( + f""" + # the bundled site.py always adds the global site package if we're on python framework build, escape this + import sys + before = sys._framework + try: + sys._framework = None + {result} + finally: + sys._framework = before + """, + ) + + +def fix_mach_o(exe, current, new, max_size): + """ + https://en.wikipedia.org/wiki/Mach-O. + + Mach-O, short for Mach object file format, is a file format for executables, object code, shared libraries, + dynamically-loaded code, and core dumps. A replacement for the a.out format, Mach-O offers more extensibility and + faster access to information in the symbol table. + + Each Mach-O file is made up of one Mach-O header, followed by a series of load commands, followed by one or more + segments, each of which contains between 0 and 255 sections. Mach-O uses the REL relocation format to handle + references to symbols. When looking up symbols Mach-O uses a two-level namespace that encodes each symbol into an + 'object/symbol name' pair that is then linearly searched for by first the object and then the symbol name. + + The basic structure—a list of variable-length "load commands" that reference pages of data elsewhere in the file—was + also used in the executable file format for Accent. The Accent file format was in turn, based on an idea from Spice + Lisp. + + With the introduction of Mac OS X 10.6 platform the Mach-O file underwent a significant modification that causes + binaries compiled on a computer running 10.6 or later to be (by default) executable only on computers running Mac + OS X 10.6 or later. The difference stems from load commands that the dynamic linker, in previous Mac OS X versions, + does not understand. Another significant change to the Mach-O format is the change in how the Link Edit tables + (found in the __LINKEDIT section) function. In 10.6 these new Link Edit tables are compressed by removing unused and + unneeded bits of information, however Mac OS X 10.5 and earlier cannot read this new Link Edit table format. + """ + try: + LOGGER.debug("change Mach-O for %s from %s to %s", exe, current, new) + _builtin_change_mach_o(max_size)(exe, current, new) + except Exception as e: # noqa: BLE001 + LOGGER.warning("Could not call _builtin_change_mac_o: %s. Trying to call install_name_tool instead.", e) + try: + cmd = ["install_name_tool", "-change", current, new, exe] + subprocess.check_call(cmd) + except Exception: + logging.fatal("Could not call install_name_tool -- you must have Apple's development tools installed") + raise + + +def _builtin_change_mach_o(maxint): # noqa: C901 + MH_MAGIC = 0xFEEDFACE # noqa: N806 + MH_CIGAM = 0xCEFAEDFE # noqa: N806 + MH_MAGIC_64 = 0xFEEDFACF # noqa: N806 + MH_CIGAM_64 = 0xCFFAEDFE # noqa: N806 + FAT_MAGIC = 0xCAFEBABE # noqa: N806 + BIG_ENDIAN = ">" # noqa: N806 + LITTLE_ENDIAN = "<" # noqa: N806 + LC_LOAD_DYLIB = 0xC # noqa: N806 + + class FileView: + """A proxy for file-like objects that exposes a given view of a file. Modified from macholib.""" + + def __init__(self, file_obj, start=0, size=maxint) -> None: + if isinstance(file_obj, FileView): + self._file_obj = file_obj._file_obj # noqa: SLF001 + else: + self._file_obj = file_obj + self._start = start + self._end = start + size + self._pos = 0 + + def __repr__(self) -> str: + return f"" + + def tell(self): + return self._pos + + def _checkwindow(self, seek_to, op): + if not (self._start <= seek_to <= self._end): + msg = f"{op} to offset {seek_to:d} is outside window [{self._start:d}, {self._end:d}]" + raise OSError(msg) + + def seek(self, offset, whence=0): + seek_to = offset + if whence == os.SEEK_SET: + seek_to += self._start + elif whence == os.SEEK_CUR: + seek_to += self._start + self._pos + elif whence == os.SEEK_END: + seek_to += self._end + else: + msg = f"Invalid whence argument to seek: {whence!r}" + raise OSError(msg) + self._checkwindow(seek_to, "seek") + self._file_obj.seek(seek_to) + self._pos = seek_to - self._start + + def write(self, content): + here = self._start + self._pos + self._checkwindow(here, "write") + self._checkwindow(here + len(content), "write") + self._file_obj.seek(here, os.SEEK_SET) + self._file_obj.write(content) + self._pos += len(content) + + def read(self, size=maxint): + assert size >= 0 # noqa: S101 + here = self._start + self._pos + self._checkwindow(here, "read") + size = min(size, self._end - here) + self._file_obj.seek(here, os.SEEK_SET) + read_bytes = self._file_obj.read(size) + self._pos += len(read_bytes) + return read_bytes + + def read_data(file, endian, num=1): + """Read a given number of 32-bits unsigned integers from the given file with the given endianness.""" + res = struct.unpack(endian + "L" * num, file.read(num * 4)) + if len(res) == 1: + return res[0] + return res + + def mach_o_change(at_path, what, value): # noqa: C901 + """ + Replace a given name (what) in any LC_LOAD_DYLIB command found in the given binary with a new name (value), + provided it's shorter. + """ # noqa: D205 + + def do_macho(file, bits, endian): + # Read Mach-O header (the magic number is assumed read by the caller) + _cpu_type, _cpu_sub_type, _file_type, n_commands, _size_of_commands, _flags = read_data(file, endian, 6) + # 64-bits header has one more field. + if bits == 64: # noqa: PLR2004 + read_data(file, endian) + # The header is followed by n commands + for _ in range(n_commands): + where = file.tell() + # Read command header + cmd, cmd_size = read_data(file, endian, 2) + if cmd == LC_LOAD_DYLIB: + # The first data field in LC_LOAD_DYLIB commands is the offset of the name, starting from the + # beginning of the command. + name_offset = read_data(file, endian) + file.seek(where + name_offset, os.SEEK_SET) + # Read the NUL terminated string + load = file.read(cmd_size - name_offset).decode() + load = load[: load.index("\0")] + # If the string is what is being replaced, overwrite it. + if load == what: + file.seek(where + name_offset, os.SEEK_SET) + file.write(value.encode() + b"\0") + # Seek to the next command + file.seek(where + cmd_size, os.SEEK_SET) + + def do_file(file, offset=0, size=maxint): + file = FileView(file, offset, size) + # Read magic number + magic = read_data(file, BIG_ENDIAN) + if magic == FAT_MAGIC: + # Fat binaries contain nfat_arch Mach-O binaries + n_fat_arch = read_data(file, BIG_ENDIAN) + for _ in range(n_fat_arch): + # Read arch header + _cpu_type, _cpu_sub_type, offset, size, _align = read_data(file, BIG_ENDIAN, 5) + do_file(file, offset, size) + elif magic == MH_MAGIC: + do_macho(file, 32, BIG_ENDIAN) + elif magic == MH_CIGAM: + do_macho(file, 32, LITTLE_ENDIAN) + elif magic == MH_MAGIC_64: + do_macho(file, 64, BIG_ENDIAN) + elif magic == MH_CIGAM_64: + do_macho(file, 64, LITTLE_ENDIAN) + + assert len(what) >= len(value) # noqa: S101 + + with open(at_path, "r+b") as f: + do_file(f) + + return mach_o_change + + +class CPython3macOsBrew(CPython3, CPythonPosix): + @classmethod + def can_describe(cls, interpreter): + return is_macos_brew(interpreter) and super().can_describe(interpreter) + + @classmethod + def setup_meta(cls, interpreter): # noqa: ARG003 + meta = BuiltinViaGlobalRefMeta() + meta.copy_error = "Brew disables copy creation: https://github.com/Homebrew/homebrew-core/issues/138159" + return meta + + +__all__ = [ + "CPython3macOsBrew", + "CPython3macOsFramework", + "CPythonmacOsFramework", +] diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/pypy/__init__.py b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/pypy/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/pypy/__pycache__/__init__.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/pypy/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3d881883c229d09db2ca99bfe86c87314065e80b Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/pypy/__pycache__/__init__.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/pypy/__pycache__/common.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/pypy/__pycache__/common.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..89a5bdd79d9844cb8e3e24d82d8cbb1902b1f583 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/pypy/__pycache__/common.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/pypy/__pycache__/pypy3.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/pypy/__pycache__/pypy3.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6d43da1ce5e5b0f73be253c0f0b4840aeb21ce14 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/pypy/__pycache__/pypy3.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/pypy/common.py b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/pypy/common.py new file mode 100644 index 0000000000000000000000000000000000000000..ca4b45ff14c72c5e48ab8ccecffd7b7c8434b5eb --- /dev/null +++ b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/pypy/common.py @@ -0,0 +1,53 @@ +from __future__ import annotations + +import abc +from pathlib import Path + +from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest, RefMust, RefWhen +from virtualenv.create.via_global_ref.builtin.via_global_self_do import ViaGlobalRefVirtualenvBuiltin + + +class PyPy(ViaGlobalRefVirtualenvBuiltin, abc.ABC): + @classmethod + def can_describe(cls, interpreter): + return interpreter.implementation == "PyPy" and super().can_describe(interpreter) + + @classmethod + def _executables(cls, interpreter): + host = Path(interpreter.system_executable) + targets = sorted(f"{name}{PyPy.suffix}" for name in cls.exe_names(interpreter)) + yield host, targets, RefMust.NA, RefWhen.ANY + + @classmethod + def executables(cls, interpreter): + yield from super().sources(interpreter) + + @classmethod + def exe_names(cls, interpreter): + return { + cls.exe_stem(), + "python", + f"python{interpreter.version_info.major}", + f"python{interpreter.version_info.major}.{interpreter.version_info.minor}", + } + + @classmethod + def sources(cls, interpreter): + yield from cls.executables(interpreter) + for host in cls._add_shared_libs(interpreter): + yield PathRefToDest(host, dest=lambda self, s: self.bin_dir / s.name) + + @classmethod + def _add_shared_libs(cls, interpreter): + # https://bitbucket.org/pypy/pypy/issue/1922/future-proofing-virtualenv + python_dir = Path(interpreter.system_executable).resolve().parent + yield from cls._shared_libs(python_dir) + + @classmethod + def _shared_libs(cls, python_dir): + raise NotImplementedError + + +__all__ = [ + "PyPy", +] diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/pypy/pypy3.py b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/pypy/pypy3.py new file mode 100644 index 0000000000000000000000000000000000000000..fa61ebc952f73449f18e4bba3fa39bc6dc58fd83 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/pypy/pypy3.py @@ -0,0 +1,76 @@ +from __future__ import annotations + +import abc +from pathlib import Path + +from virtualenv.create.describe import PosixSupports, Python3Supports, WindowsSupports +from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest + +from .common import PyPy + + +class PyPy3(PyPy, Python3Supports, abc.ABC): + @classmethod + def exe_stem(cls): + return "pypy3" + + @classmethod + def exe_names(cls, interpreter): + return super().exe_names(interpreter) | {"pypy"} + + +class PyPy3Posix(PyPy3, PosixSupports): + """PyPy 3 on POSIX.""" + + @classmethod + def _shared_libs(cls, python_dir): + # glob for libpypy3-c.so, libpypy3-c.dylib, libpypy3.9-c.so ... + return python_dir.glob("libpypy3*.*") + + def to_lib(self, src): + return self.dest / "lib" / src.name + + @classmethod + def sources(cls, interpreter): + yield from super().sources(interpreter) + # PyPy >= 3.8 supports a standard prefix installation, where older + # versions always used a portable/development style installation. + # If this is a standard prefix installation, skip the below: + if interpreter.system_prefix == "/usr": + return + # Also copy/symlink anything under prefix/lib, which, for "portable" + # PyPy builds, includes the tk,tcl runtime and a number of shared + # objects. In distro-specific builds or on conda this should be empty + # (on PyPy3.8+ it will, like on CPython, hold the stdlib). + host_lib = Path(interpreter.system_prefix) / "lib" + stdlib = Path(interpreter.system_stdlib) + if host_lib.exists() and host_lib.is_dir(): + for path in host_lib.iterdir(): + if stdlib == path: + # For PyPy3.8+ the stdlib lives in lib/pypy3.8 + # We need to avoid creating a symlink to it since that + # will defeat the purpose of a virtualenv + continue + yield PathRefToDest(path, dest=cls.to_lib) + + +class Pypy3Windows(PyPy3, WindowsSupports): + """PyPy 3 on Windows.""" + + @property + def less_v37(self): + return self.interpreter.version_info.minor < 7 # noqa: PLR2004 + + @classmethod + def _shared_libs(cls, python_dir): + # glob for libpypy*.dll and libffi*.dll + for pattern in ["libpypy*.dll", "libffi*.dll"]: + srcs = python_dir.glob(pattern) + yield from srcs + + +__all__ = [ + "PyPy3", + "PyPy3Posix", + "Pypy3Windows", +] diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/ref.py b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/ref.py new file mode 100644 index 0000000000000000000000000000000000000000..e2fd45ffe28e2ab035ec65f2bb0fa17ccf362389 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/ref.py @@ -0,0 +1,178 @@ +""" +Virtual environments in the traditional sense are built as reference to the host python. This file allows declarative +references to elements on the file system, allowing our system to automatically detect what modes it can support given +the constraints: e.g. can the file system symlink, can the files be read, executed, etc. +""" # noqa: D205 + +from __future__ import annotations + +import os +from abc import ABC, abstractmethod +from collections import OrderedDict +from stat import S_IXGRP, S_IXOTH, S_IXUSR + +from virtualenv.info import fs_is_case_sensitive, fs_supports_symlink +from virtualenv.util.path import copy, make_exe, symlink + + +class RefMust: + NA = "NA" + COPY = "copy" + SYMLINK = "symlink" + + +class RefWhen: + ANY = "ANY" + COPY = "copy" + SYMLINK = "symlink" + + +class PathRef(ABC): + """Base class that checks if a file reference can be symlink/copied.""" + + FS_SUPPORTS_SYMLINK = fs_supports_symlink() + FS_CASE_SENSITIVE = fs_is_case_sensitive() + + def __init__(self, src, must=RefMust.NA, when=RefWhen.ANY) -> None: + self.must = must + self.when = when + self.src = src + try: + self.exists = src.exists() + except OSError: + self.exists = False + self._can_read = None if self.exists else False + self._can_copy = None if self.exists else False + self._can_symlink = None if self.exists else False + + def __repr__(self) -> str: + return f"{self.__class__.__name__}(src={self.src})" + + @property + def can_read(self): + if self._can_read is None: + if self.src.is_file(): + try: + with self.src.open("rb"): + self._can_read = True + except OSError: + self._can_read = False + else: + self._can_read = os.access(str(self.src), os.R_OK) + return self._can_read + + @property + def can_copy(self): + if self._can_copy is None: + if self.must == RefMust.SYMLINK: + self._can_copy = self.can_symlink + else: + self._can_copy = self.can_read + return self._can_copy + + @property + def can_symlink(self): + if self._can_symlink is None: + if self.must == RefMust.COPY: + self._can_symlink = self.can_copy + else: + self._can_symlink = self.FS_SUPPORTS_SYMLINK and self.can_read + return self._can_symlink + + @abstractmethod + def run(self, creator, symlinks): + raise NotImplementedError + + def method(self, symlinks): + if self.must == RefMust.SYMLINK: + return symlink + if self.must == RefMust.COPY: + return copy + return symlink if symlinks else copy + + +class ExePathRef(PathRef, ABC): + """Base class that checks if a executable can be references via symlink/copy.""" + + def __init__(self, src, must=RefMust.NA, when=RefWhen.ANY) -> None: + super().__init__(src, must, when) + self._can_run = None + + @property + def can_symlink(self): + if self.FS_SUPPORTS_SYMLINK: + return self.can_run + return False + + @property + def can_run(self): + if self._can_run is None: + mode = self.src.stat().st_mode + for key in [S_IXUSR, S_IXGRP, S_IXOTH]: + if mode & key: + self._can_run = True + break + else: + self._can_run = False + return self._can_run + + +class PathRefToDest(PathRef): + """Link a path on the file system.""" + + def __init__(self, src, dest, must=RefMust.NA, when=RefWhen.ANY) -> None: + super().__init__(src, must, when) + self.dest = dest + + def run(self, creator, symlinks): + dest = self.dest(creator, self.src) + method = self.method(symlinks) + dest_iterable = dest if isinstance(dest, list) else (dest,) + if not dest.parent.exists(): + dest.parent.mkdir(parents=True, exist_ok=True) + for dst in dest_iterable: + method(self.src, dst) + + +class ExePathRefToDest(PathRefToDest, ExePathRef): + """Link a exe path on the file system.""" + + def __init__(self, src, targets, dest, must=RefMust.NA, when=RefWhen.ANY) -> None: + ExePathRef.__init__(self, src, must, when) + PathRefToDest.__init__(self, src, dest, must, when) + if not self.FS_CASE_SENSITIVE: + targets = list(OrderedDict((i.lower(), None) for i in targets).keys()) + self.base = targets[0] + self.aliases = targets[1:] + self.dest = dest + + def run(self, creator, symlinks): + bin_dir = self.dest(creator, self.src).parent + dest = bin_dir / self.base + method = self.method(symlinks) + method(self.src, dest) + if not symlinks: + make_exe(dest) + for extra in self.aliases: + link_file = bin_dir / extra + if link_file.exists(): + link_file.unlink() + if symlinks: + link_file.symlink_to(self.base) + else: + copy(self.src, link_file) + if not symlinks: + make_exe(link_file) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}(src={self.src}, alias={self.aliases})" + + +__all__ = [ + "ExePathRef", + "ExePathRefToDest", + "PathRef", + "PathRefToDest", + "RefMust", + "RefWhen", +] diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/via_global_self_do.py b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/via_global_self_do.py new file mode 100644 index 0000000000000000000000000000000000000000..2f7f2f11a960db5a73677c6b144c8d8e2cdeecb3 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/builtin/via_global_self_do.py @@ -0,0 +1,118 @@ +from __future__ import annotations + +from abc import ABC + +from virtualenv.create.via_global_ref.api import ViaGlobalRefApi, ViaGlobalRefMeta +from virtualenv.create.via_global_ref.builtin.ref import ( + ExePathRefToDest, + RefMust, + RefWhen, +) +from virtualenv.util.path import ensure_dir + +from .builtin_way import VirtualenvBuiltin + + +class BuiltinViaGlobalRefMeta(ViaGlobalRefMeta): + def __init__(self) -> None: + super().__init__() + self.sources = [] + + +class ViaGlobalRefVirtualenvBuiltin(ViaGlobalRefApi, VirtualenvBuiltin, ABC): + def __init__(self, options, interpreter) -> None: + super().__init__(options, interpreter) + self._sources = getattr(options.meta, "sources", None) # if we're created as a describer this might be missing + + @classmethod + def can_create(cls, interpreter): + """By default, all built-in methods assume that if we can describe it we can create it.""" + # first we must be able to describe it + if not cls.can_describe(interpreter): + return None + meta = cls.setup_meta(interpreter) + if meta is not None and meta: + cls._sources_can_be_applied(interpreter, meta) + return meta + + @classmethod + def _sources_can_be_applied(cls, interpreter, meta): + for src in cls.sources(interpreter): + if src.exists: + if meta.can_copy and not src.can_copy: + meta.copy_error = f"cannot copy {src}" + if meta.can_symlink and not src.can_symlink: + meta.symlink_error = f"cannot symlink {src}" + else: + msg = f"missing required file {src}" + if src.when == RefMust.NA: + meta.error = msg + elif src.when == RefMust.COPY: + meta.copy_error = msg + elif src.when == RefMust.SYMLINK: + meta.symlink_error = msg + if not meta.can_copy and not meta.can_symlink: + meta.error = f"neither copy or symlink supported, copy: {meta.copy_error} symlink: {meta.symlink_error}" + if meta.error: + break + meta.sources.append(src) + + @classmethod + def setup_meta(cls, interpreter): # noqa: ARG003 + return BuiltinViaGlobalRefMeta() + + @classmethod + def sources(cls, interpreter): + for host_exe, targets, must, when in cls._executables(interpreter): + yield ExePathRefToDest(host_exe, dest=cls.to_bin, targets=targets, must=must, when=when) + + def to_bin(self, src): + return self.bin_dir / src.name + + @classmethod + def _executables(cls, interpreter): + raise NotImplementedError + + def create(self): + dirs = self.ensure_directories() + for directory in list(dirs): + if any(i for i in dirs if i is not directory and directory.parts == i.parts[: len(directory.parts)]): + dirs.remove(directory) + for directory in sorted(dirs): + ensure_dir(directory) + + self.set_pyenv_cfg() + self.pyenv_cfg.write() + true_system_site = self.enable_system_site_package + try: + self.enable_system_site_package = False + for src in self._sources: + if ( + src.when == RefWhen.ANY + or (src.when == RefWhen.SYMLINK and self.symlinks is True) + or (src.when == RefWhen.COPY and self.symlinks is False) + ): + src.run(self, self.symlinks) + finally: + if true_system_site != self.enable_system_site_package: + self.enable_system_site_package = true_system_site + super().create() + + def ensure_directories(self): + return {self.dest, self.bin_dir, self.script_dir, self.stdlib} | set(self.libs) + + def set_pyenv_cfg(self): + """ + We directly inject the base prefix and base exec prefix to avoid site.py needing to discover these + from home (which usually is done within the interpreter itself). + """ # noqa: D205 + super().set_pyenv_cfg() + self.pyenv_cfg["base-prefix"] = self.interpreter.system_prefix + self.pyenv_cfg["base-exec-prefix"] = self.interpreter.system_exec_prefix + self.pyenv_cfg["base-executable"] = self.interpreter.system_executable + + +__all__ = [ + "BuiltinViaGlobalRefMeta", + "ViaGlobalRefVirtualenvBuiltin", +] diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/store.py b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/store.py new file mode 100644 index 0000000000000000000000000000000000000000..4be6689215b5275518ff05814e44f6c70a21da59 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/store.py @@ -0,0 +1,26 @@ +from __future__ import annotations + +from pathlib import Path + + +def handle_store_python(meta, interpreter): + if is_store_python(interpreter): + meta.symlink_error = "Windows Store Python does not support virtual environments via symlink" + return meta + + +def is_store_python(interpreter): + parts = Path(interpreter.system_executable).parts + return ( + len(parts) > 4 # noqa: PLR2004 + and parts[-4] == "Microsoft" + and parts[-3] == "WindowsApps" + and parts[-2].startswith("PythonSoftwareFoundation.Python.3.") + and parts[-1].startswith("python") + ) + + +__all__ = [ + "handle_store_python", + "is_store_python", +] diff --git a/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/venv.py b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/venv.py new file mode 100644 index 0000000000000000000000000000000000000000..7d6f32e39e1fe76600866673f28c085ac6a6c242 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/virtualenv/create/via_global_ref/venv.py @@ -0,0 +1,104 @@ +from __future__ import annotations # noqa: A005 + +import logging +from copy import copy + +from virtualenv.create.via_global_ref.store import handle_store_python +from virtualenv.discovery.py_info import PythonInfo +from virtualenv.util.error import ProcessCallFailedError +from virtualenv.util.path import ensure_dir +from virtualenv.util.subprocess import run_cmd + +from .api import ViaGlobalRefApi, ViaGlobalRefMeta +from .builtin.cpython.mac_os import CPython3macOsBrew +from .builtin.pypy.pypy3 import Pypy3Windows + +LOGGER = logging.getLogger(__name__) + + +class Venv(ViaGlobalRefApi): + def __init__(self, options, interpreter) -> None: + self.describe = options.describe + super().__init__(options, interpreter) + current = PythonInfo.current() + self.can_be_inline = interpreter is current and interpreter.executable == interpreter.system_executable + self._context = None + + def _args(self): + return super()._args() + ([("describe", self.describe.__class__.__name__)] if self.describe else []) + + @classmethod + def can_create(cls, interpreter): + if interpreter.has_venv: + if CPython3macOsBrew.can_describe(interpreter): + return CPython3macOsBrew.setup_meta(interpreter) + meta = ViaGlobalRefMeta() + if interpreter.platform == "win32": + meta = handle_store_python(meta, interpreter) + return meta + return None + + def create(self): + if self.can_be_inline: + self.create_inline() + else: + self.create_via_sub_process() + for lib in self.libs: + ensure_dir(lib) + super().create() + self.executables_for_win_pypy_less_v37() + + def executables_for_win_pypy_less_v37(self): + """ + PyPy <= 3.6 (v7.3.3) for Windows contains only pypy3.exe and pypy3w.exe + Venv does not handle non-existing exe sources, e.g. python.exe, so this + patch does it. + """ # noqa: D205 + creator = self.describe + if isinstance(creator, Pypy3Windows) and creator.less_v37: + for exe in creator.executables(self.interpreter): + exe.run(creator, self.symlinks) + + def create_inline(self): + from venv import EnvBuilder # noqa: PLC0415 + + builder = EnvBuilder( + system_site_packages=self.enable_system_site_package, + clear=False, + symlinks=self.symlinks, + with_pip=False, + ) + builder.create(str(self.dest)) + + def create_via_sub_process(self): + cmd = self.get_host_create_cmd() + LOGGER.info("using host built-in venv to create via %s", " ".join(cmd)) + code, out, err = run_cmd(cmd) + if code != 0: + raise ProcessCallFailedError(code, out, err, cmd) + + def get_host_create_cmd(self): + cmd = [self.interpreter.system_executable, "-m", "venv", "--without-pip"] + if self.enable_system_site_package: + cmd.append("--system-site-packages") + cmd.extend(("--symlinks" if self.symlinks else "--copies", str(self.dest))) + return cmd + + def set_pyenv_cfg(self): + # prefer venv options over ours, but keep our extra + venv_content = copy(self.pyenv_cfg.refresh()) + super().set_pyenv_cfg() + self.pyenv_cfg.update(venv_content) + + def __getattribute__(self, item): + describe = object.__getattribute__(self, "describe") + if describe is not None and hasattr(describe, item): + element = getattr(describe, item) + if not callable(element) or item == "script": + return element + return object.__getattribute__(self, item) + + +__all__ = [ + "Venv", +] diff --git a/.venv/lib/python3.11/site-packages/virtualenv/seed/__pycache__/__init__.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/seed/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f993a1680fcd3f725b9e51815b873acfe6343a79 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/seed/__pycache__/__init__.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/seed/__pycache__/seeder.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/seed/__pycache__/seeder.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fc70161b205885013bcd091cba2b3218f779bdf9 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/seed/__pycache__/seeder.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/seed/embed/via_app_data/__pycache__/__init__.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/seed/embed/via_app_data/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6a22e6dee8c3dd4abedace44a6a27c9e1d5a3dcc Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/seed/embed/via_app_data/__pycache__/__init__.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/seed/embed/via_app_data/__pycache__/via_app_data.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/seed/embed/via_app_data/__pycache__/via_app_data.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9dfc104df69096f3fda7e545ff34d1c50cf56d67 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/seed/embed/via_app_data/__pycache__/via_app_data.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/seed/embed/via_app_data/pip_install/__init__.py b/.venv/lib/python3.11/site-packages/virtualenv/seed/embed/via_app_data/pip_install/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venv/lib/python3.11/site-packages/virtualenv/seed/embed/via_app_data/pip_install/__pycache__/__init__.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/seed/embed/via_app_data/pip_install/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9697ef9d54d1e9a28568c973828db4ec39be5c80 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/seed/embed/via_app_data/pip_install/__pycache__/__init__.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/seed/embed/via_app_data/pip_install/__pycache__/base.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/seed/embed/via_app_data/pip_install/__pycache__/base.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8029b10ded7f496d809e0334321cad8a4b955542 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/seed/embed/via_app_data/pip_install/__pycache__/base.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/seed/embed/via_app_data/pip_install/__pycache__/copy.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/seed/embed/via_app_data/pip_install/__pycache__/copy.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4febfdc00fcbb3912d7c12517fe8d69fd7cc8de2 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/seed/embed/via_app_data/pip_install/__pycache__/copy.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/seed/embed/via_app_data/pip_install/__pycache__/symlink.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/seed/embed/via_app_data/pip_install/__pycache__/symlink.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3e640214647a200b5563409930516497d3f92fd2 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/seed/embed/via_app_data/pip_install/__pycache__/symlink.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/seed/embed/via_app_data/pip_install/base.py b/.venv/lib/python3.11/site-packages/virtualenv/seed/embed/via_app_data/pip_install/base.py new file mode 100644 index 0000000000000000000000000000000000000000..6cddef83921233bf4e544ec2263c75648d5c1c30 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/virtualenv/seed/embed/via_app_data/pip_install/base.py @@ -0,0 +1,206 @@ +from __future__ import annotations + +import logging +import os +import re +import zipfile +from abc import ABC, abstractmethod +from configparser import ConfigParser +from itertools import chain +from pathlib import Path +from tempfile import mkdtemp + +from distlib.scripts import ScriptMaker, enquote_executable + +from virtualenv.util.path import safe_delete + +LOGGER = logging.getLogger(__name__) + + +class PipInstall(ABC): + def __init__(self, wheel, creator, image_folder) -> None: + self._wheel = wheel + self._creator = creator + self._image_dir = image_folder + self._extracted = False + self.__dist_info = None + self._console_entry_points = None + + @abstractmethod + def _sync(self, src, dst): + raise NotImplementedError + + def install(self, version_info): + self._extracted = True + self._uninstall_previous_version() + # sync image + for filename in self._image_dir.iterdir(): + into = self._creator.purelib / filename.name + self._sync(filename, into) + # generate console executables + consoles = set() + script_dir = self._creator.script_dir + for name, module in self._console_scripts.items(): + consoles.update(self._create_console_entry_point(name, module, script_dir, version_info)) + LOGGER.debug("generated console scripts %s", " ".join(i.name for i in consoles)) + + def build_image(self): + # 1. first extract the wheel + LOGGER.debug("build install image for %s to %s", self._wheel.name, self._image_dir) + with zipfile.ZipFile(str(self._wheel)) as zip_ref: + self._shorten_path_if_needed(zip_ref) + zip_ref.extractall(str(self._image_dir)) + self._extracted = True + # 2. now add additional files not present in the distribution + new_files = self._generate_new_files() + # 3. finally fix the records file + self._fix_records(new_files) + + def _shorten_path_if_needed(self, zip_ref): + if os.name == "nt": + to_folder = str(self._image_dir) + # https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation + zip_max_len = max(len(i) for i in zip_ref.namelist()) + path_len = zip_max_len + len(to_folder) + if path_len > 260: # noqa: PLR2004 + self._image_dir.mkdir(exist_ok=True) # to get a short path must exist + + from virtualenv.util.path import get_short_path_name # noqa: PLC0415 + + to_folder = get_short_path_name(to_folder) + self._image_dir = Path(to_folder) + + def _records_text(self, files): + return "\n".join(f"{os.path.relpath(str(rec), str(self._image_dir))},," for rec in files) + + def _generate_new_files(self): + new_files = set() + installer = self._dist_info / "INSTALLER" + installer.write_text("pip\n", encoding="utf-8") + new_files.add(installer) + # inject a no-op root element, as workaround for bug in https://github.com/pypa/pip/issues/7226 + marker = self._image_dir / f"{self._dist_info.stem}.virtualenv" + marker.write_text("", encoding="utf-8") + new_files.add(marker) + folder = mkdtemp() + try: + to_folder = Path(folder) + rel = os.path.relpath(str(self._creator.script_dir), str(self._creator.purelib)) + version_info = self._creator.interpreter.version_info + for name, module in self._console_scripts.items(): + new_files.update( + Path(os.path.normpath(str(self._image_dir / rel / i.name))) + for i in self._create_console_entry_point(name, module, to_folder, version_info) + ) + finally: + safe_delete(folder) + return new_files + + @property + def _dist_info(self): + if self._extracted is False: + return None # pragma: no cover + if self.__dist_info is None: + files = [] + for filename in self._image_dir.iterdir(): + files.append(filename.name) + if filename.suffix == ".dist-info": + self.__dist_info = filename + break + else: + msg = f"no .dist-info at {self._image_dir}, has {', '.join(files)}" + raise RuntimeError(msg) # pragma: no cover + return self.__dist_info + + @abstractmethod + def _fix_records(self, extra_record_data): + raise NotImplementedError + + @property + def _console_scripts(self): + if self._extracted is False: + return None # pragma: no cover + if self._console_entry_points is None: + self._console_entry_points = {} + entry_points = self._dist_info / "entry_points.txt" + if entry_points.exists(): + parser = ConfigParser() + with entry_points.open(encoding="utf-8") as file_handler: + parser.read_file(file_handler) + if "console_scripts" in parser.sections(): + for name, value in parser.items("console_scripts"): + match = re.match(r"(.*?)-?\d\.?\d*", name) + our_name = match.groups(1)[0] if match else name + self._console_entry_points[our_name] = value + return self._console_entry_points + + def _create_console_entry_point(self, name, value, to_folder, version_info): + result = [] + maker = ScriptMakerCustom(to_folder, version_info, self._creator.exe, name) + specification = f"{name} = {value}" + new_files = maker.make(specification) + result.extend(Path(i) for i in new_files) + return result + + def _uninstall_previous_version(self): + dist_name = self._dist_info.stem.split("-")[0] + in_folders = chain.from_iterable([i.iterdir() for i in (self._creator.purelib, self._creator.platlib)]) + paths = (p for p in in_folders if p.stem.split("-")[0] == dist_name and p.suffix == ".dist-info" and p.is_dir()) + existing_dist = next(paths, None) + if existing_dist is not None: + self._uninstall_dist(existing_dist) + + @staticmethod + def _uninstall_dist(dist): + dist_base = dist.parent + LOGGER.debug("uninstall existing distribution %s from %s", dist.stem, dist_base) + + top_txt = dist / "top_level.txt" # add top level packages at folder level + paths = ( + {dist.parent / i.strip() for i in top_txt.read_text(encoding="utf-8").splitlines()} + if top_txt.exists() + else set() + ) + paths.add(dist) # add the dist-info folder itself + + base_dirs, record = paths.copy(), dist / "RECORD" # collect entries in record that we did not register yet + for name in ( + (i.split(",")[0] for i in record.read_text(encoding="utf-8").splitlines()) if record.exists() else () + ): + path = dist_base / name + if not any(p in base_dirs for p in path.parents): # only add if not already added as a base dir + paths.add(path) + + for path in sorted(paths): # actually remove stuff in a stable order + if path.exists(): + if path.is_dir() and not path.is_symlink(): + safe_delete(path) + else: + path.unlink() + + def clear(self): + if self._image_dir.exists(): + safe_delete(self._image_dir) + + def has_image(self): + return self._image_dir.exists() and next(self._image_dir.iterdir()) is not None + + +class ScriptMakerCustom(ScriptMaker): + def __init__(self, target_dir, version_info, executable, name) -> None: + super().__init__(None, str(target_dir)) + self.clobber = True # overwrite + self.set_mode = True # ensure they are executable + self.executable = enquote_executable(str(executable)) + self.version_info = version_info.major, version_info.minor + self.variants = {"", "X", "X.Y"} + self._name = name + + def _write_script(self, names, shebang, script_bytes, filenames, ext): + names.add(f"{self._name}{self.version_info[0]}.{self.version_info[1]}") + super()._write_script(names, shebang, script_bytes, filenames, ext) + + +__all__ = [ + "PipInstall", +] diff --git a/.venv/lib/python3.11/site-packages/virtualenv/seed/embed/via_app_data/pip_install/copy.py b/.venv/lib/python3.11/site-packages/virtualenv/seed/embed/via_app_data/pip_install/copy.py new file mode 100644 index 0000000000000000000000000000000000000000..af50e8d1294b439c59c20e4042e12abdd14557e7 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/virtualenv/seed/embed/via_app_data/pip_install/copy.py @@ -0,0 +1,40 @@ +from __future__ import annotations # noqa: A005 + +import os +from pathlib import Path + +from virtualenv.util.path import copy + +from .base import PipInstall + + +class CopyPipInstall(PipInstall): + def _sync(self, src, dst): + copy(src, dst) + + def _generate_new_files(self): + # create the pyc files + new_files = super()._generate_new_files() + new_files.update(self._cache_files()) + return new_files + + def _cache_files(self): + version = self._creator.interpreter.version_info + py_c_ext = f".{self._creator.interpreter.implementation.lower()}-{version.major}{version.minor}.pyc" + for root, dirs, files in os.walk(str(self._image_dir), topdown=True): + root_path = Path(root) + for name in files: + if name.endswith(".py"): + yield root_path / f"{name[:-3]}{py_c_ext}" + for name in dirs: + yield root_path / name / "__pycache__" + + def _fix_records(self, new_files): + extra_record_data_str = self._records_text(new_files) + with (self._dist_info / "RECORD").open("ab") as file_handler: + file_handler.write(extra_record_data_str.encode("utf-8")) + + +__all__ = [ + "CopyPipInstall", +] diff --git a/.venv/lib/python3.11/site-packages/virtualenv/seed/embed/via_app_data/pip_install/symlink.py b/.venv/lib/python3.11/site-packages/virtualenv/seed/embed/via_app_data/pip_install/symlink.py new file mode 100644 index 0000000000000000000000000000000000000000..7eb9f5f470ae870de0067272c94bbb55be61da47 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/virtualenv/seed/embed/via_app_data/pip_install/symlink.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +import os +from stat import S_IREAD, S_IRGRP, S_IROTH +from subprocess import PIPE, Popen + +from virtualenv.util.path import safe_delete, set_tree + +from .base import PipInstall + + +class SymlinkPipInstall(PipInstall): + def _sync(self, src, dst): + os.symlink(str(src), str(dst)) + + def _generate_new_files(self): + # create the pyc files, as the build image will be R/O + cmd = [str(self._creator.exe), "-m", "compileall", str(self._image_dir)] + process = Popen(cmd, stdout=PIPE, stderr=PIPE) + process.communicate() + # the root pyc is shared, so we'll not symlink that - but still add the pyc files to the RECORD for close + root_py_cache = self._image_dir / "__pycache__" + new_files = set() + if root_py_cache.exists(): + new_files.update(root_py_cache.iterdir()) + new_files.add(root_py_cache) + safe_delete(root_py_cache) + core_new_files = super()._generate_new_files() + # remove files that are within the image folder deeper than one level (as these will be not linked directly) + for file in core_new_files: + try: + rel = file.relative_to(self._image_dir) + if len(rel.parts) > 1: + continue + except ValueError: + pass + new_files.add(file) + return new_files + + def _fix_records(self, new_files): + new_files.update(i for i in self._image_dir.iterdir()) + extra_record_data_str = self._records_text(sorted(new_files, key=str)) + (self._dist_info / "RECORD").write_text(extra_record_data_str, encoding="utf-8") + + def build_image(self): + super().build_image() + # protect the image by making it read only + set_tree(self._image_dir, S_IREAD | S_IRGRP | S_IROTH) + + def clear(self): + if self._image_dir.exists(): + safe_delete(self._image_dir) + super().clear() + + +__all__ = [ + "SymlinkPipInstall", +] diff --git a/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/__init__.py b/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..20a398a306cbf00699bcd7910c4d580cc65ca4d1 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/__init__.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +from .acquire import get_wheel, pip_wheel_env_run +from .util import Version, Wheel + +__all__ = [ + "Version", + "Wheel", + "get_wheel", + "pip_wheel_env_run", +] diff --git a/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/__pycache__/__init__.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e145d19206690b53956dfd501d61ba842762c855 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/__pycache__/__init__.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/__pycache__/acquire.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/__pycache__/acquire.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6f8f0138d9f604949a33225c924c027e71323d58 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/__pycache__/acquire.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/__pycache__/bundle.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/__pycache__/bundle.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8bb19924e2326544e401d448c332483143066d83 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/__pycache__/bundle.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/__pycache__/periodic_update.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/__pycache__/periodic_update.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..737f111a68cb67bd9a2ab388cb2d6c3d2b54193a Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/__pycache__/periodic_update.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/__pycache__/util.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/__pycache__/util.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3a120659884a066845d6d3c7a747f71b2a5d30d7 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/__pycache__/util.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/acquire.py b/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/acquire.py new file mode 100644 index 0000000000000000000000000000000000000000..5ca610fcd565439c0df83724fae27cbd96461af3 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/acquire.py @@ -0,0 +1,134 @@ +"""Bootstrap.""" + +from __future__ import annotations + +import logging +import sys +from operator import eq, lt +from pathlib import Path +from subprocess import PIPE, CalledProcessError, Popen + +from .bundle import from_bundle +from .periodic_update import add_wheel_to_update_log +from .util import Version, Wheel, discover_wheels + +LOGGER = logging.getLogger(__name__) + + +def get_wheel( # noqa: PLR0913 + distribution, + version, + for_py_version, + search_dirs, + download, + app_data, + do_periodic_update, + env, +): + """Get a wheel with the given distribution-version-for_py_version trio, by using the extra search dir + download.""" + # not all wheels are compatible with all python versions, so we need to py version qualify it + wheel = None + + if not download or version != Version.bundle: + # 1. acquire from bundle + wheel = from_bundle(distribution, version, for_py_version, search_dirs, app_data, do_periodic_update, env) + + if download and wheel is None and version != Version.embed: + # 2. download from the internet + wheel = download_wheel( + distribution=distribution, + version_spec=Version.as_version_spec(version), + for_py_version=for_py_version, + search_dirs=search_dirs, + app_data=app_data, + to_folder=app_data.house, + env=env, + ) + if wheel is not None and app_data.can_update: + add_wheel_to_update_log(wheel, for_py_version, app_data) + + return wheel + + +def download_wheel(distribution, version_spec, for_py_version, search_dirs, app_data, to_folder, env): # noqa: PLR0913 + to_download = f"{distribution}{version_spec or ''}" + LOGGER.debug("download wheel %s %s to %s", to_download, for_py_version, to_folder) + cmd = [ + sys.executable, + "-m", + "pip", + "download", + "--progress-bar", + "off", + "--disable-pip-version-check", + "--only-binary=:all:", + "--no-deps", + "--python-version", + for_py_version, + "-d", + str(to_folder), + to_download, + ] + # pip has no interface in python - must be a new sub-process + env = pip_wheel_env_run(search_dirs, app_data, env) + process = Popen(cmd, env=env, stdout=PIPE, stderr=PIPE, universal_newlines=True, encoding="utf-8") + out, err = process.communicate() + if process.returncode != 0: + kwargs = {"output": out, "stderr": err} + raise CalledProcessError(process.returncode, cmd, **kwargs) + result = _find_downloaded_wheel(distribution, version_spec, for_py_version, to_folder, out) + LOGGER.debug("downloaded wheel %s", result.name) + return result + + +def _find_downloaded_wheel(distribution, version_spec, for_py_version, to_folder, out): + for line in out.splitlines(): + stripped_line = line.lstrip() + for marker in ("Saved ", "File was already downloaded "): + if stripped_line.startswith(marker): + return Wheel(Path(stripped_line[len(marker) :]).absolute()) + # if for some reason the output does not match fallback to the latest version with that spec + return find_compatible_in_house(distribution, version_spec, for_py_version, to_folder) + + +def find_compatible_in_house(distribution, version_spec, for_py_version, in_folder): + wheels = discover_wheels(in_folder, distribution, None, for_py_version) + start, end = 0, len(wheels) + if version_spec is not None and version_spec: + if version_spec.startswith("<"): + from_pos, op = 1, lt + elif version_spec.startswith("=="): + from_pos, op = 2, eq + else: + raise ValueError(version_spec) + version = Wheel.as_version_tuple(version_spec[from_pos:]) + start = next((at for at, w in enumerate(wheels) if op(w.version_tuple, version)), len(wheels)) + + return None if start == end else wheels[start] + + +def pip_wheel_env_run(search_dirs, app_data, env): + env = env.copy() + env.update({"PIP_USE_WHEEL": "1", "PIP_USER": "0", "PIP_NO_INPUT": "1"}) + wheel = get_wheel( + distribution="pip", + version=None, + for_py_version=f"{sys.version_info.major}.{sys.version_info.minor}", + search_dirs=search_dirs, + download=False, + app_data=app_data, + do_periodic_update=False, + env=env, + ) + if wheel is None: + msg = "could not find the embedded pip" + raise RuntimeError(msg) + env["PYTHONPATH"] = str(wheel.path) + return env + + +__all__ = [ + "download_wheel", + "get_wheel", + "pip_wheel_env_run", +] diff --git a/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/bundle.py b/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/bundle.py new file mode 100644 index 0000000000000000000000000000000000000000..523e45ca2a082f5913c4bef681ddcdb2fd0c360f --- /dev/null +++ b/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/bundle.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +from virtualenv.seed.wheels.embed import get_embed_wheel + +from .periodic_update import periodic_update +from .util import Version, Wheel, discover_wheels + + +def from_bundle(distribution, version, for_py_version, search_dirs, app_data, do_periodic_update, env): # noqa: PLR0913 + """Load the bundled wheel to a cache directory.""" + of_version = Version.of_version(version) + wheel = load_embed_wheel(app_data, distribution, for_py_version, of_version) + + if version != Version.embed: + # 2. check if we have upgraded embed + if app_data.can_update: + per = do_periodic_update + wheel = periodic_update(distribution, of_version, for_py_version, wheel, search_dirs, app_data, per, env) + + # 3. acquire from extra search dir + found_wheel = from_dir(distribution, of_version, for_py_version, search_dirs) + if found_wheel is not None and (wheel is None or found_wheel.version_tuple > wheel.version_tuple): + wheel = found_wheel + return wheel + + +def load_embed_wheel(app_data, distribution, for_py_version, version): + wheel = get_embed_wheel(distribution, for_py_version) + if wheel is not None: + version_match = version == wheel.version + if version is None or version_match: + with app_data.ensure_extracted(wheel.path, lambda: app_data.house) as wheel_path: + wheel = Wheel(wheel_path) + else: # if version does not match ignore + wheel = None + return wheel + + +def from_dir(distribution, version, for_py_version, directories): + """Load a compatible wheel from a given folder.""" + for folder in directories: + for wheel in discover_wheels(folder, distribution, version, for_py_version): + return wheel + return None + + +__all__ = [ + "from_bundle", + "load_embed_wheel", +] diff --git a/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/embed/__init__.py b/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/embed/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..664ec191dc777a2d8db1cb7e506b191af5638561 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/embed/__init__.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +from pathlib import Path + +from virtualenv.seed.wheels.util import Wheel + +BUNDLE_FOLDER = Path(__file__).absolute().parent +BUNDLE_SUPPORT = { + "3.8": { + "pip": "pip-24.3.1-py3-none-any.whl", + "setuptools": "setuptools-75.3.0-py3-none-any.whl", + "wheel": "wheel-0.45.1-py3-none-any.whl", + }, + "3.9": { + "pip": "pip-24.3.1-py3-none-any.whl", + "setuptools": "setuptools-75.8.0-py3-none-any.whl", + "wheel": "wheel-0.45.1-py3-none-any.whl", + }, + "3.10": { + "pip": "pip-24.3.1-py3-none-any.whl", + "setuptools": "setuptools-75.8.0-py3-none-any.whl", + "wheel": "wheel-0.45.1-py3-none-any.whl", + }, + "3.11": { + "pip": "pip-24.3.1-py3-none-any.whl", + "setuptools": "setuptools-75.8.0-py3-none-any.whl", + "wheel": "wheel-0.45.1-py3-none-any.whl", + }, + "3.12": { + "pip": "pip-24.3.1-py3-none-any.whl", + "setuptools": "setuptools-75.8.0-py3-none-any.whl", + "wheel": "wheel-0.45.1-py3-none-any.whl", + }, + "3.13": { + "pip": "pip-24.3.1-py3-none-any.whl", + "setuptools": "setuptools-75.8.0-py3-none-any.whl", + "wheel": "wheel-0.45.1-py3-none-any.whl", + }, + "3.14": { + "pip": "pip-24.3.1-py3-none-any.whl", + "setuptools": "setuptools-75.8.0-py3-none-any.whl", + "wheel": "wheel-0.45.1-py3-none-any.whl", + }, +} +MAX = "3.8" + + +def get_embed_wheel(distribution, for_py_version): + path = BUNDLE_FOLDER / (BUNDLE_SUPPORT.get(for_py_version, {}) or BUNDLE_SUPPORT[MAX]).get(distribution) + return Wheel.from_path(path) + + +__all__ = [ + "BUNDLE_FOLDER", + "BUNDLE_SUPPORT", + "MAX", + "get_embed_wheel", +] diff --git a/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/embed/__pycache__/__init__.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/embed/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7eb5fc0ccbf67582f9393015c7be5a81866c3276 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/embed/__pycache__/__init__.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/embed/wheel-0.45.1-py3-none-any.whl b/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/embed/wheel-0.45.1-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..589308a21938f25bd1300dae8ffd7488a8df00ca Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/embed/wheel-0.45.1-py3-none-any.whl differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/periodic_update.py b/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/periodic_update.py new file mode 100644 index 0000000000000000000000000000000000000000..ac627b3bdf1683a8a9b0e82f7404678af99b0ae1 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/periodic_update.py @@ -0,0 +1,428 @@ +"""Periodically update bundled versions.""" + +from __future__ import annotations + +import json +import logging +import os +import ssl +import sys +from datetime import datetime, timedelta, timezone +from itertools import groupby +from pathlib import Path +from shutil import copy2 +from subprocess import DEVNULL, Popen +from textwrap import dedent +from threading import Thread +from urllib.error import URLError +from urllib.request import urlopen + +from virtualenv.app_data import AppDataDiskFolder +from virtualenv.seed.wheels.embed import BUNDLE_SUPPORT +from virtualenv.seed.wheels.util import Wheel +from virtualenv.util.subprocess import CREATE_NO_WINDOW + +LOGGER = logging.getLogger(__name__) +GRACE_PERIOD_CI = timedelta(hours=1) # prevent version switch in the middle of a CI run +GRACE_PERIOD_MINOR = timedelta(days=28) +UPDATE_PERIOD = timedelta(days=14) +UPDATE_ABORTED_DELAY = timedelta(hours=1) + + +def periodic_update( # noqa: PLR0913 + distribution, + of_version, + for_py_version, + wheel, + search_dirs, + app_data, + do_periodic_update, + env, +): + if do_periodic_update: + handle_auto_update(distribution, for_py_version, wheel, search_dirs, app_data, env) + + now = datetime.now(tz=timezone.utc) + + def _update_wheel(ver): + updated_wheel = Wheel(app_data.house / ver.filename) + LOGGER.debug("using %supdated wheel %s", "periodically " if updated_wheel else "", updated_wheel) + return updated_wheel + + u_log = UpdateLog.from_app_data(app_data, distribution, for_py_version) + if of_version is None: + for _, group in groupby(u_log.versions, key=lambda v: v.wheel.version_tuple[0:2]): + # use only latest patch version per minor, earlier assumed to be buggy + all_patches = list(group) + ignore_grace_period_minor = any(version for version in all_patches if version.use(now)) + for version in all_patches: + if wheel is not None and Path(version.filename).name == wheel.name: + return wheel + if version.use(now, ignore_grace_period_minor): + return _update_wheel(version) + else: + for version in u_log.versions: + if version.wheel.version == of_version: + return _update_wheel(version) + + return wheel + + +def handle_auto_update(distribution, for_py_version, wheel, search_dirs, app_data, env): # noqa: PLR0913 + embed_update_log = app_data.embed_update_log(distribution, for_py_version) + u_log = UpdateLog.from_dict(embed_update_log.read()) + if u_log.needs_update: + u_log.periodic = True + u_log.started = datetime.now(tz=timezone.utc) + embed_update_log.write(u_log.to_dict()) + trigger_update(distribution, for_py_version, wheel, search_dirs, app_data, periodic=True, env=env) + + +def add_wheel_to_update_log(wheel, for_py_version, app_data): + embed_update_log = app_data.embed_update_log(wheel.distribution, for_py_version) + LOGGER.debug("adding %s information to %s", wheel.name, embed_update_log.file) + u_log = UpdateLog.from_dict(embed_update_log.read()) + if any(version.filename == wheel.name for version in u_log.versions): + LOGGER.warning("%s already present in %s", wheel.name, embed_update_log.file) + return + # we don't need a release date for sources other than "periodic" + version = NewVersion(wheel.name, datetime.now(tz=timezone.utc), None, "download") + u_log.versions.append(version) # always write at the end for proper updates + embed_update_log.write(u_log.to_dict()) + + +DATETIME_FMT = "%Y-%m-%dT%H:%M:%S.%fZ" + + +def dump_datetime(value): + return None if value is None else value.strftime(DATETIME_FMT) + + +def load_datetime(value): + return None if value is None else datetime.strptime(value, DATETIME_FMT).replace(tzinfo=timezone.utc) + + +class NewVersion: # noqa: PLW1641 + def __init__(self, filename, found_date, release_date, source) -> None: + self.filename = filename + self.found_date = found_date + self.release_date = release_date + self.source = source + + @classmethod + def from_dict(cls, dictionary): + return cls( + filename=dictionary["filename"], + found_date=load_datetime(dictionary["found_date"]), + release_date=load_datetime(dictionary["release_date"]), + source=dictionary["source"], + ) + + def to_dict(self): + return { + "filename": self.filename, + "release_date": dump_datetime(self.release_date), + "found_date": dump_datetime(self.found_date), + "source": self.source, + } + + def use(self, now, ignore_grace_period_minor=False, ignore_grace_period_ci=False): # noqa: FBT002 + if self.source == "manual": + return True + if self.source == "periodic" and (self.found_date < now - GRACE_PERIOD_CI or ignore_grace_period_ci): + if not ignore_grace_period_minor: + compare_from = self.release_date or self.found_date + return now - compare_from >= GRACE_PERIOD_MINOR + return True + return False + + def __repr__(self) -> str: + return ( + f"{self.__class__.__name__}(filename={self.filename}), found_date={self.found_date}, " + f"release_date={self.release_date}, source={self.source})" + ) + + def __eq__(self, other): + return type(self) == type(other) and all( # noqa: E721 + getattr(self, k) == getattr(other, k) for k in ["filename", "release_date", "found_date", "source"] + ) + + def __ne__(self, other): + return not (self == other) + + @property + def wheel(self): + return Wheel(Path(self.filename)) + + +class UpdateLog: + def __init__(self, started, completed, versions, periodic) -> None: + self.started = started + self.completed = completed + self.versions = versions + self.periodic = periodic + + @classmethod + def from_dict(cls, dictionary): + if dictionary is None: + dictionary = {} + return cls( + load_datetime(dictionary.get("started")), + load_datetime(dictionary.get("completed")), + [NewVersion.from_dict(v) for v in dictionary.get("versions", [])], + dictionary.get("periodic"), + ) + + @classmethod + def from_app_data(cls, app_data, distribution, for_py_version): + raw_json = app_data.embed_update_log(distribution, for_py_version).read() + return cls.from_dict(raw_json) + + def to_dict(self): + return { + "started": dump_datetime(self.started), + "completed": dump_datetime(self.completed), + "periodic": self.periodic, + "versions": [r.to_dict() for r in self.versions], + } + + @property + def needs_update(self): + now = datetime.now(tz=timezone.utc) + if self.completed is None: # never completed + return self._check_start(now) + if now - self.completed <= UPDATE_PERIOD: + return False + return self._check_start(now) + + def _check_start(self, now): + return self.started is None or now - self.started > UPDATE_ABORTED_DELAY + + +def trigger_update(distribution, for_py_version, wheel, search_dirs, app_data, env, periodic): # noqa: PLR0913 + wheel_path = None if wheel is None else str(wheel.path) + cmd = [ + sys.executable, + "-c", + dedent( + """ + from virtualenv.report import setup_report, MAX_LEVEL + from virtualenv.seed.wheels.periodic_update import do_update + setup_report(MAX_LEVEL, show_pid=True) + do_update({!r}, {!r}, {!r}, {!r}, {!r}, {!r}) + """, + ) + .strip() + .format(distribution, for_py_version, wheel_path, str(app_data), [str(p) for p in search_dirs], periodic), + ] + debug = env.get("_VIRTUALENV_PERIODIC_UPDATE_INLINE") == "1" + pipe = None if debug else DEVNULL + kwargs = {"stdout": pipe, "stderr": pipe} + if not debug and sys.platform == "win32": + kwargs["creationflags"] = CREATE_NO_WINDOW + process = Popen(cmd, **kwargs) + LOGGER.info( + "triggered periodic upgrade of %s%s (for python %s) via background process having PID %d", + distribution, + "" if wheel is None else f"=={wheel.version}", + for_py_version, + process.pid, + ) + if debug: + process.communicate() # on purpose not called to make it a background process + else: + # set the returncode here -> no ResourceWarning on main process exit if the subprocess still runs + process.returncode = 0 + + +def do_update(distribution, for_py_version, embed_filename, app_data, search_dirs, periodic): # noqa: PLR0913 + versions = None + try: + versions = _run_do_update(app_data, distribution, embed_filename, for_py_version, periodic, search_dirs) + finally: + LOGGER.debug("done %s %s with %s", distribution, for_py_version, versions) + return versions + + +def _run_do_update( # noqa: C901, PLR0913 + app_data, + distribution, + embed_filename, + for_py_version, + periodic, + search_dirs, +): + from virtualenv.seed.wheels import acquire # noqa: PLC0415 + + wheel_filename = None if embed_filename is None else Path(embed_filename) + embed_version = None if wheel_filename is None else Wheel(wheel_filename).version_tuple + app_data = AppDataDiskFolder(app_data) if isinstance(app_data, str) else app_data + search_dirs = [Path(p) if isinstance(p, str) else p for p in search_dirs] + wheelhouse = app_data.house + embed_update_log = app_data.embed_update_log(distribution, for_py_version) + u_log = UpdateLog.from_dict(embed_update_log.read()) + now = datetime.now(tz=timezone.utc) + + update_versions, other_versions = [], [] + for version in u_log.versions: + if version.source in {"periodic", "manual"}: + update_versions.append(version) + else: + other_versions.append(version) + + if periodic: + source = "periodic" + else: + source = "manual" + # mark the most recent one as source "manual" + if update_versions: + update_versions[0].source = source + + if wheel_filename is not None: + dest = wheelhouse / wheel_filename.name + if not dest.exists(): + copy2(str(wheel_filename), str(wheelhouse)) + last, last_version, versions, filenames = None, None, [], set() + while last is None or not last.use(now, ignore_grace_period_ci=True): + download_time = datetime.now(tz=timezone.utc) + dest = acquire.download_wheel( + distribution=distribution, + version_spec=None if last_version is None else f"<{last_version}", + for_py_version=for_py_version, + search_dirs=search_dirs, + app_data=app_data, + to_folder=wheelhouse, + env=os.environ, + ) + if dest is None or (update_versions and update_versions[0].filename == dest.name): + break + release_date = release_date_for_wheel_path(dest.path) + last = NewVersion(filename=dest.path.name, release_date=release_date, found_date=download_time, source=source) + LOGGER.info("detected %s in %s", last, datetime.now(tz=timezone.utc) - download_time) + versions.append(last) + filenames.add(last.filename) + last_wheel = last.wheel + last_version = last_wheel.version + if embed_version is not None and embed_version >= last_wheel.version_tuple: + break # stop download if we reach the embed version + u_log.periodic = periodic + if not u_log.periodic: + u_log.started = now + # update other_versions by removing version we just found + other_versions = [version for version in other_versions if version.filename not in filenames] + u_log.versions = versions + update_versions + other_versions + u_log.completed = datetime.now(tz=timezone.utc) + embed_update_log.write(u_log.to_dict()) + return versions + + +def release_date_for_wheel_path(dest): + wheel = Wheel(dest) + # the most accurate is to ask PyPi - e.g. https://pypi.org/pypi/pip/json, + # see https://warehouse.pypa.io/api-reference/json/ for more details + content = _pypi_get_distribution_info_cached(wheel.distribution) + if content is not None: + try: + upload_time = content["releases"][wheel.version][0]["upload_time"] + return datetime.strptime(upload_time, "%Y-%m-%dT%H:%M:%S").replace(tzinfo=timezone.utc) + except Exception as exception: # noqa: BLE001 + LOGGER.error("could not load release date %s because %r", content, exception) # noqa: TRY400 + return None + + +def _request_context(): + yield None + # fallback to non verified HTTPS (the information we request is not sensitive, so fallback) + yield ssl._create_unverified_context() # noqa: S323, SLF001 + + +_PYPI_CACHE = {} + + +def _pypi_get_distribution_info_cached(distribution): + if distribution not in _PYPI_CACHE: + _PYPI_CACHE[distribution] = _pypi_get_distribution_info(distribution) + return _PYPI_CACHE[distribution] + + +def _pypi_get_distribution_info(distribution): + content, url = None, f"https://pypi.org/pypi/{distribution}/json" + try: + for context in _request_context(): + try: + with urlopen(url, context=context) as file_handler: # noqa: S310 + content = json.load(file_handler) + break + except URLError as exception: + LOGGER.error("failed to access %s because %r", url, exception) # noqa: TRY400 + except Exception as exception: # noqa: BLE001 + LOGGER.error("failed to access %s because %r", url, exception) # noqa: TRY400 + return content + + +def manual_upgrade(app_data, env): + threads = [] + + for for_py_version, distribution_to_package in BUNDLE_SUPPORT.items(): + # load extra search dir for the given for_py + for distribution in distribution_to_package: + thread = Thread(target=_run_manual_upgrade, args=(app_data, distribution, for_py_version, env)) + thread.start() + threads.append(thread) + + for thread in threads: + thread.join() + + +def _run_manual_upgrade(app_data, distribution, for_py_version, env): + start = datetime.now(tz=timezone.utc) + from .bundle import from_bundle # noqa: PLC0415 + + current = from_bundle( + distribution=distribution, + version=None, + for_py_version=for_py_version, + search_dirs=[], + app_data=app_data, + do_periodic_update=False, + env=env, + ) + LOGGER.warning( + "upgrade %s for python %s with current %s", + distribution, + for_py_version, + "" if current is None else current.name, + ) + versions = do_update( + distribution=distribution, + for_py_version=for_py_version, + embed_filename=current.path, + app_data=app_data, + search_dirs=[], + periodic=False, + ) + + args = [ + distribution, + for_py_version, + datetime.now(tz=timezone.utc) - start, + ] + if versions: + args.append("\n".join(f"\t{v}" for v in versions)) + ver_update = "new entries found:\n%s" if versions else "no new versions found" + msg = f"upgraded %s for python %s in %s {ver_update}" + LOGGER.warning(msg, *args) + + +__all__ = [ + "NewVersion", + "UpdateLog", + "add_wheel_to_update_log", + "do_update", + "dump_datetime", + "load_datetime", + "manual_upgrade", + "periodic_update", + "release_date_for_wheel_path", + "trigger_update", +] diff --git a/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/util.py b/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/util.py new file mode 100644 index 0000000000000000000000000000000000000000..2bc01ae27e28f9884764768c59e73a1d3727703e --- /dev/null +++ b/.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/util.py @@ -0,0 +1,121 @@ +from __future__ import annotations + +from operator import attrgetter +from zipfile import ZipFile + + +class Wheel: + def __init__(self, path) -> None: + # https://www.python.org/dev/peps/pep-0427/#file-name-convention + # The wheel filename is {distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-{platform tag}.whl + self.path = path + self._parts = path.stem.split("-") + + @classmethod + def from_path(cls, path): + if path is not None and path.suffix == ".whl" and len(path.stem.split("-")) >= 5: # noqa: PLR2004 + return cls(path) + return None + + @property + def distribution(self): + return self._parts[0] + + @property + def version(self): + return self._parts[1] + + @property + def version_tuple(self): + return self.as_version_tuple(self.version) + + @staticmethod + def as_version_tuple(version): + result = [] + for part in version.split(".")[0:3]: + try: + result.append(int(part)) + except ValueError: # noqa: PERF203 + break + if not result: + raise ValueError(version) + return tuple(result) + + @property + def name(self): + return self.path.name + + def support_py(self, py_version): + name = f"{'-'.join(self.path.stem.split('-')[0:2])}.dist-info/METADATA" + with ZipFile(str(self.path), "r") as zip_file: + metadata = zip_file.read(name).decode("utf-8") + marker = "Requires-Python:" + requires = next((i[len(marker) :] for i in metadata.splitlines() if i.startswith(marker)), None) + if requires is None: # if it does not specify a python requires the assumption is compatible + return True + py_version_int = tuple(int(i) for i in py_version.split(".")) + for require in (i.strip() for i in requires.split(",")): + # https://www.python.org/dev/peps/pep-0345/#version-specifiers + for operator, check in [ + ("!=", lambda v: py_version_int != v), + ("==", lambda v: py_version_int == v), + ("<=", lambda v: py_version_int <= v), + (">=", lambda v: py_version_int >= v), + ("<", lambda v: py_version_int < v), + (">", lambda v: py_version_int > v), + ]: + if require.startswith(operator): + ver_str = require[len(operator) :].strip() + version = tuple((int(i) if i != "*" else None) for i in ver_str.split("."))[0:2] + if not check(version): + return False + break + return True + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.path})" + + def __str__(self) -> str: + return str(self.path) + + +def discover_wheels(from_folder, distribution, version, for_py_version): + wheels = [] + for filename in from_folder.iterdir(): + wheel = Wheel.from_path(filename) + if ( + wheel + and wheel.distribution == distribution + and (version is None or wheel.version == version) + and wheel.support_py(for_py_version) + ): + wheels.append(wheel) + return sorted(wheels, key=attrgetter("version_tuple", "distribution"), reverse=True) + + +class Version: + #: the version bundled with virtualenv + bundle = "bundle" + embed = "embed" + #: custom version handlers + non_version = (bundle, embed) + + @staticmethod + def of_version(value): + return None if value in Version.non_version else value + + @staticmethod + def as_pip_req(distribution, version): + return f"{distribution}{Version.as_version_spec(version)}" + + @staticmethod + def as_version_spec(version): + of_version = Version.of_version(version) + return "" if of_version is None else f"=={of_version}" + + +__all__ = [ + "Version", + "Wheel", + "discover_wheels", +] diff --git a/.venv/lib/python3.11/site-packages/virtualenv/util/__init__.py b/.venv/lib/python3.11/site-packages/virtualenv/util/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venv/lib/python3.11/site-packages/virtualenv/util/__pycache__/__init__.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/util/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fd74ce806ca842d95d5ae9addae36fc87b88221b Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/util/__pycache__/__init__.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/util/__pycache__/error.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/util/__pycache__/error.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..252df302b6cdd5f6c4cc12cb9b4e97422869fa53 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/util/__pycache__/error.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/util/__pycache__/lock.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/util/__pycache__/lock.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f33418224927a5449b88e5e187a22b68dc724060 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/util/__pycache__/lock.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/util/__pycache__/zipapp.cpython-311.pyc b/.venv/lib/python3.11/site-packages/virtualenv/util/__pycache__/zipapp.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..94868a2383d194b2547b517a4a64f714e44b9bc3 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/virtualenv/util/__pycache__/zipapp.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/virtualenv/util/error.py b/.venv/lib/python3.11/site-packages/virtualenv/util/error.py new file mode 100644 index 0000000000000000000000000000000000000000..a317ddc1823179badb916e1add892a162be15a37 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/virtualenv/util/error.py @@ -0,0 +1,14 @@ +"""Errors.""" + +from __future__ import annotations + + +class ProcessCallFailedError(RuntimeError): + """Failed a process call.""" + + def __init__(self, code, out, err, cmd) -> None: + super().__init__(code, out, err, cmd) + self.code = code + self.out = out + self.err = err + self.cmd = cmd diff --git a/.venv/lib/python3.11/site-packages/virtualenv/util/lock.py b/.venv/lib/python3.11/site-packages/virtualenv/util/lock.py new file mode 100644 index 0000000000000000000000000000000000000000..b8c9cf833848b6d75fff1297ebdc76a13a01c8d7 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/virtualenv/util/lock.py @@ -0,0 +1,168 @@ +"""holds locking functionality that works across processes.""" + +from __future__ import annotations + +import logging +import os +from abc import ABC, abstractmethod +from contextlib import contextmanager, suppress +from pathlib import Path +from threading import Lock, RLock + +from filelock import FileLock, Timeout + +LOGGER = logging.getLogger(__name__) + + +class _CountedFileLock(FileLock): + def __init__(self, lock_file) -> None: + parent = os.path.dirname(lock_file) + if not os.path.isdir(parent): + with suppress(OSError): + os.makedirs(parent) + + super().__init__(lock_file) + self.count = 0 + self.thread_safe = RLock() + + def acquire(self, timeout=None, poll_interval=0.05): + if not self.thread_safe.acquire(timeout=-1 if timeout is None else timeout): + raise Timeout(self.lock_file) + if self.count == 0: + super().acquire(timeout, poll_interval) + self.count += 1 + + def release(self, force=False): # noqa: FBT002 + with self.thread_safe: + if self.count > 0: + self.thread_safe.release() + if self.count == 1: + super().release(force=force) + self.count = max(self.count - 1, 0) + + +_lock_store = {} +_store_lock = Lock() + + +class PathLockBase(ABC): + def __init__(self, folder) -> None: + path = Path(folder) + self.path = path.resolve() if path.exists() else path + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.path})" + + def __truediv__(self, other): + return type(self)(self.path / other) + + @abstractmethod + def __enter__(self): + raise NotImplementedError + + @abstractmethod + def __exit__(self, exc_type, exc_val, exc_tb): + raise NotImplementedError + + @abstractmethod + @contextmanager + def lock_for_key(self, name, no_block=False): # noqa: FBT002 + raise NotImplementedError + + @abstractmethod + @contextmanager + def non_reentrant_lock_for_key(self, name): + raise NotImplementedError + + +class ReentrantFileLock(PathLockBase): + def __init__(self, folder) -> None: + super().__init__(folder) + self._lock = None + + def _create_lock(self, name=""): + lock_file = str(self.path / f"{name}.lock") + with _store_lock: + if lock_file not in _lock_store: + _lock_store[lock_file] = _CountedFileLock(lock_file) + return _lock_store[lock_file] + + @staticmethod + def _del_lock(lock): + if lock is not None: + with _store_lock, lock.thread_safe: + if lock.count == 0: + _lock_store.pop(lock.lock_file, None) + + def __del__(self) -> None: + self._del_lock(self._lock) + + def __enter__(self): + self._lock = self._create_lock() + self._lock_file(self._lock) + + def __exit__(self, exc_type, exc_val, exc_tb): + self._release(self._lock) + self._del_lock(self._lock) + self._lock = None + + def _lock_file(self, lock, no_block=False): # noqa: FBT002 + # multiple processes might be trying to get a first lock... so we cannot check if this directory exist without + # a lock, but that lock might then become expensive, and it's not clear where that lock should live. + # Instead here we just ignore if we fail to create the directory. + with suppress(OSError): + os.makedirs(str(self.path)) + + try: + lock.acquire(0.0001) + except Timeout: + if no_block: + raise + LOGGER.debug("lock file %s present, will block until released", lock.lock_file) + lock.release() # release the acquire try from above + lock.acquire() + + @staticmethod + def _release(lock): + lock.release() + + @contextmanager + def lock_for_key(self, name, no_block=False): # noqa: FBT002 + lock = self._create_lock(name) + try: + try: + self._lock_file(lock, no_block) + yield + finally: + self._release(lock) + finally: + self._del_lock(lock) + lock = None + + @contextmanager + def non_reentrant_lock_for_key(self, name): + with _CountedFileLock(str(self.path / f"{name}.lock")): + yield + + +class NoOpFileLock(PathLockBase): + def __enter__(self): + raise NotImplementedError + + def __exit__(self, exc_type, exc_val, exc_tb): + raise NotImplementedError + + @contextmanager + def lock_for_key(self, name, no_block=False): # noqa: ARG002, FBT002 + yield + + @contextmanager + def non_reentrant_lock_for_key(self, name): # noqa: ARG002 + yield + + +__all__ = [ + "NoOpFileLock", + "ReentrantFileLock", + "Timeout", +] diff --git a/.venv/lib/python3.11/site-packages/virtualenv/util/zipapp.py b/.venv/lib/python3.11/site-packages/virtualenv/util/zipapp.py new file mode 100644 index 0000000000000000000000000000000000000000..764c5c277759f61c87c7445318e12e658ea525dc --- /dev/null +++ b/.venv/lib/python3.11/site-packages/virtualenv/util/zipapp.py @@ -0,0 +1,43 @@ +from __future__ import annotations # noqa: A005 + +import logging +import os +import zipfile + +from virtualenv.info import IS_WIN, ROOT + +LOGGER = logging.getLogger(__name__) + + +def read(full_path): + sub_file = _get_path_within_zip(full_path) + with zipfile.ZipFile(ROOT, "r") as zip_file, zip_file.open(sub_file) as file_handler: + return file_handler.read().decode("utf-8") + + +def extract(full_path, dest): + LOGGER.debug("extract %s to %s", full_path, dest) + sub_file = _get_path_within_zip(full_path) + with zipfile.ZipFile(ROOT, "r") as zip_file: + info = zip_file.getinfo(sub_file) + info.filename = dest.name + zip_file.extract(info, str(dest.parent)) + + +def _get_path_within_zip(full_path): + full_path = os.path.realpath(os.path.abspath(str(full_path))) + prefix = f"{ROOT}{os.sep}" + if not full_path.startswith(prefix): + msg = f"full_path={full_path} should start with prefix={prefix}." + raise RuntimeError(msg) + sub_file = full_path[len(prefix) :] + if IS_WIN: + # paths are always UNIX separators, even on Windows, though __file__ still follows platform default + sub_file = sub_file.replace(os.sep, "/") + return sub_file + + +__all__ = [ + "extract", + "read", +]