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,43 @@ 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
- """
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.
138
- """
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 )
185
-
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 )
112
+ """Setup Python module search paths using the penv_dir."""
113
+ # Add penv_dir to module search path
114
+ site .addsitedir (penv_dir )
197
115
198
- if os .path .isfile (executable_path ):
199
- return executable_path
116
+ # Add site-packages directory
117
+ python_ver = f"python{ sys .version_info .major } .{ sys .version_info .minor } "
118
+ site_packages = (
119
+ os .path .join (penv_dir , "Lib" , "site-packages" ) if IS_WINDOWS
120
+ else os .path .join (penv_dir , "lib" , python_ver , "site-packages" )
121
+ )
200
122
201
- return executable_name # Fallback to command name
123
+ if os .path .isdir (site_packages ):
124
+ site .addsitedir (site_packages )
202
125
203
126
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" )
127
+ setup_python_paths ()
215
128
216
-
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" )
129
+ # Set executable paths from tools
130
+ esptool_binary_path = get_executable_path ("esptool" )
131
+ uv_executable = get_executable_path ("uv" )
228
132
229
133
230
134
def get_packages_to_install (deps , installed_packages ):
@@ -254,9 +158,6 @@ def install_python_deps():
254
158
Returns:
255
159
bool: True if successful, False otherwise
256
160
"""
257
- # Get uv executable path
258
- uv_executable = _get_uv_executable_path (PYTHON_EXE )
259
-
260
161
try :
261
162
result = subprocess .run (
262
163
[uv_executable , "--version" ],
@@ -275,15 +176,12 @@ def install_python_deps():
275
176
capture_output = True ,
276
177
text = True ,
277
178
timeout = 30 , # 30 second timeout
278
- env = os .environ # Use modified environment with custom PYTHONPATH
179
+ env = os .environ # Use current environment with venv Python
279
180
)
280
181
if result .returncode != 0 :
281
182
if result .stderr :
282
183
print (f"Error output: { result .stderr .strip ()} " )
283
184
return False
284
-
285
- # Update uv executable path after installation
286
- uv_executable = _get_uv_executable_path (PYTHON_EXE )
287
185
288
186
except subprocess .TimeoutExpired :
289
187
print ("Error: uv installation timed out" )
@@ -312,7 +210,7 @@ def _get_installed_uv_packages():
312
210
text = True ,
313
211
encoding = 'utf-8' ,
314
212
timeout = 30 , # 30 second timeout
315
- env = os .environ # Use modified environment with custom PYTHONPATH
213
+ env = os .environ # Use current environment with venv Python
316
214
)
317
215
318
216
if result_obj .returncode == 0 :
@@ -355,7 +253,7 @@ def _get_installed_uv_packages():
355
253
capture_output = True ,
356
254
text = True ,
357
255
timeout = 30 , # 30 second timeout for package installation
358
- env = os .environ # Use modified environment with custom PYTHONPATH
256
+ env = os .environ # Use current environment with venv Python
359
257
)
360
258
361
259
if result .returncode != 0 :
@@ -380,9 +278,6 @@ def _get_installed_uv_packages():
380
278
def install_esptool ():
381
279
"""
382
280
Install esptool from package folder "tool-esptoolpy" using uv package manager.
383
-
384
- Returns:
385
- str: Path to esptool executable
386
281
387
282
Raises:
388
283
SystemExit: If esptool installation fails
@@ -394,35 +289,33 @@ def install_esptool():
394
289
stderr = subprocess .DEVNULL ,
395
290
env = os .environ
396
291
)
397
- return _get_esptool_executable_path ( PYTHON_EXE )
292
+ return
398
293
except (subprocess .CalledProcessError , FileNotFoundError ):
399
294
pass
400
295
401
296
esptool_repo_path = env .subst (platform .get_package_dir ("tool-esptoolpy" ) or "" )
402
297
if not esptool_repo_path or not os .path .isdir (esptool_repo_path ):
403
298
print ("Error: esptool package directory not found" )
404
299
sys .exit (1 )
405
-
406
- uv_executable = _get_uv_executable_path (PYTHON_EXE )
300
+
407
301
try :
408
302
subprocess .check_call ([
409
303
uv_executable , "pip" , "install" , "--quiet" ,
410
304
f"--python={ PYTHON_EXE } " ,
411
305
"-e" , esptool_repo_path
412
306
], env = os .environ )
413
-
414
- return _get_esptool_executable_path ( PYTHON_EXE )
415
-
307
+
308
+ return
309
+
416
310
except subprocess .CalledProcessError as e :
417
311
print (f"Error: Failed to install esptool: { e } " )
418
312
sys .exit (1 )
419
313
420
314
421
- # Install Python dependencies
315
+ # Install espressif32 Python dependencies
422
316
install_python_deps ()
423
-
424
317
# Install esptool after dependencies
425
- esptool_binary_path = install_esptool ()
318
+ install_esptool ()
426
319
427
320
428
321
def BeforeUpload (target , source , env ):
0 commit comments