2020-03-05 11:18:07 +02:00
|
|
|
# Copyright 2020-present PlatformIO <contact@platformio.org>
|
2016-10-22 02:20:57 +03:00
|
|
|
#
|
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
|
#
|
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
#
|
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
|
# limitations under the License.
|
|
|
|
|
|
|
|
|
|
"""
|
2016-10-24 20:23:25 +03:00
|
|
|
Espressif IDF
|
2016-10-22 02:20:57 +03:00
|
|
|
|
2016-10-24 20:23:25 +03:00
|
|
|
Espressif IoT Development Framework for ESP32 MCU
|
2016-10-22 02:20:57 +03:00
|
|
|
|
|
|
|
|
https://github.com/espressif/esp-idf
|
|
|
|
|
"""
|
|
|
|
|
|
2020-03-19 12:07:58 +02:00
|
|
|
import copy
|
2025-09-17 00:09:47 +02:00
|
|
|
import importlib.util
|
2020-03-05 11:18:07 +02:00
|
|
|
import json
|
|
|
|
|
import subprocess
|
2017-05-19 19:58:10 +03:00
|
|
|
import sys
|
2023-06-19 21:30:18 +03:00
|
|
|
import shutil
|
2020-09-01 21:26:55 +03:00
|
|
|
import os
|
2024-12-14 12:42:42 +01:00
|
|
|
from os.path import join
|
2024-05-10 13:50:54 +03:00
|
|
|
import re
|
2024-12-14 12:42:42 +01:00
|
|
|
import requests
|
2023-08-14 14:54:50 +03:00
|
|
|
import platform as sys_platform
|
2025-09-17 00:09:47 +02:00
|
|
|
from pathlib import Path
|
|
|
|
|
from urllib.parse import urlsplit, unquote
|
2016-10-24 20:23:25 +03:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
import click
|
2021-06-22 00:05:46 +03:00
|
|
|
import semantic_version
|
2020-03-05 11:18:07 +02:00
|
|
|
|
|
|
|
|
from SCons.Script import (
|
|
|
|
|
ARGUMENTS,
|
|
|
|
|
COMMAND_LINE_TARGETS,
|
|
|
|
|
DefaultEnvironment,
|
|
|
|
|
)
|
|
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
from platformio import fs
|
2023-01-31 21:51:01 +02:00
|
|
|
from platformio.compat import IS_WINDOWS
|
2020-09-01 21:26:55 +03:00
|
|
|
from platformio.proc import exec_command
|
|
|
|
|
from platformio.builder.tools.piolib import ProjectAsLibBuilder
|
2021-06-22 00:05:46 +03:00
|
|
|
from platformio.package.version import get_original_version, pepver_to_semver
|
2016-11-06 16:46:33 +02:00
|
|
|
|
2023-01-31 21:51:01 +02:00
|
|
|
|
2016-10-24 20:23:25 +03:00
|
|
|
env = DefaultEnvironment()
|
2019-10-18 18:19:53 +03:00
|
|
|
env.SConscript("_embed_files.py", exports="env")
|
2025-09-17 00:09:47 +02:00
|
|
|
platform = env.PioPlatform()
|
|
|
|
|
|
|
|
|
|
_component_manager_file = Path(platform.get_dir()) / "builder" / "frameworks" / "component_manager.py"
|
|
|
|
|
_cm_spec = importlib.util.spec_from_file_location("component_manager", _component_manager_file)
|
|
|
|
|
_component_manager = importlib.util.module_from_spec(_cm_spec)
|
|
|
|
|
_cm_spec.loader.exec_module(_component_manager)
|
|
|
|
|
sys.modules["component_manager"] = _component_manager
|
|
|
|
|
|
|
|
|
|
_penv_setup_file = str(Path(platform.get_dir()) / "builder" / "penv_setup.py")
|
|
|
|
|
_spec = importlib.util.spec_from_file_location("penv_setup", _penv_setup_file)
|
|
|
|
|
_penv_setup = importlib.util.module_from_spec(_spec)
|
|
|
|
|
_spec.loader.exec_module(_penv_setup) # type: ignore[attr-defined]
|
|
|
|
|
sys.modules["penv_setup"] = _penv_setup
|
|
|
|
|
get_executable_path = _penv_setup.get_executable_path
|
2018-07-09 22:47:38 +03:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
# remove maybe existing old map file in project root
|
2025-09-17 00:09:47 +02:00
|
|
|
map_file = str(Path(env.subst("$PROJECT_DIR")) / (env.subst("$PROGNAME") + ".map"))
|
2025-07-03 17:12:25 +02:00
|
|
|
if os.path.exists(map_file):
|
|
|
|
|
os.remove(map_file)
|
|
|
|
|
|
2024-08-21 15:59:56 +02:00
|
|
|
# Allow changes in folders of managed components
|
|
|
|
|
os.environ["IDF_COMPONENT_OVERWRITE_MANAGED_COMPONENTS"] = "1"
|
|
|
|
|
|
2024-12-14 12:42:42 +01:00
|
|
|
config = env.GetProjectConfig()
|
2020-09-01 21:26:55 +03:00
|
|
|
board = env.BoardConfig()
|
|
|
|
|
mcu = board.get("build.mcu", "esp32")
|
2024-12-14 12:42:42 +01:00
|
|
|
flash_speed = board.get("build.f_flash", "40000000L")
|
|
|
|
|
flash_frequency = str(flash_speed.replace("000000L", "m"))
|
|
|
|
|
flash_mode = board.get("build.flash_mode", "dio")
|
2021-01-21 13:42:23 +02:00
|
|
|
idf_variant = mcu.lower()
|
2024-12-14 12:42:42 +01:00
|
|
|
flag_custom_sdkonfig = False
|
|
|
|
|
flag_custom_component_add = False
|
|
|
|
|
flag_custom_component_remove = False
|
2020-09-01 21:26:55 +03:00
|
|
|
|
2023-06-19 21:30:18 +03:00
|
|
|
IDF_ENV_VERSION = "1.0.0"
|
2025-09-17 00:09:47 +02:00
|
|
|
_framework_pkg_dir = platform.get_package_dir("framework-espidf")
|
|
|
|
|
if not _framework_pkg_dir or not os.path.isdir(_framework_pkg_dir):
|
|
|
|
|
sys.stderr.write(f"Error: Missing framework directory '{_framework_pkg_dir}'\n")
|
|
|
|
|
env.Exit(1)
|
|
|
|
|
FRAMEWORK_DIR_PATH = Path(_framework_pkg_dir).resolve()
|
|
|
|
|
FRAMEWORK_DIR = str(FRAMEWORK_DIR_PATH)
|
2022-06-14 14:53:04 +03:00
|
|
|
TOOLCHAIN_DIR = platform.get_package_dir(
|
2024-09-17 14:28:33 +02:00
|
|
|
"toolchain-xtensa-esp-elf"
|
|
|
|
|
if mcu in ("esp32", "esp32s2", "esp32s3")
|
|
|
|
|
else "toolchain-riscv32-esp"
|
2022-06-14 14:53:04 +03:00
|
|
|
)
|
2025-09-17 00:09:47 +02:00
|
|
|
PLATFORMIO_DIR = env.subst("$PROJECT_CORE_DIR")
|
2022-04-13 18:49:20 +03:00
|
|
|
|
2025-09-17 00:09:47 +02:00
|
|
|
if not TOOLCHAIN_DIR or not os.path.isdir(TOOLCHAIN_DIR):
|
|
|
|
|
sys.stderr.write(f"Error: Missing toolchain directory '{TOOLCHAIN_DIR}'\n")
|
|
|
|
|
env.Exit(1)
|
2021-11-09 15:13:58 +02:00
|
|
|
|
2019-07-04 21:21:28 +03:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def create_silent_action(action_func):
|
|
|
|
|
"""Create a silent SCons action that suppresses output"""
|
|
|
|
|
silent_action = env.Action(action_func)
|
|
|
|
|
silent_action.strfunction = lambda target, source, env: ''
|
|
|
|
|
return silent_action
|
2023-08-31 16:36:07 +03:00
|
|
|
|
2019-07-04 21:21:28 +03:00
|
|
|
if "arduino" in env.subst("$PIOFRAMEWORK"):
|
2025-09-17 00:09:47 +02:00
|
|
|
_arduino_pkg_dir = platform.get_package_dir("framework-arduinoespressif32")
|
|
|
|
|
if not _arduino_pkg_dir or not os.path.isdir(_arduino_pkg_dir):
|
|
|
|
|
sys.stderr.write(f"Error: Missing Arduino framework directory '{_arduino_pkg_dir}'\n")
|
|
|
|
|
env.Exit(1)
|
|
|
|
|
arduino_pkg_dir = Path(_arduino_pkg_dir)
|
|
|
|
|
if "@" in arduino_pkg_dir.name:
|
|
|
|
|
new_dir = arduino_pkg_dir.with_name(arduino_pkg_dir.name.replace("@", "-"))
|
|
|
|
|
if new_dir.exists():
|
|
|
|
|
arduino_pkg_dir = new_dir
|
|
|
|
|
else:
|
|
|
|
|
os.rename(str(arduino_pkg_dir), str(new_dir))
|
|
|
|
|
arduino_pkg_dir = new_dir
|
|
|
|
|
ARDUINO_FRAMEWORK_DIR_PATH = arduino_pkg_dir.resolve()
|
|
|
|
|
ARDUINO_FRAMEWORK_DIR = str(ARDUINO_FRAMEWORK_DIR_PATH)
|
|
|
|
|
if not ARDUINO_FRAMEWORK_DIR or not os.path.isdir(ARDUINO_FRAMEWORK_DIR):
|
|
|
|
|
sys.stderr.write(f"Error: Arduino framework directory not found: {ARDUINO_FRAMEWORK_DIR}\n")
|
|
|
|
|
env.Exit(1)
|
|
|
|
|
|
|
|
|
|
_arduino_lib_dir = platform.get_package_dir("framework-arduinoespressif32-libs")
|
|
|
|
|
if not _arduino_lib_dir:
|
|
|
|
|
sys.stderr.write("Error: Missing framework-arduinoespressif32-libs package\n")
|
|
|
|
|
env.Exit(1)
|
|
|
|
|
arduino_lib_dir = Path(_arduino_lib_dir)
|
|
|
|
|
ARDUINO_FRMWRK_LIB_DIR_PATH = arduino_lib_dir.resolve()
|
|
|
|
|
ARDUINO_FRMWRK_LIB_DIR = str(ARDUINO_FRMWRK_LIB_DIR_PATH)
|
|
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
if mcu == "esp32c2":
|
2025-09-17 00:09:47 +02:00
|
|
|
ARDUINO_FRMWRK_C2_LIB_DIR = str(ARDUINO_FRMWRK_LIB_DIR_PATH / mcu)
|
2025-07-03 17:12:25 +02:00
|
|
|
if not os.path.exists(ARDUINO_FRMWRK_C2_LIB_DIR):
|
2025-09-17 00:09:47 +02:00
|
|
|
_arduino_c2_dir = platform.get_package_dir("framework-arduino-c2-skeleton-lib")
|
|
|
|
|
if not _arduino_c2_dir:
|
|
|
|
|
sys.stderr.write("Error: Missing framework-arduino-c2-skeleton-lib package\n")
|
|
|
|
|
env.Exit(1)
|
|
|
|
|
arduino_c2_dir = Path(_arduino_c2_dir)
|
|
|
|
|
ARDUINO_C2_DIR = str(arduino_c2_dir / mcu)
|
2025-07-03 17:12:25 +02:00
|
|
|
shutil.copytree(ARDUINO_C2_DIR, ARDUINO_FRMWRK_C2_LIB_DIR, dirs_exist_ok=True)
|
2025-09-17 00:09:47 +02:00
|
|
|
arduino_libs_mcu = str(ARDUINO_FRMWRK_LIB_DIR_PATH / mcu)
|
2020-09-01 21:26:55 +03:00
|
|
|
|
|
|
|
|
BUILD_DIR = env.subst("$BUILD_DIR")
|
2021-06-22 00:05:46 +03:00
|
|
|
PROJECT_DIR = env.subst("$PROJECT_DIR")
|
|
|
|
|
PROJECT_SRC_DIR = env.subst("$PROJECT_SRC_DIR")
|
2025-09-17 00:09:47 +02:00
|
|
|
CMAKE_API_REPLY_PATH = str(Path(".cmake") / "api" / "v1" / "reply")
|
2023-05-03 17:15:55 +03:00
|
|
|
SDKCONFIG_PATH = os.path.expandvars(board.get(
|
2023-12-08 12:50:47 +02:00
|
|
|
"build.esp-idf.sdkconfig_path",
|
2025-09-17 00:09:47 +02:00
|
|
|
str(Path(PROJECT_DIR) / ("sdkconfig.%s" % env.subst("$PIOENV"))),
|
2023-02-27 11:37:17 +01:00
|
|
|
))
|
2016-10-24 20:23:25 +03:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def contains_path_traversal(url):
|
2025-09-17 00:09:47 +02:00
|
|
|
"""Best-effort detection of path traversal sequences."""
|
|
|
|
|
path = unquote(unquote(urlsplit(url).path)).replace("\\", "/")
|
|
|
|
|
parts = [p for p in path.split("/") if p not in ("", ".")]
|
|
|
|
|
return any(p == ".." for p in parts)
|
2025-07-03 17:12:25 +02:00
|
|
|
|
2024-12-14 12:42:42 +01:00
|
|
|
#
|
|
|
|
|
# generate modified Arduino IDF sdkconfig, applying settings from "custom_sdkconfig"
|
|
|
|
|
#
|
|
|
|
|
if config.has_option("env:"+env["PIOENV"], "custom_component_add"):
|
|
|
|
|
flag_custom_component_add = True
|
|
|
|
|
if config.has_option("env:"+env["PIOENV"], "custom_component_remove"):
|
|
|
|
|
flag_custom_component_remove = True
|
|
|
|
|
|
|
|
|
|
if config.has_option("env:"+env["PIOENV"], "custom_sdkconfig"):
|
|
|
|
|
flag_custom_sdkonfig = True
|
|
|
|
|
if "espidf.custom_sdkconfig" in board:
|
|
|
|
|
flag_custom_sdkonfig = True
|
|
|
|
|
|
|
|
|
|
def HandleArduinoIDFsettings(env):
|
2025-07-03 17:12:25 +02:00
|
|
|
"""
|
|
|
|
|
Handles Arduino IDF settings configuration with custom sdkconfig support.
|
|
|
|
|
"""
|
|
|
|
|
|
2024-12-14 12:42:42 +01:00
|
|
|
def get_MD5_hash(phrase):
|
2025-07-03 17:12:25 +02:00
|
|
|
"""Generate MD5 hash for checksum validation."""
|
2024-12-14 12:42:42 +01:00
|
|
|
import hashlib
|
2025-07-03 17:12:25 +02:00
|
|
|
return hashlib.md5(phrase.encode('utf-8')).hexdigest()[:16]
|
2024-12-14 12:42:42 +01:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def load_custom_sdkconfig_file():
|
|
|
|
|
"""Load custom sdkconfig from file or URL if specified."""
|
|
|
|
|
if not config.has_option("env:" + env["PIOENV"], "custom_sdkconfig"):
|
2024-12-14 12:42:42 +01:00
|
|
|
return ""
|
2025-07-03 17:12:25 +02:00
|
|
|
|
|
|
|
|
sdkconfig_entries = env.GetProjectOption("custom_sdkconfig").splitlines()
|
|
|
|
|
|
|
|
|
|
for file_entry in sdkconfig_entries:
|
|
|
|
|
# Handle HTTP/HTTPS URLs
|
|
|
|
|
if "http" in file_entry and "://" in file_entry:
|
|
|
|
|
url = file_entry.split(" ")[0]
|
|
|
|
|
# Path Traversal protection
|
|
|
|
|
if contains_path_traversal(url):
|
|
|
|
|
print(f"Path Traversal detected: {url} check your URL path")
|
2024-12-14 12:42:42 +01:00
|
|
|
else:
|
2025-07-03 17:12:25 +02:00
|
|
|
try:
|
|
|
|
|
response = requests.get(file_entry.split(" ")[0], timeout=10)
|
|
|
|
|
if response.ok:
|
|
|
|
|
return response.content.decode('utf-8')
|
|
|
|
|
except requests.RequestException as e:
|
|
|
|
|
print(f"Error downloading {file_entry}: {e}")
|
|
|
|
|
except UnicodeDecodeError as e:
|
|
|
|
|
print(f"Error decoding response from {file_entry}: {e}")
|
|
|
|
|
return ""
|
|
|
|
|
|
|
|
|
|
# Handle local files
|
|
|
|
|
if "file://" in file_entry:
|
|
|
|
|
file_ref = file_entry[7:] if file_entry.startswith("file://") else file_entry
|
2025-09-17 00:09:47 +02:00
|
|
|
|
|
|
|
|
if os.path.isabs(file_ref):
|
|
|
|
|
file_path = file_ref
|
|
|
|
|
else:
|
|
|
|
|
# if it's a relative path, try relative to PROJECT_DIR
|
|
|
|
|
file_path = str(Path(PROJECT_DIR) / file_ref)
|
2024-12-14 12:42:42 +01:00
|
|
|
if os.path.exists(file_path):
|
2025-07-03 17:12:25 +02:00
|
|
|
try:
|
|
|
|
|
with open(file_path, 'r') as f:
|
|
|
|
|
return f.read()
|
|
|
|
|
except IOError as e:
|
|
|
|
|
print(f"Error reading file {file_path}: {e}")
|
|
|
|
|
return ""
|
2024-12-14 12:42:42 +01:00
|
|
|
else:
|
2025-07-03 17:12:25 +02:00
|
|
|
print("File not found, check path:", file_path)
|
2024-12-14 12:42:42 +01:00
|
|
|
return ""
|
2025-07-03 17:12:25 +02:00
|
|
|
|
2024-12-14 12:42:42 +01:00
|
|
|
return ""
|
|
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def extract_flag_name(line):
|
|
|
|
|
"""Extract flag name from sdkconfig line."""
|
|
|
|
|
line = line.strip()
|
|
|
|
|
if line.startswith("#") and "is not set" in line:
|
|
|
|
|
return line.split(" ")[1]
|
|
|
|
|
elif not line.startswith("#") and "=" in line:
|
|
|
|
|
return line.split("=")[0]
|
|
|
|
|
return None
|
2024-12-14 12:42:42 +01:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
def build_idf_config_flags():
|
|
|
|
|
"""Build complete IDF configuration flags from all sources."""
|
|
|
|
|
flags = []
|
|
|
|
|
|
|
|
|
|
# Add board-specific flags first
|
|
|
|
|
if "espidf.custom_sdkconfig" in board:
|
|
|
|
|
board_flags = board.get("espidf.custom_sdkconfig", [])
|
|
|
|
|
if board_flags:
|
|
|
|
|
flags.extend(board_flags)
|
|
|
|
|
|
|
|
|
|
# Add custom sdkconfig file content
|
|
|
|
|
custom_file_content = load_custom_sdkconfig_file()
|
|
|
|
|
if custom_file_content:
|
|
|
|
|
flags.append(custom_file_content)
|
|
|
|
|
|
|
|
|
|
# Add project-level custom sdkconfig
|
|
|
|
|
if config.has_option("env:" + env["PIOENV"], "custom_sdkconfig"):
|
|
|
|
|
custom_flags = env.GetProjectOption("custom_sdkconfig").rstrip("\n")
|
|
|
|
|
if custom_flags:
|
|
|
|
|
flags.append(custom_flags)
|
|
|
|
|
|
|
|
|
|
return "\n".join(flags) + "\n" if flags else ""
|
|
|
|
|
|
|
|
|
|
def add_flash_configuration(config_flags):
|
|
|
|
|
"""Add flash frequency and mode configuration."""
|
2024-12-14 12:42:42 +01:00
|
|
|
if flash_frequency != "80m":
|
2025-07-03 17:12:25 +02:00
|
|
|
config_flags += "# CONFIG_ESPTOOLPY_FLASHFREQ_80M is not set\n"
|
|
|
|
|
config_flags += f"CONFIG_ESPTOOLPY_FLASHFREQ_{flash_frequency.upper()}=y\n"
|
|
|
|
|
config_flags += f"CONFIG_ESPTOOLPY_FLASHFREQ=\"{flash_frequency}\"\n"
|
|
|
|
|
|
2024-12-14 12:42:42 +01:00
|
|
|
if flash_mode != "qio":
|
2025-07-03 17:12:25 +02:00
|
|
|
config_flags += "# CONFIG_ESPTOOLPY_FLASHMODE_QIO is not set\n"
|
|
|
|
|
|
|
|
|
|
flash_mode_flag = f"CONFIG_ESPTOOLPY_FLASHMODE_{flash_mode.upper()}=y\n"
|
|
|
|
|
if flash_mode_flag not in config_flags:
|
|
|
|
|
config_flags += flash_mode_flag
|
|
|
|
|
|
|
|
|
|
# ESP32 specific SPIRAM configuration
|
|
|
|
|
if mcu == "esp32" and "CONFIG_FREERTOS_UNICORE=y" in config_flags:
|
|
|
|
|
config_flags += "# CONFIG_SPIRAM is not set\n"
|
|
|
|
|
|
|
|
|
|
return config_flags
|
|
|
|
|
|
|
|
|
|
def write_sdkconfig_file(idf_config_flags, checksum_source):
|
|
|
|
|
if "arduino" not in env.subst("$PIOFRAMEWORK"):
|
|
|
|
|
print("Error: Arduino framework required for sdkconfig processing")
|
|
|
|
|
return
|
|
|
|
|
"""Write the final sdkconfig.defaults file with checksum."""
|
2025-09-17 00:09:47 +02:00
|
|
|
sdkconfig_src = str(Path(arduino_libs_mcu) / "sdkconfig")
|
|
|
|
|
sdkconfig_dst = str(Path(PROJECT_DIR) / "sdkconfig.defaults")
|
|
|
|
|
if not os.path.isfile(sdkconfig_src):
|
|
|
|
|
sys.stderr.write(f"Error: Missing Arduino sdkconfig template at '{sdkconfig_src}'\n")
|
|
|
|
|
env.Exit(1)
|
2025-07-03 17:12:25 +02:00
|
|
|
|
|
|
|
|
# Generate checksum for validation (maintains original logic)
|
|
|
|
|
checksum = get_MD5_hash(checksum_source.strip() + mcu)
|
|
|
|
|
|
|
|
|
|
with open(sdkconfig_src, 'r', encoding='utf-8') as src, open(sdkconfig_dst, 'w', encoding='utf-8') as dst:
|
|
|
|
|
# Write checksum header (critical for compilation decision logic)
|
|
|
|
|
dst.write(f"# TASMOTA__{checksum}\n")
|
|
|
|
|
|
|
|
|
|
# Process each line from source sdkconfig
|
|
|
|
|
for line in src:
|
|
|
|
|
flag_name = extract_flag_name(line)
|
|
|
|
|
|
|
|
|
|
if flag_name is None:
|
2024-12-14 12:42:42 +01:00
|
|
|
dst.write(line)
|
2025-07-03 17:12:25 +02:00
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# Check if we have a custom replacement for this flag
|
|
|
|
|
flag_replaced = False
|
|
|
|
|
for custom_flag in idf_config_flags[:]: # Create copy for safe removal
|
|
|
|
|
custom_flag_name = extract_flag_name(custom_flag.replace("'", ""))
|
|
|
|
|
|
|
|
|
|
if flag_name == custom_flag_name:
|
|
|
|
|
cleaned_flag = custom_flag.replace("'", "")
|
|
|
|
|
dst.write(cleaned_flag + "\n")
|
|
|
|
|
print(f"Replace: {line.strip()} with: {cleaned_flag}")
|
|
|
|
|
idf_config_flags.remove(custom_flag)
|
|
|
|
|
flag_replaced = True
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
if not flag_replaced:
|
|
|
|
|
dst.write(line)
|
|
|
|
|
|
|
|
|
|
# Add any remaining new flags
|
|
|
|
|
for remaining_flag in idf_config_flags:
|
|
|
|
|
cleaned_flag = remaining_flag.replace("'", "")
|
|
|
|
|
print(f"Add: {cleaned_flag}")
|
|
|
|
|
dst.write(cleaned_flag + "\n")
|
|
|
|
|
|
|
|
|
|
# Main execution logic
|
|
|
|
|
has_custom_config = (
|
|
|
|
|
config.has_option("env:" + env["PIOENV"], "custom_sdkconfig") or
|
|
|
|
|
"espidf.custom_sdkconfig" in board
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if not has_custom_config:
|
2024-12-14 12:42:42 +01:00
|
|
|
return
|
2025-07-03 17:12:25 +02:00
|
|
|
|
|
|
|
|
print("*** Add \"custom_sdkconfig\" settings to IDF sdkconfig.defaults ***")
|
|
|
|
|
|
|
|
|
|
# Build complete configuration
|
|
|
|
|
idf_config_flags = build_idf_config_flags()
|
|
|
|
|
idf_config_flags = add_flash_configuration(idf_config_flags)
|
|
|
|
|
|
|
|
|
|
# Convert to list for processing
|
|
|
|
|
idf_config_list = [line for line in idf_config_flags.splitlines() if line.strip()]
|
|
|
|
|
|
|
|
|
|
# Write final configuration file with checksum
|
|
|
|
|
custom_sdk_config_flags = ""
|
|
|
|
|
if config.has_option("env:" + env["PIOENV"], "custom_sdkconfig"):
|
|
|
|
|
custom_sdk_config_flags = env.GetProjectOption("custom_sdkconfig").rstrip("\n") + "\n"
|
|
|
|
|
|
|
|
|
|
write_sdkconfig_file(idf_config_list, custom_sdk_config_flags)
|
|
|
|
|
|
|
|
|
|
|
2024-12-14 12:42:42 +01:00
|
|
|
|
|
|
|
|
def HandleCOMPONENTsettings(env):
|
2025-07-03 17:12:25 +02:00
|
|
|
from component_manager import ComponentManager
|
|
|
|
|
component_manager = ComponentManager(env)
|
|
|
|
|
|
|
|
|
|
if flag_custom_component_add or flag_custom_component_remove:
|
|
|
|
|
actions = [action for flag, action in [
|
|
|
|
|
(flag_custom_component_add, "select"),
|
|
|
|
|
(flag_custom_component_remove, "deselect")
|
|
|
|
|
] if flag]
|
|
|
|
|
action_text = " and ".join(actions)
|
|
|
|
|
print(f"*** \"custom_component\" is used to {action_text} managed idf components ***")
|
|
|
|
|
|
|
|
|
|
component_manager.handle_component_settings(
|
|
|
|
|
add_components=flag_custom_component_add,
|
|
|
|
|
remove_components=flag_custom_component_remove
|
|
|
|
|
)
|
2024-12-14 12:42:42 +01:00
|
|
|
return
|
|
|
|
|
return
|
|
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
if "arduino" in env.subst("$PIOFRAMEWORK"):
|
2024-12-14 12:42:42 +01:00
|
|
|
HandleCOMPONENTsettings(env)
|
|
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
if flag_custom_sdkonfig == True and "arduino" in env.subst("$PIOFRAMEWORK") and "espidf" not in env.subst("$PIOFRAMEWORK"):
|
2024-12-14 12:42:42 +01:00
|
|
|
HandleArduinoIDFsettings(env)
|
2025-09-17 00:09:47 +02:00
|
|
|
LIB_SOURCE = str(Path(platform.get_dir()) / "builder" / "build_lib")
|
|
|
|
|
if not bool(os.path.exists(str(Path(PROJECT_DIR) / ".dummy"))):
|
|
|
|
|
shutil.copytree(LIB_SOURCE, str(Path(PROJECT_DIR) / ".dummy"))
|
|
|
|
|
PROJECT_SRC_DIR = str(Path(PROJECT_DIR) / ".dummy")
|
2024-12-14 12:42:42 +01:00
|
|
|
env.Replace(
|
|
|
|
|
PROJECT_SRC_DIR=PROJECT_SRC_DIR,
|
|
|
|
|
BUILD_FLAGS="",
|
|
|
|
|
BUILD_UNFLAGS="",
|
|
|
|
|
LINKFLAGS="",
|
|
|
|
|
PIOFRAMEWORK="arduino",
|
|
|
|
|
ARDUINO_LIB_COMPILE_FLAG="Build",
|
|
|
|
|
)
|
|
|
|
|
env["INTEGRATION_EXTRA_DATA"].update({"arduino_lib_compile_flag": env.subst("$ARDUINO_LIB_COMPILE_FLAG")})
|
2019-04-18 21:54:59 +03:00
|
|
|
|
2020-04-16 22:03:13 +03:00
|
|
|
def get_project_lib_includes(env):
|
|
|
|
|
project = ProjectAsLibBuilder(env, "$PROJECT_DIR")
|
2020-09-01 21:26:55 +03:00
|
|
|
project.install_dependencies()
|
2020-04-16 22:03:13 +03:00
|
|
|
project.search_deps_recursive()
|
|
|
|
|
|
|
|
|
|
paths = []
|
|
|
|
|
for lb in env.GetLibBuilders():
|
|
|
|
|
if not lb.dependent:
|
|
|
|
|
continue
|
|
|
|
|
lb.env.PrependUnique(CPPPATH=lb.get_include_dirs())
|
|
|
|
|
paths.extend(lb.env["CPPPATH"])
|
|
|
|
|
|
|
|
|
|
DefaultEnvironment().Replace(__PIO_LIB_BUILDERS=None)
|
|
|
|
|
|
|
|
|
|
return paths
|
|
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
def is_cmake_reconfigure_required(cmake_api_reply_dir):
|
2025-09-17 00:09:47 +02:00
|
|
|
cmake_cache_file = str(Path(BUILD_DIR) / "CMakeCache.txt")
|
2020-04-27 17:37:01 +03:00
|
|
|
cmake_txt_files = [
|
2025-09-17 00:09:47 +02:00
|
|
|
str(Path(PROJECT_DIR) / "CMakeLists.txt"),
|
|
|
|
|
str(Path(PROJECT_SRC_DIR) / "CMakeLists.txt"),
|
2020-04-27 17:37:01 +03:00
|
|
|
]
|
2025-09-17 00:09:47 +02:00
|
|
|
cmake_preconf_dir = str(Path(BUILD_DIR) / "config")
|
|
|
|
|
default_sdk_config = str(Path(PROJECT_DIR) / "sdkconfig.defaults")
|
|
|
|
|
idf_deps_lock = str(Path(PROJECT_DIR) / "dependencies.lock")
|
|
|
|
|
ninja_buildfile = str(Path(BUILD_DIR) / "build.ninja")
|
2019-07-05 17:21:37 +03:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
for d in (cmake_api_reply_dir, cmake_preconf_dir):
|
2020-09-01 21:26:55 +03:00
|
|
|
if not os.path.isdir(d) or not os.listdir(d):
|
2020-03-05 11:18:07 +02:00
|
|
|
return True
|
2020-09-01 21:26:55 +03:00
|
|
|
if not os.path.isfile(cmake_cache_file):
|
2020-03-05 11:18:07 +02:00
|
|
|
return True
|
2024-12-19 15:01:57 +01:00
|
|
|
if not os.path.isfile(ninja_buildfile):
|
2020-03-05 11:18:07 +02:00
|
|
|
return True
|
2021-07-22 17:50:41 +03:00
|
|
|
if not os.path.isfile(SDKCONFIG_PATH) or os.path.getmtime(
|
2021-06-23 14:25:46 +03:00
|
|
|
SDKCONFIG_PATH
|
|
|
|
|
) > os.path.getmtime(cmake_cache_file):
|
2020-03-05 11:18:07 +02:00
|
|
|
return True
|
2025-09-17 00:09:47 +02:00
|
|
|
if os.path.isfile(default_sdk_config) and os.path.getmtime(
|
|
|
|
|
default_sdk_config
|
2021-07-22 17:50:41 +03:00
|
|
|
) > os.path.getmtime(cmake_cache_file):
|
|
|
|
|
return True
|
2024-12-19 15:01:57 +01:00
|
|
|
if os.path.isfile(idf_deps_lock) and os.path.getmtime(
|
|
|
|
|
idf_deps_lock
|
|
|
|
|
) > os.path.getmtime(ninja_buildfile):
|
|
|
|
|
return True
|
2020-03-05 11:18:07 +02:00
|
|
|
if any(
|
2020-09-01 21:26:55 +03:00
|
|
|
os.path.getmtime(f) > os.path.getmtime(cmake_cache_file)
|
2021-01-21 13:42:23 +02:00
|
|
|
for f in cmake_txt_files + [cmake_preconf_dir, FRAMEWORK_DIR]
|
2020-03-05 11:18:07 +02:00
|
|
|
):
|
|
|
|
|
return True
|
2019-06-20 13:42:21 +03:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
return False
|
2019-06-20 13:42:21 +03:00
|
|
|
|
2019-07-04 21:10:01 +03:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
def is_proper_idf_project():
|
|
|
|
|
return all(
|
2020-09-01 21:26:55 +03:00
|
|
|
os.path.isfile(path)
|
2020-03-05 11:18:07 +02:00
|
|
|
for path in (
|
2025-09-17 00:09:47 +02:00
|
|
|
str(Path(PROJECT_DIR) / "CMakeLists.txt"),
|
|
|
|
|
str(Path(PROJECT_SRC_DIR) / "CMakeLists.txt"),
|
2020-03-05 11:18:07 +02:00
|
|
|
)
|
|
|
|
|
)
|
2019-07-04 21:10:01 +03:00
|
|
|
|
2017-02-19 21:23:04 +02:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
def collect_src_files():
|
2020-03-16 16:05:27 +02:00
|
|
|
return [
|
|
|
|
|
f
|
|
|
|
|
for f in env.MatchSourceFiles("$PROJECT_SRC_DIR", env.get("SRC_FILTER"))
|
|
|
|
|
if not f.endswith((".h", ".hpp"))
|
|
|
|
|
]
|
2017-02-19 21:23:04 +02:00
|
|
|
|
2019-04-18 21:54:59 +03:00
|
|
|
|
2020-04-27 17:25:45 +03:00
|
|
|
def normalize_path(path):
|
2021-06-22 00:05:46 +03:00
|
|
|
if PROJECT_DIR in path:
|
|
|
|
|
path = path.replace(PROJECT_DIR, "${CMAKE_SOURCE_DIR}")
|
2020-09-01 21:26:55 +03:00
|
|
|
return fs.to_unix_path(path)
|
2020-04-27 17:25:45 +03:00
|
|
|
|
|
|
|
|
|
2025-09-17 00:09:47 +02:00
|
|
|
CMK_TOOL = platform.get_package_dir("tool-cmake")
|
|
|
|
|
if not CMK_TOOL or not os.path.isdir(CMK_TOOL):
|
|
|
|
|
sys.stderr.write(f"Error: Missing CMake package directory '{CMK_TOOL}'\n")
|
|
|
|
|
env.Exit(1)
|
|
|
|
|
CMAKE_DIR = str(Path(CMK_TOOL) / "bin" / "cmake")
|
|
|
|
|
|
|
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
def create_default_project_files():
|
|
|
|
|
root_cmake_tpl = """cmake_minimum_required(VERSION 3.16.0)
|
|
|
|
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
|
|
|
|
project(%s)
|
|
|
|
|
"""
|
2020-04-27 17:25:45 +03:00
|
|
|
prj_cmake_tpl = """# This file was automatically generated for projects
|
2020-03-06 15:41:26 +02:00
|
|
|
# without default 'CMakeLists.txt' file.
|
|
|
|
|
|
2020-04-27 17:25:45 +03:00
|
|
|
FILE(GLOB_RECURSE app_sources %s/*.*)
|
2020-03-06 15:41:26 +02:00
|
|
|
|
|
|
|
|
idf_component_register(SRCS ${app_sources})
|
|
|
|
|
"""
|
2019-04-18 21:54:59 +03:00
|
|
|
|
2021-06-22 00:05:46 +03:00
|
|
|
if not os.listdir(PROJECT_SRC_DIR):
|
2020-04-27 17:25:45 +03:00
|
|
|
# create a default main file to make CMake happy during first init
|
2025-09-17 00:09:47 +02:00
|
|
|
with open(str(Path(PROJECT_SRC_DIR) / "main.c"), "w") as fp:
|
2020-04-27 17:25:45 +03:00
|
|
|
fp.write("void app_main() {}")
|
2019-04-18 21:54:59 +03:00
|
|
|
|
2021-06-22 00:05:46 +03:00
|
|
|
project_dir = PROJECT_DIR
|
2025-09-17 00:09:47 +02:00
|
|
|
if not os.path.isfile(str(Path(project_dir) / "CMakeLists.txt")):
|
|
|
|
|
with open(str(Path(project_dir) / "CMakeLists.txt"), "w") as fp:
|
2020-09-01 21:26:55 +03:00
|
|
|
fp.write(root_cmake_tpl % os.path.basename(project_dir))
|
2019-04-18 21:54:59 +03:00
|
|
|
|
2021-06-22 00:05:46 +03:00
|
|
|
project_src_dir = PROJECT_SRC_DIR
|
2025-09-17 00:09:47 +02:00
|
|
|
if not os.path.isfile(str(Path(project_src_dir) / "CMakeLists.txt")):
|
|
|
|
|
with open(str(Path(project_src_dir) / "CMakeLists.txt"), "w") as fp:
|
2021-06-22 00:05:46 +03:00
|
|
|
fp.write(prj_cmake_tpl % normalize_path(PROJECT_SRC_DIR))
|
2017-02-19 21:23:04 +02:00
|
|
|
|
2019-09-16 13:11:52 +03:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
def get_cmake_code_model(src_dir, build_dir, extra_args=None):
|
2025-09-17 00:09:47 +02:00
|
|
|
cmake_api_dir = str(Path(build_dir) / ".cmake" / "api" / "v1")
|
|
|
|
|
cmake_api_query_dir = str(Path(cmake_api_dir) / "query")
|
|
|
|
|
cmake_api_reply_dir = str(Path(cmake_api_dir) / "reply")
|
|
|
|
|
query_file = str(Path(cmake_api_query_dir) / "codemodel-v2")
|
2017-02-19 21:23:04 +02:00
|
|
|
|
2020-09-01 21:26:55 +03:00
|
|
|
if not os.path.isfile(query_file):
|
2025-09-17 00:09:47 +02:00
|
|
|
Path(cmake_api_query_dir).mkdir(parents=True, exist_ok=True)
|
|
|
|
|
Path(query_file).touch()
|
2019-09-16 13:11:52 +03:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
if not is_proper_idf_project():
|
|
|
|
|
create_default_project_files()
|
2019-09-16 13:11:52 +03:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
if is_cmake_reconfigure_required(cmake_api_reply_dir):
|
|
|
|
|
run_cmake(src_dir, build_dir, extra_args)
|
2018-05-11 02:16:58 +03:00
|
|
|
|
2020-09-01 21:26:55 +03:00
|
|
|
if not os.path.isdir(cmake_api_reply_dir) or not os.listdir(cmake_api_reply_dir):
|
2020-03-05 11:18:07 +02:00
|
|
|
sys.stderr.write("Error: Couldn't find CMake API response file\n")
|
2019-04-18 21:54:59 +03:00
|
|
|
env.Exit(1)
|
|
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
codemodel = {}
|
2020-09-01 21:26:55 +03:00
|
|
|
for target in os.listdir(cmake_api_reply_dir):
|
2020-03-05 11:18:07 +02:00
|
|
|
if target.startswith("codemodel-v2"):
|
2025-09-17 00:09:47 +02:00
|
|
|
with open(str(Path(cmake_api_reply_dir) / target), "r") as fp:
|
2020-03-05 11:18:07 +02:00
|
|
|
codemodel = json.load(fp)
|
2025-09-17 00:09:47 +02:00
|
|
|
break
|
2019-04-18 21:54:59 +03:00
|
|
|
|
2025-09-17 00:09:47 +02:00
|
|
|
if codemodel.get("version", {}).get("major") != 2:
|
|
|
|
|
sys.stderr.write("Error: Unsupported CMake codemodel version (need major=2)\n")
|
|
|
|
|
env.Exit(1)
|
2020-03-05 11:18:07 +02:00
|
|
|
return codemodel
|
2019-04-18 21:54:59 +03:00
|
|
|
|
|
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
def populate_idf_env_vars(idf_env):
|
2022-12-01 13:43:11 +02:00
|
|
|
idf_env["IDF_PATH"] = fs.to_unix_path(FRAMEWORK_DIR)
|
2025-09-17 00:09:47 +02:00
|
|
|
NINJA_DIR = platform.get_package_dir("tool-ninja")
|
|
|
|
|
if not NINJA_DIR or not os.path.isdir(NINJA_DIR):
|
|
|
|
|
sys.stderr.write(f"Error: Missing ninja package directory '{NINJA_DIR}'\n")
|
|
|
|
|
env.Exit(1)
|
2020-03-05 11:18:07 +02:00
|
|
|
additional_packages = [
|
2025-09-17 00:09:47 +02:00
|
|
|
str(Path(TOOLCHAIN_DIR) / "bin"),
|
|
|
|
|
NINJA_DIR,
|
|
|
|
|
CMAKE_DIR,
|
2023-01-31 21:51:01 +02:00
|
|
|
os.path.dirname(get_python_exe()),
|
2020-03-05 11:18:07 +02:00
|
|
|
]
|
2019-04-18 21:54:59 +03:00
|
|
|
|
2025-09-17 00:09:47 +02:00
|
|
|
idf_env["PATH"] = os.pathsep.join([*additional_packages, idf_env["PATH"]])
|
2025-07-03 17:12:25 +02:00
|
|
|
idf_env["ESP_ROM_ELF_DIR"] = platform.get_package_dir("tool-esp-rom-elfs")
|
|
|
|
|
|
2019-04-18 21:54:59 +03:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
def get_target_config(project_configs, target_index, cmake_api_reply_dir):
|
|
|
|
|
target_json = project_configs.get("targets")[target_index].get("jsonFile", "")
|
2025-09-17 00:09:47 +02:00
|
|
|
target_config_file = str(Path(cmake_api_reply_dir) / target_json)
|
2020-09-01 21:26:55 +03:00
|
|
|
if not os.path.isfile(target_config_file):
|
2020-03-05 11:18:07 +02:00
|
|
|
sys.stderr.write("Error: Couldn't find target config %s\n" % target_json)
|
|
|
|
|
env.Exit(1)
|
2019-04-18 21:54:59 +03:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
with open(target_config_file) as fp:
|
|
|
|
|
return json.load(fp)
|
2019-04-18 21:54:59 +03:00
|
|
|
|
|
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
def load_target_configurations(cmake_codemodel, cmake_api_reply_dir):
|
|
|
|
|
configs = {}
|
|
|
|
|
project_configs = cmake_codemodel.get("configurations")[0]
|
|
|
|
|
for config in project_configs.get("projects", []):
|
|
|
|
|
for target_index in config.get("targetIndexes", []):
|
|
|
|
|
target_config = get_target_config(
|
|
|
|
|
project_configs, target_index, cmake_api_reply_dir
|
|
|
|
|
)
|
|
|
|
|
configs[target_config["name"]] = target_config
|
2019-04-18 21:54:59 +03:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
return configs
|
2019-04-18 21:54:59 +03:00
|
|
|
|
|
|
|
|
|
2022-05-06 12:52:39 +03:00
|
|
|
def build_library(
|
|
|
|
|
default_env, lib_config, project_src_dir, prepend_dir=None, debug_allowed=True
|
|
|
|
|
):
|
2020-03-05 11:18:07 +02:00
|
|
|
lib_name = lib_config["nameOnDisk"]
|
|
|
|
|
lib_path = lib_config["paths"]["build"]
|
|
|
|
|
if prepend_dir:
|
2025-09-17 00:09:47 +02:00
|
|
|
lib_path = str(Path(prepend_dir) / lib_path)
|
2020-03-05 11:18:07 +02:00
|
|
|
lib_objects = compile_source_files(
|
2022-05-06 12:52:39 +03:00
|
|
|
lib_config, default_env, project_src_dir, prepend_dir, debug_allowed
|
2020-03-05 11:18:07 +02:00
|
|
|
)
|
|
|
|
|
return default_env.Library(
|
2025-09-17 00:09:47 +02:00
|
|
|
target=str(Path("$BUILD_DIR") / lib_path / lib_name), source=lib_objects
|
2020-03-05 11:18:07 +02:00
|
|
|
)
|
2019-04-18 21:54:59 +03:00
|
|
|
|
|
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
def get_app_includes(app_config):
|
|
|
|
|
plain_includes = []
|
|
|
|
|
sys_includes = []
|
|
|
|
|
cg = app_config["compileGroups"][0]
|
|
|
|
|
for inc in cg.get("includes", []):
|
|
|
|
|
inc_path = inc["path"]
|
|
|
|
|
if inc.get("isSystem", False):
|
|
|
|
|
sys_includes.append(inc_path)
|
|
|
|
|
else:
|
|
|
|
|
plain_includes.append(inc_path)
|
2019-04-18 21:54:59 +03:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
return {"plain_includes": plain_includes, "sys_includes": sys_includes}
|
2019-04-18 21:54:59 +03:00
|
|
|
|
|
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
def extract_defines(compile_group):
|
2023-05-08 13:06:18 +03:00
|
|
|
def _normalize_define(define_string):
|
|
|
|
|
define_string = define_string.strip()
|
|
|
|
|
if "=" in define_string:
|
|
|
|
|
define, value = define_string.split("=", maxsplit=1)
|
2025-07-03 17:12:25 +02:00
|
|
|
if define == "OPENTHREAD_BUILD_DATETIME":
|
|
|
|
|
return None
|
2024-09-06 15:58:43 +02:00
|
|
|
if any(char in value for char in (' ', '<', '>')):
|
|
|
|
|
value = f'"{value}"'
|
|
|
|
|
elif '"' in value and not value.startswith("\\"):
|
2023-05-08 13:06:18 +03:00
|
|
|
value = value.replace('"', '\\"')
|
|
|
|
|
return (define, value)
|
|
|
|
|
return define_string
|
|
|
|
|
|
|
|
|
|
result = [
|
|
|
|
|
_normalize_define(d.get("define", ""))
|
|
|
|
|
for d in compile_group.get("defines", []) if d
|
|
|
|
|
]
|
|
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
for f in compile_group.get("compileCommandFragments", []):
|
2024-09-06 15:58:43 +02:00
|
|
|
fragment = f.get("fragment", "").strip()
|
|
|
|
|
if fragment.startswith('"'):
|
|
|
|
|
fragment = fragment.strip('"')
|
|
|
|
|
if fragment.startswith("-D"):
|
|
|
|
|
result.append(_normalize_define(fragment[2:]))
|
2023-05-08 13:06:18 +03:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
return result
|
2019-04-18 21:54:59 +03:00
|
|
|
|
|
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
def get_app_defines(app_config):
|
|
|
|
|
return extract_defines(app_config["compileGroups"][0])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def extract_link_args(target_config):
|
2021-01-21 13:42:23 +02:00
|
|
|
def _add_to_libpath(lib_path, link_args):
|
|
|
|
|
if lib_path not in link_args["LIBPATH"]:
|
|
|
|
|
link_args["LIBPATH"].append(lib_path)
|
|
|
|
|
|
|
|
|
|
def _add_archive(archive_path, link_args):
|
|
|
|
|
archive_name = os.path.basename(archive_path)
|
|
|
|
|
if archive_name not in link_args["LIBS"]:
|
|
|
|
|
_add_to_libpath(os.path.dirname(archive_path), link_args)
|
|
|
|
|
link_args["LIBS"].append(archive_name)
|
|
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
link_args = {"LINKFLAGS": [], "LIBS": [], "LIBPATH": [], "__LIB_DEPS": []}
|
|
|
|
|
|
|
|
|
|
for f in target_config.get("link", {}).get("commandFragments", []):
|
|
|
|
|
fragment = f.get("fragment", "").strip()
|
|
|
|
|
fragment_role = f.get("role", "").strip()
|
|
|
|
|
if not fragment or not fragment_role:
|
|
|
|
|
continue
|
|
|
|
|
args = click.parser.split_arg_string(fragment)
|
|
|
|
|
if fragment_role == "flags":
|
|
|
|
|
link_args["LINKFLAGS"].extend(args)
|
2023-08-03 13:38:40 +03:00
|
|
|
elif fragment_role in ("libraries", "libraryPath"):
|
2020-03-05 11:18:07 +02:00
|
|
|
if fragment.startswith("-l"):
|
|
|
|
|
link_args["LIBS"].extend(args)
|
|
|
|
|
elif fragment.startswith("-L"):
|
2022-06-14 04:45:19 -07:00
|
|
|
lib_path = fragment.replace("-L", "").strip(" '\"")
|
2021-01-21 13:42:23 +02:00
|
|
|
_add_to_libpath(lib_path, link_args)
|
2020-03-05 11:18:07 +02:00
|
|
|
elif fragment.startswith("-") and not fragment.startswith("-l"):
|
|
|
|
|
# CMake mistakenly marks LINKFLAGS as libraries
|
|
|
|
|
link_args["LINKFLAGS"].extend(args)
|
|
|
|
|
elif fragment.endswith(".a"):
|
2021-01-21 13:42:23 +02:00
|
|
|
archive_path = fragment
|
|
|
|
|
# process static archives
|
2023-11-13 13:58:24 +02:00
|
|
|
if os.path.isabs(archive_path):
|
|
|
|
|
# In case of precompiled archives
|
2021-01-21 13:42:23 +02:00
|
|
|
_add_archive(archive_path, link_args)
|
|
|
|
|
else:
|
|
|
|
|
# In case of archives within project
|
|
|
|
|
if archive_path.startswith(".."):
|
|
|
|
|
# Precompiled archives from project component
|
|
|
|
|
_add_archive(
|
2025-09-17 00:09:47 +02:00
|
|
|
os.path.normpath(str(Path(BUILD_DIR) / archive_path)),
|
2021-01-21 13:42:23 +02:00
|
|
|
link_args,
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
# Internally built libraries used for dependency resolution
|
|
|
|
|
link_args["__LIB_DEPS"].append(os.path.basename(archive_path))
|
2020-03-05 11:18:07 +02:00
|
|
|
|
|
|
|
|
return link_args
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def filter_args(args, allowed, ignore=None):
|
|
|
|
|
if not allowed:
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
ignore = ignore or []
|
|
|
|
|
result = []
|
|
|
|
|
i = 0
|
|
|
|
|
length = len(args)
|
|
|
|
|
while i < length:
|
|
|
|
|
if any(args[i].startswith(f) for f in allowed) and not any(
|
|
|
|
|
args[i].startswith(f) for f in ignore
|
|
|
|
|
):
|
|
|
|
|
result.append(args[i])
|
|
|
|
|
if i + 1 < length and not args[i + 1].startswith("-"):
|
|
|
|
|
i += 1
|
2020-03-16 16:05:27 +02:00
|
|
|
result.append(args[i])
|
2020-03-05 11:18:07 +02:00
|
|
|
i += 1
|
|
|
|
|
return result
|
2019-04-18 21:54:59 +03:00
|
|
|
|
|
|
|
|
|
2020-04-16 21:57:06 +03:00
|
|
|
def get_app_flags(app_config, default_config):
|
|
|
|
|
def _extract_flags(config):
|
|
|
|
|
flags = {}
|
|
|
|
|
for cg in config["compileGroups"]:
|
|
|
|
|
flags[cg["language"]] = []
|
2020-04-16 22:01:56 +03:00
|
|
|
for ccfragment in cg["compileCommandFragments"]:
|
2024-09-09 18:45:59 +02:00
|
|
|
fragment = ccfragment.get("fragment", "").strip("\" ")
|
|
|
|
|
if not fragment or fragment.startswith("-D"):
|
2020-04-16 22:01:56 +03:00
|
|
|
continue
|
2020-04-16 21:57:06 +03:00
|
|
|
flags[cg["language"]].extend(
|
2020-04-16 22:01:56 +03:00
|
|
|
click.parser.split_arg_string(fragment.strip())
|
|
|
|
|
)
|
2019-04-18 21:54:59 +03:00
|
|
|
|
2020-04-16 21:57:06 +03:00
|
|
|
return flags
|
|
|
|
|
|
|
|
|
|
app_flags = _extract_flags(app_config)
|
|
|
|
|
default_flags = _extract_flags(default_config)
|
2019-04-18 21:54:59 +03:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
# Flags are sorted because CMake randomly populates build flags in code model
|
|
|
|
|
return {
|
2024-02-22 14:03:26 +02:00
|
|
|
"ASPPFLAGS": sorted(app_flags.get("ASM", default_flags.get("ASM"))),
|
2020-04-16 21:57:06 +03:00
|
|
|
"CFLAGS": sorted(app_flags.get("C", default_flags.get("C"))),
|
|
|
|
|
"CXXFLAGS": sorted(app_flags.get("CXX", default_flags.get("CXX"))),
|
2019-04-18 21:54:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2020-04-16 16:25:52 +03:00
|
|
|
def get_sdk_configuration():
|
2025-09-17 00:09:47 +02:00
|
|
|
config_path = str(Path(BUILD_DIR) / "config" / "sdkconfig.json")
|
2020-09-01 21:26:55 +03:00
|
|
|
if not os.path.isfile(config_path):
|
2020-04-16 21:57:06 +03:00
|
|
|
print('Warning: Could not find "sdkconfig.json" file\n')
|
2020-04-16 16:25:52 +03:00
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
with open(config_path, "r") as fp:
|
|
|
|
|
return json.load(fp)
|
2025-09-17 00:09:47 +02:00
|
|
|
except (OSError, json.JSONDecodeError):
|
2020-04-16 16:25:52 +03:00
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
|
2022-05-06 21:26:22 +03:00
|
|
|
def load_component_paths(framework_components_dir, ignored_component_prefixes=None):
|
|
|
|
|
def _scan_components_from_framework():
|
|
|
|
|
result = []
|
|
|
|
|
for component in os.listdir(framework_components_dir):
|
2025-09-17 00:09:47 +02:00
|
|
|
component_path = str(Path(framework_components_dir) / component)
|
2022-05-06 21:26:22 +03:00
|
|
|
if component.startswith(ignored_component_prefixes) or not os.path.isdir(
|
|
|
|
|
component_path
|
2021-06-22 00:05:46 +03:00
|
|
|
):
|
2022-05-06 21:26:22 +03:00
|
|
|
continue
|
|
|
|
|
result.append(component_path)
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
# First of all, try to load the list of used components from the project description
|
|
|
|
|
components = []
|
|
|
|
|
ignored_component_prefixes = ignored_component_prefixes or []
|
2025-09-17 00:09:47 +02:00
|
|
|
project_description_file = str(Path(BUILD_DIR) / "project_description.json")
|
2022-05-06 21:26:22 +03:00
|
|
|
if os.path.isfile(project_description_file):
|
|
|
|
|
with open(project_description_file) as fp:
|
|
|
|
|
try:
|
|
|
|
|
data = json.load(fp)
|
|
|
|
|
for path in data.get("build_component_paths", []):
|
2025-09-17 00:09:47 +02:00
|
|
|
if not os.path.basename(path).startswith(ignored_component_prefixes):
|
2022-05-06 21:26:22 +03:00
|
|
|
components.append(path)
|
2025-09-17 00:09:47 +02:00
|
|
|
except (OSError, ValueError) as e:
|
|
|
|
|
print(f"Warning: Could not load components from project description: {e}\n")
|
2022-05-06 21:26:22 +03:00
|
|
|
|
|
|
|
|
return components or _scan_components_from_framework()
|
|
|
|
|
|
|
|
|
|
|
2024-05-10 13:50:54 +03:00
|
|
|
def extract_linker_script_fragments_backup(framework_components_dir, sdk_config):
|
2022-05-06 21:26:22 +03:00
|
|
|
# Hardware-specific components are excluded from search and added manually below
|
|
|
|
|
project_components = load_component_paths(
|
|
|
|
|
framework_components_dir, ignored_component_prefixes=("esp32", "riscv")
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
result = []
|
|
|
|
|
for component_path in project_components:
|
2025-09-17 00:09:47 +02:00
|
|
|
linker_fragment = str(Path(component_path) / "linker.lf")
|
2022-05-06 21:26:22 +03:00
|
|
|
if os.path.isfile(linker_fragment):
|
|
|
|
|
result.append(linker_fragment)
|
|
|
|
|
|
|
|
|
|
if not result:
|
|
|
|
|
sys.stderr.write("Error: Failed to extract paths to linker script fragments\n")
|
|
|
|
|
env.Exit(1)
|
2019-04-18 21:54:59 +03:00
|
|
|
|
2024-09-17 14:28:33 +02:00
|
|
|
if mcu not in ("esp32", "esp32s2", "esp32s3"):
|
2025-09-17 00:09:47 +02:00
|
|
|
result.append(str(Path(framework_components_dir) / "riscv" / "linker.lf"))
|
2021-06-23 12:32:55 +03:00
|
|
|
|
2022-06-14 04:45:19 -07:00
|
|
|
# Add extra linker fragments
|
|
|
|
|
for fragment in (
|
2025-09-17 00:09:47 +02:00
|
|
|
str(Path("esp_system") / "app.lf"),
|
|
|
|
|
str(Path("esp_common") / "common.lf"),
|
|
|
|
|
str(Path("esp_common") / "soc.lf"),
|
|
|
|
|
str(Path("newlib") / "system_libs.lf"),
|
|
|
|
|
str(Path("newlib") / "newlib.lf"),
|
2022-06-14 04:45:19 -07:00
|
|
|
):
|
2025-09-17 00:09:47 +02:00
|
|
|
result.append(str(Path(framework_components_dir) / fragment))
|
2020-04-16 16:25:52 +03:00
|
|
|
|
|
|
|
|
if sdk_config.get("SPIRAM_CACHE_WORKAROUND", False):
|
2022-05-06 21:26:22 +03:00
|
|
|
result.append(
|
2025-09-17 00:09:47 +02:00
|
|
|
str(Path(framework_components_dir) / "newlib" / "esp32-spiram-rom-functions-c.lf")
|
2020-09-01 21:26:55 +03:00
|
|
|
)
|
2019-09-16 13:11:52 +03:00
|
|
|
|
2022-05-06 21:26:22 +03:00
|
|
|
if board.get("build.esp-idf.extra_lf_files", ""):
|
|
|
|
|
result.extend(
|
|
|
|
|
[
|
2025-09-17 00:09:47 +02:00
|
|
|
lf if os.path.isabs(lf) else str(Path(PROJECT_DIR) / lf)
|
2022-05-06 21:26:22 +03:00
|
|
|
for lf in board.get("build.esp-idf.extra_lf_files").splitlines()
|
|
|
|
|
if lf.strip()
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
return result
|
2019-09-16 13:11:52 +03:00
|
|
|
|
|
|
|
|
|
2024-05-10 13:50:54 +03:00
|
|
|
def extract_linker_script_fragments(
|
|
|
|
|
ninja_buildfile, framework_components_dir, sdk_config
|
|
|
|
|
):
|
|
|
|
|
def _normalize_fragment_path(base_dir, fragment_path):
|
|
|
|
|
if not os.path.isabs(fragment_path):
|
|
|
|
|
fragment_path = os.path.abspath(
|
2025-09-17 00:09:47 +02:00
|
|
|
str(Path(base_dir) / fragment_path)
|
2024-05-10 13:50:54 +03:00
|
|
|
)
|
|
|
|
|
if not os.path.isfile(fragment_path):
|
|
|
|
|
print("Warning! The `%s` fragment is not found!" % fragment_path)
|
|
|
|
|
|
|
|
|
|
return fragment_path
|
|
|
|
|
|
2025-09-17 00:09:47 +02:00
|
|
|
if not os.path.isfile(ninja_buildfile):
|
|
|
|
|
sys.stderr.write(
|
|
|
|
|
"Error: Missing Ninja build file '%s' for linker fragment extraction\n"
|
|
|
|
|
% ninja_buildfile
|
|
|
|
|
)
|
|
|
|
|
env.Exit(1)
|
2024-05-10 13:50:54 +03:00
|
|
|
|
|
|
|
|
result = []
|
|
|
|
|
with open(ninja_buildfile, encoding="utf8") as fp:
|
|
|
|
|
for line in fp.readlines():
|
|
|
|
|
if "sections.ld: CUSTOM_COMMAND" not in line:
|
|
|
|
|
continue
|
|
|
|
|
for fragment_match in re.finditer(r"(\S+\.lf\b)+", line):
|
|
|
|
|
result.append(_normalize_fragment_path(
|
|
|
|
|
BUILD_DIR, fragment_match.group(0).replace("$:", ":")
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
# Fall back option if the new algorithm didn't work
|
|
|
|
|
if not result:
|
|
|
|
|
result = extract_linker_script_fragments_backup(
|
|
|
|
|
framework_components_dir, sdk_config
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if board.get("build.esp-idf.extra_lf_files", ""):
|
|
|
|
|
for fragment_path in board.get(
|
|
|
|
|
"build.esp-idf.extra_lf_files"
|
|
|
|
|
).splitlines():
|
|
|
|
|
if not fragment_path.strip():
|
|
|
|
|
continue
|
|
|
|
|
result.append(_normalize_fragment_path(PROJECT_DIR, fragment_path))
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
2020-04-16 21:57:06 +03:00
|
|
|
def create_custom_libraries_list(ldgen_libraries_file, ignore_targets):
|
2020-09-01 21:26:55 +03:00
|
|
|
if not os.path.isfile(ldgen_libraries_file):
|
2020-03-05 11:18:07 +02:00
|
|
|
sys.stderr.write("Error: Couldn't find the list of framework libraries\n")
|
|
|
|
|
env.Exit(1)
|
2019-09-16 13:11:52 +03:00
|
|
|
|
2020-04-16 21:57:06 +03:00
|
|
|
pio_libraries_file = ldgen_libraries_file + "_pio"
|
2019-09-16 13:11:52 +03:00
|
|
|
|
2020-09-01 21:26:55 +03:00
|
|
|
if os.path.isfile(pio_libraries_file):
|
2020-03-05 11:18:07 +02:00
|
|
|
return pio_libraries_file
|
2019-09-16 13:11:52 +03:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
lib_paths = []
|
2020-04-16 21:57:06 +03:00
|
|
|
with open(ldgen_libraries_file, "r") as fp:
|
2020-03-05 11:18:07 +02:00
|
|
|
lib_paths = fp.readlines()
|
2019-07-04 21:10:01 +03:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
with open(pio_libraries_file, "w") as fp:
|
|
|
|
|
for lib_path in lib_paths:
|
2020-04-16 21:57:06 +03:00
|
|
|
if all(
|
|
|
|
|
"lib%s.a" % t.replace("__idf_", "") not in lib_path
|
|
|
|
|
for t in ignore_targets
|
|
|
|
|
):
|
2020-03-05 11:18:07 +02:00
|
|
|
fp.write(lib_path)
|
2019-07-04 21:10:01 +03:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
return pio_libraries_file
|
2019-07-04 21:10:01 +03:00
|
|
|
|
2016-11-20 12:59:28 +02:00
|
|
|
|
2020-04-16 21:57:06 +03:00
|
|
|
def generate_project_ld_script(sdk_config, ignore_targets=None):
|
|
|
|
|
ignore_targets = ignore_targets or []
|
2022-05-06 21:26:22 +03:00
|
|
|
linker_script_fragments = extract_linker_script_fragments(
|
2025-09-17 00:09:47 +02:00
|
|
|
str(Path(BUILD_DIR) / "build.ninja"),
|
|
|
|
|
str(Path(FRAMEWORK_DIR) / "components"),
|
2024-05-10 13:50:54 +03:00
|
|
|
sdk_config
|
2020-04-16 21:57:06 +03:00
|
|
|
)
|
2016-11-20 12:59:28 +02:00
|
|
|
|
2024-05-10 13:50:54 +03:00
|
|
|
# Create a new file to avoid automatically generated library entry as files
|
|
|
|
|
# from this library are built internally by PlatformIO
|
2020-03-05 11:18:07 +02:00
|
|
|
libraries_list = create_custom_libraries_list(
|
2025-09-17 00:09:47 +02:00
|
|
|
str(Path(BUILD_DIR) / "ldgen_libraries"), ignore_targets
|
2017-02-19 21:23:04 +02:00
|
|
|
)
|
2016-11-20 12:59:28 +02:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
args = {
|
2025-09-17 00:09:47 +02:00
|
|
|
"script": str(Path(FRAMEWORK_DIR) / "tools" / "ldgen" / "ldgen.py"),
|
2021-06-23 14:25:46 +03:00
|
|
|
"config": SDKCONFIG_PATH,
|
2022-05-06 21:26:22 +03:00
|
|
|
"fragments": " ".join(
|
|
|
|
|
['"%s"' % fs.to_unix_path(f) for f in linker_script_fragments]
|
|
|
|
|
),
|
2025-09-17 00:09:47 +02:00
|
|
|
"kconfig": str(Path(FRAMEWORK_DIR) / "Kconfig"),
|
|
|
|
|
"env_file": str(Path("$BUILD_DIR") / "config.env"),
|
2020-03-05 11:18:07 +02:00
|
|
|
"libraries_list": libraries_list,
|
2025-09-17 00:09:47 +02:00
|
|
|
"objdump": str(Path(TOOLCHAIN_DIR) / "bin" / env.subst("$CC").replace("-gcc", "-objdump")),
|
2020-03-05 11:18:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd = (
|
2023-01-31 21:51:01 +02:00
|
|
|
'"$ESPIDF_PYTHONEXE" "{script}" --input $SOURCE '
|
2020-03-05 11:18:07 +02:00
|
|
|
'--config "{config}" --fragments {fragments} --output $TARGET '
|
|
|
|
|
'--kconfig "{kconfig}" --env-file "{env_file}" '
|
|
|
|
|
'--libraries-file "{libraries_list}" '
|
|
|
|
|
'--objdump "{objdump}"'
|
|
|
|
|
).format(**args)
|
|
|
|
|
|
2025-09-17 00:09:47 +02:00
|
|
|
initial_ld_script = str(Path(FRAMEWORK_DIR) / "components" / "esp_system" / "ld" / idf_variant / "sections.ld.in")
|
2024-07-30 14:44:37 +02:00
|
|
|
|
2024-08-21 15:59:56 +02:00
|
|
|
framework_version = [int(v) for v in get_framework_version().split(".")]
|
|
|
|
|
if framework_version[:2] > [5, 2]:
|
2024-07-30 14:44:37 +02:00
|
|
|
initial_ld_script = preprocess_linker_file(
|
|
|
|
|
initial_ld_script,
|
2025-09-17 00:09:47 +02:00
|
|
|
str(Path(BUILD_DIR) / "esp-idf" / "esp_system" / "ld" / "sections.ld.in"),
|
2024-07-30 14:44:37 +02:00
|
|
|
)
|
|
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
return env.Command(
|
2025-09-17 00:09:47 +02:00
|
|
|
str(Path("$BUILD_DIR") / "sections.ld"),
|
2024-07-30 14:44:37 +02:00
|
|
|
initial_ld_script,
|
2020-03-05 11:18:07 +02:00
|
|
|
env.VerboseAction(cmd, "Generating project linker script $TARGET"),
|
2016-11-20 12:59:28 +02:00
|
|
|
)
|
|
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
|
2023-01-04 20:05:31 +02:00
|
|
|
# A temporary workaround to avoid modifying CMake mainly for the "heap" library.
|
|
|
|
|
# The "tlsf.c" source file in this library has an include flag relative
|
|
|
|
|
# to CMAKE_CURRENT_SOURCE_DIR which breaks PlatformIO builds that have a
|
|
|
|
|
# different working directory
|
|
|
|
|
def _fix_component_relative_include(config, build_flags, source_index):
|
|
|
|
|
source_file_path = config["sources"][source_index]["path"]
|
|
|
|
|
build_flags = build_flags.replace("..", os.path.dirname(source_file_path) + "/..")
|
|
|
|
|
return build_flags
|
|
|
|
|
|
2023-01-16 14:22:17 +02:00
|
|
|
|
2022-05-06 12:52:39 +03:00
|
|
|
def prepare_build_envs(config, default_env, debug_allowed=True):
|
2020-03-05 11:18:07 +02:00
|
|
|
build_envs = []
|
2023-09-05 12:33:22 +03:00
|
|
|
target_compile_groups = config.get("compileGroups", [])
|
|
|
|
|
if not target_compile_groups:
|
|
|
|
|
print("Warning! The `%s` component doesn't register any source files. "
|
|
|
|
|
"Check if sources are set in component's CMakeLists.txt!" % config["name"]
|
|
|
|
|
)
|
2016-11-13 21:42:12 +02:00
|
|
|
|
2022-05-06 12:52:39 +03:00
|
|
|
is_build_type_debug = "debug" in env.GetBuildType() and debug_allowed
|
2020-03-05 11:18:07 +02:00
|
|
|
for cg in target_compile_groups:
|
|
|
|
|
includes = []
|
|
|
|
|
sys_includes = []
|
|
|
|
|
for inc in cg.get("includes", []):
|
|
|
|
|
inc_path = inc["path"]
|
|
|
|
|
if inc.get("isSystem", False):
|
|
|
|
|
sys_includes.append(inc_path)
|
|
|
|
|
else:
|
|
|
|
|
includes.append(inc_path)
|
|
|
|
|
|
|
|
|
|
defines = extract_defines(cg)
|
|
|
|
|
compile_commands = cg.get("compileCommandFragments", [])
|
|
|
|
|
build_env = default_env.Clone()
|
2023-08-03 13:56:50 +03:00
|
|
|
build_env.SetOption("implicit_cache", 1)
|
2020-03-05 11:18:07 +02:00
|
|
|
for cc in compile_commands:
|
2024-09-09 18:45:59 +02:00
|
|
|
build_flags = cc.get("fragment", "").strip("\" ")
|
2020-03-05 11:18:07 +02:00
|
|
|
if not build_flags.startswith("-D"):
|
2023-01-04 20:05:31 +02:00
|
|
|
if build_flags.startswith("-include") and ".." in build_flags:
|
|
|
|
|
source_index = cg.get("sourceIndexes")[0]
|
|
|
|
|
build_flags = _fix_component_relative_include(
|
|
|
|
|
config, build_flags, source_index)
|
2023-03-03 14:13:44 +02:00
|
|
|
parsed_flags = build_env.ParseFlags(build_flags)
|
|
|
|
|
build_env.AppendUnique(**parsed_flags)
|
|
|
|
|
if cg.get("language", "") == "ASM":
|
2024-02-22 14:03:26 +02:00
|
|
|
build_env.AppendUnique(ASPPFLAGS=parsed_flags.get("CCFLAGS", []))
|
2020-03-05 11:18:07 +02:00
|
|
|
build_env.AppendUnique(CPPDEFINES=defines, CPPPATH=includes)
|
|
|
|
|
if sys_includes:
|
|
|
|
|
build_env.Append(CCFLAGS=[("-isystem", inc) for inc in sys_includes])
|
|
|
|
|
build_env.ProcessUnFlags(default_env.get("BUILD_UNFLAGS"))
|
|
|
|
|
if is_build_type_debug:
|
|
|
|
|
build_env.ConfigureDebugFlags()
|
|
|
|
|
build_envs.append(build_env)
|
|
|
|
|
|
|
|
|
|
return build_envs
|
|
|
|
|
|
|
|
|
|
|
2022-05-06 21:26:22 +03:00
|
|
|
def compile_source_files(
|
|
|
|
|
config, default_env, project_src_dir, prepend_dir=None, debug_allowed=True
|
|
|
|
|
):
|
2022-05-06 12:52:39 +03:00
|
|
|
build_envs = prepare_build_envs(config, default_env, debug_allowed)
|
2020-03-05 11:18:07 +02:00
|
|
|
objects = []
|
2025-09-17 00:09:47 +02:00
|
|
|
# Canonical, symlink-resolved absolute path of the components directory
|
|
|
|
|
components_dir_path = (Path(FRAMEWORK_DIR) / "components").resolve()
|
2020-03-05 11:18:07 +02:00
|
|
|
for source in config.get("sources", []):
|
|
|
|
|
if source["path"].endswith(".rule"):
|
|
|
|
|
continue
|
|
|
|
|
compile_group_idx = source.get("compileGroupIndex")
|
|
|
|
|
if compile_group_idx is not None:
|
2024-03-27 20:25:34 +02:00
|
|
|
src_path = source.get("path")
|
2020-09-01 21:26:55 +03:00
|
|
|
if not os.path.isabs(src_path):
|
2020-03-05 11:18:07 +02:00
|
|
|
# For cases when sources are located near CMakeLists.txt
|
2025-09-17 00:09:47 +02:00
|
|
|
src_path = str(Path(project_src_dir) / src_path)
|
2021-01-21 13:42:23 +02:00
|
|
|
|
2025-09-17 00:09:47 +02:00
|
|
|
obj_path = str(Path("$BUILD_DIR") / (prepend_dir or ""))
|
|
|
|
|
src_path_obj = Path(src_path).resolve()
|
|
|
|
|
try:
|
|
|
|
|
rel = src_path_obj.relative_to(components_dir_path)
|
|
|
|
|
obj_path = str(Path(obj_path) / str(rel))
|
|
|
|
|
except ValueError:
|
|
|
|
|
# Preserve project substructure when possible
|
|
|
|
|
try:
|
|
|
|
|
rel_prj = src_path_obj.relative_to(Path(project_src_dir).resolve())
|
|
|
|
|
obj_path = str(Path(obj_path) / str(rel_prj))
|
|
|
|
|
except ValueError:
|
|
|
|
|
if not os.path.isabs(source["path"]):
|
|
|
|
|
obj_path = str(Path(obj_path) / source["path"])
|
|
|
|
|
else:
|
|
|
|
|
obj_path = str(Path(obj_path) / os.path.basename(src_path))
|
2021-01-21 13:42:23 +02:00
|
|
|
|
2023-01-04 20:05:31 +02:00
|
|
|
preserve_source_file_extension = board.get(
|
2024-06-06 16:08:36 +03:00
|
|
|
"build.esp-idf.preserve_source_file_extension", "yes"
|
|
|
|
|
) == "yes"
|
2023-01-04 20:05:31 +02:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
objects.append(
|
|
|
|
|
build_envs[compile_group_idx].StaticObject(
|
2023-01-04 20:05:31 +02:00
|
|
|
target=(
|
|
|
|
|
obj_path
|
|
|
|
|
if preserve_source_file_extension
|
|
|
|
|
else os.path.splitext(obj_path)[0]
|
|
|
|
|
) + ".o",
|
2025-09-17 00:09:47 +02:00
|
|
|
source=str(src_path_obj),
|
2020-03-05 11:18:07 +02:00
|
|
|
)
|
|
|
|
|
)
|
2017-01-24 23:25:18 +02:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
return objects
|
2019-09-16 13:11:52 +03:00
|
|
|
|
|
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
def run_tool(cmd):
|
2020-09-01 21:26:55 +03:00
|
|
|
idf_env = os.environ.copy()
|
2020-03-05 11:18:07 +02:00
|
|
|
populate_idf_env_vars(idf_env)
|
2019-09-16 13:11:52 +03:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
result = exec_command(cmd, env=idf_env)
|
|
|
|
|
if result["returncode"] != 0:
|
|
|
|
|
sys.stderr.write(result["out"] + "\n")
|
|
|
|
|
sys.stderr.write(result["err"] + "\n")
|
|
|
|
|
env.Exit(1)
|
2019-09-16 13:11:52 +03:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
if int(ARGUMENTS.get("PIOVERBOSE", 0)):
|
|
|
|
|
print(result["out"])
|
|
|
|
|
print(result["err"])
|
2019-09-16 13:11:52 +03:00
|
|
|
|
|
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
def RunMenuconfig(target, source, env):
|
2020-09-01 21:26:55 +03:00
|
|
|
idf_env = os.environ.copy()
|
2020-03-05 11:18:07 +02:00
|
|
|
populate_idf_env_vars(idf_env)
|
|
|
|
|
|
|
|
|
|
rc = subprocess.call(
|
|
|
|
|
[
|
2025-09-17 00:09:47 +02:00
|
|
|
CMAKE_DIR,
|
2020-03-05 11:18:07 +02:00
|
|
|
"--build",
|
2020-09-01 21:26:55 +03:00
|
|
|
BUILD_DIR,
|
2020-03-05 11:18:07 +02:00
|
|
|
"--target",
|
|
|
|
|
"menuconfig",
|
|
|
|
|
],
|
|
|
|
|
env=idf_env,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if rc != 0:
|
|
|
|
|
sys.stderr.write("Error: Couldn't execute 'menuconfig' target.\n")
|
|
|
|
|
env.Exit(1)
|
2019-09-16 13:11:52 +03:00
|
|
|
|
|
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
def run_cmake(src_dir, build_dir, extra_args=None):
|
|
|
|
|
cmd = [
|
2025-09-17 00:09:47 +02:00
|
|
|
CMAKE_DIR,
|
2020-03-05 11:18:07 +02:00
|
|
|
"-S",
|
|
|
|
|
src_dir,
|
|
|
|
|
"-B",
|
|
|
|
|
build_dir,
|
|
|
|
|
"-G",
|
|
|
|
|
"Ninja",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
if extra_args:
|
|
|
|
|
cmd.extend(extra_args)
|
|
|
|
|
|
|
|
|
|
run_tool(cmd)
|
2019-09-16 13:11:52 +03:00
|
|
|
|
|
|
|
|
|
2025-09-17 00:09:47 +02:00
|
|
|
def get_lib_ignore_components():
|
|
|
|
|
"""
|
|
|
|
|
Get components to ignore from lib_ignore project option using component_manager.
|
|
|
|
|
This ensures consistency with the Arduino framework's lib_ignore handling.
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
# Create a LibraryIgnoreHandler instance to process lib_ignore
|
|
|
|
|
config = _component_manager.ComponentManagerConfig(env)
|
|
|
|
|
logger = _component_manager.ComponentLogger()
|
|
|
|
|
lib_handler = _component_manager.LibraryIgnoreHandler(config, logger)
|
|
|
|
|
|
|
|
|
|
# Get the processed lib_ignore entries (already converted to component names)
|
|
|
|
|
lib_ignore_entries = lib_handler._get_lib_ignore_entries()
|
|
|
|
|
|
|
|
|
|
return lib_ignore_entries
|
|
|
|
|
except (OSError, ValueError, RuntimeError, KeyError) as e:
|
|
|
|
|
print(f"[ESP-IDF] Warning: Could not process lib_ignore: {e}")
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
def find_lib_deps(components_map, elf_config, link_args, ignore_components=None):
|
|
|
|
|
ignore_components = ignore_components or []
|
2025-09-17 00:09:47 +02:00
|
|
|
ignore_set = set(ignore_components)
|
|
|
|
|
result = []
|
|
|
|
|
for d in elf_config.get("dependencies", []):
|
|
|
|
|
comp = components_map.get(d["id"])
|
|
|
|
|
if not comp:
|
|
|
|
|
continue
|
|
|
|
|
comp_name = comp["config"]["name"]
|
|
|
|
|
if comp_name in ignore_set:
|
|
|
|
|
continue
|
|
|
|
|
result.append(comp["lib"])
|
2020-03-05 11:18:07 +02:00
|
|
|
|
|
|
|
|
implicit_lib_deps = link_args.get("__LIB_DEPS", [])
|
|
|
|
|
for component in components_map.values():
|
|
|
|
|
component_config = component["config"]
|
|
|
|
|
if (
|
|
|
|
|
component_config["type"] not in ("STATIC_LIBRARY", "OBJECT_LIBRARY")
|
2025-09-17 00:09:47 +02:00
|
|
|
or component_config["name"] in ignore_set
|
2020-03-05 11:18:07 +02:00
|
|
|
):
|
|
|
|
|
continue
|
|
|
|
|
if (
|
|
|
|
|
component_config["nameOnDisk"] in implicit_lib_deps
|
|
|
|
|
and component["lib"] not in result
|
|
|
|
|
):
|
|
|
|
|
result.append(component["lib"])
|
2019-09-16 13:11:52 +03:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
return result
|
2019-09-16 13:11:52 +03:00
|
|
|
|
2019-07-04 21:10:01 +03:00
|
|
|
|
2022-05-06 12:52:39 +03:00
|
|
|
def build_bootloader(sdk_config):
|
2025-09-17 00:09:47 +02:00
|
|
|
bootloader_src_dir = str(Path(FRAMEWORK_DIR) / "components" / "bootloader" / "subproject")
|
2020-03-05 11:18:07 +02:00
|
|
|
code_model = get_cmake_code_model(
|
|
|
|
|
bootloader_src_dir,
|
2025-09-17 00:09:47 +02:00
|
|
|
str(Path(BUILD_DIR) / "bootloader"),
|
2020-03-05 11:18:07 +02:00
|
|
|
[
|
2020-09-01 21:26:55 +03:00
|
|
|
"-DIDF_TARGET=" + idf_variant,
|
2020-03-05 11:18:07 +02:00
|
|
|
"-DPYTHON_DEPS_CHECKED=1",
|
2023-01-31 21:51:01 +02:00
|
|
|
"-DPYTHON=" + get_python_exe(),
|
2020-09-01 21:26:55 +03:00
|
|
|
"-DIDF_PATH=" + FRAMEWORK_DIR,
|
2021-06-23 14:25:46 +03:00
|
|
|
"-DSDKCONFIG=" + SDKCONFIG_PATH,
|
2024-09-16 20:11:32 +02:00
|
|
|
"-DPROJECT_SOURCE_DIR=" + PROJECT_DIR,
|
2020-09-01 21:26:55 +03:00
|
|
|
"-DLEGACY_INCLUDE_COMMON_HEADERS=",
|
2025-09-17 00:09:47 +02:00
|
|
|
"-DEXTRA_COMPONENT_DIRS=" + str(Path(FRAMEWORK_DIR) / "components" / "bootloader"),
|
2019-07-05 16:45:11 +03:00
|
|
|
],
|
2020-03-05 11:18:07 +02:00
|
|
|
)
|
2019-07-05 16:45:11 +03:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
if not code_model:
|
|
|
|
|
sys.stderr.write("Error: Couldn't find code model for bootloader\n")
|
|
|
|
|
env.Exit(1)
|
|
|
|
|
|
|
|
|
|
target_configs = load_target_configurations(
|
2020-09-01 21:26:55 +03:00
|
|
|
code_model,
|
2025-09-17 00:09:47 +02:00
|
|
|
str(Path(BUILD_DIR) / "bootloader" / ".cmake" / "api" / "v1" / "reply"),
|
2019-07-04 21:10:01 +03:00
|
|
|
)
|
|
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
elf_config = get_project_elf(target_configs)
|
|
|
|
|
if not elf_config:
|
|
|
|
|
sys.stderr.write(
|
|
|
|
|
"Error: Couldn't load the main firmware target of the project\n"
|
2019-07-04 21:10:01 +03:00
|
|
|
)
|
2020-03-05 11:18:07 +02:00
|
|
|
env.Exit(1)
|
2019-07-04 21:10:01 +03:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
bootloader_env = env.Clone()
|
|
|
|
|
components_map = get_components_map(
|
|
|
|
|
target_configs, ["STATIC_LIBRARY", "OBJECT_LIBRARY"]
|
2019-07-04 21:10:01 +03:00
|
|
|
)
|
|
|
|
|
|
2022-05-06 12:52:39 +03:00
|
|
|
# Note: By default the size of bootloader is limited to 0x2000 bytes,
|
|
|
|
|
# in debug mode the footprint size can be easily grow beyond this limit
|
|
|
|
|
build_components(
|
|
|
|
|
bootloader_env,
|
|
|
|
|
components_map,
|
|
|
|
|
bootloader_src_dir,
|
|
|
|
|
"bootloader",
|
|
|
|
|
debug_allowed=sdk_config.get("BOOTLOADER_COMPILER_OPTIMIZATION_DEBUG", False),
|
|
|
|
|
)
|
2020-03-05 11:18:07 +02:00
|
|
|
link_args = extract_link_args(elf_config)
|
|
|
|
|
extra_flags = filter_args(link_args["LINKFLAGS"], ["-T", "-u"])
|
|
|
|
|
link_args["LINKFLAGS"] = sorted(
|
|
|
|
|
list(set(link_args["LINKFLAGS"]) - set(extra_flags))
|
|
|
|
|
)
|
2019-07-04 21:10:01 +03:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
bootloader_env.MergeFlags(link_args)
|
|
|
|
|
bootloader_env.Append(LINKFLAGS=extra_flags)
|
|
|
|
|
bootloader_libs = find_lib_deps(components_map, elf_config, link_args)
|
2019-07-05 16:48:26 +03:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
bootloader_env.Prepend(__RPATH="-Wl,--start-group ")
|
2020-03-16 16:05:27 +02:00
|
|
|
bootloader_env.Append(
|
|
|
|
|
CPPDEFINES=["__BOOTLOADER_BUILD"], _LIBDIRFLAGS=" -Wl,--end-group"
|
|
|
|
|
)
|
2019-07-05 16:48:26 +03:00
|
|
|
|
2020-03-16 16:05:27 +02:00
|
|
|
return bootloader_env.ElfToBin(
|
2025-09-17 00:09:47 +02:00
|
|
|
str(Path("$BUILD_DIR") / "bootloader"),
|
2020-09-01 21:26:55 +03:00
|
|
|
bootloader_env.Program(
|
2025-09-17 00:09:47 +02:00
|
|
|
str(Path("$BUILD_DIR") / "bootloader.elf"), bootloader_libs
|
2020-09-01 21:26:55 +03:00
|
|
|
),
|
2020-03-05 11:18:07 +02:00
|
|
|
)
|
2016-10-24 20:23:25 +03:00
|
|
|
|
|
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
def get_targets_by_type(target_configs, target_types, ignore_targets=None):
|
|
|
|
|
ignore_targets = ignore_targets or []
|
|
|
|
|
result = []
|
|
|
|
|
for target_config in target_configs.values():
|
|
|
|
|
if (
|
|
|
|
|
target_config["type"] in target_types
|
|
|
|
|
and target_config["name"] not in ignore_targets
|
|
|
|
|
):
|
|
|
|
|
result.append(target_config)
|
2016-10-24 20:23:25 +03:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
return result
|
2017-02-19 21:23:04 +02:00
|
|
|
|
2017-02-20 21:59:41 +02:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
def get_components_map(target_configs, target_types, ignore_components=None):
|
|
|
|
|
result = {}
|
|
|
|
|
for config in get_targets_by_type(target_configs, target_types, ignore_components):
|
2022-07-25 21:54:16 +03:00
|
|
|
if "nameOnDisk" not in config:
|
|
|
|
|
config["nameOnDisk"] = "lib%s.a" % config["name"]
|
2020-03-05 11:18:07 +02:00
|
|
|
result[config["id"]] = {"config": config}
|
2019-09-16 13:11:52 +03:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
return result
|
2017-02-20 21:59:41 +02:00
|
|
|
|
2016-10-24 20:23:25 +03:00
|
|
|
|
2022-05-06 12:52:39 +03:00
|
|
|
def build_components(
|
|
|
|
|
env, components_map, project_src_dir, prepend_dir=None, debug_allowed=True
|
|
|
|
|
):
|
2020-03-05 11:18:07 +02:00
|
|
|
for k, v in components_map.items():
|
|
|
|
|
components_map[k]["lib"] = build_library(
|
2022-05-06 12:52:39 +03:00
|
|
|
env, v["config"], project_src_dir, prepend_dir, debug_allowed
|
2020-03-05 11:18:07 +02:00
|
|
|
)
|
2017-02-20 21:59:41 +02:00
|
|
|
|
2017-01-24 23:25:18 +02:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
def get_project_elf(target_configs):
|
|
|
|
|
exec_targets = get_targets_by_type(target_configs, ["EXECUTABLE"])
|
|
|
|
|
if len(exec_targets) > 1:
|
|
|
|
|
print(
|
|
|
|
|
"Warning: Multiple elf targets found. The %s will be used!"
|
|
|
|
|
% exec_targets[0]["name"]
|
|
|
|
|
)
|
2019-04-18 21:54:59 +03:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
return exec_targets[0]
|
2016-10-24 20:23:25 +03:00
|
|
|
|
2019-09-16 16:36:30 +03:00
|
|
|
|
2020-04-16 21:57:06 +03:00
|
|
|
def generate_default_component():
|
|
|
|
|
# Used to force CMake generate build environments for all supported languages
|
|
|
|
|
|
|
|
|
|
prj_cmake_tpl = """# Warning! Do not delete this auto-generated file.
|
|
|
|
|
file(GLOB component_sources *.c* *.S)
|
|
|
|
|
idf_component_register(SRCS ${component_sources})
|
|
|
|
|
"""
|
2025-09-17 00:09:47 +02:00
|
|
|
dummy_component_path = str(Path(FRAMEWORK_DIR) / "components" / "__pio_env")
|
2022-06-14 14:53:04 +03:00
|
|
|
if os.path.isdir(dummy_component_path):
|
|
|
|
|
return
|
|
|
|
|
|
2025-09-17 00:09:47 +02:00
|
|
|
os.makedirs(dummy_component_path, exist_ok=True)
|
2020-04-16 21:57:06 +03:00
|
|
|
|
|
|
|
|
for ext in (".cpp", ".c", ".S"):
|
2025-09-17 00:09:47 +02:00
|
|
|
dummy_file = str(Path(dummy_component_path) / ("__dummy" + ext))
|
2020-09-01 21:26:55 +03:00
|
|
|
if not os.path.isfile(dummy_file):
|
2020-04-16 21:57:06 +03:00
|
|
|
open(dummy_file, "a").close()
|
|
|
|
|
|
2025-09-17 00:09:47 +02:00
|
|
|
component_cmake = str(Path(dummy_component_path) / "CMakeLists.txt")
|
2020-09-01 21:26:55 +03:00
|
|
|
if not os.path.isfile(component_cmake):
|
2020-04-16 21:57:06 +03:00
|
|
|
with open(component_cmake, "w") as fp:
|
|
|
|
|
fp.write(prj_cmake_tpl)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def find_default_component(target_configs):
|
|
|
|
|
for config in target_configs:
|
|
|
|
|
if "__pio_env" in config:
|
|
|
|
|
return config
|
2022-05-31 13:52:59 +03:00
|
|
|
sys.stderr.write(
|
|
|
|
|
"Error! Failed to find the default IDF component with build information for "
|
|
|
|
|
"generic files.\nCheck that the `EXTRA_COMPONENT_DIRS` option is not overridden "
|
|
|
|
|
"in your CMakeLists.txt.\nSee an example with an extra component here "
|
|
|
|
|
"https://docs.platformio.org/en/latest/frameworks/espidf.html#esp-idf-components\n"
|
|
|
|
|
)
|
|
|
|
|
env.Exit(1)
|
2020-04-16 21:57:06 +03:00
|
|
|
|
|
|
|
|
|
2024-07-08 15:27:15 +02:00
|
|
|
def get_framework_version():
|
|
|
|
|
def _extract_from_cmake_version_file():
|
2025-09-17 00:09:47 +02:00
|
|
|
version_cmake_file = str(Path(FRAMEWORK_DIR) / "tools" / "cmake" / "version.cmake")
|
2024-07-08 15:27:15 +02:00
|
|
|
if not os.path.isfile(version_cmake_file):
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
with open(version_cmake_file, encoding="utf8") as fp:
|
|
|
|
|
pattern = r"set\(IDF_VERSION_(MAJOR|MINOR|PATCH) (\d+)\)"
|
|
|
|
|
matches = re.findall(pattern, fp.read())
|
|
|
|
|
if len(matches) != 3:
|
|
|
|
|
return
|
|
|
|
|
# If found all three parts of the version
|
|
|
|
|
return ".".join([match[1] for match in matches])
|
|
|
|
|
|
|
|
|
|
pkg = platform.get_package("framework-espidf")
|
|
|
|
|
version = get_original_version(str(pkg.metadata.version.truncate()))
|
|
|
|
|
if not version:
|
|
|
|
|
# Fallback value extracted directly from the cmake version file
|
|
|
|
|
version = _extract_from_cmake_version_file()
|
|
|
|
|
if not version:
|
|
|
|
|
version = "0.0.0"
|
|
|
|
|
|
|
|
|
|
return version
|
|
|
|
|
|
|
|
|
|
|
2020-12-02 13:10:37 +02:00
|
|
|
def create_version_file():
|
2025-09-17 00:09:47 +02:00
|
|
|
version_file = str(Path(FRAMEWORK_DIR) / "version.txt")
|
2020-09-01 21:26:55 +03:00
|
|
|
if not os.path.isfile(version_file):
|
|
|
|
|
with open(version_file, "w") as fp:
|
2024-07-08 15:27:15 +02:00
|
|
|
fp.write(get_framework_version())
|
2020-09-01 21:26:55 +03:00
|
|
|
|
|
|
|
|
|
2020-12-02 13:10:37 +02:00
|
|
|
def generate_empty_partition_image(binary_path, image_size):
|
|
|
|
|
empty_partition = env.Command(
|
|
|
|
|
binary_path,
|
|
|
|
|
None,
|
|
|
|
|
env.VerboseAction(
|
2023-01-31 21:51:01 +02:00
|
|
|
'"$ESPIDF_PYTHONEXE" "%s" %s $TARGET'
|
2020-12-02 13:10:37 +02:00
|
|
|
% (
|
2025-09-17 00:09:47 +02:00
|
|
|
str(Path(FRAMEWORK_DIR) / "components" / "partition_table" / "gen_empty_partition.py"),
|
2020-12-02 13:10:37 +02:00
|
|
|
image_size,
|
|
|
|
|
),
|
|
|
|
|
"Generating an empty partition $TARGET",
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
|
2024-12-14 12:42:42 +01:00
|
|
|
if flag_custom_sdkonfig == False:
|
|
|
|
|
env.Depends("$BUILD_DIR/$PROGNAME$PROGSUFFIX", empty_partition)
|
2020-12-02 13:10:37 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_partition_info(pt_path, pt_offset, pt_params):
|
2020-12-15 18:37:30 +02:00
|
|
|
if not os.path.isfile(pt_path):
|
|
|
|
|
sys.stderr.write(
|
2024-02-12 14:36:28 +02:00
|
|
|
"Missing partition table file `%s`\n" % pt_path
|
2021-01-21 13:42:23 +02:00
|
|
|
)
|
2020-12-15 18:37:30 +02:00
|
|
|
env.Exit(1)
|
|
|
|
|
|
2020-12-02 13:10:37 +02:00
|
|
|
cmd = [
|
2023-01-31 21:51:01 +02:00
|
|
|
get_python_exe(),
|
2025-09-17 00:09:47 +02:00
|
|
|
str(Path(FRAMEWORK_DIR) / "components" / "partition_table" / "parttool.py"),
|
2020-12-02 13:10:37 +02:00
|
|
|
"-q",
|
|
|
|
|
"--partition-table-offset",
|
|
|
|
|
hex(pt_offset),
|
|
|
|
|
"--partition-table-file",
|
|
|
|
|
pt_path,
|
|
|
|
|
"get_partition_info",
|
|
|
|
|
"--info",
|
|
|
|
|
"size",
|
|
|
|
|
"offset",
|
|
|
|
|
]
|
|
|
|
|
|
2025-09-17 00:09:47 +02:00
|
|
|
if pt_params.get("name") == "boot":
|
2020-12-02 13:10:37 +02:00
|
|
|
cmd.append("--partition-boot-default")
|
|
|
|
|
else:
|
|
|
|
|
cmd.extend(
|
|
|
|
|
[
|
|
|
|
|
"--partition-type",
|
|
|
|
|
pt_params["type"],
|
|
|
|
|
"--partition-subtype",
|
|
|
|
|
pt_params["subtype"],
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
result = exec_command(cmd)
|
|
|
|
|
if result["returncode"] != 0:
|
|
|
|
|
sys.stderr.write(
|
|
|
|
|
"Couldn't extract information for %s/%s from the partition table\n"
|
|
|
|
|
% (pt_params["type"], pt_params["subtype"])
|
|
|
|
|
)
|
|
|
|
|
sys.stderr.write(result["out"] + "\n")
|
|
|
|
|
sys.stderr.write(result["err"] + "\n")
|
|
|
|
|
env.Exit(1)
|
|
|
|
|
|
|
|
|
|
size = offset = 0
|
|
|
|
|
if result["out"].strip():
|
|
|
|
|
size, offset = result["out"].strip().split(" ", 1)
|
|
|
|
|
|
|
|
|
|
return {"size": size, "offset": offset}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_app_partition_offset(pt_table, pt_offset):
|
|
|
|
|
# Get the default boot partition offset
|
2025-09-17 00:09:47 +02:00
|
|
|
ota_app_params = get_partition_info(pt_table, pt_offset, {"type": "app", "subtype": "ota_0"})
|
|
|
|
|
if ota_app_params.get("offset"):
|
|
|
|
|
return ota_app_params["offset"]
|
|
|
|
|
factory_app_params = get_partition_info(pt_table, pt_offset, {"type": "app", "subtype": "factory"})
|
|
|
|
|
return factory_app_params.get("offset", "0x10000")
|
2020-12-02 13:10:37 +02:00
|
|
|
|
|
|
|
|
|
2024-07-30 14:44:37 +02:00
|
|
|
def preprocess_linker_file(src_ld_script, target_ld_script):
|
|
|
|
|
return env.Command(
|
|
|
|
|
target_ld_script,
|
|
|
|
|
src_ld_script,
|
|
|
|
|
env.VerboseAction(
|
|
|
|
|
" ".join(
|
|
|
|
|
[
|
2025-09-17 00:09:47 +02:00
|
|
|
f'"{CMAKE_DIR}"',
|
|
|
|
|
f'-DCC="{str(Path(TOOLCHAIN_DIR) / "bin" / "$CC")}"',
|
2024-07-30 14:44:37 +02:00
|
|
|
"-DSOURCE=$SOURCE",
|
|
|
|
|
"-DTARGET=$TARGET",
|
2025-09-17 00:09:47 +02:00
|
|
|
f'-DCONFIG_DIR="{str(Path(BUILD_DIR) / "config")}"',
|
|
|
|
|
f'-DLD_DIR="{str(Path(FRAMEWORK_DIR) / "components" / "esp_system" / "ld")}"',
|
2024-07-30 14:44:37 +02:00
|
|
|
"-P",
|
2025-09-17 00:09:47 +02:00
|
|
|
f'"{str(Path("$BUILD_DIR") / "esp-idf" / "esp_system" / "ld" / "linker_script_generator.cmake")}"',
|
2024-07-30 14:44:37 +02:00
|
|
|
]
|
|
|
|
|
),
|
|
|
|
|
"Generating LD script $TARGET",
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2021-01-21 13:42:23 +02:00
|
|
|
def generate_mbedtls_bundle(sdk_config):
|
2025-09-17 00:09:47 +02:00
|
|
|
bundle_path = str(Path("$BUILD_DIR") / "x509_crt_bundle")
|
2021-01-21 13:42:23 +02:00
|
|
|
if os.path.isfile(env.subst(bundle_path)):
|
|
|
|
|
return
|
|
|
|
|
|
2025-09-17 00:09:47 +02:00
|
|
|
default_crt_dir = str(Path(FRAMEWORK_DIR) / "components" / "mbedtls" / "esp_crt_bundle")
|
2021-01-21 13:42:23 +02:00
|
|
|
|
2025-09-17 00:09:47 +02:00
|
|
|
cmd = [get_python_exe(), str(Path(default_crt_dir) / "gen_crt_bundle.py")]
|
2021-01-21 13:42:23 +02:00
|
|
|
|
|
|
|
|
crt_args = ["--input"]
|
|
|
|
|
if sdk_config.get("MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL", False):
|
2025-09-17 00:09:47 +02:00
|
|
|
crt_args.append(str(Path(default_crt_dir) / "cacrt_all.pem"))
|
|
|
|
|
crt_args.append(str(Path(default_crt_dir) / "cacrt_local.pem"))
|
2021-01-21 13:42:23 +02:00
|
|
|
elif sdk_config.get("MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN", False):
|
2025-09-17 00:09:47 +02:00
|
|
|
crt_args.append(str(Path(default_crt_dir) / "cacrt_all.pem"))
|
|
|
|
|
crt_args.append(str(Path(default_crt_dir) / "cacrt_local.pem"))
|
2021-01-21 13:42:23 +02:00
|
|
|
cmd.extend(
|
2025-09-17 00:09:47 +02:00
|
|
|
["--filter", str(Path(default_crt_dir) / "cmn_crt_authorities.csv")]
|
2021-01-21 13:42:23 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if sdk_config.get("MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE", False):
|
|
|
|
|
cert_path = sdk_config.get("MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE_PATH", "")
|
|
|
|
|
if os.path.isfile(cert_path) or os.path.isdir(cert_path):
|
|
|
|
|
crt_args.append(os.path.abspath(cert_path))
|
|
|
|
|
else:
|
|
|
|
|
print("Warning! Couldn't find custom certificate bundle %s" % cert_path)
|
|
|
|
|
|
|
|
|
|
crt_args.append("-q")
|
|
|
|
|
|
|
|
|
|
# Use exec_command to change working directory
|
2021-06-22 00:05:46 +03:00
|
|
|
exec_command(cmd + crt_args, cwd=BUILD_DIR)
|
2021-01-21 13:42:23 +02:00
|
|
|
env.Execute(
|
|
|
|
|
env.VerboseAction(
|
|
|
|
|
" ".join(
|
|
|
|
|
[
|
2025-09-17 00:09:47 +02:00
|
|
|
f'"{CMAKE_DIR}"',
|
|
|
|
|
f'-DDATA_FILE="{bundle_path}"',
|
|
|
|
|
f'-DSOURCE_FILE="{bundle_path}.S"',
|
2021-01-21 13:42:23 +02:00
|
|
|
"-DFILE_TYPE=BINARY",
|
|
|
|
|
"-P",
|
2025-09-17 00:09:47 +02:00
|
|
|
f'"{str(Path(FRAMEWORK_DIR) / "tools" / "cmake" / "scripts" / "data_file_embed_asm.cmake")}"',
|
2021-01-21 13:42:23 +02:00
|
|
|
]
|
|
|
|
|
),
|
|
|
|
|
"Generating assembly for certificate bundle...",
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2025-09-17 00:09:47 +02:00
|
|
|
def _get_uv_exe():
|
|
|
|
|
return get_executable_path(str(Path(PLATFORMIO_DIR) / "penv"), "uv")
|
|
|
|
|
|
|
|
|
|
|
2021-06-22 00:05:46 +03:00
|
|
|
def install_python_deps():
|
2025-09-17 00:09:47 +02:00
|
|
|
UV_EXE = _get_uv_exe()
|
|
|
|
|
|
2025-07-23 17:45:04 +02:00
|
|
|
def _get_installed_uv_packages(python_exe_path):
|
2021-06-22 00:05:46 +03:00
|
|
|
result = {}
|
|
|
|
|
try:
|
2025-07-23 17:45:04 +02:00
|
|
|
uv_output = subprocess.check_output([
|
2025-09-17 00:09:47 +02:00
|
|
|
UV_EXE, "pip", "list", "--python", python_exe_path, "--format=json"
|
2025-07-23 17:45:04 +02:00
|
|
|
])
|
|
|
|
|
packages = json.loads(uv_output)
|
|
|
|
|
except (subprocess.CalledProcessError, json.JSONDecodeError, OSError) as e:
|
|
|
|
|
print(f"Warning! Couldn't extract the list of installed Python packages: {e}")
|
2021-06-22 00:05:46 +03:00
|
|
|
return {}
|
2025-07-23 17:45:04 +02:00
|
|
|
|
2021-06-22 00:05:46 +03:00
|
|
|
for p in packages:
|
|
|
|
|
result[p["name"]] = pepver_to_semver(p["version"])
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
2025-09-17 00:09:47 +02:00
|
|
|
skip_python_packages = str(Path(FRAMEWORK_DIR) / ".pio_skip_pypackages")
|
2024-03-21 15:52:17 +02:00
|
|
|
if os.path.isfile(skip_python_packages):
|
|
|
|
|
return
|
|
|
|
|
|
2021-06-22 00:05:46 +03:00
|
|
|
deps = {
|
2023-05-25 21:01:33 +03:00
|
|
|
# https://github.com/platformio/platformio-core/issues/4614
|
|
|
|
|
"urllib3": "<2",
|
2021-11-05 13:49:21 +02:00
|
|
|
# https://github.com/platformio/platform-espressif32/issues/635
|
2025-07-03 17:12:25 +02:00
|
|
|
"cryptography": "~=44.0.0",
|
2025-02-25 12:15:19 +01:00
|
|
|
"pyparsing": ">=3.1.0,<4",
|
2025-07-23 17:45:04 +02:00
|
|
|
"idf-component-manager": "~=2.2",
|
2025-07-03 17:12:25 +02:00
|
|
|
"esp-idf-kconfig": "~=2.5.0"
|
2021-06-22 00:05:46 +03:00
|
|
|
}
|
|
|
|
|
|
2023-08-14 14:54:50 +03:00
|
|
|
if sys_platform.system() == "Darwin" and "arm" in sys_platform.machine().lower():
|
|
|
|
|
deps["chardet"] = ">=3.0.2,<4"
|
|
|
|
|
|
2023-01-31 21:51:01 +02:00
|
|
|
python_exe_path = get_python_exe()
|
2025-07-23 17:45:04 +02:00
|
|
|
installed_packages = _get_installed_uv_packages(python_exe_path)
|
2021-06-22 00:05:46 +03:00
|
|
|
packages_to_install = []
|
|
|
|
|
for package, spec in deps.items():
|
|
|
|
|
if package not in installed_packages:
|
|
|
|
|
packages_to_install.append(package)
|
2023-01-16 19:21:21 +02:00
|
|
|
elif spec:
|
2021-06-22 00:05:46 +03:00
|
|
|
version_spec = semantic_version.Spec(spec)
|
|
|
|
|
if not version_spec.match(installed_packages[package]):
|
|
|
|
|
packages_to_install.append(package)
|
|
|
|
|
|
|
|
|
|
if packages_to_install:
|
2025-07-23 17:45:04 +02:00
|
|
|
packages_str = " ".join(['"%s%s"' % (p, deps[p]) for p in packages_to_install])
|
|
|
|
|
|
|
|
|
|
# Use uv to install packages in the specific Python environment
|
2021-06-22 00:05:46 +03:00
|
|
|
env.Execute(
|
|
|
|
|
env.VerboseAction(
|
2025-09-17 00:09:47 +02:00
|
|
|
f'"{UV_EXE}" pip install --python "{python_exe_path}" {packages_str}',
|
2025-07-23 17:45:04 +02:00
|
|
|
"Installing ESP-IDF's Python dependencies with uv",
|
2021-06-22 00:05:46 +03:00
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
2023-01-31 21:51:01 +02:00
|
|
|
if IS_WINDOWS and "windows-curses" not in installed_packages:
|
2025-07-23 17:45:04 +02:00
|
|
|
# Install windows-curses in the IDF Python environment
|
2023-01-16 14:22:17 +02:00
|
|
|
env.Execute(
|
|
|
|
|
env.VerboseAction(
|
2025-09-17 00:09:47 +02:00
|
|
|
f'"{UV_EXE}" pip install --python "{python_exe_path}" windows-curses',
|
2025-07-23 17:45:04 +02:00
|
|
|
"Installing windows-curses package with uv",
|
2023-01-16 14:22:17 +02:00
|
|
|
)
|
|
|
|
|
)
|
2021-06-22 00:05:46 +03:00
|
|
|
|
2024-03-21 15:51:44 +02:00
|
|
|
|
2023-06-19 21:30:18 +03:00
|
|
|
def get_idf_venv_dir():
|
|
|
|
|
# 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
|
2024-07-08 15:27:15 +02:00
|
|
|
idf_version = get_framework_version()
|
2025-09-17 00:09:47 +02:00
|
|
|
return str(Path(PLATFORMIO_DIR) / "penv" / f".espidf-{idf_version}")
|
2023-06-19 21:30:18 +03:00
|
|
|
|
2024-03-21 15:51:44 +02:00
|
|
|
|
2023-06-19 21:30:18 +03:00
|
|
|
def ensure_python_venv_available():
|
|
|
|
|
|
2025-01-12 18:01:20 +01:00
|
|
|
def _get_idf_venv_python_version():
|
|
|
|
|
try:
|
|
|
|
|
version = subprocess.check_output(
|
|
|
|
|
[
|
|
|
|
|
get_python_exe(),
|
|
|
|
|
"-c",
|
|
|
|
|
"import sys;print('{0}.{1}.{2}-{3}.{4}'.format(*list(sys.version_info)))"
|
|
|
|
|
], text=True
|
|
|
|
|
)
|
|
|
|
|
return version.strip()
|
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
|
|
|
print("Failed to extract Python version from IDF virtual env!")
|
|
|
|
|
return None
|
|
|
|
|
|
2023-06-19 21:30:18 +03:00
|
|
|
def _is_venv_outdated(venv_data_file):
|
|
|
|
|
try:
|
|
|
|
|
with open(venv_data_file, "r", encoding="utf8") as fp:
|
|
|
|
|
venv_data = json.load(fp)
|
|
|
|
|
if venv_data.get("version", "") != IDF_ENV_VERSION:
|
2025-01-12 18:01:20 +01:00
|
|
|
print(
|
|
|
|
|
"Warning! IDF virtual environment version changed!"
|
|
|
|
|
)
|
|
|
|
|
return True
|
|
|
|
|
if (
|
|
|
|
|
venv_data.get("python_version", "")
|
|
|
|
|
!= _get_idf_venv_python_version()
|
|
|
|
|
):
|
|
|
|
|
print(
|
|
|
|
|
"Warning! Python version in the IDF virtual environment"
|
|
|
|
|
" differs from the current Python!"
|
|
|
|
|
)
|
2023-06-19 21:30:18 +03:00
|
|
|
return True
|
|
|
|
|
return False
|
2025-09-17 00:09:47 +02:00
|
|
|
except (OSError, ValueError):
|
2023-06-19 21:30:18 +03:00
|
|
|
return True
|
2021-06-22 00:05:46 +03:00
|
|
|
|
2023-01-31 21:51:01 +02:00
|
|
|
def _create_venv(venv_dir):
|
2025-09-17 00:09:47 +02:00
|
|
|
uv_path = _get_uv_exe()
|
2023-06-19 21:30:18 +03:00
|
|
|
|
|
|
|
|
if os.path.isdir(venv_dir):
|
|
|
|
|
try:
|
2025-01-12 18:01:20 +01:00
|
|
|
print("Removing an outdated IDF virtual environment")
|
2023-06-19 21:30:18 +03:00
|
|
|
shutil.rmtree(venv_dir)
|
|
|
|
|
except OSError:
|
|
|
|
|
print(
|
|
|
|
|
"Error: Cannot remove an outdated IDF virtual environment. " \
|
|
|
|
|
"Please remove the `%s` folder manually!" % venv_dir
|
|
|
|
|
)
|
|
|
|
|
env.Exit(1)
|
|
|
|
|
|
2025-09-17 00:09:47 +02:00
|
|
|
# Use uv to create a standalone IDF virtual env
|
2023-06-19 22:00:47 +03:00
|
|
|
env.Execute(
|
|
|
|
|
env.VerboseAction(
|
2025-09-17 00:09:47 +02:00
|
|
|
'"%s" venv --clear --quiet --python "%s" "%s"' % (uv_path, env.subst("$PYTHONEXE"), venv_dir),
|
|
|
|
|
"Creating a new virtual environment for IDF Python dependencies using uv",
|
2023-01-31 21:51:01 +02:00
|
|
|
)
|
2023-06-19 22:00:47 +03:00
|
|
|
)
|
2023-01-31 21:51:01 +02:00
|
|
|
|
2025-09-17 00:09:47 +02:00
|
|
|
# Verify that the venv was created successfully by checking for Python executable
|
|
|
|
|
python_path = get_executable_path(venv_dir, "python")
|
|
|
|
|
if not os.path.isfile(python_path):
|
|
|
|
|
sys.stderr.write("Error: Failed to create a proper virtual environment. Missing the Python executable!\n")
|
|
|
|
|
env.Exit(1)
|
2023-01-31 21:51:01 +02:00
|
|
|
|
2023-06-19 21:30:18 +03:00
|
|
|
venv_dir = get_idf_venv_dir()
|
2025-09-17 00:09:47 +02:00
|
|
|
venv_data_file = str(Path(venv_dir) / "pio-idf-venv.json")
|
2023-06-19 21:30:18 +03:00
|
|
|
if not os.path.isfile(venv_data_file) or _is_venv_outdated(venv_data_file):
|
2023-01-31 21:51:01 +02:00
|
|
|
_create_venv(venv_dir)
|
2025-01-12 18:01:20 +01:00
|
|
|
install_python_deps()
|
2023-06-19 21:30:18 +03:00
|
|
|
with open(venv_data_file, "w", encoding="utf8") as fp:
|
2025-01-12 18:01:20 +01:00
|
|
|
venv_info = {
|
|
|
|
|
"version": IDF_ENV_VERSION,
|
|
|
|
|
"python_version": _get_idf_venv_python_version()
|
|
|
|
|
}
|
2023-06-19 21:30:18 +03:00
|
|
|
json.dump(venv_info, fp, indent=2)
|
|
|
|
|
|
2023-01-31 21:51:01 +02:00
|
|
|
|
2023-06-19 21:30:18 +03:00
|
|
|
def get_python_exe():
|
2025-09-17 00:09:47 +02:00
|
|
|
python_exe_path = get_executable_path(get_idf_venv_dir(), "python")
|
|
|
|
|
if not os.path.isfile(python_exe_path):
|
|
|
|
|
sys.stderr.write("Error: Missing Python executable file `%s`\n" % python_exe_path)
|
|
|
|
|
env.Exit(1)
|
2023-01-31 21:51:01 +02:00
|
|
|
|
|
|
|
|
return python_exe_path
|
|
|
|
|
|
|
|
|
|
|
2021-06-22 00:05:46 +03:00
|
|
|
#
|
2025-01-12 18:01:20 +01:00
|
|
|
# Ensure Python environment contains everything required for IDF
|
2021-06-22 00:05:46 +03:00
|
|
|
#
|
|
|
|
|
|
2023-06-19 21:30:18 +03:00
|
|
|
ensure_python_venv_available()
|
2021-06-22 00:05:46 +03:00
|
|
|
|
2020-09-01 21:26:55 +03:00
|
|
|
# ESP-IDF package doesn't contain .git folder, instead package version is specified
|
|
|
|
|
# in a special file "version.h" in the root folder of the package
|
|
|
|
|
|
2020-12-02 13:10:37 +02:00
|
|
|
create_version_file()
|
2020-09-01 21:26:55 +03:00
|
|
|
|
2022-06-14 14:53:04 +03:00
|
|
|
# Generate a default component with dummy C/C++/ASM source files in the framework
|
|
|
|
|
# folder. This component is used to force the IDF build system generate build
|
|
|
|
|
# information for generic C/C++/ASM sources regardless of whether such files are used in project
|
|
|
|
|
|
|
|
|
|
generate_default_component()
|
|
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
#
|
|
|
|
|
# Generate final linker script
|
|
|
|
|
#
|
|
|
|
|
|
2020-04-30 18:15:40 +03:00
|
|
|
if not board.get("build.ldscript", ""):
|
2025-09-17 00:09:47 +02:00
|
|
|
initial_ld_script = board.get("build.esp-idf.ldscript", str(Path(FRAMEWORK_DIR) / "components" / "esp_system" / "ld" / idf_variant / "memory.ld.in"))
|
2024-07-30 14:44:37 +02:00
|
|
|
|
2024-08-21 15:59:56 +02:00
|
|
|
framework_version = [int(v) for v in get_framework_version().split(".")]
|
|
|
|
|
if framework_version[:2] > [5, 2]:
|
2024-07-30 14:44:37 +02:00
|
|
|
initial_ld_script = preprocess_linker_file(
|
|
|
|
|
initial_ld_script,
|
2025-09-17 00:09:47 +02:00
|
|
|
str(Path(BUILD_DIR) / "esp-idf" / "esp_system" / "ld" / "memory.ld.in")
|
2024-07-30 14:44:37 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
linker_script = env.Command(
|
2025-09-17 00:09:47 +02:00
|
|
|
str(Path("$BUILD_DIR") / "memory.ld"),
|
2024-07-30 14:44:37 +02:00
|
|
|
initial_ld_script,
|
2020-03-05 11:18:07 +02:00
|
|
|
env.VerboseAction(
|
2022-06-14 14:53:04 +03:00
|
|
|
'$CC -I"$BUILD_DIR/config" -I"%s" -C -P -x c -E $SOURCE -o $TARGET'
|
2025-09-17 00:09:47 +02:00
|
|
|
% str(Path(FRAMEWORK_DIR) / "components" / "esp_system" / "ld"),
|
2020-03-05 11:18:07 +02:00
|
|
|
"Generating LD script $TARGET",
|
|
|
|
|
),
|
2019-09-16 16:36:30 +03:00
|
|
|
)
|
|
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
env.Depends("$BUILD_DIR/$PROGNAME$PROGSUFFIX", linker_script)
|
2022-06-14 04:45:19 -07:00
|
|
|
env.Replace(LDSCRIPT_PATH="memory.ld")
|
2019-09-16 16:36:30 +03:00
|
|
|
|
2018-09-27 22:33:50 +03:00
|
|
|
|
2017-05-19 19:58:10 +03:00
|
|
|
#
|
2020-03-05 11:18:07 +02:00
|
|
|
# Current build script limitations
|
2017-05-19 19:58:10 +03:00
|
|
|
#
|
|
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
if any(" " in p for p in (FRAMEWORK_DIR, BUILD_DIR)):
|
|
|
|
|
sys.stderr.write("Error: Detected a whitespace character in project paths.\n")
|
|
|
|
|
env.Exit(1)
|
2019-04-18 21:54:59 +03:00
|
|
|
|
2021-06-22 00:05:46 +03:00
|
|
|
if not os.path.isdir(PROJECT_SRC_DIR):
|
|
|
|
|
sys.stderr.write(
|
|
|
|
|
"Error: Missing the `%s` folder with project sources.\n"
|
|
|
|
|
% os.path.basename(PROJECT_SRC_DIR)
|
|
|
|
|
)
|
|
|
|
|
env.Exit(1)
|
2019-09-16 13:11:52 +03:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
if env.subst("$SRC_FILTER"):
|
2020-04-16 22:01:56 +03:00
|
|
|
print(
|
2020-03-05 11:18:07 +02:00
|
|
|
(
|
2020-04-16 22:01:56 +03:00
|
|
|
"Warning: the 'src_filter' option cannot be used with ESP-IDF. Select source "
|
2020-03-05 11:18:07 +02:00
|
|
|
"files to build in the project CMakeLists.txt file.\n"
|
|
|
|
|
)
|
|
|
|
|
)
|
2019-09-16 13:11:52 +03:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
#
|
|
|
|
|
# Initial targets loading
|
|
|
|
|
#
|
2019-09-16 13:11:52 +03:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
# By default 'main' folder is used to store source files. In case when a user has
|
|
|
|
|
# default 'src' folder we need to add this as an extra component. If there is no 'main'
|
|
|
|
|
# folder CMake won't generate dependencies properly
|
2022-06-14 14:53:04 +03:00
|
|
|
extra_components = []
|
2025-09-17 00:09:47 +02:00
|
|
|
if PROJECT_SRC_DIR != str(Path(PROJECT_DIR) / "main"):
|
|
|
|
|
extra_components.append(str(Path(PROJECT_SRC_DIR).resolve()))
|
2021-06-22 00:05:46 +03:00
|
|
|
if "arduino" in env.subst("$PIOFRAMEWORK"):
|
|
|
|
|
extra_components.append(ARDUINO_FRAMEWORK_DIR)
|
2023-05-03 17:15:55 +03:00
|
|
|
# Add path to internal Arduino libraries so that the LDF will be able to find them
|
|
|
|
|
env.Append(
|
2025-09-17 00:09:47 +02:00
|
|
|
LIBSOURCE_DIRS=[str(Path(ARDUINO_FRAMEWORK_DIR) / "libraries")]
|
2023-05-03 17:15:55 +03:00
|
|
|
)
|
2020-03-05 11:18:07 +02:00
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
# Set ESP-IDF version environment variables (needed for proper Kconfig processing)
|
|
|
|
|
framework_version = get_framework_version()
|
|
|
|
|
major_version = framework_version.split('.')[0] + '.' + framework_version.split('.')[1]
|
|
|
|
|
os.environ["ESP_IDF_VERSION"] = major_version
|
|
|
|
|
|
|
|
|
|
# Configure CMake arguments with ESP-IDF version
|
|
|
|
|
extra_cmake_args = [
|
|
|
|
|
"-DIDF_TARGET=" + idf_variant,
|
|
|
|
|
"-DPYTHON_DEPS_CHECKED=1",
|
2025-09-17 00:09:47 +02:00
|
|
|
"-DEXTRA_COMPONENT_DIRS:PATH=" + ";".join(str(Path(p).resolve()) for p in extra_components),
|
2025-07-03 17:12:25 +02:00
|
|
|
"-DPYTHON=" + get_python_exe(),
|
|
|
|
|
"-DSDKCONFIG=" + SDKCONFIG_PATH,
|
|
|
|
|
f"-DESP_IDF_VERSION={major_version}",
|
|
|
|
|
f"-DESP_IDF_VERSION_MAJOR={framework_version.split('.')[0]}",
|
|
|
|
|
f"-DESP_IDF_VERSION_MINOR={framework_version.split('.')[1]}",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
# This will add the linker flag for the map file
|
|
|
|
|
extra_cmake_args.append(
|
2025-09-17 00:09:47 +02:00
|
|
|
f'-DCMAKE_EXE_LINKER_FLAGS=-Wl,-Map={fs.to_unix_path(str(Path(BUILD_DIR) / (env.subst("$PROGNAME") + ".map")))}'
|
2025-07-03 17:12:25 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Add any extra args from board config
|
|
|
|
|
extra_cmake_args += click.parser.split_arg_string(board.get("build.cmake_extra_args", ""))
|
|
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
print("Reading CMake configuration...")
|
|
|
|
|
project_codemodel = get_cmake_code_model(
|
2021-06-22 00:05:46 +03:00
|
|
|
PROJECT_DIR,
|
2020-03-05 11:18:07 +02:00
|
|
|
BUILD_DIR,
|
2025-07-03 17:12:25 +02:00
|
|
|
extra_cmake_args
|
2020-03-05 11:18:07 +02:00
|
|
|
)
|
2017-05-19 19:58:10 +03:00
|
|
|
|
2021-06-23 14:25:46 +03:00
|
|
|
# At this point the sdkconfig file should be generated by the underlying build system
|
2025-09-17 00:09:47 +02:00
|
|
|
if not os.path.isfile(SDKCONFIG_PATH):
|
|
|
|
|
sys.stderr.write("Missing auto-generated SDK configuration file `%s`\n" % SDKCONFIG_PATH)
|
|
|
|
|
env.Exit(1)
|
2021-06-23 14:25:46 +03:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
if not project_codemodel:
|
|
|
|
|
sys.stderr.write("Error: Couldn't find code model generated by CMake\n")
|
|
|
|
|
env.Exit(1)
|
2019-09-16 13:11:52 +03:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
target_configs = load_target_configurations(
|
2025-09-17 00:09:47 +02:00
|
|
|
project_codemodel, str(Path(BUILD_DIR) / CMAKE_API_REPLY_PATH)
|
2020-03-05 11:18:07 +02:00
|
|
|
)
|
2017-05-19 19:58:10 +03:00
|
|
|
|
2020-04-16 16:25:52 +03:00
|
|
|
sdk_config = get_sdk_configuration()
|
|
|
|
|
|
2021-06-22 00:05:46 +03:00
|
|
|
project_target_name = "__idf_%s" % os.path.basename(PROJECT_SRC_DIR)
|
2020-04-27 17:35:32 +03:00
|
|
|
if project_target_name not in target_configs:
|
|
|
|
|
sys.stderr.write("Error: Couldn't find the main target of the project!\n")
|
|
|
|
|
env.Exit(1)
|
|
|
|
|
|
2020-04-27 19:05:18 +03:00
|
|
|
if project_target_name != "__idf_main" and "__idf_main" in target_configs:
|
2020-03-05 11:18:07 +02:00
|
|
|
sys.stderr.write(
|
|
|
|
|
(
|
|
|
|
|
"Warning! Detected two different targets with project sources. Please use "
|
2020-04-27 17:35:32 +03:00
|
|
|
"either %s or specify 'main' folder in 'platformio.ini' file.\n"
|
|
|
|
|
% project_target_name
|
2020-03-05 11:18:07 +02:00
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
env.Exit(1)
|
2019-07-05 16:48:26 +03:00
|
|
|
|
2025-09-17 00:09:47 +02:00
|
|
|
project_ld_script = generate_project_ld_script(
|
2020-09-01 21:26:55 +03:00
|
|
|
sdk_config, [project_target_name, "__pio_env"]
|
|
|
|
|
)
|
2025-09-17 00:09:47 +02:00
|
|
|
env.Depends("$BUILD_DIR/$PROGNAME$PROGSUFFIX", project_ld_script)
|
2016-11-20 12:59:28 +02:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
elf_config = get_project_elf(target_configs)
|
2020-04-16 21:57:06 +03:00
|
|
|
default_config_name = find_default_component(target_configs)
|
2020-03-05 11:18:07 +02:00
|
|
|
framework_components_map = get_components_map(
|
2020-04-16 21:57:06 +03:00
|
|
|
target_configs,
|
|
|
|
|
["STATIC_LIBRARY", "OBJECT_LIBRARY"],
|
|
|
|
|
[project_target_name, default_config_name],
|
2020-03-05 11:18:07 +02:00
|
|
|
)
|
2016-11-20 12:59:28 +02:00
|
|
|
|
2021-06-22 00:05:46 +03:00
|
|
|
build_components(env, framework_components_map, PROJECT_DIR)
|
2016-11-20 12:59:28 +02:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
if not elf_config:
|
|
|
|
|
sys.stderr.write("Error: Couldn't load the main firmware target of the project\n")
|
|
|
|
|
env.Exit(1)
|
2016-11-20 12:59:28 +02:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
for component_config in framework_components_map.values():
|
2025-09-17 00:09:47 +02:00
|
|
|
env.Depends(project_ld_script, component_config["lib"])
|
2016-11-20 12:59:28 +02:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
project_config = target_configs.get(project_target_name, {})
|
2020-04-16 21:57:06 +03:00
|
|
|
default_config = target_configs.get(default_config_name, {})
|
2020-03-05 11:18:07 +02:00
|
|
|
project_defines = get_app_defines(project_config)
|
2020-04-16 21:57:06 +03:00
|
|
|
project_flags = get_app_flags(project_config, default_config)
|
2020-03-05 11:18:07 +02:00
|
|
|
link_args = extract_link_args(elf_config)
|
|
|
|
|
app_includes = get_app_includes(elf_config)
|
2016-11-20 12:59:28 +02:00
|
|
|
|
|
|
|
|
#
|
|
|
|
|
# Compile bootloader
|
|
|
|
|
#
|
|
|
|
|
|
2024-12-14 12:42:42 +01:00
|
|
|
if flag_custom_sdkonfig == False:
|
|
|
|
|
env.Depends("$BUILD_DIR/$PROGNAME$PROGSUFFIX", build_bootloader(sdk_config))
|
2016-11-06 16:46:33 +02:00
|
|
|
|
2016-10-24 20:23:25 +03:00
|
|
|
#
|
2020-03-05 11:18:07 +02:00
|
|
|
# Target: ESP-IDF menuconfig
|
2016-10-24 20:23:25 +03:00
|
|
|
#
|
|
|
|
|
|
2020-09-01 21:26:55 +03:00
|
|
|
env.AddPlatformTarget(
|
|
|
|
|
"menuconfig",
|
|
|
|
|
None,
|
|
|
|
|
[env.VerboseAction(RunMenuconfig, "Running menuconfig...")],
|
|
|
|
|
"Run Menuconfig",
|
|
|
|
|
)
|
|
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
#
|
|
|
|
|
# Process main parts of the framework
|
|
|
|
|
#
|
|
|
|
|
|
2025-09-17 00:09:47 +02:00
|
|
|
# Get components to ignore from lib_ignore option
|
|
|
|
|
lib_ignore_components = get_lib_ignore_components()
|
|
|
|
|
if lib_ignore_components:
|
|
|
|
|
print(f"[ESP-IDF] Ignoring components based on lib_ignore: {', '.join(lib_ignore_components)}")
|
|
|
|
|
ignore_components_list = [project_target_name, *lib_ignore_components]
|
|
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
libs = find_lib_deps(
|
2025-09-17 00:09:47 +02:00
|
|
|
framework_components_map, elf_config, link_args, ignore_components_list
|
2017-02-19 21:23:04 +02:00
|
|
|
)
|
2016-11-13 21:42:12 +02:00
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
# Extra flags which need to be explicitly specified in LINKFLAGS section because SCons
|
|
|
|
|
# cannot merge them correctly
|
2024-07-30 12:14:08 +02:00
|
|
|
extra_flags = filter_args(
|
2025-01-12 18:01:20 +01:00
|
|
|
link_args["LINKFLAGS"],
|
|
|
|
|
[
|
|
|
|
|
"-T",
|
|
|
|
|
"-u",
|
|
|
|
|
"-Wl,--start-group",
|
|
|
|
|
"-Wl,--end-group",
|
|
|
|
|
"-Wl,--whole-archive",
|
|
|
|
|
"-Wl,--no-whole-archive",
|
|
|
|
|
],
|
2024-07-30 12:14:08 +02:00
|
|
|
)
|
2020-03-05 11:18:07 +02:00
|
|
|
link_args["LINKFLAGS"] = sorted(list(set(link_args["LINKFLAGS"]) - set(extra_flags)))
|
|
|
|
|
|
2022-06-14 14:53:04 +03:00
|
|
|
# remove the main linker script flags '-T memory.ld'
|
2020-03-05 11:18:07 +02:00
|
|
|
try:
|
2022-06-14 04:45:19 -07:00
|
|
|
ld_index = extra_flags.index("memory.ld")
|
2020-03-05 11:18:07 +02:00
|
|
|
extra_flags.pop(ld_index)
|
|
|
|
|
extra_flags.pop(ld_index - 1)
|
2025-09-17 00:09:47 +02:00
|
|
|
except (ValueError, IndexError):
|
2020-03-05 11:18:07 +02:00
|
|
|
print("Warning! Couldn't find the main linker script in the CMake code model.")
|
|
|
|
|
|
2020-04-16 22:01:56 +03:00
|
|
|
#
|
|
|
|
|
# Process project sources
|
|
|
|
|
#
|
|
|
|
|
|
2020-09-01 21:26:55 +03:00
|
|
|
|
2020-04-16 22:01:56 +03:00
|
|
|
# Remove project source files from following build stages as they're
|
|
|
|
|
# built as part of the framework
|
|
|
|
|
def _skip_prj_source_files(node):
|
2025-09-17 00:09:47 +02:00
|
|
|
project_src_resolved = Path(PROJECT_SRC_DIR).resolve()
|
|
|
|
|
node_path_resolved = Path(node.srcnode().get_path()).resolve()
|
|
|
|
|
try:
|
|
|
|
|
node_path_resolved.relative_to(project_src_resolved)
|
|
|
|
|
except ValueError:
|
|
|
|
|
return node
|
|
|
|
|
else:
|
2020-04-16 22:01:56 +03:00
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
env.AddBuildMiddleware(_skip_prj_source_files)
|
|
|
|
|
|
2022-06-14 14:53:04 +03:00
|
|
|
#
|
|
|
|
|
# Generate partition table
|
|
|
|
|
#
|
|
|
|
|
|
2025-09-17 00:09:47 +02:00
|
|
|
fwpartitions_dir = str(Path(FRAMEWORK_DIR) / "components" / "partition_table")
|
2022-06-14 14:53:04 +03:00
|
|
|
partitions_csv = board.get("build.partitions", "partitions_singleapp.csv")
|
2020-12-02 13:10:37 +02:00
|
|
|
partition_table_offset = sdk_config.get("PARTITION_TABLE_OFFSET", 0x8000)
|
2022-06-14 14:53:04 +03:00
|
|
|
|
|
|
|
|
env.Replace(
|
|
|
|
|
PARTITIONS_TABLE_CSV=os.path.abspath(
|
2025-09-17 00:09:47 +02:00
|
|
|
str(Path(fwpartitions_dir) / partitions_csv)
|
|
|
|
|
if os.path.isfile(str(Path(fwpartitions_dir) / partitions_csv))
|
2022-06-14 14:53:04 +03:00
|
|
|
else partitions_csv
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
partition_table = env.Command(
|
2025-09-17 00:09:47 +02:00
|
|
|
str(Path("$BUILD_DIR") / "partitions.bin"),
|
2022-06-14 14:53:04 +03:00
|
|
|
"$PARTITIONS_TABLE_CSV",
|
|
|
|
|
env.VerboseAction(
|
2023-01-31 21:51:01 +02:00
|
|
|
'"$ESPIDF_PYTHONEXE" "%s" -q --offset "%s" --flash-size "%s" $SOURCE $TARGET'
|
2022-06-14 14:53:04 +03:00
|
|
|
% (
|
2025-09-17 00:09:47 +02:00
|
|
|
str(Path(FRAMEWORK_DIR) / "components" / "partition_table" / "gen_esp32part.py"),
|
2022-06-14 14:53:04 +03:00
|
|
|
partition_table_offset,
|
|
|
|
|
board.get("upload.flash_size", "4MB"),
|
|
|
|
|
),
|
|
|
|
|
"Generating partitions $TARGET",
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
env.Depends("$BUILD_DIR/$PROGNAME$PROGSUFFIX", partition_table)
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
|
# Main environment configuration
|
|
|
|
|
#
|
|
|
|
|
|
2020-03-05 11:18:07 +02:00
|
|
|
project_flags.update(link_args)
|
|
|
|
|
env.MergeFlags(project_flags)
|
|
|
|
|
env.Prepend(
|
|
|
|
|
CPPPATH=app_includes["plain_includes"],
|
2020-04-27 17:37:01 +03:00
|
|
|
CPPDEFINES=project_defines,
|
2023-01-31 21:51:01 +02:00
|
|
|
ESPIDF_PYTHONEXE=get_python_exe(),
|
2020-03-05 11:18:07 +02:00
|
|
|
LINKFLAGS=extra_flags,
|
|
|
|
|
LIBS=libs,
|
|
|
|
|
FLASH_EXTRA_IMAGES=[
|
2020-09-15 13:12:59 +03:00
|
|
|
(
|
2021-06-23 12:32:55 +03:00
|
|
|
board.get(
|
2022-06-14 14:53:04 +03:00
|
|
|
"upload.bootloader_offset",
|
2025-07-03 17:12:25 +02:00
|
|
|
"0x1000" if mcu in ["esp32", "esp32s2"] else ("0x2000" if mcu in ["esp32c5", "esp32p4"] else "0x0"),
|
2021-06-23 12:32:55 +03:00
|
|
|
),
|
2025-09-17 00:09:47 +02:00
|
|
|
str(Path("$BUILD_DIR") / "bootloader.bin"),
|
2020-09-15 13:12:59 +03:00
|
|
|
),
|
|
|
|
|
(
|
2020-12-02 13:10:37 +02:00
|
|
|
board.get("upload.partition_table_offset", hex(partition_table_offset)),
|
2025-09-17 00:09:47 +02:00
|
|
|
str(Path("$BUILD_DIR") / "partitions.bin"),
|
2020-09-15 13:12:59 +03:00
|
|
|
),
|
2020-03-05 11:18:07 +02:00
|
|
|
],
|
|
|
|
|
)
|
2019-09-16 13:11:52 +03:00
|
|
|
|
2022-07-14 21:16:39 +03:00
|
|
|
#
|
|
|
|
|
# Propagate Arduino defines to the main build environment
|
|
|
|
|
#
|
|
|
|
|
|
|
|
|
|
if "arduino" in env.subst("$PIOFRAMEWORK"):
|
2025-09-17 00:09:47 +02:00
|
|
|
arduino_candidates = [n for n in target_configs if n.startswith("__idf_framework-arduinoespressif32")]
|
|
|
|
|
if arduino_candidates:
|
|
|
|
|
arduino_cfg = target_configs.get(arduino_candidates[0], {})
|
|
|
|
|
cg_list = arduino_cfg.get("compileGroups", [])
|
|
|
|
|
if cg_list:
|
|
|
|
|
env.AppendUnique(CPPDEFINES=extract_defines(cg_list[0]))
|
2022-07-14 21:16:39 +03:00
|
|
|
|
|
|
|
|
# Project files should be compiled only when a special
|
|
|
|
|
# option is enabled when running 'test' command
|
|
|
|
|
if "__test" not in COMMAND_LINE_TARGETS or env.GetProjectOption(
|
|
|
|
|
"test_build_project_src"
|
|
|
|
|
):
|
|
|
|
|
project_env = env.Clone()
|
|
|
|
|
if project_target_name != "__idf_main":
|
|
|
|
|
# Manually add dependencies to CPPPATH since ESP-IDF build system doesn't generate
|
|
|
|
|
# this info if the folder with sources is not named 'main'
|
|
|
|
|
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html#rename-main
|
|
|
|
|
project_env.AppendUnique(CPPPATH=app_includes["plain_includes"])
|
|
|
|
|
|
|
|
|
|
# Add include dirs from PlatformIO build system to project CPPPATH so
|
|
|
|
|
# they're visible to PIOBUILDFILES
|
|
|
|
|
project_env.AppendUnique(
|
2023-08-03 13:38:40 +03:00
|
|
|
CPPPATH=["$PROJECT_INCLUDE_DIR", "$PROJECT_SRC_DIR", "$PROJECT_DIR"]
|
2022-07-14 21:16:39 +03:00
|
|
|
+ get_project_lib_includes(env)
|
|
|
|
|
)
|
|
|
|
|
|
2023-07-21 15:56:22 +03:00
|
|
|
project_env.ProcessFlags(env.get("SRC_BUILD_FLAGS"))
|
2022-07-14 21:16:39 +03:00
|
|
|
env.Append(
|
|
|
|
|
PIOBUILDFILES=compile_source_files(
|
|
|
|
|
target_configs.get(project_target_name),
|
|
|
|
|
project_env,
|
|
|
|
|
project_env.subst("$PROJECT_DIR"),
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
2021-01-21 13:42:23 +02:00
|
|
|
#
|
|
|
|
|
# Generate mbedtls bundle
|
|
|
|
|
#
|
|
|
|
|
|
|
|
|
|
if sdk_config.get("MBEDTLS_CERTIFICATE_BUNDLE", False):
|
|
|
|
|
generate_mbedtls_bundle(sdk_config)
|
|
|
|
|
|
2023-12-08 12:52:08 +02:00
|
|
|
#
|
|
|
|
|
# Check if flash size is set correctly in the IDF configuration file
|
|
|
|
|
#
|
|
|
|
|
|
|
|
|
|
board_flash_size = board.get("upload.flash_size", "4MB")
|
|
|
|
|
idf_flash_size = sdk_config.get("ESPTOOLPY_FLASHSIZE", "4MB")
|
|
|
|
|
if board_flash_size != idf_flash_size:
|
|
|
|
|
print(
|
|
|
|
|
"Warning! Flash memory size mismatch detected. Expected %s, found %s!"
|
|
|
|
|
% (board_flash_size, idf_flash_size)
|
|
|
|
|
)
|
|
|
|
|
print(
|
|
|
|
|
"Please select a proper value in your `sdkconfig.defaults` "
|
|
|
|
|
"or via the `menuconfig` target!"
|
|
|
|
|
)
|
|
|
|
|
|
2020-03-19 12:07:58 +02:00
|
|
|
#
|
|
|
|
|
# To embed firmware checksum a special argument for esptool.py is required
|
|
|
|
|
#
|
|
|
|
|
|
2023-12-08 12:50:47 +02:00
|
|
|
extra_elf2bin_flags = "--elf-sha256-offset 0xb0"
|
|
|
|
|
# https://github.com/espressif/esp-idf/blob/master/components/esptool_py/project_include.cmake#L58
|
|
|
|
|
# For chips that support configurable MMU page size feature
|
|
|
|
|
# If page size is configured to values other than the default "64KB" in menuconfig,
|
|
|
|
|
mmu_page_size = "64KB"
|
2025-07-03 17:12:25 +02:00
|
|
|
if sdk_config.get("MMU_PAGE_SIZE_8KB", False):
|
|
|
|
|
mmu_page_size = "8KB"
|
|
|
|
|
elif sdk_config.get("MMU_PAGE_SIZE_16KB", False):
|
|
|
|
|
mmu_page_size = "16KB"
|
|
|
|
|
elif sdk_config.get("MMU_PAGE_SIZE_32KB", False):
|
|
|
|
|
mmu_page_size = "32KB"
|
|
|
|
|
else:
|
|
|
|
|
mmu_page_size = "64KB"
|
|
|
|
|
|
2023-12-08 12:50:47 +02:00
|
|
|
if sdk_config.get("SOC_MMU_PAGE_SIZE_CONFIGURABLE", False):
|
|
|
|
|
if board_flash_size == "2MB":
|
|
|
|
|
mmu_page_size = "32KB"
|
|
|
|
|
elif board_flash_size == "1MB":
|
|
|
|
|
mmu_page_size = "16KB"
|
|
|
|
|
|
|
|
|
|
if mmu_page_size != "64KB":
|
|
|
|
|
extra_elf2bin_flags += " --flash-mmu-page-size %s" % mmu_page_size
|
|
|
|
|
|
2020-03-19 12:07:58 +02:00
|
|
|
action = copy.deepcopy(env["BUILDERS"]["ElfToBin"].action)
|
2023-12-08 12:50:47 +02:00
|
|
|
|
2020-03-19 12:07:58 +02:00
|
|
|
action.cmd_list = env["BUILDERS"]["ElfToBin"].action.cmd_list.replace(
|
2023-12-08 12:50:47 +02:00
|
|
|
"-o", extra_elf2bin_flags + " -o"
|
2020-09-01 21:26:55 +03:00
|
|
|
)
|
2020-03-19 12:07:58 +02:00
|
|
|
env["BUILDERS"]["ElfToBin"].action = action
|
|
|
|
|
|
2019-07-04 21:10:01 +03:00
|
|
|
#
|
2020-03-05 11:18:07 +02:00
|
|
|
# Compile ULP sources in 'ulp' folder
|
2019-07-04 21:10:01 +03:00
|
|
|
#
|
|
|
|
|
|
2025-09-17 00:09:47 +02:00
|
|
|
ulp_dir = str(Path(PROJECT_DIR) / "ulp")
|
2024-09-16 20:11:32 +02:00
|
|
|
if os.path.isdir(ulp_dir) and os.listdir(ulp_dir) and mcu not in ("esp32c2", "esp32c3", "esp32h2"):
|
|
|
|
|
env.SConscript("ulp.py", exports="env sdk_config project_config app_includes idf_variant")
|
2020-12-02 13:10:37 +02:00
|
|
|
|
|
|
|
|
#
|
2024-12-14 12:42:42 +01:00
|
|
|
# Compile Arduino IDF sources
|
|
|
|
|
#
|
|
|
|
|
|
2025-07-03 17:12:25 +02:00
|
|
|
if ("arduino" in env.subst("$PIOFRAMEWORK")) and ("espidf" not in env.subst("$PIOFRAMEWORK")):
|
2024-12-14 12:42:42 +01:00
|
|
|
def idf_lib_copy(source, target, env):
|
2025-09-17 00:09:47 +02:00
|
|
|
def _replace_move(src, dst):
|
|
|
|
|
dst_p = Path(dst)
|
|
|
|
|
dst_p.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
try:
|
|
|
|
|
os.remove(dst)
|
|
|
|
|
except FileNotFoundError:
|
|
|
|
|
pass
|
|
|
|
|
try:
|
|
|
|
|
os.replace(src, dst)
|
|
|
|
|
except OSError:
|
|
|
|
|
shutil.move(src, dst)
|
|
|
|
|
env_build = str(Path(env["PROJECT_BUILD_DIR"]) / env["PIOENV"])
|
|
|
|
|
sdkconfig_h_path = str(Path(env_build) / "config" / "sdkconfig.h")
|
|
|
|
|
arduino_libs = str(Path(ARDUINO_FRMWRK_LIB_DIR))
|
|
|
|
|
lib_src = str(Path(env_build) / "esp-idf")
|
|
|
|
|
lib_dst = str(Path(arduino_libs) / mcu / "lib")
|
|
|
|
|
ld_dst = str(Path(arduino_libs) / mcu / "ld")
|
|
|
|
|
mem_var = str(Path(arduino_libs) / mcu / board.get("build.arduino.memory_type", (board.get("build.flash_mode", "dio") + "_qspi")))
|
|
|
|
|
# Ensure destinations exist
|
|
|
|
|
for d in (lib_dst, ld_dst, mem_var, str(Path(mem_var) / "include")):
|
|
|
|
|
Path(d).mkdir(parents=True, exist_ok=True)
|
|
|
|
|
src = [str(Path(lib_src) / x) for x in os.listdir(lib_src)]
|
2024-12-14 12:42:42 +01:00
|
|
|
src = [folder for folder in src if not os.path.isfile(folder)] # folders only
|
|
|
|
|
for folder in src:
|
2025-09-17 00:09:47 +02:00
|
|
|
files = [str(Path(folder) / x) for x in os.listdir(folder)]
|
2024-12-14 12:42:42 +01:00
|
|
|
for file in files:
|
|
|
|
|
if file.strip().endswith(".a"):
|
2025-09-17 00:09:47 +02:00
|
|
|
shutil.copyfile(file, str(Path(lib_dst) / file.split(os.path.sep)[-1]))
|
2024-12-14 12:42:42 +01:00
|
|
|
|
2025-09-17 00:09:47 +02:00
|
|
|
_replace_move(str(Path(lib_dst) / "libspi_flash.a"), str(Path(mem_var) / "libspi_flash.a"))
|
|
|
|
|
_replace_move(str(Path(env_build) / "memory.ld"), str(Path(ld_dst) / "memory.ld"))
|
2024-12-14 12:42:42 +01:00
|
|
|
if mcu == "esp32s3":
|
2025-09-17 00:09:47 +02:00
|
|
|
_replace_move(str(Path(lib_dst) / "libesp_psram.a"), str(Path(mem_var) / "libesp_psram.a"))
|
|
|
|
|
_replace_move(str(Path(lib_dst) / "libesp_system.a"), str(Path(mem_var) / "libesp_system.a"))
|
|
|
|
|
_replace_move(str(Path(lib_dst) / "libfreertos.a"), str(Path(mem_var) / "libfreertos.a"))
|
|
|
|
|
_replace_move(str(Path(lib_dst) / "libbootloader_support.a"), str(Path(mem_var) / "libbootloader_support.a"))
|
|
|
|
|
_replace_move(str(Path(lib_dst) / "libesp_hw_support.a"), str(Path(mem_var) / "libesp_hw_support.a"))
|
|
|
|
|
_replace_move(str(Path(lib_dst) / "libesp_lcd.a"), str(Path(mem_var) / "libesp_lcd.a"))
|
|
|
|
|
|
|
|
|
|
shutil.copyfile(sdkconfig_h_path, str(Path(mem_var) / "include" / "sdkconfig.h"))
|
|
|
|
|
if not bool(os.path.isfile(str(Path(arduino_libs) / mcu / "sdkconfig.orig"))):
|
|
|
|
|
shutil.move(str(Path(arduino_libs) / mcu / "sdkconfig"), str(Path(arduino_libs) / mcu / "sdkconfig.orig"))
|
|
|
|
|
shutil.copyfile(str(Path(env.subst("$PROJECT_DIR")) / ("sdkconfig." + env["PIOENV"])), str(Path(arduino_libs) / mcu / "sdkconfig"))
|
|
|
|
|
shutil.copyfile(str(Path(env.subst("$PROJECT_DIR")) / ("sdkconfig." + env["PIOENV"])), str(Path(arduino_libs) / "sdkconfig"))
|
2025-02-28 18:17:11 +01:00
|
|
|
try:
|
2025-09-17 00:09:47 +02:00
|
|
|
os.remove(str(Path(env.subst("$PROJECT_DIR")) / "dependencies.lock"))
|
|
|
|
|
os.remove(str(Path(env.subst("$PROJECT_DIR")) / "CMakeLists.txt"))
|
|
|
|
|
except FileNotFoundError:
|
2025-02-28 18:17:11 +01:00
|
|
|
pass
|
2025-09-17 00:09:47 +02:00
|
|
|
except OSError as e:
|
|
|
|
|
print(f"Warning: cleanup failed: {e}")
|
2024-12-14 12:42:42 +01:00
|
|
|
print("*** Copied compiled %s IDF libraries to Arduino framework ***" % idf_variant)
|
|
|
|
|
|
2025-09-17 00:09:47 +02:00
|
|
|
PYTHON_EXE = env.subst("$PYTHONEXE")
|
|
|
|
|
pio_exe_path = str(Path(os.path.dirname(PYTHON_EXE)) / ("pio" + (".exe" if IS_WINDOWS else "")))
|
2024-12-14 12:42:42 +01:00
|
|
|
pio_cmd = env["PIOENV"]
|
|
|
|
|
env.Execute(
|
|
|
|
|
env.VerboseAction(
|
|
|
|
|
(
|
|
|
|
|
'"%s" run -e ' % pio_exe_path
|
|
|
|
|
+ " ".join(['"%s"' % pio_cmd])
|
|
|
|
|
),
|
|
|
|
|
"*** Starting Arduino compile %s with custom libraries ***" % pio_cmd,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
if flag_custom_component_add == True or flag_custom_component_remove == True:
|
|
|
|
|
try:
|
2025-09-17 00:09:47 +02:00
|
|
|
shutil.copy(str(Path(ARDUINO_FRAMEWORK_DIR) / "idf_component.yml.orig"), str(Path(ARDUINO_FRAMEWORK_DIR) / "idf_component.yml"))
|
2024-12-14 12:42:42 +01:00
|
|
|
print("*** Original Arduino \"idf_component.yml\" restored ***")
|
2025-09-17 00:09:47 +02:00
|
|
|
except (FileNotFoundError, PermissionError, OSError):
|
2024-12-14 12:42:42 +01:00
|
|
|
print("*** Original Arduino \"idf_component.yml\" couldnt be restored ***")
|
2025-07-03 17:12:25 +02:00
|
|
|
# Restore original pioarduino-build.py
|
|
|
|
|
from component_manager import ComponentManager
|
|
|
|
|
component_manager = ComponentManager(env)
|
|
|
|
|
component_manager.restore_pioarduino_build_py()
|
|
|
|
|
silent_action = create_silent_action(idf_lib_copy)
|
|
|
|
|
env.AddPostAction("checkprogsize", silent_action)
|
|
|
|
|
|
|
|
|
|
if "espidf" in env.subst("$PIOFRAMEWORK") and (flag_custom_component_add == True or flag_custom_component_remove == True):
|
2024-12-14 12:42:42 +01:00
|
|
|
def idf_custom_component(source, target, env):
|
|
|
|
|
try:
|
2025-09-17 00:09:47 +02:00
|
|
|
if "arduino" in env.subst("$PIOFRAMEWORK"):
|
|
|
|
|
shutil.copy(str(Path(ARDUINO_FRAMEWORK_DIR) / "idf_component.yml.orig"),
|
|
|
|
|
str(Path(ARDUINO_FRAMEWORK_DIR) / "idf_component.yml"))
|
|
|
|
|
print("*** Original Arduino \"idf_component.yml\" restored ***")
|
|
|
|
|
except (FileNotFoundError, PermissionError, OSError):
|
2024-12-14 12:42:42 +01:00
|
|
|
try:
|
2025-09-17 00:09:47 +02:00
|
|
|
shutil.copy(str(Path(PROJECT_SRC_DIR) / "idf_component.yml.orig"), str(Path(PROJECT_SRC_DIR) / "idf_component.yml"))
|
2024-12-14 12:42:42 +01:00
|
|
|
print("*** Original \"idf_component.yml\" restored ***")
|
2025-09-17 00:09:47 +02:00
|
|
|
except (FileNotFoundError, PermissionError, OSError): # no "idf_component.yml" in source folder
|
2024-12-14 12:42:42 +01:00
|
|
|
try:
|
2025-09-17 00:09:47 +02:00
|
|
|
os.remove(str(Path(PROJECT_SRC_DIR) / "idf_component.yml"))
|
2024-12-14 12:42:42 +01:00
|
|
|
print("*** pioarduino generated \"idf_component.yml\" removed ***")
|
2025-09-17 00:09:47 +02:00
|
|
|
except (FileNotFoundError, PermissionError, OSError):
|
2025-07-03 17:12:25 +02:00
|
|
|
print("*** no custom \"idf_component.yml\" found for removing ***")
|
|
|
|
|
if "arduino" in env.subst("$PIOFRAMEWORK"):
|
|
|
|
|
# Restore original pioarduino-build.py, only used with Arduino
|
|
|
|
|
from component_manager import ComponentManager
|
|
|
|
|
component_manager = ComponentManager(env)
|
|
|
|
|
component_manager.restore_pioarduino_build_py()
|
|
|
|
|
silent_action = create_silent_action(idf_custom_component)
|
|
|
|
|
env.AddPostAction("checkprogsize", silent_action)
|
2025-09-17 00:09:47 +02:00
|
|
|
|
2024-12-14 12:42:42 +01:00
|
|
|
#
|
2020-12-02 13:10:37 +02:00
|
|
|
# Process OTA partition and image
|
|
|
|
|
#
|
|
|
|
|
|
|
|
|
|
ota_partition_params = get_partition_info(
|
|
|
|
|
env.subst("$PARTITIONS_TABLE_CSV"),
|
|
|
|
|
partition_table_offset,
|
|
|
|
|
{"name": "ota", "type": "data", "subtype": "ota"},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if ota_partition_params["size"] and ota_partition_params["offset"]:
|
|
|
|
|
# Generate an empty image if OTA is enabled in partition table
|
2025-09-17 00:09:47 +02:00
|
|
|
ota_partition_image = str(Path("$BUILD_DIR") / "ota_data_initial.bin")
|
2024-12-14 12:42:42 +01:00
|
|
|
if "arduino" in env.subst("$PIOFRAMEWORK"):
|
2025-09-17 00:09:47 +02:00
|
|
|
ota_partition_image = str(Path(ARDUINO_FRAMEWORK_DIR) / "tools" / "partitions" / "boot_app0.bin")
|
2024-12-14 12:42:42 +01:00
|
|
|
else:
|
|
|
|
|
generate_empty_partition_image(ota_partition_image, ota_partition_params["size"])
|
2020-12-02 13:10:37 +02:00
|
|
|
|
|
|
|
|
env.Append(
|
|
|
|
|
FLASH_EXTRA_IMAGES=[
|
|
|
|
|
(
|
|
|
|
|
board.get(
|
|
|
|
|
"upload.ota_partition_offset", ota_partition_params["offset"]
|
|
|
|
|
),
|
|
|
|
|
ota_partition_image,
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
)
|
2025-09-17 00:09:47 +02:00
|
|
|
extra_imgs = board.get("upload.arduino.flash_extra_images", [])
|
|
|
|
|
if extra_imgs:
|
|
|
|
|
extra_img_dir = Path(env.subst("$PROJECT_DIR")) / "variants" / "tasmota"
|
|
|
|
|
env.Append(
|
|
|
|
|
FLASH_EXTRA_IMAGES=[(offset, str(extra_img_dir / img)) for offset, img in extra_imgs]
|
|
|
|
|
)
|
2024-12-14 12:42:42 +01:00
|
|
|
|
|
|
|
|
def _parse_size(value):
|
|
|
|
|
if isinstance(value, int):
|
|
|
|
|
return value
|
|
|
|
|
elif value.isdigit():
|
|
|
|
|
return int(value)
|
|
|
|
|
elif value.startswith("0x"):
|
|
|
|
|
return int(value, 16)
|
|
|
|
|
elif value[-1].upper() in ("K", "M"):
|
|
|
|
|
base = 1024 if value[-1].upper() == "K" else 1024 * 1024
|
|
|
|
|
return int(value[:-1]) * base
|
|
|
|
|
return value
|
2020-12-02 13:10:37 +02:00
|
|
|
|
|
|
|
|
#
|
|
|
|
|
# Configure application partition offset
|
|
|
|
|
#
|
|
|
|
|
|
2025-09-17 00:09:47 +02:00
|
|
|
app_offset = get_app_partition_offset(
|
|
|
|
|
env.subst("$PARTITIONS_TABLE_CSV"),
|
|
|
|
|
partition_table_offset
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
env.Replace(ESP32_APP_OFFSET=app_offset)
|
2020-12-02 13:10:37 +02:00
|
|
|
|
2024-12-14 12:42:42 +01:00
|
|
|
#
|
2020-12-02 13:10:37 +02:00
|
|
|
# Propagate application offset to debug configurations
|
2024-12-14 12:42:42 +01:00
|
|
|
#
|
|
|
|
|
|
2023-01-04 20:05:31 +02:00
|
|
|
env["INTEGRATION_EXTRA_DATA"].update(
|
|
|
|
|
{"application_offset": env.subst("$ESP32_APP_OFFSET")}
|
|
|
|
|
)
|