99import json
1010import os
1111import shutil
12+ import stat
1213import subprocess
1314import sys
1415from contextlib import contextmanager
2021from typing import IO , Any , Dict , Iterator , cast
2122
2223from dev_cmd import color
24+ from dev_cmd .errors import DevCmdError
2325from dev_cmd .model import Command , PythonConfig , Venv
2426
2527AVAILABLE = False
@@ -35,7 +37,7 @@ def _fingerprint(data: bytes) -> str:
3537
3638
3739@contextmanager
38- def named_temporary_file (
40+ def _named_temporary_file (
3941 tmp_dir : str | None = None , prefix : str | None = None
4042) -> Iterator [IO [bytes ]]:
4143 # Work around Windows issue with auto-delete: https://bugs.python.org/issue14243
@@ -55,21 +57,21 @@ def _ensure_cache_dir() -> Path:
5557 gitignore = cache_dir / ".gitignore"
5658 if not gitignore .exists ():
5759 cache_dir .mkdir (parents = True , exist_ok = True )
58- with named_temporary_file (tmp_dir = fspath (cache_dir ), prefix = ".gitignore." ) as gitignore_fp :
60+ with _named_temporary_file (tmp_dir = fspath (cache_dir ), prefix = ".gitignore." ) as gitignore_fp :
5961 gitignore_fp .write (b"*\n " )
6062 gitignore_fp .close ()
6163 os .rename (gitignore_fp .name , gitignore )
6264 return cache_dir
6365
6466
6567@dataclass (frozen = True )
66- class VenvLayout :
68+ class _VenvLayout :
6769 python : str
6870 site_packages_dir : str
6971
7072
71- def _create_venv (python : str , venv_dir : str ) -> VenvLayout :
72- subprocess .run (
73+ def _create_venv (python : str , venv_dir : str ) -> _VenvLayout :
74+ result = subprocess .run (
7375 args = [
7476 "pex3" ,
7577 "venv" ,
@@ -81,8 +83,11 @@ def _create_venv(python: str, venv_dir: str) -> VenvLayout:
8183 "--dest-dir" ,
8284 venv_dir ,
8385 ],
84- check = True ,
86+ capture_output = True ,
87+ text = True ,
8588 )
89+ if result .returncode != 0 :
90+ raise DevCmdError (result .stderr )
8691
8792 result = subprocess .run (
8893 args = ["pex3" , "venv" , "inspect" , venv_dir ],
@@ -94,7 +99,7 @@ def _create_venv(python: str, venv_dir: str) -> VenvLayout:
9499 python_exe = venv_data ["interpreter" ]["binary" ]
95100 site_packages_dir = venv_data ["site_packages" ]
96101
97- return VenvLayout (python = python_exe , site_packages_dir = site_packages_dir )
102+ return _VenvLayout (python = python_exe , site_packages_dir = site_packages_dir )
98103
99104
100105def marker_environment (python : str ) -> dict [str , str ]:
@@ -202,6 +207,18 @@ def extract_command_fingerprint_data(command: Command | None) -> dict[str, Any]
202207 )
203208
204209
210+ def _chmod_plus_x (path : str ) -> None :
211+ path_mode = os .stat (path ).st_mode
212+ path_mode &= 0o777
213+ if path_mode & stat .S_IRUSR :
214+ path_mode |= stat .S_IXUSR
215+ if path_mode & stat .S_IRGRP :
216+ path_mode |= stat .S_IXGRP
217+ if path_mode & stat .S_IROTH :
218+ path_mode |= stat .S_IXOTH
219+ os .chmod (path , path_mode )
220+
221+
205222def ensure (python : str , config : PythonConfig , rebuild_if_needed : bool = True ) -> Venv :
206223 fingerprint = _fingerprint_python_config (python = python , config = config )
207224 venv_dir = _ensure_cache_dir () / "venvs" / fingerprint
@@ -214,7 +231,7 @@ def ensure(python: str, config: PythonConfig, rebuild_if_needed: bool = True) ->
214231 )
215232 work_dir = Path (f"{ venv_dir } .work" )
216233 venv_layout = _create_venv (python , venv_dir = fspath (work_dir ))
217- with named_temporary_file (
234+ with _named_temporary_file (
218235 tmp_dir = fspath (work_dir ), prefix = "3rdparty-reqs."
219236 ) as reqs_fp :
220237 reqs_fp .close ()
@@ -257,7 +274,7 @@ def ensure(python: str, config: PythonConfig, rebuild_if_needed: bool = True) ->
257274 @contextmanager
258275 def _extra_requirements_args () -> Iterator [list [str ]]:
259276 if isinstance (config .extra_requirements , str ):
260- with named_temporary_file (
277+ with _named_temporary_file (
261278 tmp_dir = fspath (work_dir ), prefix = "extra-reqs."
262279 ) as fp :
263280 fp .write (config .extra_requirements .encode ())
@@ -313,6 +330,7 @@ def _extra_requirements_args() -> Iterator[list[str]]:
313330 )
314331 shutil .copyfileobj (candidate_fp , rewrite_fp )
315332 os .rename (rewrite_fp .name , candidate_console_script )
333+ _chmod_plus_x (candidate_console_script )
316334
317335 with (work_dir / layout_file .name ).open ("w" ) as out_fp :
318336 json .dump (
0 commit comments