penv setup moved in platform (#296)

This commit is contained in:
Jason2866
2025-10-08 18:57:41 +02:00
committed by GitHub
parent 85062ff9e3
commit dabbde41f9
9 changed files with 933 additions and 269 deletions
+4 -4
View File
@@ -22,10 +22,10 @@ kinds of creative coding, interactive objects, spaces or physical experiences.
http://arduino.cc/en/Reference/HomePage
"""
import os
import sys
import shutil
import hashlib
import os
import shutil
import sys
import threading
from contextlib import suppress
from os.path import join, exists, isabs, splitdrive, commonpath, relpath
@@ -886,7 +886,7 @@ if check_reinstall_frwrk():
if flag_custom_sdkconfig and not flag_any_custom_sdkconfig:
call_compile_libs()
# Main logic for Arduino Framework
# Arduino framework configuration and build logic
pioframework = env.subst("$PIOFRAMEWORK")
arduino_lib_compile_flag = env.subst("$ARDUINO_LIB_COMPILE_FLAG")
+2 -2
View File
@@ -12,9 +12,9 @@ import os
import shutil
import re
import yaml
from yaml import SafeLoader
from pathlib import Path
from typing import Set, Optional, Dict, Any, List, Tuple, Pattern
from yaml import SafeLoader
class ComponentManagerConfig:
@@ -252,7 +252,7 @@ class ComponentHandler:
Returns:
Absolute path to the component YAML file
"""
# Try Arduino framework first
# Check Arduino framework directory first
afd = self.config.arduino_framework_dir
framework_yml = str(Path(afd) / "idf_component.yml") if afd else ""
if framework_yml and os.path.exists(framework_yml):
+539 -91
View File
@@ -23,14 +23,13 @@ https://github.com/espressif/esp-idf
import copy
import importlib.util
import json
import subprocess
import sys
import shutil
import os
from os.path import join
import platform as sys_platform
import re
import requests
import platform as sys_platform
import shutil
import subprocess
import sys
from pathlib import Path
from urllib.parse import urlsplit, unquote
@@ -79,7 +78,7 @@ config = env.GetProjectConfig()
board = env.BoardConfig()
mcu = board.get("build.mcu", "esp32")
flash_speed = board.get("build.f_flash", "40000000L")
flash_frequency = str(flash_speed.replace("000000L", "m"))
flash_frequency = str(flash_speed.replace("000000L", ""))
flash_mode = board.get("build.flash_mode", "dio")
idf_variant = mcu.lower()
flag_custom_sdkonfig = False
@@ -105,6 +104,47 @@ if not TOOLCHAIN_DIR or not os.path.isdir(TOOLCHAIN_DIR):
env.Exit(1)
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
def create_silent_action(action_func):
"""Create a silent SCons action that suppresses output"""
silent_action = env.Action(action_func)
@@ -178,6 +218,29 @@ if config.has_option("env:"+env["PIOENV"], "custom_sdkconfig"):
if "espidf.custom_sdkconfig" in board:
flag_custom_sdkonfig = True
# 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
def HandleArduinoIDFsettings(env):
"""
Handles Arduino IDF settings configuration with custom sdkconfig support.
@@ -244,48 +307,322 @@ def HandleArduinoIDFsettings(env):
return line.split("=")[0]
return None
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
def build_idf_config_flags():
"""Build complete IDF configuration flags from all sources."""
flags = []
# Add board-specific flags first
if "espidf.custom_sdkconfig" in board:
board_flags = board.get("espidf.custom_sdkconfig", [])
if board_flags:
flags.extend(board_flags)
# FIRST: Add board-specific flags derived from board.json manifest
board_flags = generate_board_specific_config()
if board_flags:
flags.extend(board_flags)
# Add custom sdkconfig file content
# SECOND: Add board-specific flags from board manifest (espidf.custom_sdkconfig)
if "espidf.custom_sdkconfig" in board:
board_manifest_flags = board.get("espidf.custom_sdkconfig", [])
if board_manifest_flags:
flags.extend(board_manifest_flags)
# THIRD: Add custom sdkconfig file content
custom_file_content = load_custom_sdkconfig_file()
if custom_file_content:
flags.append(custom_file_content)
# Add project-level custom sdkconfig
# FOURTH: Add project-level custom sdkconfig (highest precedence for user overrides)
if config.has_option("env:" + env["PIOENV"], "custom_sdkconfig"):
custom_flags = env.GetProjectOption("custom_sdkconfig").rstrip("\n")
if custom_flags:
flags.append(custom_flags)
# 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)
return "\n".join(flags) + "\n" if flags else ""
def add_flash_configuration(config_flags):
"""Add flash frequency and mode configuration."""
if flash_frequency != "80m":
config_flags += "# CONFIG_ESPTOOLPY_FLASHFREQ_80M is not set\n"
config_flags += f"CONFIG_ESPTOOLPY_FLASHFREQ_{flash_frequency.upper()}=y\n"
config_flags += f"CONFIG_ESPTOOLPY_FLASHFREQ=\"{flash_frequency}\"\n"
if flash_mode != "qio":
config_flags += "# CONFIG_ESPTOOLPY_FLASHMODE_QIO is not set\n"
flash_mode_flag = f"CONFIG_ESPTOOLPY_FLASHMODE_{flash_mode.upper()}=y\n"
if flash_mode_flag not in config_flags:
config_flags += flash_mode_flag
def apply_esp32_compatibility_fixes(config_flags_str):
"""Apply ESP32-specific compatibility fixes based on final configuration."""
compatibility_flags = []
# ESP32 specific SPIRAM configuration
if mcu == "esp32" and "CONFIG_FREERTOS_UNICORE=y" in config_flags:
config_flags += "# CONFIG_SPIRAM is not set\n"
# 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")
return config_flags
return compatibility_flags
def write_sdkconfig_file(idf_config_flags, checksum_source):
if "arduino" not in env.subst("$PIOFRAMEWORK"):
@@ -306,7 +643,9 @@ def HandleArduinoIDFsettings(env):
dst.write(f"# TASMOTA__{checksum}\n")
# Process each line from source sdkconfig
for line in src:
src_lines = src.readlines()
for line in src_lines:
flag_name = extract_flag_name(line)
if flag_name is None:
@@ -335,20 +674,25 @@ def HandleArduinoIDFsettings(env):
print(f"Add: {cleaned_flag}")
dst.write(cleaned_flag + "\n")
# Main execution logic
has_custom_config = (
config.has_option("env:" + env["PIOENV"], "custom_sdkconfig") or
"espidf.custom_sdkconfig" in board
)
if not has_custom_config:
has_board_config = has_board_specific_config()
if not has_custom_config and not has_board_config:
return
print("*** Add \"custom_sdkconfig\" settings to IDF sdkconfig.defaults ***")
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 ***")
# Build complete configuration
idf_config_flags = build_idf_config_flags()
idf_config_flags = add_flash_configuration(idf_config_flags)
# Convert to list for processing
idf_config_list = [line for line in idf_config_flags.splitlines() if line.strip()]
@@ -946,8 +1290,8 @@ def generate_project_ld_script(sdk_config, ignore_targets=None):
initial_ld_script = str(Path(FRAMEWORK_DIR) / "components" / "esp_system" / "ld" / idf_variant / "sections.ld.in")
framework_version = [int(v) for v in get_framework_version().split(".")]
if framework_version[:2] > [5, 2]:
framework_version_list = [int(v) for v in get_framework_version().split(".")]
if framework_version_list[:2] > [5, 2]:
initial_ld_script = preprocess_linker_file(
initial_ld_script,
str(Path(BUILD_DIR) / "esp-idf" / "esp_system" / "ld" / "sections.ld.in"),
@@ -1023,11 +1367,14 @@ def compile_source_files(
# Canonical, symlink-resolved absolute path of the components directory
components_dir_path = (Path(FRAMEWORK_DIR) / "components").resolve()
for source in config.get("sources", []):
if source["path"].endswith(".rule"):
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":
continue
compile_group_idx = source.get("compileGroupIndex")
if compile_group_idx is not None:
src_path = source.get("path")
if not os.path.isabs(src_path):
# For cases when sources are located near CMakeLists.txt
src_path = str(Path(project_src_dir) / src_path)
@@ -1130,7 +1477,10 @@ def get_lib_ignore_components():
lib_handler = _component_manager.LibraryIgnoreHandler(config, logger)
# Get the processed lib_ignore entries (already converted to component names)
lib_ignore_entries = lib_handler._get_lib_ignore_entries()
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()
)
return lib_ignore_entries
except (OSError, ValueError, RuntimeError, KeyError) as e:
@@ -1182,6 +1532,9 @@ def build_bootloader(sdk_config):
"-DPROJECT_SOURCE_DIR=" + PROJECT_DIR,
"-DLEGACY_INCLUDE_COMMON_HEADERS=",
"-DEXTRA_COMPONENT_DIRS=" + str(Path(FRAMEWORK_DIR) / "components" / "bootloader"),
f"-DESP_IDF_VERSION={major_version}",
f"-DESP_IDF_VERSION_MAJOR={framework_version.split('.')[0]}",
f"-DESP_IDF_VERSION_MINOR={framework_version.split('.')[1]}",
],
)
@@ -1222,7 +1575,84 @@ def build_bootloader(sdk_config):
)
bootloader_env.MergeFlags(link_args)
bootloader_env.Append(LINKFLAGS=extra_flags)
# 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)
bootloader_libs = find_lib_deps(components_map, elf_config, link_args)
bootloader_env.Prepend(__RPATH="-Wl,--start-group ")
@@ -1318,31 +1748,6 @@ def find_default_component(target_configs):
env.Exit(1)
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"
return version
def create_version_file():
version_file = str(Path(FRAMEWORK_DIR) / "version.txt")
if not os.path.isfile(version_file):
@@ -1427,26 +1832,73 @@ def get_app_partition_offset(pt_table, pt_offset):
return factory_app_params.get("offset", "0x10000")
def preprocess_linker_file(src_ld_script, target_ld_script):
return env.Command(
target_ld_script,
src_ld_script,
env.VerboseAction(
" ".join(
[
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([
f'"{CMAKE_DIR}"',
f'-DCC="{str(Path(TOOLCHAIN_DIR) / "bin" / "$CC")}"',
"-DSOURCE=$SOURCE",
"-DTARGET=$TARGET",
f'-DCONFIG_DIR="{str(Path(BUILD_DIR) / "config")}"',
f'-DCONFIG_DIR="{config_dir}"',
f'-DLD_DIR="{str(Path(FRAMEWORK_DIR) / "components" / "esp_system" / "ld")}"',
"-P",
f'"{str(Path("$BUILD_DIR") / "esp-idf" / "esp_system" / "ld" / "linker_script_generator.cmake")}"',
]
]),
"Generating LD script $TARGET",
),
"Generating LD script $TARGET",
),
)
)
def generate_mbedtls_bundle(sdk_config):
@@ -1530,6 +1982,7 @@ def install_python_deps():
# https://github.com/platformio/platform-espressif32/issues/635
"cryptography": "~=44.0.0",
"pyparsing": ">=3.1.0,<4",
"pydantic": "~=2.11.10",
"idf-component-manager": "~=2.2",
"esp-idf-kconfig": "~=2.5.0"
}
@@ -1672,8 +2125,8 @@ def get_python_exe():
ensure_python_venv_available()
# ESP-IDF package doesn't contain .git folder, instead package version is specified
# in a special file "version.h" in the root folder of the package
# ESP-IDF package version is determined from version.h file
# since the package distribution doesn't include .git metadata
create_version_file()
@@ -1690,8 +2143,8 @@ generate_default_component()
if not board.get("build.ldscript", ""):
initial_ld_script = board.get("build.esp-idf.ldscript", str(Path(FRAMEWORK_DIR) / "components" / "esp_system" / "ld" / idf_variant / "memory.ld.in"))
framework_version = [int(v) for v in get_framework_version().split(".")]
if framework_version[:2] > [5, 2]:
framework_version_list = [int(v) for v in get_framework_version().split(".")]
if framework_version_list[:2] > [5, 2]:
initial_ld_script = preprocess_linker_file(
initial_ld_script,
str(Path(BUILD_DIR) / "esp-idf" / "esp_system" / "ld" / "memory.ld.in")
@@ -1712,7 +2165,7 @@ if not board.get("build.ldscript", ""):
#
# Current build script limitations
# Known build system limitations
#
if any(" " in p for p in (FRAMEWORK_DIR, BUILD_DIR)):
@@ -1751,12 +2204,7 @@ if "arduino" in env.subst("$PIOFRAMEWORK"):
LIBSOURCE_DIRS=[str(Path(ARDUINO_FRAMEWORK_DIR) / "libraries")]
)
# Set ESP-IDF version environment variables (needed for proper Kconfig processing)
framework_version = get_framework_version()
major_version = framework_version.split('.')[0] + '.' + framework_version.split('.')[1]
os.environ["ESP_IDF_VERSION"] = major_version
# Configure CMake arguments with ESP-IDF version
# Setup CMake configuration arguments
extra_cmake_args = [
"-DIDF_TARGET=" + idf_variant,
"-DPYTHON_DEPS_CHECKED=1",
@@ -1850,7 +2298,7 @@ if flag_custom_sdkonfig == False:
env.Depends("$BUILD_DIR/$PROGNAME$PROGSUFFIX", build_bootloader(sdk_config))
#
# Target: ESP-IDF menuconfig
# ESP-IDF menuconfig target implementation
#
env.AddPlatformTarget(
@@ -1995,8 +2443,8 @@ if "__test" not in COMMAND_LINE_TARGETS or env.GetProjectOption(
):
project_env = env.Clone()
if project_target_name != "__idf_main":
# Manually add dependencies to CPPPATH since ESP-IDF build system doesn't generate
# this info if the folder with sources is not named 'main'
# Add dependencies to CPPPATH for non-main source directories
# ESP-IDF build system requires manual dependency handling for custom source folders
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html#rename-main
project_env.AppendUnique(CPPPATH=app_includes["plain_includes"])
@@ -2044,7 +2492,7 @@ if board_flash_size != idf_flash_size:
#
extra_elf2bin_flags = "--elf-sha256-offset 0xb0"
# https://github.com/espressif/esp-idf/blob/master/components/esptool_py/project_include.cmake#L58
# Reference: ESP-IDF esptool_py component configuration
# 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"