Skip to content

Commit 8507dd8

Browse files
committed
Better handling of ESP IDF Python dependencies
#1006, #1007, resolves #1013 Now IDF Python deps are installed in a pre-сreated virtual environment. The name of the IDF venv contains the IDF version to avoid possible conflicts and unnecessary reinstallation of Python dependencies in cases when Arduino as an IDF component requires a different version of the IDF package and hence a different set of Python deps or their versions
1 parent f3d3c29 commit 8507dd8

File tree

1 file changed

+73
-27
lines changed

1 file changed

+73
-27
lines changed

builder/frameworks/espidf.py

Lines changed: 73 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,17 @@
3737
)
3838

3939
from platformio import fs
40+
from platformio.compat import IS_WINDOWS
4041
from platformio.proc import exec_command
41-
from platformio.util import get_systype
4242
from platformio.builder.tools.piolib import ProjectAsLibBuilder
4343
from 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+
4551
env = DefaultEnvironment()
4652
env.SConscript("_embed_files.py", exports="env")
4753

@@ -51,8 +57,7 @@
5157
idf_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")
5661
FRAMEWORK_DIR = platform.get_package_dir("framework-espidf")
5762
TOOLCHAIN_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

10531058
def 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

11421187
install_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):
13961441
env.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

Comments
 (0)