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
2020-09-01 21:26:55 +03:00
import os
2025-10-08 18:57:41 +02:00
import platform as sys_platform
2024-05-10 13:50:54 +03:00
import re
2024-12-14 12:42:42 +01:00
import requests
2025-10-08 18:57:41 +02:00
import shutil
import subprocess
import sys
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 " )
2025-10-08 18:57:41 +02:00
flash_frequency = str ( flash_speed . replace ( " 000000L " , " " ) )
2024-12-14 12:42:42 +01:00
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-10-08 18:57:41 +02:00
def get_framework_version ( ) :
def _extract_from_cmake_version_file ( ) :
version_cmake_file = str ( Path ( FRAMEWORK_DIR ) / " tools " / " cmake " / " version.cmake " )
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 "
# Normalize to semver (handles "6.0.0-rc1", VCS metadata, etc.)
try :
coerced = semantic_version . Version . coerce ( version , partial = True )
major = coerced . major or 0
minor = coerced . minor or 0
patch = coerced . patch or 0
return f " { major } . { minor } . { patch } "
except ( ValueError , TypeError ) :
m = re . match ( r " ( \ d+) \ .( \ d+) \ .( \ d+) " , str ( version ) )
return " . " . join ( m . groups ( ) ) if m else " 0.0.0 "
# Configure ESP-IDF version environment variables
framework_version = get_framework_version ( )
_mv = framework_version . split ( " . " )
major_version = f " { _mv [ 0 ] } . { _mv [ 1 ] if len ( _mv ) > 1 else ' 0 ' } "
os . environ [ " ESP_IDF_VERSION " ] = major_version
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
2025-10-08 18:57:41 +02:00
# Check for board-specific configurations that require sdkconfig generation
def has_board_specific_config ( ) :
""" Check if board has configuration that needs to be applied to sdkconfig. """
# Check for PSRAM support
extra_flags = board . get ( " build.extra_flags " , [ ] )
has_psram = any ( " -DBOARD_HAS_PSRAM " in flag for flag in extra_flags )
# Check for special memory types
memory_type = None
build_section = board . get ( " build " , { } )
arduino_section = build_section . get ( " arduino " , { } )
if " memory_type " in arduino_section :
memory_type = arduino_section [ " memory_type " ]
elif " memory_type " in build_section :
memory_type = build_section [ " memory_type " ]
has_special_memory = memory_type and ( " opi " in memory_type . lower ( ) )
return has_psram or has_special_memory
if has_board_specific_config ( ) :
flag_custom_sdkonfig = True
2024-12-14 12:42:42 +01:00
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-10-08 18:57:41 +02:00
def generate_board_specific_config ( ) :
""" Generate board-specific sdkconfig settings from board.json manifest. """
board_config_flags = [ ]
# Handle memory type configuration with platformio.ini override support
# Priority: platformio.ini > board.json manifest
memory_type = None
# Check for memory_type override in platformio.ini
if hasattr ( env , ' GetProjectOption ' ) :
try :
memory_type = env . GetProjectOption ( " board_build.memory_type " , None )
except :
pass
# Fallback to board.json manifest
if not memory_type :
build_section = board . get ( " build " , { } )
arduino_section = build_section . get ( " arduino " , { } )
if " memory_type " in arduino_section :
memory_type = arduino_section [ " memory_type " ]
elif " memory_type " in build_section :
memory_type = build_section [ " memory_type " ]
flash_memory_type = None
psram_memory_type = None
if memory_type :
parts = memory_type . split ( " _ " )
if len ( parts ) == 2 :
flash_memory_type , psram_memory_type = parts
else :
flash_memory_type = memory_type
# Check for additional flash configuration indicators
boot_mode = board . get ( " build " , { } ) . get ( " boot " , None )
flash_mode = board . get ( " build " , { } ) . get ( " flash_mode " , None )
# Override flash_memory_type if boot mode indicates OPI
if boot_mode == " opi " or flash_mode in [ " dout " , " opi " ] :
if not flash_memory_type or flash_memory_type . lower ( ) != " opi " :
flash_memory_type = " opi "
print ( f " Info: Detected OPI Flash via boot_mode= ' { boot_mode } ' or flash_mode= ' { flash_mode } ' " )
# Set CPU frequency with platformio.ini override support
# Priority: platformio.ini > board.json manifest
f_cpu = None
if hasattr ( env , ' GetProjectOption ' ) :
# Check for board_build.f_cpu override in platformio.ini
try :
f_cpu = env . GetProjectOption ( " board_build.f_cpu " , None )
except :
pass
# Fallback to board.json manifest
if not f_cpu :
f_cpu = board . get ( " build.f_cpu " , None )
if f_cpu :
cpu_freq = str ( f_cpu ) . replace ( " 000000L " , " " )
board_config_flags . append ( f " CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ= { cpu_freq } " )
# Disable other CPU frequency options and enable the specific one
common_cpu_freqs = [ " 80 " , " 160 " , " 240 " ]
for freq in common_cpu_freqs :
if freq != cpu_freq :
if mcu == " esp32 " :
board_config_flags . append ( f " # CONFIG_ESP32_DEFAULT_CPU_FREQ_ { freq } is not set " )
elif mcu in [ " esp32s2 " , " esp32s3 " ] :
board_config_flags . append ( f " # CONFIG_ESP32S2_DEFAULT_CPU_FREQ_ { freq } is not set " if mcu == " esp32s2 " else f " # CONFIG_ESP32S3_DEFAULT_CPU_FREQ_ { freq } is not set " )
elif mcu in [ " esp32c2 " , " esp32c3 " , " esp32c6 " ] :
board_config_flags . append ( f " # CONFIG_ESP32C3_DEFAULT_CPU_FREQ_ { freq } is not set " )
# Enable the specific CPU frequency
if mcu == " esp32 " :
board_config_flags . append ( f " CONFIG_ESP32_DEFAULT_CPU_FREQ_ { cpu_freq } =y " )
elif mcu == " esp32s2 " :
board_config_flags . append ( f " CONFIG_ESP32S2_DEFAULT_CPU_FREQ_ { cpu_freq } =y " )
elif mcu == " esp32s3 " :
board_config_flags . append ( f " CONFIG_ESP32S3_DEFAULT_CPU_FREQ_ { cpu_freq } =y " )
elif mcu in [ " esp32c2 " , " esp32c3 " , " esp32c6 " ] :
board_config_flags . append ( f " CONFIG_ESP32C3_DEFAULT_CPU_FREQ_ { cpu_freq } =y " )
# Set flash size with platformio.ini override support
# Priority: platformio.ini > board.json manifest
flash_size = None
if hasattr ( env , ' GetProjectOption ' ) :
# Check for board_upload.flash_size override in platformio.ini
try :
flash_size = env . GetProjectOption ( " board_upload.flash_size " , None )
except :
pass
# Fallback to board.json manifest
if not flash_size :
flash_size = board . get ( " upload " , { } ) . get ( " flash_size " , None )
if flash_size :
# Configure both string and boolean flash size formats
# Disable other flash size options first
flash_sizes = [ " 4MB " , " 8MB " , " 16MB " , " 32MB " , " 64MB " , " 128MB " ]
for size in flash_sizes :
if size != flash_size :
board_config_flags . append ( f " # CONFIG_ESPTOOLPY_FLASHSIZE_ { size } is not set " )
# Set the specific flash size configs
board_config_flags . append ( f " CONFIG_ESPTOOLPY_FLASHSIZE= \" { flash_size } \" " )
board_config_flags . append ( f " CONFIG_ESPTOOLPY_FLASHSIZE_ { flash_size } =y " )
# Handle Flash and PSRAM frequency configuration with platformio.ini override support
# Priority: platformio.ini > board.json manifest
# From 80MHz onwards, Flash and PSRAM frequencies must be identical
# Get f_flash with override support
f_flash = None
if hasattr ( env , ' GetProjectOption ' ) :
try :
f_flash = env . GetProjectOption ( " board_build.f_flash " , None )
except :
pass
if not f_flash :
f_flash = board . get ( " build.f_flash " , None )
# Get f_boot with override support
f_boot = None
if hasattr ( env , ' GetProjectOption ' ) :
try :
f_boot = env . GetProjectOption ( " board_build.f_boot " , None )
except :
pass
if not f_boot :
f_boot = board . get ( " build.f_boot " , None )
# Determine the frequencies to use
esptool_flash_freq = f_flash # Always use f_flash for esptool compatibility
compile_freq = f_boot if f_boot else f_flash # Use f_boot for compile-time if available
if f_flash and compile_freq :
# Ensure frequency compatibility (>= 80MHz must be identical for Flash and PSRAM)
compile_freq_val = int ( str ( compile_freq ) . replace ( " 000000L " , " " ) )
if compile_freq_val > = 80 :
# Above 80MHz, both Flash and PSRAM must use same frequency
unified_freq = compile_freq_val
flash_freq_str = f " { unified_freq } m "
psram_freq_str = str ( unified_freq )
print ( f " Info: Unified frequency mode (>= 80MHz): { unified_freq } MHz for both Flash and PSRAM " )
else :
# Below 80MHz, frequencies can differ
flash_freq_str = str ( compile_freq ) . replace ( " 000000L " , " m " )
psram_freq_str = str ( compile_freq ) . replace ( " 000000L " , " " )
print ( f " Info: Independent frequency mode (< 80MHz): Flash= { flash_freq_str } , PSRAM= { psram_freq_str } " )
# Configure Flash frequency
# Disable other flash frequency options first
flash_freqs = [ " 20m " , " 26m " , " 40m " , " 80m " , " 120m " ]
for freq in flash_freqs :
if freq != flash_freq_str :
board_config_flags . append ( f " # CONFIG_ESPTOOLPY_FLASHFREQ_ { freq . upper ( ) } is not set " )
# Then set the specific frequency configs
board_config_flags . append ( f " CONFIG_ESPTOOLPY_FLASHFREQ= \" { flash_freq_str } \" " )
board_config_flags . append ( f " CONFIG_ESPTOOLPY_FLASHFREQ_ { flash_freq_str . upper ( ) } =y " )
# Configure PSRAM frequency (same as Flash for >= 80MHz)
# Disable other SPIRAM speed options first
psram_freqs = [ " 40 " , " 80 " , " 120 " ]
for freq in psram_freqs :
if freq != psram_freq_str :
board_config_flags . append ( f " # CONFIG_SPIRAM_SPEED_ { freq } M is not set " )
# Then set the specific SPIRAM configs
board_config_flags . append ( f " CONFIG_SPIRAM_SPEED= { psram_freq_str } " )
board_config_flags . append ( f " CONFIG_SPIRAM_SPEED_ { psram_freq_str } M=y " )
# Enable experimental features for frequencies > 80MHz
if compile_freq_val > 80 :
board_config_flags . append ( " CONFIG_IDF_EXPERIMENTAL_FEATURES=y " )
board_config_flags . append ( " CONFIG_SPI_FLASH_HPM_ENABLE=y " )
board_config_flags . append ( " CONFIG_SPI_FLASH_HPM_AUTO=y " )
# Check for PSRAM support based on board flags
extra_flags = board . get ( " build.extra_flags " , [ ] )
has_psram = any ( " -DBOARD_HAS_PSRAM " in flag for flag in extra_flags )
# Additional PSRAM detection methods
if not has_psram :
# Check if memory_type contains psram indicators
if memory_type and ( " opi " in memory_type . lower ( ) or " psram " in memory_type . lower ( ) ) :
has_psram = True
# Check build.psram_type
elif " psram_type " in board . get ( " build " , { } ) :
has_psram = True
# Check for SPIRAM mentions in extra_flags
elif any ( " SPIRAM " in str ( flag ) for flag in extra_flags ) :
has_psram = True
# For ESP32-S3, assume PSRAM capability (can be disabled later if not present)
elif mcu == " esp32s3 " :
has_psram = True
if has_psram :
# Enable basic SPIRAM support
board_config_flags . append ( " CONFIG_SPIRAM=y " )
# Determine PSRAM type with platformio.ini override support
# Priority: platformio.ini > memory_type > build.psram_type > default
psram_type = None
# Priority 1: Check for platformio.ini override
if hasattr ( env , ' GetProjectOption ' ) :
try :
psram_type = env . GetProjectOption ( " board_build.psram_type " , None )
if psram_type :
psram_type = psram_type . lower ( )
except :
pass
# Priority 2: Check psram_memory_type from memory_type field (e.g., "qio_opi")
if not psram_type and psram_memory_type :
psram_type = psram_memory_type . lower ( )
# Priority 3: Check build.psram_type field as fallback
elif not psram_type and " psram_type " in board . get ( " build " , { } ) :
psram_type = board . get ( " build.psram_type " , " qio " ) . lower ( )
# Priority 4: Default to qio
elif not psram_type :
psram_type = " qio "
# Configure PSRAM mode based on detected type
if psram_type == " opi " :
# Octal PSRAM configuration (for ESP32-S3 only)
if mcu == " esp32s3 " :
board_config_flags . extend ( [
" CONFIG_IDF_EXPERIMENTAL_FEATURES=y " ,
" # CONFIG_SPIRAM_MODE_QUAD is not set " ,
" CONFIG_SPIRAM_MODE_OCT=y " ,
" CONFIG_SPIRAM_TYPE_AUTO=y "
] )
else :
# Fallback to QUAD for non-S3 chips
board_config_flags . extend ( [
" # CONFIG_SPIRAM_MODE_OCT is not set " ,
" CONFIG_SPIRAM_MODE_QUAD=y "
] )
elif psram_type in [ " qio " , " qspi " ] :
# Quad PSRAM configuration
if mcu in [ " esp32s2 " , " esp32s3 " ] :
board_config_flags . extend ( [
" # CONFIG_SPIRAM_MODE_OCT is not set " ,
" CONFIG_SPIRAM_MODE_QUAD=y "
] )
elif mcu == " esp32 " :
board_config_flags . extend ( [
" # CONFIG_SPIRAM_MODE_OCT is not set " ,
" # CONFIG_SPIRAM_MODE_QUAD is not set "
] )
# Use flash_memory_type for flash config
if flash_memory_type and " opi " in flash_memory_type . lower ( ) :
# OPI Flash configurations require specific settings
board_config_flags . extend ( [
" # CONFIG_ESPTOOLPY_FLASHMODE_QIO is not set " ,
" # CONFIG_ESPTOOLPY_FLASHMODE_QOUT is not set " ,
" # CONFIG_ESPTOOLPY_FLASHMODE_DIO is not set " ,
" CONFIG_ESPTOOLPY_FLASHMODE_DOUT=y " ,
" CONFIG_ESPTOOLPY_OCT_FLASH=y " ,
" # CONFIG_ESPTOOLPY_FLASH_SAMPLE_MODE_STR is not set " ,
" CONFIG_ESPTOOLPY_FLASH_SAMPLE_MODE_DTR=y "
] )
return board_config_flags
2025-07-03 17:12:25 +02:00
def build_idf_config_flags ( ) :
""" Build complete IDF configuration flags from all sources. """
flags = [ ]
2025-10-08 18:57:41 +02:00
# FIRST: Add board-specific flags derived from board.json manifest
board_flags = generate_board_specific_config ( )
if board_flags :
flags . extend ( board_flags )
# SECOND: Add board-specific flags from board manifest (espidf.custom_sdkconfig)
2025-07-03 17:12:25 +02:00
if " espidf.custom_sdkconfig " in board :
2025-10-08 18:57:41 +02:00
board_manifest_flags = board . get ( " espidf.custom_sdkconfig " , [ ] )
if board_manifest_flags :
flags . extend ( board_manifest_flags )
2025-07-03 17:12:25 +02:00
2025-10-08 18:57:41 +02:00
# THIRD: Add custom sdkconfig file content
2025-07-03 17:12:25 +02:00
custom_file_content = load_custom_sdkconfig_file ( )
if custom_file_content :
flags . append ( custom_file_content )
2025-10-08 18:57:41 +02:00
# FOURTH: Add project-level custom sdkconfig (highest precedence for user overrides)
2025-07-03 17:12:25 +02:00
if config . has_option ( " env: " + env [ " PIOENV " ] , " custom_sdkconfig " ) :
custom_flags = env . GetProjectOption ( " custom_sdkconfig " ) . rstrip ( " \n " )
if custom_flags :
flags . append ( custom_flags )
2025-10-08 18:57:41 +02:00
# FIFTH: Apply ESP32-specific compatibility fixes
all_flags_str = " \n " . join ( flags ) + " \n " if flags else " "
esp32_compatibility_flags = apply_esp32_compatibility_fixes ( all_flags_str )
if esp32_compatibility_flags :
flags . extend ( esp32_compatibility_flags )
2025-07-03 17:12:25 +02:00
return " \n " . join ( flags ) + " \n " if flags else " "
2025-10-08 18:57:41 +02:00
def apply_esp32_compatibility_fixes ( config_flags_str ) :
""" Apply ESP32-specific compatibility fixes based on final configuration. """
compatibility_flags = [ ]
2025-07-03 17:12:25 +02:00
# ESP32 specific SPIRAM configuration
2025-10-08 18:57:41 +02:00
# On ESP32, SPIRAM is not used with UNICORE mode
if mcu == " esp32 " and " CONFIG_FREERTOS_UNICORE=y " in config_flags_str :
if " CONFIG_SPIRAM=y " in config_flags_str :
compatibility_flags . append ( " # CONFIG_SPIRAM is not set " )
print ( " Info: ESP32 SPIRAM disabled since solo1 core mode is enabled " )
2025-07-03 17:12:25 +02:00
2025-10-08 18:57:41 +02:00
return compatibility_flags
2025-07-03 17:12:25 +02:00
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
2025-10-08 18:57:41 +02:00
src_lines = src . readlines ( )
for line in src_lines :
2025-07-03 17:12:25 +02:00
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 " )
2025-10-08 18:57:41 +02:00
2025-07-03 17:12:25 +02:00
# Main execution logic
has_custom_config = (
config . has_option ( " env: " + env [ " PIOENV " ] , " custom_sdkconfig " ) or
" espidf.custom_sdkconfig " in board
)
2025-10-08 18:57:41 +02:00
has_board_config = has_board_specific_config ( )
if not has_custom_config and not has_board_config :
2024-12-14 12:42:42 +01:00
return
2025-07-03 17:12:25 +02:00
2025-10-08 18:57:41 +02:00
if has_board_config and not has_custom_config :
print ( " *** Apply board-specific settings to IDF sdkconfig.defaults *** " )
else :
print ( " *** Add \" custom_sdkconfig \" settings to IDF sdkconfig.defaults *** " )
2025-07-03 17:12:25 +02:00
# Build complete configuration
idf_config_flags = build_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
2025-10-08 18:57:41 +02:00
framework_version_list = [ int ( v ) for v in get_framework_version ( ) . split ( " . " ) ]
if framework_version_list [ : 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 " , [ ] ) :
2025-10-08 18:57:41 +02:00
src_path = source [ " path " ]
if src_path . endswith ( " .rule " ) :
continue
# Always skip dummy_src.c to avoid duplicate build actions
if os . path . basename ( src_path ) == " dummy_src.c " :
2020-03-05 11:18:07 +02:00
continue
compile_group_idx = source . get ( " compileGroupIndex " )
if compile_group_idx is not None :
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)
2025-10-08 18:57:41 +02:00
get_entries = getattr ( lib_handler , " get_lib_ignore_entries " , None )
lib_ignore_entries = (
get_entries ( ) if callable ( get_entries ) else lib_handler . _get_lib_ignore_entries ( )
)
2025-09-17 00:09:47 +02:00
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 " ) ,
2025-10-08 18:57:41 +02:00
f " -DESP_IDF_VERSION= { major_version } " ,
f " -DESP_IDF_VERSION_MAJOR= { framework_version . split ( ' . ' ) [ 0 ] } " ,
f " -DESP_IDF_VERSION_MINOR= { framework_version . split ( ' . ' ) [ 1 ] } " ,
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 )
2025-10-08 18:57:41 +02:00
# Handle ESP-IDF 6.0 linker script preprocessing for .ld.in files
# In bootloader context, only .ld.in templates exist and need preprocessing
processed_extra_flags = [ ]
# Bootloader preprocessing configuration
bootloader_config_dir = str ( Path ( BUILD_DIR ) / " bootloader " / " config " )
bootloader_extra_includes = [
str ( Path ( FRAMEWORK_DIR ) / " components " / " bootloader " / " subproject " / " main " / " ld " / idf_variant )
]
i = 0
while i < len ( extra_flags ) :
if extra_flags [ i ] == " -T " and i + 1 < len ( extra_flags ) :
linker_script = extra_flags [ i + 1 ]
# Process .ld.in templates directly
if linker_script . endswith ( " .ld.in " ) :
script_name = os . path . basename ( linker_script ) . replace ( " .ld.in " , " .ld " )
target_script = str ( Path ( BUILD_DIR ) / " bootloader " / script_name )
preprocessed_script = preprocess_linker_file (
linker_script ,
target_script ,
config_dir = bootloader_config_dir ,
extra_include_dirs = bootloader_extra_includes
)
bootloader_env . Depends ( " $BUILD_DIR/bootloader.elf " , preprocessed_script )
processed_extra_flags . extend ( [ " -T " , target_script ] )
# Handle .ld files - prioritize using original scripts when available
elif linker_script . endswith ( " .ld " ) :
script_basename = os . path . basename ( linker_script )
# Check if the original .ld file exists in framework and use it directly
original_script_path = str ( Path ( FRAMEWORK_DIR ) / " components " / " bootloader " / " subproject " / " main " / " ld " / idf_variant / script_basename )
if os . path . isfile ( original_script_path ) :
# Use the original script directly - no preprocessing needed
processed_extra_flags . extend ( [ " -T " , original_script_path ] )
else :
# Only generate from template if no original .ld file exists
script_name_in = script_basename . replace ( " .ld " , " .ld.in " )
bootloader_script_in_path = str ( Path ( FRAMEWORK_DIR ) / " components " / " bootloader " / " subproject " / " main " / " ld " / idf_variant / script_name_in )
# ESP32-P4 specific: Check for bootloader.rev3.ld.in
if idf_variant == " esp32p4 " and script_basename == " bootloader.ld " :
sdk_config = get_sdk_configuration ( )
if sdk_config . get ( " ESP32P4_REV_MIN_300 " , False ) :
bootloader_rev3_path = str ( Path ( FRAMEWORK_DIR ) / " components " / " bootloader " / " subproject " / " main " / " ld " / idf_variant / " bootloader.rev3.ld.in " )
if os . path . isfile ( bootloader_rev3_path ) :
bootloader_script_in_path = bootloader_rev3_path
# Preprocess the .ld.in template to generate the .ld file
if os . path . isfile ( bootloader_script_in_path ) :
target_script = str ( Path ( BUILD_DIR ) / " bootloader " / script_basename )
preprocessed_script = preprocess_linker_file (
bootloader_script_in_path ,
target_script ,
config_dir = bootloader_config_dir ,
extra_include_dirs = bootloader_extra_includes
)
bootloader_env . Depends ( " $BUILD_DIR/bootloader.elf " , preprocessed_script )
processed_extra_flags . extend ( [ " -T " , target_script ] )
else :
# Pass through if neither original nor template found (e.g., ROM scripts)
processed_extra_flags . extend ( [ " -T " , linker_script ] )
else :
# Pass through any other linker flags unchanged
processed_extra_flags . extend ( [ " -T " , linker_script ] )
i + = 2
else :
processed_extra_flags . append ( extra_flags [ i ] )
i + = 1
bootloader_env . Append ( LINKFLAGS = processed_extra_flags )
2020-03-05 11:18:07 +02:00
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. \n Check that the `EXTRA_COMPONENT_DIRS` option is not overridden "
" in your CMakeLists.txt. \n See 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
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
2025-10-08 18:57:41 +02:00
def preprocess_linker_file ( src_ld_script , target_ld_script , config_dir = None , extra_include_dirs = None ) :
"""
Preprocess a linker script file (.ld.in) to generate the final .ld file.
Supports both IDF 5.x (linker_script_generator.cmake) and IDF 6.x (linker_script_preprocessor.cmake).
Args:
src_ld_script: Source .ld.in file path
target_ld_script: Target .ld file path
config_dir: Configuration directory (defaults to BUILD_DIR/config for main app)
extra_include_dirs: Additional include directories (list)
"""
if config_dir is None :
config_dir = str ( Path ( BUILD_DIR ) / " config " )
# Convert all paths to forward slashes for CMake compatibility on Windows
config_dir = fs . to_unix_path ( config_dir )
src_ld_script = fs . to_unix_path ( src_ld_script )
target_ld_script = fs . to_unix_path ( target_ld_script )
# Check IDF version to determine which CMake script to use
framework_version_list = [ int ( v ) for v in get_framework_version ( ) . split ( " . " ) ]
# IDF 6.0+ uses linker_script_preprocessor.cmake with CFLAGS approach
if framework_version_list [ 0 ] > = 6 :
include_dirs = [ f ' " { config_dir } " ' ]
include_dirs . append ( f ' " { fs . to_unix_path ( str ( Path ( FRAMEWORK_DIR ) / " components " / " esp_system " / " ld " ) ) } " ' )
if extra_include_dirs :
include_dirs . extend ( f ' " { fs . to_unix_path ( dir_path ) } " ' for dir_path in extra_include_dirs )
cflags_value = " -I " + " -I " . join ( include_dirs )
return env . Command (
target_ld_script ,
src_ld_script ,
env . VerboseAction (
" " . join ( [
f ' " { CMAKE_DIR } " ' ,
f ' -DCC= " { fs . to_unix_path ( str ( Path ( TOOLCHAIN_DIR ) / " bin " / " $CC " ) ) } " ' ,
f ' -DSOURCE= " { src_ld_script } " ' ,
f ' -DTARGET= " { target_ld_script } " ' ,
f ' -DCFLAGS= " { cflags_value } " ' ,
" -P " ,
f ' " { fs . to_unix_path ( str ( Path ( FRAMEWORK_DIR ) / " tools " / " cmake " / " linker_script_preprocessor.cmake " ) ) } " ' ,
] ) ,
" Generating LD script $TARGET " ,
) ,
)
else :
# IDF 5.x: Use legacy linker_script_generator.cmake method
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-10-08 18:57:41 +02:00
f ' -DCONFIG_DIR= " { config_dir } " ' ,
2025-09-17 00:09:47 +02:00
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 " ) } " ' ,
2025-10-08 18:57:41 +02:00
] ) ,
" Generating LD script $TARGET " ,
2024-07-30 14:44:37 +02:00
) ,
2025-10-08 18:57:41 +02:00
)
2024-07-30 14:44:37 +02:00
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-10-08 18:57:41 +02:00
" pydantic " : " ~=2.11.10 " ,
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
2025-10-08 18:57:41 +02:00
# ESP-IDF package version is determined from version.h file
# since the package distribution doesn't include .git metadata
2020-09-01 21:26:55 +03:00
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
2025-10-08 18:57:41 +02:00
framework_version_list = [ int ( v ) for v in get_framework_version ( ) . split ( " . " ) ]
if framework_version_list [ : 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
#
2025-10-08 18:57:41 +02:00
# Known build system 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-10-08 18:57:41 +02:00
# Setup CMake configuration arguments
2025-07-03 17:12:25 +02:00
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
#
2025-10-08 18:57:41 +02:00
# ESP-IDF menuconfig target implementation
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 " :
2025-10-08 18:57:41 +02:00
# Add dependencies to CPPPATH for non-main source directories
# ESP-IDF build system requires manual dependency handling for custom source folders
2022-07-14 21:16:39 +03:00
# 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 "
2025-10-08 18:57:41 +02:00
# Reference: ESP-IDF esptool_py component configuration
2023-12-08 12:50:47 +02:00
# 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 " ) }
)