5151platform = env .PioPlatform ()
5252projectconfig = env .GetProjectConfig ()
5353terminal_cp = locale .getpreferredencoding ().lower ()
54+ PYTHON_EXE = env .subst ("$PYTHONEXE" ) # Global Python executable path
5455
5556# Framework directory path
5657FRAMEWORK_DIR = platform .get_package_dir ("framework-arduinoespressif32" )
@@ -80,24 +81,20 @@ def add_to_pythonpath(path):
8081 sys .path .insert (0 , normalized_path )
8182
8283
83- def setup_python_paths (env ):
84+ def setup_python_paths ():
8485 """
8586 Setup Python paths based on the actual Python executable being used.
86-
87- Args:
88- env: SCons environment object
8987 """
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 ):
9289 return
9390
9491 # Get the directory containing the Python executable
95- python_dir = os .path .dirname (python_exe )
92+ python_dir = os .path .dirname (PYTHON_EXE )
9693 add_to_pythonpath (python_dir )
9794
9895 # Try to find site-packages directory using the actual Python executable
9996 result = subprocess .run (
100- [python_exe , "-c" , "import site; print(site.getsitepackages()[0])" ],
97+ [PYTHON_EXE , "-c" , "import site; print(site.getsitepackages()[0])" ],
10198 capture_output = True ,
10299 text = True ,
103100 timeout = 5
@@ -108,7 +105,68 @@ def setup_python_paths(env):
108105 add_to_pythonpath (site_packages )
109106
110107# 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" )
112170
113171
114172def get_packages_to_install (deps , installed_packages ):
@@ -138,9 +196,12 @@ def install_python_deps():
138196 Returns:
139197 bool: True if successful, False otherwise
140198 """
199+ # Get uv executable path
200+ uv_executable = _get_uv_executable_path (PYTHON_EXE )
201+
141202 try :
142203 result = subprocess .run (
143- ["uv" , "--version" ],
204+ [uv_executable , "--version" ],
144205 capture_output = True ,
145206 text = True ,
146207 timeout = 3
@@ -152,7 +213,7 @@ def install_python_deps():
152213 if not uv_available :
153214 try :
154215 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" ],
156217 capture_output = True ,
157218 text = True ,
158219 timeout = 30 , # 30 second timeout
@@ -162,6 +223,17 @@ def install_python_deps():
162223 if result .stderr :
163224 print (f"Error output: { result .stderr .strip ()} " )
164225 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+
165237 except subprocess .TimeoutExpired :
166238 print ("Error: uv installation timed out" )
167239 return False
@@ -182,7 +254,7 @@ def _get_installed_uv_packages():
182254 """
183255 result = {}
184256 try :
185- cmd = ["uv" , "pip" , "list" , "--format=json" ]
257+ cmd = [uv_executable , "pip" , "list" , "--format=json" ]
186258 result_obj = subprocess .run (
187259 cmd ,
188260 capture_output = True ,
@@ -221,8 +293,8 @@ def _get_installed_uv_packages():
221293 packages_list = [f"{ p } { python_deps [p ]} " for p in packages_to_install ]
222294
223295 cmd = [
224- "uv" , "pip" , "install" ,
225- f"--python={ env . subst ( '$PYTHONEXE' ) } " ,
296+ uv_executable , "pip" , "install" ,
297+ f"--python={ PYTHON_EXE } " ,
226298 "--quiet" , "--upgrade"
227299 ] + packages_list
228300
@@ -254,68 +326,37 @@ def _get_installed_uv_packages():
254326 return True
255327
256328
257- def install_esptool (env ):
329+ def install_esptool ():
258330 """
259331 Install esptool from package folder "tool-esptoolpy" using uv package manager.
260332 Also determines the path to the esptool executable binary.
261333
262- Args:
263- env: SCons environment object
264-
265334 Returns:
266335 str: Path to esptool executable, or 'esptool' as fallback
267336 """
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-
295337 try :
296338 subprocess .check_call (
297- [env . subst ( "$PYTHONEXE" ) , "-c" , "import esptool" ],
339+ [PYTHON_EXE , "-c" , "import esptool" ],
298340 stdout = subprocess .DEVNULL ,
299341 stderr = subprocess .DEVNULL ,
300342 env = os .environ
301343 )
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 )
304345 return esptool_binary_path
305346 except (subprocess .CalledProcessError , FileNotFoundError ):
306347 pass
307348
308349 esptool_repo_path = env .subst (platform .get_package_dir ("tool-esptoolpy" ) or "" )
309350 if esptool_repo_path and os .path .isdir (esptool_repo_path ):
351+ uv_executable = _get_uv_executable_path (PYTHON_EXE )
310352 try :
311353 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 } " ,
314356 "-e" , esptool_repo_path
315357 ], env = os .environ )
316358
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 )
319360 return esptool_binary_path
320361
321362 except subprocess .CalledProcessError as e :
@@ -327,7 +368,7 @@ def _get_esptool_executable_path(python_exe):
327368
328369# Install Python dependencies and esptool
329370install_python_deps ()
330- esptool_binary_path = install_esptool (env )
371+ esptool_binary_path = install_esptool ()
331372
332373
333374def BeforeUpload (target , source , env ):
@@ -874,7 +915,7 @@ def firmware_metrics(target, source, env):
874915 return
875916
876917 try :
877- cmd = [env . subst ( "$PYTHONEXE" ) , "-m" , "esp_idf_size" , "--ng" ]
918+ cmd = [PYTHON_EXE , "-m" , "esp_idf_size" , "--ng" ]
878919
879920 # Parameters from platformio.ini
880921 extra_args = env .GetProjectOption ("custom_esp_idf_size_args" , "" )
@@ -1002,7 +1043,7 @@ def firmware_metrics(target, source, env):
10021043 env .Replace (
10031044 UPLOADER = join (FRAMEWORK_DIR , "tools" , "espota.py" ),
10041045 UPLOADERFLAGS = ["--debug" , "--progress" , "-i" , "$UPLOAD_PORT" ],
1005- UPLOADCMD = '"$PYTHONEXE " "$UPLOADER" $UPLOADERFLAGS -f $SOURCE' ,
1046+ UPLOADCMD = f'" { PYTHON_EXE } " "$UPLOADER" $UPLOADERFLAGS -f $SOURCE' ,
10061047 )
10071048 if set (["uploadfs" , "uploadfsota" ]) & set (COMMAND_LINE_TARGETS ):
10081049 env .Append (UPLOADERFLAGS = ["--spiffs" ])
0 commit comments