-
-
Notifications
You must be signed in to change notification settings - Fork 1k
/
builtin.py
186 lines (158 loc) · 6.23 KB
/
builtin.py
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
from __future__ import absolute_import, unicode_literals
import logging
import os
import sys
from virtualenv.info import IS_WIN
from virtualenv.util.six import ensure_str, ensure_text
from .discover import Discover
from .py_info import PythonInfo
from .py_spec import PythonSpec
class Builtin(Discover):
def __init__(self, options):
super(Builtin, self).__init__(options)
self.python_spec = options.python if options.python else [sys.executable]
self.app_data = options.app_data
self.try_first_with = options.try_first_with
@classmethod
def add_parser_arguments(cls, parser):
parser.add_argument(
"-p",
"--python",
dest="python",
metavar="py",
type=str,
action="append",
default=[],
help="interpreter based on what to create environment (path/identifier) "
"- by default use the interpreter where the tool is installed - first found wins",
)
parser.add_argument(
"--try-first-with",
dest="try_first_with",
metavar="py_exe",
type=str,
action="append",
default=[],
help="try first these interpreters before starting the discovery",
)
def run(self):
for python_spec in self.python_spec:
result = get_interpreter(python_spec, self.try_first_with, self.app_data, self._env)
if result is not None:
return result
return None
def __repr__(self):
return ensure_str(self.__unicode__())
def __unicode__(self):
spec = self.python_spec[0] if len(self.python_spec) == 1 else self.python_spec
return "{} discover of python_spec={!r}".format(self.__class__.__name__, spec)
def get_interpreter(key, try_first_with, app_data=None, env=None):
spec = PythonSpec.from_string_spec(key)
logging.info("find interpreter for spec %r", spec)
proposed_paths = set()
env = os.environ if env is None else env
for interpreter, impl_must_match in propose_interpreters(spec, try_first_with, app_data, env):
key = interpreter.system_executable, impl_must_match
if key in proposed_paths:
continue
logging.info("proposed %s", interpreter)
if interpreter.satisfies(spec, impl_must_match):
logging.debug("accepted %s", interpreter)
return interpreter
proposed_paths.add(key)
def propose_interpreters(spec, try_first_with, app_data, env=None):
# 0. try with first
env = os.environ if env is None else env
for py_exe in try_first_with:
path = os.path.abspath(py_exe)
try:
os.lstat(path) # Windows Store Python does not work with os.path.exists, but does for os.lstat
except OSError:
pass
else:
yield PythonInfo.from_exe(os.path.abspath(path), app_data, env=env), True
# 1. if it's a path and exists
if spec.path is not None:
try:
os.lstat(spec.path) # Windows Store Python does not work with os.path.exists, but does for os.lstat
except OSError:
if spec.is_abs:
raise
else:
yield PythonInfo.from_exe(os.path.abspath(spec.path), app_data, env=env), True
if spec.is_abs:
return
else:
# 2. otherwise try with the current
yield PythonInfo.current_system(app_data), True
# 3. otherwise fallback to platform default logic
if IS_WIN:
from .windows import propose_interpreters
for interpreter in propose_interpreters(spec, app_data, env):
yield interpreter, True
# finally just find on path, the path order matters (as the candidates are less easy to control by end user)
paths = get_paths(env)
tested_exes = set()
for pos, path in enumerate(paths):
path = ensure_text(path)
logging.debug(LazyPathDump(pos, path, env))
for candidate, match in possible_specs(spec):
found = check_path(candidate, path)
if found is not None:
exe = os.path.abspath(found)
if exe not in tested_exes:
tested_exes.add(exe)
interpreter = PathPythonInfo.from_exe(exe, app_data, raise_on_error=False, env=env)
if interpreter is not None:
yield interpreter, match
def get_paths(env):
path = env.get(str("PATH"), None)
if path is None:
try:
path = os.confstr("CS_PATH")
except (AttributeError, ValueError):
path = os.defpath
if not path:
paths = []
else:
paths = [p for p in path.split(os.pathsep) if os.path.exists(p)]
return paths
class LazyPathDump(object):
def __init__(self, pos, path, env):
self.pos = pos
self.path = path
self.env = env
def __repr__(self):
return ensure_str(self.__unicode__())
def __unicode__(self):
content = "discover PATH[{}]={}".format(self.pos, self.path)
if self.env.get(str("_VIRTUALENV_DEBUG")): # this is the over the board debug
content += " with =>"
for file_name in os.listdir(self.path):
try:
file_path = os.path.join(self.path, file_name)
if os.path.isdir(file_path) or not os.access(file_path, os.X_OK):
continue
except OSError:
pass
content += " "
content += file_name
return content
def check_path(candidate, path):
_, ext = os.path.splitext(candidate)
if sys.platform == "win32" and ext != ".exe":
candidate = candidate + ".exe"
if os.path.isfile(candidate):
return candidate
candidate = os.path.join(path, candidate)
if os.path.isfile(candidate):
return candidate
return None
def possible_specs(spec):
# 4. then maybe it's something exact on PATH - if it was direct lookup implementation no longer counts
yield spec.str_spec, False
# 5. or from the spec we can deduce a name on path that matches
for exe, match in spec.generate_names():
yield exe, match
class PathPythonInfo(PythonInfo):
""""""