16
16
import json
17
17
import os
18
18
import re
19
+ import site
19
20
import semantic_version
20
21
import shlex
21
22
import subprocess
61
62
platform = env .PioPlatform ()
62
63
projectconfig = env .GetProjectConfig ()
63
64
terminal_cp = locale .getpreferredencoding ().lower ()
64
- PYTHON_EXE = env .subst ("$PYTHONEXE" ) # Global Python executable path
65
-
66
- # Framework directory path
67
65
FRAMEWORK_DIR = platform .get_package_dir ("framework-arduinoespressif32" )
68
-
69
66
platformio_dir = projectconfig .get ("platformio" , "core_dir" )
67
+
68
+ # Global Python executable path, replaced later with venv python path
69
+ PYTHON_EXE = env .subst ("$PYTHONEXE" )
70
70
penv_dir = os .path .join (platformio_dir , "penv" )
71
71
72
- pip_path = os .path .join (
73
- penv_dir ,
74
- "Scripts" if IS_WINDOWS else "bin" ,
75
- "pip" + (".exe" if IS_WINDOWS else "" ),
76
- )
72
+
73
+ def get_executable_path (executable_name ):
74
+ """
75
+ Get the path to an executable based on the penv_dir.
76
+ """
77
+ exe_suffix = ".exe" if IS_WINDOWS else ""
78
+ scripts_dir = "Scripts" if IS_WINDOWS else "bin"
79
+
80
+ return os .path .join (penv_dir , scripts_dir , f"{ executable_name } { exe_suffix } " )
81
+
77
82
78
83
def setup_pipenv_in_package ():
79
84
"""
@@ -87,144 +92,49 @@ def setup_pipenv_in_package():
87
92
)
88
93
)
89
94
assert os .path .isfile (
90
- pip_path
95
+ get_executable_path ( "pip" )
91
96
), "Error: Failed to create a proper virtual environment. Missing the `pip` binary!"
92
97
93
- penv_python = os .path .join (penv_dir , "Scripts" , "python.exe" ) if IS_WINDOWS else os .path .join (penv_dir , "bin" , "python" )
94
- env .Replace (PYTHONEXE = penv_python )
95
98
96
- # Setup virtual environment if needed and find path to Python exe
99
+ # Setup virtual environment if needed
97
100
setup_pipenv_in_package ()
101
+
98
102
# Set Python Scons Var to env Python
99
- PYTHON_EXE = env . subst ( "$PYTHONEXE " )
100
- # Remove PYTHONHOME if set
101
- os . environ . pop ( 'PYTHONHOME' , None )
103
+ penv_python = get_executable_path ( "python " )
104
+ env . Replace ( PYTHONEXE = penv_python )
105
+ PYTHON_EXE = penv_python
102
106
103
107
# check for python binary, exit with error when not found
104
108
assert os .path .isfile (PYTHON_EXE ), f"Python executable not found: { PYTHON_EXE } "
105
109
106
- def add_to_pythonpath (path ):
107
- """
108
- Add a path to the PYTHONPATH environment variable (cross-platform).
109
-
110
- Args:
111
- path (str): The path to add to PYTHONPATH
112
- """
113
- # Normalize the path for the current OS
114
- normalized_path = os .path .normpath (path )
115
-
116
- # Add to PYTHONPATH environment variable
117
- if "PYTHONPATH" in os .environ :
118
- current_paths = os .environ ["PYTHONPATH" ].split (os .pathsep )
119
- normalized_current_paths = [os .path .normpath (p ) for p in current_paths ]
120
- if normalized_path not in normalized_current_paths :
121
- # Rebuild PYTHONPATH with normalized paths to avoid duplicates
122
- normalized_current_paths .insert (0 , normalized_path )
123
- os .environ ["PYTHONPATH" ] = os .pathsep .join (normalized_current_paths )
124
- else :
125
- os .environ ["PYTHONPATH" ] = normalized_path
126
-
127
- # Also add to sys.path for immediate availability
128
- if normalized_path not in sys .path :
129
- sys .path .insert (0 , normalized_path )
130
-
131
110
132
111
def setup_python_paths ():
133
112
"""
134
- Setup Python paths based on the actual Python executable being used.
135
-
136
- This function configures both PYTHONPATH environment variable and sys.path
137
- to include the Python executable directory and site-packages directory.
113
+ Setup Python module search paths using the penv_dir.
138
114
"""
139
- # Get the directory containing the Python executable
140
- python_dir = os .path .dirname (PYTHON_EXE )
141
-
142
- # Add Scripts directory to PATH for Windows
143
- if IS_WINDOWS :
144
- scripts_dir = os .path .join (python_dir , "Scripts" )
145
- if os .path .isdir (scripts_dir ):
146
- os .environ ["PATH" ] = scripts_dir + os .pathsep + os .environ .get ("PATH" , "" )
147
- else :
148
- bin_dir = os .path .join (python_dir , "bin" )
149
- if os .path .isdir (bin_dir ):
150
- os .environ ["PATH" ] = bin_dir + os .pathsep + os .environ .get ("PATH" , "" )
151
-
152
- penv_site_packages = None
153
- if python_dir not in sys .path :
154
- add_to_pythonpath (python_dir )
155
- if IS_WINDOWS :
156
- penv_site_packages = os .path .join (penv_dir , "Lib" , "site-packages" )
157
- else :
158
- # Find the actual site-packages directory in the venv
159
- penv_lib_dir = os .path .join (penv_dir , "lib" )
160
- if os .path .isdir (penv_lib_dir ):
161
- for python_version_dir in os .listdir (penv_lib_dir ):
162
- if python_version_dir .startswith ("python" ):
163
- penv_site_packages = os .path .join (penv_lib_dir , python_version_dir , "site-packages" )
164
- break
165
-
166
- if penv_site_packages and os .path .isdir (penv_site_packages ) and penv_site_packages not in sys .path :
167
- add_to_pythonpath (penv_site_packages )
168
-
169
- setup_python_paths ()
170
-
171
-
172
- def _get_executable_path (python_exe , executable_name ):
173
- """
174
- Get the path to an executable binary (esptool, uv, etc.) based on the Python executable path.
175
-
176
- Args:
177
- python_exe (str): Path to Python executable
178
- executable_name (str): Name of the executable to find (e.g., 'esptool', 'uv')
179
-
180
- Returns:
181
- str: Path to executable or fallback to executable name
182
- """
183
-
184
- python_dir = os .path .dirname (python_exe )
115
+ # Add penv_dir to module search path
116
+ site .addsitedir (penv_dir )
185
117
186
- if IS_WINDOWS :
187
- executable_path = os .path .join (python_dir , f"{ executable_name } .exe" )
188
- else :
189
- # For Unix-like systems, executables are typically in the same directory as python
190
- # or in a bin subdirectory
191
- executable_path = os .path .join (python_dir , executable_name )
192
-
193
- # If not found in python directory, try bin subdirectory
194
- if not os .path .isfile (executable_path ):
195
- bin_dir = os .path .join (python_dir , "bin" )
196
- executable_path = os .path .join (bin_dir , executable_name )
197
-
198
- if os .path .isfile (executable_path ):
199
- return executable_path
118
+ # Add site-packages directory
119
+ site_packages = (
120
+ os .path .join (penv_dir , "Lib" , "site-packages" ) if IS_WINDOWS
121
+ else next (
122
+ (os .path .join (penv_dir , "lib" , d , "site-packages" )
123
+ for d in os .listdir (os .path .join (penv_dir , "lib" ))
124
+ if d .startswith ("python" )),
125
+ None
126
+ ) if os .path .isdir (os .path .join (penv_dir , "lib" )) else None
127
+ )
200
128
201
- return executable_name # Fallback to command name
129
+ if site_packages and os .path .isdir (site_packages ):
130
+ site .addsitedir (site_packages )
202
131
203
132
204
- def _get_esptool_executable_path (python_exe ):
205
- """
206
- Get the path to the esptool executable binary.
207
-
208
- Args:
209
- python_exe (str): Path to Python executable
210
-
211
- Returns:
212
- str: Path to esptool executable
213
- """
214
- return _get_executable_path (python_exe , "esptool" )
215
-
133
+ setup_python_paths ()
216
134
217
- def _get_uv_executable_path (python_exe ):
218
- """
219
- Get the path to the uv executable binary.
220
-
221
- Args:
222
- python_exe (str): Path to Python executable
223
-
224
- Returns:
225
- str: Path to uv executable
226
- """
227
- return _get_executable_path (python_exe , "uv" )
135
+ # Set executable paths from tools
136
+ esptool_binary_path = get_executable_path ("esptool" )
137
+ uv_executable = get_executable_path ("uv" )
228
138
229
139
230
140
def get_packages_to_install (deps , installed_packages ):
@@ -254,9 +164,6 @@ def install_python_deps():
254
164
Returns:
255
165
bool: True if successful, False otherwise
256
166
"""
257
- # Get uv executable path
258
- uv_executable = _get_uv_executable_path (PYTHON_EXE )
259
-
260
167
try :
261
168
result = subprocess .run (
262
169
[uv_executable , "--version" ],
@@ -275,15 +182,12 @@ def install_python_deps():
275
182
capture_output = True ,
276
183
text = True ,
277
184
timeout = 30 , # 30 second timeout
278
- env = os .environ # Use modified environment with custom PYTHONPATH
185
+ env = os .environ # Use modified environment with venv Python
279
186
)
280
187
if result .returncode != 0 :
281
188
if result .stderr :
282
189
print (f"Error output: { result .stderr .strip ()} " )
283
190
return False
284
-
285
- # Update uv executable path after installation
286
- uv_executable = _get_uv_executable_path (PYTHON_EXE )
287
191
288
192
except subprocess .TimeoutExpired :
289
193
print ("Error: uv installation timed out" )
@@ -312,7 +216,7 @@ def _get_installed_uv_packages():
312
216
text = True ,
313
217
encoding = 'utf-8' ,
314
218
timeout = 30 , # 30 second timeout
315
- env = os .environ # Use modified environment with custom PYTHONPATH
219
+ env = os .environ # Use modified environment with venv Python
316
220
)
317
221
318
222
if result_obj .returncode == 0 :
@@ -355,7 +259,7 @@ def _get_installed_uv_packages():
355
259
capture_output = True ,
356
260
text = True ,
357
261
timeout = 30 , # 30 second timeout for package installation
358
- env = os .environ # Use modified environment with custom PYTHONPATH
262
+ env = os .environ # Use modified environment with venv Python
359
263
)
360
264
361
265
if result .returncode != 0 :
@@ -394,35 +298,33 @@ def install_esptool():
394
298
stderr = subprocess .DEVNULL ,
395
299
env = os .environ
396
300
)
397
- return _get_esptool_executable_path ( PYTHON_EXE )
301
+ return
398
302
except (subprocess .CalledProcessError , FileNotFoundError ):
399
303
pass
400
304
401
305
esptool_repo_path = env .subst (platform .get_package_dir ("tool-esptoolpy" ) or "" )
402
306
if not esptool_repo_path or not os .path .isdir (esptool_repo_path ):
403
307
print ("Error: esptool package directory not found" )
404
308
sys .exit (1 )
405
-
406
- uv_executable = _get_uv_executable_path (PYTHON_EXE )
309
+
407
310
try :
408
311
subprocess .check_call ([
409
312
uv_executable , "pip" , "install" , "--quiet" ,
410
313
f"--python={ PYTHON_EXE } " ,
411
314
"-e" , esptool_repo_path
412
315
], env = os .environ )
413
316
414
- return _get_esptool_executable_path ( PYTHON_EXE )
317
+ return
415
318
416
319
except subprocess .CalledProcessError as e :
417
320
print (f"Error: Failed to install esptool: { e } " )
418
321
sys .exit (1 )
419
322
420
323
421
- # Install Python dependencies
324
+ # Install espressif32 Python dependencies
422
325
install_python_deps ()
423
-
424
326
# Install esptool after dependencies
425
- esptool_binary_path = install_esptool ()
327
+ install_esptool ()
426
328
427
329
428
330
def BeforeUpload (target , source , env ):
0 commit comments