22import functools
33import os
44import sys
5- from typing import Callable
6- from typing import ContextManager
5+ from typing import Dict
76from typing import Generator
87from typing import Optional
98from typing import Sequence
2625ENVIRONMENT_DIR = 'py_env'
2726
2827
28+ @functools .lru_cache (maxsize = None )
29+ def _version_info (exe : str ) -> str :
30+ prog = 'import sys;print(".".join(str(p) for p in sys.version_info))'
31+ try :
32+ return cmd_output (exe , '-S' , '-c' , prog )[1 ].strip ()
33+ except CalledProcessError :
34+ return f'<<error retrieving version from { exe } >>'
35+
36+
37+ def _read_pyvenv_cfg (filename : str ) -> Dict [str , str ]:
38+ ret = {}
39+ with open (filename ) as f :
40+ for line in f :
41+ try :
42+ k , v = line .split ('=' )
43+ except ValueError : # blank line / comment / etc.
44+ continue
45+ else :
46+ ret [k .strip ()] = v .strip ()
47+ return ret
48+
49+
2950def bin_dir (venv : str ) -> str :
3051 """On windows there's a different directory for the virtualenv"""
3152 bin_part = 'Scripts' if os .name == 'nt' else 'bin'
@@ -116,6 +137,9 @@ def _sys_executable_matches(version: str) -> bool:
116137
117138
118139def norm_version (version : str ) -> str :
140+ if version == C .DEFAULT :
141+ return os .path .realpath (sys .executable )
142+
119143 # first see if our current executable is appropriate
120144 if _sys_executable_matches (version ):
121145 return sys .executable
@@ -140,70 +164,59 @@ def norm_version(version: str) -> str:
140164 return os .path .expanduser (version )
141165
142166
143- def py_interface (
144- _dir : str ,
145- _make_venv : Callable [[str , str ], None ],
146- ) -> Tuple [
147- Callable [[Prefix , str ], ContextManager [None ]],
148- Callable [[Prefix , str ], bool ],
149- Callable [[Hook , Sequence [str ], bool ], Tuple [int , bytes ]],
150- Callable [[Prefix , str , Sequence [str ]], None ],
151- ]:
152- @contextlib .contextmanager
153- def in_env (
154- prefix : Prefix ,
155- language_version : str ,
156- ) -> Generator [None , None , None ]:
157- envdir = prefix .path (helpers .environment_dir (_dir , language_version ))
158- with envcontext (get_env_patch (envdir )):
159- yield
160-
161- def healthy (prefix : Prefix , language_version : str ) -> bool :
162- envdir = helpers .environment_dir (_dir , language_version )
163- exe_name = 'python.exe' if sys .platform == 'win32' else 'python'
164- py_exe = prefix .path (bin_dir (envdir ), exe_name )
165- with in_env (prefix , language_version ):
166- retcode , _ , _ = cmd_output_b (
167- py_exe , '-c' , 'import ctypes, datetime, io, os, ssl, weakref' ,
168- cwd = '/' ,
169- retcode = None ,
170- )
171- return retcode == 0
172-
173- def run_hook (
174- hook : Hook ,
175- file_args : Sequence [str ],
176- color : bool ,
177- ) -> Tuple [int , bytes ]:
178- with in_env (hook .prefix , hook .language_version ):
179- return helpers .run_xargs (hook , hook .cmd , file_args , color = color )
180-
181- def install_environment (
182- prefix : Prefix ,
183- version : str ,
184- additional_dependencies : Sequence [str ],
185- ) -> None :
186- directory = helpers .environment_dir (_dir , version )
187- install = ('python' , '-mpip' , 'install' , '.' , * additional_dependencies )
188-
189- env_dir = prefix .path (directory )
190- with clean_path_on_failure (env_dir ):
191- if version != C .DEFAULT :
192- python = norm_version (version )
193- else :
194- python = os .path .realpath (sys .executable )
195- _make_venv (env_dir , python )
196- with in_env (prefix , version ):
197- helpers .run_setup_cmd (prefix , install )
167+ @contextlib .contextmanager
168+ def in_env (
169+ prefix : Prefix ,
170+ language_version : str ,
171+ ) -> Generator [None , None , None ]:
172+ directory = helpers .environment_dir (ENVIRONMENT_DIR , language_version )
173+ envdir = prefix .path (directory )
174+ with envcontext (get_env_patch (envdir )):
175+ yield
198176
199- return in_env , healthy , run_hook , install_environment
200177
178+ def healthy (prefix : Prefix , language_version : str ) -> bool :
179+ directory = helpers .environment_dir (ENVIRONMENT_DIR , language_version )
180+ envdir = prefix .path (directory )
181+ pyvenv_cfg = os .path .join (envdir , 'pyvenv.cfg' )
201182
202- def make_venv (envdir : str , python : str ) -> None :
203- env = dict (os .environ , VIRTUALENV_NO_DOWNLOAD = '1' )
204- cmd = (sys .executable , '-mvirtualenv' , envdir , '-p' , python )
205- cmd_output_b (* cmd , env = env , cwd = '/' )
183+ # created with "old" virtualenv
184+ if not os .path .exists (pyvenv_cfg ):
185+ return False
186+
187+ exe_name = 'python.exe' if sys .platform == 'win32' else 'python'
188+ py_exe = prefix .path (bin_dir (envdir ), exe_name )
189+ cfg = _read_pyvenv_cfg (pyvenv_cfg )
190+
191+ return (
192+ 'version_info' in cfg and
193+ _version_info (py_exe ) == cfg ['version_info' ] and (
194+ 'base-executable' not in cfg or
195+ _version_info (cfg ['base-executable' ]) == cfg ['version_info' ]
196+ )
197+ )
206198
207199
208- _interface = py_interface (ENVIRONMENT_DIR , make_venv )
209- in_env , healthy , run_hook , install_environment = _interface
200+ def install_environment (
201+ prefix : Prefix ,
202+ version : str ,
203+ additional_dependencies : Sequence [str ],
204+ ) -> None :
205+ envdir = prefix .path (helpers .environment_dir (ENVIRONMENT_DIR , version ))
206+ python = norm_version (version )
207+ venv_cmd = (sys .executable , '-mvirtualenv' , envdir , '-p' , python )
208+ install_cmd = ('python' , '-mpip' , 'install' , '.' , * additional_dependencies )
209+
210+ with clean_path_on_failure (envdir ):
211+ cmd_output_b (* venv_cmd , cwd = '/' )
212+ with in_env (prefix , version ):
213+ helpers .run_setup_cmd (prefix , install_cmd )
214+
215+
216+ def run_hook (
217+ hook : Hook ,
218+ file_args : Sequence [str ],
219+ color : bool ,
220+ ) -> Tuple [int , bytes ]:
221+ with in_env (hook .prefix , hook .language_version ):
222+ return helpers .run_xargs (hook , hook .cmd , file_args , color = color )
0 commit comments