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,43 @@ 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 ():
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 )
197115
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+ )
200122
201- return executable_name # Fallback to command name
123+ if os .path .isdir (site_packages ):
124+ site .addsitedir (site_packages )
202125
203126
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 ()
215128
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" )
228132
229133
230134def get_packages_to_install (deps , installed_packages ):
@@ -254,9 +158,6 @@ def install_python_deps():
254158 Returns:
255159 bool: True if successful, False otherwise
256160 """
257- # Get uv executable path
258- uv_executable = _get_uv_executable_path (PYTHON_EXE )
259-
260161 try :
261162 result = subprocess .run (
262163 [uv_executable , "--version" ],
@@ -275,15 +176,12 @@ def install_python_deps():
275176 capture_output = True ,
276177 text = True ,
277178 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
279180 )
280181 if result .returncode != 0 :
281182 if result .stderr :
282183 print (f"Error output: { result .stderr .strip ()} " )
283184 return False
284-
285- # Update uv executable path after installation
286- uv_executable = _get_uv_executable_path (PYTHON_EXE )
287185
288186 except subprocess .TimeoutExpired :
289187 print ("Error: uv installation timed out" )
@@ -312,7 +210,7 @@ def _get_installed_uv_packages():
312210 text = True ,
313211 encoding = 'utf-8' ,
314212 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
316214 )
317215
318216 if result_obj .returncode == 0 :
@@ -355,7 +253,7 @@ def _get_installed_uv_packages():
355253 capture_output = True ,
356254 text = True ,
357255 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
359257 )
360258
361259 if result .returncode != 0 :
@@ -380,9 +278,6 @@ def _get_installed_uv_packages():
380278def install_esptool ():
381279 """
382280 Install esptool from package folder "tool-esptoolpy" using uv package manager.
383-
384- Returns:
385- str: Path to esptool executable
386281
387282 Raises:
388283 SystemExit: If esptool installation fails
@@ -394,35 +289,33 @@ def install_esptool():
394289 stderr = subprocess .DEVNULL ,
395290 env = os .environ
396291 )
397- return _get_esptool_executable_path ( PYTHON_EXE )
292+ return
398293 except (subprocess .CalledProcessError , FileNotFoundError ):
399294 pass
400295
401296 esptool_repo_path = env .subst (platform .get_package_dir ("tool-esptoolpy" ) or "" )
402297 if not esptool_repo_path or not os .path .isdir (esptool_repo_path ):
403298 print ("Error: esptool package directory not found" )
404299 sys .exit (1 )
405-
406- uv_executable = _get_uv_executable_path (PYTHON_EXE )
300+
407301 try :
408302 subprocess .check_call ([
409303 uv_executable , "pip" , "install" , "--quiet" ,
410304 f"--python={ PYTHON_EXE } " ,
411305 "-e" , esptool_repo_path
412306 ], env = os .environ )
413-
414- return _get_esptool_executable_path ( PYTHON_EXE )
415-
307+
308+ return
309+
416310 except subprocess .CalledProcessError as e :
417311 print (f"Error: Failed to install esptool: { e } " )
418312 sys .exit (1 )
419313
420314
421- # Install Python dependencies
315+ # Install espressif32 Python dependencies
422316install_python_deps ()
423-
424317# Install esptool after dependencies
425- esptool_binary_path = install_esptool ()
318+ install_esptool ()
426319
427320
428321def BeforeUpload (target , source , env ):
0 commit comments