Skip to content

Commit 35bd721

Browse files
authored
Implement framework version extraction and preprocessing
Added a function to extract and normalize the ESP-IDF framework version from the CMake version file and environment variables. Updated linker script preprocessing to support both IDF 5.x and 6.x.
1 parent d82480b commit 35bd721

File tree

1 file changed

+185
-47
lines changed

1 file changed

+185
-47
lines changed

builder/frameworks/espidf.py

Lines changed: 185 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,47 @@
104104
env.Exit(1)
105105

106106

107+
def get_framework_version():
108+
def _extract_from_cmake_version_file():
109+
version_cmake_file = str(Path(FRAMEWORK_DIR) / "tools" / "cmake" / "version.cmake")
110+
if not os.path.isfile(version_cmake_file):
111+
return
112+
113+
with open(version_cmake_file, encoding="utf8") as fp:
114+
pattern = r"set\(IDF_VERSION_(MAJOR|MINOR|PATCH) (\d+)\)"
115+
matches = re.findall(pattern, fp.read())
116+
if len(matches) != 3:
117+
return
118+
# If found all three parts of the version
119+
return ".".join([match[1] for match in matches])
120+
121+
pkg = platform.get_package("framework-espidf")
122+
version = get_original_version(str(pkg.metadata.version.truncate()))
123+
if not version:
124+
# Fallback value extracted directly from the cmake version file
125+
version = _extract_from_cmake_version_file()
126+
if not version:
127+
version = "0.0.0"
128+
129+
# Normalize to semver (handles "6.0.0-rc1", VCS metadata, etc.)
130+
try:
131+
coerced = semantic_version.Version.coerce(version, partial=True)
132+
major = coerced.major or 0
133+
minor = coerced.minor or 0
134+
patch = coerced.patch or 0
135+
return f"{major}.{minor}.{patch}"
136+
except (ValueError, TypeError):
137+
m = re.match(r"(\d+)\.(\d+)\.(\d+)", str(version))
138+
return ".".join(m.groups()) if m else "0.0.0"
139+
140+
141+
# Configure ESP-IDF version environment variables
142+
framework_version = get_framework_version()
143+
_mv = framework_version.split(".")
144+
major_version = f"{_mv[0]}.{_mv[1] if len(_mv) > 1 else '0'}"
145+
os.environ["ESP_IDF_VERSION"] = major_version
146+
147+
107148
def create_silent_action(action_func):
108149
"""Create a silent SCons action that suppresses output"""
109150
silent_action = env.Action(action_func)
@@ -945,8 +986,8 @@ def generate_project_ld_script(sdk_config, ignore_targets=None):
945986

946987
initial_ld_script = str(Path(FRAMEWORK_DIR) / "components" / "esp_system" / "ld" / idf_variant / "sections.ld.in")
947988

948-
framework_version = [int(v) for v in get_framework_version().split(".")]
949-
if framework_version[:2] > [5, 2]:
989+
framework_version_list = [int(v) for v in get_framework_version().split(".")]
990+
if framework_version_list[:2] > [5, 2]:
950991
initial_ld_script = preprocess_linker_file(
951992
initial_ld_script,
952993
str(Path(BUILD_DIR) / "esp-idf" / "esp_system" / "ld" / "sections.ld.in"),
@@ -1187,6 +1228,9 @@ def build_bootloader(sdk_config):
11871228
"-DPROJECT_SOURCE_DIR=" + PROJECT_DIR,
11881229
"-DLEGACY_INCLUDE_COMMON_HEADERS=",
11891230
"-DEXTRA_COMPONENT_DIRS=" + str(Path(FRAMEWORK_DIR) / "components" / "bootloader"),
1231+
f"-DESP_IDF_VERSION={major_version}",
1232+
f"-DESP_IDF_VERSION_MAJOR={framework_version.split('.')[0]}",
1233+
f"-DESP_IDF_VERSION_MINOR={framework_version.split('.')[1]}",
11901234
],
11911235
)
11921236

@@ -1227,7 +1271,84 @@ def build_bootloader(sdk_config):
12271271
)
12281272

12291273
bootloader_env.MergeFlags(link_args)
1230-
bootloader_env.Append(LINKFLAGS=extra_flags)
1274+
1275+
# Handle ESP-IDF 6.0 linker script preprocessing for .ld.in files
1276+
# In bootloader context, only .ld.in templates exist and need preprocessing
1277+
processed_extra_flags = []
1278+
1279+
# Bootloader preprocessing configuration
1280+
bootloader_config_dir = str(Path(BUILD_DIR) / "bootloader" / "config")
1281+
bootloader_extra_includes = [
1282+
str(Path(FRAMEWORK_DIR) / "components" / "bootloader" / "subproject" / "main" / "ld" / idf_variant)
1283+
]
1284+
1285+
i = 0
1286+
while i < len(extra_flags):
1287+
if extra_flags[i] == "-T" and i + 1 < len(extra_flags):
1288+
linker_script = extra_flags[i + 1]
1289+
1290+
# Process .ld.in templates directly
1291+
if linker_script.endswith(".ld.in"):
1292+
script_name = os.path.basename(linker_script).replace(".ld.in", ".ld")
1293+
target_script = str(Path(BUILD_DIR) / "bootloader" / script_name)
1294+
1295+
preprocessed_script = preprocess_linker_file(
1296+
linker_script,
1297+
target_script,
1298+
config_dir=bootloader_config_dir,
1299+
extra_include_dirs=bootloader_extra_includes
1300+
)
1301+
1302+
bootloader_env.Depends("$BUILD_DIR/bootloader.elf", preprocessed_script)
1303+
processed_extra_flags.extend(["-T", target_script])
1304+
# Handle .ld files - prioritize using original scripts when available
1305+
elif linker_script.endswith(".ld"):
1306+
script_basename = os.path.basename(linker_script)
1307+
1308+
# Check if the original .ld file exists in framework and use it directly
1309+
original_script_path = str(Path(FRAMEWORK_DIR) / "components" / "bootloader" / "subproject" / "main" / "ld" / idf_variant / script_basename)
1310+
1311+
if os.path.isfile(original_script_path):
1312+
# Use the original script directly - no preprocessing needed
1313+
processed_extra_flags.extend(["-T", original_script_path])
1314+
else:
1315+
# Only generate from template if no original .ld file exists
1316+
script_name_in = script_basename.replace(".ld", ".ld.in")
1317+
bootloader_script_in_path = str(Path(FRAMEWORK_DIR) / "components" / "bootloader" / "subproject" / "main" / "ld" / idf_variant / script_name_in)
1318+
1319+
# ESP32-P4 specific: Check for bootloader.rev3.ld.in
1320+
if idf_variant == "esp32p4" and script_basename == "bootloader.ld":
1321+
sdk_config = get_sdk_configuration()
1322+
if sdk_config.get("ESP32P4_REV_MIN_300", False):
1323+
bootloader_rev3_path = str(Path(FRAMEWORK_DIR) / "components" / "bootloader" / "subproject" / "main" / "ld" / idf_variant / "bootloader.rev3.ld.in")
1324+
if os.path.isfile(bootloader_rev3_path):
1325+
bootloader_script_in_path = bootloader_rev3_path
1326+
1327+
# Preprocess the .ld.in template to generate the .ld file
1328+
if os.path.isfile(bootloader_script_in_path):
1329+
target_script = str(Path(BUILD_DIR) / "bootloader" / script_basename)
1330+
1331+
preprocessed_script = preprocess_linker_file(
1332+
bootloader_script_in_path,
1333+
target_script,
1334+
config_dir=bootloader_config_dir,
1335+
extra_include_dirs=bootloader_extra_includes
1336+
)
1337+
1338+
bootloader_env.Depends("$BUILD_DIR/bootloader.elf", preprocessed_script)
1339+
processed_extra_flags.extend(["-T", target_script])
1340+
else:
1341+
# Pass through if neither original nor template found (e.g., ROM scripts)
1342+
processed_extra_flags.extend(["-T", linker_script])
1343+
else:
1344+
# Pass through any other linker flags unchanged
1345+
processed_extra_flags.extend(["-T", linker_script])
1346+
i += 2
1347+
else:
1348+
processed_extra_flags.append(extra_flags[i])
1349+
i += 1
1350+
1351+
bootloader_env.Append(LINKFLAGS=processed_extra_flags)
12311352
bootloader_libs = find_lib_deps(components_map, elf_config, link_args)
12321353

12331354
bootloader_env.Prepend(__RPATH="-Wl,--start-group ")
@@ -1323,31 +1444,6 @@ def find_default_component(target_configs):
13231444
env.Exit(1)
13241445

13251446

1326-
def get_framework_version():
1327-
def _extract_from_cmake_version_file():
1328-
version_cmake_file = str(Path(FRAMEWORK_DIR) / "tools" / "cmake" / "version.cmake")
1329-
if not os.path.isfile(version_cmake_file):
1330-
return
1331-
1332-
with open(version_cmake_file, encoding="utf8") as fp:
1333-
pattern = r"set\(IDF_VERSION_(MAJOR|MINOR|PATCH) (\d+)\)"
1334-
matches = re.findall(pattern, fp.read())
1335-
if len(matches) != 3:
1336-
return
1337-
# If found all three parts of the version
1338-
return ".".join([match[1] for match in matches])
1339-
1340-
pkg = platform.get_package("framework-espidf")
1341-
version = get_original_version(str(pkg.metadata.version.truncate()))
1342-
if not version:
1343-
# Fallback value extracted directly from the cmake version file
1344-
version = _extract_from_cmake_version_file()
1345-
if not version:
1346-
version = "0.0.0"
1347-
1348-
return version
1349-
1350-
13511447
def create_version_file():
13521448
version_file = str(Path(FRAMEWORK_DIR) / "version.txt")
13531449
if not os.path.isfile(version_file):
@@ -1432,26 +1528,73 @@ def get_app_partition_offset(pt_table, pt_offset):
14321528
return factory_app_params.get("offset", "0x10000")
14331529

14341530

1435-
def preprocess_linker_file(src_ld_script, target_ld_script):
1436-
return env.Command(
1437-
target_ld_script,
1438-
src_ld_script,
1439-
env.VerboseAction(
1440-
" ".join(
1441-
[
1531+
def preprocess_linker_file(src_ld_script, target_ld_script, config_dir=None, extra_include_dirs=None):
1532+
"""
1533+
Preprocess a linker script file (.ld.in) to generate the final .ld file.
1534+
Supports both IDF 5.x (linker_script_generator.cmake) and IDF 6.x (linker_script_preprocessor.cmake).
1535+
1536+
Args:
1537+
src_ld_script: Source .ld.in file path
1538+
target_ld_script: Target .ld file path
1539+
config_dir: Configuration directory (defaults to BUILD_DIR/config for main app)
1540+
extra_include_dirs: Additional include directories (list)
1541+
"""
1542+
if config_dir is None:
1543+
config_dir = str(Path(BUILD_DIR) / "config")
1544+
1545+
# Convert all paths to forward slashes for CMake compatibility on Windows
1546+
config_dir = fs.to_unix_path(config_dir)
1547+
src_ld_script = fs.to_unix_path(src_ld_script)
1548+
target_ld_script = fs.to_unix_path(target_ld_script)
1549+
1550+
# Check IDF version to determine which CMake script to use
1551+
framework_version_list = [int(v) for v in get_framework_version().split(".")]
1552+
1553+
# IDF 6.0+ uses linker_script_preprocessor.cmake with CFLAGS approach
1554+
if framework_version_list[0] >= 6:
1555+
include_dirs = [f'"{config_dir}"']
1556+
include_dirs.append(f'"{fs.to_unix_path(str(Path(FRAMEWORK_DIR) / "components" / "esp_system" / "ld"))}"')
1557+
1558+
if extra_include_dirs:
1559+
include_dirs.extend(f'"{fs.to_unix_path(dir_path)}"' for dir_path in extra_include_dirs)
1560+
1561+
cflags_value = "-I" + " -I".join(include_dirs)
1562+
1563+
return env.Command(
1564+
target_ld_script,
1565+
src_ld_script,
1566+
env.VerboseAction(
1567+
" ".join([
1568+
f'"{CMAKE_DIR}"',
1569+
f'-DCC="{fs.to_unix_path(str(Path(TOOLCHAIN_DIR) / "bin" / "$CC"))}"',
1570+
f'-DSOURCE="{src_ld_script}"',
1571+
f'-DTARGET="{target_ld_script}"',
1572+
f'-DCFLAGS="{cflags_value}"',
1573+
"-P",
1574+
f'"{fs.to_unix_path(str(Path(FRAMEWORK_DIR) / "tools" / "cmake" / "linker_script_preprocessor.cmake"))}"',
1575+
]),
1576+
"Generating LD script $TARGET",
1577+
),
1578+
)
1579+
else:
1580+
# IDF 5.x: Use legacy linker_script_generator.cmake method
1581+
return env.Command(
1582+
target_ld_script,
1583+
src_ld_script,
1584+
env.VerboseAction(
1585+
" ".join([
14421586
f'"{CMAKE_DIR}"',
14431587
f'-DCC="{str(Path(TOOLCHAIN_DIR) / "bin" / "$CC")}"',
14441588
"-DSOURCE=$SOURCE",
14451589
"-DTARGET=$TARGET",
1446-
f'-DCONFIG_DIR="{str(Path(BUILD_DIR) / "config")}"',
1590+
f'-DCONFIG_DIR="{config_dir}"',
14471591
f'-DLD_DIR="{str(Path(FRAMEWORK_DIR) / "components" / "esp_system" / "ld")}"',
14481592
"-P",
14491593
f'"{str(Path("$BUILD_DIR") / "esp-idf" / "esp_system" / "ld" / "linker_script_generator.cmake")}"',
1450-
]
1594+
]),
1595+
"Generating LD script $TARGET",
14511596
),
1452-
"Generating LD script $TARGET",
1453-
),
1454-
)
1597+
)
14551598

14561599

14571600
def generate_mbedtls_bundle(sdk_config):
@@ -1696,8 +1839,8 @@ def get_python_exe():
16961839
if not board.get("build.ldscript", ""):
16971840
initial_ld_script = board.get("build.esp-idf.ldscript", str(Path(FRAMEWORK_DIR) / "components" / "esp_system" / "ld" / idf_variant / "memory.ld.in"))
16981841

1699-
framework_version = [int(v) for v in get_framework_version().split(".")]
1700-
if framework_version[:2] > [5, 2]:
1842+
framework_version_list = [int(v) for v in get_framework_version().split(".")]
1843+
if framework_version_list[:2] > [5, 2]:
17011844
initial_ld_script = preprocess_linker_file(
17021845
initial_ld_script,
17031846
str(Path(BUILD_DIR) / "esp-idf" / "esp_system" / "ld" / "memory.ld.in")
@@ -1757,11 +1900,6 @@ def get_python_exe():
17571900
LIBSOURCE_DIRS=[str(Path(ARDUINO_FRAMEWORK_DIR) / "libraries")]
17581901
)
17591902

1760-
# Configure ESP-IDF version environment variables for Kconfig processing
1761-
framework_version = get_framework_version()
1762-
major_version = framework_version.split('.')[0] + '.' + framework_version.split('.')[1]
1763-
os.environ["ESP_IDF_VERSION"] = major_version
1764-
17651903
# Setup CMake configuration arguments
17661904
extra_cmake_args = [
17671905
"-DIDF_TARGET=" + idf_variant,

0 commit comments

Comments
 (0)