Skip to content

Commit 5384f15

Browse files
committed
Initial support for ESP-IDF v4.3
Resolve platformio#566, resolve platformio#471, resolve platformio#561
1 parent 4cb0633 commit 5384f15

File tree

3 files changed

+113
-72
lines changed

3 files changed

+113
-72
lines changed

builder/frameworks/espidf.py

Lines changed: 108 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import os
2828

2929
import click
30+
import semantic_version
3031

3132
from SCons.Script import (
3233
ARGUMENTS,
@@ -38,7 +39,7 @@
3839
from platformio.proc import exec_command
3940
from platformio.util import get_systype
4041
from platformio.builder.tools.piolib import ProjectAsLibBuilder
41-
from platformio.package.version import get_original_version
42+
from platformio.package.version import get_original_version, pepver_to_semver
4243

4344
env = DefaultEnvironment()
4445
env.SConscript("_embed_files.py", exports="env")
@@ -68,33 +69,10 @@
6869
assert ARDUINO_FRAMEWORK_DIR and os.path.isdir(ARDUINO_FRAMEWORK_DIR)
6970

7071
BUILD_DIR = env.subst("$BUILD_DIR")
72+
PROJECT_DIR = env.subst("$PROJECT_DIR")
73+
PROJECT_SRC_DIR = env.subst("$PROJECT_SRC_DIR")
7174
CMAKE_API_REPLY_PATH = os.path.join(".cmake", "api", "v1", "reply")
7275

73-
try:
74-
import future
75-
import pyparsing
76-
import cryptography
77-
except ImportError:
78-
env.Execute(
79-
env.VerboseAction(
80-
'$PYTHONEXE -m pip install "cryptography>=2.1.4" "future>=0.15.2" "pyparsing>=2.0.3,<2.4.0" ',
81-
"Installing ESP-IDF's Python dependencies",
82-
)
83-
)
84-
85-
# a special "esp-windows-curses" python package is required on Windows for Menuconfig
86-
if "windows" in get_systype():
87-
import pkg_resources
88-
89-
if "esp-windows-curses" not in {pkg.key for pkg in pkg_resources.working_set}:
90-
env.Execute(
91-
env.VerboseAction(
92-
'$PYTHONEXE -m pip install "file://%s/tools/kconfig_new/esp-windows-curses" windows-curses'
93-
% FRAMEWORK_DIR,
94-
"Installing windows-curses package",
95-
)
96-
)
97-
9876

9977
def get_project_lib_includes(env):
10078
project = ProjectAsLibBuilder(env, "$PROJECT_DIR")
@@ -116,11 +94,11 @@ def get_project_lib_includes(env):
11694
def is_cmake_reconfigure_required(cmake_api_reply_dir):
11795
cmake_cache_file = os.path.join(BUILD_DIR, "CMakeCache.txt")
11896
cmake_txt_files = [
119-
os.path.join(env.subst("$PROJECT_DIR"), "CMakeLists.txt"),
120-
os.path.join(env.subst("$PROJECT_SRC_DIR"), "CMakeLists.txt"),
97+
os.path.join(PROJECT_DIR, "CMakeLists.txt"),
98+
os.path.join(PROJECT_SRC_DIR, "CMakeLists.txt"),
12199
]
122100
cmake_preconf_dir = os.path.join(BUILD_DIR, "config")
123-
sdkconfig = os.path.join(env.subst("$PROJECT_DIR"), "sdkconfig")
101+
sdkconfig = os.path.join(PROJECT_DIR, "sdkconfig")
124102

125103
for d in (cmake_api_reply_dir, cmake_preconf_dir):
126104
if not os.path.isdir(d) or not os.listdir(d):
@@ -146,8 +124,8 @@ def is_proper_idf_project():
146124
return all(
147125
os.path.isfile(path)
148126
for path in (
149-
os.path.join(env.subst("$PROJECT_DIR"), "CMakeLists.txt"),
150-
os.path.join(env.subst("$PROJECT_SRC_DIR"), "CMakeLists.txt"),
127+
os.path.join(PROJECT_DIR, "CMakeLists.txt"),
128+
os.path.join(PROJECT_SRC_DIR, "CMakeLists.txt"),
151129
)
152130
)
153131

@@ -161,9 +139,8 @@ def collect_src_files():
161139

162140

163141
def normalize_path(path):
164-
project_dir = env.subst("$PROJECT_DIR")
165-
if project_dir in path:
166-
path = path.replace(project_dir, "${CMAKE_SOURCE_DIR}")
142+
if PROJECT_DIR in path:
143+
path = path.replace(PROJECT_DIR, "${CMAKE_SOURCE_DIR}")
167144
return fs.to_unix_path(path)
168145

169146

@@ -180,20 +157,20 @@ def create_default_project_files():
180157
idf_component_register(SRCS ${app_sources})
181158
"""
182159

183-
if not os.listdir(os.path.join(env.subst("$PROJECT_SRC_DIR"))):
160+
if not os.listdir(PROJECT_SRC_DIR):
184161
# create a default main file to make CMake happy during first init
185-
with open(os.path.join(env.subst("$PROJECT_SRC_DIR"), "main.c"), "w") as fp:
162+
with open(os.path.join(PROJECT_SRC_DIR, "main.c"), "w") as fp:
186163
fp.write("void app_main() {}")
187164

188-
project_dir = env.subst("$PROJECT_DIR")
165+
project_dir = PROJECT_DIR
189166
if not os.path.isfile(os.path.join(project_dir, "CMakeLists.txt")):
190167
with open(os.path.join(project_dir, "CMakeLists.txt"), "w") as fp:
191168
fp.write(root_cmake_tpl % os.path.basename(project_dir))
192169

193-
project_src_dir = env.subst("$PROJECT_SRC_DIR")
170+
project_src_dir = PROJECT_SRC_DIR
194171
if not os.path.isfile(os.path.join(project_src_dir, "CMakeLists.txt")):
195172
with open(os.path.join(project_src_dir, "CMakeLists.txt"), "w") as fp:
196-
fp.write(prj_cmake_tpl % normalize_path(env.subst("$PROJECT_SRC_DIR")))
173+
fp.write(prj_cmake_tpl % normalize_path(PROJECT_SRC_DIR))
197174

198175

199176
def get_cmake_code_model(src_dir, build_dir, extra_args=None):
@@ -431,7 +408,9 @@ def find_framework_service_files(search_path, sdk_config):
431408
continue
432409
for f in os.listdir(path):
433410
# Skip hardware specific files as they will be added later
434-
if f == "linker.lf" and not os.path.basename(path).startswith("esp32"):
411+
if f == "linker.lf" and not os.path.basename(path).startswith(
412+
("esp32", "riscv")
413+
):
435414
result["lf_files"].append(os.path.join(path, f))
436415
elif f == "Kconfig.projbuild":
437416
result["kconfig_build_files"].append(os.path.join(path, f))
@@ -506,7 +485,7 @@ def generate_project_ld_script(sdk_config, ignore_targets=None):
506485

507486
args = {
508487
"script": os.path.join(FRAMEWORK_DIR, "tools", "ldgen", "ldgen.py"),
509-
"config": os.path.join(env.subst("$PROJECT_DIR"), "sdkconfig"),
488+
"config": os.path.join(PROJECT_DIR, "sdkconfig"),
510489
"fragments": " ".join(['"%s"' % f for f in project_files.get("lf_files")]),
511490
"kconfig": os.path.join(FRAMEWORK_DIR, "Kconfig"),
512491
"env_file": os.path.join("$BUILD_DIR", "config.env"),
@@ -704,7 +683,7 @@ def build_bootloader():
704683
"-DPYTHON_DEPS_CHECKED=1",
705684
"-DPYTHON=" + env.subst("$PYTHONEXE"),
706685
"-DIDF_PATH=" + FRAMEWORK_DIR,
707-
"-DSDKCONFIG=" + os.path.join(env.subst("$PROJECT_DIR"), "sdkconfig"),
686+
"-DSDKCONFIG=" + os.path.join(PROJECT_DIR, "sdkconfig"),
708687
"-DLEGACY_INCLUDE_COMMON_HEADERS=",
709688
"-DEXTRA_COMPONENT_DIRS="
710689
+ os.path.join(FRAMEWORK_DIR, "components", "bootloader"),
@@ -983,7 +962,7 @@ def generate_mbedtls_bundle(sdk_config):
983962
crt_args.append("-q")
984963

985964
# Use exec_command to change working directory
986-
exec_command(cmd + crt_args, cwd=env.subst("$BUILD_DIR"))
965+
exec_command(cmd + crt_args, cwd=BUILD_DIR)
987966
bundle_path = os.path.join("$BUILD_DIR", "x509_crt_bundle")
988967
env.Execute(
989968
env.VerboseAction(
@@ -1012,6 +991,72 @@ def generate_mbedtls_bundle(sdk_config):
1012991
)
1013992

1014993

994+
def install_python_deps():
995+
def _get_installed_pip_packages():
996+
result = {}
997+
packages = {}
998+
pip_output = subprocess.check_output(
999+
[env.subst("$PYTHONEXE"), "-m", "pip", "list", "--format=json"]
1000+
)
1001+
try:
1002+
packages = json.loads(pip_output)
1003+
except:
1004+
print("Warning! Couldn't extract the list of installed Python packages.")
1005+
return {}
1006+
for p in packages:
1007+
result[p["name"]] = pepver_to_semver(p["version"])
1008+
1009+
return result
1010+
1011+
deps = {
1012+
"cryptography": ">=2.1.4",
1013+
"future": ">=0.15.2",
1014+
"pyparsing": ">=2.0.3,<2.4.0",
1015+
"kconfiglib": "==13.7.1",
1016+
}
1017+
1018+
installed_packages = _get_installed_pip_packages()
1019+
packages_to_install = []
1020+
for package, spec in deps.items():
1021+
if package not in installed_packages:
1022+
packages_to_install.append(package)
1023+
else:
1024+
version_spec = semantic_version.Spec(spec)
1025+
if not version_spec.match(installed_packages[package]):
1026+
packages_to_install.append(package)
1027+
1028+
if packages_to_install:
1029+
env.Execute(
1030+
env.VerboseAction(
1031+
(
1032+
'"$PYTHONEXE" -m pip install -U --force-reinstall '
1033+
+ " ".join(['"%s%s"' % (p, deps[p]) for p in packages_to_install])
1034+
),
1035+
"Installing ESP-IDF's Python dependencies",
1036+
)
1037+
)
1038+
1039+
# a special "esp-windows-curses" python package is required on Windows for Menuconfig
1040+
if "windows" in get_systype():
1041+
import pkg_resources
1042+
1043+
if "esp-windows-curses" not in {pkg.key for pkg in pkg_resources.working_set}:
1044+
env.Execute(
1045+
env.VerboseAction(
1046+
'$PYTHONEXE -m pip install "file://%s/tools/kconfig_new/esp-windows-curses" windows-curses'
1047+
% FRAMEWORK_DIR,
1048+
"Installing windows-curses package",
1049+
)
1050+
)
1051+
1052+
1053+
#
1054+
# ESP-IDF requires Python packages with specific versions
1055+
#
1056+
1057+
install_python_deps()
1058+
1059+
10151060
# ESP-IDF package doesn't contain .git folder, instead package version is specified
10161061
# in a special file "version.h" in the root folder of the package
10171062

@@ -1079,6 +1124,12 @@ def generate_mbedtls_bundle(sdk_config):
10791124
sys.stderr.write("Error: Detected a whitespace character in project paths.\n")
10801125
env.Exit(1)
10811126

1127+
if not os.path.isdir(PROJECT_SRC_DIR):
1128+
sys.stderr.write(
1129+
"Error: Missing the `%s` folder with project sources.\n"
1130+
% os.path.basename(PROJECT_SRC_DIR)
1131+
)
1132+
env.Exit(1)
10821133

10831134
if env.subst("$SRC_FILTER"):
10841135
print(
@@ -1088,7 +1139,7 @@ def generate_mbedtls_bundle(sdk_config):
10881139
)
10891140
)
10901141

1091-
if os.path.isfile(os.path.join(env.subst("$PROJECT_SRC_DIR"), "sdkconfig.h")):
1142+
if os.path.isfile(os.path.join(PROJECT_SRC_DIR, "sdkconfig.h")):
10921143
print(
10931144
"Warning! Starting with ESP-IDF v4.0, new project structure is required: \n"
10941145
"https://docs.platformio.org/en/latest/frameworks/espidf.html#project-structure"
@@ -1102,16 +1153,18 @@ def generate_mbedtls_bundle(sdk_config):
11021153
# default 'src' folder we need to add this as an extra component. If there is no 'main'
11031154
# folder CMake won't generate dependencies properly
11041155
extra_components = [generate_default_component()]
1105-
if env.subst("$PROJECT_SRC_DIR") != os.path.join(env.subst("$PROJECT_DIR"), "main"):
1106-
extra_components.append(env.subst("$PROJECT_SRC_DIR"))
1107-
if "arduino" in env.subst("$PIOFRAMEWORK"):
1108-
print("Warning! Arduino framework as an ESP-IDF component doesn't handle "
1109-
"the `variant` field! The default `esp32` variant will be used.")
1110-
extra_components.append(ARDUINO_FRAMEWORK_DIR)
1156+
if PROJECT_SRC_DIR != os.path.join(PROJECT_DIR, "main"):
1157+
extra_components.append(PROJECT_SRC_DIR)
1158+
if "arduino" in env.subst("$PIOFRAMEWORK"):
1159+
print(
1160+
"Warning! Arduino framework as an ESP-IDF component doesn't handle "
1161+
"the `variant` field! The default `esp32` variant will be used."
1162+
)
1163+
extra_components.append(ARDUINO_FRAMEWORK_DIR)
11111164

11121165
print("Reading CMake configuration...")
11131166
project_codemodel = get_cmake_code_model(
1114-
env.subst("$PROJECT_DIR"),
1167+
PROJECT_DIR,
11151168
BUILD_DIR,
11161169
[
11171170
"-DIDF_TARGET=" + idf_variant,
@@ -1132,7 +1185,7 @@ def generate_mbedtls_bundle(sdk_config):
11321185

11331186
sdk_config = get_sdk_configuration()
11341187

1135-
project_target_name = "__idf_%s" % os.path.basename(env.subst("$PROJECT_SRC_DIR"))
1188+
project_target_name = "__idf_%s" % os.path.basename(PROJECT_SRC_DIR)
11361189
if project_target_name not in target_configs:
11371190
sys.stderr.write("Error: Couldn't find the main target of the project!\n")
11381191
env.Exit(1)
@@ -1160,7 +1213,7 @@ def generate_mbedtls_bundle(sdk_config):
11601213
[project_target_name, default_config_name],
11611214
)
11621215

1163-
build_components(env, framework_components_map, env.subst("$PROJECT_DIR"))
1216+
build_components(env, framework_components_map, PROJECT_DIR)
11641217

11651218
if not elf_config:
11661219
sys.stderr.write("Error: Couldn't load the main firmware target of the project\n")
@@ -1223,12 +1276,7 @@ def generate_mbedtls_bundle(sdk_config):
12231276
# Remove project source files from following build stages as they're
12241277
# built as part of the framework
12251278
def _skip_prj_source_files(node):
1226-
if (
1227-
node.srcnode()
1228-
.get_path()
1229-
.lower()
1230-
.startswith(env.subst("$PROJECT_SRC_DIR").lower())
1231-
):
1279+
if node.srcnode().get_path().lower().startswith(PROJECT_SRC_DIR.lower()):
12321280
return None
12331281
return node
12341282

@@ -1281,16 +1329,6 @@ def _skip_prj_source_files(node):
12811329
],
12821330
)
12831331

1284-
# USB stack for ESP32-S2 is implemented using tinyusb library. In IDF v4.2 it's added as
1285-
# an INTERFACE library which means that CMake doesn't export build information for it
1286-
# in File-API hence it's not present in components map. As a workaround we can build
1287-
# the lib using project build environment with additional flags from CMakeLists.txt
1288-
if (
1289-
sdk_config.get("USB_ENABLED", False)
1290-
and "__idf_tinyusb" not in framework_components_map
1291-
):
1292-
build_tinyusb_lib(env)
1293-
12941332
#
12951333
# Generate mbedtls bundle
12961334
#
@@ -1312,7 +1350,7 @@ def _skip_prj_source_files(node):
13121350
# Compile ULP sources in 'ulp' folder
13131351
#
13141352

1315-
ulp_dir = os.path.join(env.subst("$PROJECT_DIR"), "ulp")
1353+
ulp_dir = os.path.join(PROJECT_DIR, "ulp")
13161354
if os.path.isdir(ulp_dir) and os.listdir(ulp_dir):
13171355
env.SConscript("ulp.py", exports="env project_config idf_variant")
13181356

platform.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@
8787
"type": "framework",
8888
"optional": true,
8989
"owner": "platformio",
90-
"version": "~3.40201.0",
90+
"version": "~3.40300.0",
9191
"optionalVersions": ["~3.40001.0"]
9292
},
9393
"framework-simba": {
@@ -105,7 +105,8 @@
105105
"tool-esptoolpy": {
106106
"type": "uploader",
107107
"owner": "platformio",
108-
"version": "~1.30000.0"
108+
"version": "~1.30000.0",
109+
"optionalVersions": ["~1.30100.0"]
109110
},
110111
"tool-mbctool": {
111112
"optional": true,

platform.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,10 @@ def configure_default_packages(self, variables, targets):
4747
# ESP32-S2 toolchain is identical for both Arduino and ESP-IDF
4848
if mcu == "esp32s2":
4949
self.packages.pop("toolchain-xtensa32", None)
50+
self.packages.pop("toolchain-esp32ulp", None)
5051
self.packages["toolchain-xtensa32s2"]["optional"] = False
5152
self.packages["toolchain-esp32s2ulp"]["optional"] = False
53+
self.packages["tool-esptoolpy"]["version"] = "~1.30100.0"
5254

5355
build_core = variables.get(
5456
"board_build.core", board_config.get("build.core", "arduino")

0 commit comments

Comments
 (0)