9
9
import re
10
10
import sys
11
11
from contextlib import closing
12
+ from textwrap import dedent
12
13
14
+ from pex import third_party
13
15
from pex .common import AtomicDirectory , is_exe , safe_mkdir
14
16
from pex .compatibility import get_stdout_bytes_buffer
15
17
from pex .interpreter import PythonInterpreter
19
21
from pex .util import named_temporary_file
20
22
21
23
if TYPE_CHECKING :
22
- from typing import Iterator , Optional
24
+ import attr # vendor:skip
25
+ from typing import Iterator , Optional , Union
26
+ else :
27
+ from pex .third_party import attr
23
28
24
29
_MIN_PIP_PYTHON_VERSION = (2 , 7 , 9 )
25
30
@@ -71,7 +76,32 @@ def _is_python_script(executable):
71
76
)
72
77
73
78
79
+ class InvalidVirtualenvError (Exception ):
80
+ pass
81
+
82
+
83
+ @attr .s (frozen = True )
84
+ class DistributionInfo (object ):
85
+ project_name = attr .ib () # type: str
86
+ version = attr .ib () # type: str
87
+ sys_path_entry = attr .ib () # type: str
88
+
89
+
74
90
class Virtualenv (object ):
91
+ @classmethod
92
+ def enclosing (cls , python ):
93
+ # type: (Union[str, PythonInterpreter]) -> Optional[Virtualenv]
94
+ interpreter = (
95
+ python
96
+ if isinstance (python , PythonInterpreter )
97
+ else PythonInterpreter .from_binary (python )
98
+ )
99
+ if not interpreter .is_venv :
100
+ return None
101
+ return cls (
102
+ venv_dir = interpreter .prefix , python_exe_name = os .path .basename (interpreter .binary )
103
+ )
104
+
75
105
@classmethod
76
106
def create (
77
107
cls ,
@@ -163,9 +193,16 @@ def __init__(
163
193
self ._venv_dir = venv_dir
164
194
self ._custom_prompt = custom_prompt
165
195
self ._bin_dir = os .path .join (venv_dir , "bin" )
166
- self ._interpreter = PythonInterpreter .from_binary (
167
- os .path .join (self ._bin_dir , python_exe_name )
168
- )
196
+ python_exe_path = os .path .join (self ._bin_dir , python_exe_name )
197
+ try :
198
+ self ._interpreter = PythonInterpreter .from_binary (python_exe_path )
199
+ except PythonInterpreter .InterpreterNotFound as e :
200
+ raise InvalidVirtualenvError (
201
+ "The virtualenv at {venv_dir} is not valid. Failed to load an interpreter at "
202
+ "{python_exe_path}: {err}" .format (
203
+ venv_dir = self ._venv_dir , python_exe_path = python_exe_path , err = e
204
+ )
205
+ )
169
206
self ._site_packages_dir = (
170
207
os .path .join (venv_dir , "site-packages" )
171
208
if self ._interpreter .identity .interpreter == "PyPy"
@@ -178,6 +215,13 @@ def __init__(
178
215
"site-packages" ,
179
216
)
180
217
)
218
+ if not os .path .isdir (self ._site_packages_dir ):
219
+ raise InvalidVirtualenvError (
220
+ "The virtualenv at {venv_dir} is not valid. The expected site-packages directory "
221
+ "at {site_packages_dir} does not exist." .format (
222
+ venv_dir = venv_dir , site_packages_dir = self ._site_packages_dir
223
+ )
224
+ )
181
225
self ._base_bin = frozenset (_iter_files (self ._bin_dir ))
182
226
183
227
@property
@@ -219,6 +263,48 @@ def iter_executables(self):
219
263
if is_exe (path ):
220
264
yield path
221
265
266
+ def iter_distributions (self ):
267
+ # type: () -> Iterator[DistributionInfo]
268
+ """"""
269
+ setuptools_path = tuple (third_party .expose (["setuptools" ]))
270
+ _ , stdout , _ = self .interpreter .execute (
271
+ args = [
272
+ "-c" ,
273
+ dedent (
274
+ """\
275
+ from __future__ import print_function
276
+
277
+ import sys
278
+
279
+
280
+ setuptools_path = {setuptools_path!r}
281
+ sys.path.extend(setuptools_path)
282
+
283
+ from pkg_resources import working_set
284
+
285
+
286
+ for dist in working_set:
287
+ if dist.location in setuptools_path:
288
+ continue
289
+ print(
290
+ "{{project_name}} {{version}} {{sys_path_entry}}".format(
291
+ project_name=dist.project_name,
292
+ version=dist.version,
293
+ sys_path_entry=dist.location,
294
+ )
295
+ )
296
+ """ .format (
297
+ setuptools_path = setuptools_path
298
+ )
299
+ ),
300
+ ]
301
+ )
302
+ for line in stdout .splitlines ():
303
+ project_name , version , sys_path_entry = line .split ()
304
+ yield DistributionInfo (
305
+ project_name = project_name , version = version , sys_path_entry = sys_path_entry
306
+ )
307
+
222
308
def _rewrite_base_scripts (self , real_venv_dir ):
223
309
# type: (str) -> Iterator[str]
224
310
scripts = [
0 commit comments