3434from platformio .project .helpers import get_project_dir
3535from platformio .package .version import pepver_to_semver
3636from platformio .util import get_serial_ports
37+ from platformio .compat import IS_WINDOWS
3738
3839# Python dependencies required for the build process
3940python_deps = {
5657# Framework directory path
5758FRAMEWORK_DIR = platform .get_package_dir ("framework-arduinoespressif32" )
5859
60+ platformio_dir = projectconfig .get ("platformio" , "core_dir" )
61+ penv_dir = os .path .join (platformio_dir , "penv" )
62+
63+ pip_path = os .path .join (
64+ penv_dir ,
65+ "Scripts" if IS_WINDOWS else "bin" ,
66+ "pip" + (".exe" if IS_WINDOWS else "" ),
67+ )
68+
69+ def setup_pipenv_in_package ():
70+ """
71+ Checks if 'penv' folder exists in platformio dir and creates virtual environment if not.
72+ """
73+ if not os .path .exists (penv_dir ):
74+ env .Execute (
75+ env .VerboseAction (
76+ '"$PYTHONEXE" -m venv --clear "%s"' % penv_dir ,
77+ "Creating a new virtual environment for Python dependencies" ,
78+ )
79+ )
80+
81+ assert os .path .isfile (
82+ pip_path
83+ ), "Error: Failed to create a proper virtual environment. Missing the `pip` binary!"
84+
85+ penv_python = os .path .join (penv_dir , "Scripts" , "python.exe" ) if IS_WINDOWS else os .path .join (penv_dir , "bin" , "python" )
86+ env .Replace (PYTHONEXE = penv_python )
87+ print (f"PYTHONEXE updated to penv environment: { penv_python } " )
88+
89+ setup_pipenv_in_package ()
90+ # Update global PYTHON_EXE variable after potential pipenv setup
91+ PYTHON_EXE = env .subst ("$PYTHONEXE" )
92+ python_exe = PYTHON_EXE
93+
94+ # Ensure penv Python directory is in PATH for subprocess calls
95+ python_dir = os .path .dirname (PYTHON_EXE )
96+ current_path = os .environ .get ("PATH" , "" )
97+ if python_dir not in current_path :
98+ os .environ ["PATH" ] = python_dir + os .pathsep + current_path
99+
100+ # Verify the Python executable exists
101+ assert os .path .isfile (PYTHON_EXE ), f"Python executable not found: { PYTHON_EXE } "
102+
103+ if os .path .isfile (python_exe ):
104+ # Update sys.path to include penv site-packages
105+ if IS_WINDOWS :
106+ penv_site_packages = os .path .join (penv_dir , "Lib" , "site-packages" )
107+ else :
108+ # Find the actual site-packages directory in the venv
109+ penv_lib_dir = os .path .join (penv_dir , "lib" )
110+ if os .path .isdir (penv_lib_dir ):
111+ for python_dir in os .listdir (penv_lib_dir ):
112+ if python_dir .startswith ("python" ):
113+ penv_site_packages = os .path .join (penv_lib_dir , python_dir , "site-packages" )
114+ break
115+ else :
116+ penv_site_packages = None
117+ else :
118+ penv_site_packages = None
119+
120+ if penv_site_packages and os .path .isdir (penv_site_packages ) and penv_site_packages not in sys .path :
121+ sys .path .insert (0 , penv_site_packages )
59122
60123def add_to_pythonpath (path ):
61124 """
@@ -80,14 +143,10 @@ def add_to_pythonpath(path):
80143 if normalized_path not in sys .path :
81144 sys .path .insert (0 , normalized_path )
82145
83-
84146def setup_python_paths ():
85147 """
86148 Setup Python paths based on the actual Python executable being used.
87- """
88- if not PYTHON_EXE or not os .path .isfile (PYTHON_EXE ):
89- return
90-
149+ """
91150 # Get the directory containing the Python executable
92151 python_dir = os .path .dirname (PYTHON_EXE )
93152 add_to_pythonpath (python_dir )
@@ -107,7 +166,6 @@ def setup_python_paths():
107166# Setup Python paths based on the actual Python executable
108167setup_python_paths ()
109168
110-
111169def _get_executable_path (python_exe , executable_name ):
112170 """
113171 Get the path to an executable binary (esptool, uv, etc.) based on the Python executable path.
@@ -119,14 +177,11 @@ def _get_executable_path(python_exe, executable_name):
119177 Returns:
120178 str: Path to executable or fallback to executable name
121179 """
122- if not python_exe or not os .path .isfile (python_exe ):
123- return executable_name # Fallback to command name
124180
125181 python_dir = os .path .dirname (python_exe )
126182
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" )
183+ if IS_WINDOWS :
184+ executable_path = os .path .join (python_dir , f"{ executable_name } .exe" )
130185 else :
131186 # For Unix-like systems, executables are typically in the same directory as python
132187 # or in a bin subdirectory
@@ -228,7 +283,7 @@ def install_python_deps():
228283 uv_executable = _get_uv_executable_path (PYTHON_EXE )
229284
230285 # Add Scripts directory to PATH for Windows
231- if sys . platform == "win32" :
286+ if IS_WINDOWS :
232287 python_dir = os .path .dirname (PYTHON_EXE )
233288 scripts_dir = os .path .join (python_dir , "Scripts" )
234289 if os .path .isdir (scripts_dir ):
@@ -366,8 +421,10 @@ def install_esptool():
366421 return 'esptool' # Fallback
367422
368423
369- # Install Python dependencies and esptool
424+ # Install Python dependencies
370425install_python_deps ()
426+
427+ # Install esptool after dependencies
371428esptool_binary_path = install_esptool ()
372429
373430
@@ -756,7 +813,6 @@ def switch_off_ldf():
756813 if ' ' in esptool_binary_path
757814 else esptool_binary_path
758815)
759-
760816# Configure build tools and environment variables
761817env .Replace (
762818 __get_board_boot_mode = _get_board_boot_mode ,
0 commit comments