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
38
+
39
+ # Check Python version requirement
40
+ if sys .version_info < (3 , 10 ):
41
+ sys .stderr .write (
42
+ f"Error: Python 3.10 or higher is required. "
43
+ f"Current version: { sys .version_info .major } .{ sys .version_info .minor } .{ sys .version_info .micro } \n "
44
+ f"Please update your Python installation.\n "
45
+ )
46
+ sys .exit (1 )
37
47
38
48
# Python dependencies required for the build process
39
49
python_deps = {
56
66
# Framework directory path
57
67
FRAMEWORK_DIR = platform .get_package_dir ("framework-arduinoespressif32" )
58
68
69
+ platformio_dir = projectconfig .get ("platformio" , "core_dir" )
70
+ penv_dir = os .path .join (platformio_dir , "penv" )
71
+
72
+ pip_path = os .path .join (
73
+ penv_dir ,
74
+ "Scripts" if IS_WINDOWS else "bin" ,
75
+ "pip" + (".exe" if IS_WINDOWS else "" ),
76
+ )
77
+
78
+ def setup_pipenv_in_package ():
79
+ """
80
+ Checks if 'penv' folder exists in platformio dir and creates virtual environment if not.
81
+ """
82
+ if not os .path .exists (penv_dir ):
83
+ env .Execute (
84
+ env .VerboseAction (
85
+ '"$PYTHONEXE" -m venv --clear "%s"' % penv_dir ,
86
+ "Creating a new virtual environment for Python dependencies" ,
87
+ )
88
+ )
89
+
90
+ assert os .path .isfile (
91
+ pip_path
92
+ ), "Error: Failed to create a proper virtual environment. Missing the `pip` binary!"
93
+
94
+ penv_python = os .path .join (penv_dir , "Scripts" , "python.exe" ) if IS_WINDOWS else os .path .join (penv_dir , "bin" , "python" )
95
+ env .Replace (PYTHONEXE = penv_python )
96
+ print (f"PYTHONEXE updated to penv environment: { penv_python } " )
97
+
98
+ setup_pipenv_in_package ()
99
+ # Update global PYTHON_EXE variable after potential pipenv setup
100
+ PYTHON_EXE = env .subst ("$PYTHONEXE" )
101
+ python_exe = PYTHON_EXE
102
+
103
+ # Ensure penv Python directory is in PATH for subprocess calls
104
+ python_dir = os .path .dirname (PYTHON_EXE )
105
+ current_path = os .environ .get ("PATH" , "" )
106
+ if python_dir not in current_path :
107
+ os .environ ["PATH" ] = python_dir + os .pathsep + current_path
108
+
109
+ # Verify the Python executable exists
110
+ assert os .path .isfile (PYTHON_EXE ), f"Python executable not found: { PYTHON_EXE } "
111
+
112
+ if os .path .isfile (python_exe ):
113
+ # Update sys.path to include penv site-packages
114
+ if IS_WINDOWS :
115
+ penv_site_packages = os .path .join (penv_dir , "Lib" , "site-packages" )
116
+ else :
117
+ # Find the actual site-packages directory in the venv
118
+ penv_lib_dir = os .path .join (penv_dir , "lib" )
119
+ if os .path .isdir (penv_lib_dir ):
120
+ for python_dir in os .listdir (penv_lib_dir ):
121
+ if python_dir .startswith ("python" ):
122
+ penv_site_packages = os .path .join (penv_lib_dir , python_dir , "site-packages" )
123
+ break
124
+ else :
125
+ penv_site_packages = None
126
+ else :
127
+ penv_site_packages = None
128
+
129
+ if penv_site_packages and os .path .isdir (penv_site_packages ) and penv_site_packages not in sys .path :
130
+ sys .path .insert (0 , penv_site_packages )
59
131
60
132
def add_to_pythonpath (path ):
61
133
"""
@@ -80,14 +152,10 @@ def add_to_pythonpath(path):
80
152
if normalized_path not in sys .path :
81
153
sys .path .insert (0 , normalized_path )
82
154
83
-
84
155
def setup_python_paths ():
85
156
"""
86
157
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
-
158
+ """
91
159
# Get the directory containing the Python executable
92
160
python_dir = os .path .dirname (PYTHON_EXE )
93
161
add_to_pythonpath (python_dir )
@@ -107,7 +175,6 @@ def setup_python_paths():
107
175
# Setup Python paths based on the actual Python executable
108
176
setup_python_paths ()
109
177
110
-
111
178
def _get_executable_path (python_exe , executable_name ):
112
179
"""
113
180
Get the path to an executable binary (esptool, uv, etc.) based on the Python executable path.
@@ -119,14 +186,11 @@ def _get_executable_path(python_exe, executable_name):
119
186
Returns:
120
187
str: Path to executable or fallback to executable name
121
188
"""
122
- if not python_exe or not os .path .isfile (python_exe ):
123
- return executable_name # Fallback to command name
124
189
125
190
python_dir = os .path .dirname (python_exe )
126
191
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" )
192
+ if IS_WINDOWS :
193
+ executable_path = os .path .join (python_dir , f"{ executable_name } .exe" )
130
194
else :
131
195
# For Unix-like systems, executables are typically in the same directory as python
132
196
# or in a bin subdirectory
@@ -228,7 +292,7 @@ def install_python_deps():
228
292
uv_executable = _get_uv_executable_path (PYTHON_EXE )
229
293
230
294
# Add Scripts directory to PATH for Windows
231
- if sys . platform == "win32" :
295
+ if IS_WINDOWS :
232
296
python_dir = os .path .dirname (PYTHON_EXE )
233
297
scripts_dir = os .path .join (python_dir , "Scripts" )
234
298
if os .path .isdir (scripts_dir ):
@@ -366,8 +430,10 @@ def install_esptool():
366
430
return 'esptool' # Fallback
367
431
368
432
369
- # Install Python dependencies and esptool
433
+ # Install Python dependencies
370
434
install_python_deps ()
435
+
436
+ # Install esptool after dependencies
371
437
esptool_binary_path = install_esptool ()
372
438
373
439
@@ -756,7 +822,6 @@ def switch_off_ldf():
756
822
if ' ' in esptool_binary_path
757
823
else esptool_binary_path
758
824
)
759
-
760
825
# Configure build tools and environment variables
761
826
env .Replace (
762
827
__get_board_boot_mode = _get_board_boot_mode ,
0 commit comments