Tipragot
628be439b8
Cela permet de ne pas avoir de problèmes de compatibilité car python est dans le git.
103 lines
4.2 KiB
Python
103 lines
4.2 KiB
Python
"""Patches that are applied at runtime to the virtual environment."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
from contextlib import suppress
|
|
|
|
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:
|
|
# initialize lock[0] lazily
|
|
if len(self.lock) == 0:
|
|
import threading
|
|
|
|
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
|
|
from importlib.util import find_spec
|
|
|
|
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:
|
|
with suppress(AttributeError): # C-Extension loaders are r/o such as zipimporter with <3.7
|
|
setattr(spec.loader, func_name, partial(func, old))
|
|
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())
|