_virtualenv.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. """Patches that are applied at runtime to the virtual environment"""
  2. # -*- coding: utf-8 -*-
  3. import os
  4. import sys
  5. VIRTUALENV_PATCH_FILE = os.path.join(__file__)
  6. def patch_dist(dist):
  7. """
  8. Distutils allows user to configure some arguments via a configuration file:
  9. https://docs.python.org/3/install/index.html#distutils-configuration-files
  10. Some of this arguments though don't make sense in context of the virtual environment files, let's fix them up.
  11. """
  12. # we cannot allow some install config as that would get packages installed outside of the virtual environment
  13. old_parse_config_files = dist.Distribution.parse_config_files
  14. def parse_config_files(self, *args, **kwargs):
  15. result = old_parse_config_files(self, *args, **kwargs)
  16. install = self.get_option_dict("install")
  17. if "prefix" in install: # the prefix governs where to install the libraries
  18. install["prefix"] = VIRTUALENV_PATCH_FILE, os.path.abspath(sys.prefix)
  19. for base in ("purelib", "platlib", "headers", "scripts", "data"):
  20. key = "install_{}".format(base)
  21. if key in install: # do not allow global configs to hijack venv paths
  22. install.pop(key, None)
  23. return result
  24. dist.Distribution.parse_config_files = parse_config_files
  25. # Import hook that patches some modules to ignore configuration values that break package installation in case
  26. # of virtual environments.
  27. _DISTUTILS_PATCH = "distutils.dist", "setuptools.dist"
  28. if sys.version_info > (3, 4):
  29. # https://docs.python.org/3/library/importlib.html#setting-up-an-importer
  30. class _Finder:
  31. """A meta path finder that allows patching the imported distutils modules"""
  32. fullname = None
  33. # lock[0] is threading.Lock(), but initialized lazily to avoid importing threading very early at startup,
  34. # because there are gevent-based applications that need to be first to import threading by themselves.
  35. # See https://github.com/pypa/virtualenv/issues/1895 for details.
  36. lock = []
  37. def find_spec(self, fullname, path, target=None):
  38. if fullname in _DISTUTILS_PATCH and self.fullname is None:
  39. # initialize lock[0] lazily
  40. if len(self.lock) == 0:
  41. import threading
  42. lock = threading.Lock()
  43. # there is possibility that two threads T1 and T2 are simultaneously running into find_spec,
  44. # observing .lock as empty, and further going into hereby initialization. However due to the GIL,
  45. # list.append() operation is atomic and this way only one of the threads will "win" to put the lock
  46. # - that every thread will use - into .lock[0].
  47. # https://docs.python.org/3/faq/library.html#what-kinds-of-global-value-mutation-are-thread-safe
  48. self.lock.append(lock)
  49. from functools import partial
  50. from importlib.util import find_spec
  51. with self.lock[0]:
  52. self.fullname = fullname
  53. try:
  54. spec = find_spec(fullname, path)
  55. if spec is not None:
  56. # https://www.python.org/dev/peps/pep-0451/#how-loading-will-work
  57. is_new_api = hasattr(spec.loader, "exec_module")
  58. func_name = "exec_module" if is_new_api else "load_module"
  59. old = getattr(spec.loader, func_name)
  60. func = self.exec_module if is_new_api else self.load_module
  61. if old is not func:
  62. try:
  63. setattr(spec.loader, func_name, partial(func, old))
  64. except AttributeError:
  65. pass # C-Extension loaders are r/o such as zipimporter with <python 3.7
  66. return spec
  67. finally:
  68. self.fullname = None
  69. @staticmethod
  70. def exec_module(old, module):
  71. old(module)
  72. if module.__name__ in _DISTUTILS_PATCH:
  73. patch_dist(module)
  74. @staticmethod
  75. def load_module(old, name):
  76. module = old(name)
  77. if module.__name__ in _DISTUTILS_PATCH:
  78. patch_dist(module)
  79. return module
  80. sys.meta_path.insert(0, _Finder())
  81. else:
  82. # https://www.python.org/dev/peps/pep-0302/
  83. from imp import find_module
  84. from pkgutil import ImpImporter, ImpLoader
  85. class _VirtualenvImporter(object, ImpImporter):
  86. def __init__(self, path=None):
  87. object.__init__(self)
  88. ImpImporter.__init__(self, path)
  89. def find_module(self, fullname, path=None):
  90. if fullname in _DISTUTILS_PATCH:
  91. try:
  92. return _VirtualenvLoader(fullname, *find_module(fullname.split(".")[-1], path))
  93. except ImportError:
  94. pass
  95. return None
  96. class _VirtualenvLoader(object, ImpLoader):
  97. def __init__(self, fullname, file, filename, etc):
  98. object.__init__(self)
  99. ImpLoader.__init__(self, fullname, file, filename, etc)
  100. def load_module(self, fullname):
  101. module = super(_VirtualenvLoader, self).load_module(fullname)
  102. patch_dist(module)
  103. module.__loader__ = None # distlib fallback
  104. return module
  105. sys.meta_path.append(_VirtualenvImporter())