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