3737)
3838
3939from platformio import fs
40+ from platformio .compat import IS_WINDOWS
4041from platformio .proc import exec_command
41- from platformio .util import get_systype
4242from platformio .builder .tools .piolib import ProjectAsLibBuilder
4343from platformio .package .version import get_original_version , pepver_to_semver
4444
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+
4551env = DefaultEnvironment ()
4652env .SConscript ("_embed_files.py" , exports = "env" )
4753
5157idf_variant = mcu .lower ()
5258
5359# 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" )
5661FRAMEWORK_DIR = platform .get_package_dir ("framework-espidf" )
5762TOOLCHAIN_DIR = platform .get_package_dir (
5863 "toolchain-%s" % ("riscv32-esp" if mcu == "esp32c3" else ("xtensa-%s" % mcu ))
@@ -224,15 +229,15 @@ def populate_idf_env_vars(idf_env):
224229 os .path .join (TOOLCHAIN_DIR , "bin" ),
225230 platform .get_package_dir ("tool-ninja" ),
226231 os .path .join (platform .get_package_dir ("tool-cmake" ), "bin" ),
227- os .path .dirname (env . subst ( "$PYTHONEXE" )),
232+ os .path .dirname (get_python_exe ( )),
228233 ]
229234
230235 if mcu != "esp32c3" :
231236 additional_packages .append (
232237 os .path .join (platform .get_package_dir ("toolchain-esp32ulp" ), "bin" ),
233238 )
234239
235- if "windows" in get_systype () :
240+ if IS_WINDOWS :
236241 additional_packages .append (platform .get_package_dir ("tool-mconf" ))
237242
238243 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):
559564 }
560565
561566 cmd = (
562- '"$PYTHONEXE " "{script}" --input $SOURCE '
567+ '"$ESPIDF_PYTHONEXE " "{script}" --input $SOURCE '
563568 '--config "{config}" --fragments {fragments} --output $TARGET '
564569 '--kconfig "{kconfig}" --env-file "{env_file}" '
565570 '--libraries-file "{libraries_list}" '
@@ -763,7 +768,7 @@ def build_bootloader(sdk_config):
763768 [
764769 "-DIDF_TARGET=" + idf_variant ,
765770 "-DPYTHON_DEPS_CHECKED=1" ,
766- "-DPYTHON=" + env . subst ( "$PYTHONEXE" ),
771+ "-DPYTHON=" + get_python_exe ( ),
767772 "-DIDF_PATH=" + FRAMEWORK_DIR ,
768773 "-DSDKCONFIG=" + SDKCONFIG_PATH ,
769774 "-DLEGACY_INCLUDE_COMMON_HEADERS=" ,
@@ -918,7 +923,7 @@ def generate_empty_partition_image(binary_path, image_size):
918923 binary_path ,
919924 None ,
920925 env .VerboseAction (
921- '"$PYTHONEXE " "%s" %s $TARGET'
926+ '"$ESPIDF_PYTHONEXE " "%s" %s $TARGET'
922927 % (
923928 os .path .join (
924929 FRAMEWORK_DIR ,
@@ -943,7 +948,7 @@ def get_partition_info(pt_path, pt_offset, pt_params):
943948 env .Exit (1 )
944949
945950 cmd = [
946- env . subst ( "$PYTHONEXE" ),
951+ get_python_exe ( ),
947952 os .path .join (FRAMEWORK_DIR , "components" , "partition_table" , "parttool.py" ),
948953 "-q" ,
949954 "--partition-table-offset" ,
@@ -1000,7 +1005,7 @@ def generate_mbedtls_bundle(sdk_config):
10001005 FRAMEWORK_DIR , "components" , "mbedtls" , "esp_crt_bundle"
10011006 )
10021007
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" )]
10041009
10051010 crt_args = ["--input" ]
10061011 if sdk_config .get ("MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL" , False ):
@@ -1051,12 +1056,12 @@ def generate_mbedtls_bundle(sdk_config):
10511056
10521057
10531058def install_python_deps ():
1054- def _get_installed_pip_packages ():
1059+ def _get_installed_pip_packages (python_exe_path ):
10551060 result = {}
10561061 packages = {}
10571062 pip_output = subprocess .check_output (
10581063 [
1059- env . subst ( "$PYTHONEXE" ) ,
1064+ python_exe_path ,
10601065 "-m" ,
10611066 "pip" ,
10621067 "list" ,
@@ -1087,7 +1092,8 @@ def _get_installed_pip_packages():
10871092 # Remove specific versions for IDF5 as not required
10881093 deps = {dep : "" for dep in deps }
10891094
1090- installed_packages = _get_installed_pip_packages ()
1095+ python_exe_path = get_python_exe ()
1096+ installed_packages = _get_installed_pip_packages (python_exe_path )
10911097 packages_to_install = []
10921098 for package , spec in deps .items ():
10931099 if package not in installed_packages :
@@ -1101,22 +1107,17 @@ def _get_installed_pip_packages():
11011107 env .Execute (
11021108 env .VerboseAction (
11031109 (
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 ])
11111112 ),
11121113 "Installing ESP-IDF's Python dependencies" ,
11131114 )
11141115 )
11151116
1116- if "windows" in get_systype () and "windows-curses" not in installed_packages :
1117+ if IS_WINDOWS and "windows-curses" not in installed_packages :
11171118 env .Execute (
11181119 env .VerboseAction (
1119- "$PYTHONEXE -m pip install windows-curses" ,
1120+ '"%s" -m pip install windows-curses' % python_exe_path ,
11201121 "Installing windows-curses package" ,
11211122 )
11221123 )
@@ -1128,15 +1129,59 @@ def _get_installed_pip_packages():
11281129 }:
11291130 env .Execute (
11301131 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 ) ,
11331134 "Installing windows-curses package" ,
11341135 )
11351136 )
11361137
11371138
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+
11381183#
1139- # ESP-IDF requires Python packages with specific versions
1184+ # ESP-IDF requires Python packages with specific versions in a virtual environment
11401185#
11411186
11421187install_python_deps ()
@@ -1235,7 +1280,7 @@ def _get_installed_pip_packages():
12351280 "-DIDF_TARGET=" + idf_variant ,
12361281 "-DPYTHON_DEPS_CHECKED=1" ,
12371282 "-DEXTRA_COMPONENT_DIRS:PATH=" + ";" .join (extra_components ),
1238- "-DPYTHON=" + env . subst ( "$PYTHONEXE" ),
1283+ "-DPYTHON=" + get_python_exe ( ),
12391284 "-DSDKCONFIG=" + SDKCONFIG_PATH ,
12401285 ]
12411286 + click .parser .split_arg_string (board .get ("build.cmake_extra_args" , "" )),
@@ -1373,7 +1418,7 @@ def _skip_prj_source_files(node):
13731418 os .path .join ("$BUILD_DIR" , "partitions.bin" ),
13741419 "$PARTITIONS_TABLE_CSV" ,
13751420 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'
13771422 % (
13781423 os .path .join (
13791424 FRAMEWORK_DIR , "components" , "partition_table" , "gen_esp32part.py"
@@ -1396,6 +1441,7 @@ def _skip_prj_source_files(node):
13961441env .Prepend (
13971442 CPPPATH = app_includes ["plain_includes" ],
13981443 CPPDEFINES = project_defines ,
1444+ ESPIDF_PYTHONEXE = get_python_exe (),
13991445 LINKFLAGS = extra_flags ,
14001446 LIBS = libs ,
14011447 FLASH_EXTRA_IMAGES = [
0 commit comments