11import hashlib
2+ import os
23from pdb import set_trace
34import shutil
45import subprocess
@@ -94,9 +95,11 @@ def _cmd_exec_helper(
9495 returncode = process .wait ()
9596
9697 if check and returncode != 0 :
97- raise subprocess .CalledProcessError (
98- returncode , cmd , output = "\n " .join (output_lines )
99- )
98+ error_output = "\n " .join (output_lines )
99+ logger .error (f"Command failed with exit code { returncode } : { ' ' .join (cmd )} " )
100+ if error_output :
101+ logger .error (f"Command output:\n { error_output } " )
102+ raise subprocess .CalledProcessError (returncode , cmd , output = error_output )
100103
101104 return subprocess .CompletedProcess (
102105 args = cmd ,
@@ -105,6 +108,97 @@ def _cmd_exec_helper(
105108 stderr = None ,
106109 )
107110
111+ @staticmethod
112+ def _get_base_interpreter () -> Path :
113+ """Get the base Python interpreter path.
114+
115+ This method finds a suitable base Python interpreter that can be used
116+ to create virtual environments, even when running from within a virtual environment.
117+ It supports various Python version managers like pyenv, conda, asdf, etc.
118+
119+ Returns:
120+ Path: The base Python interpreter path.
121+
122+ Raises:
123+ RuntimeError: If no suitable Python interpreter can be found.
124+ """
125+ # If we're not in a virtual environment, use the current interpreter
126+ if sys .prefix == sys .base_prefix :
127+ return Path (sys .executable )
128+
129+ # We're inside a virtual environment; need to find the base interpreter
130+
131+ # First, check if user explicitly set SYSTEM_PYTHON
132+ if system_python := os .getenv ("SYSTEM_PYTHON" ):
133+ system_python_path = Path (system_python )
134+ if system_python_path .exists () and system_python_path .is_file ():
135+ return system_python_path
136+
137+ # Try to get the base interpreter from sys.base_executable (Python 3.3+)
138+ if hasattr (sys , "base_executable" ) and sys .base_executable :
139+ base_exec = Path (sys .base_executable )
140+ if base_exec .exists () and base_exec .is_file ():
141+ return base_exec
142+
143+ # Try to find Python interpreters using shlex.which
144+ python_candidates = []
145+
146+ # Use shutil.which to find python3 and python in PATH
147+ for python_name in ["python3" , "python" ]:
148+ if python_path := shutil .which (python_name ):
149+ candidate = Path (python_path )
150+ # Skip if this is the current virtual environment's python
151+ if not str (candidate ).startswith (sys .prefix ):
152+ python_candidates .append (candidate )
153+
154+ # Check pyenv installation
155+ if pyenv_root := os .getenv ("PYENV_ROOT" ):
156+ pyenv_python = Path (pyenv_root ) / "shims" / "python"
157+ if pyenv_python .exists ():
158+ python_candidates .append (pyenv_python )
159+
160+ # Check default pyenv location
161+ home_pyenv = Path .home () / ".pyenv" / "shims" / "python"
162+ if home_pyenv .exists ():
163+ python_candidates .append (home_pyenv )
164+
165+ # Check conda base environment
166+ if conda_prefix := os .getenv (
167+ "CONDA_PREFIX_1"
168+ ): # Original conda env before activation
169+ conda_python = Path (conda_prefix ) / "bin" / "python"
170+ if conda_python .exists ():
171+ python_candidates .append (conda_python )
172+
173+ # Check asdf
174+ if asdf_dir := os .getenv ("ASDF_DIR" ):
175+ asdf_python = Path (asdf_dir ) / "shims" / "python"
176+ if asdf_python .exists ():
177+ python_candidates .append (asdf_python )
178+
179+ # Test candidates to find a working Python interpreter
180+ for candidate in python_candidates :
181+ try :
182+ # Test if the interpreter works and can create venv
183+ result = subprocess .run (
184+ [str (candidate ), "-c" , "import venv; print('OK')" ],
185+ capture_output = True ,
186+ text = True ,
187+ timeout = 5 ,
188+ )
189+ if result .returncode == 0 and "OK" in result .stdout :
190+ return candidate
191+ except (subprocess .TimeoutExpired , FileNotFoundError , PermissionError ):
192+ continue
193+
194+ # If nothing works, raise an informative error
195+ raise RuntimeError (
196+ f"Could not find a suitable base Python interpreter. "
197+ f"Current environment: { sys .executable } (prefix: { sys .prefix } ). "
198+ f"Please set the SYSTEM_PYTHON environment variable to point to "
199+ f"a working Python interpreter that can create virtual environments."
200+ )
201+
108202 def __enter__ (self ) -> "AnalyzerCore" :
109203 # If no virtualenv is provided, try to create one using requirements.txt or pyproject.toml
110204 venv_path = self .cache_dir / self .project_dir .name / "virtualenv"
@@ -114,7 +208,7 @@ def __enter__(self) -> "AnalyzerCore":
114208 if not venv_path .exists () or self .rebuild_analysis :
115209 logger .info (f"(Re-)creating virtual environment at { venv_path } " )
116210 self ._cmd_exec_helper (
117- [sys . executable , "-m" , "venv" , str (venv_path )],
211+ [str ( self . _get_base_interpreter ()) , "-m" , "venv" , str (venv_path )],
118212 check = True ,
119213 )
120214 # Find python in the virtual environment
0 commit comments