51
51
platform = env .PioPlatform ()
52
52
projectconfig = env .GetProjectConfig ()
53
53
terminal_cp = locale .getpreferredencoding ().lower ()
54
+ PYTHON_EXE = env .subst ("$PYTHONEXE" ) # Global Python executable path
54
55
55
56
# Framework directory path
56
57
FRAMEWORK_DIR = platform .get_package_dir ("framework-arduinoespressif32" )
@@ -80,24 +81,20 @@ def add_to_pythonpath(path):
80
81
sys .path .insert (0 , normalized_path )
81
82
82
83
83
- def setup_python_paths (env ):
84
+ def setup_python_paths ():
84
85
"""
85
86
Setup Python paths based on the actual Python executable being used.
86
-
87
- Args:
88
- env: SCons environment object
89
87
"""
90
- python_exe = env .subst ('$PYTHONEXE' )
91
- if not python_exe or not os .path .isfile (python_exe ):
88
+ if not PYTHON_EXE or not os .path .isfile (PYTHON_EXE ):
92
89
return
93
90
94
91
# Get the directory containing the Python executable
95
- python_dir = os .path .dirname (python_exe )
92
+ python_dir = os .path .dirname (PYTHON_EXE )
96
93
add_to_pythonpath (python_dir )
97
94
98
95
# Try to find site-packages directory using the actual Python executable
99
96
result = subprocess .run (
100
- [python_exe , "-c" , "import site; print(site.getsitepackages()[0])" ],
97
+ [PYTHON_EXE , "-c" , "import site; print(site.getsitepackages()[0])" ],
101
98
capture_output = True ,
102
99
text = True ,
103
100
timeout = 5
@@ -108,7 +105,68 @@ def setup_python_paths(env):
108
105
add_to_pythonpath (site_packages )
109
106
110
107
# Setup Python paths based on the actual Python executable
111
- setup_python_paths (env )
108
+ setup_python_paths ()
109
+
110
+
111
+ def _get_executable_path (python_exe , executable_name ):
112
+ """
113
+ Get the path to an executable binary (esptool, uv, etc.) based on the Python executable path.
114
+
115
+ Args:
116
+ python_exe (str): Path to Python executable
117
+ executable_name (str): Name of the executable to find (e.g., 'esptool', 'uv')
118
+
119
+ Returns:
120
+ str: Path to executable or fallback to executable name
121
+ """
122
+ if not python_exe or not os .path .isfile (python_exe ):
123
+ return executable_name # Fallback to command name
124
+
125
+ python_dir = os .path .dirname (python_exe )
126
+
127
+ if sys .platform == "win32" :
128
+ scripts_dir = os .path .join (python_dir , "Scripts" )
129
+ executable_path = os .path .join (scripts_dir , f"{ executable_name } .exe" )
130
+ else :
131
+ # For Unix-like systems, executables are typically in the same directory as python
132
+ # or in a bin subdirectory
133
+ executable_path = os .path .join (python_dir , executable_name )
134
+
135
+ # If not found in python directory, try bin subdirectory
136
+ if not os .path .isfile (executable_path ):
137
+ bin_dir = os .path .join (python_dir , "bin" )
138
+ executable_path = os .path .join (bin_dir , executable_name )
139
+
140
+ if os .path .isfile (executable_path ):
141
+ return executable_path
142
+
143
+ return executable_name # Fallback to command name
144
+
145
+
146
+ def _get_esptool_executable_path (python_exe ):
147
+ """
148
+ Get the path to the esptool executable binary.
149
+
150
+ Args:
151
+ python_exe (str): Path to Python executable
152
+
153
+ Returns:
154
+ str: Path to esptool executable
155
+ """
156
+ return _get_executable_path (python_exe , "esptool" )
157
+
158
+
159
+ def _get_uv_executable_path (python_exe ):
160
+ """
161
+ Get the path to the uv executable binary.
162
+
163
+ Args:
164
+ python_exe (str): Path to Python executable
165
+
166
+ Returns:
167
+ str: Path to uv executable
168
+ """
169
+ return _get_executable_path (python_exe , "uv" )
112
170
113
171
114
172
def get_packages_to_install (deps , installed_packages ):
@@ -138,9 +196,12 @@ def install_python_deps():
138
196
Returns:
139
197
bool: True if successful, False otherwise
140
198
"""
199
+ # Get uv executable path
200
+ uv_executable = _get_uv_executable_path (PYTHON_EXE )
201
+
141
202
try :
142
203
result = subprocess .run (
143
- ["uv" , "--version" ],
204
+ [uv_executable , "--version" ],
144
205
capture_output = True ,
145
206
text = True ,
146
207
timeout = 3
@@ -152,7 +213,7 @@ def install_python_deps():
152
213
if not uv_available :
153
214
try :
154
215
result = subprocess .run (
155
- [env . subst ( "$PYTHONEXE" ) , "-m" , "pip" , "install" , "uv>=0.1.0" , "-q" , "-q" , "-q" ],
216
+ [PYTHON_EXE , "-m" , "pip" , "install" , "uv>=0.1.0" , "-q" , "-q" , "-q" ],
156
217
capture_output = True ,
157
218
text = True ,
158
219
timeout = 30 , # 30 second timeout
@@ -162,6 +223,17 @@ def install_python_deps():
162
223
if result .stderr :
163
224
print (f"Error output: { result .stderr .strip ()} " )
164
225
return False
226
+
227
+ # Update uv executable path after installation
228
+ uv_executable = _get_uv_executable_path (PYTHON_EXE )
229
+
230
+ # Add Scripts directory to PATH for Windows
231
+ if sys .platform == "win32" :
232
+ python_dir = os .path .dirname (PYTHON_EXE )
233
+ scripts_dir = os .path .join (python_dir , "Scripts" )
234
+ if os .path .isdir (scripts_dir ):
235
+ os .environ ["PATH" ] = scripts_dir + os .pathsep + os .environ .get ("PATH" , "" )
236
+
165
237
except subprocess .TimeoutExpired :
166
238
print ("Error: uv installation timed out" )
167
239
return False
@@ -182,7 +254,7 @@ def _get_installed_uv_packages():
182
254
"""
183
255
result = {}
184
256
try :
185
- cmd = ["uv" , "pip" , "list" , "--format=json" ]
257
+ cmd = [uv_executable , "pip" , "list" , "--format=json" ]
186
258
result_obj = subprocess .run (
187
259
cmd ,
188
260
capture_output = True ,
@@ -221,8 +293,8 @@ def _get_installed_uv_packages():
221
293
packages_list = [f"{ p } { python_deps [p ]} " for p in packages_to_install ]
222
294
223
295
cmd = [
224
- "uv" , "pip" , "install" ,
225
- f"--python={ env . subst ( '$PYTHONEXE' ) } " ,
296
+ uv_executable , "pip" , "install" ,
297
+ f"--python={ PYTHON_EXE } " ,
226
298
"--quiet" , "--upgrade"
227
299
] + packages_list
228
300
@@ -254,68 +326,37 @@ def _get_installed_uv_packages():
254
326
return True
255
327
256
328
257
- def install_esptool (env ):
329
+ def install_esptool ():
258
330
"""
259
331
Install esptool from package folder "tool-esptoolpy" using uv package manager.
260
332
Also determines the path to the esptool executable binary.
261
333
262
- Args:
263
- env: SCons environment object
264
-
265
334
Returns:
266
335
str: Path to esptool executable, or 'esptool' as fallback
267
336
"""
268
- def _get_esptool_executable_path (python_exe ):
269
- """
270
- Get the path to the esptool executable binary.
271
-
272
- Args:
273
- python_exe (str): Path to Python executable
274
-
275
- Returns:
276
- str: Path to esptool executable
277
- """
278
- if not python_exe or not os .path .isfile (python_exe ):
279
- return 'esptool' # Fallback
280
-
281
- python_dir = os .path .dirname (python_exe )
282
-
283
- if sys .platform == "win32" :
284
- scripts_dir = os .path .join (python_dir , "Scripts" )
285
- esptool_exe = os .path .join (scripts_dir , "esptool.exe" )
286
- else :
287
- scripts_dir = os .path .join (python_dir )
288
- esptool_exe = os .path .join (scripts_dir , "esptool" )
289
-
290
- if os .path .isfile (esptool_exe ):
291
- return esptool_exe
292
-
293
- return 'esptool'
294
-
295
337
try :
296
338
subprocess .check_call (
297
- [env . subst ( "$PYTHONEXE" ) , "-c" , "import esptool" ],
339
+ [PYTHON_EXE , "-c" , "import esptool" ],
298
340
stdout = subprocess .DEVNULL ,
299
341
stderr = subprocess .DEVNULL ,
300
342
env = os .environ
301
343
)
302
- python_exe = env .subst ("$PYTHONEXE" )
303
- esptool_binary_path = _get_esptool_executable_path (python_exe )
344
+ esptool_binary_path = _get_esptool_executable_path (PYTHON_EXE )
304
345
return esptool_binary_path
305
346
except (subprocess .CalledProcessError , FileNotFoundError ):
306
347
pass
307
348
308
349
esptool_repo_path = env .subst (platform .get_package_dir ("tool-esptoolpy" ) or "" )
309
350
if esptool_repo_path and os .path .isdir (esptool_repo_path ):
351
+ uv_executable = _get_uv_executable_path (PYTHON_EXE )
310
352
try :
311
353
subprocess .check_call ([
312
- "uv" , "pip" , "install" , "--quiet" ,
313
- f"--python={ env . subst ( '$PYTHONEXE' ) } " ,
354
+ uv_executable , "pip" , "install" , "--quiet" ,
355
+ f"--python={ PYTHON_EXE } " ,
314
356
"-e" , esptool_repo_path
315
357
], env = os .environ )
316
358
317
- python_exe = env .subst ("$PYTHONEXE" )
318
- esptool_binary_path = _get_esptool_executable_path (python_exe )
359
+ esptool_binary_path = _get_esptool_executable_path (PYTHON_EXE )
319
360
return esptool_binary_path
320
361
321
362
except subprocess .CalledProcessError as e :
@@ -327,7 +368,7 @@ def _get_esptool_executable_path(python_exe):
327
368
328
369
# Install Python dependencies and esptool
329
370
install_python_deps ()
330
- esptool_binary_path = install_esptool (env )
371
+ esptool_binary_path = install_esptool ()
331
372
332
373
333
374
def BeforeUpload (target , source , env ):
@@ -874,7 +915,7 @@ def firmware_metrics(target, source, env):
874
915
return
875
916
876
917
try :
877
- cmd = [env . subst ( "$PYTHONEXE" ) , "-m" , "esp_idf_size" , "--ng" ]
918
+ cmd = [PYTHON_EXE , "-m" , "esp_idf_size" , "--ng" ]
878
919
879
920
# Parameters from platformio.ini
880
921
extra_args = env .GetProjectOption ("custom_esp_idf_size_args" , "" )
@@ -1002,7 +1043,7 @@ def firmware_metrics(target, source, env):
1002
1043
env .Replace (
1003
1044
UPLOADER = join (FRAMEWORK_DIR , "tools" , "espota.py" ),
1004
1045
UPLOADERFLAGS = ["--debug" , "--progress" , "-i" , "$UPLOAD_PORT" ],
1005
- UPLOADCMD = '"$PYTHONEXE " "$UPLOADER" $UPLOADERFLAGS -f $SOURCE' ,
1046
+ UPLOADCMD = f'" { PYTHON_EXE } " "$UPLOADER" $UPLOADERFLAGS -f $SOURCE' ,
1006
1047
)
1007
1048
if set (["uploadfs" , "uploadfsota" ]) & set (COMMAND_LINE_TARGETS ):
1008
1049
env .Append (UPLOADERFLAGS = ["--spiffs" ])
0 commit comments