34
34
from platformio .project .helpers import get_project_dir
35
35
from platformio .package .version import pepver_to_semver
36
36
from platformio .util import get_serial_ports
37
+ from platformio .compat import IS_WINDOWS
37
38
38
39
# Python dependencies required for the build process
39
40
python_deps = {
56
57
# Framework directory path
57
58
FRAMEWORK_DIR = platform .get_package_dir ("framework-arduinoespressif32" )
58
59
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 )
59
122
60
123
def add_to_pythonpath (path ):
61
124
"""
@@ -80,14 +143,10 @@ def add_to_pythonpath(path):
80
143
if normalized_path not in sys .path :
81
144
sys .path .insert (0 , normalized_path )
82
145
83
-
84
146
def setup_python_paths ():
85
147
"""
86
148
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
+ """
91
150
# Get the directory containing the Python executable
92
151
python_dir = os .path .dirname (PYTHON_EXE )
93
152
add_to_pythonpath (python_dir )
@@ -107,7 +166,6 @@ def setup_python_paths():
107
166
# Setup Python paths based on the actual Python executable
108
167
setup_python_paths ()
109
168
110
-
111
169
def _get_executable_path (python_exe , executable_name ):
112
170
"""
113
171
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):
119
177
Returns:
120
178
str: Path to executable or fallback to executable name
121
179
"""
122
- if not python_exe or not os .path .isfile (python_exe ):
123
- return executable_name # Fallback to command name
124
180
125
181
python_dir = os .path .dirname (python_exe )
126
182
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" )
130
185
else :
131
186
# For Unix-like systems, executables are typically in the same directory as python
132
187
# or in a bin subdirectory
@@ -228,7 +283,7 @@ def install_python_deps():
228
283
uv_executable = _get_uv_executable_path (PYTHON_EXE )
229
284
230
285
# Add Scripts directory to PATH for Windows
231
- if sys . platform == "win32" :
286
+ if IS_WINDOWS :
232
287
python_dir = os .path .dirname (PYTHON_EXE )
233
288
scripts_dir = os .path .join (python_dir , "Scripts" )
234
289
if os .path .isdir (scripts_dir ):
@@ -366,8 +421,10 @@ def install_esptool():
366
421
return 'esptool' # Fallback
367
422
368
423
369
- # Install Python dependencies and esptool
424
+ # Install Python dependencies
370
425
install_python_deps ()
426
+
427
+ # Install esptool after dependencies
371
428
esptool_binary_path = install_esptool ()
372
429
373
430
@@ -756,7 +813,6 @@ def switch_off_ldf():
756
813
if ' ' in esptool_binary_path
757
814
else esptool_binary_path
758
815
)
759
-
760
816
# Configure build tools and environment variables
761
817
env .Replace (
762
818
__get_board_boot_mode = _get_board_boot_mode ,
0 commit comments