1616import json
1717import os
1818import re
19+ import site
1920import semantic_version
2021import shlex
2122import subprocess
6162platform = env .PioPlatform ()
6263projectconfig = env .GetProjectConfig ()
6364terminal_cp = locale .getpreferredencoding ().lower ()
64- PYTHON_EXE = env .subst ("$PYTHONEXE" ) # Global Python executable path
65-
66- # Framework directory path
6765FRAMEWORK_DIR = platform .get_package_dir ("framework-arduinoespressif32" )
68-
6966platformio_dir = projectconfig .get ("platformio" , "core_dir" )
67+
68+ # Global Python executable path, replaced later with venv python path
69+ PYTHON_EXE = env .subst ("$PYTHONEXE" )
7070penv_dir = os .path .join (platformio_dir , "penv" )
7171
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+
7782
7883def setup_pipenv_in_package ():
7984 """
@@ -87,144 +92,49 @@ def setup_pipenv_in_package():
8792 )
8893 )
8994 assert os .path .isfile (
90- pip_path
95+ get_executable_path ( "pip" )
9196 ), "Error: Failed to create a proper virtual environment. Missing the `pip` binary!"
9297
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 )
9598
96- # Setup virtual environment if needed and find path to Python exe
99+ # Setup virtual environment if needed
97100setup_pipenv_in_package ()
101+
98102# 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
102106
103107# check for python binary, exit with error when not found
104108assert os .path .isfile (PYTHON_EXE ), f"Python executable not found: { PYTHON_EXE } "
105109
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-
131110
132111def setup_python_paths ():
133112 """
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.
138114 """
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 )
185117
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+ )
200128
201- return executable_name # Fallback to command name
129+ if site_packages and os .path .isdir (site_packages ):
130+ site .addsitedir (site_packages )
202131
203132
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 ()
216134
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" )
228138
229139
230140def get_packages_to_install (deps , installed_packages ):
@@ -254,9 +164,6 @@ def install_python_deps():
254164 Returns:
255165 bool: True if successful, False otherwise
256166 """
257- # Get uv executable path
258- uv_executable = _get_uv_executable_path (PYTHON_EXE )
259-
260167 try :
261168 result = subprocess .run (
262169 [uv_executable , "--version" ],
@@ -275,15 +182,12 @@ def install_python_deps():
275182 capture_output = True ,
276183 text = True ,
277184 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
279186 )
280187 if result .returncode != 0 :
281188 if result .stderr :
282189 print (f"Error output: { result .stderr .strip ()} " )
283190 return False
284-
285- # Update uv executable path after installation
286- uv_executable = _get_uv_executable_path (PYTHON_EXE )
287191
288192 except subprocess .TimeoutExpired :
289193 print ("Error: uv installation timed out" )
@@ -312,7 +216,7 @@ def _get_installed_uv_packages():
312216 text = True ,
313217 encoding = 'utf-8' ,
314218 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
316220 )
317221
318222 if result_obj .returncode == 0 :
@@ -355,7 +259,7 @@ def _get_installed_uv_packages():
355259 capture_output = True ,
356260 text = True ,
357261 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
359263 )
360264
361265 if result .returncode != 0 :
@@ -394,35 +298,33 @@ def install_esptool():
394298 stderr = subprocess .DEVNULL ,
395299 env = os .environ
396300 )
397- return _get_esptool_executable_path ( PYTHON_EXE )
301+ return
398302 except (subprocess .CalledProcessError , FileNotFoundError ):
399303 pass
400304
401305 esptool_repo_path = env .subst (platform .get_package_dir ("tool-esptoolpy" ) or "" )
402306 if not esptool_repo_path or not os .path .isdir (esptool_repo_path ):
403307 print ("Error: esptool package directory not found" )
404308 sys .exit (1 )
405-
406- uv_executable = _get_uv_executable_path (PYTHON_EXE )
309+
407310 try :
408311 subprocess .check_call ([
409312 uv_executable , "pip" , "install" , "--quiet" ,
410313 f"--python={ PYTHON_EXE } " ,
411314 "-e" , esptool_repo_path
412315 ], env = os .environ )
413316
414- return _get_esptool_executable_path ( PYTHON_EXE )
317+ return
415318
416319 except subprocess .CalledProcessError as e :
417320 print (f"Error: Failed to install esptool: { e } " )
418321 sys .exit (1 )
419322
420323
421- # Install Python dependencies
324+ # Install espressif32 Python dependencies
422325install_python_deps ()
423-
424326# Install esptool after dependencies
425- esptool_binary_path = install_esptool ()
327+ install_esptool ()
426328
427329
428330def BeforeUpload (target , source , env ):
0 commit comments