37
37
)
38
38
39
39
from platformio import fs
40
+ from platformio .compat import IS_WINDOWS
40
41
from platformio .proc import exec_command
41
- from platformio .util import get_systype
42
42
from platformio .builder .tools .piolib import ProjectAsLibBuilder
43
43
from platformio .package .version import get_original_version , pepver_to_semver
44
44
45
+ # Added to avoid conflicts between installed Python packages from
46
+ # the IDF virtual environment and PlatformIO Core
47
+ # Note: This workaround can be safely deleted when PlatformIO 6.1.7 is released
48
+ if os .environ .get ("PYTHONPATH" ):
49
+ del os .environ ["PYTHONPATH" ]
50
+
45
51
env = DefaultEnvironment ()
46
52
env .SConscript ("_embed_files.py" , exports = "env" )
47
53
51
57
idf_variant = mcu .lower ()
52
58
53
59
# Required until Arduino switches to v5
54
- IDF5 = platform .get_package_version (
55
- "framework-espidf" ).split ("." )[1 ].startswith ("5" )
60
+ IDF5 = platform .get_package_version ("framework-espidf" ).split ("." )[1 ].startswith ("5" )
56
61
FRAMEWORK_DIR = platform .get_package_dir ("framework-espidf" )
57
62
TOOLCHAIN_DIR = platform .get_package_dir (
58
63
"toolchain-%s" % ("riscv32-esp" if mcu == "esp32c3" else ("xtensa-%s" % mcu ))
@@ -224,15 +229,15 @@ def populate_idf_env_vars(idf_env):
224
229
os .path .join (TOOLCHAIN_DIR , "bin" ),
225
230
platform .get_package_dir ("tool-ninja" ),
226
231
os .path .join (platform .get_package_dir ("tool-cmake" ), "bin" ),
227
- os .path .dirname (env . subst ( "$PYTHONEXE" )),
232
+ os .path .dirname (get_python_exe ( )),
228
233
]
229
234
230
235
if mcu != "esp32c3" :
231
236
additional_packages .append (
232
237
os .path .join (platform .get_package_dir ("toolchain-esp32ulp" ), "bin" ),
233
238
)
234
239
235
- if "windows" in get_systype () :
240
+ if IS_WINDOWS :
236
241
additional_packages .append (platform .get_package_dir ("tool-mconf" ))
237
242
238
243
idf_env ["PATH" ] = os .pathsep .join (additional_packages + [idf_env ["PATH" ]])
@@ -559,7 +564,7 @@ def generate_project_ld_script(sdk_config, ignore_targets=None):
559
564
}
560
565
561
566
cmd = (
562
- '"$PYTHONEXE " "{script}" --input $SOURCE '
567
+ '"$ESPIDF_PYTHONEXE " "{script}" --input $SOURCE '
563
568
'--config "{config}" --fragments {fragments} --output $TARGET '
564
569
'--kconfig "{kconfig}" --env-file "{env_file}" '
565
570
'--libraries-file "{libraries_list}" '
@@ -763,7 +768,7 @@ def build_bootloader(sdk_config):
763
768
[
764
769
"-DIDF_TARGET=" + idf_variant ,
765
770
"-DPYTHON_DEPS_CHECKED=1" ,
766
- "-DPYTHON=" + env . subst ( "$PYTHONEXE" ),
771
+ "-DPYTHON=" + get_python_exe ( ),
767
772
"-DIDF_PATH=" + FRAMEWORK_DIR ,
768
773
"-DSDKCONFIG=" + SDKCONFIG_PATH ,
769
774
"-DLEGACY_INCLUDE_COMMON_HEADERS=" ,
@@ -918,7 +923,7 @@ def generate_empty_partition_image(binary_path, image_size):
918
923
binary_path ,
919
924
None ,
920
925
env .VerboseAction (
921
- '"$PYTHONEXE " "%s" %s $TARGET'
926
+ '"$ESPIDF_PYTHONEXE " "%s" %s $TARGET'
922
927
% (
923
928
os .path .join (
924
929
FRAMEWORK_DIR ,
@@ -943,7 +948,7 @@ def get_partition_info(pt_path, pt_offset, pt_params):
943
948
env .Exit (1 )
944
949
945
950
cmd = [
946
- env . subst ( "$PYTHONEXE" ),
951
+ get_python_exe ( ),
947
952
os .path .join (FRAMEWORK_DIR , "components" , "partition_table" , "parttool.py" ),
948
953
"-q" ,
949
954
"--partition-table-offset" ,
@@ -1000,7 +1005,7 @@ def generate_mbedtls_bundle(sdk_config):
1000
1005
FRAMEWORK_DIR , "components" , "mbedtls" , "esp_crt_bundle"
1001
1006
)
1002
1007
1003
- cmd = [env . subst ( "$PYTHONEXE" ), os .path .join (default_crt_dir , "gen_crt_bundle.py" )]
1008
+ cmd = [get_python_exe ( ), os .path .join (default_crt_dir , "gen_crt_bundle.py" )]
1004
1009
1005
1010
crt_args = ["--input" ]
1006
1011
if sdk_config .get ("MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL" , False ):
@@ -1051,12 +1056,12 @@ def generate_mbedtls_bundle(sdk_config):
1051
1056
1052
1057
1053
1058
def install_python_deps ():
1054
- def _get_installed_pip_packages ():
1059
+ def _get_installed_pip_packages (python_exe_path ):
1055
1060
result = {}
1056
1061
packages = {}
1057
1062
pip_output = subprocess .check_output (
1058
1063
[
1059
- env . subst ( "$PYTHONEXE" ) ,
1064
+ python_exe_path ,
1060
1065
"-m" ,
1061
1066
"pip" ,
1062
1067
"list" ,
@@ -1087,7 +1092,8 @@ def _get_installed_pip_packages():
1087
1092
# Remove specific versions for IDF5 as not required
1088
1093
deps = {dep : "" for dep in deps }
1089
1094
1090
- installed_packages = _get_installed_pip_packages ()
1095
+ python_exe_path = get_python_exe ()
1096
+ installed_packages = _get_installed_pip_packages (python_exe_path )
1091
1097
packages_to_install = []
1092
1098
for package , spec in deps .items ():
1093
1099
if package not in installed_packages :
@@ -1101,22 +1107,17 @@ def _get_installed_pip_packages():
1101
1107
env .Execute (
1102
1108
env .VerboseAction (
1103
1109
(
1104
- '"$PYTHONEXE" -m pip install -U '
1105
- + " " .join (
1106
- [
1107
- '"%s%s"' % (p , deps [p ])
1108
- for p in packages_to_install
1109
- ]
1110
- )
1110
+ '"%s" -m pip install -U ' % python_exe_path
1111
+ + " " .join (['"%s%s"' % (p , deps [p ]) for p in packages_to_install ])
1111
1112
),
1112
1113
"Installing ESP-IDF's Python dependencies" ,
1113
1114
)
1114
1115
)
1115
1116
1116
- if "windows" in get_systype () and "windows-curses" not in installed_packages :
1117
+ if IS_WINDOWS and "windows-curses" not in installed_packages :
1117
1118
env .Execute (
1118
1119
env .VerboseAction (
1119
- "$PYTHONEXE -m pip install windows-curses" ,
1120
+ '"%s" -m pip install windows-curses' % python_exe_path ,
1120
1121
"Installing windows-curses package" ,
1121
1122
)
1122
1123
)
@@ -1128,15 +1129,59 @@ def _get_installed_pip_packages():
1128
1129
}:
1129
1130
env .Execute (
1130
1131
env .VerboseAction (
1131
- '$PYTHONEXE -m pip install "file://%s/tools/kconfig_new/esp-windows-curses"'
1132
- % FRAMEWORK_DIR ,
1132
+ '"%s" -m pip install "file://%s/tools/kconfig_new/esp-windows-curses"'
1133
+ % ( python_exe_path , FRAMEWORK_DIR ) ,
1133
1134
"Installing windows-curses package" ,
1134
1135
)
1135
1136
)
1136
1137
1137
1138
1139
+ def get_python_exe ():
1140
+ def _create_venv (venv_dir ):
1141
+ pip_path = os .path .join (
1142
+ venv_dir ,
1143
+ "Scripts" if IS_WINDOWS else "bin" ,
1144
+ "pip" + (".exe" if IS_WINDOWS else "" ),
1145
+ )
1146
+ if not os .path .isfile (pip_path ):
1147
+ # Use the built-in PlatformIO Python to create a standalone IDF virtual env
1148
+ env .Execute (
1149
+ env .VerboseAction (
1150
+ '"$PYTHONEXE" -m venv --clear "%s"' % venv_dir ,
1151
+ "Creating a virtual environment for IDF Python dependencies" ,
1152
+ )
1153
+ )
1154
+
1155
+ assert os .path .isfile (
1156
+ pip_path
1157
+ ), "Error: Failed to create a proper virtual environment. Missing the pip binary!"
1158
+
1159
+ # The name of the IDF venv contains the IDF version to avoid possible conflicts and
1160
+ # unnecessary reinstallation of Python dependencies in cases when Arduino
1161
+ # as an IDF component requires a different version of the IDF package and
1162
+ # hence a different set of Python deps or their versions
1163
+ idf_version = get_original_version (platform .get_package_version ("framework-espidf" ))
1164
+ venv_dir = os .path .join (
1165
+ env .subst ("$PROJECT_CORE_DIR" ), "penv" , ".espidf-" + idf_version )
1166
+
1167
+ if not os .path .isdir (venv_dir ):
1168
+ _create_venv (venv_dir )
1169
+
1170
+ python_exe_path = os .path .join (
1171
+ venv_dir ,
1172
+ "Scripts" if IS_WINDOWS else "bin" ,
1173
+ "python" + (".exe" if IS_WINDOWS else "" ),
1174
+ )
1175
+
1176
+ assert os .path .isfile (python_exe_path ), (
1177
+ "Error: Missing Python executable file `%s`" % python_exe_path
1178
+ )
1179
+
1180
+ return python_exe_path
1181
+
1182
+
1138
1183
#
1139
- # ESP-IDF requires Python packages with specific versions
1184
+ # ESP-IDF requires Python packages with specific versions in a virtual environment
1140
1185
#
1141
1186
1142
1187
install_python_deps ()
@@ -1235,7 +1280,7 @@ def _get_installed_pip_packages():
1235
1280
"-DIDF_TARGET=" + idf_variant ,
1236
1281
"-DPYTHON_DEPS_CHECKED=1" ,
1237
1282
"-DEXTRA_COMPONENT_DIRS:PATH=" + ";" .join (extra_components ),
1238
- "-DPYTHON=" + env . subst ( "$PYTHONEXE" ),
1283
+ "-DPYTHON=" + get_python_exe ( ),
1239
1284
"-DSDKCONFIG=" + SDKCONFIG_PATH ,
1240
1285
]
1241
1286
+ click .parser .split_arg_string (board .get ("build.cmake_extra_args" , "" )),
@@ -1373,7 +1418,7 @@ def _skip_prj_source_files(node):
1373
1418
os .path .join ("$BUILD_DIR" , "partitions.bin" ),
1374
1419
"$PARTITIONS_TABLE_CSV" ,
1375
1420
env .VerboseAction (
1376
- '"$PYTHONEXE " "%s" -q --offset "%s" --flash-size "%s" $SOURCE $TARGET'
1421
+ '"$ESPIDF_PYTHONEXE " "%s" -q --offset "%s" --flash-size "%s" $SOURCE $TARGET'
1377
1422
% (
1378
1423
os .path .join (
1379
1424
FRAMEWORK_DIR , "components" , "partition_table" , "gen_esp32part.py"
@@ -1396,6 +1441,7 @@ def _skip_prj_source_files(node):
1396
1441
env .Prepend (
1397
1442
CPPPATH = app_includes ["plain_includes" ],
1398
1443
CPPDEFINES = project_defines ,
1444
+ ESPIDF_PYTHONEXE = get_python_exe (),
1399
1445
LINKFLAGS = extra_flags ,
1400
1446
LIBS = libs ,
1401
1447
FLASH_EXTRA_IMAGES = [
0 commit comments