1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
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
class CPython3(CPython, Python3Supports, metaclass=abc.ABCMeta):
"""CPython 3 or later"""
class CPython3Posix(CPythonPosix, CPython3):
@classmethod
def can_describe(cls, interpreter):
return is_mac_os_framework(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):
""" """
@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
@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!
"""
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",
]
|