1
1
import hashlib
2
+ import os
2
3
from pdb import set_trace
3
4
import shutil
4
5
import subprocess
@@ -94,9 +95,11 @@ def _cmd_exec_helper(
94
95
returncode = process .wait ()
95
96
96
97
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 )
100
103
101
104
return subprocess .CompletedProcess (
102
105
args = cmd ,
@@ -105,6 +108,97 @@ def _cmd_exec_helper(
105
108
stderr = None ,
106
109
)
107
110
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
+
108
202
def __enter__ (self ) -> "AnalyzerCore" :
109
203
# If no virtualenv is provided, try to create one using requirements.txt or pyproject.toml
110
204
venv_path = self .cache_dir / self .project_dir .name / "virtualenv"
@@ -114,7 +208,7 @@ def __enter__(self) -> "AnalyzerCore":
114
208
if not venv_path .exists () or self .rebuild_analysis :
115
209
logger .info (f"(Re-)creating virtual environment at { venv_path } " )
116
210
self ._cmd_exec_helper (
117
- [sys . executable , "-m" , "venv" , str (venv_path )],
211
+ [str ( self . _get_base_interpreter ()) , "-m" , "venv" , str (venv_path )],
118
212
check = True ,
119
213
)
120
214
# Find python in the virtual environment
0 commit comments