Skip to content

Commit 1118ba0

Browse files
authored
prepare IDF 6: add changed linker script handling (#302)
Refactor ESP-IDF version handling and linker script preprocessing.
1 parent 5c22d9e commit 1118ba0

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
@@ -105,6 +105,47 @@
105105
env.Exit(1)
106106

107107

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

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

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

@@ -1228,7 +1272,84 @@ def build_bootloader(sdk_config):
12281272
)
12291273

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

12341355
bootloader_env.Prepend(__RPATH="-Wl,--start-group ")
@@ -1324,31 +1445,6 @@ def find_default_component(target_configs):
13241445
env.Exit(1)
13251446

13261447

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

14351531

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

14571600

14581601
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)